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
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# create-mcp-server
|
|
2
|
+
|
|
3
|
+
> Scaffold a production-ready MCP server in seconds.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx wm-create-mcp-server my-server
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The fastest way to start building with the [Model Context Protocol](https://modelcontextprotocol.io). Opinionated structure, typed tool definitions with full Zod inference, local inspector harness, and CI included out of the box.
|
|
10
|
+
|
|
11
|
+
Developed by [Working Model Inc](https://workingmodel.co)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx wm-create-mcp-server my-server
|
|
19
|
+
# → interactive wizard
|
|
20
|
+
# → installs dependencies
|
|
21
|
+
# → ready in < 60 seconds
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd my-server
|
|
26
|
+
npm run dev # start server in watch mode
|
|
27
|
+
npm run inspector # build + open MCP Inspector UI
|
|
28
|
+
npm run test # run unit tests
|
|
29
|
+
npm run validate # typecheck + tests
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Templates
|
|
35
|
+
|
|
36
|
+
| Template | What's included |
|
|
37
|
+
|----------|----------------|
|
|
38
|
+
| `standard` (default) | Tool registry, typed `defineTool()`, tests, inspector harness, SSE entry point |
|
|
39
|
+
| `minimal` | Single `index.ts`, one example tool — maximum flexibility |
|
|
40
|
+
| `full` | Everything in `standard` + MCP resources, prompts, auth stub, Docker |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Typed tools
|
|
45
|
+
|
|
46
|
+
The `defineTool()` helper converts your Zod schema into MCP-compatible JSON Schema automatically, with full TypeScript inference in the handler:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { z } from "zod";
|
|
50
|
+
import { defineTool, text } from "./lib/tool-builder.js";
|
|
51
|
+
|
|
52
|
+
export const getWeatherTool = defineTool({
|
|
53
|
+
name: "get-weather",
|
|
54
|
+
description: "Get current weather for a location",
|
|
55
|
+
input: z.object({
|
|
56
|
+
location: z.string().describe("City name"),
|
|
57
|
+
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
|
|
58
|
+
}),
|
|
59
|
+
handler: async ({ location, units }) => {
|
|
60
|
+
// location: string, units: "celsius" | "fahrenheit" — fully typed
|
|
61
|
+
return text(`Weather in ${location}: 22${units === "celsius" ? "°C" : "°F"}`);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Register it by adding to `src/tools/index.ts`:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { getWeatherTool } from "./get-weather.js";
|
|
70
|
+
export const tools = [echoTool, getWeatherTool];
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or use the built-in generator — from inside your project:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx create-mcp-server add tool get-weather
|
|
77
|
+
# → creates src/tools/get-weather.ts (typed, with handler stub)
|
|
78
|
+
# → creates src/tools/__tests__/get-weather.test.ts
|
|
79
|
+
# → auto-registers in src/tools/index.ts
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Transport options
|
|
85
|
+
|
|
86
|
+
**stdio** (default) — for Claude Desktop, Cursor, Windsurf, and local clients
|
|
87
|
+
|
|
88
|
+
**SSE** — for remote HTTP clients. Select SSE in the wizard and your project gets a `node:http` server with session routing at `GET /sse` + `POST /messages`:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm run dev:sse # start SSE server on http://localhost:3000/sse
|
|
92
|
+
npm run start:sse # production
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Connecting to Claude Desktop
|
|
98
|
+
|
|
99
|
+
After running `npm run build`, add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"my-server": {
|
|
105
|
+
"command": "node",
|
|
106
|
+
"args": ["/absolute/path/to/my-server/dist/index.js"]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## CLI reference
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
npx wm-create-mcp-server [project-name] [flags]
|
|
118
|
+
npx wm-create-mcp-server add tool <tool-name>
|
|
119
|
+
|
|
120
|
+
Flags:
|
|
121
|
+
--template, -t Template to use: minimal, standard (default), full
|
|
122
|
+
--yes, -y Skip prompts, use defaults
|
|
123
|
+
--version, -v Print version
|
|
124
|
+
--help, -h Show help
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
npx wm-create-mcp-server my-server
|
|
128
|
+
npx wm-create-mcp-server my-server --yes
|
|
129
|
+
npx wm-create-mcp-server my-server --template full
|
|
130
|
+
npx wm-create-mcp-server add tool send-email
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Developed by [Working Model Inc](https://workingmodel.co)
|
|
136
|
+
|
|
137
|
+
MIT License
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import kleur2 from "kleur";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
import path3 from "path";
|
|
7
|
+
import fs3 from "fs";
|
|
8
|
+
|
|
9
|
+
// src/cli/scaffold.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
function readCliVersion() {
|
|
15
|
+
const candidates = [
|
|
16
|
+
path.resolve(__dirname, "../package.json"),
|
|
17
|
+
// dist/index.js
|
|
18
|
+
path.resolve(__dirname, "../../package.json")
|
|
19
|
+
// src/cli/scaffold.ts
|
|
20
|
+
];
|
|
21
|
+
for (const c of candidates) {
|
|
22
|
+
if (fs.existsSync(c)) return JSON.parse(fs.readFileSync(c, "utf8")).version;
|
|
23
|
+
}
|
|
24
|
+
return "0.0.0";
|
|
25
|
+
}
|
|
26
|
+
var CLI_VERSION = readCliVersion();
|
|
27
|
+
function scaffold(options) {
|
|
28
|
+
const { projectName, targetDir, template, transport, includeResources, includePrompts } = options;
|
|
29
|
+
const vars = {
|
|
30
|
+
PROJECT_NAME: projectName,
|
|
31
|
+
VERSION: CLI_VERSION
|
|
32
|
+
};
|
|
33
|
+
const templatesRoot = findTemplatesRoot();
|
|
34
|
+
const templateDir = path.join(templatesRoot, template);
|
|
35
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
36
|
+
copyDir(templateDir, targetDir, vars);
|
|
37
|
+
if (transport === "sse" && template !== "minimal") {
|
|
38
|
+
const sseEntry = path.join(targetDir, "src", "index.sse.ts");
|
|
39
|
+
const stdioEntry = path.join(targetDir, "src", "index.ts");
|
|
40
|
+
if (fs.existsSync(sseEntry)) {
|
|
41
|
+
fs.renameSync(sseEntry, stdioEntry);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
const sseEntry = path.join(targetDir, "src", "index.sse.ts");
|
|
45
|
+
if (fs.existsSync(sseEntry)) fs.unlinkSync(sseEntry);
|
|
46
|
+
}
|
|
47
|
+
if (template === "standard") {
|
|
48
|
+
if (includeResources) {
|
|
49
|
+
const resourcesSrc = path.join(templatesRoot, "full", "src", "resources");
|
|
50
|
+
copyDir(resourcesSrc, path.join(targetDir, "src", "resources"), vars);
|
|
51
|
+
}
|
|
52
|
+
if (includePrompts) {
|
|
53
|
+
const promptsSrc = path.join(templatesRoot, "full", "src", "prompts");
|
|
54
|
+
copyDir(promptsSrc, path.join(targetDir, "src", "prompts"), vars);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function findTemplatesRoot() {
|
|
59
|
+
const candidates = [
|
|
60
|
+
// tsx from source: src/cli/ → ../templates
|
|
61
|
+
path.resolve(__dirname, "../templates"),
|
|
62
|
+
// built: dist/ → ../src/templates (tsup flattens to dist/index.js)
|
|
63
|
+
path.resolve(__dirname, "../src/templates"),
|
|
64
|
+
// built with subdir: dist/cli/ → ../../src/templates
|
|
65
|
+
path.resolve(__dirname, "../../src/templates")
|
|
66
|
+
];
|
|
67
|
+
for (const c of candidates) {
|
|
68
|
+
if (fs.existsSync(c)) return c;
|
|
69
|
+
}
|
|
70
|
+
throw new Error("Cannot locate templates directory");
|
|
71
|
+
}
|
|
72
|
+
function copyDir(src, dest, vars) {
|
|
73
|
+
if (!fs.existsSync(src)) return;
|
|
74
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
75
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
76
|
+
const srcPath = path.join(src, entry.name);
|
|
77
|
+
const destName = entry.name.startsWith("_dot_") ? "." + entry.name.slice(5) : entry.name;
|
|
78
|
+
const destPath = path.join(dest, destName);
|
|
79
|
+
if (entry.isDirectory()) {
|
|
80
|
+
copyDir(srcPath, destPath, vars);
|
|
81
|
+
} else {
|
|
82
|
+
let content = fs.readFileSync(srcPath, "utf8");
|
|
83
|
+
content = applyVars(content, vars);
|
|
84
|
+
fs.writeFileSync(destPath, content, "utf8");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function applyVars(content, vars) {
|
|
89
|
+
return content.replace(/\{\{([A-Z_]+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/cli/install.ts
|
|
93
|
+
import { execSync } from "child_process";
|
|
94
|
+
async function install(cwd, pm) {
|
|
95
|
+
const cmd = pm === "bun" ? "bun install" : `${pm} install`;
|
|
96
|
+
try {
|
|
97
|
+
execSync(cmd, { cwd, stdio: "ignore" });
|
|
98
|
+
} catch {
|
|
99
|
+
console.warn(` Warning: auto-install failed. Run \`${cmd}\` in the project directory.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/cli/add-tool.ts
|
|
104
|
+
import fs2 from "fs";
|
|
105
|
+
import path2 from "path";
|
|
106
|
+
import kleur from "kleur";
|
|
107
|
+
async function addTool(toolName, cwd = process.cwd()) {
|
|
108
|
+
const pkgPath = path2.join(cwd, "package.json");
|
|
109
|
+
if (!fs2.existsSync(pkgPath)) {
|
|
110
|
+
console.error(kleur.red("\n No package.json found. Run this inside a scaffolded project.\n"));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const toolsDir = path2.join(cwd, "src", "tools");
|
|
114
|
+
const testsDir = path2.join(toolsDir, "__tests__");
|
|
115
|
+
const indexPath = path2.join(toolsDir, "index.ts");
|
|
116
|
+
if (!fs2.existsSync(toolsDir)) {
|
|
117
|
+
console.error(kleur.red("\n src/tools/ not found. Is this a standard or full template project?\n"));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
const fileName = toKebab(toolName);
|
|
121
|
+
const varName = toCamel(toolName) + "Tool";
|
|
122
|
+
const toolFile = path2.join(toolsDir, `${fileName}.ts`);
|
|
123
|
+
const testFile = path2.join(testsDir, `${fileName}.test.ts`);
|
|
124
|
+
if (fs2.existsSync(toolFile)) {
|
|
125
|
+
console.error(kleur.red(`
|
|
126
|
+
Tool file already exists: src/tools/${fileName}.ts
|
|
127
|
+
`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
fs2.writeFileSync(toolFile, toolTemplate(varName, fileName), "utf8");
|
|
131
|
+
fs2.mkdirSync(testsDir, { recursive: true });
|
|
132
|
+
fs2.writeFileSync(testFile, testTemplate(varName, fileName), "utf8");
|
|
133
|
+
if (fs2.existsSync(indexPath)) {
|
|
134
|
+
let index = fs2.readFileSync(indexPath, "utf8");
|
|
135
|
+
const importLine = `import { ${varName} } from "./${fileName}.js";`;
|
|
136
|
+
if (!index.includes(importLine)) {
|
|
137
|
+
const lastImport = index.lastIndexOf("\nimport ");
|
|
138
|
+
const insertAt = index.indexOf("\n", lastImport + 1);
|
|
139
|
+
index = index.slice(0, insertAt) + "\n" + importLine + index.slice(insertAt);
|
|
140
|
+
}
|
|
141
|
+
index = index.replace(
|
|
142
|
+
/export const tools[^=]*=\s*\[([^\]]*)\]/s,
|
|
143
|
+
(match, inner) => {
|
|
144
|
+
const trimmed = inner.trimEnd();
|
|
145
|
+
const sep = trimmed.endsWith(",") || trimmed.trim() === "" ? "\n " : ",\n ";
|
|
146
|
+
return match.replace(inner, trimmed + sep + varName + ",\n");
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
fs2.writeFileSync(indexPath, index, "utf8");
|
|
150
|
+
}
|
|
151
|
+
console.log();
|
|
152
|
+
console.log(kleur.green(` \u2713 Created src/tools/${fileName}.ts`));
|
|
153
|
+
console.log(kleur.green(` \u2713 Created src/tools/__tests__/${fileName}.test.ts`));
|
|
154
|
+
if (fs2.existsSync(indexPath)) {
|
|
155
|
+
console.log(kleur.green(` \u2713 Registered in src/tools/index.ts`));
|
|
156
|
+
}
|
|
157
|
+
console.log();
|
|
158
|
+
console.log(kleur.gray(` Edit ${kleur.white(`src/tools/${fileName}.ts`)} to implement your tool.`));
|
|
159
|
+
console.log();
|
|
160
|
+
}
|
|
161
|
+
function toolTemplate(varName, fileName) {
|
|
162
|
+
return `import { z } from "zod";
|
|
163
|
+
import { defineTool, text } from "../lib/tool-builder.js";
|
|
164
|
+
|
|
165
|
+
export const ${varName} = defineTool({
|
|
166
|
+
name: "${fileName}",
|
|
167
|
+
description: "TODO: describe what this tool does",
|
|
168
|
+
input: z.object({
|
|
169
|
+
// TODO: define your input schema
|
|
170
|
+
param: z.string().describe("TODO: describe this parameter"),
|
|
171
|
+
}),
|
|
172
|
+
handler: async ({ param }) => {
|
|
173
|
+
// TODO: implement your tool logic
|
|
174
|
+
return text(\`Result: \${param}\`);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
function testTemplate(varName, fileName) {
|
|
180
|
+
return `import { describe, it, expect } from "vitest";
|
|
181
|
+
import { ${varName} } from "../${fileName}.js";
|
|
182
|
+
|
|
183
|
+
describe("${fileName} tool", () => {
|
|
184
|
+
it("returns a result", async () => {
|
|
185
|
+
const result = await ${varName}.handler({ param: "test" });
|
|
186
|
+
expect(result.content[0].type).toBe("text");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
function toKebab(name) {
|
|
192
|
+
return name.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
193
|
+
}
|
|
194
|
+
function toCamel(name) {
|
|
195
|
+
return toKebab(name).split("-").map((w, i) => i === 0 ? w : w[0].toUpperCase() + w.slice(1)).join("");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/cli/index.ts
|
|
199
|
+
function readVersion() {
|
|
200
|
+
const candidates = [
|
|
201
|
+
path3.resolve(path3.dirname(new URL(import.meta.url).pathname), "../package.json"),
|
|
202
|
+
path3.resolve(path3.dirname(new URL(import.meta.url).pathname), "../../package.json")
|
|
203
|
+
];
|
|
204
|
+
for (const c of candidates) {
|
|
205
|
+
if (fs3.existsSync(c)) return JSON.parse(fs3.readFileSync(c, "utf8")).version;
|
|
206
|
+
}
|
|
207
|
+
return "0.0.0";
|
|
208
|
+
}
|
|
209
|
+
var VERSION = readVersion();
|
|
210
|
+
var args = process.argv.slice(2);
|
|
211
|
+
if (args[0] === "add" && args[1] === "tool") {
|
|
212
|
+
const toolName = args[2];
|
|
213
|
+
if (!toolName) {
|
|
214
|
+
console.error(kleur2.red("\n Usage: create-mcp-server add tool <tool-name>\n"));
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
addTool(toolName).catch((err) => {
|
|
218
|
+
console.error(kleur2.red("\n Error: " + err.message));
|
|
219
|
+
process.exit(1);
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
let printHelp = function() {
|
|
223
|
+
console.log(`
|
|
224
|
+
${kleur2.bold().cyan("create-mcp-server")} v${VERSION}
|
|
225
|
+
|
|
226
|
+
${kleur2.bold("Usage:")}
|
|
227
|
+
npx create-mcp-server [project-name] [flags]
|
|
228
|
+
|
|
229
|
+
${kleur2.bold("Flags:")}
|
|
230
|
+
--template, -t Template to use: minimal, standard (default), full
|
|
231
|
+
--yes, -y Skip prompts, use defaults
|
|
232
|
+
--version, -v Print version
|
|
233
|
+
--help, -h Show this help
|
|
234
|
+
|
|
235
|
+
${kleur2.bold("Subcommands:")}
|
|
236
|
+
add tool <name> Scaffold a new tool file + test in an existing project
|
|
237
|
+
|
|
238
|
+
${kleur2.bold("Examples:")}
|
|
239
|
+
npx create-mcp-server my-server
|
|
240
|
+
npx create-mcp-server my-server --yes
|
|
241
|
+
npx create-mcp-server my-server --template full
|
|
242
|
+
npx create-mcp-server add tool get-weather
|
|
243
|
+
`);
|
|
244
|
+
};
|
|
245
|
+
printHelp2 = printHelp;
|
|
246
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
247
|
+
console.log(VERSION);
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
251
|
+
printHelp();
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
const nonFlagArgs = args.filter((a, i) => {
|
|
255
|
+
if (a.startsWith("-")) return false;
|
|
256
|
+
const prev = args[i - 1];
|
|
257
|
+
if (prev === "--template" || prev === "-t") return false;
|
|
258
|
+
return true;
|
|
259
|
+
});
|
|
260
|
+
const projectNameArg = nonFlagArgs[0];
|
|
261
|
+
const yesFlag = args.includes("--yes") || args.includes("-y");
|
|
262
|
+
const templateFlag = (() => {
|
|
263
|
+
const idx = args.findIndex((a) => a === "--template" || a === "-t");
|
|
264
|
+
return idx !== -1 ? args[idx + 1] : void 0;
|
|
265
|
+
})();
|
|
266
|
+
const VALID_TEMPLATES = ["minimal", "standard", "full"];
|
|
267
|
+
if (templateFlag && !VALID_TEMPLATES.includes(templateFlag)) {
|
|
268
|
+
console.error(kleur2.red(`
|
|
269
|
+
Invalid template "${templateFlag}". Choose: minimal, standard, full.
|
|
270
|
+
`));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
async function main() {
|
|
274
|
+
console.log();
|
|
275
|
+
console.log(kleur2.bold().cyan(" create-mcp-server") + kleur2.gray(` v${VERSION}`));
|
|
276
|
+
console.log(kleur2.gray(" Scaffold a production-ready MCP server\n"));
|
|
277
|
+
let projectName = projectNameArg;
|
|
278
|
+
if (!projectName) {
|
|
279
|
+
const res = await prompts({
|
|
280
|
+
type: "text",
|
|
281
|
+
name: "projectName",
|
|
282
|
+
message: "Project name",
|
|
283
|
+
initial: "my-mcp-server",
|
|
284
|
+
validate: (v) => /^[a-z0-9-]+$/.test(v) || "Use lowercase letters, numbers, and hyphens only"
|
|
285
|
+
});
|
|
286
|
+
if (!res.projectName) process.exit(0);
|
|
287
|
+
projectName = res.projectName;
|
|
288
|
+
}
|
|
289
|
+
const targetDir = path3.resolve(process.cwd(), projectName);
|
|
290
|
+
if (fs3.existsSync(targetDir)) {
|
|
291
|
+
console.error(kleur2.red(`
|
|
292
|
+
Directory "${projectName}" already exists.
|
|
293
|
+
`));
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
let answers = {
|
|
297
|
+
template: templateFlag ?? "standard",
|
|
298
|
+
transport: "stdio",
|
|
299
|
+
includeResources: false,
|
|
300
|
+
includePrompts: false,
|
|
301
|
+
packageManager: "npm"
|
|
302
|
+
};
|
|
303
|
+
if (!yesFlag) {
|
|
304
|
+
const res = await prompts(
|
|
305
|
+
[
|
|
306
|
+
{
|
|
307
|
+
type: "select",
|
|
308
|
+
name: "template",
|
|
309
|
+
message: "Template",
|
|
310
|
+
choices: [
|
|
311
|
+
{ title: "standard \u2014 tool registry, tests, CI", value: "standard" },
|
|
312
|
+
{ title: "minimal \u2014 single file, one example tool", value: "minimal" },
|
|
313
|
+
{ title: "full \u2014 resources, prompts, auth stub, Docker", value: "full" }
|
|
314
|
+
],
|
|
315
|
+
initial: 0
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
type: "select",
|
|
319
|
+
name: "transport",
|
|
320
|
+
message: "Transport",
|
|
321
|
+
choices: [
|
|
322
|
+
{ title: "stdio \u2014 for Claude Desktop / local clients (recommended)", value: "stdio" },
|
|
323
|
+
{ title: "SSE \u2014 for remote HTTP clients (Cursor, web apps)", value: "sse" }
|
|
324
|
+
],
|
|
325
|
+
initial: 0
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
type: (prev, values) => values.template === "standard" ? "toggle" : null,
|
|
329
|
+
name: "includeResources",
|
|
330
|
+
message: "Include MCP resources?",
|
|
331
|
+
initial: false,
|
|
332
|
+
active: "yes",
|
|
333
|
+
inactive: "no"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
type: (prev, values) => values.template === "standard" ? "toggle" : null,
|
|
337
|
+
name: "includePrompts",
|
|
338
|
+
message: "Include MCP prompts?",
|
|
339
|
+
initial: false,
|
|
340
|
+
active: "yes",
|
|
341
|
+
inactive: "no"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
type: "select",
|
|
345
|
+
name: "packageManager",
|
|
346
|
+
message: "Package manager",
|
|
347
|
+
choices: [
|
|
348
|
+
{ title: "npm", value: "npm" },
|
|
349
|
+
{ title: "pnpm", value: "pnpm" },
|
|
350
|
+
{ title: "bun", value: "bun" }
|
|
351
|
+
],
|
|
352
|
+
initial: 0
|
|
353
|
+
}
|
|
354
|
+
],
|
|
355
|
+
{ onCancel: () => process.exit(0) }
|
|
356
|
+
);
|
|
357
|
+
answers = { ...answers, ...res };
|
|
358
|
+
}
|
|
359
|
+
console.log();
|
|
360
|
+
console.log(kleur2.gray(` Scaffolding ${kleur2.white(projectName)}...`));
|
|
361
|
+
scaffold({
|
|
362
|
+
projectName,
|
|
363
|
+
targetDir,
|
|
364
|
+
template: answers.template,
|
|
365
|
+
transport: answers.transport,
|
|
366
|
+
includeResources: answers.includeResources || answers.template === "full",
|
|
367
|
+
includePrompts: answers.includePrompts || answers.template === "full"
|
|
368
|
+
});
|
|
369
|
+
console.log(kleur2.green(" \u2713 Files created"));
|
|
370
|
+
console.log(kleur2.gray(` Installing dependencies with ${answers.packageManager}...`));
|
|
371
|
+
await install(targetDir, answers.packageManager);
|
|
372
|
+
console.log(kleur2.green(" \u2713 Dependencies installed\n"));
|
|
373
|
+
console.log(kleur2.bold(" Next steps:\n"));
|
|
374
|
+
console.log(kleur2.cyan(` cd ${projectName}`));
|
|
375
|
+
const devCmd = answers.transport === "sse" ? "npm run dev:sse" : "npm run dev ";
|
|
376
|
+
const devNote = answers.transport === "sse" ? "# start SSE server (http://localhost:3000/sse)" : "# start server in watch mode";
|
|
377
|
+
console.log(kleur2.cyan(` ${devCmd} `) + kleur2.gray(devNote));
|
|
378
|
+
console.log(kleur2.cyan(` npm run inspector `) + kleur2.gray("# open MCP inspector UI"));
|
|
379
|
+
console.log(kleur2.cyan(` npm run test `) + kleur2.gray("# run tool unit tests\n"));
|
|
380
|
+
console.log(
|
|
381
|
+
kleur2.gray(" Docs: https://github.com/workingmodel/wm-create-mcp-server\n")
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
main().catch((err) => {
|
|
385
|
+
console.error(kleur2.red("\n Error: " + err.message));
|
|
386
|
+
process.exit(1);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
var printHelp2;
|
|
390
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../src/cli/scaffold.ts","../src/cli/install.ts","../src/cli/add-tool.ts"],"sourcesContent":["import kleur from \"kleur\";\nimport prompts from \"prompts\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport { scaffold } from \"./scaffold.js\";\nimport { install } from \"./install.js\";\nimport { addTool } from \"./add-tool.js\";\n\nfunction readVersion(): string {\n const candidates = [\n path.resolve(path.dirname(new URL(import.meta.url).pathname), \"../package.json\"),\n path.resolve(path.dirname(new URL(import.meta.url).pathname), \"../../package.json\"),\n ];\n for (const c of candidates) {\n if (fs.existsSync(c)) return JSON.parse(fs.readFileSync(c, \"utf8\")).version as string;\n }\n return \"0.0.0\";\n}\nconst VERSION = readVersion();\n\nconst args = process.argv.slice(2);\n\n// Subcommand: create-mcp-server add tool <name>\nif (args[0] === \"add\" && args[1] === \"tool\") {\n const toolName = args[2];\n if (!toolName) {\n console.error(kleur.red(\"\\n Usage: create-mcp-server add tool <tool-name>\\n\"));\n process.exit(1);\n }\n addTool(toolName).catch((err) => {\n console.error(kleur.red(\"\\n Error: \" + err.message));\n process.exit(1);\n });\n // Exit early — don't run the scaffold wizard\n} else {\n\nif (args.includes(\"--version\") || args.includes(\"-v\")) {\n console.log(VERSION);\n process.exit(0);\n}\n\nif (args.includes(\"--help\") || args.includes(\"-h\")) {\n printHelp();\n process.exit(0);\n}\n\nconst nonFlagArgs = args.filter((a, i) => {\n if (a.startsWith(\"-\")) return false;\n const prev = args[i - 1];\n if (prev === \"--template\" || prev === \"-t\") return false;\n return true;\n});\nconst projectNameArg = nonFlagArgs[0];\nconst yesFlag = args.includes(\"--yes\") || args.includes(\"-y\");\nconst templateFlag = (() => {\n const idx = args.findIndex((a) => a === \"--template\" || a === \"-t\");\n return idx !== -1 ? args[idx + 1] : undefined;\n})();\nconst VALID_TEMPLATES = [\"minimal\", \"standard\", \"full\"] as const;\ntype Template = typeof VALID_TEMPLATES[number];\nif (templateFlag && !VALID_TEMPLATES.includes(templateFlag as Template)) {\n console.error(kleur.red(`\\n Invalid template \"${templateFlag}\". Choose: minimal, standard, full.\\n`));\n process.exit(1);\n}\n\nasync function main() {\n console.log();\n console.log(kleur.bold().cyan(\" create-mcp-server\") + kleur.gray(` v${VERSION}`));\n console.log(kleur.gray(\" Scaffold a production-ready MCP server\\n\"));\n\n let projectName = projectNameArg;\n\n if (!projectName) {\n const res = await prompts({\n type: \"text\",\n name: \"projectName\",\n message: \"Project name\",\n initial: \"my-mcp-server\",\n validate: (v) => /^[a-z0-9-]+$/.test(v) || \"Use lowercase letters, numbers, and hyphens only\",\n });\n if (!res.projectName) process.exit(0);\n projectName = res.projectName;\n }\n\n const targetDir = path.resolve(process.cwd(), projectName);\n\n if (fs.existsSync(targetDir)) {\n console.error(kleur.red(`\\n Directory \"${projectName}\" already exists.\\n`));\n process.exit(1);\n }\n\n let answers = {\n template: (templateFlag ?? \"standard\") as Template,\n transport: \"stdio\" as \"stdio\" | \"sse\",\n includeResources: false,\n includePrompts: false,\n packageManager: \"npm\" as \"npm\" | \"pnpm\" | \"bun\",\n };\n\n if (!yesFlag) {\n const res = await prompts(\n [\n {\n type: \"select\",\n name: \"template\",\n message: \"Template\",\n choices: [\n { title: \"standard — tool registry, tests, CI\", value: \"standard\" },\n { title: \"minimal — single file, one example tool\", value: \"minimal\" },\n { title: \"full — resources, prompts, auth stub, Docker\", value: \"full\" },\n ],\n initial: 0,\n },\n {\n type: \"select\",\n name: \"transport\",\n message: \"Transport\",\n choices: [\n { title: \"stdio — for Claude Desktop / local clients (recommended)\", value: \"stdio\" },\n { title: \"SSE — for remote HTTP clients (Cursor, web apps)\", value: \"sse\" },\n ],\n initial: 0,\n },\n {\n type: (prev: string, values: Record<string, unknown>) =>\n values.template === \"standard\" ? \"toggle\" : null,\n name: \"includeResources\",\n message: \"Include MCP resources?\",\n initial: false,\n active: \"yes\",\n inactive: \"no\",\n },\n {\n type: (prev: string, values: Record<string, unknown>) =>\n values.template === \"standard\" ? \"toggle\" : null,\n name: \"includePrompts\",\n message: \"Include MCP prompts?\",\n initial: false,\n active: \"yes\",\n inactive: \"no\",\n },\n {\n type: \"select\",\n name: \"packageManager\",\n message: \"Package manager\",\n choices: [\n { title: \"npm\", value: \"npm\" },\n { title: \"pnpm\", value: \"pnpm\" },\n { title: \"bun\", value: \"bun\" },\n ],\n initial: 0,\n },\n ],\n { onCancel: () => process.exit(0) }\n );\n\n answers = { ...answers, ...res };\n }\n\n console.log();\n console.log(kleur.gray(` Scaffolding ${kleur.white(projectName)}...`));\n\n scaffold({\n projectName,\n targetDir,\n template: answers.template,\n transport: answers.transport,\n includeResources: answers.includeResources || answers.template === \"full\",\n includePrompts: answers.includePrompts || answers.template === \"full\",\n });\n\n console.log(kleur.green(\" ✓ Files created\"));\n console.log(kleur.gray(` Installing dependencies with ${answers.packageManager}...`));\n\n await install(targetDir, answers.packageManager);\n\n console.log(kleur.green(\" ✓ Dependencies installed\\n\"));\n console.log(kleur.bold(\" Next steps:\\n\"));\n console.log(kleur.cyan(` cd ${projectName}`));\n const devCmd = answers.transport === \"sse\" ? \"npm run dev:sse\" : \"npm run dev \";\n const devNote = answers.transport === \"sse\" ? \"# start SSE server (http://localhost:3000/sse)\" : \"# start server in watch mode\";\n console.log(kleur.cyan(` ${devCmd} `) + kleur.gray(devNote));\n console.log(kleur.cyan(` npm run inspector `) + kleur.gray(\"# open MCP inspector UI\"));\n console.log(kleur.cyan(` npm run test `) + kleur.gray(\"# run tool unit tests\\n\"));\n console.log(\n kleur.gray(\" Docs: https://github.com/workingmodel/wm-create-mcp-server\\n\")\n );\n}\n\nfunction printHelp() {\n console.log(`\n ${kleur.bold().cyan(\"create-mcp-server\")} v${VERSION}\n\n ${kleur.bold(\"Usage:\")}\n npx create-mcp-server [project-name] [flags]\n\n ${kleur.bold(\"Flags:\")}\n --template, -t Template to use: minimal, standard (default), full\n --yes, -y Skip prompts, use defaults\n --version, -v Print version\n --help, -h Show this help\n\n ${kleur.bold(\"Subcommands:\")}\n add tool <name> Scaffold a new tool file + test in an existing project\n\n ${kleur.bold(\"Examples:\")}\n npx create-mcp-server my-server\n npx create-mcp-server my-server --yes\n npx create-mcp-server my-server --template full\n npx create-mcp-server add tool get-weather\n `);\n}\n\nmain().catch((err) => {\n console.error(kleur.red(\"\\n Error: \" + err.message));\n process.exit(1);\n});\n\n} // end else (not a subcommand)\n","import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nfunction readCliVersion(): string {\n const candidates = [\n path.resolve(__dirname, \"../package.json\"), // dist/index.js\n path.resolve(__dirname, \"../../package.json\"), // src/cli/scaffold.ts\n ];\n for (const c of candidates) {\n if (fs.existsSync(c)) return JSON.parse(fs.readFileSync(c, \"utf8\")).version as string;\n }\n return \"0.0.0\";\n}\nconst CLI_VERSION = readCliVersion();\n\nexport interface ScaffoldOptions {\n projectName: string;\n targetDir: string;\n template: \"minimal\" | \"standard\" | \"full\";\n transport: \"stdio\" | \"sse\";\n includeResources: boolean;\n includePrompts: boolean;\n}\n\nexport function scaffold(options: ScaffoldOptions) {\n const { projectName, targetDir, template, transport, includeResources, includePrompts } = options;\n\n const vars: Record<string, string> = {\n PROJECT_NAME: projectName,\n VERSION: CLI_VERSION,\n };\n\n // templates live next to src/ in the package; when running from dist, go up two levels\n const templatesRoot = findTemplatesRoot();\n const templateDir = path.join(templatesRoot, template);\n\n fs.mkdirSync(targetDir, { recursive: true });\n\n copyDir(templateDir, targetDir, vars);\n\n // SSE transport: swap index.ts with the SSE entry point, remove the stdio one\n if (transport === \"sse\" && template !== \"minimal\") {\n const sseEntry = path.join(targetDir, \"src\", \"index.sse.ts\");\n const stdioEntry = path.join(targetDir, \"src\", \"index.ts\");\n if (fs.existsSync(sseEntry)) {\n fs.renameSync(sseEntry, stdioEntry);\n }\n } else {\n // Remove the SSE entry — user chose stdio\n const sseEntry = path.join(targetDir, \"src\", \"index.sse.ts\");\n if (fs.existsSync(sseEntry)) fs.unlinkSync(sseEntry);\n }\n\n // optionally layer in resources / prompts from the full template\n if (template === \"standard\") {\n if (includeResources) {\n const resourcesSrc = path.join(templatesRoot, \"full\", \"src\", \"resources\");\n copyDir(resourcesSrc, path.join(targetDir, \"src\", \"resources\"), vars);\n }\n if (includePrompts) {\n const promptsSrc = path.join(templatesRoot, \"full\", \"src\", \"prompts\");\n copyDir(promptsSrc, path.join(targetDir, \"src\", \"prompts\"), vars);\n }\n }\n}\n\nfunction findTemplatesRoot(): string {\n const candidates = [\n // tsx from source: src/cli/ → ../templates\n path.resolve(__dirname, \"../templates\"),\n // built: dist/ → ../src/templates (tsup flattens to dist/index.js)\n path.resolve(__dirname, \"../src/templates\"),\n // built with subdir: dist/cli/ → ../../src/templates\n path.resolve(__dirname, \"../../src/templates\"),\n ];\n for (const c of candidates) {\n if (fs.existsSync(c)) return c;\n }\n throw new Error(\"Cannot locate templates directory\");\n}\n\nfunction copyDir(src: string, dest: string, vars: Record<string, string>) {\n if (!fs.existsSync(src)) return;\n fs.mkdirSync(dest, { recursive: true });\n\n for (const entry of fs.readdirSync(src, { withFileTypes: true })) {\n const srcPath = path.join(src, entry.name);\n // _gitignore → .gitignore (npm strips dotfiles from published packages)\n const destName = entry.name.startsWith(\"_dot_\") ? \".\" + entry.name.slice(5) : entry.name;\n const destPath = path.join(dest, destName);\n\n if (entry.isDirectory()) {\n copyDir(srcPath, destPath, vars);\n } else {\n let content = fs.readFileSync(srcPath, \"utf8\");\n content = applyVars(content, vars);\n fs.writeFileSync(destPath, content, \"utf8\");\n }\n }\n}\n\nfunction applyVars(content: string, vars: Record<string, string>): string {\n return content.replace(/\\{\\{([A-Z_]+)\\}\\}/g, (_, key) => vars[key] ?? `{{${key}}}`);\n}\n\n","import { execSync } from \"child_process\";\n\nexport async function install(cwd: string, pm: \"npm\" | \"pnpm\" | \"bun\") {\n const cmd = pm === \"bun\" ? \"bun install\" : `${pm} install`;\n try {\n execSync(cmd, { cwd, stdio: \"ignore\" });\n } catch {\n // non-fatal — user can install manually\n console.warn(` Warning: auto-install failed. Run \\`${cmd}\\` in the project directory.`);\n }\n}\n","/**\n * `create-mcp-server add tool <name>`\n *\n * Scaffolds a new typed tool file + test stub, then appends the import\n * and registration to src/tools/index.ts.\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport kleur from \"kleur\";\n\nexport async function addTool(toolName: string, cwd = process.cwd()) {\n\n // Validate we're inside a create-mcp-server project\n const pkgPath = path.join(cwd, \"package.json\");\n if (!fs.existsSync(pkgPath)) {\n console.error(kleur.red(\"\\n No package.json found. Run this inside a scaffolded project.\\n\"));\n process.exit(1);\n }\n\n const toolsDir = path.join(cwd, \"src\", \"tools\");\n const testsDir = path.join(toolsDir, \"__tests__\");\n const indexPath = path.join(toolsDir, \"index.ts\");\n\n if (!fs.existsSync(toolsDir)) {\n console.error(kleur.red(\"\\n src/tools/ not found. Is this a standard or full template project?\\n\"));\n process.exit(1);\n }\n\n const fileName = toKebab(toolName);\n const varName = toCamel(toolName) + \"Tool\";\n const toolFile = path.join(toolsDir, `${fileName}.ts`);\n const testFile = path.join(testsDir, `${fileName}.test.ts`);\n\n if (fs.existsSync(toolFile)) {\n console.error(kleur.red(`\\n Tool file already exists: src/tools/${fileName}.ts\\n`));\n process.exit(1);\n }\n\n // Write tool file\n fs.writeFileSync(toolFile, toolTemplate(varName, fileName), \"utf8\");\n\n // Write test stub\n fs.mkdirSync(testsDir, { recursive: true });\n fs.writeFileSync(testFile, testTemplate(varName, fileName), \"utf8\");\n\n // Append import + registration to src/tools/index.ts\n if (fs.existsSync(indexPath)) {\n let index = fs.readFileSync(indexPath, \"utf8\");\n // Add import after last existing import line\n const importLine = `import { ${varName} } from \"./${fileName}.js\";`;\n if (!index.includes(importLine)) {\n const lastImport = index.lastIndexOf(\"\\nimport \");\n const insertAt = index.indexOf(\"\\n\", lastImport + 1);\n index = index.slice(0, insertAt) + \"\\n\" + importLine + index.slice(insertAt);\n }\n // Add to tools array\n index = index.replace(\n /export const tools[^=]*=\\s*\\[([^\\]]*)\\]/s,\n (match, inner) => {\n const trimmed = inner.trimEnd();\n const sep = trimmed.endsWith(\",\") || trimmed.trim() === \"\" ? \"\\n \" : \",\\n \";\n return match.replace(inner, trimmed + sep + varName + \",\\n\");\n }\n );\n fs.writeFileSync(indexPath, index, \"utf8\");\n }\n\n console.log();\n console.log(kleur.green(` ✓ Created src/tools/${fileName}.ts`));\n console.log(kleur.green(` ✓ Created src/tools/__tests__/${fileName}.test.ts`));\n if (fs.existsSync(indexPath)) {\n console.log(kleur.green(` ✓ Registered in src/tools/index.ts`));\n }\n console.log();\n console.log(kleur.gray(` Edit ${kleur.white(`src/tools/${fileName}.ts`)} to implement your tool.`));\n console.log();\n}\n\nfunction toolTemplate(varName: string, fileName: string): string {\n return `import { z } from \"zod\";\nimport { defineTool, text } from \"../lib/tool-builder.js\";\n\nexport const ${varName} = defineTool({\n name: \"${fileName}\",\n description: \"TODO: describe what this tool does\",\n input: z.object({\n // TODO: define your input schema\n param: z.string().describe(\"TODO: describe this parameter\"),\n }),\n handler: async ({ param }) => {\n // TODO: implement your tool logic\n return text(\\`Result: \\${param}\\`);\n },\n});\n`;\n}\n\nfunction testTemplate(varName: string, fileName: string): string {\n return `import { describe, it, expect } from \"vitest\";\nimport { ${varName} } from \"../${fileName}.js\";\n\ndescribe(\"${fileName} tool\", () => {\n it(\"returns a result\", async () => {\n const result = await ${varName}.handler({ param: \"test\" });\n expect(result.content[0].type).toBe(\"text\");\n });\n});\n`;\n}\n\nfunction toKebab(name: string): string {\n return name\n .replace(/([a-z])([A-Z])/g, \"$1-$2\")\n .replace(/[\\s_]+/g, \"-\")\n .toLowerCase();\n}\n\nfunction toCamel(name: string): string {\n return toKebab(name)\n .split(\"-\")\n .map((w, i) => (i === 0 ? w : w[0].toUpperCase() + w.slice(1)))\n .join(\"\");\n}\n"],"mappings":"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","names":["kleur","path","fs","fs","path","path","fs","kleur","printHelp"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wm-create-mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a production-ready MCP server in seconds — typed tools, local inspector, CI included",
|
|
5
|
+
"keywords": ["mcp", "model-context-protocol", "claude", "ai", "scaffold", "create", "cli"],
|
|
6
|
+
"author": "Working Model <adam@workingmodel.co>",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://github.com/workingmodel/wm-create-mcp-server#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/workingmodel/wm-create-mcp-server.git"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"bin": {
|
|
15
|
+
"wm-create-mcp-server": "./dist/index.js",
|
|
16
|
+
"create-mcp-server": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src/templates"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"smoke": "node dist/index.js --yes test-project-smoke && rm -rf test-project-smoke"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"kleur": "^4.1.5",
|
|
32
|
+
"prompts": "^2.4.2"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"@types/prompts": "^2.4.9",
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.4.0",
|
|
39
|
+
"vitest": "^1.6.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
FROM node:20-slim AS base
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
COPY package*.json ./
|
|
4
|
+
RUN npm ci --omit=dev
|
|
5
|
+
|
|
6
|
+
FROM base AS build
|
|
7
|
+
COPY . .
|
|
8
|
+
RUN npm ci && npm run build
|
|
9
|
+
|
|
10
|
+
FROM node:20-slim AS runtime
|
|
11
|
+
WORKDIR /app
|
|
12
|
+
COPY --from=build /app/dist ./dist
|
|
13
|
+
COPY --from=base /app/node_modules ./node_modules
|
|
14
|
+
EXPOSE 3000
|
|
15
|
+
CMD ["node", "dist/index.js"]
|