# Introduction Source: https://picomcp.github.io/picomcp/fr/docs/ > Ce qu’est picomcp, ce qu’il n’est pas, et les dix lignes qui l’expliquent. picomcp est un framework TypeScript pour construire des serveurs [Model Context Protocol](https://modelcontextprotocol.io). Un serveur MCP donne à une application d’IA (Claude, Cursor, VS Code, votre propre agent) des **outils** qu’elle peut appeler, des **ressources** qu’elle peut lire et des **prompts** qu’elle peut proposer à l’utilisateur. ```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(); ``` C’est un serveur complet. Lancez-le avec `node server.ts` et connectez-le à n’importe quel client MCP. ## Pourquoi picomcp - **Léger.** Environ 4 kB gzippé, zéro dépendance. La bibliothèque entière tient dans votre tête et dans une revue de code. - **Typé par votre schéma.** Passez un schéma Zod, Valibot ou ArkType. Votre handler reçoit des arguments typés, le client reçoit un JSON Schema, les appels invalides reçoivent une erreur lisible. - **Tourne partout.** Node 22+, Bun, Deno, Cloudflare Workers et tout runtime qui dispose de `fetch`. Aucun module intégré de Node dans le cœur. - **Deux transports, un seul serveur.** stdio pour les clients de bureau, Streamable HTTP pour les serveurs distants. On passe de l’un à l’autre avec une option. - **Testable sans client.** `server.call("forecast", { city: "Lyon" })` exécute un outil dans le processus, entièrement typé. - **Vérifié avec le SDK officiel.** La suite de tests pilote picomcp avec `@modelcontextprotocol/sdk` sur les deux transports. ## Ce que picomcp n’est pas picomcp implémente le côté serveur de MCP, et rien d’autre. Il n’inclut ni client, ni boucle d’agent, ni SDK de LLM. Il privilégie la moitié sans état, en requête/réponse, du protocole : c’est ce dont la plupart des serveurs ont besoin. Les fonctionnalités initiées par le serveur, comme le sampling et l’elicitation, ne sont pas encore prises en charge : voir [Couverture du protocole](https://picomcp.github.io/picomcp/fr/docs/protocol) pour la liste exacte. ## Pour la suite - [Démarrage rapide](https://picomcp.github.io/picomcp/fr/docs/quickstart) : construire et connecter un serveur en cinq minutes. - [Outils](https://picomcp.github.io/picomcp/fr/docs/tools) : tout ce qu’un outil sait faire. - [Pour les agents IA](https://picomcp.github.io/picomcp/fr/docs/agents) : donner la doc à votre agent de code en une URL. --- # Démarrage rapide Source: https://picomcp.github.io/picomcp/fr/docs/quickstart > Générer, tester et connecter votre premier serveur MCP en cinq minutes. Il vous faut Node 22.18 ou plus récent (il exécute directement les fichiers TypeScript), Bun ou Deno. ## Générer un projet ```sh npx picomcp init my-server cd my-server npm install ``` Vous obtenez un serveur prêt à tourner : | Fichier | Rôle | | --- | --- | | `server.ts` | La définition de votre serveur : outils, ressources, prompts | | `main.ts` | Démarre le serveur en stdio | | `server.test.ts` | Un test qui appelle un outil dans le processus | Vous préférez partir de zéro ? Installez les deux paquets et écrivez `server.ts` vous-même : ```sh npm install picomcp zod ``` ## Écrire un outil Ouvrez `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` est ce que le modèle lit pour décider quand appeler l’outil. Écrivez-la pour le modèle. - `input` accepte n’importe quel [Standard Schema](https://picomcp.github.io/picomcp/fr/docs/schemas). Dans `run`, `name` est typé `string`. - Ici, `run` renvoie une chaîne. Il peut aussi renvoyer des objets, des blocs de contenu, ou lever une erreur : voir [Outils](https://picomcp.github.io/picomcp/fr/docs/tools). ## Le tester ```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` est vérifié au typage : une faute dans le nom de l’outil ou un argument manquant ne compile pas. ## Le connecter Pour Claude Code : ```sh claude mcp add my-server -- node /absolute/path/to/my-server/main.ts ``` Demandez ensuite à Claude de saluer quelqu’un. Pour Claude Desktop, Cursor, VS Code et les autres, voir [Connecter un client](https://picomcp.github.io/picomcp/fr/docs/clients). ## L’inspecter MCP Inspector permet d’appeler les outils à la main et de voir les messages bruts : ```sh npx @modelcontextprotocol/inspector node main.ts ``` --- # Connecter un client Source: https://picomcp.github.io/picomcp/fr/docs/clients > La configuration pour Claude Code, Claude Desktop, Cursor, VS Code et MCP Inspector. Tous les clients MCP lancent un serveur local de la même façon : ils exécutent une commande et lui parlent en stdio. Les serveurs distants s’atteignent par URL, en Streamable HTTP. Utilisez des chemins absolus dans toutes les configurations ci-dessous. ## 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 ``` Ajoutez `--scope project` pour écrire le serveur dans `.mcp.json` et le partager avec votre équipe. ## Claude Desktop Modifiez `claude_desktop_config.json` (Réglages → Développeur → Modifier la configuration), puis redémarrez l’application : ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] } } } ``` ## Cursor Créez `.cursor/mcp.json` dans votre projet, ou `~/.cursor/mcp.json` pour tous vos projets : ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "url": "https://example.com/mcp" } } } ``` ## VS Code Créez `.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 ``` Pour inspecter un serveur HTTP, démarrez-le avec `serve({ http: 3000 })`, ouvrez l’Inspector, choisissez **Streamable HTTP** et saisissez `http://127.0.0.1:3000/mcp`. L’Inspector tourne sur localhost, ce que picomcp autorise par défaut. ## Bun et Deno Remplacez `node` par votre runtime : ```json { "command": "bun", "args": ["/absolute/path/main.ts"] } ``` ```json { "command": "deno", "args": ["run", "--allow-net", "/absolute/path/main.ts"] } ``` ## Dépannage **Le client n’affiche aucun outil.** Lancez vous-même la commande de la configuration. Si elle s’arrête ou affiche une erreur, le client voit la même chose. **« Unexpected token » ou erreurs JSON dans les logs du client.** Quelque chose a écrit sur stdout. picomcp redirige `console.log` vers stderr quand il sert en stdio, mais d’autres bibliothèques peuvent écrire directement dans `process.stdout`. **Node refuse d’exécuter les fichiers `.ts`.** Passez à Node 22.18 ou plus récent, ou lancez le serveur avec Bun. --- # Outils Source: https://picomcp.github.io/picomcp/fr/docs/tools > Définir des outils, valider l’entrée, renvoyer du texte, des objets ou des médias, signaler les erreurs et gérer l’annulation. Un outil est une fonction que le modèle peut appeler. Enregistrez-en un avec `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"); }, }); ``` ## Configuration | Option | Type | Description | | --- | --- | --- | | `description` | `string` | Ce que fait l’outil et quand l’utiliser. Le modèle lit ce texte. | | `title` | `string` | Nom affiché par les clients. | | `input` | Standard Schema | Schéma des arguments. Omettez-le pour les outils sans argument. | | `output` | Standard Schema | Schéma du résultat structuré. Voir [Sortie structurée](#sortie-structurée). | | `annotations` | `ToolAnnotations` | Indications pour les clients : `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`. | | `run` | `(input, ctx) => value` | Le handler. Il peut être asynchrone. | Les noms d’outils font de 1 à 128 caractères et n’utilisent que des lettres, des chiffres, `_`, `.` et `-`. Enregistrer un nom invalide ou déjà pris lève une erreur immédiatement : les erreurs apparaissent au démarrage du serveur, pas quand un modèle appelle l’outil. ## Validation de l’entrée picomcp valide les arguments avec votre schéma avant d’appeler `run`. Les valeurs par défaut et les transformations s’appliquent : dans l’exemple, `limit` est toujours un nombre dans `run`. Si la validation échoue, l’outil n’est pas appelé. Le client reçoit une erreur d’outil qui nomme le champ fautif, pour que le modèle corrige son appel et réessaie : ```text Invalid arguments for tool "search_issues": limit: Too big: expected number to be <=50 ``` ## Valeurs de retour Renvoyez ce qui est naturel. picomcp le transforme en résultat MCP : | `run` renvoie | Le client reçoit | | --- | --- | | `string` | Un bloc de texte | | un objet, un tableau ou un nombre | Son JSON en texte. Les objets sont aussi envoyés en `structuredContent`. | | un bloc de contenu, ou un tableau de blocs | Ces blocs, tels quels | | `{ content: [...] }` | Le résultat tel quel, pour tout contrôler | | `undefined` | Un résultat vide | Construisez des blocs de contenu avec les helpers : ```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" }), ]; ``` ## Sortie structurée Déclarez un schéma `output` quand le résultat est consommé par des clients ou du code, et pas seulement par le modèle : ```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 alors : 1. annonce le schéma comme `outputSchema` dans `tools/list` ; 2. vérifie au typage la valeur renvoyée par `run` ; 3. valide la valeur à l’exécution et l’envoie en `structuredContent`, avec une copie JSON en texte pour les anciens clients. Si la valeur ne correspond pas, le client reçoit une erreur d’outil plutôt que des données malformées. ## Erreurs Levez une erreur dans `run`. Son message devient une erreur d’outil (`isError: true`) que le modèle peut lire et à laquelle il peut réagir : ```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; }; ``` Écrivez les messages d’erreur pour le modèle : dites ce qui n’a pas marché et quoi faire ensuite. Les erreurs d’outil font partie d’une conversation normale. Les erreurs de protocole, non : n’utilisez `McpError` que si la requête elle-même est fautive, par exemple quand l’utilisateur n’a pas les droits : ```ts import { McpError } from "picomcp"; throw new McpError(-32001, "Not authorised for this workspace"); ``` ## Annulation Le second argument de `run` porte un `AbortSignal`. Il est déclenché quand le client envoie `notifications/cancelled`, en général parce que l’utilisateur a cliqué sur stop. Passez-le à `fetch` et aux autres API qui l’acceptent : ```ts run: async ({ url }, { signal }) => { const res = await fetch(url, { signal }); return res.text(); }; ``` Une fois une requête annulée, picomcp n’envoie aucune réponse pour elle, comme l’exige le protocole. ## Métadonnées `ctx.meta` contient l’objet `_meta` envoyé par le client avec la requête, s’il y en a un. Les clients s’en servent pour le traçage et les jetons de progression. ## Écrire de bons outils - **Une tâche par outil.** `create_invoice` et `send_invoice` valent mieux que `invoice(action)`. - **Décrivez les arguments.** `.describe()` sur chaque champ se retrouve dans le JSON Schema que voit le modèle. - **Renvoyez moins.** Le modèle paie chaque token. Renvoyez les champs utiles, pas toute la réponse de l’API. - **Annotez honnêtement.** `readOnlyHint: true` permet aux clients de sauter la demande de confirmation. Ne le mettez jamais sur un outil qui écrit. --- # Ressources Source: https://picomcp.github.io/picomcp/fr/docs/resources > Exposer des fichiers, des enregistrements et des données vivantes que le client peut lire, avec des templates d’URI typés. Les ressources sont des données que le client peut lire et joindre à une conversation : fichiers, lignes de base de données, réponses d’API. Les outils sont appelés par le modèle ; les ressources sont en général choisies par l’utilisateur ou l’application. ```ts server.resource("config://app", { description: "The app configuration", read: () => loadConfig(), }); ``` ## Templates d’URI Mettez des `{variables}` dans l’URI pour servir toute une famille de ressources. Les variables sont typées à partir de la chaîne elle-même : ```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(); }, }); ``` | Syntaxe | Correspond à | Exemple | | --- | --- | --- | | `{name}` | Un segment de chemin, sans `/`, `?` ni `#` | `users://{id}` correspond à `users://42` | | `{+name}` | N’importe quoi, `/` compris | `files://{+path}` correspond à `files://src/index.ts` | Les valeurs sont décodées (URL) avant d’arriver dans `read`. Les ressources statiques apparaissent dans `resources/list`, les templates dans `resources/templates/list`. ## Valeurs de retour | `read` renvoie | Contenu envoyé | | --- | --- | | `string` | Du texte, avec `mimeType` ou `text/plain` | | `Uint8Array` | Un blob en base64, avec `mimeType` ou `application/octet-stream` | | un tableau de `{ uri, text }` ou `{ uri, blob }` | Ces contenus, tels quels | | toute autre valeur | Son JSON, en `application/json` | `read` reçoit le contexte en second argument, avec l’`uri` demandée en plus : ```ts read: (params, { uri, signal }) => fetch(toHttp(uri), { signal }).then((r) => r.text()); ``` ## Options | Option | Description | | --- | --- | | `name` | Identifiant affiché aux utilisateurs. Par défaut, l’URI. | | `title` | Nom affiché. | | `description` | Ce que contient la ressource. | | `mimeType` | Type de contenu de ce que renvoie `read`. | | `annotations` | Indications `audience`, `priority` et `lastModified`. | ## Introuvable Quand aucune ressource ne correspond à une URI, picomcp répond avec l’erreur `-32002 Resource not found` du protocole. Pour rejeter une URI qui correspond à un template mais ne désigne rien, levez une `McpError` avec le même code : ```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; }; ``` ## Lier depuis un outil Un outil peut orienter le modèle vers une ressource au lieu de l’inclure, ce qui garde les gros volumes hors du contexte tant qu’on n’en a pas besoin : ```ts import { link } from "picomcp"; run: async () => link("reports://2026-q3", "Q3 report", { mimeType: "application/pdf" }); ``` --- # Prompts Source: https://picomcp.github.io/picomcp/fr/docs/prompts > Proposer des modèles de prompts réutilisables que l’utilisateur choisit dans le client, avec des arguments validés. Les prompts sont des modèles que l’utilisateur choisit dans le client, souvent sous forme de commandes slash. Ils transforment quelques arguments en un ou plusieurs messages. ```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}`, }); ``` Le client affiche `diff` comme obligatoire et `focus` comme facultatif, avec leurs descriptions, le tout déduit du schéma. ## Les arguments sont des chaînes MCP envoie les arguments de prompt sous forme de chaînes. Utilisez des schémas de chaînes, ou convertissez : ```ts args: z.object({ count: z.coerce.number().int().min(1) }); ``` Les arguments invalides sont rejetés avec une erreur `Invalid params` avant l’appel de `run`. ## Valeurs de retour | `run` renvoie | Messages envoyés | | --- | --- | | `string` | Un message utilisateur | | `PromptMessage[]` | Ces messages | | `{ description, messages }` | Le résultat complet | Utilisez un tableau pour les modèles à plusieurs tours ou pour inclure des médias : ```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") }, ]; ``` --- # Schémas Source: https://picomcp.github.io/picomcp/fr/docs/schemas > Utiliser Zod, Valibot, ArkType ou toute bibliothèque Standard Schema, et comment le JSON Schema est généré. picomcp n’embarque pas de bibliothèque de schémas. Il accepte toute bibliothèque qui implémente [Standard Schema](https://standardschema.dev), et génère le JSON Schema dont les clients MCP ont besoin via [Standard JSON Schema](https://standardschema.dev/json-schema). Vous gardez la bibliothèque que vous utilisez déjà, et picomcp reste léger. ## Zod 4 Fonctionne tel quel : ```ts import { z } from "zod"; server.tool("forecast", { input: z.object({ city: z.string().describe("City name") }), run: ({ city }) => getForecast(city), }); ``` ## ArkType Fonctionne tel quel : ```ts import { type } from "arktype"; server.tool("forecast", { input: type({ city: "string" }), run: ({ city }) => getForecast(city), }); ``` ## Valibot Valibot garde la prise en charge du JSON Schema dans un paquet séparé. Enveloppez le schéma avec `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), }); ``` ## Toute autre bibliothèque Si une bibliothèque valide via Standard Schema mais ne sait pas produire de JSON Schema (Zod 3, par exemple), joignez-en un vous-même avec `withJsonSchema` : ```ts import { withJsonSchema } from "picomcp"; server.tool("forecast", { input: withJsonSchema(schema, { type: "object", properties: { city: { type: "string" } }, required: ["city"], }), run: ({ city }) => getForecast(city), }); ``` Sans cela, l’enregistrement de l’outil lève une erreur qui vous dit exactement ceci : le problème n’atteint jamais un client. ## Comment circulent les types - `run` reçoit le type de **sortie** du schéma, après valeurs par défaut et transformations. - `server.call()` accepte le type d’**entrée** du schéma : les champs qui ont une valeur par défaut sont facultatifs dans les tests. - Avec un schéma `output`, `run` doit renvoyer le type d’entrée de ce schéma. ## Conseils pour des schémas que les modèles comprennent - Décrivez chaque champ. Les descriptions sont la documentation que lit le modèle. - Préférez les enums au texte libre quand l’ensemble des valeurs est connu. - Utilisez des valeurs par défaut pour les comportements facultatifs : le modèle peut omettre ces arguments. - Gardez des objets plats. Les arguments très imbriqués sont plus difficiles à remplir pour un modèle. --- # Transports Source: https://picomcp.github.io/picomcp/fr/docs/transports > stdio pour les clients locaux, Streamable HTTP pour les clients distants, et le handler fetch qui fait tourner les deux. Un transport achemine les messages JSON-RPC entre le client et votre serveur. picomcp prend en charge les deux que définit MCP. Vos outils ne changent pas de l’un à l’autre. ## stdio ```ts await server.serve(); ``` Le client lance votre serveur comme processus enfant et échange du JSON délimité par des retours à la ligne sur stdin et stdout. C’est ce qu’utilisent les clients de bureau et les agents de code pour les serveurs locaux. stdout appartient au protocole. Quand il sert en stdio, picomcp redirige `console.log`, `console.info` et `console.debug` vers stderr : une ligne de log égarée ne peut plus casser la connexion. Les clients affichent en général stderr dans leurs logs. ## 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 })` démarre un serveur sur Bun, Deno ou Node, selon le runtime en cours. Options : | Option | Défaut | Description | | --- | --- | --- | | `port` | `3000` | Port d’écoute. `0` en choisit un libre. | | `hostname` | `"127.0.0.1"` | Utilisez `"0.0.0.0"` pour accepter les connexions d’autres machines. | | `path` | `"/mcp"` | Chemin de l’endpoint. Les autres chemins répondent 404. | Il renvoie `{ url, close() }`. ## Le handler fetch `server.fetch` est une fonction `(Request) => Promise` conforme aux standards du Web. Confiez-la à n’importe quel runtime ou framework qui parle `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` ne regarde pas le chemin : routez les requêtes vers lui vous-même. D’autres plateformes dans [Déployer](https://picomcp.github.io/picomcp/fr/docs/deploy). ## Comment picomcp parle HTTP picomcp utilise Streamable HTTP en mode JSON sans état : - `POST` avec un message JSON-RPC ou un lot → `200` avec une réponse JSON, ou `202` pour les notifications. - `GET` et `DELETE` → `405`, ce qui indique aux clients qu’aucun flux du serveur vers le client n’est proposé. - `OPTIONS` → preflight CORS pour les origines autorisées. - Un en-tête `MCP-Protocol-Version` non pris en charge → `400`. Il n’y a pas de session à stocker : n’importe quelle instance peut répondre à n’importe quelle requête. picomcp se prête donc aux plateformes serverless, aux load balancers et à l’autoscaling sans travail supplémentaire. ## Origines et DNS rebinding Les navigateurs envoient un en-tête `Origin`. Une page malveillante pourrait s’en servir pour atteindre un serveur sur votre machine, alors picomcp le vérifie : - Les requêtes sans `Origin` (CLI, serveurs, applications de bureau) sont toujours acceptées. - Par défaut, seules les origines `localhost`, `127.0.0.1` et `[::1]` sont acceptées, sur n’importe quel port. - Les autres origines reçoivent `403`. Autorisez-en d’autres avec l’option `origins` : ```ts pico({ name: "notes", origins: ["https://claude.ai", "https://app.example.com"] }); pico({ name: "public", origins: "*" }); // any origin: only for public, unauthenticated servers ``` ## Transports personnalisés `server.handle(message)` traite un message JSON-RPC, ou un lot, et renvoie la réponse. Il renvoie `undefined` pour les notifications. Utilisez-le pour brancher picomcp sur des WebSockets, des files de messages ou autre chose : ```ts socket.on("message", async (data) => { const response = await server.handle(JSON.parse(data)); if (response) socket.send(JSON.stringify(response)); }); ``` --- # Tests Source: https://picomcp.github.io/picomcp/fr/docs/testing > Tester les outils dans le processus avec les types complets, puis tester le protocole de bout en bout. ## Appeler les outils dans le processus `server.call(name, input)` exécute un outil exactement comme le ferait un client, sans transport ni sous-processus. La validation, les valeurs par défaut, la vérification de la sortie et la gestion des erreurs s’appliquent toutes. ```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); }); }); ``` Cela fonctionne avec n’importe quel lanceur de tests : Vitest, `node:test`, `bun test`, `deno test`. ## Des appels vérifiés au typage Quand les outils sont enregistrés en chaîne, `call` connaît leurs noms et leurs entrées : ```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 ``` Si vous enregistrez les outils dans des instructions séparées, le type de `server` ne peut pas les suivre et `call` accepte n’importe quel nom. Enchaînez les appels, ou réassignez : `const app = server.tool(...)`. ## Tester le protocole `server.handle` prend du JSON-RPC brut, ce qui est pratique pour vérifier les listes et les schémas : ```ts const res = await server.handle({ jsonrpc: "2.0", id: 1, method: "tools/list" }); expect(res).toMatchSnapshot(); ``` Un snapshot de `tools/list` est un moyen simple de remarquer quand un changement de schéma modifie ce que voient les modèles. ## Tester avec un vrai client Pour des tests de bout en bout, pilotez le serveur avec le client du SDK officiel, en 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 de tests de picomcp fait exactement cela, en stdio comme en HTTP. --- # Déployer Source: https://picomcp.github.io/picomcp/fr/docs/deploy > Faire tourner un serveur MCP distant sur Cloudflare Workers, Deno Deploy, Bun, Node, Hono ou Next.js, avec authentification. Un serveur distant s’atteint par URL, en Streamable HTTP. Comme picomcp est sans état, il tourne sur les plateformes serverless sans configuration supplémentaire. ## 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; ``` ## Authentification picomcp ne gère pas lui-même l’authentification : enveloppez `server.fetch` et vérifiez la requête avant qu’elle n’atteigne vos outils. ```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); }, }; ``` Les clients envoient l’en-tête défini dans leur configuration, par exemple : ```sh claude mcp add --transport http notes https://notes.example.com/mcp --header "Authorization: Bearer $TOKEN" ``` Pour OAuth, placez le middleware de votre fournisseur devant `server.fetch` et suivez la [spécification d’autorisation MCP](https://modelcontextprotocol.io/specification/latest/basic/authorization). ## Check-list - Servez en HTTPS. - Exigez une authentification, sauf si le serveur est public et en lecture seule. - Réglez `origins` sur les origines navigateur que vous attendez, s’il y en a. - Gardez les secrets dans des variables d’environnement, jamais dans les descriptions ou les résultats des outils. - Traitez les arguments des outils comme des entrées non fiables : le modèle peut être influencé par le contenu qu’il lit. --- # Référence de l’API Source: https://picomcp.github.io/picomcp/fr/docs/api > Tous les exports de picomcp, avec leurs signatures. ```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) Crée un serveur. ```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) Enregistre un outil et renvoie le serveur. Voir [Outils](https://picomcp.github.io/picomcp/fr/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; } ``` Lève une `TypeError` pour un nom invalide ou déjà pris, ou pour un schéma sans prise en charge du JSON Schema. ## server.resource(uri, config) Enregistre une ressource, ou un template de ressource quand `uri` contient des `{variables}`. Renvoie le serveur. Voir [Ressources](https://picomcp.github.io/picomcp/fr/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) Enregistre un prompt et renvoie le serveur. Voir [Prompts](https://picomcp.github.io/picomcp/fr/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?) Exécute un outil dans le processus et renvoie son `CallToolResult`. Vérifié au typage quand les outils ont été enregistrés en chaîne. Rejette la promesse pour un nom d’outil inconnu. Voir [Tests](https://picomcp.github.io/picomcp/fr/docs/testing). ```ts call(name: string, input?: unknown): Promise; interface CallToolResult { content: ContentBlock[]; structuredContent?: Record; isError?: boolean; } ``` ## server.serve(options?) Démarre le serveur en stdio, ou en Streamable HTTP quand `http` est défini. Voir [Transports](https://picomcp.github.io/picomcp/fr/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 aux standards du Web. Lié au serveur : on peut le passer comme une simple valeur. ```ts fetch(request: Request): Promise; ``` ## server.handle(message) Traite un message JSON-RPC ou un lot. Renvoie la réponse, ou `undefined` pour les notifications. ```ts handle(message: unknown): Promise; ``` ## Context Le second argument de chaque handler. ```ts interface Context { signal: AbortSignal; // aborted when the client cancels meta: Record | undefined; // the request's _meta } ``` ## Helpers de contenu ```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` est du base64 ou des octets bruts. Les octets sont encodés pour vous, sur tous les runtimes. ## withJsonSchema(schema, jsonSchema, output?) Joint un JSON Schema à un Standard Schema qui ne sait pas en générer. Voir [Schémas](https://picomcp.github.io/picomcp/fr/docs/schemas). ```ts withJsonSchema(schema: S, jsonSchema: JsonSchema, output?: JsonSchema): StandardSchemaV1, Infer>; ``` ## McpError Levez-la dans un handler pour répondre avec une erreur JSON-RPC plutôt qu’avec un résultat. ```ts new McpError(code: number, message: string, data?: unknown); ``` | Code | Signification | | --- | --- | | `-32602` | Paramètres invalides | | `-32002` | Ressource introuvable | | `-32000` à `-32099` | Libres pour les erreurs propres à votre serveur | ## PROTOCOL_VERSIONS Les versions du protocole MCP que parle picomcp, de la plus récente à la plus ancienne : `["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"]`. ## Helpers de types | Type | Description | | --- | --- | | `Infer` | Type de sortie d’un schéma | | `InferInput` | Type d’entrée d’un schéma | | `UriParams<"a://{x}/{+y}">` | `{ x: string; y: string }` | | `StandardSchemaV1` | L’interface Standard Schema, intégrée | --- # Couverture du protocole Source: https://picomcp.github.io/picomcp/fr/docs/protocol > Exactement quelles parties de la spécification MCP picomcp implémente, et lesquelles pas encore. picomcp vise MCP `2025-11-25` et sait négocier vers `2025-06-18`, `2025-03-26` et `2024-11-05`. Chaque fonctionnalité marquée comme prise en charge est couverte par des tests, dont des tests de bout en bout avec le client du SDK TypeScript officiel. ## Pris en charge | Domaine | Fonctionnalité | | --- | --- | | Cycle de vie | `initialize` avec négociation de version, `notifications/initialized`, `ping` | | Outils | `tools/list`, `tools/call`, validation de l’entrée, `outputSchema` et `structuredContent`, annotations, titres | | Contenu | texte, image, audio, liens de ressources, ressources intégrées | | Ressources | `resources/list`, `resources/templates/list`, `resources/read`, contenus texte et binaires | | Prompts | `prompts/list`, `prompts/get`, arguments déduits des schémas | | Utilitaires | `notifications/cancelled` avec `AbortSignal` | | JSON-RPC | Lots, codes d’erreur, réponses du client ignorées | | stdio | JSON délimité par des retours à la ligne, stdout protégé des logs égarés | | Streamable HTTP | Réponses JSON sans état, `202` pour les notifications, `405` pour `GET`, CORS, vérification de `Origin` et de `MCP-Protocol-Version` | ## Pas encore pris en charge | Fonctionnalité | Pourquoi | | --- | --- | | Sampling, elicitation, roots | Ils exigent que le serveur envoie des requêtes au client. La conception sans état de picomcp ne garde pas encore de canal ouvert pour cela. | | Notifications de log, de progression et `list_changed` | Même raison : des messages du serveur vers le client. | | Abonnements aux ressources | Même raison. | | Complétions | Prévu. | | Curseurs de pagination | Les listes sont renvoyées en entier, ce qui convient aux serveurs qui ont jusqu’à quelques centaines d’entrées. | | Sessions (`Mcp-Session-Id`) et flux SSE | Inutiles en mode sans état. | | Tasks | Expérimental dans la spécification. | Vous avez besoin de l’une d’elles ? [Ouvrez une issue](https://github.com/picomcp/picomcp/issues) et décrivez votre cas d’usage. Les fonctionnalités arrivent quand on peut les ajouter sans alourdir picomcp. --- # Pour les agents IA Source: https://picomcp.github.io/picomcp/fr/docs/agents > Une doc lisible par les machines, llms.txt, des pages Markdown et des règles à donner à votre agent de code. Les agents de code écrivent beaucoup de serveurs MCP. Cette documentation est conçue pour être lue par eux autant que par vous. ## Une doc lisible par les machines | URL | Contenu | | --- | --- | | [`/llms.txt`](https://picomcp.github.io/picomcp/llms.txt) | L’index de toutes les pages avec un résumé d’une ligne, au format [llms.txt](https://llmstxt.org) | | [`/llms-full.txt`](https://picomcp.github.io/picomcp/llms-full.txt) | Toute la documentation dans un seul fichier Markdown | | `/docs/.md` | N’importe quelle page en Markdown. Ajoutez `.md` à son URL, par exemple [`/docs/tools.md`](https://picomcp.github.io/picomcp/fr/docs/tools.md) | Chaque page a aussi un bouton **Copier en Markdown**, pour la coller dans une conversation. Ces fichiers sont en anglais, la langue de référence et celle que les modèles suivent le mieux. Chaque traduction a aussi son propre `llms-full.txt`, par exemple `/fr/llms-full.txt`. ## Orienter votre agent vers la doc Collez ceci dans les instructions de votre agent, par exemple dans `AGENTS.md` ou `CLAUDE.md` à la racine de votre projet : ```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. ``` ## Donner un serveur de doc à votre agent La documentation existe aussi sous forme de serveur MCP, [`picomcp-docs`](https://www.npmjs.com/package/picomcp-docs), construit avec picomcp. Il donne aux agents trois outils, `list_docs`, `search_docs` et `read_doc`, et chaque page en tant que ressource. Dans Claude Code : ```sh claude mcp add picomcp-docs -- npx -y picomcp-docs ``` Dans Cursor, Windsurf, Claude Desktop et la plupart des autres clients, ajoutez-le au fichier de configuration MCP (`.cursor/mcp.json` pour Cursor) : ```json { "mcpServers": { "picomcp-docs": { "command": "npx", "args": ["-y", "picomcp-docs"] } } } ``` Le code source est dans [`packages/docs`](https://github.com/picomcp/picomcp/tree/main/packages/docs), environ 100 lignes. ## Ce que les agents se trompent souvent Voici les erreurs que font le plus souvent les modèles quand ils écrivent des serveurs MCP, et comment les choses fonctionnent dans picomcp : - **En stdio, stdout est le protocole.** Un `console.log` le corromprait. picomcp redirige la sortie console vers stderr quand il sert en stdio, mais `process.stdout.write` reste dangereux. - **Les erreurs d’outil sont des résultats, pas des exceptions.** Lever une erreur dans `run` produit `isError: true` avec votre message. Le modèle le voit et peut réessayer. N’attrapez pas les erreurs juste pour renvoyer des chaînes `"Error: ..."`. - **Les arguments de prompt sont des chaînes.** Utilisez `z.coerce.number()` pour les arguments numériques. - **Le JSON Schema vient de la bibliothèque de schémas.** Zod 4 et ArkType n’ont besoin de rien. Valibot a besoin de `toStandardJsonSchema`. Les autres, de `withJsonSchema`. - **`server.fetch` ignore le chemin.** Routez `/mcp` vers lui vous-même, ou utilisez `serve({ http })`, qui ne répond que sur `/mcp`. - **Le typage de `server.call` demande d’enchaîner.** `pico().tool().tool()` conserve les types. Des instructions séparées les perdent. ## Contribuer avec un agent Le dépôt contient un [`AGENTS.md`](https://github.com/picomcp/picomcp/blob/main/AGENTS.md) avec les règles pour travailler sur picomcp lui-même : le budget de taille, les commandes de test et les contraintes de conception.