# Introduzione Source: https://picomcp.github.io/picomcp/it/docs/ > Cos’è picomcp, cosa non è, e le dieci righe che lo spiegano. picomcp è un framework TypeScript per costruire server [Model Context Protocol](https://modelcontextprotocol.io). Un server MCP dà a un’applicazione di IA (Claude, Cursor, VS Code, il tuo agente) dei **tool** da chiamare, delle **risorse** da leggere e dei **prompt** da proporre all’utente. ```ts import { pico } from "picomcp"; import { z } from "zod"; pico({ name: "weather" }) .tool("forecast", { description: "Get the forecast for a city.", input: z.object({ city: z.string() }), run: ({ city }) => getForecast(city), }) .serve(); ``` Questo è un server completo. Avvialo con `node server.ts` e collegalo a qualsiasi client MCP. ## Perché picomcp - **Piccolo.** Circa 4 kB compresso con gzip, zero dipendenze. L’intera libreria ti sta in testa e in una code review. - **Tipizzato dal tuo schema.** Passa uno schema Zod, Valibot o ArkType. Il tuo handler riceve argomenti tipizzati, il client riceve un JSON Schema, le chiamate non valide ricevono un errore leggibile. - **Gira ovunque.** Node 22+, Bun, Deno, Cloudflare Workers e qualsiasi runtime con `fetch`. Nessun modulo integrato di Node nel nucleo. - **Due trasporti, un solo server.** stdio per i client desktop, Streamable HTTP per i server remoti. Si passa dall’uno all’altro con un’opzione. - **Testabile senza client.** `server.call("forecast", { city: "Lyon" })` esegue un tool nel processo, completamente tipizzato. - **Verificato con l’SDK ufficiale.** La suite di test pilota picomcp con `@modelcontextprotocol/sdk` su entrambi i trasporti. ## Cosa non è picomcp picomcp implementa il lato server di MCP e nient’altro. Non include un client, un ciclo di agente né un SDK per LLM. Privilegia la metà senza stato, a richiesta/risposta, del protocollo: quella che serve alla maggior parte dei server. Le funzionalità avviate dal server, come sampling ed elicitation, non sono ancora supportate: l’elenco esatto è in [Copertura del protocollo](https://picomcp.github.io/picomcp/it/docs/protocol). ## Prossimi passi - [Avvio rapido](https://picomcp.github.io/picomcp/it/docs/quickstart): costruisci e collega un server in cinque minuti. - [Tool](https://picomcp.github.io/picomcp/it/docs/tools): tutto quello che un tool sa fare. - [Per gli agenti IA](https://picomcp.github.io/picomcp/it/docs/agents): dai la documentazione al tuo agente di programmazione con un solo URL. --- # Avvio rapido Source: https://picomcp.github.io/picomcp/it/docs/quickstart > Crea, testa e collega il tuo primo server MCP in cinque minuti. Ti serve Node 22.18 o successivo (esegue direttamente i file TypeScript), Bun o Deno. ## Crea un progetto ```sh npx picomcp init my-server cd my-server npm install ``` Ottieni un server pronto all’uso: | File | Scopo | | --- | --- | | `server.ts` | La definizione del server: tool, risorse, prompt | | `main.ts` | Avvia il server su stdio | | `server.test.ts` | Un test che chiama un tool nel processo | Preferisci partire da zero? Installa i due pacchetti e scrivi `server.ts` da solo: ```sh npm install picomcp zod ``` ## Scrivi un tool Apri `server.ts`: ```ts import { pico } from "picomcp"; import { z } from "zod"; export const server = pico({ name: "my-server", version: "0.1.0" }) .tool("greet", { description: "Greets someone by name.", input: z.object({ name: z.string().describe("Who to greet") }), run: ({ name }) => `Hello, ${name}!`, }); ``` - `description` è ciò che il modello legge per decidere quando chiamare il tool. Scrivila per il modello. - `input` è qualsiasi [Standard Schema](https://picomcp.github.io/picomcp/it/docs/schemas). Dentro `run`, `name` è tipizzato come `string`. - Qui `run` restituisce una stringa. Può anche restituire oggetti, blocchi di contenuto o lanciare un errore: vedi [Tool](https://picomcp.github.io/picomcp/it/docs/tools). ## Testalo ```ts import assert from "node:assert/strict"; import { test } from "node:test"; import { server } from "./server.ts"; test("greet says hello", async () => { const result = await server.call("greet", { name: "Ada" }); assert.deepEqual(result.content, [{ type: "text", text: "Hello, Ada!" }]); }); ``` ```sh npm test ``` `server.call` è controllato dal sistema di tipi: un refuso nel nome del tool o un argomento mancante non compila. ## Collegalo Per Claude Code: ```sh claude mcp add my-server -- node /absolute/path/to/my-server/main.ts ``` Poi chiedi a Claude di salutare qualcuno. Per Claude Desktop, Cursor, VS Code e altri, vedi [Collegare un client](https://picomcp.github.io/picomcp/it/docs/clients). ## Ispezionalo MCP Inspector ti permette di chiamare i tool a mano e vedere i messaggi grezzi: ```sh npx @modelcontextprotocol/inspector node main.ts ``` --- # Collegare un client Source: https://picomcp.github.io/picomcp/it/docs/clients > Configurazione per Claude Code, Claude Desktop, Cursor, VS Code e MCP Inspector. Tutti i client MCP avviano un server locale allo stesso modo: eseguono un comando e gli parlano via stdio. I server remoti si raggiungono tramite URL, con Streamable HTTP. Usa percorsi assoluti in tutte le configurazioni qui sotto. ## Claude Code ```sh # local, over stdio claude mcp add weather -- node /absolute/path/main.ts # remote, over HTTP claude mcp add --transport http weather https://example.com/mcp ``` Aggiungi `--scope project` per scrivere il server in `.mcp.json` e condividerlo con il tuo team. ## Claude Desktop Modifica `claude_desktop_config.json` (Impostazioni → Sviluppatore → Modifica configurazione), poi riavvia l’app: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] } } } ``` ## Cursor Crea `.cursor/mcp.json` nel tuo progetto, oppure `~/.cursor/mcp.json` per tutti i progetti: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "url": "https://example.com/mcp" } } } ``` ## VS Code Crea `.vscode/mcp.json`: ```json { "servers": { "weather": { "type": "stdio", "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "type": "http", "url": "https://example.com/mcp" } } } ``` ## MCP Inspector ```sh npx @modelcontextprotocol/inspector node /absolute/path/main.ts ``` Per ispezionare un server HTTP, avvialo con `serve({ http: 3000 })`, apri l’Inspector, scegli **Streamable HTTP** e inserisci `http://127.0.0.1:3000/mcp`. L’Inspector gira su localhost, che picomcp consente per impostazione predefinita. ## Bun e Deno Sostituisci `node` con il tuo runtime: ```json { "command": "bun", "args": ["/absolute/path/main.ts"] } ``` ```json { "command": "deno", "args": ["run", "--allow-net", "/absolute/path/main.ts"] } ``` ## Risoluzione dei problemi **Il client non mostra alcun tool.** Esegui tu stesso il comando della configurazione. Se termina o stampa un errore, il client vede la stessa cosa. **«Unexpected token» o errori JSON nei log del client.** Qualcosa ha scritto su stdout. picomcp reindirizza `console.log` su stderr quando serve via stdio, ma altre librerie potrebbero scrivere direttamente su `process.stdout`. **Node non riesce a eseguire i file `.ts`.** Aggiorna a Node 22.18 o successivo, oppure avvia il server con Bun. --- # Tool Source: https://picomcp.github.io/picomcp/it/docs/tools > Definire tool, validare l’input, restituire testo, oggetti o media, segnalare errori e gestire l’annullamento. Un tool è una funzione che il modello può chiamare. Registrane uno con `server.tool(name, config)`: ```ts server.tool("search_issues", { title: "Search issues", description: "Search open GitHub issues in a repository. Use it before filing a new issue.", input: z.object({ repo: z.string().describe("owner/name"), query: z.string(), limit: z.number().int().max(50).default(10), }), annotations: { readOnlyHint: true, openWorldHint: true }, run: async ({ repo, query, limit }, { signal }) => { const res = await fetch(`https://api.github.com/search/issues?q=${encodeURIComponent(`${query} repo:${repo}`)}&per_page=${limit}`, { signal }); const { items } = await res.json(); return items.map((i: { title: string; html_url: string }) => `${i.title} — ${i.html_url}`).join("\n"); }, }); ``` ## Configurazione | Opzione | Tipo | Descrizione | | --- | --- | --- | | `description` | `string` | Cosa fa il tool e quando usarlo. Il modello legge questo testo. | | `title` | `string` | Nome mostrato dai client. | | `input` | Standard Schema | Schema degli argomenti. Omettilo per i tool senza argomenti. | | `output` | Standard Schema | Schema del risultato strutturato. Vedi [Output strutturato](#output-strutturato). | | `annotations` | `ToolAnnotations` | Suggerimenti per i client: `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`. | | `run` | `(input, ctx) => value` | L’handler. Può essere asincrono. | I nomi dei tool vanno da 1 a 128 caratteri e usano solo lettere, cifre, `_`, `.` e `-`. Registrare un nome non valido o duplicato lancia subito un errore: gli sbagli emergono all’avvio del server, non quando un modello chiama il tool. ## Validazione dell’input picomcp valida gli argomenti con il tuo schema prima di chiamare `run`. Valori predefiniti e trasformazioni vengono applicati, quindi nell’esempio `limit` dentro `run` è sempre un numero. Se la validazione fallisce, il tool non viene chiamato. Il client riceve un errore del tool che nomina il campo sbagliato, così il modello può correggere la chiamata e riprovare: ```text Invalid arguments for tool "search_issues": limit: Too big: expected number to be <=50 ``` ## Valori di ritorno Restituisci ciò che è naturale. picomcp lo trasforma in un risultato MCP: | `run` restituisce | Il client riceve | | --- | --- | | `string` | Un blocco di testo | | un oggetto, un array o un numero | Il suo JSON come testo. Gli oggetti vengono inviati anche come `structuredContent`. | | un blocco di contenuto, o un array di blocchi | Quei blocchi così come sono | | `{ content: [...] }` | Il risultato così com’è, per il pieno controllo | | `undefined` | Un risultato vuoto | Costruisci i blocchi di contenuto con gli helper: ```ts import { audio, image, link, text } from "picomcp"; run: async () => [ text("Here is the chart:"), image(await renderPng(), "image/png"), // Uint8Array or base64 string link("file:///reports/q3.csv", "q3.csv", { mimeType: "text/csv" }), ]; ``` ## Output strutturato Dichiara uno schema `output` quando il risultato è usato da client o da codice, non solo dal modello: ```ts server.tool("get_price", { input: z.object({ sku: z.string() }), output: z.object({ sku: z.string(), cents: z.number().int(), currency: z.literal("EUR") }), run: async ({ sku }) => ({ sku, cents: await priceOf(sku), currency: "EUR" }), }); ``` picomcp allora: 1. annuncia lo schema come `outputSchema` in `tools/list`, 2. controlla con il sistema di tipi il valore restituito da `run`, 3. valida il valore a runtime e lo invia come `structuredContent`, con una copia JSON in testo per i client più vecchi. Se il valore non corrisponde, il client riceve un errore del tool invece di dati malformati. ## Errori Lancia un errore da `run`. Il messaggio diventa un errore del tool (`isError: true`) che il modello può leggere e a cui può reagire: ```ts run: async ({ id }) => { const user = await db.users.find(id); if (!user) throw new Error(`No user with id ${id}. Call list_users to see valid ids.`); return user; }; ``` Scrivi i messaggi di errore per il modello: di’ cosa è andato storto e cosa fare dopo. Gli errori dei tool fanno parte di una normale conversazione. Gli errori di protocollo no: usa `McpError` solo quando la richiesta stessa è sbagliata, per esempio quando l’utente non ha accesso: ```ts import { McpError } from "picomcp"; throw new McpError(-32001, "Not authorised for this workspace"); ``` ## Annullamento Il secondo argomento di `run` contiene un `AbortSignal`. Scatta quando il client invia `notifications/cancelled`, di solito perché l’utente ha premuto stop. Passalo a `fetch` e alle altre API che lo accettano: ```ts run: async ({ url }, { signal }) => { const res = await fetch(url, { signal }); return res.text(); }; ``` Una volta annullata una richiesta, picomcp non invia alcuna risposta, come richiede il protocollo. ## Metadati `ctx.meta` contiene l’oggetto `_meta` che il client ha inviato con la richiesta, se presente. I client lo usano per il tracing e per i token di avanzamento. ## Scrivere buoni tool - **Un compito per tool.** `create_invoice` e `send_invoice` sono meglio di `invoice(action)`. - **Descrivi gli argomenti.** `.describe()` su ogni campo finisce nel JSON Schema che vede il modello. - **Restituisci meno.** I modelli pagano ogni token. Restituisci i campi che contano, non l’intera risposta dell’API. - **Annota con onestà.** `readOnlyHint: true` permette ai client di saltare la conferma. Non metterlo mai su un tool che scrive. --- # Risorse Source: https://picomcp.github.io/picomcp/it/docs/resources > Esporre file, record e dati in tempo reale che il client può leggere, con template di URI tipizzati. Le risorse sono dati che il client può leggere e allegare a una conversazione: file, righe di database, risposte di API. I tool li chiama il modello; le risorse di solito le sceglie l’utente o l’applicazione. ```ts server.resource("config://app", { description: "The app configuration", read: () => loadConfig(), }); ``` ## Template di URI Inserisci `{variables}` nell’URI per servire un’intera famiglia di risorse. Le variabili sono tipizzate a partire dalla stringa stessa: ```ts server.resource("github://{owner}/{repo}/readme", { mimeType: "text/markdown", read: async ({ owner, repo }) => { // ^? { owner: string; repo: string } const res = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/HEAD/README.md`); return res.text(); }, }); ``` | Sintassi | Corrisponde a | Esempio | | --- | --- | --- | | `{name}` | Un segmento di percorso, senza `/`, `?` o `#` | `users://{id}` corrisponde a `users://42` | | `{+name}` | Qualsiasi cosa, `/` compreso | `files://{+path}` corrisponde a `files://src/index.ts` | I valori vengono decodificati (URL) prima di arrivare a `read`. Le risorse statiche compaiono in `resources/list`, i template in `resources/templates/list`. ## Valori di ritorno | `read` restituisce | Contenuto inviato | | --- | --- | | `string` | Testo, con `mimeType` o `text/plain` | | `Uint8Array` | Blob in base64, con `mimeType` o `application/octet-stream` | | un array di `{ uri, text }` o `{ uri, blob }` | Quei contenuti così come sono | | qualsiasi altro valore | Il suo JSON, come `application/json` | `read` riceve il contesto come secondo argomento, con in più l’`uri` richiesto: ```ts read: (params, { uri, signal }) => fetch(toHttp(uri), { signal }).then((r) => r.text()); ``` ## Opzioni | Opzione | Descrizione | | --- | --- | | `name` | Identificativo mostrato agli utenti. Predefinito: l’URI. | | `title` | Nome visualizzato. | | `description` | Cosa contiene la risorsa. | | `mimeType` | Tipo di contenuto di ciò che restituisce `read`. | | `annotations` | Suggerimenti `audience`, `priority` e `lastModified`. | ## Non trovata Quando nessuna risorsa corrisponde a un URI, picomcp risponde con l’errore di protocollo `-32002 Resource not found`. Per rifiutare un URI che corrisponde a un template ma non punta a niente, lancia una `McpError` con lo stesso codice: ```ts import { McpError } from "picomcp"; read: async ({ id }) => { const user = await db.users.find(id); if (!user) throw new McpError(-32002, `Resource not found: users://${id}`); return user; }; ``` ## Collegare dai tool Un tool può indicare al modello una risorsa invece di includerla, così i dati voluminosi restano fuori dal contesto finché non servono: ```ts import { link } from "picomcp"; run: async () => link("reports://2026-q3", "Q3 report", { mimeType: "application/pdf" }); ``` --- # Prompt Source: https://picomcp.github.io/picomcp/it/docs/prompts > Offrire modelli di prompt riutilizzabili che l’utente sceglie dal client, con argomenti validati. I prompt sono modelli che l’utente sceglie dal client, spesso come comandi slash. Trasformano qualche argomento in uno o più messaggi. ```ts server.prompt("review", { title: "Code review", description: "Review a diff for bugs and style issues.", args: z.object({ diff: z.string().describe("The diff to review"), focus: z.string().optional().describe("What to pay attention to"), }), run: ({ diff, focus }) => `Review this diff${focus ? `, focusing on ${focus}` : ""}:\n\n${diff}`, }); ``` Il client mostra `diff` come obbligatorio e `focus` come facoltativo, con le loro descrizioni, il tutto ricavato dallo schema. ## Gli argomenti sono stringhe MCP invia gli argomenti dei prompt come stringhe. Usa schemi di stringhe, oppure converti: ```ts args: z.object({ count: z.coerce.number().int().min(1) }); ``` Gli argomenti non validi vengono respinti con un errore `Invalid params` prima che `run` venga chiamato. ## Valori di ritorno | `run` restituisce | Messaggi inviati | | --- | --- | | `string` | Un messaggio utente | | `PromptMessage[]` | Quei messaggi | | `{ description, messages }` | Il risultato completo | Usa un array per i modelli a più turni o per includere media: ```ts import { image, text } from "picomcp"; run: async ({ id }) => [ { role: "user", content: text("What is wrong with this screenshot?") }, { role: "user", content: image(await screenshot(id), "image/png") }, ]; ``` --- # Schemi Source: https://picomcp.github.io/picomcp/it/docs/schemas > Usare Zod, Valibot, ArkType o qualsiasi libreria Standard Schema, e come viene generato il JSON Schema. picomcp non include una libreria di schemi. Accetta qualsiasi libreria che implementi [Standard Schema](https://standardschema.dev) e genera il JSON Schema di cui hanno bisogno i client MCP tramite [Standard JSON Schema](https://standardschema.dev/json-schema). Tieni la libreria che già usi, e picomcp resta piccolo. ## Zod 4 Funziona così com’è: ```ts import { z } from "zod"; server.tool("forecast", { input: z.object({ city: z.string().describe("City name") }), run: ({ city }) => getForecast(city), }); ``` ## ArkType Funziona così com’è: ```ts import { type } from "arktype"; server.tool("forecast", { input: type({ city: "string" }), run: ({ city }) => getForecast(city), }); ``` ## Valibot Valibot tiene il supporto al JSON Schema in un pacchetto separato. Avvolgi lo schema con `toStandardJsonSchema`: ```ts import * as v from "valibot"; import { toStandardJsonSchema } from "@valibot/to-json-schema"; server.tool("forecast", { input: toStandardJsonSchema(v.object({ city: v.string() })), run: ({ city }) => getForecast(city), }); ``` ## Qualsiasi altra libreria Se una libreria valida tramite Standard Schema ma non sa produrre un JSON Schema (Zod 3, per esempio), aggiungine uno tu con `withJsonSchema`: ```ts import { withJsonSchema } from "picomcp"; server.tool("forecast", { input: withJsonSchema(schema, { type: "object", properties: { city: { type: "string" } }, required: ["city"], }), run: ({ city }) => getForecast(city), }); ``` Senza, la registrazione del tool lancia un errore che ti dice esattamente questo: il problema non arriva mai a un client. ## Come scorrono i tipi - `run` riceve il tipo di **output** dello schema, dopo valori predefiniti e trasformazioni. - `server.call()` accetta il tipo di **input** dello schema, quindi nei test i campi con un valore predefinito sono facoltativi. - Con uno schema `output`, `run` deve restituire il tipo di input di quello schema. ## Consigli per schemi che i modelli capiscono - Descrivi ogni campo. Le descrizioni sono la documentazione che legge il modello. - Preferisci gli enum al testo libero quando l’insieme dei valori è noto. - Usa valori predefiniti per i comportamenti facoltativi, così il modello può omettere quegli argomenti. - Tieni gli oggetti piatti. Gli argomenti molto annidati sono più difficili da compilare per un modello. --- # Trasporti Source: https://picomcp.github.io/picomcp/it/docs/transports > stdio per i client locali, Streamable HTTP per quelli remoti, e l’handler fetch che li fa funzionare entrambi. Un trasporto porta i messaggi JSON-RPC tra il client e il tuo server. picomcp supporta i due definiti da MCP. I tuoi tool non cambiano dall’uno all’altro. ## stdio ```ts await server.serve(); ``` Il client avvia il tuo server come processo figlio e scambia JSON delimitato da a capo su stdin e stdout. È ciò che usano i client desktop e gli agenti di programmazione per i server locali. stdout appartiene al protocollo. Quando serve via stdio, picomcp reindirizza `console.log`, `console.info` e `console.debug` su stderr: una riga di log fuori posto non può più rompere la connessione. Di solito i client mostrano stderr nei loro log. ## Streamable HTTP ```ts const running = await server.serve({ http: 3000 }); console.error(`Listening on ${running.url}`); // http://127.0.0.1:3000/mcp ``` `serve({ http })` avvia un server su Bun, Deno o Node, a seconda del runtime in uso. Opzioni: | Opzione | Predefinito | Descrizione | | --- | --- | --- | | `port` | `3000` | Porta in ascolto. `0` ne sceglie una libera. | | `hostname` | `"127.0.0.1"` | Usa `"0.0.0.0"` per accettare connessioni da altre macchine. | | `path` | `"/mcp"` | Percorso dell’endpoint. Gli altri percorsi rispondono 404. | Restituisce `{ url, close() }`. ## L’handler fetch `server.fetch` è una funzione `(Request) => Promise` conforme agli standard del Web. Passala a qualsiasi runtime o framework che parli `fetch`: ```ts Bun.serve({ port: 3000, fetch: server.fetch }); ``` ```ts Deno.serve({ port: 3000 }, server.fetch); ``` ```ts export default { fetch: server.fetch }; // Cloudflare Workers ``` `server.fetch` non guarda il percorso: instrada tu le richieste verso di lui. Altre piattaforme in [Deploy](https://picomcp.github.io/picomcp/it/docs/deploy). ## Come picomcp parla HTTP picomcp usa Streamable HTTP in modalità JSON senza stato: - `POST` con un messaggio JSON-RPC o un batch → `200` con una risposta JSON, oppure `202` per le notifiche. - `GET` e `DELETE` → `405`, che indica ai client che non viene offerto alcun flusso dal server al client. - `OPTIONS` → preflight CORS per le origini consentite. - Un header `MCP-Protocol-Version` non supportato → `400`. Non ci sono sessioni da memorizzare, quindi qualsiasi istanza può rispondere a qualsiasi richiesta. Per questo picomcp si adatta a piattaforme serverless, load balancer e autoscaling senza lavoro in più. ## Origini e DNS rebinding I browser inviano un header `Origin`. Una pagina malevola potrebbe usarlo per raggiungere un server sulla tua macchina, quindi picomcp lo controlla: - Le richieste senza `Origin` (CLI, server, app desktop) sono sempre consentite. - Per impostazione predefinita sono consentite solo le origini `localhost`, `127.0.0.1` e `[::1]`, su qualsiasi porta. - Le altre origini ricevono `403`. Consentine altre con l’opzione `origins`: ```ts pico({ name: "notes", origins: ["https://claude.ai", "https://app.example.com"] }); pico({ name: "public", origins: "*" }); // any origin: only for public, unauthenticated servers ``` ## Trasporti personalizzati `server.handle(message)` elabora un messaggio JSON-RPC, o un batch, e restituisce la risposta. Restituisce `undefined` per le notifiche. Usalo per collegare picomcp a WebSocket, code di messaggi o qualsiasi altra cosa: ```ts socket.on("message", async (data) => { const response = await server.handle(JSON.parse(data)); if (response) socket.send(JSON.stringify(response)); }); ``` --- # Test Source: https://picomcp.github.io/picomcp/it/docs/testing > Testare i tool nel processo con i tipi completi, poi testare il protocollo da un capo all’altro. ## Chiamare i tool nel processo `server.call(name, input)` esegue un tool esattamente come farebbe un client, senza trasporto né sottoprocesso. Validazione, valori predefiniti, controllo dell’output e gestione degli errori si applicano tutti. ```ts import { describe, expect, it } from "vitest"; import { server } from "./server.ts"; describe("forecast", () => { it("returns the forecast", async () => { const result = await server.call("forecast", { city: "Lyon" }); expect(result.isError).toBeUndefined(); expect(result.structuredContent).toMatchObject({ city: "Lyon" }); }); it("explains invalid input to the model", async () => { const result = await server.call("forecast", { city: "Lyon", days: 30 }); expect(result.isError).toBe(true); }); }); ``` Funziona con qualsiasi test runner: Vitest, `node:test`, `bun test`, `deno test`. ## Chiamate controllate dai tipi Quando i tool sono registrati in catena, `call` ne conosce nomi e input: ```ts const server = pico({ name: "weather" }).tool("forecast", { input: z.object({ city: z.string(), days: z.number().default(3) }), run: ({ city, days }) => `${city}: ${days} days`, }); server.call("forecast", { city: "Lyon" }); // ✓ days is optional: it has a default server.call("forecast", {}); // ✗ city is missing server.call("forcast", { city: "Lyon" }); // ✗ no tool named forcast ``` Se registri i tool in istruzioni separate, il tipo di `server` non può seguirli e `call` accetta qualsiasi nome. Concatena le chiamate, oppure riassegna: `const app = server.tool(...)`. ## Testare il protocollo `server.handle` accetta JSON-RPC grezzo, utile per controllare elenchi e schemi: ```ts const res = await server.handle({ jsonrpc: "2.0", id: 1, method: "tools/list" }); expect(res).toMatchSnapshot(); ``` Uno snapshot di `tools/list` è un modo economico per accorgersi quando una modifica a uno schema cambia ciò che vedono i modelli. ## Testare con un client vero Per i test end-to-end, pilota il server con il client dell’SDK ufficiale via HTTP: ```ts import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const running = await server.serve({ http: { port: 0 } }); const client = new Client({ name: "test", version: "1.0.0" }); await client.connect(new StreamableHTTPClientTransport(new URL(running.url!))); const { tools } = await client.listTools(); await client.close(); await running.close(); ``` La suite di test di picomcp fa esattamente questo, sia via stdio sia via HTTP. --- # Deploy Source: https://picomcp.github.io/picomcp/it/docs/deploy > Eseguire un server MCP remoto su Cloudflare Workers, Deno Deploy, Bun, Node, Hono o Next.js, con autenticazione. Un server remoto si raggiunge tramite URL, con Streamable HTTP. Dato che picomcp è senza stato, gira sulle piattaforme serverless senza configurazione aggiuntiva. ## Cloudflare Workers ```ts import { server } from "./server.ts"; export default { fetch(request: Request) { if (new URL(request.url).pathname !== "/mcp") return new Response("Not found", { status: 404 }); return server.fetch(request); }, }; ``` ```sh npx wrangler deploy src/worker.ts ``` ## Deno Deploy ```ts import { server } from "./server.ts"; Deno.serve((request) => server.fetch(request)); ``` ## Bun ```ts Bun.serve({ port: Number(process.env.PORT ?? 3000), routes: { "/mcp": server.fetch }, }); ``` ## Node ```ts await server.serve({ http: { port: Number(process.env.PORT ?? 3000), hostname: "0.0.0.0" } }); ``` ## Hono ```ts import { Hono } from "hono"; const app = new Hono(); app.all("/mcp", (c) => server.fetch(c.req.raw)); export default app; ``` ## Next.js ```ts // app/mcp/route.ts import { server } from "@/lib/mcp"; export const POST = server.fetch; export const GET = server.fetch; export const OPTIONS = server.fetch; ``` ## Autenticazione picomcp non gestisce l’autenticazione da sé: avvolgi `server.fetch` e controlla la richiesta prima che raggiunga i tuoi tool. ```ts export default { async fetch(request: Request, env: { TOKEN: string }) { if (request.headers.get("authorization") !== `Bearer ${env.TOKEN}`) { return new Response("Unauthorized", { status: 401, headers: { "www-authenticate": "Bearer" } }); } return server.fetch(request); }, }; ``` I client inviano l’header indicato nella loro configurazione, per esempio: ```sh claude mcp add --transport http notes https://notes.example.com/mcp --header "Authorization: Bearer $TOKEN" ``` Per OAuth, metti il middleware del tuo provider davanti a `server.fetch` e segui la [specifica di autorizzazione MCP](https://modelcontextprotocol.io/specification/latest/basic/authorization). ## Checklist - Servi in HTTPS. - Richiedi l’autenticazione, a meno che il server non sia pubblico e di sola lettura. - Imposta `origins` sulle origini browser che ti aspetti, se ce ne sono. - Tieni i segreti nelle variabili d’ambiente, mai nelle descrizioni o nei risultati dei tool. - Tratta gli argomenti dei tool come input non attendibili: il modello può essere influenzato dai contenuti che legge. --- # Riferimento API Source: https://picomcp.github.io/picomcp/it/docs/api > Tutti gli export di picomcp, con le loro firme. ```ts import { pico, McpError, PROTOCOL_VERSIONS, text, image, audio, link, withJsonSchema } from "picomcp"; import type { Server, ToolConfig, ResourceConfig, PromptConfig, Context, CallToolResult } from "picomcp"; ``` ## pico(info) Crea un server. ```ts function pico(info: ServerInfo): Server; interface ServerInfo { name: string; // machine-friendly name, e.g. "weather" version?: string; // defaults to "0.0.0" title?: string; // display name instructions?: string; // how the model should use the server as a whole origins?: string[] | "*"; // allowed browser origins for HTTP; defaults to localhost } ``` ## server.tool(name, config) Registra un tool e restituisce il server. Vedi [Tool](https://picomcp.github.io/picomcp/it/docs/tools). ```ts interface ToolConfig { title?: string; description?: string; input?: I; // Standard Schema output?: O; // Standard Schema; enables structuredContent annotations?: ToolAnnotations; run(input: Infer, ctx: Context): Return | Promise; } interface ToolAnnotations { title?: string; readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; } ``` Lancia un `TypeError` per un nome non valido o duplicato, o per uno schema senza supporto al JSON Schema. ## server.resource(uri, config) Registra una risorsa, o un template di risorsa quando `uri` contiene `{variables}`. Restituisce il server. Vedi [Risorse](https://picomcp.github.io/picomcp/it/docs/resources). ```ts interface ResourceConfig { name?: string; title?: string; description?: string; mimeType?: string; annotations?: Annotations; read(params: UriParams, ctx: Context & { uri: string }): ResourceReturn | Promise; } ``` ## server.prompt(name, config) Registra un prompt e restituisce il server. Vedi [Prompt](https://picomcp.github.io/picomcp/it/docs/prompts). ```ts interface PromptConfig { title?: string; description?: string; args?: A; // Standard Schema of an object with string fields run(args: Infer, ctx: Context): string | PromptMessage[] | { description?: string; messages: PromptMessage[] }; } ``` ## server.call(name, input?) Esegue un tool nel processo e restituisce il suo `CallToolResult`. Controllato dai tipi quando i tool sono stati registrati in catena. Rifiuta la promise per un nome di tool sconosciuto. Vedi [Test](https://picomcp.github.io/picomcp/it/docs/testing). ```ts call(name: string, input?: unknown): Promise; interface CallToolResult { content: ContentBlock[]; structuredContent?: Record; isError?: boolean; } ``` ## server.serve(options?) Avvia il server via stdio, oppure via Streamable HTTP quando `http` è impostato. Vedi [Trasporti](https://picomcp.github.io/picomcp/it/docs/transports). ```ts serve(options?: { http?: number | { port?: number; hostname?: string; path?: string } }): Promise<{ url?: string; close(): Promise }>; ``` ## server.fetch(request) Handler Streamable HTTP conforme agli standard del Web. È legato al server, quindi lo si può passare come un semplice valore. ```ts fetch(request: Request): Promise; ``` ## server.handle(message) Elabora un messaggio JSON-RPC o un batch. Restituisce la risposta, oppure `undefined` per le notifiche. ```ts handle(message: unknown): Promise; ``` ## Context Il secondo argomento di ogni handler. ```ts interface Context { signal: AbortSignal; // aborted when the client cancels meta: Record | undefined; // the request's _meta } ``` ## Helper di contenuto ```ts text(text: string, annotations?: Annotations): ContentBlock; image(data: string | Uint8Array, mimeType: string): ContentBlock; audio(data: string | Uint8Array, mimeType: string): ContentBlock; link(uri: string, name: string, extra?: { title?: string; description?: string; mimeType?: string }): ContentBlock; ``` `data` è base64 o byte grezzi. I byte vengono codificati per te su ogni runtime. ## withJsonSchema(schema, jsonSchema, output?) Aggiunge un JSON Schema a uno Standard Schema che non sa generarne uno. Vedi [Schemi](https://picomcp.github.io/picomcp/it/docs/schemas). ```ts withJsonSchema(schema: S, jsonSchema: JsonSchema, output?: JsonSchema): StandardSchemaV1, Infer>; ``` ## McpError Lanciala da un handler per rispondere con un errore JSON-RPC invece che con un risultato. ```ts new McpError(code: number, message: string, data?: unknown); ``` | Codice | Significato | | --- | --- | | `-32602` | Parametri non validi | | `-32002` | Risorsa non trovata | | da `-32000` a `-32099` | Liberi per gli errori del tuo server | ## PROTOCOL_VERSIONS Le versioni del protocollo MCP che picomcp parla, dalla più recente: `["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"]`. ## Helper di tipo | Tipo | Descrizione | | --- | --- | | `Infer` | Tipo di output di uno schema | | `InferInput` | Tipo di input di uno schema | | `UriParams<"a://{x}/{+y}">` | `{ x: string; y: string }` | | `StandardSchemaV1` | L’interfaccia Standard Schema, incorporata | --- # Copertura del protocollo Source: https://picomcp.github.io/picomcp/it/docs/protocol > Esattamente quali parti della specifica MCP picomcp implementa, e quali non ancora. picomcp punta a MCP `2025-11-25` e negozia anche `2025-06-18`, `2025-03-26` e `2024-11-05`. Ogni funzionalità indicata come supportata è coperta da test, compresi test end-to-end con il client dell’SDK TypeScript ufficiale. ## Supportato | Area | Funzionalità | | --- | --- | | Ciclo di vita | `initialize` con negoziazione della versione, `notifications/initialized`, `ping` | | Tool | `tools/list`, `tools/call`, validazione dell’input, `outputSchema` e `structuredContent`, annotazioni, titoli | | Contenuti | testo, immagine, audio, link a risorse, risorse incorporate | | Risorse | `resources/list`, `resources/templates/list`, `resources/read`, contenuti testuali e binari | | Prompt | `prompts/list`, `prompts/get`, argomenti ricavati dagli schemi | | Utilità | `notifications/cancelled` con `AbortSignal` | | JSON-RPC | Batch, codici di errore, risposte del client ignorate | | stdio | JSON delimitato da a capo, stdout protetto dai log fuori posto | | Streamable HTTP | Risposte JSON senza stato, `202` per le notifiche, `405` per `GET`, CORS, controllo di `Origin` e `MCP-Protocol-Version` | ## Non ancora supportato | Funzionalità | Perché | | --- | --- | | Sampling, elicitation, roots | Richiedono che il server invii richieste al client. Il design senza stato di picomcp non tiene ancora aperto un canale per questo. | | Notifiche di log, di avanzamento e `list_changed` | Stesso motivo: messaggi dal server al client. | | Sottoscrizioni alle risorse | Stesso motivo. | | Completamenti | In programma. | | Cursori di paginazione | Gli elenchi vengono restituiti per intero, il che va bene per server con fino a qualche centinaio di voci. | | Sessioni (`Mcp-Session-Id`) e flussi SSE | Non necessari in modalità senza stato. | | Tasks | Sperimentale nella specifica. | Ti serve una di queste? [Apri una issue](https://github.com/picomcp/picomcp/issues) e descrivi il tuo caso d’uso. Le funzionalità arrivano quando si possono aggiungere senza appesantire picomcp. --- # Per gli agenti IA Source: https://picomcp.github.io/picomcp/it/docs/agents > Documentazione leggibile dalle macchine, llms.txt, pagine Markdown e regole da dare al tuo agente di programmazione. Gli agenti di programmazione scrivono molti server MCP. Questa documentazione è pensata per essere letta da loro tanto quanto da te. ## Documentazione leggibile dalle macchine | URL | Contenuto | | --- | --- | | [`/llms.txt`](https://picomcp.github.io/picomcp/llms.txt) | Indice di tutte le pagine con un riassunto di una riga, nel formato [llms.txt](https://llmstxt.org) | | [`/llms-full.txt`](https://picomcp.github.io/picomcp/llms-full.txt) | Tutta la documentazione in un unico file Markdown | | `/docs/.md` | Qualsiasi pagina in Markdown. Aggiungi `.md` al suo URL, per esempio [`/docs/tools.md`](https://picomcp.github.io/picomcp/it/docs/tools.md) | Ogni pagina ha anche un pulsante **Copia come Markdown**, per incollarla in una chat. Questi file sono in inglese, la lingua di riferimento e quella che i modelli seguono meglio. Ogni traduzione ha anche il suo `llms-full.txt`, per esempio `/fr/llms-full.txt`. ## Indica la documentazione al tuo agente Incolla questo nelle istruzioni del tuo agente, per esempio in `AGENTS.md` o `CLAUDE.md` nella radice del progetto: ```md ## MCP servers This project builds MCP servers with picomcp. - Read https://picomcp.github.io/picomcp/llms-full.txt before writing picomcp code. - Define servers with `pico({ name })` and chain `.tool()`, `.resource()`, `.prompt()`. - Use Zod 4 schemas with `.describe()` on every field. - Write tool descriptions and error messages for the model: say what the tool does, when to use it, and what to do after an error. - Never write to stdout in a stdio server. Use `console.error` for logs. - Test tools with `server.call(name, input)`. Do not spawn a client in unit tests. ``` ## Dai al tuo agente un server di documentazione La documentazione è disponibile anche come server MCP, [`picomcp-docs`](https://www.npmjs.com/package/picomcp-docs), costruito con picomcp. Dà agli agenti tre tool, `list_docs`, `search_docs` e `read_doc`, e ogni pagina come risorsa. In Claude Code: ```sh claude mcp add picomcp-docs -- npx -y picomcp-docs ``` In Cursor, Windsurf, Claude Desktop e nella maggior parte degli altri client, aggiungilo al file di configurazione MCP (`.cursor/mcp.json` per Cursor): ```json { "mcpServers": { "picomcp-docs": { "command": "npx", "args": ["-y", "picomcp-docs"] } } } ``` Il codice sorgente è in [`packages/docs`](https://github.com/picomcp/picomcp/tree/main/packages/docs), circa 100 righe. ## Cosa sbagliano spesso gli agenti Questi sono gli errori più frequenti dei modelli quando scrivono server MCP, e come funzionano le cose in picomcp: - **In modalità stdio, stdout è il protocollo.** Un `console.log` lo corromperebbe. picomcp reindirizza l’output della console su stderr quando serve via stdio, ma `process.stdout.write` resta pericoloso. - **Gli errori dei tool sono risultati, non eccezioni.** Lanciare un errore dentro `run` produce `isError: true` con il tuo messaggio. Il modello lo vede e può riprovare. Non catturare gli errori solo per restituire stringhe `"Error: ..."`. - **Gli argomenti dei prompt sono stringhe.** Usa `z.coerce.number()` per gli argomenti numerici dei prompt. - **Il JSON Schema viene dalla libreria di schemi.** Zod 4 e ArkType non hanno bisogno di nulla. Valibot ha bisogno di `toStandardJsonSchema`. Gli altri di `withJsonSchema`. - **`server.fetch` ignora il percorso.** Instrada tu `/mcp` verso di lui, oppure usa `serve({ http })`, che risponde solo su `/mcp`. - **Il `server.call` tipizzato richiede la concatenazione.** `pico().tool().tool()` mantiene i tipi. Istruzioni separate li perdono. ## Contribuire con un agente Il repository contiene un [`AGENTS.md`](https://github.com/picomcp/picomcp/blob/main/AGENTS.md) con le regole per lavorare su picomcp stesso: il budget di dimensione, i comandi di test e i vincoli di progettazione.