@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,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 Go
16
+ uses: actions/setup-go@v5
17
+ with:
18
+ go-version: "1.22"
19
+ cache: true
20
+
21
+ - name: Download dependencies
22
+ run: go mod download
23
+
24
+ - name: Build
25
+ run: go build -v .
26
+
27
+ - name: Test
28
+ run: go test -v ./...
@@ -0,0 +1,86 @@
1
+ // Auto-generated client SDK for {{info.title}} v{{info.version}}
2
+ // Generated: {{generatedAt}}
3
+
4
+ package main
5
+
6
+ import (
7
+ "bytes"
8
+ "encoding/json"
9
+ "fmt"
10
+ "io"
11
+ "net/http"
12
+ "strings"
13
+ "time"
14
+ )
15
+
16
+ type APIClient struct {
17
+ BaseURL string
18
+ Token string
19
+ HTTP *http.Client
20
+ }
21
+
22
+ func NewAPIClient(baseURL, token string) *APIClient {
23
+ if token == "" {
24
+ token = apiToken
25
+ }
26
+ return &APIClient{BaseURL: strings.TrimRight(baseURL, "/"), Token: token, HTTP: &http.Client{Timeout: 30 * time.Second}}
27
+ }
28
+
29
+ type APIError struct {
30
+ Status int
31
+ Body string
32
+ }
33
+
34
+ func (e *APIError) Error() string {
35
+ return fmt.Sprintf("HTTP %d: %s", e.Status, e.Body)
36
+ }
37
+
38
+ func (c *APIClient) do(method, path string, params map[string]any) (any, error) {
39
+ path = c.expand(path, params)
40
+ var body io.Reader
41
+ if v, ok := params["body"]; ok {
42
+ b, err := json.Marshal(v)
43
+ if err != nil {
44
+ return nil, err
45
+ }
46
+ body = bytes.NewReader(b)
47
+ }
48
+ req, err := http.NewRequest(method, c.BaseURL+path, body)
49
+ if err != nil {
50
+ return nil, err
51
+ }
52
+ if c.Token != "" {
53
+ req.Header.Set("Authorization", "Bearer "+c.Token)
54
+ }
55
+ if body != nil {
56
+ req.Header.Set("Content-Type", "application/json")
57
+ }
58
+ {{#each (allHeaderParams tools)}}
59
+ if v, ok := params["{{name}}"]; ok {
60
+ req.Header.Set("{{name}}", fmt.Sprintf("%v", v))
61
+ }
62
+ {{/each}}
63
+ resp, err := c.HTTP.Do(req)
64
+ if err != nil {
65
+ return nil, err
66
+ }
67
+ defer resp.Body.Close()
68
+ raw, _ := io.ReadAll(resp.Body)
69
+ if resp.StatusCode >= 400 {
70
+ return nil, &APIError{Status: resp.StatusCode, Body: string(raw)}
71
+ }
72
+ var out any
73
+ if len(raw) > 0 {
74
+ if err := json.Unmarshal(raw, &out); err != nil {
75
+ return string(raw), nil
76
+ }
77
+ }
78
+ return out, nil
79
+ }
80
+
81
+ func (c *APIClient) expand(path string, params map[string]any) string {
82
+ for k, v := range params {
83
+ path = strings.ReplaceAll(path, "{"+k+"}", fmt.Sprintf("%v", v))
84
+ }
85
+ return path
86
+ }
@@ -0,0 +1,5 @@
1
+ module {{serverName}}
2
+
3
+ go 1.22
4
+
5
+ require github.com/mark3labs/mcp-go v0.20.0
@@ -0,0 +1,28 @@
1
+ // Auto-generated by mcp-gen v{{generatorVersion}} — do not edit.
2
+ // Spec: {{info.title}} v{{info.version}}
3
+
4
+ package main
5
+
6
+ {{#each models}}
7
+ {{#if isEnum}}
8
+ // {{escapeText description}}
9
+ type {{name}} = {{#each enumValues}}{{#if (eq (typeof this) "string")}}"{{escapeLiteral this}}"{{else}}{{this}}{{/if}}{{#unless @last}} | {{/unless}}{{/each}}
10
+
11
+ {{else if oneOf}}
12
+ // {{escapeText description}}
13
+ type {{name}} = {{#each oneOf}}{{this}}{{#unless @last}} | {{/unless}}{{/each}}
14
+
15
+ {{else if anyOf}}
16
+ // {{escapeText description}}
17
+ type {{name}} = {{#each anyOf}}{{this}}{{#unless @last}} | {{/unless}}{{/each}}
18
+
19
+ {{else}}
20
+ // {{escapeText description}}
21
+ type {{name}} struct {
22
+ {{#each properties}}
23
+ {{pascal name}} {{#if ref}}{{ref}}{{#if isArray}}[]{{/if}}{{else if isArray}}[]any{{else}}{{goType type}}{{/if}} `json:"{{name}}{{#unless (includes ../required name)}},omitempty{{/unless}}"`{{#if description}} // {{escapeText description}}{{/if}}
24
+ {{/each}}
25
+ }
26
+
27
+ {{/if}}
28
+ {{/each}}
@@ -0,0 +1,220 @@
1
+ // Auto-generated by mcp-gen v{{generatorVersion}} — do not edit.
2
+ // Spec: {{info.title}} v{{info.version}}
3
+ // Generated: {{generatedAt}}
4
+ {{#if incremental}}
5
+ // Incremental mode: edit code between @@mcp-gen markers — it will be preserved on re-generation.
6
+ {{/if}}
7
+ package main
8
+
9
+ import (
10
+ "context"
11
+ "encoding/json"
12
+ "fmt"
13
+ "os"
14
+ "strings"
15
+
16
+ "github.com/mark3labs/mcp-go/mcp"
17
+ "github.com/mark3labs/mcp-go/server"
18
+ )
19
+
20
+ var baseURL = "{{baseUrl}}"
21
+ var apiToken = os.Getenv("TOKEN")
22
+
23
+ // RAW_CREDENTIAL_KEYS defines credential keywords that are blocked by default
24
+ var RAW_CREDENTIAL_KEYS = map[string]bool{
25
+ "authorization": true,
26
+ "token": true,
27
+ "access_token": true,
28
+ "api_key": true,
29
+ "apikey": true,
30
+ "x_api_key": true,
31
+ "client_secret": true,
32
+ "refresh_token": true,
33
+ "password": true,
34
+ "secret": true,
35
+ }
36
+
37
+ // TOOL_POLICIES defines per-tool security policies
38
+ var TOOL_POLICIES = map[string]struct {
39
+ Endpoint string
40
+ RequiresTTL bool
41
+ RequiresSpendLimit bool
42
+ RequiresRequestLog bool
43
+ RequiresRevocationCheck bool
44
+ }{
45
+ {{#each tools}}
46
+ "{{name}}": {
47
+ Endpoint: "{{method}} {{path}}",
48
+ RequiresTTL: true,
49
+ RequiresSpendLimit: true,
50
+ RequiresRequestLog: true,
51
+ RequiresRevocationCheck: true,
52
+ },
53
+ {{/each}}
54
+ }
55
+
56
+ func jsonSerialize(v any) (*mcp.CallToolResult, error) {
57
+ b, err := json.MarshalIndent(v, "", " ")
58
+ if err != nil {
59
+ return nil, err
60
+ }
61
+ return mcp.NewToolResultText(string(b)), nil
62
+ }
63
+
64
+ func jsonExample(s string) any {
65
+ var v any
66
+ if err := json.Unmarshal([]byte(s), &v); err != nil {
67
+ return nil
68
+ }
69
+ return v
70
+ }
71
+
72
+ func hasRawCredentialKey(value any) bool {
73
+ if value == nil {
74
+ return false
75
+ }
76
+ switch v := value.(type) {
77
+ case map[string]any:
78
+ for key, nested := range v {
79
+ normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(key, "-", ""), "_", ""))
80
+ for candidate := range RAW_CREDENTIAL_KEYS {
81
+ candNorm := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(candidate, "-", ""), "_", ""))
82
+ if normalized == candNorm {
83
+ return true
84
+ }
85
+ }
86
+ if hasRawCredentialKey(nested) {
87
+ return true
88
+ }
89
+ }
90
+ case []any:
91
+ for _, item := range v {
92
+ if hasRawCredentialKey(item) {
93
+ return true
94
+ }
95
+ }
96
+ }
97
+ return false
98
+ }
99
+
100
+ func requireSecurity(toolName string, security any, authContext map[string]any, requestArgs map[string]any) error {
101
+ if requestArgs != nil && hasRawCredentialKey(requestArgs) {
102
+ return fmt.Errorf("raw provider credentials are not allowed in tool arguments. use scoped auth_context metadata only")
103
+ }
104
+ if authContext != nil && hasRawCredentialKey(authContext) {
105
+ return fmt.Errorf("raw provider credentials are not allowed in auth_context. use scoped metadata only")
106
+ }
107
+
108
+ policy, ok := TOOL_POLICIES[toolName]
109
+ if !ok {
110
+ return fmt.Errorf("missing security policy for tool: %s", toolName)
111
+ }
112
+
113
+ if authContext == nil {
114
+ return fmt.Errorf("missing auth_context. provide scoped metadata (token_id, expires_at, limits, request_id) from your gateway")
115
+ }
116
+
117
+ if policy.RequiresRevocationCheck {
118
+ if revoked, ok := authContext["revoked"].(bool); ok && revoked {
119
+ return fmt.Errorf("access revoked")
120
+ }
121
+ }
122
+
123
+ if policy.RequiresTTL {
124
+ if expiresAt, ok := authContext["expires_at"].(string); ok {
125
+ // Simple check - in production use proper parsing
126
+ if expiresAt == "" || expiresAt < "2025-01-01T00:00:00Z" {
127
+ return fmt.Errorf("token expired or missing TTL")
128
+ }
129
+ } else {
130
+ return fmt.Errorf("token expired or missing TTL")
131
+ }
132
+ }
133
+
134
+ if policy.RequiresRequestLog {
135
+ if _, ok := authContext["request_id"].(string); !ok {
136
+ return fmt.Errorf("request_id is required for audit logging")
137
+ }
138
+ }
139
+
140
+ if policy.RequiresSpendLimit {
141
+ limit, hasLimit := authContext["spend_limit_usd"].(float64)
142
+ used, hasUsed := authContext["spend_used_usd"].(float64)
143
+ if !hasLimit || !hasUsed {
144
+ return fmt.Errorf("spend limit metadata is required")
145
+ }
146
+ if used > limit {
147
+ return fmt.Errorf("spend limit exceeded")
148
+ }
149
+ }
150
+
151
+ if allowedTools, ok := authContext["allowed_tools"].([]any); ok {
152
+ found := false
153
+ for _, t := range allowedTools {
154
+ if ts, ok := t.(string); ok && ts == toolName {
155
+ found = true
156
+ break
157
+ }
158
+ }
159
+ if !found {
160
+ return fmt.Errorf("tool not allowed by scope: %s", toolName)
161
+ }
162
+ }
163
+
164
+ if endpointAllowlist, ok := authContext["endpoint_allowlist"].([]any); ok {
165
+ found := false
166
+ for _, e := range endpointAllowlist {
167
+ if es, ok := e.(string); ok && es == policy.Endpoint {
168
+ found = true
169
+ break
170
+ }
171
+ }
172
+ if !found {
173
+ return fmt.Errorf("endpoint not allowed: %s", policy.Endpoint)
174
+ }
175
+ }
176
+
177
+ if security != nil {
178
+ if _, ok := authContext["token_id"].(string); !ok {
179
+ return fmt.Errorf("missing token_id for secured operation")
180
+ }
181
+ }
182
+
183
+ return nil
184
+ }
185
+
186
+ func main() {
187
+ s := server.NewMCPServer("{{serverName}}", "{{serverVersion}}")
188
+
189
+ {{#each tools}}
190
+ // {{method}} {{path}}
191
+ {{name}}Tool := mcp.NewTool("{{name}}",
192
+ mcp.WithDescription("{{escapeText description}}"),
193
+ {{#each params}}
194
+ mcp.With{{pascalGoType type}}("{{name}}",
195
+ mcp.Description("{{escapeText description}}"),
196
+ {{#if required}}
197
+ mcp.Required(),
198
+ {{/if}}
199
+ ),
200
+ {{/each}}
201
+ )
202
+ s.AddTool({{name}}Tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
203
+ _ = request.Params.Arguments
204
+ // @@mcp-gen:start:{{name}}
205
+ {{#if http}}
206
+ return nil, fmt.Errorf("impl via client not yet wired: {{name}} (edit inside @@mcp-gen markers to call {{method}} {{path}})")
207
+ {{else if exampleResponse}}
208
+ return jsonSerialize(jsonExample(`{{json exampleResponse}}`))
209
+ {{else}}
210
+ return nil, fmt.Errorf("handler not implemented: {{name}}")
211
+ {{/if}}
212
+ // @@mcp-gen:end:{{name}}
213
+ })
214
+
215
+ {{/each}}
216
+ if err := server.ServeStdio(s); err != nil {
217
+ fmt.Fprintf(os.Stderr, "Fatal: %v\n", err)
218
+ os.Exit(1)
219
+ }
220
+ }
@@ -0,0 +1,12 @@
1
+ FROM python:3.12-slim AS runtime
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt ./
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ ENV PYTHONUNBUFFERED=1
11
+
12
+ CMD ["python", "server.py"]
@@ -0,0 +1,115 @@
1
+ # {{info.title}} — MCP Server (Python)
2
+
3
+ > Generated by [mcp-gen](https://github.com/your-org/openapi-to-mcp)
4
+ > OpenAPI version: {{info.version}}
5
+
6
+ {{info.description}}
7
+
8
+ ## Setup
9
+
10
+ ```bash
11
+ pip install -r requirements.txt
12
+ ```
13
+
14
+ ## Run
15
+
16
+ ```bash
17
+ python server.py
18
+ ```
19
+
20
+ ## Connect to Claude Desktop
21
+
22
+ Add to your `claude_desktop_config.json`:
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "{{serverName}}": {
28
+ "command": "python",
29
+ "args": ["/absolute/path/to/server.py"]
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## Available Tools ({{tools.length}})
36
+
37
+ {{#each tools}}
38
+ ### `{{name}}`
39
+
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
+ {{else}}
51
+ No parameters.
52
+ {{/if}}
53
+
54
+ {{/each}}
55
+
56
+ ## Implement Handlers
57
+
58
+ Find each function in `server.py` and replace the stub:
59
+
60
+ ```python
61
+ @mcp.tool()
62
+ async def your_tool_name(param: str) -> Any:
63
+ """Your tool description"""
64
+ # @@mcp-gen:start:your_tool_name
65
+ result = await your_api_call(param)
66
+ return result
67
+ # @@mcp-gen:end:your_tool_name
68
+ ```
69
+
70
+ ## Base URL
71
+
72
+ `{{baseUrl}}`
73
+
74
+ ---
75
+
76
+ ## Security
77
+
78
+ - **Don't embed credentials**: don't include provider keys or secrets directly in generated projects. Use environment variables or a secret manager (e.g., Vault, AWS Secrets Manager) or credential management service (e.g., Cohesivity.ai) for storage and secret revocation.
79
+ - **Scoped and TTL tokens**: prefer short-lived tokens with minimal scope, usage limits, and revocation capability.
80
+ - **Logs and audit**: log sensitive requests and enable audit for critical operations.
81
+ - **Never put secrets in model context**: avoid passing keys, tokens, or secrets in prompts or model context.
82
+
83
+ ### Default `auth_context` contract
84
+
85
+ Generated tools expect **scoped authorization metadata** (not raw provider credentials), for example:
86
+
87
+ ```json
88
+ {
89
+ "token_id": "tok_abc123",
90
+ "principal": "user:42",
91
+ "expires_at": "2026-05-11T14:00:00Z",
92
+ "allowed_tools": ["get_orders"],
93
+ "endpoint_allowlist": ["GET /orders"],
94
+ "spend_limit_usd": 5,
95
+ "spend_used_usd": 1.2,
96
+ "revoked": false,
97
+ "request_id": "req_01J..."
98
+ }
99
+ ```
100
+
101
+ The scaffold blocks raw credential-style arguments such as `token`, `authorization`, `api_key`, `client_secret`, and similar keys by default.
102
+
103
+ *Quick example (env var):*
104
+
105
+ ```bash
106
+ # Linux / macOS
107
+ export PROVIDER_KEY=your_key_here
108
+
109
+ # Windows (PowerShell)
110
+ $env:PROVIDER_KEY='your_key_here'
111
+ ```
112
+
113
+ For advanced integrations and management, consider Cohesivity.ai as a backend for auth, storage, and revocation policies.
114
+
115
+ *Generated by mcp-gen — edit handlers, not the scaffold.*
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install dependencies
21
+ run: pip install -r requirements.txt pytest pytest-asyncio
22
+
23
+ - name: Run tests
24
+ run: pytest --tb=short
@@ -0,0 +1,20 @@
1
+ # Auto-generated by mcp-gen v{{generatorVersion}} — do not edit.
2
+ # Spec: {{info.title}} v{{info.version}}
3
+
4
+ from pydantic import BaseModel, Field
5
+ from typing import Optional, List, Any, Literal
6
+
7
+
8
+ {{#each models}}
9
+ {{#if isEnum}}
10
+ {{name}} = {{#each enumValues}}{{literal this}}{{#unless @last}} | {{/unless}}{{/each}}
11
+ {{else}}
12
+ class {{name}}(BaseModel):
13
+ """{{escapeText description}}"""
14
+ {{#each properties}}
15
+ {{name}}: {{pyType this}} {{#unless (includes ../required name)}} = None{{/unless}}
16
+ {{#if description}} # {{escapeText description}}{{/if}}
17
+ {{/each}}
18
+
19
+ {{/if}}
20
+ {{/each}}
@@ -0,0 +1,3 @@
1
+ mcp[cli]>=1.0.0
2
+ pydantic>=2.0.0
3
+ httpx>=0.27.0