19 — MCP Authoring
Three primitive types:
- Tools — callable functions (JSON schema input → structured output). Model decides when to call. "Do something."
- Resources — addressable content (files, DB rows, feeds) returned as text/binary. "Read something."
- Prompts — reusable templates with typed args. "Fill and inject."
Source authority: modelcontextprotocol.io/introduction, @modelcontextprotocol/sdk NPM.
When to author an MCP server
Build when:
- A REST API/Worker would provide genuine agent value and schema-wrapping cost < benefit
- Tool set needs sharing across multiple Claude sessions without copy-pasting prompts
- CF Worker owns business logic and you want Claude persistent auth-aware access (HTTP transport = zero extra infra)
- Extending the forge pipeline (
--target=mcp-server— seeforge-mcp-from-openapi.md)
Do NOT build when a simple [[hono-api]] route + direct fetch suffices — MCP adds SDK overhead not justified for one-off integrations.
Architecture
Claude Code / Claude Desktop
│ JSON-RPC 2.0
▼
┌──────────────┐
│ MCP Server │
│ ┌────────┐ │
│ │ tools │ │ ← JSON-schema validated inputs + Zod-validated outputs
│ ├────────┤ │
│ │resourc.│ │ ← URI-addressed, MIME-typed
│ ├────────┤ │
│ │prompts │ │ ← Named templates with typed args
│ └────────┘ │
└──────────────┘
│
▼
External system (D1 / R2 / Vectorize / external API)
Every tool input: z.parse() before hitting the system. Every result: Zod-validated before returning. Per [[contract-first-ai]] and [[zod-everywhere]].
Transport decision
| Criterion | stdio | HTTP + SSE |
|---|---|---|
| Where it runs | Local process, same machine as Claude | Any origin — CF Workers, remote server |
| Auth | None (process-level trust) | HTTP headers, Bearer tokens, CF Zero Trust |
| Session state | Process lifetime | DO / KV per session ID |
| Streaming | Native (stdout) | SSE (text/event-stream) |
| Setup | ~/.claude.json mcpServers entry |
CF Worker deploy + .claude.json remote entry |
| Best for | Dev tools, local scripts, secret-laden CLIs | Shared team tools, SaaS integrations, per-user auth |
Per [[cloudflare-lock-in-is-leverage]]: prefer HTTP on CF Workers over any third-party MCP host.
.claude.json registration
stdio server
{
"mcpServers": {
"my-local-tool": {
"command": "node",
"args": ["/absolute/path/to/mcp-server/dist/index.js"],
"env": { "DB_PATH": "/Users/Apple/data/mydb.sqlite" }
}
}
}
HTTP server (CF Workers)
{
"mcpServers": {
"my-worker-tool": {
"url": "https://my-mcp.workers.dev/mcp",
"headers": { "Authorization": "Bearer ${MY_MCP_TOKEN}" }
}
}
}
Place at ~/.claude.json (global) or .claude.json at repo root (project-scoped).
Sub-modules
stdio-server-template.md— complete TypeScript stdio server with sample tool + resource + prompthttp-server-on-workers.md— Hono + MCP SDK + SSE on CF Workers, wrangler.toml, authforge-mcp-from-openapi.md— plan for extendingbin/forge-skill-from-openapi.mjsto emit MCP servers
Quality gates (every MCP server)
- All tool inputs have a Zod schema — never accept raw
unknown - All tool results conform to a Zod output schema before returning
- Errors return MCP
isError: truewith structured{ code, message }— never throw raw JS errors - Every tool
description≤2 sentences, specific enough for an LLM to decide when to call it - No secret values in tool schemas or resource URIs — pass via
envblock in.claude.json - Smoke-test with
npx @modelcontextprotocol/inspectorbefore registering
Cross-links
[[cloudflare-lock-in-is-leverage]]— Workers HTTP transport over any third-party MCP host[[ai-agent-supervisor]]— MCP tools are the supervised boundary for agent actions[[contract-first-ai]]— Zod at every tool boundary[[hono-api]]— HTTP transport built on Hono05-architecture-and-stack/cf-agents-do-pattern.md— stateful MCP sessions via Durable Objectsrules/ai-agent-security.md— tool scope minimization, input sanitization, rate limiting