mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-03-20 11:33:08 +00:00
feat(plugin): mcp-server-dev — close remote-path dead-end with deploy/test/connect guidance
The remote HTTP path (starred default) previously ended at localhost:3000 with no next steps. Adds CF Workers deploy reference, Inspector test commands, user-connection guide, and a versions ledger. Swaps the build-mcp-app scaffold from stdio to streamable-HTTP to match the remote-first stance.
This commit is contained in:
@@ -174,16 +174,17 @@ Keep widgets **small and single-purpose**. A picker picks. A chart displays. Don
|
|||||||
**Install:**
|
**Install:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps zod
|
npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps zod express
|
||||||
```
|
```
|
||||||
|
|
||||||
**Server (`src/server.ts`):**
|
**Server (`src/server.ts`):**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||||
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE }
|
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE }
|
||||||
from "@modelcontextprotocol/ext-apps/server";
|
from "@modelcontextprotocol/ext-apps/server";
|
||||||
|
import express from "express";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -206,9 +207,19 @@ registerAppResource(server, "Contact Picker", "ui://widgets/picker.html", {},
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await server.connect(new StdioServerTransport());
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
app.post("/mcp", async (req, res) => {
|
||||||
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
||||||
|
res.on("close", () => transport.close());
|
||||||
|
await server.connect(transport);
|
||||||
|
await transport.handleRequest(req, res, req.body);
|
||||||
|
});
|
||||||
|
app.listen(process.env.PORT ?? 3000);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For local-only widget apps (driving a desktop app, reading local files), swap the transport to `StdioServerTransport` and package via the `build-mcpb` skill.
|
||||||
|
|
||||||
**Widget (`widgets/picker.html`):**
|
**Widget (`widgets/picker.html`):**
|
||||||
|
|
||||||
```html
|
```html
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ A hosted service speaking MCP over streamable HTTP. This is the **recommended pa
|
|||||||
|
|
||||||
**Choose this unless** the server *must* touch the user's local machine.
|
**Choose this unless** the server *must* touch the user's local machine.
|
||||||
|
|
||||||
→ Scaffold with `references/remote-http-scaffold.md`
|
→ **Fastest deploy:** Cloudflare Workers — `references/deploy-cloudflare-workers.md` (zero to live URL in two commands)
|
||||||
|
→ **Portable Node/Python:** `references/remote-http-scaffold.md` (Express or FastMCP, runs on any host)
|
||||||
|
|
||||||
### Elicitation (structured input, no UI build)
|
### Elicitation (structured input, no UI build)
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ If the user already has a language/stack in mind, go with it — both produce id
|
|||||||
|
|
||||||
Once you've settled the four decisions (deployment model, tool pattern, framework, auth), do **one** of:
|
Once you've settled the four decisions (deployment model, tool pattern, framework, auth), do **one** of:
|
||||||
|
|
||||||
1. **Remote HTTP, no UI** → Scaffold inline using `references/remote-http-scaffold.md`. This skill can finish the job.
|
1. **Remote HTTP, no UI** → Scaffold inline using `references/remote-http-scaffold.md` (portable) or `references/deploy-cloudflare-workers.md` (fastest deploy). This skill can finish the job.
|
||||||
2. **MCP app (UI widgets)** → Summarize the decisions so far, then load the **`build-mcp-app`** skill.
|
2. **MCP app (UI widgets)** → Summarize the decisions so far, then load the **`build-mcp-app`** skill.
|
||||||
3. **MCPB (bundled local)** → Summarize the decisions so far, then load the **`build-mcpb`** skill.
|
3. **MCPB (bundled local)** → Summarize the decisions so far, then load the **`build-mcpb`** skill.
|
||||||
4. **Local stdio prototype** → Scaffold inline (simplest case), flag the MCPB upgrade path.
|
4. **Local stdio prototype** → Scaffold inline (simplest case), flag the MCPB upgrade path.
|
||||||
@@ -198,8 +199,10 @@ Tools are one of three server primitives. Most servers start with tools and neve
|
|||||||
## Reference files
|
## Reference files
|
||||||
|
|
||||||
- `references/remote-http-scaffold.md` — minimal remote server in TS SDK and FastMCP
|
- `references/remote-http-scaffold.md` — minimal remote server in TS SDK and FastMCP
|
||||||
|
- `references/deploy-cloudflare-workers.md` — fastest deploy path (Workers-native scaffold)
|
||||||
- `references/tool-design.md` — writing tool descriptions and schemas Claude understands well
|
- `references/tool-design.md` — writing tool descriptions and schemas Claude understands well
|
||||||
- `references/auth.md` — OAuth, CIMD, DCR, token storage patterns
|
- `references/auth.md` — OAuth, CIMD, DCR, token storage patterns
|
||||||
- `references/resources-and-prompts.md` — the two non-tool primitives
|
- `references/resources-and-prompts.md` — the two non-tool primitives
|
||||||
- `references/elicitation.md` — spec-native user input mid-tool (capability check + fallback)
|
- `references/elicitation.md` — spec-native user input mid-tool (capability check + fallback)
|
||||||
- `references/server-capabilities.md` — instructions, sampling, roots, logging, progress, cancellation
|
- `references/server-capabilities.md` — instructions, sampling, roots, logging, progress, cancellation
|
||||||
|
- `references/versions.md` — version-sensitive claims ledger (check when updating)
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# Deploy to Cloudflare Workers
|
||||||
|
|
||||||
|
Fastest path from zero to a live `https://` MCP URL. Free tier, no credit card to start, two commands to deploy.
|
||||||
|
|
||||||
|
**Trade-off:** This is a Workers-native scaffold, not a deploy target for the Express scaffold in `remote-http-scaffold.md`. Different runtime. If you need portability across hosts, stick with Express. If you just want it live, start here.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bootstrap
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm create cloudflare@latest -- my-mcp-server \
|
||||||
|
--template=cloudflare/ai/demos/remote-mcp-authless
|
||||||
|
cd my-mcp-server
|
||||||
|
```
|
||||||
|
|
||||||
|
This pulls a minimal template with the right deps (`agents`, `zod`) and a working `wrangler.jsonc`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `src/index.ts`
|
||||||
|
|
||||||
|
Replace the template's calculator example with your tools. Use `registerTool()` (same API as the Express scaffold — the `McpServer` instance is identical):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import { McpAgent } from "agents/mcp";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export class MyMCP extends McpAgent {
|
||||||
|
server = new McpServer(
|
||||||
|
{ name: "my-service", version: "0.1.0" },
|
||||||
|
{ instructions: "Prefer search_items before get_item — IDs aren't guessable." },
|
||||||
|
);
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.server.registerTool(
|
||||||
|
"search_items",
|
||||||
|
{
|
||||||
|
description: "Search items by keyword. Returns up to `limit` matches.",
|
||||||
|
inputSchema: {
|
||||||
|
query: z.string().describe("Search keywords"),
|
||||||
|
limit: z.number().int().min(1).max(50).default(10),
|
||||||
|
},
|
||||||
|
annotations: { readOnlyHint: true },
|
||||||
|
},
|
||||||
|
async ({ query, limit }) => {
|
||||||
|
const results = await upstreamApi.search(query, limit);
|
||||||
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetch(request: Request, env: Env, ctx: ExecutionContext) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
if (url.pathname === "/mcp") {
|
||||||
|
return MyMCP.serve("/mcp").fetch(request, env, ctx);
|
||||||
|
}
|
||||||
|
return new Response("Not found", { status: 404 });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`McpAgent` is Cloudflare's wrapper — it handles the streamable-HTTP transport, session routing, and Durable Object plumbing. Your code only touches `this.server`, which is the same `McpServer` class from the SDK. Everything in `tool-design.md` and `server-capabilities.md` applies unchanged.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `wrangler.jsonc`
|
||||||
|
|
||||||
|
The template ships this. The Durable Objects block is **boilerplate** — `McpAgent` uses DO for session state. You don't interact with it directly.
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"name": "my-mcp-server",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"compatibility_date": "2025-03-10",
|
||||||
|
"compatibility_flags": ["nodejs_compat"],
|
||||||
|
"migrations": [{ "new_sqlite_classes": ["MyMCP"], "tag": "v1" }],
|
||||||
|
"durable_objects": {
|
||||||
|
"bindings": [{ "class_name": "MyMCP", "name": "MCP_OBJECT" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you rename the `MyMCP` class, update both `new_sqlite_classes` and `class_name` to match.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run and deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx wrangler dev # → http://localhost:8787/mcp
|
||||||
|
npx wrangler deploy # → https://my-mcp-server.<account>.workers.dev/mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
`wrangler deploy` prints the live URL. That's the URL users paste into Claude.
|
||||||
|
|
||||||
|
Secrets (upstream API keys): `npx wrangler secret put UPSTREAM_API_KEY`, then read `env.UPSTREAM_API_KEY` inside `init()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OAuth
|
||||||
|
|
||||||
|
Cloudflare ships `@cloudflare/workers-oauth-provider` — a drop-in that handles the authorization server side (CIMD/DCR endpoints, token issuance, consent UI). It wraps your `McpAgent` and gates `/mcp` behind a token check. See `auth.md` for the protocol details; the CF template `cloudflare/ai/demos/remote-mcp-github-oauth` shows the wiring.
|
||||||
@@ -155,6 +155,48 @@ server.registerTool(
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Test it
|
||||||
|
|
||||||
|
The MCP Inspector connects to any transport and lets you poke tools interactively.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive — opens a UI on localhost:6274
|
||||||
|
npx @modelcontextprotocol/inspector
|
||||||
|
# → select "Streamable HTTP", paste http://localhost:3000/mcp, Connect
|
||||||
|
```
|
||||||
|
|
||||||
|
For scripted checks (CI, smoke tests):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx @modelcontextprotocol/inspector --cli http://localhost:3000/mcp \
|
||||||
|
--transport http --method tools/list
|
||||||
|
|
||||||
|
npx @modelcontextprotocol/inspector --cli http://localhost:3000/mcp \
|
||||||
|
--transport http --method tools/call --tool-name search_items --tool-arg query=test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connect users
|
||||||
|
|
||||||
|
Once deployed, users add the URL directly — no install step.
|
||||||
|
|
||||||
|
| Surface | How |
|
||||||
|
|---|---|
|
||||||
|
| **Claude Code** | `claude mcp add --transport http <name> <url>` (add `--scope user` for global, `--header "Authorization: Bearer ..."` for auth) |
|
||||||
|
| **Claude Desktop / Claude.ai** | Settings → Connectors → Add custom connector. **Not** `claude_desktop_config.json` — remote servers configured there are ignored. |
|
||||||
|
| **Connector directory** | Anthropic maintains a submission guide for listing in the public connector directory. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
**Fastest path:** Cloudflare Workers — two commands from zero to a live `https://` URL on the free tier. Uses a Workers-native scaffold (not Express). → `deploy-cloudflare-workers.md`
|
||||||
|
|
||||||
|
**This Express scaffold** runs on any Node host — Render, Railway, Fly.io, a VPS. Containerize it (`node:20-slim`, copy, `npm ci`, `node dist/server.js`) and ship. FastMCP is the same story with a Python base image.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Deployment checklist
|
## Deployment checklist
|
||||||
|
|
||||||
- [ ] `POST /mcp` responds to `initialize` with server capabilities
|
- [ ] `POST /mcp` responds to `initialize` with server capabilities
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Version pins
|
||||||
|
|
||||||
|
Every version-sensitive claim in this skill, in one place. When updating the skill, check these first.
|
||||||
|
|
||||||
|
| Claim | Where stated | Last verified |
|
||||||
|
|---|---|---|
|
||||||
|
| `@modelcontextprotocol/ext-apps@1.2.2` CDN pin | `build-mcp-app/SKILL.md`, `build-mcp-app/references/widget-templates.md` (4×) | 2026-03 |
|
||||||
|
| Claude Code ≥2.1.76 for elicitation | `elicitation.md:15`, `build-mcp-server/SKILL.md:43,76` | 2026-03 |
|
||||||
|
| MCP spec 2025-11-25 CIMD/DCR status | `auth.md:20,24,41` | 2026-03 |
|
||||||
|
| MCPB manifest schema v0.4 | `build-mcpb/references/manifest-schema.md` | 2026-03 |
|
||||||
|
| CF `agents` SDK / `McpAgent` API | `deploy-cloudflare-workers.md` | 2026-03 |
|
||||||
|
| CF template path `cloudflare/ai/demos/remote-mcp-authless` | `deploy-cloudflare-workers.md` | 2026-03 |
|
||||||
|
|
||||||
|
## How to verify
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ext-apps latest
|
||||||
|
npm view @modelcontextprotocol/ext-apps version
|
||||||
|
|
||||||
|
# CF template still exists
|
||||||
|
gh api repos/cloudflare/ai/contents/demos/remote-mcp-authless/src/index.ts --jq '.sha'
|
||||||
|
|
||||||
|
# MCPB schema
|
||||||
|
curl -sI https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json | head -1
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user