@christopher_dondici/mcp-gen 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +217 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/README.pt-BR.md +320 -0
  5. package/RELEASE_NOTES.md +98 -0
  6. package/SECURITY.md +75 -0
  7. package/SECURITY.pt-BR.md +77 -0
  8. package/dist/cli/index.d.ts +3 -0
  9. package/dist/cli/index.d.ts.map +1 -0
  10. package/dist/cli/index.js +685 -0
  11. package/dist/cli/index.js.map +1 -0
  12. package/dist/core/generator.d.ts +4 -0
  13. package/dist/core/generator.d.ts.map +1 -0
  14. package/dist/core/generator.js +275 -0
  15. package/dist/core/generator.js.map +1 -0
  16. package/dist/core/incremental.d.ts +25 -0
  17. package/dist/core/incremental.d.ts.map +1 -0
  18. package/dist/core/incremental.js +91 -0
  19. package/dist/core/incremental.js.map +1 -0
  20. package/dist/core/parser.d.ts +3 -0
  21. package/dist/core/parser.d.ts.map +1 -0
  22. package/dist/core/parser.js +372 -0
  23. package/dist/core/parser.js.map +1 -0
  24. package/dist/core/registry.d.ts +13 -0
  25. package/dist/core/registry.d.ts.map +1 -0
  26. package/dist/core/registry.js +107 -0
  27. package/dist/core/registry.js.map +1 -0
  28. package/dist/core/security-lint.d.ts +53 -0
  29. package/dist/core/security-lint.d.ts.map +1 -0
  30. package/dist/core/security-lint.js +470 -0
  31. package/dist/core/security-lint.js.map +1 -0
  32. package/dist/core/security.d.ts +41 -0
  33. package/dist/core/security.d.ts.map +1 -0
  34. package/dist/core/security.js +150 -0
  35. package/dist/core/security.js.map +1 -0
  36. package/dist/core/templating.d.ts +5 -0
  37. package/dist/core/templating.d.ts.map +1 -0
  38. package/dist/core/templating.js +211 -0
  39. package/dist/core/templating.js.map +1 -0
  40. package/dist/core/types.d.ts +104 -0
  41. package/dist/core/types.d.ts.map +1 -0
  42. package/dist/core/types.js +3 -0
  43. package/dist/core/types.js.map +1 -0
  44. package/dist/index.d.ts +7 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +20 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/templates/go/Dockerfile.hbs +16 -0
  49. package/dist/templates/go/README.md.hbs +77 -0
  50. package/dist/templates/go/ci.yml.hbs +28 -0
  51. package/dist/templates/go/client.go.hbs +86 -0
  52. package/dist/templates/go/go.mod.hbs +5 -0
  53. package/dist/templates/go/models.go.hbs +28 -0
  54. package/dist/templates/go/server.go.hbs +220 -0
  55. package/dist/templates/python/Dockerfile.hbs +12 -0
  56. package/dist/templates/python/README.md.hbs +115 -0
  57. package/dist/templates/python/ci.yml.hbs +24 -0
  58. package/dist/templates/python/models.py.hbs +20 -0
  59. package/dist/templates/python/requirements.txt.hbs +3 -0
  60. package/dist/templates/python/server.py.hbs +195 -0
  61. package/dist/templates/typescript/Dockerfile.hbs +18 -0
  62. package/dist/templates/typescript/README.md.hbs +91 -0
  63. package/dist/templates/typescript/ci.yml.hbs +28 -0
  64. package/dist/templates/typescript/client.hbs +49 -0
  65. package/dist/templates/typescript/models.hbs +23 -0
  66. package/dist/templates/typescript/package.json.hbs +26 -0
  67. package/dist/templates/typescript/server.hbs +337 -0
  68. package/dist/templates/typescript/tsconfig.json.hbs +17 -0
  69. package/examples/petstore.json +130 -0
  70. package/examples/petstore.yaml +131 -0
  71. package/examples/week3.json +47 -0
  72. package/package.json +64 -0
@@ -0,0 +1,195 @@
1
+ """
2
+ Auto-generated by mcp-gen v{{generatorVersion}}
3
+ Spec: {{info.title}} v{{info.version}}
4
+ Generated: {{generatedAt}}
5
+ {{#if incremental}}
6
+ Incremental mode: edit code between @@mcp-gen markers — it will be preserved on re-generation.
7
+ {{/if}}
8
+ Base URL: {{baseUrl}}
9
+ """
10
+
11
+ from mcp.server.fastmcp import FastMCP
12
+ from models import {{#each models}}{{name}}{{#unless @last}}, {{/unless}}{{/each}}
13
+ from typing import Optional, Any, Dict
14
+ import httpx
15
+ from datetime import datetime, timezone
16
+
17
+ mcp = FastMCP("{{serverName}}")
18
+
19
+ BASE_URL = "{{baseUrl}}"
20
+
21
+ _http_headers: Dict[str, str] = {}
22
+ {{#if requiresAuth}}
23
+ import os
24
+ _token = os.environ.get("TOKEN")
25
+ if _token:
26
+ _http_headers["Authorization"] = f"Bearer {_token}"
27
+ {{/if}}
28
+
29
+ RAW_CREDENTIAL_KEYS = {
30
+ "authorization",
31
+ "token",
32
+ "access_token",
33
+ "api_key",
34
+ "apikey",
35
+ "x_api_key",
36
+ "client_secret",
37
+ "refresh_token",
38
+ "password",
39
+ "secret",
40
+ }
41
+
42
+ TOOL_POLICIES = {
43
+ {{#each tools}}
44
+ "{{name}}": {
45
+ "endpoint": "{{method}} {{path}}",
46
+ "requires_ttl": True,
47
+ "requires_spend_limit": True,
48
+ "requires_request_log": True,
49
+ "requires_revocation_check": True,
50
+ },
51
+ {{/each}}
52
+ }
53
+
54
+
55
+ def _normalize_key(key: str) -> str:
56
+ return "".join(ch for ch in key.lower() if ch.isalnum() or ch == "_")
57
+
58
+
59
+ def _contains_raw_credentials(value: Any) -> bool:
60
+ if isinstance(value, dict):
61
+ for key, nested in value.items():
62
+ normalized = _normalize_key(str(key)).replace("-", "_")
63
+ for candidate in RAW_CREDENTIAL_KEYS:
64
+ if normalized == _normalize_key(candidate):
65
+ return True
66
+ if _contains_raw_credentials(nested):
67
+ return True
68
+ elif isinstance(value, list):
69
+ return any(_contains_raw_credentials(item) for item in value)
70
+ return False
71
+
72
+
73
+ def ensure_no_raw_credentials(payload: Optional[dict]) -> None:
74
+ if payload and _contains_raw_credentials(payload):
75
+ raise ValueError(
76
+ "Raw provider credentials are not allowed in tool arguments. "
77
+ "Use scoped auth_context metadata only."
78
+ )
79
+
80
+
81
+ def _is_expired(expires_at: Optional[str]) -> bool:
82
+ if not expires_at:
83
+ return True
84
+ try:
85
+ parsed = datetime.fromisoformat(expires_at.replace("Z", "+00:00"))
86
+ except ValueError:
87
+ return True
88
+ if parsed.tzinfo is None:
89
+ parsed = parsed.replace(tzinfo=timezone.utc)
90
+ return parsed <= datetime.now(timezone.utc)
91
+
92
+
93
+ def require_security(tool_name: str, security: Any, auth_context: Optional[dict], request_args: Optional[dict]) -> None:
94
+ ensure_no_raw_credentials(request_args)
95
+ if auth_context is not None:
96
+ ensure_no_raw_credentials(auth_context)
97
+
98
+ policy = TOOL_POLICIES.get(tool_name)
99
+ if not policy:
100
+ raise ValueError(f"Missing security policy for tool: {tool_name}")
101
+
102
+ if not isinstance(auth_context, dict):
103
+ raise ValueError(
104
+ "Missing auth_context. Provide scoped metadata "
105
+ "(token_id, expires_at, limits, request_id) from your gateway."
106
+ )
107
+
108
+ if policy["requires_revocation_check"] and auth_context.get("revoked") is True:
109
+ raise PermissionError("Access revoked")
110
+
111
+ if policy["requires_ttl"] and _is_expired(auth_context.get("expires_at")):
112
+ raise PermissionError("Token expired or missing TTL")
113
+
114
+ if policy["requires_request_log"] and not auth_context.get("request_id"):
115
+ raise ValueError("request_id is required for audit logging")
116
+
117
+ if policy["requires_spend_limit"]:
118
+ limit = auth_context.get("spend_limit_usd")
119
+ used = auth_context.get("spend_used_usd")
120
+ if not isinstance(limit, (int, float)) or not isinstance(used, (int, float)):
121
+ raise ValueError("Spend limit metadata is required")
122
+ if used > limit:
123
+ raise PermissionError("Spend limit exceeded")
124
+
125
+ allowed_tools = auth_context.get("allowed_tools")
126
+ if isinstance(allowed_tools, list) and tool_name not in allowed_tools:
127
+ raise PermissionError(f"Tool not allowed by scope: {tool_name}")
128
+
129
+ endpoint_allowlist = auth_context.get("endpoint_allowlist")
130
+ endpoint = policy["endpoint"]
131
+ if isinstance(endpoint_allowlist, list) and endpoint not in endpoint_allowlist:
132
+ raise PermissionError(f"Endpoint not allowed: {endpoint}")
133
+
134
+ if security:
135
+ if not auth_context.get("token_id"):
136
+ raise PermissionError("Missing token_id for secured operation")
137
+
138
+
139
+ async def _call_api(method: str, path: str, params: dict, headers: Optional[dict] = None) -> Any:
140
+ """Real HTTP call to the underlying API (used in --http mode)."""
141
+ url = BASE_URL.rstrip("/") + _expand_path(path, params)
142
+ merged_headers = dict(_http_headers)
143
+ if headers:
144
+ merged_headers.update(headers)
145
+ body = params.get("body")
146
+ async with httpx.AsyncClient() as client:
147
+ r = await client.request(method, url, json=body, headers=merged_headers)
148
+ if r.status_code >= 400:
149
+ raise RuntimeError(f"HTTP {r.status_code}: {r.text}")
150
+ try:
151
+ return r.json()
152
+ except ValueError:
153
+ return r.text
154
+
155
+
156
+ def _expand_path(path: str, params: dict) -> str:
157
+ out = path
158
+ for k, v in params.items():
159
+ out = out.replace("{" + k + "}", str(v))
160
+ return out
161
+
162
+
163
+ # ─── Tools ───────────────────────────────────────────────────────────────────
164
+
165
+ {{#each tools}}
166
+ @mcp.tool()
167
+ async def {{name}}({{#each params}}{{name}}: {{#if required}}{{#eq type "string"}}str{{/eq}}{{#eq type "number"}}float{{/eq}}{{#eq type "boolean"}}bool{{/eq}}{{#eq type "object"}}dict{{/eq}}{{#eq type "array"}}list{{/eq}}{{else}}Optional[{{#eq type "string"}}str{{/eq}}{{#eq type "number"}}float{{/eq}}{{#eq type "boolean"}}bool{{/eq}}{{#eq type "object"}}dict{{/eq}}{{#eq type "array"}}list{{/eq}}] = None{{/if}}{{#unless @last}}, {{/unless}}{{/each}}{{#if params}}, {{/if}}auth_context: Optional[dict] = None) -> Any:
168
+ """{{escapeText description}}
169
+
170
+ {{method}} {{path}}
171
+ """
172
+ # @@mcp-gen:start:{{name}}
173
+ request_args = {
174
+ key: value
175
+ for key, value in locals().items()
176
+ if key not in {"auth_context", "request_args"}
177
+ }
178
+ require_security("{{name}}", {{#if security}}{{json security}}{{else}}None{{/if}}, auth_context, request_args)
179
+ {{#if ../http}}
180
+ return {{#if ../requiresAuth}}await _call_api("{{method}}", "{{path}}", request_args){{else}}await _call_api("{{method}}", "{{path}}", request_args){{/if}}
181
+ {{else}}
182
+ {{#if exampleResponse}}
183
+ return {{json exampleResponse}}
184
+ {{else}}
185
+ raise NotImplementedError("Handler not implemented: {{name}}")
186
+ {{/if}}
187
+ {{/if}}
188
+ # @@mcp-gen:end:{{name}}
189
+
190
+
191
+ {{/each}}
192
+ # ─── Start ───────────────────────────────────────────────────────────────────
193
+
194
+ if __name__ == "__main__":
195
+ mcp.run()
@@ -0,0 +1,18 @@
1
+ FROM node:20-alpine AS builder
2
+
3
+ WORKDIR /app
4
+ COPY package*.json ./
5
+ RUN npm ci --production=false
6
+ COPY . .
7
+ RUN npm run build
8
+
9
+ FROM node:20-alpine AS runtime
10
+
11
+ WORKDIR /app
12
+ COPY package*.json ./
13
+ RUN npm ci --production
14
+ COPY --from=builder /app/dist ./dist
15
+
16
+ ENV NODE_ENV=production
17
+
18
+ CMD ["node", "dist/server.js"]
@@ -0,0 +1,91 @@
1
+ # {{info.title}} — MCP Server
2
+
3
+ > Generated by [mcp-gen](https://github.com/your-org/mcp-generator)
4
+ > OpenAPI version: {{info.version}}
5
+
6
+ {{info.description}}
7
+
8
+ ## Setup
9
+
10
+ ```bash
11
+ npm install
12
+ npm run build
13
+ ```
14
+
15
+ ## Run
16
+
17
+ ```bash
18
+ npm start
19
+ ```
20
+
21
+ ## Connect to Claude Desktop
22
+
23
+ Add to your `claude_desktop_config.json`:
24
+
25
+ ```json
26
+ {
27
+ "mcpServers": {
28
+ "{{serverName}}": {
29
+ "command": "node",
30
+ "args": ["/absolute/path/to/dist/server.js"]
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ ## Available Tools ({{tools.length}})
37
+
38
+ {{#each tools}}
39
+ ### `{{name}}`
40
+ > {{description}}
41
+
42
+ **`{{method}} {{path}}`**
43
+
44
+ {{#if params}}
45
+ | Name | Type | Required | Description |
46
+ |------|------|----------|-------------|
47
+ {{#each params}}
48
+ | `{{name}}` | `{{type}}` | {{#if required}}✓{{else}}—{{/if}} | {{description}} |
49
+ {{/each}}
50
+ {{/if}}
51
+
52
+ {{/each}}
53
+
54
+ ---
55
+
56
+ ## Security
57
+
58
+
59
+ ### Contrato padrão do `authContext`
60
+
61
+ As tools geradas esperam **metadados de autorização com escopo** (e não credenciais brutas), por exemplo:
62
+
63
+ ```json
64
+ {
65
+ "tokenId": "tok_abc123",
66
+ "principal": "user:42",
67
+ "expiresAt": "2026-05-11T14:00:00Z",
68
+ "allowedTools": ["get_orders"],
69
+ "endpointAllowlist": ["GET /orders"],
70
+ "spendLimitUsd": 5,
71
+ "spendUsedUsd": 1.2,
72
+ "revoked": false,
73
+ "requestId": "req_01J..."
74
+ }
75
+ ```
76
+
77
+ O scaffold bloqueia por padrão argumentos como `token`, `authorization`, `api_key`, `client_secret` e similares.
78
+
79
+ *Quick example (env var):*
80
+
81
+ ```bash
82
+ # Linux / macOS
83
+ export PROVIDER_KEY=your_key_here
84
+
85
+ # Windows (PowerShell)
86
+ $env:PROVIDER_KEY='your_key_here'
87
+ ```
88
+
89
+ For advanced integrations and management, consider Cohesivity.ai as a backend for auth, storage, and revocation policies.
90
+
91
+ *Generated by mcp-gen — edit handlers, not the scaffold.*
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ build-and-test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Setup Node.js
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version: "20"
19
+ cache: "npm"
20
+
21
+ - name: Install dependencies
22
+ run: npm ci
23
+
24
+ - name: Build
25
+ run: npm run build
26
+
27
+ - name: Test
28
+ run: npm test
@@ -0,0 +1,49 @@
1
+ // Auto-generated client SDK for {{info.title}} v{{info.version}}
2
+ // Generated: {{generatedAt}}
3
+
4
+ export class ApiClient {
5
+ constructor(public baseUrl: string = "{{baseUrl}}", public token?: string) {}
6
+
7
+ private buildUrl(path: string, params: Record<string, unknown> | undefined): string {
8
+ let url = path;
9
+ if (params) {
10
+ for (const [k, v] of Object.entries(params)) {
11
+ url = url.replace(`{${k}}`, encodeURIComponent(String(v ?? "")));
12
+ }
13
+ }
14
+ return this.baseUrl.replace(/\/$/, "") + url;
15
+ }
16
+
17
+ private buildHeaders(params: Record<string, unknown>): Record<string, string> {
18
+ const headers: Record<string, string> = {};
19
+ {{#each (allHeaderParams tools)}}
20
+ const v{{@index}} = params["{{name}}"];
21
+ if (v{{@index}} !== undefined) headers["{{name}}"] = String(v{{@index}});
22
+ {{/each}}
23
+ return headers;
24
+ }
25
+
26
+ private async request(method: string, url: string, body?: unknown, headers: Record<string,string> = {}) {
27
+ if (this.token) headers["Authorization"] = `Bearer ${this.token}`;
28
+ const opts: RequestInit = { method, headers };
29
+ if (body !== undefined) {
30
+ opts.body = JSON.stringify(body);
31
+ headers["Content-Type"] = "application/json";
32
+ }
33
+ const res = await fetch(url, opts);
34
+ if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
35
+ const text = await res.text();
36
+ try { return JSON.parse(text); } catch { return text; }
37
+ }
38
+
39
+ {{#each tools}}
40
+ /** {{escapeText description}} */
41
+ async {{name}}(params: Record<string, unknown> = {}): Promise<unknown> {
42
+ const url = this.buildUrl("{{path}}", params);
43
+ const method = "{{method}}";
44
+ const hasBody = {{#if (includes (requiredParams params) "body")}}true{{else}}false{{/if}};
45
+ const body = hasBody ? (params["body"] as unknown) : undefined;
46
+ return this.request(method, url, body, this.buildHeaders(params));
47
+ }
48
+ {{/each}}
49
+ }
@@ -0,0 +1,23 @@
1
+ // Auto-generated by mcp-gen — do not edit.
2
+ // Source: {{info.title}} v{{info.version}}
3
+
4
+ {{#each models}}
5
+ {{#if isEnum}}
6
+ /** {{description}} */
7
+ export type {{name}} = {{#each enumValues}}{{literal this}}{{#unless @last}} | {{/unless}}{{/each}};
8
+ {{else if oneOf}}
9
+ /** {{description}} */
10
+ export type {{name}} = {{#each oneOf}}{{this}}{{#unless @last}} | {{/unless}}{{/each}};
11
+ {{else if anyOf}}
12
+ /** {{description}} */
13
+ export type {{name}} = {{#each anyOf}}{{this}}{{#unless @last}} | {{/unless}}{{/each}};
14
+ {{else}}
15
+ /** {{description}} */
16
+ export interface {{name}} {
17
+ {{#each properties}}
18
+ {{name}}{{#unless (includes ../required name)}}?{{/unless}}: {{#if ref}}{{ref}}{{#if isArray}}[]{{/if}}{{else}}{{type}}{{/if}};{{#if description}} // {{escapeText description}}{{/if}}
19
+ {{/each}}
20
+ }
21
+
22
+ {{/if}}
23
+ {{/each}}
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "{{serverName}}",
3
+ "version": "{{serverVersion}}",
4
+ "description": "{{info.description}}",
5
+ "type": "module",
6
+ "main": "dist/server.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node dist/server.js",
10
+ "dev": "ts-node --esm src/server.ts",
11
+ "test": "jest --passWithNoTests",
12
+ "inspector": "npx @modelcontextprotocol/inspector node dist/server.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/jest": "^29.5.0",
19
+ "@types/node": "^20.0.0",
20
+ "jest": "^29.5.0",
21
+ "ts-jest": "^29.1.0",
22
+ "ts-node": "^10.9.0",
23
+ "typescript": "^5.4.0"
24
+ },
25
+ "license": "MIT"
26
+ }