# はじめに Source: https://picomcp.github.io/picomcp/ja/docs/ > picomcp とは何か、何ではないか、そしてそれを説明する 10 行のコード。 picomcp は、[Model Context Protocol](https://modelcontextprotocol.io) サーバーを作るための TypeScript フレームワークです。MCP サーバーは AI アプリケーション(Claude、Cursor、VS Code、あなた自身のエージェント)に、呼び出せる**ツール**、読み取れる**リソース**、ユーザーに提示できる**プロンプト**を提供します。 ```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(); ``` これだけで完全なサーバーです。`node server.ts` で起動し、任意の MCP クライアントに接続できます。 ## picomcp を選ぶ理由 - **小さい。** gzip で約 4 kB、依存関係ゼロ。ライブラリ全体を頭に入れられ、1 回のコードレビューで読み切れます。 - **スキーマから型を生成。** Zod、Valibot、ArkType のスキーマを渡すだけ。ハンドラーは型付きの引数を受け取り、クライアントは JSON Schema を受け取り、不正な呼び出しには読みやすいエラーが返ります。 - **どこでも動く。** Node 22+、Bun、Deno、Cloudflare Workers、そして `fetch` を持つあらゆるランタイム。コアは Node の組み込みモジュールを使いません。 - **2 つのトランスポートに 1 つのサーバー。** デスクトップクライアントには stdio、リモートサーバーには Streamable HTTP。オプション 1 つで切り替えられます。 - **クライアントなしでテスト可能。** `server.call("forecast", { city: "Lyon" })` でツールをプロセス内で実行でき、型も完全に効きます。 - **公式 SDK で検証済み。** テストスイートは `@modelcontextprotocol/sdk` を使い、両方のトランスポートで picomcp を動かしています。 ## picomcp ではないもの picomcp が実装するのは MCP のサーバー側だけです。クライアント、エージェントループ、LLM SDK は含みません。ほとんどのサーバーに必要な、プロトコルのうちステートレスなリクエスト/レスポンスの部分に注力しています。sampling や elicitation のようにサーバーから開始する機能はまだサポートしていません。正確な一覧は[プロトコル対応状況](https://picomcp.github.io/picomcp/ja/docs/protocol)を参照してください。 ## 次のステップ - [クイックスタート](https://picomcp.github.io/picomcp/ja/docs/quickstart):5 分でサーバーを作って接続する。 - [ツール](https://picomcp.github.io/picomcp/ja/docs/tools):ツールにできることのすべて。 - [AI エージェント向け](https://picomcp.github.io/picomcp/ja/docs/agents):URL 1 つでコーディングエージェントにドキュメントを渡す。 --- # クイックスタート Source: https://picomcp.github.io/picomcp/ja/docs/quickstart > 最初の MCP サーバーを 5 分で作成、テスト、接続します。 Node 22.18 以降(TypeScript ファイルを直接実行できます)、Bun、または Deno が必要です。 ## プロジェクトを作成する ```sh npx picomcp init my-server cd my-server npm install ``` すぐに動かせるサーバーが作成されます: | ファイル | 役割 | | --- | --- | | `server.ts` | サーバーの定義:ツール、リソース、プロンプト | | `main.ts` | stdio でサーバーを起動する | | `server.test.ts` | ツールをプロセス内で呼び出すテスト | ゼロから始めたい場合は、2 つのパッケージをインストールして `server.ts` を自分で書きます: ```sh npm install picomcp zod ``` ## ツールを書く `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` は、モデルがいつツールを呼び出すかを判断するために読むものです。モデルに向けて書いてください。 - `input` には任意の [Standard Schema](https://picomcp.github.io/picomcp/ja/docs/schemas) を渡せます。`run` の中で `name` は `string` 型になります。 - ここでは `run` は文字列を返しています。オブジェクトやコンテンツブロックを返すことも、エラーを投げることもできます。詳しくは[ツール](https://picomcp.github.io/picomcp/ja/docs/tools)を参照してください。 ## テストする ```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` は型チェックされます。ツール名のタイプミスや引数の不足はコンパイルエラーになります。 ## 接続する Claude Code の場合: ```sh claude mcp add my-server -- node /absolute/path/to/my-server/main.ts ``` あとは Claude に誰かへの挨拶を頼んでみてください。Claude Desktop、Cursor、VS Code などについては[クライアントを接続する](https://picomcp.github.io/picomcp/ja/docs/clients)を参照してください。 ## 中身を調べる MCP Inspector を使うと、ツールを手動で呼び出して生のメッセージを確認できます: ```sh npx @modelcontextprotocol/inspector node main.ts ``` --- # クライアントを接続する Source: https://picomcp.github.io/picomcp/ja/docs/clients > Claude Code、Claude Desktop、Cursor、VS Code、MCP Inspector の設定方法。 どの MCP クライアントも、ローカルサーバーを同じ方法で起動します。コマンドを実行し、stdio で通信するのです。リモートサーバーには URL を使い、Streamable HTTP で接続します。以下の設定では、すべて絶対パスを使ってください。 ## 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 ``` `--scope project` を付けると、サーバーが `.mcp.json` に書き込まれ、チームと共有できます。 ## Claude Desktop `claude_desktop_config.json` を編集し(設定 → 開発者 → 構成を編集)、アプリを再起動します: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] } } } ``` ## Cursor プロジェクトに `.cursor/mcp.json` を作成します。すべてのプロジェクトで使う場合は `~/.cursor/mcp.json` を作成します: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "url": "https://example.com/mcp" } } } ``` ## VS Code `.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 ``` HTTP サーバーを調べるには、`serve({ http: 3000 })` で起動してから Inspector を開き、**Streamable HTTP** を選んで `http://127.0.0.1:3000/mcp` を入力します。Inspector は localhost で動くため、picomcp はデフォルトで接続を許可します。 ## Bun と Deno `node` を使っているランタイムに置き換えます: ```json { "command": "bun", "args": ["/absolute/path/main.ts"] } ``` ```json { "command": "deno", "args": ["run", "--allow-net", "/absolute/path/main.ts"] } ``` ## トラブルシューティング **クライアントにツールが 1 つも表示されない。** 設定に書いたコマンドを自分で実行してみてください。すぐに終了したりエラーが出たりするなら、クライアントにも同じことが起きています。 **クライアントのログに「Unexpected token」や JSON のエラーが出る。** 何かが stdout に書き込んでいます。picomcp は stdio で動作している間 `console.log` を stderr にリダイレクトしますが、他のライブラリが `process.stdout` に直接書き込んでいる可能性があります。 **Node が `.ts` ファイルを実行できない。** Node 22.18 以降にアップグレードするか、Bun でサーバーを起動してください。 --- # ツール Source: https://picomcp.github.io/picomcp/ja/docs/tools > ツールの定義、入力の検証、テキスト・オブジェクト・メディアの返却、エラー報告、キャンセル処理。 ツールは、モデルが呼び出せる関数です。`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"); }, }); ``` ## 設定 | オプション | 型 | 説明 | | --- | --- | --- | | `description` | `string` | ツールが何をするか、いつ使うか。モデルはこの文章を読みます。 | | `title` | `string` | クライアントに表示される名前。 | | `input` | Standard Schema | 引数のスキーマ。引数のないツールでは省略します。 | | `output` | Standard Schema | 構造化された結果のスキーマ。[構造化出力](#構造化出力)を参照。 | | `annotations` | `ToolAnnotations` | クライアント向けのヒント:`readOnlyHint`、`destructiveHint`、`idempotentHint`、`openWorldHint`。 | | `run` | `(input, ctx) => value` | ハンドラー。非同期でもかまいません。 | ツール名は 1〜128 文字で、英字、数字、`_`、`.`、`-` だけを使えます。不正な名前や重複した名前を登録するとすぐにエラーになるので、ミスはモデルがツールを呼んだときではなく、サーバーの起動時に見つかります。 ## 入力の検証 picomcp は `run` を呼ぶ前に、スキーマで引数を検証します。デフォルト値と変換も適用されるので、上の例では `run` の中の `limit` は必ず数値です。 検証に失敗した場合、ツールは呼ばれません。クライアントには問題のあるフィールドを示すツールエラーが返るので、モデルは呼び出しを直して再試行できます: ```text Invalid arguments for tool "search_issues": limit: Too big: expected number to be <=50 ``` ## 戻り値 自然な形で返してください。picomcp が MCP の結果に変換します: | `run` の戻り値 | クライアントが受け取るもの | | --- | --- | | `string` | テキストブロック 1 つ | | オブジェクト、配列、数値 | その JSON をテキストとして。オブジェクトは `structuredContent` としても送られます。 | | コンテンツブロック、またはその配列 | それらのブロックをそのまま | | `{ content: [...] }` | 結果をそのまま。細かく制御したいとき向け | | `undefined` | 空の結果 | コンテンツブロックはヘルパーで作ります: ```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` スキーマを宣言します: ```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 は: 1. `tools/list` でスキーマを `outputSchema` として公開し、 2. `run` の戻り値を型レベルでチェックし、 3. 実行時にも値を検証して `structuredContent` として送ります。古いクライアント向けに JSON のテキストも添えます。 値がスキーマに合わない場合、クライアントは壊れたデータではなくツールエラーを受け取ります。 ## エラー `run` の中でエラーを投げてください。そのメッセージはツールエラー(`isError: true`)になり、モデルはそれを読んで対応できます: ```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; }; ``` エラーメッセージはモデルに向けて書きましょう。何がうまくいかなかったのか、次に何をすべきかを伝えます。 ツールエラーは通常の会話の一部ですが、プロトコルエラーは違います。`McpError` は、ユーザーに権限がないなど、リクエスト自体が誤っている場合にだけ使ってください: ```ts import { McpError } from "picomcp"; throw new McpError(-32001, "Not authorised for this workspace"); ``` ## キャンセル `run` の第 2 引数には `AbortSignal` が入っています。クライアントが `notifications/cancelled` を送ると発火します。たいていはユーザーが停止ボタンを押したときです。`fetch` など、シグナルを受け付ける API に渡してください: ```ts run: async ({ url }, { signal }) => { const res = await fetch(url, { signal }); return res.text(); }; ``` リクエストがキャンセルされると、プロトコルの規定どおり、picomcp はそのリクエストに応答を返しません。 ## メタデータ クライアントがリクエストに `_meta` オブジェクトを付けて送った場合、`ctx.meta` に入ります。クライアントはこれをトレースや進捗トークンに使います。 ## 良いツールを書くために - **1 つのツールに 1 つの仕事。** `invoice(action)` よりも `create_invoice` と `send_invoice` のほうが良い設計です。 - **引数を説明する。** 各フィールドの `.describe()` は、モデルが見る JSON Schema に反映されます。 - **返す量を減らす。** モデルはトークンごとにコストがかかります。API レスポンス全体ではなく、必要なフィールドだけを返しましょう。 - **正直にアノテーションする。** `readOnlyHint: true` があると、クライアントは確認を省略できます。書き込みを行うツールには絶対に付けないでください。 --- # リソース Source: https://picomcp.github.io/picomcp/ja/docs/resources > クライアントが読み取れるファイル、レコード、ライブデータを、型付きの URI テンプレートで公開します。 リソースは、クライアントが読み取って会話に添付できるデータです。ファイル、データベースの行、API レスポンスなどがあります。ツールはモデルが呼び出しますが、リソースは通常ユーザーやアプリケーションが選びます。 ```ts server.resource("config://app", { description: "The app configuration", read: () => loadConfig(), }); ``` ## URI テンプレート URI に `{variables}` を入れると、同じ種類のリソースをまとめて提供できます。変数の型は文字列そのものから推論されます: ```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(); }, }); ``` | 構文 | マッチするもの | 例 | | --- | --- | --- | | `{name}` | パスの 1 セグメント。`/`、`?`、`#` を含まない | `users://{id}` は `users://42` にマッチ | | `{+name}` | `/` を含むあらゆる文字列 | `files://{+path}` は `files://src/index.ts` にマッチ | 値は URL デコードされてから `read` に渡されます。静的なリソースは `resources/list` に、テンプレートは `resources/templates/list` に表示されます。 ## 戻り値 | `read` の戻り値 | 送られる内容 | | --- | --- | | `string` | テキスト。`mimeType` または `text/plain` | | `Uint8Array` | base64 の blob。`mimeType` または `application/octet-stream` | | `{ uri, text }` または `{ uri, blob }` の配列 | それらの内容をそのまま | | その他の値 | その JSON。`application/json` | `read` の第 2 引数にはコンテキストが渡され、要求された `uri` も含まれます: ```ts read: (params, { uri, signal }) => fetch(toHttp(uri), { signal }).then((r) => r.text()); ``` ## オプション | オプション | 説明 | | --- | --- | | `name` | ユーザーに表示される識別子。デフォルトは URI。 | | `title` | 表示名。 | | `description` | リソースの内容。 | | `mimeType` | `read` が返す内容の種類。 | | `annotations` | `audience`、`priority`、`lastModified` のヒント。 | ## 見つからない場合 URI にマッチするリソースがない場合、picomcp はプロトコルエラー `-32002 Resource not found` を返します。テンプレートにはマッチするものの実体がない URI を拒否するには、同じコードで `McpError` を投げます: ```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; }; ``` ## ツールからリンクする ツールは、リソースの中身を埋め込む代わりに、モデルにリソースを指し示すことができます。大きなデータを、必要になるまでコンテキストに入れずに済みます: ```ts import { link } from "picomcp"; run: async () => link("reports://2026-q3", "Q3 report", { mimeType: "application/pdf" }); ``` --- # プロンプト Source: https://picomcp.github.io/picomcp/ja/docs/prompts > ユーザーがクライアントで選べる再利用可能なプロンプトテンプレートを、引数の検証付きで提供します。 プロンプトは、ユーザーがクライアントで選ぶテンプレートです。スラッシュコマンドとして表示されることがよくあります。いくつかの引数を、1 つ以上のメッセージに変換します。 ```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}`, }); ``` クライアントには `diff` が必須、`focus` が任意として、それぞれの説明とともに表示されます。どれもスキーマから導かれます。 ## 引数は文字列 MCP はプロンプトの引数を文字列として送ります。文字列のスキーマを使うか、型変換してください: ```ts args: z.object({ count: z.coerce.number().int().min(1) }); ``` 不正な引数は、`run` が呼ばれる前に `Invalid params` エラーで拒否されます。 ## 戻り値 | `run` の戻り値 | 送られるメッセージ | | --- | --- | | `string` | ユーザーメッセージ 1 つ | | `PromptMessage[]` | それらのメッセージ | | `{ description, messages }` | 完全な結果 | 複数ターンのテンプレートやメディアを含める場合は、配列を使います: ```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") }, ]; ``` --- # スキーマ Source: https://picomcp.github.io/picomcp/ja/docs/schemas > Zod、Valibot、ArkType など任意の Standard Schema ライブラリの使い方と、JSON Schema の生成方法。 picomcp はスキーマライブラリを同梱していません。[Standard Schema](https://standardschema.dev) を実装したライブラリならどれでも受け付け、MCP クライアントに必要な JSON Schema を [Standard JSON Schema](https://standardschema.dev/json-schema) 経由で生成します。使い慣れたライブラリをそのまま使えて、picomcp は小さいままです。 ## Zod 4 そのまま使えます: ```ts import { z } from "zod"; server.tool("forecast", { input: z.object({ city: z.string().describe("City name") }), run: ({ city }) => getForecast(city), }); ``` ## ArkType そのまま使えます: ```ts import { type } from "arktype"; server.tool("forecast", { input: type({ city: "string" }), run: ({ city }) => getForecast(city), }); ``` ## Valibot Valibot は JSON Schema のサポートを別パッケージに分けています。スキーマを `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), }); ``` ## その他のライブラリ Standard Schema で検証はできても JSON Schema を生成できないライブラリ(たとえば Zod 3)の場合は、`withJsonSchema` で自分で JSON Schema を添えます: ```ts import { withJsonSchema } from "picomcp"; server.tool("forecast", { input: withJsonSchema(schema, { type: "object", properties: { city: { type: "string" } }, required: ["city"], }), run: ({ city }) => getForecast(city), }); ``` これがないと、ツールの登録時にまさにそのことを伝えるエラーが投げられます。問題がクライアントまで届くことはありません。 ## 型の流れ - `run` が受け取るのはスキーマの**出力**型です。デフォルト値と変換が適用された後の型です。 - `server.call()` が受け付けるのはスキーマの**入力**型です。そのため、テストではデフォルト値のあるフィールドを省略できます。 - `output` スキーマがある場合、`run` はそのスキーマの入力型を返す必要があります。 ## モデルが理解しやすいスキーマのコツ - すべてのフィールドに説明を付ける。説明は、モデルが読むドキュメントそのものです。 - 取りうる値が決まっているなら、自由記述より enum を使う。 - 任意の挙動にはデフォルト値を使い、モデルが引数を省略できるようにする。 - オブジェクトはフラットに保つ。深くネストした引数は、モデルが正しく埋めにくくなります。 --- # トランスポート Source: https://picomcp.github.io/picomcp/ja/docs/transports > ローカルクライアント向けの stdio、リモート向けの Streamable HTTP、そして両方を支える fetch ハンドラー。 トランスポートは、クライアントとサーバーの間で JSON-RPC メッセージを運びます。picomcp は MCP が定義する 2 つのトランスポートに対応しています。どちらを使っても、ツールのコードは変わりません。 ## stdio ```ts await server.serve(); ``` クライアントはサーバーを子プロセスとして起動し、改行区切りの JSON を stdin と stdout でやり取りします。デスクトップクライアントやコーディングエージェントは、ローカルサーバーをこの方法で使います。 stdout はプロトコル専用です。stdio で動作している間、picomcp は `console.log`、`console.info`、`console.debug` を stderr にリダイレクトするので、うっかり出力したログで接続が壊れることはありません。クライアントは通常、stderr を自身のログに表示します。 ## 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 })` は、実行中のランタイム(Bun、Deno、Node)でサーバーを起動します。オプション: | オプション | デフォルト | 説明 | | --- | --- | --- | | `port` | `3000` | 待ち受けるポート。`0` なら空いているポートを選びます。 | | `hostname` | `"127.0.0.1"` | 他のマシンからの接続を受け付けるには `"0.0.0.0"` を使います。 | | `path` | `"/mcp"` | エンドポイントのパス。それ以外のパスは 404 を返します。 | 戻り値は `{ url, close() }` です。 ## fetch ハンドラー `server.fetch` は、Web 標準の `(Request) => Promise` 関数です。`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` はパスを見ません。リクエストのルーティングは自分で行ってください。その他のプラットフォームは[デプロイ](https://picomcp.github.io/picomcp/ja/docs/deploy)を参照してください。 ## picomcp の HTTP の扱い picomcp は Streamable HTTP をステートレスな JSON モードで動かします: - JSON-RPC メッセージまたはバッチを含む `POST` → JSON のレスポンスとともに `200`。通知の場合は `202`。 - `GET` と `DELETE` → `405`。サーバーからクライアントへのストリームを提供しないことをクライアントに伝えます。 - `OPTIONS` → 許可されたオリジン向けの CORS プリフライト。 - サポートしていない `MCP-Protocol-Version` ヘッダー → `400`。 保存すべきセッションがないので、どのインスタンスでもどのリクエストにも応答できます。そのため picomcp は、サーバーレスプラットフォーム、ロードバランサー、オートスケーリングに追加の作業なしで対応できます。 ## オリジンと DNS リバインディング ブラウザは `Origin` ヘッダーを送ります。悪意のあるページがそれを使ってあなたのマシン上のサーバーにアクセスするおそれがあるため、picomcp はこのヘッダーをチェックします: - `Origin` のないリクエスト(CLI、サーバー、デスクトップアプリ)は常に許可されます。 - デフォルトでは、`localhost`、`127.0.0.1`、`[::1]` のオリジンだけが許可されます。ポートは問いません。 - それ以外のオリジンには `403` が返ります。 `origins` オプションで許可するオリジンを追加できます: ```ts pico({ name: "notes", origins: ["https://claude.ai", "https://app.example.com"] }); pico({ name: "public", origins: "*" }); // any origin: only for public, unauthenticated servers ``` ## カスタムトランスポート `server.handle(message)` は JSON-RPC メッセージ 1 つ、またはバッチを処理してレスポンスを返します。通知に対しては `undefined` を返します。これを使えば、WebSocket やメッセージキューなど、何にでも picomcp をつなげられます: ```ts socket.on("message", async (data) => { const response = await server.handle(JSON.parse(data)); if (response) socket.send(JSON.stringify(response)); }); ``` --- # テスト Source: https://picomcp.github.io/picomcp/ja/docs/testing > ツールを完全な型付きでプロセス内テストし、その後プロトコルをエンドツーエンドでテストします。 ## ツールをプロセス内で呼び出す `server.call(name, input)` は、トランスポートもサブプロセスも使わずに、クライアントとまったく同じようにツールを実行します。検証、デフォルト値、出力のチェック、エラー処理はすべて有効です。 ```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); }); }); ``` どのテストランナーでも動きます:Vitest、`node:test`、`bun test`、`deno test`。 ## 型チェックされる呼び出し ツールをチェーンで登録すると、`call` はツール名と入力を把握します: ```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 ``` ツールを別々の文で登録すると、`server` の型はそれを追跡できず、`call` はどんな名前も受け付けてしまいます。呼び出しをチェーンするか、再代入してください:`const app = server.tool(...)`。 ## プロトコルをテストする `server.handle` は生の JSON-RPC を受け取ります。一覧やスキーマの確認に便利です: ```ts const res = await server.handle({ jsonrpc: "2.0", id: 1, method: "tools/list" }); expect(res).toMatchSnapshot(); ``` `tools/list` のスナップショットを取っておけば、スキーマの変更でモデルに見える内容が変わったことに手軽に気づけます。 ## 本物のクライアントでテストする エンドツーエンドのテストでは、公式 SDK のクライアントから 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(); ``` picomcp 自身のテストスイートも、stdio と HTTP の両方でまさにこれを行っています。 --- # デプロイ Source: https://picomcp.github.io/picomcp/ja/docs/deploy > Cloudflare Workers、Deno Deploy、Bun、Node、Hono、Next.js でリモート MCP サーバーを認証付きで動かします。 リモートサーバーには、URL を使って Streamable HTTP で接続します。picomcp はステートレスなので、追加の設定なしでサーバーレスプラットフォーム上で動きます。 ## 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; ``` ## 認証 picomcp 自体は認証を扱いません。`server.fetch` をラップして、リクエストがツールに届く前にチェックしてください。 ```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); }, }; ``` クライアントは設定に書いたヘッダーを送ります。たとえば: ```sh claude mcp add --transport http notes https://notes.example.com/mcp --header "Authorization: Bearer $TOKEN" ``` OAuth を使う場合は、プロバイダーのミドルウェアを `server.fetch` の前に置き、[MCP の認可仕様](https://modelcontextprotocol.io/specification/latest/basic/authorization)に従ってください。 ## チェックリスト - HTTPS で提供する。 - サーバーが公開かつ読み取り専用でない限り、認証を必須にする。 - 想定するブラウザのオリジンがあれば、`origins` に設定する。 - シークレットは環境変数に置き、ツールの説明や結果には絶対に含めない。 - ツールの引数は信頼できない入力として扱う。モデルは読んだ内容に影響されることがあります。 --- # API リファレンス Source: https://picomcp.github.io/picomcp/ja/docs/api > picomcp のすべてのエクスポートとそのシグネチャ。 ```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) サーバーを作成します。 ```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) ツールを登録し、サーバーを返します。[ツール](https://picomcp.github.io/picomcp/ja/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; } ``` 名前が不正または重複している場合、またはスキーマが JSON Schema に対応していない場合は `TypeError` を投げます。 ## server.resource(uri, config) リソースを登録します。`uri` に `{variables}` が含まれる場合はリソーステンプレートとして登録します。サーバーを返します。[リソース](https://picomcp.github.io/picomcp/ja/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) プロンプトを登録し、サーバーを返します。[プロンプト](https://picomcp.github.io/picomcp/ja/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?) ツールをプロセス内で実行し、その `CallToolResult` を返します。ツールをチェーンで登録した場合は型チェックされます。未知のツール名の場合は Promise が reject されます。[テスト](https://picomcp.github.io/picomcp/ja/docs/testing)を参照。 ```ts call(name: string, input?: unknown): Promise; interface CallToolResult { content: ContentBlock[]; structuredContent?: Record; isError?: boolean; } ``` ## server.serve(options?) stdio でサーバーを起動します。`http` を指定した場合は Streamable HTTP で起動します。[トランスポート](https://picomcp.github.io/picomcp/ja/docs/transports)を参照。 ```ts serve(options?: { http?: number | { port?: number; hostname?: string; path?: string } }): Promise<{ url?: string; close(): Promise }>; ``` ## server.fetch(request) Web 標準の Streamable HTTP ハンドラー。サーバーにバインドされているので、値としてそのまま渡せます。 ```ts fetch(request: Request): Promise; ``` ## server.handle(message) JSON-RPC メッセージ 1 つ、またはバッチを処理します。レスポンスを返し、通知に対しては `undefined` を返します。 ```ts handle(message: unknown): Promise; ``` ## Context すべてのハンドラーの第 2 引数。 ```ts interface Context { signal: AbortSignal; // aborted when the client cancels meta: Record | undefined; // the request's _meta } ``` ## コンテンツヘルパー ```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 文字列か生のバイト列です。バイト列はどのランタイムでも自動的にエンコードされます。 ## withJsonSchema(schema, jsonSchema, output?) JSON Schema を生成できない Standard Schema に JSON Schema を添えます。[スキーマ](https://picomcp.github.io/picomcp/ja/docs/schemas)を参照。 ```ts withJsonSchema(schema: S, jsonSchema: JsonSchema, output?: JsonSchema): StandardSchemaV1, Infer>; ``` ## McpError ハンドラーから投げると、結果の代わりに JSON-RPC エラーを返します。 ```ts new McpError(code: number, message: string, data?: unknown); ``` | コード | 意味 | | --- | --- | | `-32602` | 不正なパラメーター | | `-32002` | リソースが見つからない | | `-32000` 〜 `-32099` | 独自のサーバーエラーに自由に使える | ## PROTOCOL_VERSIONS picomcp が対応する MCP プロトコルのバージョン。新しい順:`["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"]`。 ## 型ヘルパー | 型 | 説明 | | --- | --- | | `Infer` | スキーマの出力型 | | `InferInput` | スキーマの入力型 | | `UriParams<"a://{x}/{+y}">` | `{ x: string; y: string }` | | `StandardSchemaV1` | 同梱の Standard Schema インターフェース | --- # プロトコル対応状況 Source: https://picomcp.github.io/picomcp/ja/docs/protocol > picomcp が MCP 仕様のどの部分を実装していて、どの部分がまだなのか。 picomcp は MCP `2025-11-25` を対象とし、`2025-06-18`、`2025-03-26`、`2024-11-05` へのネゴシエーションにも対応しています。対応済みとした機能はすべてテストでカバーされており、公式 TypeScript SDK のクライアントを使ったエンドツーエンドのテストも含まれます。 ## 対応済み | 領域 | 機能 | | --- | --- | | ライフサイクル | バージョンネゴシエーション付きの `initialize`、`notifications/initialized`、`ping` | | ツール | `tools/list`、`tools/call`、入力の検証、`outputSchema` と `structuredContent`、アノテーション、タイトル | | コンテンツ | テキスト、画像、音声、リソースリンク、埋め込みリソース | | リソース | `resources/list`、`resources/templates/list`、`resources/read`、テキストとバイナリのコンテンツ | | プロンプト | `prompts/list`、`prompts/get`、スキーマから導かれる引数 | | ユーティリティ | `AbortSignal` による `notifications/cancelled` | | JSON-RPC | バッチ、エラーコード、クライアントからのレスポンスは無視 | | stdio | 改行区切りの JSON、うっかり出力したログから stdout を保護 | | Streamable HTTP | ステートレスな JSON レスポンス、通知には `202`、`GET` には `405`、CORS、`Origin` と `MCP-Protocol-Version` のチェック | ## 未対応 | 機能 | 理由 | | --- | --- | | Sampling、elicitation、roots | サーバーからクライアントへリクエストを送る必要があります。picomcp のステートレスな設計では、そのためのチャネルをまだ開いたままにしていません。 | | ログ、進捗、`list_changed` の通知 | 同じ理由です。サーバーからクライアントへのメッセージです。 | | リソースの購読 | 同じ理由です。 | | 補完 | 予定しています。 | | ページネーションのカーソル | 一覧はすべて一度に返します。数百件程度までのサーバーならこれで十分です。 | | セッション(`Mcp-Session-Id`)と SSE ストリーム | ステートレスモードでは不要です。 | | Tasks | 仕様上まだ実験的な機能です。 | どれかが必要ですか? [issue を作成](https://github.com/picomcp/picomcp/issues)して、ユースケースを教えてください。picomcp を大きくせずに追加できる機能から取り入れていきます。 --- # AI エージェント向け Source: https://picomcp.github.io/picomcp/ja/docs/agents > 機械可読なドキュメント、llms.txt、Markdown ページ、そしてコーディングエージェントに渡すルール。 コーディングエージェントは多くの MCP サーバーを書きます。このドキュメントは、あなたと同じようにエージェントにも読みやすく作られています。 ## 機械可読なドキュメント | URL | 内容 | | --- | --- | | [`/llms.txt`](https://picomcp.github.io/picomcp/llms.txt) | 全ページの索引と 1 行の要約。[llms.txt](https://llmstxt.org) 形式 | | [`/llms-full.txt`](https://picomcp.github.io/picomcp/llms-full.txt) | ドキュメント全体を 1 つの Markdown ファイルにまとめたもの | | `/docs/.md` | 任意のページの Markdown。URL に `.md` を付けます。例:[`/docs/tools.md`](https://picomcp.github.io/picomcp/ja/docs/tools.md) | 各ページには**Markdown でコピー**ボタンもあり、チャットにそのまま貼り付けられます。 これらのファイルは英語です。英語が正本の言語であり、モデルが最もよく従える言語でもあります。各翻訳にも専用の `llms-full.txt` があります。例:`/fr/llms-full.txt`。 ## エージェントにドキュメントを教える 次の内容をエージェントの指示に貼り付けてください。たとえばプロジェクトのルートにある `AGENTS.md` や `CLAUDE.md` です: ```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. ``` ## エージェントにドキュメントサーバーを渡す ドキュメントは MCP サーバー [`picomcp-docs`](https://www.npmjs.com/package/picomcp-docs) としても提供しており、これ自体も picomcp で作られています。エージェントに `list_docs`、`search_docs`、`read_doc` の 3 つのツールを提供し、各ページをリソースとして公開します。 Claude Code の場合: ```sh claude mcp add picomcp-docs -- npx -y picomcp-docs ``` Cursor、Windsurf、Claude Desktop など多くのクライアントでは、MCP の設定ファイルに追加します(Cursor では `.cursor/mcp.json`): ```json { "mcpServers": { "picomcp-docs": { "command": "npx", "args": ["-y", "picomcp-docs"] } } } ``` ソースコードは [`packages/docs`](https://github.com/picomcp/picomcp/tree/main/packages/docs) にあり、約 100 行です。 ## エージェントがよく間違えること モデルが MCP サーバーを書くときに最もよくある間違いと、picomcp での正しい動作です: - **stdio モードでは stdout がプロトコルそのもの。** `console.log` 1 つで壊れてしまいます。picomcp は stdio で動作している間コンソール出力を stderr にリダイレクトしますが、`process.stdout.write` は依然として危険です。 - **ツールのエラーは例外ではなく結果。** `run` の中でエラーを投げると、あなたのメッセージ付きの `isError: true` になります。モデルはそれを見て再試行できます。`"Error: ..."` という文字列を返すためだけにエラーを捕まえないでください。 - **プロンプトの引数は文字列。** 数値のプロンプト引数には `z.coerce.number()` を使います。 - **JSON Schema はスキーマライブラリから生成される。** Zod 4 と ArkType は何もいりません。Valibot には `toStandardJsonSchema` が、その他には `withJsonSchema` が必要です。 - **`server.fetch` はパスを見ない。** `/mcp` へのルーティングは自分で行うか、`/mcp` にだけ応答する `serve({ http })` を使ってください。 - **型付きの `server.call` にはチェーンが必要。** `pico().tool().tool()` なら型が保たれます。別々の文で書くと型が失われます。 ## エージェントと一緒に貢献する リポジトリには [`AGENTS.md`](https://github.com/picomcp/picomcp/blob/main/AGENTS.md) があり、picomcp 自体の開発ルールがまとめられています。サイズの上限、テストのコマンド、設計上の制約です。