openapi-mcp-gen 1.0.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/LICENSE +21 -0
- package/README.md +202 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +526 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/templates/package.json.hbs +26 -0
- package/templates/server.ts.hbs +212 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 openapi-to-mcp contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# openapi-to-mcp
|
|
2
|
+
|
|
3
|
+
Generate a fully working **MCP (Model Context Protocol) server** from any OpenAPI 3.x spec — JSON or YAML, local file or URL.
|
|
4
|
+
|
|
5
|
+
One command turns your REST API into an AI-ready MCP server with proper tool definitions, input validation, authentication, and error handling.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g openapi-to-mcp
|
|
11
|
+
# or use without installing:
|
|
12
|
+
npx openapi-to-mcp ./petstore.yaml
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# From a local file
|
|
19
|
+
npx openapi-to-mcp ./petstore.yaml --output ./petstore-mcp
|
|
20
|
+
|
|
21
|
+
# From a URL
|
|
22
|
+
npx openapi-to-mcp https://petstore3.swagger.io/api/v3/openapi.json -o ./petstore-mcp
|
|
23
|
+
|
|
24
|
+
# Preview tools without generating files
|
|
25
|
+
npx openapi-to-mcp ./petstore.yaml --list-tools
|
|
26
|
+
|
|
27
|
+
# Dry run (parse only, no output)
|
|
28
|
+
npx openapi-to-mcp ./petstore.yaml --dry-run
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## What Gets Generated
|
|
32
|
+
|
|
33
|
+
Given this input:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
# petstore.yaml (OpenAPI 3.0)
|
|
37
|
+
openapi: "3.0.0"
|
|
38
|
+
info:
|
|
39
|
+
title: Petstore
|
|
40
|
+
version: "1.0.0"
|
|
41
|
+
servers:
|
|
42
|
+
- url: https://petstore3.swagger.io/api/v3
|
|
43
|
+
paths:
|
|
44
|
+
/pet/{petId}:
|
|
45
|
+
get:
|
|
46
|
+
operationId: getPetById
|
|
47
|
+
summary: Find pet by ID
|
|
48
|
+
parameters:
|
|
49
|
+
- name: petId
|
|
50
|
+
in: path
|
|
51
|
+
required: true
|
|
52
|
+
schema:
|
|
53
|
+
type: integer
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You get a ready-to-run MCP server:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
petstore-mcp/
|
|
60
|
+
├── src/
|
|
61
|
+
│ └── server.ts # MCP server with all tools
|
|
62
|
+
├── package.json
|
|
63
|
+
├── tsconfig.json
|
|
64
|
+
├── .env.example
|
|
65
|
+
└── README.md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cd petstore-mcp
|
|
72
|
+
npm install
|
|
73
|
+
npm run build
|
|
74
|
+
node dist/server.js
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## CLI Reference
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
openapi-to-mcp <spec> [options]
|
|
81
|
+
|
|
82
|
+
Arguments:
|
|
83
|
+
spec Path or URL to an OpenAPI 3.x spec (JSON or YAML)
|
|
84
|
+
|
|
85
|
+
Options:
|
|
86
|
+
-o, --output <dir> Output directory (default: ./mcp-server)
|
|
87
|
+
--dry-run Parse and show info, do not write files
|
|
88
|
+
--list-tools Print all tools that would be generated, then exit
|
|
89
|
+
-V, --version Show version
|
|
90
|
+
-h, --help Show help
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Authentication
|
|
94
|
+
|
|
95
|
+
`openapi-to-mcp` auto-detects auth from `securitySchemes` and generates the appropriate environment variable handling.
|
|
96
|
+
|
|
97
|
+
| Scheme | Detected as | Env var | Behaviour |
|
|
98
|
+
|--------|------------|---------|-----------|
|
|
99
|
+
| `http` + `bearer` | Bearer token | `API_BEARER_TOKEN` | `Authorization: Bearer $TOKEN` |
|
|
100
|
+
| `http` + `basic` | Basic auth | `API_BASIC_CREDENTIALS` | `Authorization: Basic $CREDS` |
|
|
101
|
+
| `apiKey` | API key | `API_KEY` | Header / query / cookie |
|
|
102
|
+
| (none) | No auth | — | No auth header added |
|
|
103
|
+
|
|
104
|
+
The generated server exits with a clear error message if a required env var is missing.
|
|
105
|
+
|
|
106
|
+
## Generated Server
|
|
107
|
+
|
|
108
|
+
The generated `src/server.ts`:
|
|
109
|
+
|
|
110
|
+
- Uses the official `@modelcontextprotocol/sdk` package
|
|
111
|
+
- Runs on **stdio** transport (standard for MCP)
|
|
112
|
+
- Creates **one tool per API endpoint** (named by `operationId` or derived from method + path)
|
|
113
|
+
- Validates inputs via MCP's built-in schema support
|
|
114
|
+
- Calls the real API with `fetch`, including auth
|
|
115
|
+
- Returns human-readable errors on HTTP failures
|
|
116
|
+
- Flattens JSON request body top-level properties into tool parameters for ergonomic use
|
|
117
|
+
|
|
118
|
+
## OpenAPI Feature Support
|
|
119
|
+
|
|
120
|
+
| Feature | Support |
|
|
121
|
+
|---------|---------|
|
|
122
|
+
| OpenAPI 3.0.x | ✅ |
|
|
123
|
+
| OpenAPI 3.1.x | ✅ |
|
|
124
|
+
| YAML specs | ✅ |
|
|
125
|
+
| JSON specs | ✅ |
|
|
126
|
+
| Remote URL specs | ✅ |
|
|
127
|
+
| Path parameters | ✅ |
|
|
128
|
+
| Query parameters | ✅ |
|
|
129
|
+
| Header parameters | ✅ |
|
|
130
|
+
| Request body (JSON) | ✅ |
|
|
131
|
+
| Bearer auth | ✅ |
|
|
132
|
+
| API key auth | ✅ |
|
|
133
|
+
| Basic auth | ✅ |
|
|
134
|
+
| `$ref` resolution | ✅ (local refs) |
|
|
135
|
+
| Deprecated endpoints | ✅ (annotated) |
|
|
136
|
+
| Duplicate operationIds | ✅ (auto-suffixed) |
|
|
137
|
+
|
|
138
|
+
## Using with Claude Desktop
|
|
139
|
+
|
|
140
|
+
Add the generated server to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"petstore": {
|
|
146
|
+
"command": "node",
|
|
147
|
+
"args": ["/absolute/path/to/petstore-mcp/dist/server.js"],
|
|
148
|
+
"env": {
|
|
149
|
+
"API_BEARER_TOKEN": "your-token-here"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Using with Claude Code
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
claude mcp add petstore -- node /absolute/path/to/petstore-mcp/dist/server.js
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
With environment variables:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
claude mcp add petstore \
|
|
166
|
+
-e API_BEARER_TOKEN=your-token \
|
|
167
|
+
-- node /absolute/path/to/petstore-mcp/dist/server.js
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Example: GitHub API
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npx openapi-to-mcp https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.yaml \
|
|
174
|
+
--output ./github-mcp
|
|
175
|
+
|
|
176
|
+
cd github-mcp
|
|
177
|
+
npm install && npm run build
|
|
178
|
+
export API_BEARER_TOKEN=ghp_your_token
|
|
179
|
+
node dist/server.js
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Recommended Models
|
|
183
|
+
|
|
184
|
+
When using your generated MCP server with Claude, these model IDs are confirmed compatible:
|
|
185
|
+
|
|
186
|
+
- `claude-haiku-4.5` — fast, low-cost, great for simple API calls
|
|
187
|
+
- `claude-sonnet-4-6` — balanced performance (recommended default)
|
|
188
|
+
- `claude-opus-4-6` — most capable, best for complex multi-step API workflows
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
Pull requests welcome. The core pipeline is:
|
|
193
|
+
|
|
194
|
+
1. `src/parser.ts` — loads and parses the OpenAPI spec into a clean domain model
|
|
195
|
+
2. `src/generator.ts` — maps the parsed spec to template data and writes files
|
|
196
|
+
3. `src/templates.ts` — Handlebars rendering helpers
|
|
197
|
+
4. `templates/server.ts.hbs` — the MCP server template
|
|
198
|
+
5. `templates/package.json.hbs` — generated project package.json template
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import path4 from "path";
|
|
7
|
+
|
|
8
|
+
// src/parser.ts
|
|
9
|
+
import fs from "fs-extra";
|
|
10
|
+
import yaml from "js-yaml";
|
|
11
|
+
import path from "path";
|
|
12
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
13
|
+
function toCamelCase(str) {
|
|
14
|
+
return str.replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (c) => c.toLowerCase());
|
|
15
|
+
}
|
|
16
|
+
function toSafeIdentifier(str) {
|
|
17
|
+
const cleaned = str.replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_");
|
|
18
|
+
return toCamelCase(cleaned);
|
|
19
|
+
}
|
|
20
|
+
function deriveToolName(method, urlPath, operationId) {
|
|
21
|
+
if (operationId && operationId.trim()) {
|
|
22
|
+
return toSafeIdentifier(operationId.trim());
|
|
23
|
+
}
|
|
24
|
+
const segments = urlPath.split("/").filter(Boolean).map((s) => s.replace(/\{([^}]+)\}/, "By_$1")).map((s) => s.replace(/[^a-zA-Z0-9]/g, "_"));
|
|
25
|
+
const name = [method, ...segments].join("_");
|
|
26
|
+
return toSafeIdentifier(name);
|
|
27
|
+
}
|
|
28
|
+
function resolveRef(spec, ref) {
|
|
29
|
+
const parts = ref.replace(/^#\//, "").split("/");
|
|
30
|
+
let node = spec;
|
|
31
|
+
for (const part of parts) {
|
|
32
|
+
node = node?.[part];
|
|
33
|
+
}
|
|
34
|
+
return node ?? { type: "object" };
|
|
35
|
+
}
|
|
36
|
+
function resolveSchema(spec, schema) {
|
|
37
|
+
if (!schema) return { type: "object" };
|
|
38
|
+
if (schema["$ref"]) return resolveRef(spec, schema["$ref"]);
|
|
39
|
+
return schema;
|
|
40
|
+
}
|
|
41
|
+
function parseParam(spec, raw) {
|
|
42
|
+
const schema = resolveSchema(spec, raw.schema ?? { type: "string" });
|
|
43
|
+
return {
|
|
44
|
+
name: raw.name,
|
|
45
|
+
camelName: toCamelCase(raw.name),
|
|
46
|
+
in: raw.in,
|
|
47
|
+
description: raw.description ?? "",
|
|
48
|
+
required: raw.required ?? false,
|
|
49
|
+
schema
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function detectAuth(spec) {
|
|
53
|
+
const schemes = spec.components?.securitySchemes ?? {};
|
|
54
|
+
for (const [, scheme] of Object.entries(schemes)) {
|
|
55
|
+
if (scheme.type === "http" && scheme.scheme === "bearer") {
|
|
56
|
+
return {
|
|
57
|
+
type: "bearer",
|
|
58
|
+
paramName: "Authorization",
|
|
59
|
+
paramIn: "header",
|
|
60
|
+
envVar: "API_BEARER_TOKEN",
|
|
61
|
+
description: scheme.description ?? "Bearer token authentication"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (scheme.type === "http" && scheme.scheme === "basic") {
|
|
65
|
+
return {
|
|
66
|
+
type: "basic",
|
|
67
|
+
paramName: "Authorization",
|
|
68
|
+
paramIn: "header",
|
|
69
|
+
envVar: "API_BASIC_CREDENTIALS",
|
|
70
|
+
description: scheme.description ?? "Basic authentication (base64 user:pass)"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (scheme.type === "apiKey") {
|
|
74
|
+
const paramIn = scheme.in ?? "header";
|
|
75
|
+
const paramName = scheme.name ?? "X-API-Key";
|
|
76
|
+
return {
|
|
77
|
+
type: "apiKey",
|
|
78
|
+
paramName,
|
|
79
|
+
paramIn,
|
|
80
|
+
envVar: "API_KEY",
|
|
81
|
+
description: scheme.description ?? `API key sent in ${paramIn} as "${paramName}"`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
type: "none",
|
|
87
|
+
paramName: "",
|
|
88
|
+
paramIn: "header",
|
|
89
|
+
envVar: "",
|
|
90
|
+
description: "No authentication"
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function parseEndpoint(spec, urlPath, method, op, pathLevelParams) {
|
|
94
|
+
const rawParams = [...pathLevelParams];
|
|
95
|
+
for (const p of op.parameters ?? []) {
|
|
96
|
+
const resolved = p["$ref"] ? resolveRef(spec, p["$ref"]) : p;
|
|
97
|
+
const existing = rawParams.findIndex(
|
|
98
|
+
(x) => x.name === resolved.name && x.in === resolved.in
|
|
99
|
+
);
|
|
100
|
+
if (existing >= 0) rawParams[existing] = resolved;
|
|
101
|
+
else rawParams.push(resolved);
|
|
102
|
+
}
|
|
103
|
+
const parsedParams = rawParams.map((p) => parseParam(spec, p));
|
|
104
|
+
const pathParams = parsedParams.filter((p) => p.in === "path");
|
|
105
|
+
const queryParams = parsedParams.filter((p) => p.in === "query");
|
|
106
|
+
const headerParams = parsedParams.filter((p) => p.in === "header");
|
|
107
|
+
let hasBody = false;
|
|
108
|
+
let bodyRequired = false;
|
|
109
|
+
let bodyContentType = "application/json";
|
|
110
|
+
let bodySchema = null;
|
|
111
|
+
if (op.requestBody) {
|
|
112
|
+
const rb = op.requestBody;
|
|
113
|
+
hasBody = true;
|
|
114
|
+
bodyRequired = rb.required ?? false;
|
|
115
|
+
const contentTypes = Object.keys(rb.content ?? {});
|
|
116
|
+
bodyContentType = contentTypes.find((ct) => ct.includes("json")) ?? contentTypes[0] ?? "application/json";
|
|
117
|
+
const mediaType = rb.content?.[bodyContentType];
|
|
118
|
+
if (mediaType?.schema) {
|
|
119
|
+
bodySchema = resolveSchema(spec, mediaType.schema);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const allRequired = [
|
|
123
|
+
...pathParams.filter((p) => p.required).map((p) => p.name),
|
|
124
|
+
...queryParams.filter((p) => p.required).map((p) => p.name),
|
|
125
|
+
...headerParams.filter((p) => p.required).map((p) => p.name)
|
|
126
|
+
];
|
|
127
|
+
if (hasBody && bodyRequired) allRequired.push("body");
|
|
128
|
+
const operationId = op.operationId ?? "";
|
|
129
|
+
const toolName = deriveToolName(method, urlPath, operationId);
|
|
130
|
+
return {
|
|
131
|
+
toolName,
|
|
132
|
+
operationId,
|
|
133
|
+
method,
|
|
134
|
+
path: urlPath,
|
|
135
|
+
summary: op.summary ?? "",
|
|
136
|
+
description: op.description ?? op.summary ?? "",
|
|
137
|
+
tags: op.tags ?? [],
|
|
138
|
+
deprecated: op.deprecated ?? false,
|
|
139
|
+
pathParams,
|
|
140
|
+
queryParams,
|
|
141
|
+
headerParams,
|
|
142
|
+
hasBody,
|
|
143
|
+
bodyRequired,
|
|
144
|
+
bodyContentType,
|
|
145
|
+
bodySchema,
|
|
146
|
+
allRequired
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function loadSpec(input) {
|
|
150
|
+
let raw;
|
|
151
|
+
if (input.startsWith("http://") || input.startsWith("https://")) {
|
|
152
|
+
const res = await fetch(input);
|
|
153
|
+
if (!res.ok) {
|
|
154
|
+
throw new Error(`Failed to fetch spec: ${res.status} ${res.statusText}`);
|
|
155
|
+
}
|
|
156
|
+
raw = await res.text();
|
|
157
|
+
} else {
|
|
158
|
+
const resolved = path.resolve(input);
|
|
159
|
+
if (!fs.existsSync(resolved)) {
|
|
160
|
+
throw new Error(`Spec file not found: ${resolved}`);
|
|
161
|
+
}
|
|
162
|
+
raw = await fs.readFile(resolved, "utf-8");
|
|
163
|
+
}
|
|
164
|
+
let spec;
|
|
165
|
+
const trimmed = raw.trimStart();
|
|
166
|
+
if (trimmed.startsWith("{")) {
|
|
167
|
+
spec = JSON.parse(raw);
|
|
168
|
+
} else {
|
|
169
|
+
spec = yaml.load(raw);
|
|
170
|
+
}
|
|
171
|
+
if (!spec || typeof spec !== "object") {
|
|
172
|
+
throw new Error("Could not parse spec: result is not an object");
|
|
173
|
+
}
|
|
174
|
+
const s = spec;
|
|
175
|
+
if (!s.openapi) {
|
|
176
|
+
throw new Error("Not a valid OpenAPI 3.x spec (missing 'openapi' field)");
|
|
177
|
+
}
|
|
178
|
+
if (!s.openapi.startsWith("3.")) {
|
|
179
|
+
throw new Error(`Unsupported OpenAPI version: ${s.openapi} (only 3.x is supported)`);
|
|
180
|
+
}
|
|
181
|
+
return s;
|
|
182
|
+
}
|
|
183
|
+
function parseSpec(spec) {
|
|
184
|
+
const endpoints = [];
|
|
185
|
+
for (const [urlPath, pathItem] of Object.entries(spec.paths ?? {})) {
|
|
186
|
+
const pathLevelParams = (pathItem.parameters ?? []).map(
|
|
187
|
+
(p) => p["$ref"] ? resolveRef(spec, p["$ref"]) : p
|
|
188
|
+
);
|
|
189
|
+
for (const method of HTTP_METHODS) {
|
|
190
|
+
const op = pathItem[method];
|
|
191
|
+
if (!op) continue;
|
|
192
|
+
const endpoint = parseEndpoint(spec, urlPath, method, op, pathLevelParams);
|
|
193
|
+
endpoints.push(endpoint);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const seen = /* @__PURE__ */ new Map();
|
|
197
|
+
for (const ep of endpoints) {
|
|
198
|
+
const count = (seen.get(ep.toolName) ?? 0) + 1;
|
|
199
|
+
seen.set(ep.toolName, count);
|
|
200
|
+
if (count > 1) ep.toolName = `${ep.toolName}_${count}`;
|
|
201
|
+
}
|
|
202
|
+
const baseUrl = spec.servers?.[0]?.url?.replace(/\/$/, "") ?? "https://api.example.com";
|
|
203
|
+
return {
|
|
204
|
+
title: spec.info.title,
|
|
205
|
+
version: spec.info.version,
|
|
206
|
+
description: spec.info.description ?? "",
|
|
207
|
+
baseUrl,
|
|
208
|
+
endpoints,
|
|
209
|
+
auth: detectAuth(spec),
|
|
210
|
+
rawSchemas: spec.components?.schemas ?? {}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// src/generator.ts
|
|
215
|
+
import path3 from "path";
|
|
216
|
+
import fs3 from "fs-extra";
|
|
217
|
+
|
|
218
|
+
// src/templates.ts
|
|
219
|
+
import Handlebars from "handlebars";
|
|
220
|
+
import fs2 from "fs-extra";
|
|
221
|
+
import path2 from "path";
|
|
222
|
+
import { fileURLToPath } from "url";
|
|
223
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
224
|
+
var __dirname = path2.dirname(__filename);
|
|
225
|
+
Handlebars.registerHelper("eq", (a, b) => a === b);
|
|
226
|
+
Handlebars.registerHelper("neq", (a, b) => a !== b);
|
|
227
|
+
Handlebars.registerHelper("and", (a, b) => Boolean(a) && Boolean(b));
|
|
228
|
+
Handlebars.registerHelper("or", (a, b) => Boolean(a) || Boolean(b));
|
|
229
|
+
Handlebars.registerHelper("not", (a) => !a);
|
|
230
|
+
Handlebars.registerHelper("gt", (a, b) => a > b);
|
|
231
|
+
Handlebars.registerHelper(
|
|
232
|
+
"json",
|
|
233
|
+
(value) => JSON.stringify(value, null, 2)
|
|
234
|
+
);
|
|
235
|
+
Handlebars.registerHelper(
|
|
236
|
+
"jsonInline",
|
|
237
|
+
(value) => JSON.stringify(value)
|
|
238
|
+
);
|
|
239
|
+
Handlebars.registerHelper(
|
|
240
|
+
"ucFirst",
|
|
241
|
+
(s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : ""
|
|
242
|
+
);
|
|
243
|
+
function getTemplatesRoot() {
|
|
244
|
+
const candidates = [
|
|
245
|
+
path2.resolve(__dirname, "..", "templates"),
|
|
246
|
+
path2.resolve(__dirname, "..", "..", "templates")
|
|
247
|
+
];
|
|
248
|
+
for (const candidate of candidates) {
|
|
249
|
+
if (fs2.existsSync(candidate)) return candidate;
|
|
250
|
+
}
|
|
251
|
+
throw new Error(
|
|
252
|
+
"Templates directory not found. Searched: " + candidates.join(", ")
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
function renderTemplate(templateFile, data) {
|
|
256
|
+
const templatesRoot = getTemplatesRoot();
|
|
257
|
+
const templatePath = path2.join(templatesRoot, templateFile);
|
|
258
|
+
if (!fs2.existsSync(templatePath)) {
|
|
259
|
+
throw new Error(`Template file not found: ${templatePath}`);
|
|
260
|
+
}
|
|
261
|
+
const raw = fs2.readFileSync(templatePath, "utf-8");
|
|
262
|
+
const compiled = Handlebars.compile(raw, { noEscape: true });
|
|
263
|
+
return compiled(data);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/generator.ts
|
|
267
|
+
function buildJsonSchema(schema) {
|
|
268
|
+
if (!schema) return { type: "object" };
|
|
269
|
+
const result = {};
|
|
270
|
+
if (schema.type) result.type = schema.type;
|
|
271
|
+
if (schema.description) result.description = schema.description;
|
|
272
|
+
if (schema.enum) result.enum = schema.enum;
|
|
273
|
+
if (schema.default !== void 0) result.default = schema.default;
|
|
274
|
+
if (schema.format) result.format = schema.format;
|
|
275
|
+
if (schema.minimum !== void 0) result.minimum = schema.minimum;
|
|
276
|
+
if (schema.maximum !== void 0) result.maximum = schema.maximum;
|
|
277
|
+
if (schema.minLength !== void 0) result.minLength = schema.minLength;
|
|
278
|
+
if (schema.maxLength !== void 0) result.maxLength = schema.maxLength;
|
|
279
|
+
if (schema.pattern) result.pattern = schema.pattern;
|
|
280
|
+
if (schema.properties) {
|
|
281
|
+
result.type = result.type ?? "object";
|
|
282
|
+
result.properties = Object.fromEntries(
|
|
283
|
+
Object.entries(schema.properties).map(([k, v]) => [k, buildJsonSchema(v)])
|
|
284
|
+
);
|
|
285
|
+
if (schema.required) result.required = schema.required;
|
|
286
|
+
}
|
|
287
|
+
if (schema.items) {
|
|
288
|
+
result.type = result.type ?? "array";
|
|
289
|
+
result.items = buildJsonSchema(schema.items);
|
|
290
|
+
}
|
|
291
|
+
if (schema.allOf) {
|
|
292
|
+
result.allOf = schema.allOf.map(buildJsonSchema);
|
|
293
|
+
}
|
|
294
|
+
if (schema.oneOf) {
|
|
295
|
+
result.oneOf = schema.oneOf.map(buildJsonSchema);
|
|
296
|
+
}
|
|
297
|
+
if (schema.anyOf) {
|
|
298
|
+
result.anyOf = schema.anyOf.map(buildJsonSchema);
|
|
299
|
+
}
|
|
300
|
+
if (result.type === void 0 && !schema.properties && !schema.items) {
|
|
301
|
+
result.type = "string";
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
function paramToJsonSchemaProp(param) {
|
|
306
|
+
const base = buildJsonSchema(param.schema);
|
|
307
|
+
if (param.description && !base.description) {
|
|
308
|
+
base.description = param.description;
|
|
309
|
+
}
|
|
310
|
+
return base;
|
|
311
|
+
}
|
|
312
|
+
function buildInputSchema(ep) {
|
|
313
|
+
const properties = {};
|
|
314
|
+
const required = [];
|
|
315
|
+
for (const p of ep.pathParams) {
|
|
316
|
+
properties[p.name] = {
|
|
317
|
+
...paramToJsonSchemaProp(p),
|
|
318
|
+
description: (p.description ? p.description + " " : "") + `(path parameter)`
|
|
319
|
+
};
|
|
320
|
+
if (p.required) required.push(p.name);
|
|
321
|
+
}
|
|
322
|
+
for (const p of ep.queryParams) {
|
|
323
|
+
properties[p.name] = {
|
|
324
|
+
...paramToJsonSchemaProp(p),
|
|
325
|
+
description: (p.description ? p.description + " " : "") + `(query parameter)`
|
|
326
|
+
};
|
|
327
|
+
if (p.required) required.push(p.name);
|
|
328
|
+
}
|
|
329
|
+
for (const p of ep.headerParams) {
|
|
330
|
+
properties[p.name] = {
|
|
331
|
+
...paramToJsonSchemaProp(p),
|
|
332
|
+
description: (p.description ? p.description + " " : "") + `(header parameter)`
|
|
333
|
+
};
|
|
334
|
+
if (p.required) required.push(p.name);
|
|
335
|
+
}
|
|
336
|
+
if (ep.hasBody) {
|
|
337
|
+
if (ep.bodySchema?.properties) {
|
|
338
|
+
for (const [key, propSchema] of Object.entries(ep.bodySchema.properties)) {
|
|
339
|
+
properties[key] = buildJsonSchema(propSchema);
|
|
340
|
+
if (ep.bodySchema.required?.includes(key)) required.push(key);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
properties["body"] = ep.bodySchema ? buildJsonSchema(ep.bodySchema) : { type: "object", description: "Request body" };
|
|
344
|
+
if (ep.bodyRequired) required.push("body");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return { type: "object", properties, required };
|
|
348
|
+
}
|
|
349
|
+
function buildToolData(ep) {
|
|
350
|
+
const inputSchema = buildInputSchema(ep);
|
|
351
|
+
const bodyHasTopLevelProps = Boolean(ep.bodySchema?.properties);
|
|
352
|
+
const bodyProps = bodyHasTopLevelProps ? Object.keys(ep.bodySchema.properties) : [];
|
|
353
|
+
return {
|
|
354
|
+
toolName: ep.toolName,
|
|
355
|
+
description: [ep.summary, ep.description].filter(Boolean).filter((v, i, a) => a.indexOf(v) === i).join(" \u2014 ").slice(0, 1024) || ep.path,
|
|
356
|
+
inputSchemaJson: JSON.stringify(inputSchema, null, 4),
|
|
357
|
+
method: ep.method.toUpperCase(),
|
|
358
|
+
urlPath: ep.path,
|
|
359
|
+
pathParams: ep.pathParams.map((p) => p.name),
|
|
360
|
+
pathReplacements: ep.pathParams.map((p) => ({
|
|
361
|
+
placeholder: `{${p.name}}`,
|
|
362
|
+
paramName: p.name
|
|
363
|
+
})),
|
|
364
|
+
queryParams: ep.queryParams.map((p) => p.name),
|
|
365
|
+
headerParams: ep.headerParams.map((p) => p.name),
|
|
366
|
+
hasBody: ep.hasBody,
|
|
367
|
+
bodyHasTopLevelProps,
|
|
368
|
+
bodyProps,
|
|
369
|
+
bodyContentType: ep.bodyContentType,
|
|
370
|
+
deprecated: ep.deprecated
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function generate(spec, options) {
|
|
374
|
+
const { outputDir } = options;
|
|
375
|
+
await fs3.ensureDir(outputDir);
|
|
376
|
+
await fs3.ensureDir(path3.join(outputDir, "src"));
|
|
377
|
+
const tools = spec.endpoints.map(buildToolData);
|
|
378
|
+
const serverName = spec.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "openapi-mcp";
|
|
379
|
+
const templateData = {
|
|
380
|
+
serverName,
|
|
381
|
+
serverTitle: spec.title,
|
|
382
|
+
serverVersion: spec.info ? spec.version : "1.0.0",
|
|
383
|
+
serverDescription: spec.description || spec.title,
|
|
384
|
+
baseUrl: spec.baseUrl,
|
|
385
|
+
tools,
|
|
386
|
+
toolCount: tools.length,
|
|
387
|
+
auth: spec.auth,
|
|
388
|
+
hasAuth: spec.auth.type !== "none",
|
|
389
|
+
isBearer: spec.auth.type === "bearer",
|
|
390
|
+
isApiKey: spec.auth.type === "apiKey",
|
|
391
|
+
isBasic: spec.auth.type === "basic",
|
|
392
|
+
apiKeyInHeader: spec.auth.paramIn === "header",
|
|
393
|
+
apiKeyInQuery: spec.auth.paramIn === "query"
|
|
394
|
+
};
|
|
395
|
+
const serverCode = renderTemplate("server.ts.hbs", templateData);
|
|
396
|
+
await fs3.writeFile(path3.join(outputDir, "src", "server.ts"), serverCode, "utf-8");
|
|
397
|
+
const pkgJson = renderTemplate("package.json.hbs", templateData);
|
|
398
|
+
await fs3.writeFile(path3.join(outputDir, "package.json"), pkgJson, "utf-8");
|
|
399
|
+
const tsconfigContent = JSON.stringify(
|
|
400
|
+
{
|
|
401
|
+
compilerOptions: {
|
|
402
|
+
target: "ES2022",
|
|
403
|
+
module: "ESNext",
|
|
404
|
+
moduleResolution: "bundler",
|
|
405
|
+
strict: true,
|
|
406
|
+
esModuleInterop: true,
|
|
407
|
+
skipLibCheck: true,
|
|
408
|
+
outDir: "dist",
|
|
409
|
+
rootDir: "src",
|
|
410
|
+
resolveJsonModule: true
|
|
411
|
+
},
|
|
412
|
+
include: ["src"],
|
|
413
|
+
exclude: ["node_modules", "dist"]
|
|
414
|
+
},
|
|
415
|
+
null,
|
|
416
|
+
2
|
|
417
|
+
);
|
|
418
|
+
await fs3.writeFile(
|
|
419
|
+
path3.join(outputDir, "tsconfig.json"),
|
|
420
|
+
tsconfigContent,
|
|
421
|
+
"utf-8"
|
|
422
|
+
);
|
|
423
|
+
const envLines = ["# Generated by openapi-to-mcp", ""];
|
|
424
|
+
if (spec.auth.type !== "none") {
|
|
425
|
+
envLines.push(`# ${spec.auth.description}`);
|
|
426
|
+
envLines.push(`${spec.auth.envVar}=`);
|
|
427
|
+
envLines.push("");
|
|
428
|
+
}
|
|
429
|
+
envLines.push("# Override the base URL if needed");
|
|
430
|
+
envLines.push(`BASE_URL=${spec.baseUrl}`);
|
|
431
|
+
envLines.push("");
|
|
432
|
+
await fs3.writeFile(
|
|
433
|
+
path3.join(outputDir, ".env.example"),
|
|
434
|
+
envLines.join("\n"),
|
|
435
|
+
"utf-8"
|
|
436
|
+
);
|
|
437
|
+
const readme = buildGeneratedReadme(spec, serverName, tools.length);
|
|
438
|
+
await fs3.writeFile(path3.join(outputDir, "README.md"), readme, "utf-8");
|
|
439
|
+
}
|
|
440
|
+
function buildGeneratedReadme(spec, serverName, toolCount) {
|
|
441
|
+
const authSection = spec.auth.type === "none" ? "" : `## Authentication
|
|
442
|
+
|
|
443
|
+
Set the \`${spec.auth.envVar}\` environment variable:
|
|
444
|
+
|
|
445
|
+
\`\`\`bash
|
|
446
|
+
export ${spec.auth.envVar}=your_token_here
|
|
447
|
+
\`\`\`
|
|
448
|
+
|
|
449
|
+
${spec.auth.description}
|
|
450
|
+
`;
|
|
451
|
+
return `# ${spec.title} MCP Server
|
|
452
|
+
|
|
453
|
+
Generated by [openapi-to-mcp](https://github.com/example/openapi-to-mcp).
|
|
454
|
+
|
|
455
|
+
**${spec.description || spec.title}**
|
|
456
|
+
|
|
457
|
+
- **Base URL:** \`${spec.baseUrl}\`
|
|
458
|
+
- **Tools:** ${toolCount}
|
|
459
|
+
|
|
460
|
+
## Quick Start
|
|
461
|
+
|
|
462
|
+
\`\`\`bash
|
|
463
|
+
npm install
|
|
464
|
+
npm run build
|
|
465
|
+
node dist/server.js
|
|
466
|
+
\`\`\`
|
|
467
|
+
|
|
468
|
+
${authSection}
|
|
469
|
+
## Usage with Claude Desktop
|
|
470
|
+
|
|
471
|
+
Add to your \`claude_desktop_config.json\`:
|
|
472
|
+
|
|
473
|
+
\`\`\`json
|
|
474
|
+
{
|
|
475
|
+
"mcpServers": {
|
|
476
|
+
"${serverName}": {
|
|
477
|
+
"command": "node",
|
|
478
|
+
"args": ["/absolute/path/to/${serverName}/dist/server.js"]${spec.auth.type !== "none" ? `,
|
|
479
|
+
"env": {
|
|
480
|
+
"${spec.auth.envVar}": "your_token_here"
|
|
481
|
+
}` : ""}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
\`\`\`
|
|
486
|
+
|
|
487
|
+
## Available Tools (${toolCount})
|
|
488
|
+
|
|
489
|
+
| Tool | Method | Path | Description |
|
|
490
|
+
|------|--------|------|-------------|
|
|
491
|
+
${spec.endpoints.map(
|
|
492
|
+
(ep) => `| \`${ep.toolName}\` | \`${ep.method.toUpperCase()}\` | \`${ep.path}\` | ${ep.summary || ep.description || ""} |`
|
|
493
|
+
).join("\n")}
|
|
494
|
+
`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/index.ts
|
|
498
|
+
var program = new Command();
|
|
499
|
+
program.name("mcp-from-openapi").description("Generate a fully working MCP server from an OpenAPI 3.x spec").version("1.0.0").argument("<spec>", "Path or URL to OpenAPI 3.x spec (JSON or YAML)").option("-o, --output <dir>", "Output directory for generated MCP server", "./mcp-server").action(async (specPath, opts) => {
|
|
500
|
+
try {
|
|
501
|
+
console.log(chalk.blue("Loading OpenAPI spec:"), specPath);
|
|
502
|
+
const spec = await loadSpec(specPath);
|
|
503
|
+
const parsed = parseSpec(spec);
|
|
504
|
+
console.log(chalk.green("Found"), `${parsed.endpoints.length} endpoints`);
|
|
505
|
+
if (parsed.auth) {
|
|
506
|
+
console.log(chalk.green("Auth:"), `${parsed.auth.type} (${parsed.auth.envVar})`);
|
|
507
|
+
}
|
|
508
|
+
const outputDir = path4.resolve(opts.output);
|
|
509
|
+
console.log(chalk.blue("Generating MCP server to:"), outputDir);
|
|
510
|
+
await generate(parsed, { outputDir });
|
|
511
|
+
console.log("");
|
|
512
|
+
console.log(chalk.green.bold("MCP server generated successfully!"));
|
|
513
|
+
console.log("");
|
|
514
|
+
console.log("Next steps:");
|
|
515
|
+
console.log(chalk.dim(` cd ${opts.output}`));
|
|
516
|
+
console.log(chalk.dim(" npm install"));
|
|
517
|
+
console.log(chalk.dim(" npm run build"));
|
|
518
|
+
console.log(chalk.dim(" npm start"));
|
|
519
|
+
console.log("");
|
|
520
|
+
} catch (err) {
|
|
521
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : err);
|
|
522
|
+
process.exit(1);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
program.parse();
|
|
526
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/parser.ts","../src/generator.ts","../src/templates.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport path from \"node:path\";\nimport { loadSpec, parseSpec } from \"./parser.js\";\nimport { generate } from \"./generator.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"mcp-from-openapi\")\n .description(\"Generate a fully working MCP server from an OpenAPI 3.x spec\")\n .version(\"1.0.0\")\n .argument(\"<spec>\", \"Path or URL to OpenAPI 3.x spec (JSON or YAML)\")\n .option(\"-o, --output <dir>\", \"Output directory for generated MCP server\", \"./mcp-server\")\n .action(async (specPath: string, opts: { output: string }) => {\n try {\n console.log(chalk.blue(\"Loading OpenAPI spec:\"), specPath);\n\n const spec = await loadSpec(specPath);\n const parsed = parseSpec(spec);\n\n console.log(chalk.green(\"Found\"), `${parsed.endpoints.length} endpoints`);\n if (parsed.auth) {\n console.log(chalk.green(\"Auth:\"), `${parsed.auth.type} (${parsed.auth.envVar})`);\n }\n\n const outputDir = path.resolve(opts.output);\n console.log(chalk.blue(\"Generating MCP server to:\"), outputDir);\n\n await generate(parsed, { outputDir });\n\n console.log(\"\");\n console.log(chalk.green.bold(\"MCP server generated successfully!\"));\n console.log(\"\");\n console.log(\"Next steps:\");\n console.log(chalk.dim(` cd ${opts.output}`));\n console.log(chalk.dim(\" npm install\"));\n console.log(chalk.dim(\" npm run build\"));\n console.log(chalk.dim(\" npm start\"));\n console.log(\"\");\n } catch (err) {\n console.error(chalk.red(\"Error:\"), err instanceof Error ? err.message : err);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import fs from \"fs-extra\";\nimport yaml from \"js-yaml\";\nimport path from \"node:path\";\n\n// ─── OpenAPI 3.x types (minimal subset we care about) ────────────────────────\n\nexport interface OpenAPISpec {\n openapi: string;\n info: { title: string; version: string; description?: string };\n servers?: Array<{ url: string; description?: string }>;\n paths?: Record<string, PathItem>;\n components?: {\n schemas?: Record<string, SchemaObject>;\n securitySchemes?: Record<string, SecurityScheme>;\n };\n security?: SecurityRequirement[];\n}\n\nexport type HttpMethod = \"get\" | \"post\" | \"put\" | \"patch\" | \"delete\" | \"head\" | \"options\";\n\nexport interface PathItem {\n get?: OperationObject;\n post?: OperationObject;\n put?: OperationObject;\n patch?: OperationObject;\n delete?: OperationObject;\n head?: OperationObject;\n options?: OperationObject;\n parameters?: ParameterObject[];\n}\n\nexport interface OperationObject {\n operationId?: string;\n summary?: string;\n description?: string;\n tags?: string[];\n parameters?: ParameterObject[];\n requestBody?: RequestBodyObject;\n responses?: Record<string, ResponseObject>;\n security?: SecurityRequirement[];\n deprecated?: boolean;\n}\n\nexport interface ParameterObject {\n name: string;\n in: \"query\" | \"path\" | \"header\" | \"cookie\";\n description?: string;\n required?: boolean;\n schema?: SchemaObject;\n \"$ref\"?: string;\n}\n\nexport interface RequestBodyObject {\n description?: string;\n required?: boolean;\n content: Record<string, { schema?: SchemaObject }>;\n}\n\nexport interface ResponseObject {\n description?: string;\n content?: Record<string, { schema?: SchemaObject }>;\n}\n\nexport interface SchemaObject {\n type?: string;\n format?: string;\n description?: string;\n properties?: Record<string, SchemaObject>;\n items?: SchemaObject;\n required?: string[];\n enum?: unknown[];\n default?: unknown;\n nullable?: boolean;\n allOf?: SchemaObject[];\n oneOf?: SchemaObject[];\n anyOf?: SchemaObject[];\n \"$ref\"?: string;\n additionalProperties?: boolean | SchemaObject;\n minimum?: number;\n maximum?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n example?: unknown;\n}\n\nexport interface SecurityScheme {\n type: \"apiKey\" | \"http\" | \"oauth2\" | \"openIdConnect\";\n scheme?: string; // for type=http: \"bearer\", \"basic\"\n in?: \"header\" | \"query\" | \"cookie\"; // for type=apiKey\n name?: string; // for type=apiKey\n description?: string;\n flows?: Record<string, unknown>;\n}\n\nexport type SecurityRequirement = Record<string, string[]>;\n\n// ─── Parsed domain types ──────────────────────────────────────────────────────\n\nexport interface ParsedEndpoint {\n /** Sanitised identifier, e.g. \"getPetById\" */\n toolName: string;\n /** Original operationId (may be empty) */\n operationId: string;\n method: HttpMethod;\n path: string;\n summary: string;\n description: string;\n tags: string[];\n deprecated: boolean;\n pathParams: ParsedParam[];\n queryParams: ParsedParam[];\n headerParams: ParsedParam[];\n hasBody: boolean;\n bodyRequired: boolean;\n bodyContentType: string;\n bodySchema: SchemaObject | null;\n allRequired: string[];\n}\n\nexport interface ParsedParam {\n name: string;\n camelName: string;\n in: \"query\" | \"path\" | \"header\" | \"cookie\";\n description: string;\n required: boolean;\n schema: SchemaObject;\n}\n\nexport type AuthType = \"apiKey\" | \"bearer\" | \"basic\" | \"none\";\n\nexport interface ParsedAuth {\n type: AuthType;\n /** For apiKey: header/query/cookie name */\n paramName: string;\n /** For apiKey: where the key is sent */\n paramIn: \"header\" | \"query\" | \"cookie\";\n envVar: string;\n description: string;\n}\n\nexport interface ParsedSpec {\n title: string;\n version: string;\n description: string;\n baseUrl: string;\n endpoints: ParsedEndpoint[];\n auth: ParsedAuth;\n rawSchemas: Record<string, SchemaObject>;\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nconst HTTP_METHODS: HttpMethod[] = [\"get\", \"post\", \"put\", \"patch\", \"delete\", \"head\", \"options\"];\n\nfunction toCamelCase(str: string): string {\n return str\n .replace(/[-_\\s]+(.)/g, (_, c: string) => c.toUpperCase())\n .replace(/^(.)/, (c: string) => c.toLowerCase());\n}\n\nfunction toSafeIdentifier(str: string): string {\n // Replace non-alphanumeric chars with underscores, then camelCase\n const cleaned = str.replace(/[^a-zA-Z0-9_]/g, \"_\").replace(/_+/g, \"_\");\n return toCamelCase(cleaned);\n}\n\nfunction deriveToolName(method: HttpMethod, urlPath: string, operationId?: string): string {\n if (operationId && operationId.trim()) {\n return toSafeIdentifier(operationId.trim());\n }\n // Generate from method + path segments\n const segments = urlPath\n .split(\"/\")\n .filter(Boolean)\n .map((s) => s.replace(/\\{([^}]+)\\}/, \"By_$1\"))\n .map((s) => s.replace(/[^a-zA-Z0-9]/g, \"_\"));\n const name = [method, ...segments].join(\"_\");\n return toSafeIdentifier(name);\n}\n\nfunction resolveRef(spec: OpenAPISpec, ref: string): SchemaObject {\n // Only supports local $ref: \"#/components/schemas/Foo\"\n const parts = ref.replace(/^#\\//, \"\").split(\"/\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let node: any = spec;\n for (const part of parts) {\n node = node?.[part];\n }\n return (node as SchemaObject) ?? { type: \"object\" };\n}\n\nfunction resolveSchema(spec: OpenAPISpec, schema?: SchemaObject): SchemaObject {\n if (!schema) return { type: \"object\" };\n if (schema[\"$ref\"]) return resolveRef(spec, schema[\"$ref\"]);\n return schema;\n}\n\nfunction parseParam(spec: OpenAPISpec, raw: ParameterObject): ParsedParam {\n const schema = resolveSchema(spec, raw.schema ?? { type: \"string\" });\n return {\n name: raw.name,\n camelName: toCamelCase(raw.name),\n in: raw.in as ParsedParam[\"in\"],\n description: raw.description ?? \"\",\n required: raw.required ?? false,\n schema,\n };\n}\n\nfunction detectAuth(spec: OpenAPISpec): ParsedAuth {\n const schemes = spec.components?.securitySchemes ?? {};\n\n for (const [, scheme] of Object.entries(schemes)) {\n if (scheme.type === \"http\" && scheme.scheme === \"bearer\") {\n return {\n type: \"bearer\",\n paramName: \"Authorization\",\n paramIn: \"header\",\n envVar: \"API_BEARER_TOKEN\",\n description: scheme.description ?? \"Bearer token authentication\",\n };\n }\n if (scheme.type === \"http\" && scheme.scheme === \"basic\") {\n return {\n type: \"basic\",\n paramName: \"Authorization\",\n paramIn: \"header\",\n envVar: \"API_BASIC_CREDENTIALS\",\n description: scheme.description ?? \"Basic authentication (base64 user:pass)\",\n };\n }\n if (scheme.type === \"apiKey\") {\n const paramIn = (scheme.in ?? \"header\") as \"header\" | \"query\" | \"cookie\";\n const paramName = scheme.name ?? \"X-API-Key\";\n return {\n type: \"apiKey\",\n paramName,\n paramIn,\n envVar: \"API_KEY\",\n description: scheme.description ?? `API key sent in ${paramIn} as \"${paramName}\"`,\n };\n }\n }\n\n return {\n type: \"none\",\n paramName: \"\",\n paramIn: \"header\",\n envVar: \"\",\n description: \"No authentication\",\n };\n}\n\nfunction parseEndpoint(\n spec: OpenAPISpec,\n urlPath: string,\n method: HttpMethod,\n op: OperationObject,\n pathLevelParams: ParameterObject[]\n): ParsedEndpoint {\n // Merge path-level params with operation-level (operation overrides)\n const rawParams: ParameterObject[] = [...pathLevelParams];\n for (const p of op.parameters ?? []) {\n const resolved = p[\"$ref\"]\n ? (resolveRef(spec, p[\"$ref\"]) as ParameterObject)\n : p;\n const existing = rawParams.findIndex(\n (x) => x.name === resolved.name && x.in === resolved.in\n );\n if (existing >= 0) rawParams[existing] = resolved;\n else rawParams.push(resolved);\n }\n\n const parsedParams = rawParams.map((p) => parseParam(spec, p));\n const pathParams = parsedParams.filter((p) => p.in === \"path\");\n const queryParams = parsedParams.filter((p) => p.in === \"query\");\n const headerParams = parsedParams.filter((p) => p.in === \"header\");\n\n // Request body\n let hasBody = false;\n let bodyRequired = false;\n let bodyContentType = \"application/json\";\n let bodySchema: SchemaObject | null = null;\n\n if (op.requestBody) {\n const rb = op.requestBody;\n hasBody = true;\n bodyRequired = rb.required ?? false;\n const contentTypes = Object.keys(rb.content ?? {});\n // Prefer JSON, fall back to first available\n bodyContentType =\n contentTypes.find((ct) => ct.includes(\"json\")) ??\n contentTypes[0] ??\n \"application/json\";\n const mediaType = rb.content?.[bodyContentType];\n if (mediaType?.schema) {\n bodySchema = resolveSchema(spec, mediaType.schema);\n }\n }\n\n // Required list for MCP inputSchema\n const allRequired: string[] = [\n ...pathParams.filter((p) => p.required).map((p) => p.name),\n ...queryParams.filter((p) => p.required).map((p) => p.name),\n ...headerParams.filter((p) => p.required).map((p) => p.name),\n ];\n if (hasBody && bodyRequired) allRequired.push(\"body\");\n\n const operationId = op.operationId ?? \"\";\n const toolName = deriveToolName(method, urlPath, operationId);\n\n return {\n toolName,\n operationId,\n method,\n path: urlPath,\n summary: op.summary ?? \"\",\n description: op.description ?? op.summary ?? \"\",\n tags: op.tags ?? [],\n deprecated: op.deprecated ?? false,\n pathParams,\n queryParams,\n headerParams,\n hasBody,\n bodyRequired,\n bodyContentType,\n bodySchema,\n allRequired,\n };\n}\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\nexport async function loadSpec(input: string): Promise<OpenAPISpec> {\n let raw: string;\n\n if (input.startsWith(\"http://\") || input.startsWith(\"https://\")) {\n const res = await fetch(input);\n if (!res.ok) {\n throw new Error(`Failed to fetch spec: ${res.status} ${res.statusText}`);\n }\n raw = await res.text();\n } else {\n const resolved = path.resolve(input);\n if (!fs.existsSync(resolved)) {\n throw new Error(`Spec file not found: ${resolved}`);\n }\n raw = await fs.readFile(resolved, \"utf-8\");\n }\n\n let spec: unknown;\n const trimmed = raw.trimStart();\n if (trimmed.startsWith(\"{\")) {\n spec = JSON.parse(raw);\n } else {\n spec = yaml.load(raw);\n }\n\n if (!spec || typeof spec !== \"object\") {\n throw new Error(\"Could not parse spec: result is not an object\");\n }\n\n const s = spec as OpenAPISpec;\n if (!s.openapi) {\n throw new Error(\"Not a valid OpenAPI 3.x spec (missing 'openapi' field)\");\n }\n if (!s.openapi.startsWith(\"3.\")) {\n throw new Error(`Unsupported OpenAPI version: ${s.openapi} (only 3.x is supported)`);\n }\n\n return s;\n}\n\nexport function parseSpec(spec: OpenAPISpec): ParsedSpec {\n const endpoints: ParsedEndpoint[] = [];\n\n for (const [urlPath, pathItem] of Object.entries(spec.paths ?? {})) {\n const pathLevelParams = (pathItem.parameters ?? []).map((p) =>\n p[\"$ref\"] ? (resolveRef(spec, p[\"$ref\"]) as ParameterObject) : p\n );\n\n for (const method of HTTP_METHODS) {\n const op = pathItem[method];\n if (!op) continue;\n const endpoint = parseEndpoint(spec, urlPath, method, op, pathLevelParams);\n endpoints.push(endpoint);\n }\n }\n\n // Ensure unique tool names — suffix duplicates with _2, _3, …\n const seen = new Map<string, number>();\n for (const ep of endpoints) {\n const count = (seen.get(ep.toolName) ?? 0) + 1;\n seen.set(ep.toolName, count);\n if (count > 1) ep.toolName = `${ep.toolName}_${count}`;\n }\n\n const baseUrl =\n spec.servers?.[0]?.url?.replace(/\\/$/, \"\") ?? \"https://api.example.com\";\n\n return {\n title: spec.info.title,\n version: spec.info.version,\n description: spec.info.description ?? \"\",\n baseUrl,\n endpoints,\n auth: detectAuth(spec),\n rawSchemas: spec.components?.schemas ?? {},\n };\n}\n","import path from \"node:path\";\nimport fs from \"fs-extra\";\nimport { renderTemplate } from \"./templates.js\";\nimport type { ParsedSpec, ParsedEndpoint, SchemaObject, ParsedParam } from \"./parser.js\";\n\n// ─── JSON Schema builder from OpenAPI schema ──────────────────────────────────\n\nfunction buildJsonSchema(schema: SchemaObject | null): Record<string, unknown> {\n if (!schema) return { type: \"object\" };\n\n const result: Record<string, unknown> = {};\n\n if (schema.type) result.type = schema.type;\n if (schema.description) result.description = schema.description;\n if (schema.enum) result.enum = schema.enum;\n if (schema.default !== undefined) result.default = schema.default;\n if (schema.format) result.format = schema.format;\n if (schema.minimum !== undefined) result.minimum = schema.minimum;\n if (schema.maximum !== undefined) result.maximum = schema.maximum;\n if (schema.minLength !== undefined) result.minLength = schema.minLength;\n if (schema.maxLength !== undefined) result.maxLength = schema.maxLength;\n if (schema.pattern) result.pattern = schema.pattern;\n\n if (schema.properties) {\n result.type = result.type ?? \"object\";\n result.properties = Object.fromEntries(\n Object.entries(schema.properties).map(([k, v]) => [k, buildJsonSchema(v)])\n );\n if (schema.required) result.required = schema.required;\n }\n\n if (schema.items) {\n result.type = result.type ?? \"array\";\n result.items = buildJsonSchema(schema.items);\n }\n\n if (schema.allOf) {\n result.allOf = schema.allOf.map(buildJsonSchema);\n }\n if (schema.oneOf) {\n result.oneOf = schema.oneOf.map(buildJsonSchema);\n }\n if (schema.anyOf) {\n result.anyOf = schema.anyOf.map(buildJsonSchema);\n }\n\n if (result.type === undefined && !schema.properties && !schema.items) {\n result.type = \"string\";\n }\n\n return result;\n}\n\nfunction paramToJsonSchemaProp(param: ParsedParam): Record<string, unknown> {\n const base = buildJsonSchema(param.schema);\n if (param.description && !base.description) {\n base.description = param.description;\n }\n return base;\n}\n\n// ─── Build the full inputSchema for a tool ────────────────────────────────────\n\ninterface ToolInputSchema {\n type: \"object\";\n properties: Record<string, unknown>;\n required: string[];\n}\n\nfunction buildInputSchema(ep: ParsedEndpoint): ToolInputSchema {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n // Path params\n for (const p of ep.pathParams) {\n properties[p.name] = {\n ...paramToJsonSchemaProp(p),\n description:\n (p.description ? p.description + \" \" : \"\") + `(path parameter)`,\n };\n if (p.required) required.push(p.name);\n }\n\n // Query params\n for (const p of ep.queryParams) {\n properties[p.name] = {\n ...paramToJsonSchemaProp(p),\n description:\n (p.description ? p.description + \" \" : \"\") + `(query parameter)`,\n };\n if (p.required) required.push(p.name);\n }\n\n // Header params (non-auth)\n for (const p of ep.headerParams) {\n properties[p.name] = {\n ...paramToJsonSchemaProp(p),\n description:\n (p.description ? p.description + \" \" : \"\") + `(header parameter)`,\n };\n if (p.required) required.push(p.name);\n }\n\n // Request body\n if (ep.hasBody) {\n if (ep.bodySchema?.properties) {\n // Flatten top-level body properties into the tool input\n for (const [key, propSchema] of Object.entries(ep.bodySchema.properties)) {\n properties[key] = buildJsonSchema(propSchema);\n if (ep.bodySchema.required?.includes(key)) required.push(key);\n }\n } else {\n // Treat body as opaque \"body\" parameter\n properties[\"body\"] = ep.bodySchema\n ? buildJsonSchema(ep.bodySchema)\n : { type: \"object\", description: \"Request body\" };\n if (ep.bodyRequired) required.push(\"body\");\n }\n }\n\n return { type: \"object\", properties, required };\n}\n\n// ─── Template data shapes ─────────────────────────────────────────────────────\n\ninterface PathReplacement {\n /** The literal OpenAPI placeholder, e.g. \"{petId}\" */\n placeholder: string;\n /** The JS arg name, e.g. \"petId\" */\n paramName: string;\n}\n\ninterface ToolData {\n toolName: string;\n description: string;\n inputSchemaJson: string;\n method: string;\n urlPath: string;\n pathParams: string[]; // param names\n pathReplacements: PathReplacement[];\n queryParams: string[]; // param names\n headerParams: string[]; // param names\n hasBody: boolean;\n bodyHasTopLevelProps: boolean;\n bodyProps: string[]; // top-level body prop names (when flattened)\n bodyContentType: string;\n deprecated: boolean;\n}\n\nfunction buildToolData(ep: ParsedEndpoint): ToolData {\n const inputSchema = buildInputSchema(ep);\n const bodyHasTopLevelProps = Boolean(ep.bodySchema?.properties);\n const bodyProps = bodyHasTopLevelProps\n ? Object.keys(ep.bodySchema!.properties!)\n : [];\n\n return {\n toolName: ep.toolName,\n description: [ep.summary, ep.description]\n .filter(Boolean)\n .filter((v, i, a) => a.indexOf(v) === i) // deduplicate\n .join(\" — \")\n .slice(0, 1024) || ep.path,\n inputSchemaJson: JSON.stringify(inputSchema, null, 4),\n method: ep.method.toUpperCase(),\n urlPath: ep.path,\n pathParams: ep.pathParams.map((p) => p.name),\n pathReplacements: ep.pathParams.map((p) => ({\n placeholder: `{${p.name}}`,\n paramName: p.name,\n })),\n queryParams: ep.queryParams.map((p) => p.name),\n headerParams: ep.headerParams.map((p) => p.name),\n hasBody: ep.hasBody,\n bodyHasTopLevelProps,\n bodyProps,\n bodyContentType: ep.bodyContentType,\n deprecated: ep.deprecated,\n };\n}\n\n// ─── Main generator ───────────────────────────────────────────────────────────\n\nexport interface GenerateOptions {\n outputDir: string;\n}\n\nexport async function generate(\n spec: ParsedSpec,\n options: GenerateOptions\n): Promise<void> {\n const { outputDir } = options;\n\n await fs.ensureDir(outputDir);\n await fs.ensureDir(path.join(outputDir, \"src\"));\n\n const tools = spec.endpoints.map(buildToolData);\n\n const serverName = spec.title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\") || \"openapi-mcp\";\n\n const templateData = {\n serverName,\n serverTitle: spec.title,\n serverVersion: spec.info ? spec.version : \"1.0.0\",\n serverDescription: spec.description || spec.title,\n baseUrl: spec.baseUrl,\n tools,\n toolCount: tools.length,\n auth: spec.auth,\n hasAuth: spec.auth.type !== \"none\",\n isBearer: spec.auth.type === \"bearer\",\n isApiKey: spec.auth.type === \"apiKey\",\n isBasic: spec.auth.type === \"basic\",\n apiKeyInHeader: spec.auth.paramIn === \"header\",\n apiKeyInQuery: spec.auth.paramIn === \"query\",\n };\n\n // Generate src/server.ts\n const serverCode = renderTemplate(\"server.ts.hbs\", templateData);\n await fs.writeFile(path.join(outputDir, \"src\", \"server.ts\"), serverCode, \"utf-8\");\n\n // Generate package.json\n const pkgJson = renderTemplate(\"package.json.hbs\", templateData);\n await fs.writeFile(path.join(outputDir, \"package.json\"), pkgJson, \"utf-8\");\n\n // Generate tsconfig.json\n const tsconfigContent = JSON.stringify(\n {\n compilerOptions: {\n target: \"ES2022\",\n module: \"ESNext\",\n moduleResolution: \"bundler\",\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n outDir: \"dist\",\n rootDir: \"src\",\n resolveJsonModule: true,\n },\n include: [\"src\"],\n exclude: [\"node_modules\", \"dist\"],\n },\n null,\n 2\n );\n await fs.writeFile(\n path.join(outputDir, \"tsconfig.json\"),\n tsconfigContent,\n \"utf-8\"\n );\n\n // Generate .env.example\n const envLines: string[] = [\"# Generated by openapi-to-mcp\", \"\"];\n if (spec.auth.type !== \"none\") {\n envLines.push(`# ${spec.auth.description}`);\n envLines.push(`${spec.auth.envVar}=`);\n envLines.push(\"\");\n }\n envLines.push(\"# Override the base URL if needed\");\n envLines.push(`BASE_URL=${spec.baseUrl}`);\n envLines.push(\"\");\n await fs.writeFile(\n path.join(outputDir, \".env.example\"),\n envLines.join(\"\\n\"),\n \"utf-8\"\n );\n\n // Generate README.md for the generated server\n const readme = buildGeneratedReadme(spec, serverName, tools.length);\n await fs.writeFile(path.join(outputDir, \"README.md\"), readme, \"utf-8\");\n}\n\nfunction buildGeneratedReadme(\n spec: ParsedSpec,\n serverName: string,\n toolCount: number\n): string {\n const authSection =\n spec.auth.type === \"none\"\n ? \"\"\n : `## Authentication\n\nSet the \\`${spec.auth.envVar}\\` environment variable:\n\n\\`\\`\\`bash\nexport ${spec.auth.envVar}=your_token_here\n\\`\\`\\`\n\n${spec.auth.description}\n`;\n\n return `# ${spec.title} MCP Server\n\nGenerated by [openapi-to-mcp](https://github.com/example/openapi-to-mcp).\n\n**${spec.description || spec.title}**\n\n- **Base URL:** \\`${spec.baseUrl}\\`\n- **Tools:** ${toolCount}\n\n## Quick Start\n\n\\`\\`\\`bash\nnpm install\nnpm run build\nnode dist/server.js\n\\`\\`\\`\n\n${authSection}\n## Usage with Claude Desktop\n\nAdd to your \\`claude_desktop_config.json\\`:\n\n\\`\\`\\`json\n{\n \"mcpServers\": {\n \"${serverName}\": {\n \"command\": \"node\",\n \"args\": [\"/absolute/path/to/${serverName}/dist/server.js\"]${spec.auth.type !== \"none\" ? `,\n \"env\": {\n \"${spec.auth.envVar}\": \"your_token_here\"\n }` : \"\"}\n }\n }\n}\n\\`\\`\\`\n\n## Available Tools (${toolCount})\n\n| Tool | Method | Path | Description |\n|------|--------|------|-------------|\n${spec.endpoints\n .map(\n (ep) =>\n `| \\`${ep.toolName}\\` | \\`${ep.method.toUpperCase()}\\` | \\`${ep.path}\\` | ${ep.summary || ep.description || \"\"} |`\n )\n .join(\"\\n\")}\n`;\n}\n","import Handlebars from \"handlebars\";\nimport fs from \"fs-extra\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nHandlebars.registerHelper(\"eq\", (a: unknown, b: unknown) => a === b);\nHandlebars.registerHelper(\"neq\", (a: unknown, b: unknown) => a !== b);\nHandlebars.registerHelper(\"and\", (a: unknown, b: unknown) => Boolean(a) && Boolean(b));\nHandlebars.registerHelper(\"or\", (a: unknown, b: unknown) => Boolean(a) || Boolean(b));\nHandlebars.registerHelper(\"not\", (a: unknown) => !a);\nHandlebars.registerHelper(\"gt\", (a: number, b: number) => a > b);\n\n/** JSON-stringify a value for embedding in a template */\nHandlebars.registerHelper(\"json\", (value: unknown) =>\n JSON.stringify(value, null, 2)\n);\n\n/** JSON-stringify inline (no pretty-print) */\nHandlebars.registerHelper(\"jsonInline\", (value: unknown) =>\n JSON.stringify(value)\n);\n\n/** Upper-case first letter */\nHandlebars.registerHelper(\"ucFirst\", (s: string) =>\n s ? s.charAt(0).toUpperCase() + s.slice(1) : \"\"\n);\n\n// ─── Template resolution ──────────────────────────────────────────────────────\n\nfunction getTemplatesRoot(): string {\n const candidates = [\n path.resolve(__dirname, \"..\", \"templates\"),\n path.resolve(__dirname, \"..\", \"..\", \"templates\"),\n ];\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) return candidate;\n }\n throw new Error(\n \"Templates directory not found. Searched: \" + candidates.join(\", \")\n );\n}\n\nexport function renderTemplate(\n templateFile: string,\n data: Record<string, unknown>\n): string {\n const templatesRoot = getTemplatesRoot();\n const templatePath = path.join(templatesRoot, templateFile);\n\n if (!fs.existsSync(templatePath)) {\n throw new Error(`Template file not found: ${templatePath}`);\n }\n\n const raw = fs.readFileSync(templatePath, \"utf-8\");\n const compiled = Handlebars.compile(raw, { noEscape: true });\n return compiled(data);\n}\n"],"mappings":"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","names":["path","path","fs","fs","path","fs","path","path"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openapi-mcp-gen",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Generate a fully working MCP server from an OpenAPI 3.x spec",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openapi-to-mcp": "./dist/index.js",
|
|
8
|
+
"mcp-from-openapi": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"templates"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"lint": "tsc --noEmit",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"mcp",
|
|
25
|
+
"model-context-protocol",
|
|
26
|
+
"openapi",
|
|
27
|
+
"swagger",
|
|
28
|
+
"codegen",
|
|
29
|
+
"cli",
|
|
30
|
+
"generator",
|
|
31
|
+
"ai",
|
|
32
|
+
"llm"
|
|
33
|
+
],
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"chalk": "^5.3.0",
|
|
38
|
+
"commander": "^12.1.0",
|
|
39
|
+
"fs-extra": "^11.2.0",
|
|
40
|
+
"handlebars": "^4.7.8",
|
|
41
|
+
"js-yaml": "^4.1.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/fs-extra": "^11.0.4",
|
|
45
|
+
"@types/js-yaml": "^4.0.9",
|
|
46
|
+
"@types/node": "^20.14.0",
|
|
47
|
+
"tsup": "^8.1.0",
|
|
48
|
+
"typescript": "^5.5.0",
|
|
49
|
+
"vitest": "^1.6.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{{serverName}}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for {{{serverTitle}}} — generated by openapi-to-mcp",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/server.js",
|
|
10
|
+
"dev": "node --watch dist/server.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
14
|
+
"zod": "^3.23.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^20.14.0",
|
|
18
|
+
"typescript": "^5.5.0"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"{{{serverName}}}"
|
|
24
|
+
],
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
// ─── Configuration ────────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
const BASE_URL = process.env.BASE_URL?.replace(/\/$/, "") ?? "{{{baseUrl}}}";
|
|
8
|
+
{{#if hasAuth}}
|
|
9
|
+
|
|
10
|
+
{{#if isBearer}}
|
|
11
|
+
const BEARER_TOKEN = process.env.{{auth.envVar}};
|
|
12
|
+
if (!BEARER_TOKEN) {
|
|
13
|
+
console.error("Error: {{auth.envVar}} environment variable is required.");
|
|
14
|
+
console.error("Set it to your Bearer token before starting the server.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
{{/if}}
|
|
18
|
+
{{#if isApiKey}}
|
|
19
|
+
const API_KEY = process.env.{{auth.envVar}};
|
|
20
|
+
if (!API_KEY) {
|
|
21
|
+
console.error("Error: {{auth.envVar}} environment variable is required.");
|
|
22
|
+
console.error("Set it to your API key before starting the server.");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
{{/if}}
|
|
26
|
+
{{#if isBasic}}
|
|
27
|
+
const BASIC_CREDENTIALS = process.env.{{auth.envVar}};
|
|
28
|
+
if (!BASIC_CREDENTIALS) {
|
|
29
|
+
console.error("Error: {{auth.envVar}} environment variable is required.");
|
|
30
|
+
console.error("Set it to base64-encoded 'username:password' before starting the server.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
{{/if}}
|
|
34
|
+
{{/if}}
|
|
35
|
+
|
|
36
|
+
// ─── HTTP client helpers ──────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function buildAuthHeaders(): Record<string, string> {
|
|
39
|
+
{{#if isBearer}}
|
|
40
|
+
return { Authorization: `Bearer ${BEARER_TOKEN}` };
|
|
41
|
+
{{else if isBasic}}
|
|
42
|
+
return { Authorization: `Basic ${BASIC_CREDENTIALS}` };
|
|
43
|
+
{{else}}
|
|
44
|
+
return {};
|
|
45
|
+
{{/if}}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
{{#if isApiKey}}
|
|
49
|
+
{{#if apiKeyInQuery}}
|
|
50
|
+
function buildAuthQuery(): Record<string, string> {
|
|
51
|
+
return { {{{auth.paramName}}}: API_KEY! };
|
|
52
|
+
}
|
|
53
|
+
{{/if}}
|
|
54
|
+
{{/if}}
|
|
55
|
+
|
|
56
|
+
interface FetchOptions {
|
|
57
|
+
method: string;
|
|
58
|
+
headers?: Record<string, string>;
|
|
59
|
+
body?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function apiFetch(
|
|
63
|
+
urlPath: string,
|
|
64
|
+
options: FetchOptions
|
|
65
|
+
): Promise<{ ok: boolean; status: number; data: unknown }> {
|
|
66
|
+
const headers: Record<string, string> = {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
Accept: "application/json",
|
|
69
|
+
...buildAuthHeaders(),
|
|
70
|
+
...(options.headers ?? {}),
|
|
71
|
+
};
|
|
72
|
+
{{#if isApiKey}}
|
|
73
|
+
{{#if apiKeyInHeader}}
|
|
74
|
+
headers["{{{auth.paramName}}}"] = API_KEY!;
|
|
75
|
+
{{/if}}
|
|
76
|
+
{{/if}}
|
|
77
|
+
|
|
78
|
+
const url = new URL(BASE_URL + urlPath);
|
|
79
|
+
{{#if isApiKey}}
|
|
80
|
+
{{#if apiKeyInQuery}}
|
|
81
|
+
const authQuery = buildAuthQuery();
|
|
82
|
+
for (const [k, v] of Object.entries(authQuery)) url.searchParams.set(k, v);
|
|
83
|
+
{{/if}}
|
|
84
|
+
{{/if}}
|
|
85
|
+
|
|
86
|
+
const res = await fetch(url.toString(), {
|
|
87
|
+
method: options.method,
|
|
88
|
+
headers,
|
|
89
|
+
body: options.body,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
let data: unknown;
|
|
93
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
94
|
+
if (contentType.includes("application/json")) {
|
|
95
|
+
data = await res.json();
|
|
96
|
+
} else {
|
|
97
|
+
data = await res.text();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { ok: res.ok, status: res.status, data };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ─── Server setup ─────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
const server = new McpServer({
|
|
106
|
+
name: "{{{serverName}}}",
|
|
107
|
+
version: "{{{serverVersion}}}",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// ─── Tools ────────────────────────────────────────────────────────────────────
|
|
111
|
+
{{#each tools}}
|
|
112
|
+
|
|
113
|
+
{{#if deprecated}}
|
|
114
|
+
// ⚠ DEPRECATED
|
|
115
|
+
{{/if}}
|
|
116
|
+
// {{method}} {{urlPath}}
|
|
117
|
+
server.tool(
|
|
118
|
+
"{{{toolName}}}",
|
|
119
|
+
"{{{description}}}",
|
|
120
|
+
{{{inputSchemaJson}}},
|
|
121
|
+
async (args) => {
|
|
122
|
+
// Build URL with path parameters
|
|
123
|
+
let urlPath = "{{{urlPath}}}";
|
|
124
|
+
{{#each pathParams}}
|
|
125
|
+
urlPath = urlPath.replace("{{{{}}}}{{{this}}}{{{{}}}}", encodeURIComponent(String(args.{{{this}}})));
|
|
126
|
+
{{/each}}
|
|
127
|
+
|
|
128
|
+
// Build query string
|
|
129
|
+
const query = new URLSearchParams();
|
|
130
|
+
{{#each queryParams}}
|
|
131
|
+
if (args.{{{this}}} !== undefined && args.{{{this}}} !== null) {
|
|
132
|
+
query.set("{{{this}}}", String(args.{{{this}}}));
|
|
133
|
+
}
|
|
134
|
+
{{/each}}
|
|
135
|
+
const queryString = query.toString();
|
|
136
|
+
if (queryString) urlPath += "?" + queryString;
|
|
137
|
+
|
|
138
|
+
// Build extra headers
|
|
139
|
+
const extraHeaders: Record<string, string> = {};
|
|
140
|
+
{{#each headerParams}}
|
|
141
|
+
if (args.{{{this}}} !== undefined) {
|
|
142
|
+
extraHeaders["{{{this}}}"] = String(args.{{{this}}});
|
|
143
|
+
}
|
|
144
|
+
{{/each}}
|
|
145
|
+
|
|
146
|
+
{{#if hasBody}}
|
|
147
|
+
// Build request body
|
|
148
|
+
{{#if bodyHasTopLevelProps}}
|
|
149
|
+
const bodyObj: Record<string, unknown> = {};
|
|
150
|
+
{{#each bodyProps}}
|
|
151
|
+
if (args.{{{this}}} !== undefined) bodyObj["{{{this}}}"] = args.{{{this}}};
|
|
152
|
+
{{/each}}
|
|
153
|
+
const bodyStr = JSON.stringify(bodyObj);
|
|
154
|
+
{{else}}
|
|
155
|
+
const bodyStr = args.body !== undefined ? JSON.stringify(args.body) : undefined;
|
|
156
|
+
{{/if}}
|
|
157
|
+
{{/if}}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const result = await apiFetch(urlPath, {
|
|
161
|
+
method: "{{{method}}}",
|
|
162
|
+
headers: Object.keys(extraHeaders).length > 0 ? extraHeaders : undefined,
|
|
163
|
+
{{#if hasBody}}
|
|
164
|
+
body: bodyStr,
|
|
165
|
+
{{/if}}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (!result.ok) {
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: `API error ${result.status}: ${JSON.stringify(result.data, null, 2)}`,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
isError: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: "text",
|
|
184
|
+
text: typeof result.data === "string"
|
|
185
|
+
? result.data
|
|
186
|
+
: JSON.stringify(result.data, null, 2),
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
} catch (err) {
|
|
191
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: "text", text: `Request failed: ${message}` }],
|
|
194
|
+
isError: true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
{{/each}}
|
|
200
|
+
|
|
201
|
+
// ─── Start ────────────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
async function main() {
|
|
204
|
+
const transport = new StdioServerTransport();
|
|
205
|
+
await server.connect(transport);
|
|
206
|
+
console.error("{{{serverTitle}}} MCP server running on stdio");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
main().catch((err) => {
|
|
210
|
+
console.error("Fatal error:", err);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
});
|