Files
gh-jezweb-claude-skills-ski…/templates/basic-mcp-server.ts
2025-11-30 08:24:23 +08:00

232 lines
6.4 KiB
TypeScript

/**
* Basic MCP Server (No Authentication)
*
* A simple Model Context Protocol server with basic tools.
* Demonstrates the core McpAgent pattern without authentication.
*
* Perfect for: Internal tools, development, public APIs
*
* Based on: https://github.com/cloudflare/ai/tree/main/demos/remote-mcp-authless
*
* ⚠️ CRITICAL URL CONFIGURATION:
*
* This template serves MCP at TWO base paths:
* - SSE transport: /sse
* - HTTP transport: /mcp
*
* Your client configuration MUST match:
* {
* "mcpServers": {
* "my-mcp": {
* "url": "https://YOUR-WORKER.workers.dev/sse" // ← Include /sse!
* }
* }
* }
*
* Common mistakes:
* ❌ "url": "https://YOUR-WORKER.workers.dev" // Missing /sse → 404
* ❌ "url": "http://localhost:8788" // Wrong after deploy
* ✅ "url": "https://YOUR-WORKER.workers.dev/sse" // Correct!
*
* After deploying:
* 1. Test with: curl https://YOUR-WORKER.workers.dev/sse
* 2. Update client config with exact URL from step 1
* 3. Restart Claude Desktop
*/
import { McpAgent } from "agents/mcp";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
type Env = {
// Add your environment bindings here
// Example: MY_KV: KVNamespace;
};
/**
* MyMCP extends McpAgent to create a stateless MCP server
*/
export class MyMCP extends McpAgent<Env> {
server = new McpServer({
name: "My MCP Server",
version: "1.0.0",
});
/**
* Initialize tools, resources, and prompts
* Called automatically by McpAgent base class
*/
async init() {
// Simple calculation tool
this.server.tool(
"add",
"Add two numbers together",
{
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
},
async ({ a, b }) => ({
content: [
{
type: "text",
text: `The sum of ${a} + ${b} = ${a + b}`,
},
],
})
);
// Calculator tool with operations
this.server.tool(
"calculate",
"Perform basic arithmetic operations",
{
operation: z
.enum(["add", "subtract", "multiply", "divide"])
.describe("The arithmetic operation to perform"),
a: z.number().describe("First operand"),
b: z.number().describe("Second operand"),
},
async ({ operation, a, b }) => {
let result: number;
switch (operation) {
case "add":
result = a + b;
break;
case "subtract":
result = a - b;
break;
case "multiply":
result = a * b;
break;
case "divide":
if (b === 0) {
return {
content: [
{
type: "text",
text: "Error: Division by zero is not allowed",
},
],
isError: true,
};
}
result = a / b;
break;
}
return {
content: [
{
type: "text",
text: `Result: ${a} ${operation} ${b} = ${result}`,
},
],
};
}
);
// Example resource (optional)
this.server.resource({
uri: "about://server",
name: "About this server",
description: "Information about this MCP server",
mimeType: "text/plain",
}, async () => ({
contents: [{
uri: "about://server",
mimeType: "text/plain",
text: "This is a basic MCP server running on Cloudflare Workers"
}]
}));
}
}
/**
* Worker fetch handler
* Supports both SSE and Streamable HTTP transports
*
* ⚠️ URL CONFIGURATION GUIDE:
*
* Option 1: Serve at /sse (current setup)
* -----------------------------------------
* Server code (below): MyMCP.serveSSE("/sse").fetch(...)
* Client config: "url": "https://worker.dev/sse"
* Tools available at: https://worker.dev/sse/tools/list
*
* Option 2: Serve at root / (alternative)
* -----------------------------------------
* Server code: MyMCP.serveSSE("/").fetch(...)
* Client config: "url": "https://worker.dev"
* Tools available at: https://worker.dev/tools/list
*
* The base path argument MUST match what the client expects!
*
* TESTING YOUR CONFIGURATION:
* 1. Deploy: npx wrangler deploy
* 2. Test: curl https://YOUR-WORKER.workers.dev/sse
* 3. Configure: Use exact URL from step 2 in client config
* 4. Restart: Restart Claude Desktop to load new config
*/
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const { pathname } = new URL(request.url);
// Handle CORS preflight (for browser-based clients like MCP Inspector)
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400",
},
});
}
// SSE transport (legacy, but widely supported)
// ⚠️ IMPORTANT: Use pathname.startsWith() to match sub-paths like /sse/tools/list
if (pathname.startsWith("/sse")) {
return MyMCP.serveSSE("/sse").fetch(request, env, ctx);
// ↑ Base path "/sse" means client URL must be: https://worker.dev/sse
}
// Streamable HTTP transport (2025 standard)
// ⚠️ IMPORTANT: Use pathname.startsWith() to match sub-paths like /mcp/tools/list
if (pathname.startsWith("/mcp")) {
return MyMCP.serve("/mcp").fetch(request, env, ctx);
// ↑ Base path "/mcp" means client URL must be: https://worker.dev/mcp
}
// Health check endpoint (useful for debugging connection issues)
// Test with: curl https://YOUR-WORKER.workers.dev/
if (pathname === "/" || pathname === "/health") {
return new Response(
JSON.stringify({
name: "My MCP Server",
version: "1.0.0",
transports: {
sse: "/sse",
http: "/mcp",
},
status: "ok",
timestamp: new Date().toISOString(),
}),
{
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
);
}
return new Response("Not Found", { status: 404 });
},
};