mcp-new 0.1.0 → 1.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 +59 -20
- package/dist/{chunk-QRUHMGU5.js → chunk-K3TOM22I.js} +248 -13
- package/dist/cli.js +5 -5
- package/dist/index.d.ts +12 -6
- package/dist/index.js +2 -2
- package/package.json +2 -2
- package/templates/go/.env.example +5 -0
- package/templates/go/.gitignore.ejs +30 -0
- package/templates/go/README.md.ejs +88 -0
- package/templates/go/cmd/server/main.go.ejs +101 -0
- package/templates/go/go.mod.ejs +7 -0
- package/templates/go/internal/tools/example.go.ejs +40 -0
- package/templates/rust/.env.example +8 -0
- package/templates/rust/.gitignore.ejs +19 -0
- package/templates/rust/Cargo.toml.ejs +18 -0
- package/templates/rust/README.md.ejs +92 -0
- package/templates/rust/src/main.rs.ejs +46 -0
- package/templates/rust/src/tools.rs.ejs +105 -0
- /package/bin/{mcp-new.js → mcp-generator.js} +0 -0
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# mcp-
|
|
1
|
+
# mcp-generator
|
|
2
2
|
|
|
3
3
|
> CLI generator for MCP servers in seconds. Like `create-react-app`, but for MCP.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npx mcp-
|
|
8
|
+
npx mcp-generator my-server
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **Interactive wizard** — create MCP server through step-by-step prompts
|
|
14
|
-
- **
|
|
14
|
+
- **Multi-language support** — TypeScript, Python, Go, and Rust
|
|
15
15
|
- **OpenAPI generation** — auto-create tools from OpenAPI/Swagger specification
|
|
16
16
|
- **AI generation** — create tools from text description using Claude
|
|
17
17
|
- **Ready-to-use templates** — working code with examples out of the box
|
|
@@ -19,13 +19,13 @@ npx mcp-new my-server
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install -g mcp-
|
|
22
|
+
npm install -g mcp-generator
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Or use directly via npx:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npx mcp-
|
|
28
|
+
npx mcp-generator my-server
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Usage
|
|
@@ -33,7 +33,7 @@ npx mcp-new my-server
|
|
|
33
33
|
### Basic creation
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
npx mcp-
|
|
36
|
+
npx mcp-generator my-weather-api
|
|
37
37
|
|
|
38
38
|
# Answer the questions:
|
|
39
39
|
# ? Project name: my-weather-api
|
|
@@ -51,22 +51,28 @@ npm run dev
|
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
# TypeScript project (skips language prompt)
|
|
54
|
-
npx mcp-
|
|
54
|
+
npx mcp-generator my-server -t
|
|
55
55
|
|
|
56
56
|
# Python project (skips language prompt)
|
|
57
|
-
npx mcp-
|
|
57
|
+
npx mcp-generator my-server -p
|
|
58
|
+
|
|
59
|
+
# Go project (skips language prompt)
|
|
60
|
+
npx mcp-generator my-server -g
|
|
61
|
+
|
|
62
|
+
# Rust project (skips language prompt)
|
|
63
|
+
npx mcp-generator my-server -r
|
|
58
64
|
|
|
59
65
|
# Skip dependency installation
|
|
60
|
-
npx mcp-
|
|
66
|
+
npx mcp-generator my-server --skip-install
|
|
61
67
|
|
|
62
68
|
# Use default values
|
|
63
|
-
npx mcp-
|
|
69
|
+
npx mcp-generator my-server -y
|
|
64
70
|
```
|
|
65
71
|
|
|
66
72
|
### From OpenAPI specification
|
|
67
73
|
|
|
68
74
|
```bash
|
|
69
|
-
npx mcp-
|
|
75
|
+
npx mcp-generator stripe-mcp --from-openapi ./stripe-api.yaml
|
|
70
76
|
|
|
71
77
|
# CLI will show found endpoints and let you select the ones you need
|
|
72
78
|
```
|
|
@@ -74,7 +80,7 @@ npx mcp-new stripe-mcp --from-openapi ./stripe-api.yaml
|
|
|
74
80
|
### From text description (AI)
|
|
75
81
|
|
|
76
82
|
```bash
|
|
77
|
-
npx mcp-
|
|
83
|
+
npx mcp-generator notion-mcp --from-prompt
|
|
78
84
|
|
|
79
85
|
# Describe your API in the editor
|
|
80
86
|
# Claude will generate tools automatically
|
|
@@ -86,23 +92,23 @@ Requires `ANTHROPIC_API_KEY` environment variable.
|
|
|
86
92
|
|
|
87
93
|
```bash
|
|
88
94
|
cd my-existing-project
|
|
89
|
-
npx mcp-
|
|
95
|
+
npx mcp-generator init
|
|
90
96
|
```
|
|
91
97
|
|
|
92
98
|
### Add new tool
|
|
93
99
|
|
|
94
100
|
```bash
|
|
95
101
|
cd my-mcp-server
|
|
96
|
-
npx mcp-
|
|
102
|
+
npx mcp-generator add-tool
|
|
97
103
|
```
|
|
98
104
|
|
|
99
105
|
## CLI Commands
|
|
100
106
|
|
|
101
107
|
| Command | Description |
|
|
102
108
|
|---------|-------------|
|
|
103
|
-
| `mcp-
|
|
104
|
-
| `mcp-
|
|
105
|
-
| `mcp-
|
|
109
|
+
| `mcp-generator <name>` | Create new MCP server |
|
|
110
|
+
| `mcp-generator init` | Initialize MCP in current directory |
|
|
111
|
+
| `mcp-generator add-tool` | Add tool to existing server |
|
|
106
112
|
|
|
107
113
|
## Options
|
|
108
114
|
|
|
@@ -110,6 +116,8 @@ npx mcp-new add-tool
|
|
|
110
116
|
|------|-------------|
|
|
111
117
|
| `-t, --typescript` | Use TypeScript (skips language prompt) |
|
|
112
118
|
| `-p, --python` | Use Python (skips language prompt) |
|
|
119
|
+
| `-g, --go` | Use Go (skips language prompt) |
|
|
120
|
+
| `-r, --rust` | Use Rust (skips language prompt) |
|
|
113
121
|
| `--skip-install` | Skip dependency installation |
|
|
114
122
|
| `--from-openapi <path>` | Create from OpenAPI specification |
|
|
115
123
|
| `--from-prompt` | Create via AI from description |
|
|
@@ -149,12 +157,41 @@ my-server/
|
|
|
149
157
|
└── example_tool.py
|
|
150
158
|
```
|
|
151
159
|
|
|
160
|
+
### Go
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
my-server/
|
|
164
|
+
├── go.mod
|
|
165
|
+
├── README.md
|
|
166
|
+
├── .gitignore
|
|
167
|
+
├── .env.example
|
|
168
|
+
├── cmd/
|
|
169
|
+
│ └── server/
|
|
170
|
+
│ └── main.go # Main server file
|
|
171
|
+
└── internal/
|
|
172
|
+
└── tools/
|
|
173
|
+
└── example.go
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Rust
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
my-server/
|
|
180
|
+
├── Cargo.toml
|
|
181
|
+
├── README.md
|
|
182
|
+
├── .gitignore
|
|
183
|
+
├── .env.example
|
|
184
|
+
└── src/
|
|
185
|
+
├── main.rs # Main server file
|
|
186
|
+
└── tools.rs
|
|
187
|
+
```
|
|
188
|
+
|
|
152
189
|
## Development
|
|
153
190
|
|
|
154
191
|
```bash
|
|
155
192
|
# Clone repository
|
|
156
|
-
git clone https://github.com/d1maash/mcp-
|
|
157
|
-
cd mcp-
|
|
193
|
+
git clone https://github.com/d1maash/mcp-generator.git
|
|
194
|
+
cd mcp-generator
|
|
158
195
|
|
|
159
196
|
# Install dependencies
|
|
160
197
|
npm install
|
|
@@ -170,7 +207,7 @@ npm test
|
|
|
170
207
|
|
|
171
208
|
# Local CLI testing
|
|
172
209
|
npm link
|
|
173
|
-
mcp-
|
|
210
|
+
mcp-generator test-project
|
|
174
211
|
```
|
|
175
212
|
|
|
176
213
|
## Links
|
|
@@ -178,6 +215,8 @@ mcp-new test-project
|
|
|
178
215
|
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
|
179
216
|
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
180
217
|
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
|
|
218
|
+
- [MCP Go SDK](https://github.com/mark3labs/mcp-go)
|
|
219
|
+
- [MCP Rust SDK](https://github.com/modelcontextprotocol/rust-sdk)
|
|
181
220
|
|
|
182
221
|
## License
|
|
183
222
|
|
|
@@ -100,7 +100,9 @@ async function promptLanguage() {
|
|
|
100
100
|
message: "Select language:",
|
|
101
101
|
choices: [
|
|
102
102
|
{ name: "TypeScript", value: "typescript" },
|
|
103
|
-
{ name: "Python", value: "python" }
|
|
103
|
+
{ name: "Python", value: "python" },
|
|
104
|
+
{ name: "Go", value: "go" },
|
|
105
|
+
{ name: "Rust", value: "rust" }
|
|
104
106
|
],
|
|
105
107
|
default: "typescript"
|
|
106
108
|
}
|
|
@@ -639,11 +641,19 @@ var BaseGenerator = class {
|
|
|
639
641
|
logger.info("Skipping dependency installation (--skip-install)");
|
|
640
642
|
return;
|
|
641
643
|
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
644
|
+
switch (this.config.language) {
|
|
645
|
+
case "typescript":
|
|
646
|
+
await this.installNodeDependencies();
|
|
647
|
+
break;
|
|
648
|
+
case "python":
|
|
649
|
+
await this.installPythonDependencies();
|
|
650
|
+
break;
|
|
651
|
+
case "go":
|
|
652
|
+
await this.installGoDependencies();
|
|
653
|
+
break;
|
|
654
|
+
case "rust":
|
|
655
|
+
await this.installRustDependencies();
|
|
656
|
+
break;
|
|
647
657
|
}
|
|
648
658
|
}
|
|
649
659
|
async installNodeDependencies() {
|
|
@@ -676,9 +686,49 @@ var BaseGenerator = class {
|
|
|
676
686
|
"Failed to install dependencies"
|
|
677
687
|
);
|
|
678
688
|
}
|
|
689
|
+
async installGoDependencies() {
|
|
690
|
+
const hasGo = await this.checkCommand("go");
|
|
691
|
+
if (!hasGo) {
|
|
692
|
+
logger.warning("Go not found. Please install dependencies manually:");
|
|
693
|
+
logger.code("go mod download");
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
await withSpinner(
|
|
697
|
+
"Installing Go dependencies...",
|
|
698
|
+
async () => {
|
|
699
|
+
await execa2("go", ["mod", "download"], {
|
|
700
|
+
cwd: this.outputDir
|
|
701
|
+
});
|
|
702
|
+
await execa2("go", ["mod", "tidy"], {
|
|
703
|
+
cwd: this.outputDir
|
|
704
|
+
});
|
|
705
|
+
},
|
|
706
|
+
"Dependencies installed",
|
|
707
|
+
"Failed to install dependencies"
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
async installRustDependencies() {
|
|
711
|
+
const hasCargo = await this.checkCommand("cargo");
|
|
712
|
+
if (!hasCargo) {
|
|
713
|
+
logger.warning("Cargo not found. Please install dependencies manually:");
|
|
714
|
+
logger.code("cargo build");
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
await withSpinner(
|
|
718
|
+
"Building Rust project...",
|
|
719
|
+
async () => {
|
|
720
|
+
await execa2("cargo", ["build"], {
|
|
721
|
+
cwd: this.outputDir
|
|
722
|
+
});
|
|
723
|
+
},
|
|
724
|
+
"Project built successfully",
|
|
725
|
+
"Failed to build project"
|
|
726
|
+
);
|
|
727
|
+
}
|
|
679
728
|
async checkCommand(command) {
|
|
680
729
|
try {
|
|
681
|
-
|
|
730
|
+
const checkCmd = process.platform === "win32" ? "where" : "which";
|
|
731
|
+
await execa2(checkCmd, [command]);
|
|
682
732
|
return true;
|
|
683
733
|
} catch {
|
|
684
734
|
return false;
|
|
@@ -1165,7 +1215,7 @@ async function createCommand(projectName, options) {
|
|
|
1165
1215
|
}
|
|
1166
1216
|
async function handleWizardGeneration(projectName, options) {
|
|
1167
1217
|
let config;
|
|
1168
|
-
const presetLanguage = options.typescript ? "typescript" : options.python ? "python" : void 0;
|
|
1218
|
+
const presetLanguage = options.typescript ? "typescript" : options.python ? "python" : options.go ? "go" : options.rust ? "rust" : void 0;
|
|
1169
1219
|
if (options.yes) {
|
|
1170
1220
|
config = await runQuickWizard(projectName, presetLanguage);
|
|
1171
1221
|
} else {
|
|
@@ -1193,14 +1243,14 @@ async function handleOpenAPIGeneration(projectName, options) {
|
|
|
1193
1243
|
const name = projectName || path4.basename(specPath, path4.extname(specPath)) + "-mcp";
|
|
1194
1244
|
await generateFromOpenAPI(specPath, {
|
|
1195
1245
|
name,
|
|
1196
|
-
language: options.typescript ? "typescript" : options.python ? "python" : void 0,
|
|
1246
|
+
language: options.typescript ? "typescript" : options.python ? "python" : options.go ? "go" : options.rust ? "rust" : void 0,
|
|
1197
1247
|
skipInstall: options.skipInstall
|
|
1198
1248
|
});
|
|
1199
1249
|
}
|
|
1200
1250
|
async function handlePromptGeneration(projectName, options) {
|
|
1201
1251
|
await generateFromPrompt({
|
|
1202
1252
|
name: projectName,
|
|
1203
|
-
language: options.typescript ? "typescript" : options.python ? "python" : void 0,
|
|
1253
|
+
language: options.typescript ? "typescript" : options.python ? "python" : options.go ? "go" : options.rust ? "rust" : void 0,
|
|
1204
1254
|
skipInstall: options.skipInstall
|
|
1205
1255
|
});
|
|
1206
1256
|
}
|
|
@@ -1214,7 +1264,9 @@ async function initCommand(options) {
|
|
|
1214
1264
|
const dirName = path5.basename(currentDir);
|
|
1215
1265
|
const hasPackageJson = await exists(path5.join(currentDir, "package.json"));
|
|
1216
1266
|
const hasPyproject = await exists(path5.join(currentDir, "pyproject.toml"));
|
|
1217
|
-
|
|
1267
|
+
const hasGoMod = await exists(path5.join(currentDir, "go.mod"));
|
|
1268
|
+
const hasCargoToml = await exists(path5.join(currentDir, "Cargo.toml"));
|
|
1269
|
+
if ((hasPackageJson || hasPyproject || hasGoMod || hasCargoToml) && !options.force) {
|
|
1218
1270
|
const { proceed } = await inquirer8.prompt([
|
|
1219
1271
|
{
|
|
1220
1272
|
type: "confirm",
|
|
@@ -1233,12 +1285,22 @@ async function initCommand(options) {
|
|
|
1233
1285
|
language = "typescript";
|
|
1234
1286
|
} else if (options.python) {
|
|
1235
1287
|
language = "python";
|
|
1288
|
+
} else if (options.go) {
|
|
1289
|
+
language = "go";
|
|
1290
|
+
} else if (options.rust) {
|
|
1291
|
+
language = "rust";
|
|
1236
1292
|
} else if (hasPackageJson) {
|
|
1237
1293
|
language = "typescript";
|
|
1238
1294
|
logger.info("Detected existing Node.js project, using TypeScript");
|
|
1239
1295
|
} else if (hasPyproject) {
|
|
1240
1296
|
language = "python";
|
|
1241
1297
|
logger.info("Detected existing Python project");
|
|
1298
|
+
} else if (hasGoMod) {
|
|
1299
|
+
language = "go";
|
|
1300
|
+
logger.info("Detected existing Go project");
|
|
1301
|
+
} else if (hasCargoToml) {
|
|
1302
|
+
language = "rust";
|
|
1303
|
+
logger.info("Detected existing Rust project");
|
|
1242
1304
|
} else {
|
|
1243
1305
|
language = await promptLanguage();
|
|
1244
1306
|
}
|
|
@@ -1283,7 +1345,9 @@ async function addToolCommand(options) {
|
|
|
1283
1345
|
const currentDir = process.cwd();
|
|
1284
1346
|
const isTypeScript = await exists(path6.join(currentDir, "package.json"));
|
|
1285
1347
|
const isPython = await exists(path6.join(currentDir, "pyproject.toml"));
|
|
1286
|
-
|
|
1348
|
+
const isGo = await exists(path6.join(currentDir, "go.mod"));
|
|
1349
|
+
const isRust = await exists(path6.join(currentDir, "Cargo.toml"));
|
|
1350
|
+
if (!isTypeScript && !isPython && !isGo && !isRust) {
|
|
1287
1351
|
logger.error("No MCP server project found in current directory.");
|
|
1288
1352
|
logger.info("Run this command from the root of your MCP server project.");
|
|
1289
1353
|
process.exit(1);
|
|
@@ -1301,8 +1365,12 @@ async function addToolCommand(options) {
|
|
|
1301
1365
|
}
|
|
1302
1366
|
if (isTypeScript) {
|
|
1303
1367
|
await addToolToTypeScript(currentDir, tool);
|
|
1304
|
-
} else {
|
|
1368
|
+
} else if (isPython) {
|
|
1305
1369
|
await addToolToPython(currentDir, tool);
|
|
1370
|
+
} else if (isGo) {
|
|
1371
|
+
await addToolToGo(currentDir, tool);
|
|
1372
|
+
} else if (isRust) {
|
|
1373
|
+
await addToolToRust(currentDir, tool);
|
|
1306
1374
|
}
|
|
1307
1375
|
logger.success(`Tool "${tool.name}" added successfully!`);
|
|
1308
1376
|
logger.info("Remember to implement the tool logic in the generated file.");
|
|
@@ -1479,6 +1547,173 @@ function mapTypeToPython(type) {
|
|
|
1479
1547
|
return "str";
|
|
1480
1548
|
}
|
|
1481
1549
|
}
|
|
1550
|
+
async function addToolToGo(projectDir, tool) {
|
|
1551
|
+
const toolsDir = path6.join(projectDir, "internal", "tools");
|
|
1552
|
+
const toolFileName = `${tool.name}.go`;
|
|
1553
|
+
const toolFilePath = path6.join(toolsDir, toolFileName);
|
|
1554
|
+
if (await exists(toolFilePath)) {
|
|
1555
|
+
logger.error(`Tool file already exists: ${toolFilePath}`);
|
|
1556
|
+
process.exit(1);
|
|
1557
|
+
}
|
|
1558
|
+
const content = generateGoToolFile(tool);
|
|
1559
|
+
await writeFile(toolFilePath, content);
|
|
1560
|
+
logger.info(`Created: internal/tools/${toolFileName}`);
|
|
1561
|
+
logger.blank();
|
|
1562
|
+
logger.info("Next steps:");
|
|
1563
|
+
logger.list([
|
|
1564
|
+
`Implement the tool logic in internal/tools/${toolFileName}`,
|
|
1565
|
+
"Import and register the tool in cmd/server/main.go"
|
|
1566
|
+
]);
|
|
1567
|
+
}
|
|
1568
|
+
async function addToolToRust(projectDir, tool) {
|
|
1569
|
+
const srcDir = path6.join(projectDir, "src");
|
|
1570
|
+
const toolFileName = `${tool.name}.rs`;
|
|
1571
|
+
const toolFilePath = path6.join(srcDir, toolFileName);
|
|
1572
|
+
if (await exists(toolFilePath)) {
|
|
1573
|
+
logger.error(`Tool file already exists: ${toolFilePath}`);
|
|
1574
|
+
process.exit(1);
|
|
1575
|
+
}
|
|
1576
|
+
const content = generateRustToolFile(tool);
|
|
1577
|
+
await writeFile(toolFilePath, content);
|
|
1578
|
+
logger.info(`Created: src/${toolFileName}`);
|
|
1579
|
+
logger.blank();
|
|
1580
|
+
logger.info("Next steps:");
|
|
1581
|
+
logger.list([
|
|
1582
|
+
`Implement the tool logic in src/${toolFileName}`,
|
|
1583
|
+
`Add "mod ${tool.name};" to src/main.rs`,
|
|
1584
|
+
"Register the tool in the server builder"
|
|
1585
|
+
]);
|
|
1586
|
+
}
|
|
1587
|
+
function generateGoToolFile(tool) {
|
|
1588
|
+
const structFields = tool.parameters.map((p) => ` ${toPascalCase(p.name)} ${mapTypeToGo(p.type)} \`json:"${p.name}"\``).join("\n");
|
|
1589
|
+
return `package tools
|
|
1590
|
+
|
|
1591
|
+
import (
|
|
1592
|
+
"context"
|
|
1593
|
+
"fmt"
|
|
1594
|
+
|
|
1595
|
+
"github.com/mark3labs/mcp-go/mcp"
|
|
1596
|
+
)
|
|
1597
|
+
|
|
1598
|
+
// ${toPascalCase(tool.name)}Input represents the input for the ${tool.name} tool
|
|
1599
|
+
type ${toPascalCase(tool.name)}Input struct {
|
|
1600
|
+
${structFields || " // No parameters"}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// ${toPascalCase(tool.name)}Tool creates the ${tool.name} tool definition
|
|
1604
|
+
func ${toPascalCase(tool.name)}Tool() mcp.Tool {
|
|
1605
|
+
return mcp.NewTool("${tool.name}",
|
|
1606
|
+
mcp.WithDescription("${tool.description}"),
|
|
1607
|
+
${tool.parameters.map((p) => {
|
|
1608
|
+
const mcpType = p.type === "number" ? "WithNumber" : p.type === "boolean" ? "WithBoolean" : "WithString";
|
|
1609
|
+
return ` mcp.${mcpType}("${p.name}",
|
|
1610
|
+
${p.required ? " mcp.Required(),\n" : ""} mcp.Description("${p.description}"),
|
|
1611
|
+
),`;
|
|
1612
|
+
}).join("\n")}
|
|
1613
|
+
)
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// ${toPascalCase(tool.name)}Handler handles the ${tool.name} tool execution
|
|
1617
|
+
func ${toPascalCase(tool.name)}Handler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
1618
|
+
// TODO: Implement ${tool.name} logic
|
|
1619
|
+
${tool.parameters.map((p) => {
|
|
1620
|
+
const goType = mapTypeToGo(p.type);
|
|
1621
|
+
if (goType === "float64") {
|
|
1622
|
+
return ` ${p.name}, _ := request.Params.Arguments["${p.name}"].(float64)`;
|
|
1623
|
+
} else if (goType === "bool") {
|
|
1624
|
+
return ` ${p.name}, _ := request.Params.Arguments["${p.name}"].(bool)`;
|
|
1625
|
+
} else {
|
|
1626
|
+
return ` ${p.name}, _ := request.Params.Arguments["${p.name}"].(string)`;
|
|
1627
|
+
}
|
|
1628
|
+
}).join("\n")}
|
|
1629
|
+
|
|
1630
|
+
return mcp.NewToolResultText(fmt.Sprintf("${tool.name} called with: %v", request.Params.Arguments)), nil
|
|
1631
|
+
}
|
|
1632
|
+
`;
|
|
1633
|
+
}
|
|
1634
|
+
function generateRustToolFile(tool) {
|
|
1635
|
+
const structFields = tool.parameters.map((p) => ` pub ${p.name}: ${mapTypeToRust(p.type)},`).join("\n");
|
|
1636
|
+
const required = tool.parameters.filter((p) => p.required).map((p) => `"${p.name}".to_string()`).join(", ");
|
|
1637
|
+
return `use rmcp::{
|
|
1638
|
+
model::{CallToolResult, Content, Tool, ToolInputSchema},
|
|
1639
|
+
tool,
|
|
1640
|
+
};
|
|
1641
|
+
use serde::Deserialize;
|
|
1642
|
+
use serde_json::{json, Value};
|
|
1643
|
+
use std::collections::HashMap;
|
|
1644
|
+
|
|
1645
|
+
/// ${tool.description}
|
|
1646
|
+
#[derive(Debug, Deserialize)]
|
|
1647
|
+
pub struct ${toPascalCase(tool.name)}Input {
|
|
1648
|
+
${structFields || " // No parameters"}
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
pub fn ${tool.name}_tool() -> Tool {
|
|
1652
|
+
Tool {
|
|
1653
|
+
name: "${tool.name}".to_string(),
|
|
1654
|
+
description: Some("${tool.description}".to_string()),
|
|
1655
|
+
input_schema: ToolInputSchema {
|
|
1656
|
+
r#type: "object".to_string(),
|
|
1657
|
+
properties: Some({
|
|
1658
|
+
let mut props = HashMap::new();
|
|
1659
|
+
${tool.parameters.map(
|
|
1660
|
+
(p) => ` props.insert(
|
|
1661
|
+
"${p.name}".to_string(),
|
|
1662
|
+
json!({
|
|
1663
|
+
"type": "${p.type}",
|
|
1664
|
+
"description": "${p.description}"
|
|
1665
|
+
}),
|
|
1666
|
+
);`
|
|
1667
|
+
).join("\n")}
|
|
1668
|
+
props
|
|
1669
|
+
}),
|
|
1670
|
+
required: Some(vec![${required}]),
|
|
1671
|
+
},
|
|
1672
|
+
handler: Box::new(|args| {
|
|
1673
|
+
Box::pin(async move {
|
|
1674
|
+
// TODO: Implement ${tool.name} logic
|
|
1675
|
+
let input: ${toPascalCase(tool.name)}Input = serde_json::from_value(args.clone())?;
|
|
1676
|
+
|
|
1677
|
+
Ok(CallToolResult {
|
|
1678
|
+
content: vec![Content::Text {
|
|
1679
|
+
text: format!("${tool.name} called with: {:?}", args),
|
|
1680
|
+
}],
|
|
1681
|
+
is_error: None,
|
|
1682
|
+
})
|
|
1683
|
+
})
|
|
1684
|
+
}),
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
`;
|
|
1688
|
+
}
|
|
1689
|
+
function mapTypeToGo(type) {
|
|
1690
|
+
switch (type) {
|
|
1691
|
+
case "number":
|
|
1692
|
+
return "float64";
|
|
1693
|
+
case "boolean":
|
|
1694
|
+
return "bool";
|
|
1695
|
+
case "array":
|
|
1696
|
+
return "[]interface{}";
|
|
1697
|
+
case "object":
|
|
1698
|
+
return "map[string]interface{}";
|
|
1699
|
+
default:
|
|
1700
|
+
return "string";
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function mapTypeToRust(type) {
|
|
1704
|
+
switch (type) {
|
|
1705
|
+
case "number":
|
|
1706
|
+
return "f64";
|
|
1707
|
+
case "boolean":
|
|
1708
|
+
return "bool";
|
|
1709
|
+
case "array":
|
|
1710
|
+
return "Vec<Value>";
|
|
1711
|
+
case "object":
|
|
1712
|
+
return "HashMap<String, Value>";
|
|
1713
|
+
default:
|
|
1714
|
+
return "String";
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1482
1717
|
|
|
1483
1718
|
export {
|
|
1484
1719
|
projectNameRegex,
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
addToolCommand,
|
|
4
4
|
createCommand,
|
|
5
5
|
initCommand
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-K3TOM22I.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { Command } from "commander";
|
|
@@ -11,13 +11,13 @@ import chalk from "chalk";
|
|
|
11
11
|
var program = new Command();
|
|
12
12
|
var logo = `
|
|
13
13
|
${chalk.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
|
|
14
|
-
${chalk.cyan("\u2551")} ${chalk.bold.white("mcp-
|
|
14
|
+
${chalk.cyan("\u2551")} ${chalk.bold.white("mcp-generator")} ${chalk.cyan("\u2551")}
|
|
15
15
|
${chalk.cyan("\u2551")} ${chalk.gray("Generate MCP servers in seconds")} ${chalk.cyan("\u2551")}
|
|
16
16
|
${chalk.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
17
17
|
`;
|
|
18
|
-
program.name("mcp-
|
|
19
|
-
program.argument("[project-name]", "Name of the project to create").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("--skip-install", "Skip dependency installation").option("--from-openapi <path>", "Generate from OpenAPI/Swagger specification").option("--from-prompt", "Generate tools using AI from text description").option("-y, --yes", "Skip prompts and use defaults").action(createCommand);
|
|
20
|
-
program.command("init").description("Initialize MCP server in the current directory").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("--skip-install", "Skip dependency installation").option("-f, --force", "Initialize even if directory contains files").action(initCommand);
|
|
18
|
+
program.name("mcp-generator").description("CLI tool for generating MCP (Model Context Protocol) servers").version("0.1.0").addHelpText("beforeAll", logo);
|
|
19
|
+
program.argument("[project-name]", "Name of the project to create").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("-g, --go", "Use Go template").option("-r, --rust", "Use Rust template").option("--skip-install", "Skip dependency installation").option("--from-openapi <path>", "Generate from OpenAPI/Swagger specification").option("--from-prompt", "Generate tools using AI from text description").option("-y, --yes", "Skip prompts and use defaults").action(createCommand);
|
|
20
|
+
program.command("init").description("Initialize MCP server in the current directory").option("-t, --typescript", "Use TypeScript template").option("-p, --python", "Use Python template").option("-g, --go", "Use Go template").option("-r, --rust", "Use Rust template").option("--skip-install", "Skip dependency installation").option("-f, --force", "Initialize even if directory contains files").action(initCommand);
|
|
21
21
|
program.command("add-tool").description("Add a new tool to an existing MCP server").option("-n, --name <name>", "Tool name (snake_case)").action(addToolCommand);
|
|
22
22
|
program.parse();
|
|
23
23
|
if (process.argv.length === 2) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
-
declare const LanguageSchema: z.ZodEnum<["typescript", "python"]>;
|
|
3
|
+
declare const LanguageSchema: z.ZodEnum<["typescript", "python", "go", "rust"]>;
|
|
4
4
|
type Language = z.infer<typeof LanguageSchema>;
|
|
5
5
|
declare const TransportSchema: z.ZodEnum<["stdio", "sse"]>;
|
|
6
6
|
type Transport = z.infer<typeof TransportSchema>;
|
|
@@ -80,7 +80,7 @@ type ResourceConfig = z.infer<typeof ResourceConfigSchema>;
|
|
|
80
80
|
declare const ProjectConfigSchema: z.ZodObject<{
|
|
81
81
|
name: z.ZodString;
|
|
82
82
|
description: z.ZodDefault<z.ZodString>;
|
|
83
|
-
language: z.ZodEnum<["typescript", "python"]>;
|
|
83
|
+
language: z.ZodEnum<["typescript", "python", "go", "rust"]>;
|
|
84
84
|
transport: z.ZodEnum<["stdio", "sse"]>;
|
|
85
85
|
tools: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
86
86
|
name: z.ZodString;
|
|
@@ -142,7 +142,7 @@ declare const ProjectConfigSchema: z.ZodObject<{
|
|
|
142
142
|
}, "strip", z.ZodTypeAny, {
|
|
143
143
|
name: string;
|
|
144
144
|
description: string;
|
|
145
|
-
language: "typescript" | "python";
|
|
145
|
+
language: "typescript" | "python" | "go" | "rust";
|
|
146
146
|
transport: "stdio" | "sse";
|
|
147
147
|
tools: {
|
|
148
148
|
name: string;
|
|
@@ -165,7 +165,7 @@ declare const ProjectConfigSchema: z.ZodObject<{
|
|
|
165
165
|
initGit: boolean;
|
|
166
166
|
}, {
|
|
167
167
|
name: string;
|
|
168
|
-
language: "typescript" | "python";
|
|
168
|
+
language: "typescript" | "python" | "go" | "rust";
|
|
169
169
|
transport: "stdio" | "sse";
|
|
170
170
|
description?: string | undefined;
|
|
171
171
|
tools?: {
|
|
@@ -197,6 +197,8 @@ interface GeneratorContext {
|
|
|
197
197
|
interface CLIOptions {
|
|
198
198
|
typescript?: boolean;
|
|
199
199
|
python?: boolean;
|
|
200
|
+
go?: boolean;
|
|
201
|
+
rust?: boolean;
|
|
200
202
|
skipInstall?: boolean;
|
|
201
203
|
fromOpenapi?: string;
|
|
202
204
|
fromPrompt?: boolean;
|
|
@@ -354,6 +356,8 @@ declare abstract class BaseGenerator {
|
|
|
354
356
|
protected installDependencies(): Promise<void>;
|
|
355
357
|
private installNodeDependencies;
|
|
356
358
|
private installPythonDependencies;
|
|
359
|
+
private installGoDependencies;
|
|
360
|
+
private installRustDependencies;
|
|
357
361
|
private checkCommand;
|
|
358
362
|
protected initializeGit(): Promise<void>;
|
|
359
363
|
protected checkOutputDir(): Promise<boolean>;
|
|
@@ -493,16 +497,18 @@ interface WizardOptions {
|
|
|
493
497
|
defaultName?: string;
|
|
494
498
|
skipDescription?: boolean;
|
|
495
499
|
skipAdvanced?: boolean;
|
|
496
|
-
presetLanguage?:
|
|
500
|
+
presetLanguage?: Language;
|
|
497
501
|
}
|
|
498
502
|
declare function runWizard(options?: WizardOptions): Promise<ProjectConfig>;
|
|
499
|
-
declare function runQuickWizard(defaultName?: string, presetLanguage?:
|
|
503
|
+
declare function runQuickWizard(defaultName?: string, presetLanguage?: Language): Promise<ProjectConfig>;
|
|
500
504
|
|
|
501
505
|
declare function createCommand(projectName: string | undefined, options: CLIOptions): Promise<void>;
|
|
502
506
|
|
|
503
507
|
interface InitOptions {
|
|
504
508
|
typescript?: boolean;
|
|
505
509
|
python?: boolean;
|
|
510
|
+
go?: boolean;
|
|
511
|
+
rust?: boolean;
|
|
506
512
|
skipInstall?: boolean;
|
|
507
513
|
force?: boolean;
|
|
508
514
|
}
|
package/dist/index.js
CHANGED
|
@@ -55,11 +55,11 @@ import {
|
|
|
55
55
|
walkDir,
|
|
56
56
|
withSpinner,
|
|
57
57
|
writeFile
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-K3TOM22I.js";
|
|
59
59
|
|
|
60
60
|
// src/types/config.ts
|
|
61
61
|
import { z } from "zod";
|
|
62
|
-
var LanguageSchema = z.enum(["typescript", "python"]);
|
|
62
|
+
var LanguageSchema = z.enum(["typescript", "python", "go", "rust"]);
|
|
63
63
|
var TransportSchema = z.enum(["stdio", "sse"]);
|
|
64
64
|
var ToolParameterSchema = z.object({
|
|
65
65
|
name: z.string(),
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-new",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI generator for MCP servers. Like create-react-app, but for MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"mcp-
|
|
9
|
+
"mcp-generator": "./bin/mcp-generator.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Binaries
|
|
2
|
+
*.exe
|
|
3
|
+
*.exe~
|
|
4
|
+
*.dll
|
|
5
|
+
*.so
|
|
6
|
+
*.dylib
|
|
7
|
+
<%= name %>
|
|
8
|
+
|
|
9
|
+
# Test binary
|
|
10
|
+
*.test
|
|
11
|
+
|
|
12
|
+
# Output of the go coverage tool
|
|
13
|
+
*.out
|
|
14
|
+
|
|
15
|
+
# Dependency directories
|
|
16
|
+
vendor/
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.idea/
|
|
20
|
+
.vscode/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# Environment
|
|
25
|
+
.env
|
|
26
|
+
.env.local
|
|
27
|
+
|
|
28
|
+
# Build
|
|
29
|
+
/bin/
|
|
30
|
+
/dist/
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# <%= name %>
|
|
2
|
+
|
|
3
|
+
<%= description || 'MCP Server generated by mcp-generator' %>
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This is an MCP (Model Context Protocol) server built with Go using the [mcp-go](https://github.com/mark3labs/mcp-go) SDK.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- Go 1.21 or higher
|
|
12
|
+
- Git
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Install dependencies
|
|
18
|
+
go mod download
|
|
19
|
+
|
|
20
|
+
# Build the server
|
|
21
|
+
go build -o <%= name %> ./cmd/server
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Running the Server
|
|
25
|
+
|
|
26
|
+
<% if (transport === 'stdio') { %>
|
|
27
|
+
### STDIO Transport
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
./<%= name %>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The server communicates via standard input/output.
|
|
34
|
+
<% } else { %>
|
|
35
|
+
### SSE Transport
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
./<%= name %>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The server will start an HTTP server with SSE endpoint.
|
|
42
|
+
<% } %>
|
|
43
|
+
|
|
44
|
+
## Available Tools
|
|
45
|
+
|
|
46
|
+
<% if (includeExampleTool) { %>
|
|
47
|
+
- **example_tool**: An example tool that echoes the input
|
|
48
|
+
<% } %>
|
|
49
|
+
<% tools.forEach(function(tool) { %>
|
|
50
|
+
- **<%= tool.name %>**: <%= tool.description %>
|
|
51
|
+
<% }); %>
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# Run in development mode
|
|
57
|
+
go run ./cmd/server
|
|
58
|
+
|
|
59
|
+
# Run tests
|
|
60
|
+
go test ./...
|
|
61
|
+
|
|
62
|
+
# Build for production
|
|
63
|
+
go build -ldflags="-s -w" -o <%= name %> ./cmd/server
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Testing with MCP Inspector
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx @modelcontextprotocol/inspector ./<%= name %>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Project Structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
.
|
|
76
|
+
├── cmd/
|
|
77
|
+
│ └── server/
|
|
78
|
+
│ └── main.go # Server entry point
|
|
79
|
+
├── internal/
|
|
80
|
+
│ └── tools/ # Tool implementations
|
|
81
|
+
├── go.mod
|
|
82
|
+
├── go.sum
|
|
83
|
+
└── README.md
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"os"
|
|
7
|
+
|
|
8
|
+
"github.com/mark3labs/mcp-go/mcp"
|
|
9
|
+
"github.com/mark3labs/mcp-go/server"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func main() {
|
|
13
|
+
// Create MCP server
|
|
14
|
+
s := server.NewMCPServer(
|
|
15
|
+
"<%= name %>",
|
|
16
|
+
"1.0.0",
|
|
17
|
+
server.WithToolCapabilities(true),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
<% if (includeExampleTool) { %>
|
|
21
|
+
// Register example tool
|
|
22
|
+
exampleTool := mcp.NewTool("example_tool",
|
|
23
|
+
mcp.WithDescription("An example tool that echoes the input"),
|
|
24
|
+
mcp.WithString("query",
|
|
25
|
+
mcp.Required(),
|
|
26
|
+
mcp.Description("The query to echo"),
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
s.AddTool(exampleTool, exampleToolHandler)
|
|
31
|
+
<% } %>
|
|
32
|
+
|
|
33
|
+
<% tools.forEach(function(tool) { %>
|
|
34
|
+
// Register <%= tool.name %> tool
|
|
35
|
+
<%= tool.name %>Tool := mcp.NewTool("<%= tool.name %>",
|
|
36
|
+
mcp.WithDescription("<%= tool.description %>"),
|
|
37
|
+
<% tool.parameters.forEach(function(param, index) { %>
|
|
38
|
+
<% if (param.type === 'string') { %>
|
|
39
|
+
mcp.WithString("<%= param.name %>",
|
|
40
|
+
<% } else if (param.type === 'number') { %>
|
|
41
|
+
mcp.WithNumber("<%= param.name %>",
|
|
42
|
+
<% } else if (param.type === 'boolean') { %>
|
|
43
|
+
mcp.WithBoolean("<%= param.name %>",
|
|
44
|
+
<% } else { %>
|
|
45
|
+
mcp.WithString("<%= param.name %>",
|
|
46
|
+
<% } %>
|
|
47
|
+
<% if (param.required) { %>
|
|
48
|
+
mcp.Required(),
|
|
49
|
+
<% } %>
|
|
50
|
+
mcp.Description("<%= param.description %>"),
|
|
51
|
+
),
|
|
52
|
+
<% }); %>
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
s.AddTool(<%= tool.name %>Tool, <%= tool.name %>Handler)
|
|
56
|
+
<% }); %>
|
|
57
|
+
|
|
58
|
+
<% if (transport === 'stdio') { %>
|
|
59
|
+
// Start STDIO server
|
|
60
|
+
if err := server.ServeStdio(s); err != nil {
|
|
61
|
+
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
|
|
62
|
+
os.Exit(1)
|
|
63
|
+
}
|
|
64
|
+
<% } else { %>
|
|
65
|
+
// Start SSE server
|
|
66
|
+
if err := server.ServeSSE(s, ":8080", "/messages"); err != nil {
|
|
67
|
+
fmt.Fprintf(os.Stderr, "Server error: %v\n", err)
|
|
68
|
+
os.Exit(1)
|
|
69
|
+
}
|
|
70
|
+
<% } %>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
<% if (includeExampleTool) { %>
|
|
74
|
+
func exampleToolHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
75
|
+
query, ok := request.Params.Arguments["query"].(string)
|
|
76
|
+
if !ok {
|
|
77
|
+
return mcp.NewToolResultError("query parameter is required"), nil
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return mcp.NewToolResultText(fmt.Sprintf("Echo: %s", query)), nil
|
|
81
|
+
}
|
|
82
|
+
<% } %>
|
|
83
|
+
|
|
84
|
+
<% tools.forEach(function(tool) { %>
|
|
85
|
+
func <%= tool.name %>Handler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
86
|
+
// TODO: Implement <%= tool.name %> logic
|
|
87
|
+
<% tool.parameters.forEach(function(param) { %>
|
|
88
|
+
<% if (param.type === 'string') { %>
|
|
89
|
+
<%= param.name %>, _ := request.Params.Arguments["<%= param.name %>"].(string)
|
|
90
|
+
<% } else if (param.type === 'number') { %>
|
|
91
|
+
<%= param.name %>, _ := request.Params.Arguments["<%= param.name %>"].(float64)
|
|
92
|
+
<% } else if (param.type === 'boolean') { %>
|
|
93
|
+
<%= param.name %>, _ := request.Params.Arguments["<%= param.name %>"].(bool)
|
|
94
|
+
<% } else { %>
|
|
95
|
+
<%= param.name %> := request.Params.Arguments["<%= param.name %>"]
|
|
96
|
+
<% } %>
|
|
97
|
+
<% }); %>
|
|
98
|
+
|
|
99
|
+
return mcp.NewToolResultText(fmt.Sprintf("<%= tool.name %> called with: %v", request.Params.Arguments)), nil
|
|
100
|
+
}
|
|
101
|
+
<% }); %>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package tools
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
|
|
7
|
+
"github.com/mark3labs/mcp-go/mcp"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// ExampleInput represents the input for the example tool
|
|
11
|
+
type ExampleInput struct {
|
|
12
|
+
Query string `json:"query"`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ExampleOutput represents the output of the example tool
|
|
16
|
+
type ExampleOutput struct {
|
|
17
|
+
Result string `json:"result"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ExampleTool creates the example tool definition
|
|
21
|
+
func ExampleTool() mcp.Tool {
|
|
22
|
+
return mcp.NewTool("example_tool",
|
|
23
|
+
mcp.WithDescription("An example tool that echoes the input"),
|
|
24
|
+
mcp.WithString("query",
|
|
25
|
+
mcp.Required(),
|
|
26
|
+
mcp.Description("The query to echo"),
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ExampleHandler handles the example tool execution
|
|
32
|
+
func ExampleHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
33
|
+
query, ok := request.Params.Arguments["query"].(string)
|
|
34
|
+
if !ok {
|
|
35
|
+
return mcp.NewToolResultError("query parameter is required"), nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
result := fmt.Sprintf("Echo: %s", query)
|
|
39
|
+
return mcp.NewToolResultText(result), nil
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "<%= name %>"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "<%= description || 'MCP Server generated by mcp-generator' %>"
|
|
6
|
+
|
|
7
|
+
[dependencies]
|
|
8
|
+
rmcp = { version = "0.1", features = ["server"<% if (transport === 'stdio') { %>, "transport-io"<% } else { %>, "transport-sse-server"<% } %>] }
|
|
9
|
+
tokio = { version = "1", features = ["full"] }
|
|
10
|
+
serde = { version = "1", features = ["derive"] }
|
|
11
|
+
serde_json = "1"
|
|
12
|
+
anyhow = "1"
|
|
13
|
+
tracing = "0.1"
|
|
14
|
+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|
15
|
+
|
|
16
|
+
[[bin]]
|
|
17
|
+
name = "<%= name %>"
|
|
18
|
+
path = "src/main.rs"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# <%= name %>
|
|
2
|
+
|
|
3
|
+
<%= description || 'MCP Server generated by mcp-generator' %>
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This is an MCP (Model Context Protocol) server built with Rust using the [rmcp](https://github.com/anthropics/rmcp) SDK.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- Rust 1.70 or higher
|
|
12
|
+
- Cargo
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Build the project
|
|
18
|
+
cargo build --release
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Running the Server
|
|
22
|
+
|
|
23
|
+
<% if (transport === 'stdio') { %>
|
|
24
|
+
### STDIO Transport
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cargo run --release
|
|
28
|
+
# or
|
|
29
|
+
./target/release/<%= name %>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The server communicates via standard input/output.
|
|
33
|
+
<% } else { %>
|
|
34
|
+
### SSE Transport
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cargo run --release
|
|
38
|
+
# or
|
|
39
|
+
./target/release/<%= name %>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The server will start an HTTP server with SSE endpoint on port 8080.
|
|
43
|
+
<% } %>
|
|
44
|
+
|
|
45
|
+
## Available Tools
|
|
46
|
+
|
|
47
|
+
<% if (includeExampleTool) { %>
|
|
48
|
+
- **example_tool**: An example tool that echoes the input
|
|
49
|
+
<% } %>
|
|
50
|
+
<% tools.forEach(function(tool) { %>
|
|
51
|
+
- **<%= tool.name %>**: <%= tool.description %>
|
|
52
|
+
<% }); %>
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Run in development mode
|
|
58
|
+
cargo run
|
|
59
|
+
|
|
60
|
+
# Run tests
|
|
61
|
+
cargo test
|
|
62
|
+
|
|
63
|
+
# Check for issues
|
|
64
|
+
cargo clippy
|
|
65
|
+
|
|
66
|
+
# Format code
|
|
67
|
+
cargo fmt
|
|
68
|
+
|
|
69
|
+
# Build release
|
|
70
|
+
cargo build --release
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Testing with MCP Inspector
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx @modelcontextprotocol/inspector ./target/release/<%= name %>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Project Structure
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
.
|
|
83
|
+
├── src/
|
|
84
|
+
│ ├── main.rs # Server entry point
|
|
85
|
+
│ └── tools.rs # Tool implementations
|
|
86
|
+
├── Cargo.toml
|
|
87
|
+
└── README.md
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use rmcp::{
|
|
3
|
+
model::{CallToolResult, Content, Tool, ToolInputSchema},
|
|
4
|
+
server::Server,
|
|
5
|
+
tool,
|
|
6
|
+
};
|
|
7
|
+
use serde_json::{json, Value};
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
10
|
+
|
|
11
|
+
mod tools;
|
|
12
|
+
|
|
13
|
+
#[tokio::main]
|
|
14
|
+
async fn main() -> Result<()> {
|
|
15
|
+
// Initialize logging
|
|
16
|
+
tracing_subscriber::registry()
|
|
17
|
+
.with(tracing_subscriber::EnvFilter::new(
|
|
18
|
+
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
|
|
19
|
+
))
|
|
20
|
+
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
|
|
21
|
+
.init();
|
|
22
|
+
|
|
23
|
+
tracing::info!("Starting <%= name %> MCP server");
|
|
24
|
+
|
|
25
|
+
// Create server
|
|
26
|
+
let server = Server::builder()
|
|
27
|
+
.name("<%= name %>")
|
|
28
|
+
.version("1.0.0")
|
|
29
|
+
<% if (includeExampleTool) { %>
|
|
30
|
+
.tool(tools::example_tool())
|
|
31
|
+
<% } %>
|
|
32
|
+
<% tools.forEach(function(tool) { %>
|
|
33
|
+
.tool(tools::<%= tool.name %>_tool())
|
|
34
|
+
<% }); %>
|
|
35
|
+
.build();
|
|
36
|
+
|
|
37
|
+
<% if (transport === 'stdio') { %>
|
|
38
|
+
// Run with STDIO transport
|
|
39
|
+
server.run_stdio().await?;
|
|
40
|
+
<% } else { %>
|
|
41
|
+
// Run with SSE transport
|
|
42
|
+
server.run_sse("0.0.0.0:8080", "/messages").await?;
|
|
43
|
+
<% } %>
|
|
44
|
+
|
|
45
|
+
Ok(())
|
|
46
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
use rmcp::{
|
|
2
|
+
model::{CallToolResult, Content, Tool, ToolInputSchema},
|
|
3
|
+
tool,
|
|
4
|
+
};
|
|
5
|
+
use serde::{Deserialize, Serialize};
|
|
6
|
+
use serde_json::{json, Value};
|
|
7
|
+
use std::collections::HashMap;
|
|
8
|
+
|
|
9
|
+
<% if (includeExampleTool) { %>
|
|
10
|
+
/// Example tool that echoes the input
|
|
11
|
+
#[derive(Debug, Deserialize)]
|
|
12
|
+
pub struct ExampleToolInput {
|
|
13
|
+
pub query: String,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub fn example_tool() -> Tool {
|
|
17
|
+
Tool {
|
|
18
|
+
name: "example_tool".to_string(),
|
|
19
|
+
description: Some("An example tool that echoes the input".to_string()),
|
|
20
|
+
input_schema: ToolInputSchema {
|
|
21
|
+
r#type: "object".to_string(),
|
|
22
|
+
properties: Some({
|
|
23
|
+
let mut props = HashMap::new();
|
|
24
|
+
props.insert(
|
|
25
|
+
"query".to_string(),
|
|
26
|
+
json!({
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "The query to echo"
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
props
|
|
32
|
+
}),
|
|
33
|
+
required: Some(vec!["query".to_string()]),
|
|
34
|
+
},
|
|
35
|
+
handler: Box::new(|args| {
|
|
36
|
+
Box::pin(async move {
|
|
37
|
+
let input: ExampleToolInput = serde_json::from_value(args)?;
|
|
38
|
+
Ok(CallToolResult {
|
|
39
|
+
content: vec![Content::Text {
|
|
40
|
+
text: format!("Echo: {}", input.query),
|
|
41
|
+
}],
|
|
42
|
+
is_error: None,
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
}),
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
<% } %>
|
|
49
|
+
|
|
50
|
+
<% tools.forEach(function(tool) { %>
|
|
51
|
+
/// <%= tool.description %>
|
|
52
|
+
#[derive(Debug, Deserialize)]
|
|
53
|
+
pub struct <%= tool.name.charAt(0).toUpperCase() + tool.name.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase()) %>Input {
|
|
54
|
+
<% tool.parameters.forEach(function(param) { %>
|
|
55
|
+
<% if (param.type === 'number') { %>
|
|
56
|
+
pub <%= param.name %>: f64,
|
|
57
|
+
<% } else if (param.type === 'boolean') { %>
|
|
58
|
+
pub <%= param.name %>: bool,
|
|
59
|
+
<% } else if (param.type === 'array') { %>
|
|
60
|
+
pub <%= param.name %>: Vec<Value>,
|
|
61
|
+
<% } else if (param.type === 'object') { %>
|
|
62
|
+
pub <%= param.name %>: HashMap<String, Value>,
|
|
63
|
+
<% } else { %>
|
|
64
|
+
pub <%= param.name %>: String,
|
|
65
|
+
<% } %>
|
|
66
|
+
<% }); %>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pub fn <%= tool.name %>_tool() -> Tool {
|
|
70
|
+
Tool {
|
|
71
|
+
name: "<%= tool.name %>".to_string(),
|
|
72
|
+
description: Some("<%= tool.description %>".to_string()),
|
|
73
|
+
input_schema: ToolInputSchema {
|
|
74
|
+
r#type: "object".to_string(),
|
|
75
|
+
properties: Some({
|
|
76
|
+
let mut props = HashMap::new();
|
|
77
|
+
<% tool.parameters.forEach(function(param) { %>
|
|
78
|
+
props.insert(
|
|
79
|
+
"<%= param.name %>".to_string(),
|
|
80
|
+
json!({
|
|
81
|
+
"type": "<%= param.type %>",
|
|
82
|
+
"description": "<%= param.description %>"
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
<% }); %>
|
|
86
|
+
props
|
|
87
|
+
}),
|
|
88
|
+
required: Some(vec![<%= tool.parameters.filter(p => p.required).map(p => '"' + p.name + '".to_string()').join(', ') %>]),
|
|
89
|
+
},
|
|
90
|
+
handler: Box::new(|args| {
|
|
91
|
+
Box::pin(async move {
|
|
92
|
+
// TODO: Implement <%= tool.name %> logic
|
|
93
|
+
let input: <%= tool.name.charAt(0).toUpperCase() + tool.name.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase()) %>Input = serde_json::from_value(args.clone())?;
|
|
94
|
+
|
|
95
|
+
Ok(CallToolResult {
|
|
96
|
+
content: vec![Content::Text {
|
|
97
|
+
text: format!("<%= tool.name %> called with: {:?}", args),
|
|
98
|
+
}],
|
|
99
|
+
is_error: None,
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
}),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
<% }); %>
|
|
File without changes
|