wm-create-mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/dist/index.js +390 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
- package/src/templates/full/Dockerfile +15 -0
- package/src/templates/full/README.md +76 -0
- package/src/templates/full/_dot_env.example +3 -0
- package/src/templates/full/_dot_gitignore +5 -0
- package/src/templates/full/_dot_vscode/extensions.json +7 -0
- package/src/templates/full/docker-compose.yml +8 -0
- package/src/templates/full/package.json +35 -0
- package/src/templates/full/src/auth/index.ts +24 -0
- package/src/templates/full/src/index.sse.ts +58 -0
- package/src/templates/full/src/index.ts +6 -0
- package/src/templates/full/src/lib/tool-builder.ts +45 -0
- package/src/templates/full/src/prompts/index.ts +41 -0
- package/src/templates/full/src/resources/index.ts +37 -0
- package/src/templates/full/src/server.ts +18 -0
- package/src/templates/full/src/tools/__tests__/echo.test.ts +14 -0
- package/src/templates/full/src/tools/__tests__/get-weather.test.ts +16 -0
- package/src/templates/full/src/tools/echo.ts +11 -0
- package/src/templates/full/src/tools/get-weather.ts +24 -0
- package/src/templates/full/src/tools/index.ts +9 -0
- package/src/templates/full/tsconfig.json +14 -0
- package/src/templates/full/vitest.config.ts +7 -0
- package/src/templates/minimal/README.md +41 -0
- package/src/templates/minimal/_dot_gitignore +5 -0
- package/src/templates/minimal/package.json +24 -0
- package/src/templates/minimal/src/index.ts +21 -0
- package/src/templates/minimal/tsconfig.json +12 -0
- package/src/templates/standard/.github/workflows/test.yml +20 -0
- package/src/templates/standard/README.md +83 -0
- package/src/templates/standard/_dot_env.example +3 -0
- package/src/templates/standard/_dot_gitignore +5 -0
- package/src/templates/standard/_dot_vscode/extensions.json +7 -0
- package/src/templates/standard/package.json +32 -0
- package/src/templates/standard/src/index.sse.ts +58 -0
- package/src/templates/standard/src/index.ts +6 -0
- package/src/templates/standard/src/lib/tool-builder.ts +45 -0
- package/src/templates/standard/src/server.ts +14 -0
- package/src/templates/standard/src/tools/__tests__/echo.test.ts +14 -0
- package/src/templates/standard/src/tools/__tests__/get-weather.test.ts +16 -0
- package/src/templates/standard/src/tools/echo.ts +11 -0
- package/src/templates/standard/src/tools/get-weather.ts +24 -0
- package/src/templates/standard/src/tools/index.ts +9 -0
- package/src/templates/standard/tsconfig.json +14 -0
- package/src/templates/standard/vitest.config.ts +7 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
An MCP server built with [create-mcp-server](https://github.com/workingmodel/create-mcp-server).
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev # start server in watch mode
|
|
9
|
+
npm run inspector # open MCP Inspector UI to test tools interactively
|
|
10
|
+
npm run test # run unit tests
|
|
11
|
+
npm run validate # typecheck + tests
|
|
12
|
+
npm run build # compile to dist/
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Project structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
tools/
|
|
20
|
+
index.ts ← register all tools here
|
|
21
|
+
echo.ts ← example: echo tool
|
|
22
|
+
get-weather.ts ← example: weather tool (stub — wire to a real API)
|
|
23
|
+
__tests__/ ← vitest unit tests per tool
|
|
24
|
+
lib/
|
|
25
|
+
tool-builder.ts ← defineTool() helper + registerTools()
|
|
26
|
+
server.ts ← MCP server setup
|
|
27
|
+
index.ts ← transport entry point
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Adding a tool
|
|
31
|
+
|
|
32
|
+
1. Create `src/tools/my-tool.ts`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { z } from "zod";
|
|
36
|
+
import { defineTool, text } from "../lib/tool-builder.js";
|
|
37
|
+
|
|
38
|
+
export const myTool = defineTool({
|
|
39
|
+
name: "my-tool",
|
|
40
|
+
description: "What this tool does",
|
|
41
|
+
input: z.object({
|
|
42
|
+
param: z.string().describe("Description of param"),
|
|
43
|
+
}),
|
|
44
|
+
handler: async ({ param }) => text(`Result: ${param}`),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. Add to `src/tools/index.ts`:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { myTool } from "./my-tool.js";
|
|
52
|
+
export const tools = [...existingTools, myTool];
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
3. Write a test in `src/tools/__tests__/my-tool.test.ts`.
|
|
56
|
+
|
|
57
|
+
## Connecting to Claude Desktop
|
|
58
|
+
|
|
59
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"{{PROJECT_NAME}}": {
|
|
65
|
+
"command": "node",
|
|
66
|
+
"args": ["/absolute/path/to/{{PROJECT_NAME}}/dist/index.js"]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Connecting to Cursor / Windsurf
|
|
73
|
+
|
|
74
|
+
In your editor's MCP config:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"{{PROJECT_NAME}}": {
|
|
79
|
+
"command": "node",
|
|
80
|
+
"args": ["/absolute/path/to/{{PROJECT_NAME}}/dist/index.js"]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "{{VERSION}}",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tsx --watch src/index.ts",
|
|
7
|
+
"dev:sse": "tsx --watch src/index.sse.ts",
|
|
8
|
+
"build": "tsup src/index.ts src/index.sse.ts --format esm --out-dir dist",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"start:sse": "node dist/index.sse.js",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"inspector": "npm run build && npx @modelcontextprotocol/inspector node dist/index.js",
|
|
15
|
+
"validate": "npm run typecheck && npm run test"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
19
|
+
"zod": "^3.23.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@modelcontextprotocol/inspector": "^0.6.0",
|
|
23
|
+
"@types/node": "^20.0.0",
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"tsx": "^4.0.0",
|
|
26
|
+
"typescript": "^5.4.0",
|
|
27
|
+
"vitest": "^1.6.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE transport entry point — for remote HTTP clients (Cursor, Windsurf, web apps).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npm run dev:sse # dev mode
|
|
6
|
+
* npm run start:sse # production
|
|
7
|
+
*
|
|
8
|
+
* Connect your MCP client to: http://localhost:PORT/sse
|
|
9
|
+
*/
|
|
10
|
+
import { createServer as createHttpServer, IncomingMessage, ServerResponse } from "node:http";
|
|
11
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
12
|
+
import { createServer } from "./server.js";
|
|
13
|
+
|
|
14
|
+
const PORT = Number(process.env.PORT ?? 3000);
|
|
15
|
+
|
|
16
|
+
const mcpServer = createServer();
|
|
17
|
+
|
|
18
|
+
// Map sessionId → transport so POST /messages can route to the right connection
|
|
19
|
+
const transports = new Map<string, SSEServerTransport>();
|
|
20
|
+
|
|
21
|
+
const httpServer = createHttpServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
22
|
+
const url = new URL(req.url ?? "/", `http://localhost:${PORT}`);
|
|
23
|
+
|
|
24
|
+
// Client opens SSE stream
|
|
25
|
+
if (req.method === "GET" && url.pathname === "/sse") {
|
|
26
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
27
|
+
transports.set(transport.sessionId, transport);
|
|
28
|
+
|
|
29
|
+
res.on("close", () => {
|
|
30
|
+
transports.delete(transport.sessionId);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await mcpServer.connect(transport);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Client posts a message to an established session
|
|
38
|
+
if (req.method === "POST" && url.pathname === "/messages") {
|
|
39
|
+
const sessionId = url.searchParams.get("sessionId") ?? "";
|
|
40
|
+
const transport = transports.get(sessionId);
|
|
41
|
+
|
|
42
|
+
if (!transport) {
|
|
43
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
44
|
+
res.end("Session not found");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await transport.handlePostMessage(req, res);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
53
|
+
res.end("Not found");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
httpServer.listen(PORT, () => {
|
|
57
|
+
console.log(`{{PROJECT_NAME}} MCP server (SSE) listening on http://localhost:${PORT}/sse`);
|
|
58
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
|
|
4
|
+
export interface ToolResult {
|
|
5
|
+
content: Array<
|
|
6
|
+
| { type: "text"; text: string }
|
|
7
|
+
| { type: "image"; data: string; mimeType: string }
|
|
8
|
+
>;
|
|
9
|
+
isError?: boolean;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export interface ToolDefinition<TInput extends z.ZodRawShape = any> {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
input: z.ZodObject<TInput>;
|
|
18
|
+
handler: (input: z.infer<z.ZodObject<TInput>>) => Promise<ToolResult>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function defineTool<TInput extends z.ZodRawShape>(
|
|
22
|
+
definition: ToolDefinition<TInput>
|
|
23
|
+
): ToolDefinition<TInput> {
|
|
24
|
+
return definition;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function registerTools(server: McpServer, tools: ToolDefinition[]) {
|
|
28
|
+
for (const tool of tools) {
|
|
29
|
+
server.tool(
|
|
30
|
+
tool.name,
|
|
31
|
+
tool.description,
|
|
32
|
+
tool.input.shape,
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
async (input: any) => tool.handler(input)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function text(content: string): ToolResult {
|
|
40
|
+
return { content: [{ type: "text", text: content }] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function error(message: string): ToolResult {
|
|
44
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
45
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerTools } from "./lib/tool-builder.js";
|
|
3
|
+
import { tools } from "./tools/index.js";
|
|
4
|
+
|
|
5
|
+
export function createServer(): McpServer {
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "{{PROJECT_NAME}}",
|
|
8
|
+
version: "{{VERSION}}",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
registerTools(server, tools);
|
|
12
|
+
|
|
13
|
+
return server;
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { echoTool } from "../echo.js";
|
|
3
|
+
|
|
4
|
+
describe("echo tool", () => {
|
|
5
|
+
it("returns the input message", async () => {
|
|
6
|
+
const result = await echoTool.handler({ message: "hello world" });
|
|
7
|
+
expect(result.content[0]).toEqual({ type: "text", text: "hello world" });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("handles empty string", async () => {
|
|
11
|
+
const result = await echoTool.handler({ message: "" });
|
|
12
|
+
expect(result.content[0]).toEqual({ type: "text", text: "" });
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { getWeatherTool } from "../get-weather.js";
|
|
3
|
+
|
|
4
|
+
describe("get-weather tool", () => {
|
|
5
|
+
it("returns weather for a location in celsius", async () => {
|
|
6
|
+
const result = await getWeatherTool.handler({ location: "London", units: "celsius" });
|
|
7
|
+
expect(result.content[0].type).toBe("text");
|
|
8
|
+
expect((result.content[0] as { type: "text"; text: string }).text).toContain("London");
|
|
9
|
+
expect((result.content[0] as { type: "text"; text: string }).text).toContain("°C");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("returns weather in fahrenheit", async () => {
|
|
13
|
+
const result = await getWeatherTool.handler({ location: "New York", units: "fahrenheit" });
|
|
14
|
+
expect((result.content[0] as { type: "text"; text: string }).text).toContain("°F");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineTool, text } from "../lib/tool-builder.js";
|
|
3
|
+
|
|
4
|
+
export const echoTool = defineTool({
|
|
5
|
+
name: "echo",
|
|
6
|
+
description: "Returns the input message unchanged. Useful for testing your server connection.",
|
|
7
|
+
input: z.object({
|
|
8
|
+
message: z.string().describe("The message to echo back"),
|
|
9
|
+
}),
|
|
10
|
+
handler: async ({ message }) => text(message),
|
|
11
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineTool, text, error } from "../lib/tool-builder.js";
|
|
3
|
+
|
|
4
|
+
// Example tool showing a realistic pattern: validated input, error handling, typed output.
|
|
5
|
+
// Replace with your own logic or delete this file.
|
|
6
|
+
export const getWeatherTool = defineTool({
|
|
7
|
+
name: "get-weather",
|
|
8
|
+
description: "Get the current weather for a city. Returns temperature and conditions.",
|
|
9
|
+
input: z.object({
|
|
10
|
+
location: z.string().describe("City name, e.g. 'New York' or 'London, UK'"),
|
|
11
|
+
units: z
|
|
12
|
+
.enum(["celsius", "fahrenheit"])
|
|
13
|
+
.default("celsius")
|
|
14
|
+
.describe("Temperature unit"),
|
|
15
|
+
}),
|
|
16
|
+
handler: async ({ location, units }) => {
|
|
17
|
+
// TODO: Replace with a real weather API call
|
|
18
|
+
// e.g. const data = await fetch(`https://api.openweathermap.org/...`)
|
|
19
|
+
|
|
20
|
+
// Stub response for demonstration
|
|
21
|
+
const temp = units === "celsius" ? "22°C" : "72°F";
|
|
22
|
+
return text(`Weather in ${location}: ${temp}, partly cloudy.`);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolDefinition } from "../lib/tool-builder.js";
|
|
2
|
+
import { echoTool } from "./echo.js";
|
|
3
|
+
import { getWeatherTool } from "./get-weather.js";
|
|
4
|
+
|
|
5
|
+
// Register all tools here. Import and add to this array.
|
|
6
|
+
export const tools: ToolDefinition[] = [
|
|
7
|
+
echoTool,
|
|
8
|
+
getWeatherTool,
|
|
9
|
+
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"baseUrl": ".",
|
|
11
|
+
"paths": { "@/*": ["src/*"] }
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|