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.
Files changed (47) hide show
  1. package/README.md +137 -0
  2. package/dist/index.js +390 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +44 -0
  5. package/src/templates/full/Dockerfile +15 -0
  6. package/src/templates/full/README.md +76 -0
  7. package/src/templates/full/_dot_env.example +3 -0
  8. package/src/templates/full/_dot_gitignore +5 -0
  9. package/src/templates/full/_dot_vscode/extensions.json +7 -0
  10. package/src/templates/full/docker-compose.yml +8 -0
  11. package/src/templates/full/package.json +35 -0
  12. package/src/templates/full/src/auth/index.ts +24 -0
  13. package/src/templates/full/src/index.sse.ts +58 -0
  14. package/src/templates/full/src/index.ts +6 -0
  15. package/src/templates/full/src/lib/tool-builder.ts +45 -0
  16. package/src/templates/full/src/prompts/index.ts +41 -0
  17. package/src/templates/full/src/resources/index.ts +37 -0
  18. package/src/templates/full/src/server.ts +18 -0
  19. package/src/templates/full/src/tools/__tests__/echo.test.ts +14 -0
  20. package/src/templates/full/src/tools/__tests__/get-weather.test.ts +16 -0
  21. package/src/templates/full/src/tools/echo.ts +11 -0
  22. package/src/templates/full/src/tools/get-weather.ts +24 -0
  23. package/src/templates/full/src/tools/index.ts +9 -0
  24. package/src/templates/full/tsconfig.json +14 -0
  25. package/src/templates/full/vitest.config.ts +7 -0
  26. package/src/templates/minimal/README.md +41 -0
  27. package/src/templates/minimal/_dot_gitignore +5 -0
  28. package/src/templates/minimal/package.json +24 -0
  29. package/src/templates/minimal/src/index.ts +21 -0
  30. package/src/templates/minimal/tsconfig.json +12 -0
  31. package/src/templates/standard/.github/workflows/test.yml +20 -0
  32. package/src/templates/standard/README.md +83 -0
  33. package/src/templates/standard/_dot_env.example +3 -0
  34. package/src/templates/standard/_dot_gitignore +5 -0
  35. package/src/templates/standard/_dot_vscode/extensions.json +7 -0
  36. package/src/templates/standard/package.json +32 -0
  37. package/src/templates/standard/src/index.sse.ts +58 -0
  38. package/src/templates/standard/src/index.ts +6 -0
  39. package/src/templates/standard/src/lib/tool-builder.ts +45 -0
  40. package/src/templates/standard/src/server.ts +14 -0
  41. package/src/templates/standard/src/tools/__tests__/echo.test.ts +14 -0
  42. package/src/templates/standard/src/tools/__tests__/get-weather.test.ts +16 -0
  43. package/src/templates/standard/src/tools/echo.ts +11 -0
  44. package/src/templates/standard/src/tools/get-weather.ts +24 -0
  45. package/src/templates/standard/src/tools/index.ts +9 -0
  46. package/src/templates/standard/tsconfig.json +14 -0
  47. 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,3 @@
1
+ # Copy this file to .env and fill in values
2
+ # Add your API keys and config here
3
+ # SOME_API_KEY=your_key_here
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+ .env
5
+ .DS_Store
@@ -0,0 +1,7 @@
1
+ {
2
+ "recommendations": [
3
+ "dbaeumer.vscode-eslint",
4
+ "esbenp.prettier-vscode",
5
+ "vitest.explorer"
6
+ ]
7
+ }
@@ -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,6 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createServer } from "./server.js";
3
+
4
+ const server = createServer();
5
+ const transport = new StdioServerTransport();
6
+ await server.connect(transport);
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/__tests__/**/*.test.ts"],
6
+ },
7
+ });