@karlangas12/openapi-to-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.es.md +130 -0
  3. package/README.md +222 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +169 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/telemetry/index.d.ts +78 -0
  8. package/dist/core/telemetry/index.js +152 -0
  9. package/dist/core/telemetry/index.js.map +1 -0
  10. package/dist/generator/codegen.d.ts +15 -0
  11. package/dist/generator/codegen.js +33 -0
  12. package/dist/generator/codegen.js.map +1 -0
  13. package/dist/generator/operations.d.ts +47 -0
  14. package/dist/generator/operations.js +143 -0
  15. package/dist/generator/operations.js.map +1 -0
  16. package/dist/http/client.d.ts +47 -0
  17. package/dist/http/client.js +92 -0
  18. package/dist/http/client.js.map +1 -0
  19. package/dist/http/format.d.ts +10 -0
  20. package/dist/http/format.js +22 -0
  21. package/dist/http/format.js.map +1 -0
  22. package/dist/index.d.ts +27 -0
  23. package/dist/index.js +18 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/mcp/jsonrpc.d.ts +43 -0
  26. package/dist/mcp/jsonrpc.js +27 -0
  27. package/dist/mcp/jsonrpc.js.map +1 -0
  28. package/dist/mcp/server.d.ts +32 -0
  29. package/dist/mcp/server.js +99 -0
  30. package/dist/mcp/server.js.map +1 -0
  31. package/dist/mcp/stdio.d.ts +10 -0
  32. package/dist/mcp/stdio.js +49 -0
  33. package/dist/mcp/stdio.js.map +1 -0
  34. package/dist/openapi/load.d.ts +15 -0
  35. package/dist/openapi/load.js +60 -0
  36. package/dist/openapi/load.js.map +1 -0
  37. package/dist/openapi/resolve.d.ts +20 -0
  38. package/dist/openapi/resolve.js +57 -0
  39. package/dist/openapi/resolve.js.map +1 -0
  40. package/dist/openapi/types.d.ts +94 -0
  41. package/dist/openapi/types.js +20 -0
  42. package/dist/openapi/types.js.map +1 -0
  43. package/dist/schema/to-json.d.ts +14 -0
  44. package/dist/schema/to-json.js +41 -0
  45. package/dist/schema/to-json.js.map +1 -0
  46. package/dist/schema/to-zod.d.ts +8 -0
  47. package/dist/schema/to-zod.js +131 -0
  48. package/dist/schema/to-zod.js.map +1 -0
  49. package/dist/utils/errors.d.ts +47 -0
  50. package/dist/utils/errors.js +81 -0
  51. package/dist/utils/errors.js.map +1 -0
  52. package/dist/utils/http.d.ts +46 -0
  53. package/dist/utils/http.js +70 -0
  54. package/dist/utils/http.js.map +1 -0
  55. package/dist/utils/index.d.ts +4 -0
  56. package/dist/utils/index.js +5 -0
  57. package/dist/utils/index.js.map +1 -0
  58. package/dist/utils/time.d.ts +27 -0
  59. package/dist/utils/time.js +32 -0
  60. package/dist/utils/time.js.map +1 -0
  61. package/package.json +74 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Karlangas12
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.es.md ADDED
@@ -0,0 +1,130 @@
1
+ <div align="center">
2
+
3
+ # 🔌 openapi-to-mcp
4
+
5
+ **Convierte cualquier especificación OpenAPI 3.x en un servidor MCP en vivo.**
6
+ Apúntalo a una spec (fichero o URL) y cada endpoint se vuelve una herramienta
7
+ validada que tu agente de IA puede invocar — sin código pegamento, sin escribir
8
+ un servidor por cada API.
9
+
10
+ [![CI](https://github.com/Karlangas12/openapi-to-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Karlangas12/openapi-to-mcp/actions/workflows/ci.yml)
11
+ [![npm](https://img.shields.io/npm/v/@karlangas12/openapi-to-mcp?color=cb3837&logo=npm)](https://www.npmjs.com/package/@karlangas12/openapi-to-mcp)
12
+ [![tests](https://img.shields.io/badge/tests-32%20passing-brightgreen)](#pruebas)
13
+ [![node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen)](https://nodejs.org)
14
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
15
+
16
+ [English](README.md)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## Por qué
23
+
24
+ MCP permite a un agente invocar herramientas, pero alguien tiene que escribirlas.
25
+ Si la capacidad que quieres ya existe como una API REST con especificación
26
+ OpenAPI, escribir un servidor MCP a medida para ella es trabajo mecánico.
27
+
28
+ `openapi-to-mcp` lee la especificación y hace el mapeo por ti: cada
29
+ `MÉTODO /ruta` se convierte en una herramienta, cada parámetro y cuerpo de
30
+ petición se vuelve una entrada **validada con Zod**, y cada llamada se traduce a
31
+ una petición HTTP con tu cabecera de autenticación. La respuesta vuelve recortada
32
+ para la ventana de contexto de un LLM.
33
+
34
+ ## Inicio rápido
35
+
36
+ Sin instalación: ejecútalo directamente desde npm.
37
+
38
+ ```bash
39
+ npx @karlangas12/openapi-to-mcp --spec https://api.example.com/openapi.json \
40
+ --auth-header "Authorization: Bearer TU_TOKEN"
41
+ ```
42
+
43
+ Mira qué herramientas produce una spec sin arrancar el servidor:
44
+
45
+ ```bash
46
+ npx @karlangas12/openapi-to-mcp --spec ./openapi.yaml --list
47
+ ```
48
+
49
+ ### Claude Desktop — `claude_desktop_config.json`
50
+
51
+ ```jsonc
52
+ {
53
+ "mcpServers": {
54
+ "petstore": {
55
+ "command": "npx",
56
+ "args": [
57
+ "-y", "@karlangas12/openapi-to-mcp",
58
+ "--spec", "https://petstore3.swagger.io/api/v3/openapi.json",
59
+ "--auth-header", "Authorization: Bearer TU_TOKEN"
60
+ ]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ Reinicia el cliente y los endpoints de la API aparecen como herramientas.
67
+
68
+ ## Cómo mapea
69
+
70
+ | OpenAPI | Se convierte en |
71
+ | --- | --- |
72
+ | `operationId`, o `MÉTODO /ruta` si falta | Nombre de la herramienta (`get_pets_by_pet_id`) |
73
+ | `summary` / `description` | Descripción de la herramienta |
74
+ | Parámetros de ruta, query y cabecera | Propiedades de entrada (los de ruta son obligatorios) |
75
+ | `requestBody` (`application/json`) | Una propiedad `body` |
76
+ | Cada schema de entrada | Un validador **Zod** + un JSON Schema para `tools/list` |
77
+ | `$ref` local (`#/components/...`) | Resuelto en línea |
78
+ | `enum`, `minimum`/`maximum`, `required`, `nullable`, `oneOf`/`anyOf`/`allOf` | Exigidos en la entrada |
79
+
80
+ Los argumentos se validan **antes** de que salga ninguna petición HTTP. Una
81
+ respuesta `4xx`/`5xx` vuelve como resultado de error que el modelo puede leer y
82
+ corregir, no como un fallo silencioso.
83
+
84
+ ## Emparejar con mcp-shield-proxy
85
+
86
+ `openapi-to-mcp` habla con una **API de terceros** en tu nombre y con tus
87
+ credenciales. Si quieres una política, enmascarado de credenciales y un registro
88
+ de auditoría alrededor, envuélvelo con
89
+ [mcp-shield-proxy](https://www.npmjs.com/package/mcp-shield-proxy) — se componen
90
+ con una línea más:
91
+
92
+ ```jsonc
93
+ {
94
+ "mcpServers": {
95
+ "petstore": {
96
+ "command": "npx",
97
+ "args": [
98
+ "-y", "mcp-shield-proxy", "--",
99
+ "npx", "-y", "@karlangas12/openapi-to-mcp",
100
+ "--spec", "https://api.example.com/openapi.json",
101
+ "--auth-header", "Authorization: Bearer TU_TOKEN"
102
+ ]
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ## Limitaciones honestas
109
+
110
+ - **Sólo transporte stdio.** Cubre Claude Desktop, Cursor y Claude Code. Servir
111
+ por HTTP/SSE nativo aún no está implementado.
112
+ - **Sólo `$ref` locales.** Las referencias internas (`#/components/...`) se
113
+ resuelven; las externas (a otros ficheros o URLs) dan un error claro.
114
+ - **Cuerpos `application/json`.** `multipart/form-data` y formularios aún no.
115
+ - **Es un mapeador, no una pasarela.** No añade reintentos, caché ni límites de
116
+ tasa. Emparéjalo con un proxy si los necesitas.
117
+
118
+ ## Pruebas
119
+
120
+ ```bash
121
+ npm test # 32 tests
122
+ npm run typecheck # TypeScript estricto
123
+ ```
124
+
125
+ Cubre el parseo JSON **y** YAML, la conversión endpoint→herramienta con esquemas
126
+ Zod, y un `tools/call` completo por JSON-RPC contra un backend HTTP simulado.
127
+
128
+ ## Licencia
129
+
130
+ MIT
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ <div align="center">
2
+
3
+ # 🔌 openapi-to-mcp
4
+
5
+ **Turn any OpenAPI 3.x spec into a live MCP server.**
6
+ Point it at a spec (file or URL) and every endpoint becomes a validated tool your
7
+ AI agent can call — no glue code, no per-API server to write.
8
+
9
+ [![CI](https://github.com/Karlangas12/openapi-to-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Karlangas12/openapi-to-mcp/actions/workflows/ci.yml)
10
+ [![npm](https://img.shields.io/npm/v/@karlangas12/openapi-to-mcp?color=cb3837&logo=npm)](https://www.npmjs.com/package/@karlangas12/openapi-to-mcp)
11
+ [![tests](https://img.shields.io/badge/tests-32%20passing-brightgreen)](#testing)
12
+ [![node](https://img.shields.io/badge/node-%E2%89%A520-brightgreen)](https://nodejs.org)
13
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
14
+
15
+ [Quick start](#quick-start) · [How it maps](#how-it-maps) · [Config](#configuration) · [Secure it](#pairing-with-mcp-shield-proxy) · [Limits](#honest-limitations) · [Español](README.es.md)
16
+
17
+ </div>
18
+
19
+ ```
20
+ OpenAPI 3.x spec your AI client
21
+ (JSON / YAML, file or URL) (Claude, Cursor)
22
+ │ │
23
+ ▼ │ MCP / JSON-RPC (stdio)
24
+ [ openapi-to-mcp ] ◀───────────────────────────┘
25
+ │ GET /pets/{id} → tool "get_pet_by_id"
26
+ ▼ HTTP + your auth header
27
+ the underlying REST API
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Why
33
+
34
+ MCP lets an agent call tools, but someone has to write those tools. If the
35
+ capability you want already exists as a REST API with an OpenAPI spec, writing a
36
+ bespoke MCP server for it is busywork.
37
+
38
+ `openapi-to-mcp` reads the spec and does the mapping for you: each `METHOD /path`
39
+ becomes a tool, each parameter and request body becomes a **Zod-validated** input,
40
+ and every call is turned into an HTTP request with your auth header attached. The
41
+ response comes back trimmed for an LLM context window.
42
+
43
+ ## Quick start
44
+
45
+ No install needed — run it straight from npm:
46
+
47
+ ```bash
48
+ npx @karlangas12/openapi-to-mcp --spec https://api.example.com/openapi.json \
49
+ --auth-header "Authorization: Bearer YOUR_TOKEN"
50
+ ```
51
+
52
+ See what tools a spec produces without starting a server:
53
+
54
+ ```bash
55
+ npx @karlangas12/openapi-to-mcp --spec ./openapi.yaml --list
56
+ ```
57
+
58
+ ### Claude Desktop — `claude_desktop_config.json`
59
+
60
+ ```jsonc
61
+ {
62
+ "mcpServers": {
63
+ "petstore": {
64
+ "command": "npx",
65
+ "args": [
66
+ "-y", "@karlangas12/openapi-to-mcp",
67
+ "--spec", "https://petstore3.swagger.io/api/v3/openapi.json",
68
+ "--auth-header", "Authorization: Bearer YOUR_TOKEN"
69
+ ]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ### Cursor — `.cursor/mcp.json`
76
+
77
+ ```jsonc
78
+ {
79
+ "mcpServers": {
80
+ "my-api": {
81
+ "command": "npx",
82
+ "args": [
83
+ "-y", "@karlangas12/openapi-to-mcp",
84
+ "--spec", "./openapi.yaml",
85
+ "--base-url", "https://api.internal.company.com"
86
+ ]
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ That's the whole integration. Restart the client and the API's endpoints show up
93
+ as tools.
94
+
95
+ ## How it maps
96
+
97
+ | OpenAPI | Becomes |
98
+ | --- | --- |
99
+ | `operationId`, or `METHOD /path` if absent | Tool name (`get_pets_by_pet_id`) |
100
+ | `summary` / `description` | Tool description |
101
+ | Path, query and header parameters | Tool input properties (path params are required) |
102
+ | `requestBody` (`application/json`) | A `body` input property |
103
+ | Every input schema | A **Zod** validator + a JSON Schema for `tools/list` |
104
+ | Local `$ref` (`#/components/...`) | Resolved inline |
105
+ | `enum`, `minimum`/`maximum`, `required`, `nullable`, `oneOf`/`anyOf`/`allOf` | Enforced on input |
106
+
107
+ When the agent calls a tool, arguments are validated **before** any HTTP request
108
+ goes out. Path params are substituted into the URL, query params are appended,
109
+ header params and your static `--auth-header` values are sent, and a JSON body is
110
+ serialized. A `4xx`/`5xx` response comes back as an error result the model can
111
+ read and react to — not a silent failure.
112
+
113
+ ## Configuration
114
+
115
+ ```
116
+ openapi-to-mcp --spec <path|url> [options]
117
+
118
+ -s, --spec <path|url> OpenAPI 3.x spec (JSON or YAML, local or remote)
119
+ --base-url <url> API base URL (when the spec declares no "servers")
120
+ -H, --auth-header <h> Header to forward, "Name: value" (repeatable)
121
+ --format <mode> Response format: markdown (default) or json
122
+ -n, --name <name> MCP server name
123
+ --list Print the generated tools and exit
124
+ -h, --help / -v, --version
125
+ ```
126
+
127
+ Multiple headers:
128
+
129
+ ```bash
130
+ npx @karlangas12/openapi-to-mcp --spec ./api.yaml \
131
+ --auth-header "Authorization: Bearer XYZ" \
132
+ --auth-header "X-Api-Version: 2024-01"
133
+ ```
134
+
135
+ ### Export a standalone server (codegen)
136
+
137
+ ```bash
138
+ npx @karlangas12/openapi-to-mcp codegen --spec ./api.yaml -o server.ts
139
+ ```
140
+
141
+ Writes a single self-contained TypeScript file that embeds the spec and boots the
142
+ server via this package — handy for committing a pinned server to a repo.
143
+
144
+ ## Pairing with mcp-shield-proxy
145
+
146
+ `openapi-to-mcp` talks to a **third-party API** on your behalf, with your
147
+ credentials. If you want a policy, credential masking and an audit trail around
148
+ that, wrap it with [mcp-shield-proxy](https://www.npmjs.com/package/mcp-shield-proxy)
149
+ — they compose with one extra line:
150
+
151
+ ```jsonc
152
+ {
153
+ "mcpServers": {
154
+ "petstore": {
155
+ "command": "npx",
156
+ "args": [
157
+ "-y", "mcp-shield-proxy", "--", // ← inspect, mask, audit
158
+ "npx", "-y", "@karlangas12/openapi-to-mcp",
159
+ "--spec", "https://api.example.com/openapi.json",
160
+ "--auth-header", "Authorization: Bearer YOUR_TOKEN"
161
+ ]
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
167
+ Now every generated tool call is policy-checked, credentials in the traffic are
168
+ masked, and the whole session lands in a verifiable audit log.
169
+
170
+ ## Use as a library
171
+
172
+ ```ts
173
+ import { loadSpec, buildTools, createMcpServer, startStdioServer } from '@karlangas12/openapi-to-mcp';
174
+
175
+ const doc = await loadSpec('./openapi.yaml');
176
+ const { tools, baseUrl } = buildTools(doc);
177
+
178
+ const server = createMcpServer({
179
+ tools,
180
+ baseUrl: baseUrl ?? 'https://api.example.com',
181
+ headers: { Authorization: 'Bearer YOUR_TOKEN' },
182
+ fetchFn: (url, init) => fetch(url, init as RequestInit),
183
+ });
184
+
185
+ startStdioServer(server);
186
+ ```
187
+
188
+ The parser, the schema→Zod converter and the tool builder are all exported
189
+ independently.
190
+
191
+ ## Honest limitations
192
+
193
+ - **stdio transport only.** The MCP server speaks stdio, which covers Claude
194
+ Desktop, Cursor and Claude Code. Native HTTP/SSE serving isn't implemented yet.
195
+ - **Local `$ref` only.** References inside the document (`#/components/...`) are
196
+ resolved. External refs (other files or URLs) are not — they raise a clear
197
+ error rather than failing quietly.
198
+ - **`application/json` bodies.** Request bodies are mapped for JSON content.
199
+ `multipart/form-data` and `application/x-www-form-urlencoded` aren't mapped yet.
200
+ - **It's a mapper, not a gateway.** It doesn't add retries, caching or rate
201
+ limiting. Pair it with a proxy if you need those.
202
+
203
+ ## Performance
204
+
205
+ The spec is parsed once at startup; after that, each tool call is a schema
206
+ validation plus one HTTP request. Parsing and tool generation for a typical spec
207
+ is sub-millisecond — the latency you feel is the underlying API's, not this.
208
+
209
+ ## Testing
210
+
211
+ ```bash
212
+ npm test # 32 tests
213
+ npm run typecheck # strict TypeScript
214
+ ```
215
+
216
+ Coverage includes JSON **and** YAML parsing, endpoint→tool conversion with Zod
217
+ schemas, and a full `tools/call` round trip over JSON-RPC against a mocked HTTP
218
+ backend (asserting the URL, method, headers and body that reach it).
219
+
220
+ ## License
221
+
222
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { createLogger } from './core/telemetry/index.js';
4
+ import { toAppError } from './utils/errors.js';
5
+ import { loadSpec } from './openapi/load.js';
6
+ import { buildTools } from './generator/operations.js';
7
+ import { generateServerCode } from './generator/codegen.js';
8
+ import { createMcpServer } from './mcp/server.js';
9
+ import { startStdioServer } from './mcp/stdio.js';
10
+ const VERSION = '0.1.0';
11
+ /** Transporte que manda TODO a stderr: stdout queda limpio para el protocolo MCP. */
12
+ const stderrTransport = {
13
+ emit(record) {
14
+ process.stderr.write(`${JSON.stringify(record)}\n`);
15
+ },
16
+ };
17
+ /** Envoltorio del `fetch` global con la forma desacoplada `FetchLike`. */
18
+ const nodeFetch = (url, init) => fetch(url, init);
19
+ async function runCli(argv) {
20
+ if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
21
+ printHelp();
22
+ return;
23
+ }
24
+ if (argv.includes('--version') || argv.includes('-v')) {
25
+ process.stdout.write(`openapi-to-mcp v${VERSION}\n`);
26
+ return;
27
+ }
28
+ const isCodegen = argv[0] === 'codegen';
29
+ const options = parseArgs(isCodegen ? argv.slice(1) : argv);
30
+ if (options.spec === undefined) {
31
+ throw toAppError(new Error('Falta --spec <ruta|url> con la especificación OpenAPI.'));
32
+ }
33
+ const doc = await loadSpec(options.spec, { fetchFn: nodeFetch });
34
+ if (isCodegen) {
35
+ await runCodegen(doc, options);
36
+ return;
37
+ }
38
+ const { tools, baseUrl } = buildTools(doc, options.baseUrl === undefined ? {} : { baseUrl: options.baseUrl });
39
+ if (options.list) {
40
+ process.stdout.write(`${tools.length} herramienta(s) generada(s):\n`);
41
+ for (const tool of tools)
42
+ process.stdout.write(` ${tool.name} — ${tool.description}\n`);
43
+ return;
44
+ }
45
+ const resolvedBaseUrl = options.baseUrl ?? baseUrl;
46
+ if (resolvedBaseUrl === undefined || resolvedBaseUrl === '') {
47
+ throw toAppError(new Error('No hay URL base para la API. Declara "servers" en la especificación o pásala con --base-url.'));
48
+ }
49
+ const logger = createLogger({ service: 'openapi-to-mcp', transport: stderrTransport });
50
+ const server = createMcpServer({
51
+ tools,
52
+ baseUrl: resolvedBaseUrl,
53
+ headers: options.headers,
54
+ fetchFn: nodeFetch,
55
+ formatMode: options.format,
56
+ logger,
57
+ ...(options.name === undefined ? {} : { serverInfo: { name: options.name, version: VERSION } }),
58
+ });
59
+ logger.info('servidor MCP listo', { tools: tools.length, baseUrl: resolvedBaseUrl });
60
+ startStdioServer(server);
61
+ }
62
+ async function runCodegen(doc, options) {
63
+ const code = generateServerCode(doc, {
64
+ ...(options.baseUrl === undefined ? {} : { baseUrl: options.baseUrl }),
65
+ ...(options.name === undefined ? {} : { serverName: options.name }),
66
+ });
67
+ if (options.codegenOut === undefined) {
68
+ process.stdout.write(code);
69
+ return;
70
+ }
71
+ await writeFile(options.codegenOut, code, 'utf8');
72
+ process.stdout.write(`✓ Servidor MCP generado en ${options.codegenOut}\n`);
73
+ }
74
+ function parseArgs(argv) {
75
+ const options = { headers: {}, format: 'markdown', list: false };
76
+ for (let i = 0; i < argv.length; i += 1) {
77
+ const arg = argv[i];
78
+ switch (arg) {
79
+ case '--spec':
80
+ case '-s':
81
+ options.spec = requireValue(argv, (i += 1), arg);
82
+ break;
83
+ case '--url':
84
+ // Alias práctico: si te pasan la URL de la especificación como --url.
85
+ options.spec = requireValue(argv, (i += 1), arg);
86
+ break;
87
+ case '--base-url':
88
+ options.baseUrl = requireValue(argv, (i += 1), arg);
89
+ break;
90
+ case '--auth-header':
91
+ case '--header':
92
+ case '-H': {
93
+ const [name, value] = splitHeader(requireValue(argv, (i += 1), arg));
94
+ options.headers[name] = value;
95
+ break;
96
+ }
97
+ case '--format':
98
+ options.format = parseFormat(requireValue(argv, (i += 1), arg));
99
+ break;
100
+ case '--name':
101
+ case '-n':
102
+ options.name = requireValue(argv, (i += 1), arg);
103
+ break;
104
+ case '--out':
105
+ case '-o':
106
+ options.codegenOut = requireValue(argv, (i += 1), arg);
107
+ break;
108
+ case '--list':
109
+ options.list = true;
110
+ break;
111
+ default:
112
+ // Primer argumento posicional sin bandera: se toma como --spec.
113
+ if (arg !== undefined && !arg.startsWith('-') && options.spec === undefined) {
114
+ options.spec = arg;
115
+ }
116
+ break;
117
+ }
118
+ }
119
+ return options;
120
+ }
121
+ function requireValue(argv, index, flag) {
122
+ const value = argv[index];
123
+ if (value === undefined) {
124
+ throw toAppError(new Error(`La opción ${flag} necesita un valor.`));
125
+ }
126
+ return value;
127
+ }
128
+ function splitHeader(raw) {
129
+ const separator = raw.indexOf(':');
130
+ if (separator === -1) {
131
+ throw toAppError(new Error(`Cabecera mal formada: "${raw}". Usa el formato "Nombre: valor".`));
132
+ }
133
+ return [raw.slice(0, separator).trim(), raw.slice(separator + 1).trim()];
134
+ }
135
+ function parseFormat(raw) {
136
+ if (raw === 'json' || raw === 'markdown')
137
+ return raw;
138
+ throw toAppError(new Error(`Formato no válido: "${raw}". Usa "markdown" o "json".`));
139
+ }
140
+ function printHelp() {
141
+ process.stdout.write(`openapi-to-mcp v${VERSION} — sirve cualquier OpenAPI 3.x como servidor MCP
142
+
143
+ USO
144
+ openapi-to-mcp --spec <ruta|url> [opciones] Levanta un servidor MCP por stdio
145
+ openapi-to-mcp --spec <ruta|url> --list Lista las herramientas y sale
146
+ openapi-to-mcp codegen --spec <ruta|url> -o server.ts Exporta un servidor autocontenido
147
+
148
+ OPCIONES
149
+ -s, --spec <ruta|url> Especificación OpenAPI (JSON o YAML, local o remota)
150
+ --base-url <url> URL base de la API (si la spec no declara "servers")
151
+ -H, --auth-header <h> Cabecera a propagar, "Nombre: valor" (repetible)
152
+ --format <modo> Formato de respuesta: markdown (por defecto) o json
153
+ -n, --name <nombre> Nombre del servidor MCP
154
+ --list Muestra las herramientas generadas y termina
155
+ -o, --out <fichero> (codegen) Fichero de salida
156
+ -h, --help Esta ayuda
157
+ -v, --version Versión
158
+
159
+ EJEMPLO
160
+ openapi-to-mcp --spec https://api.example.com/openapi.json \\
161
+ --auth-header "Authorization: Bearer XYZ"
162
+ `);
163
+ }
164
+ void runCli(process.argv.slice(2)).catch((error) => {
165
+ const appError = toAppError(error);
166
+ process.stderr.write(`✗ ${appError.message}\n`);
167
+ process.exitCode = 1;
168
+ });
169
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Telemetría unificada: logging estructurado y métricas.
3
+ *
4
+ * Diseño pensado para el edge:
5
+ * - Sin dependencias externas: el transporte por defecto es la consola.
6
+ * - Estructurado en JSON, para que Better Stack / Axiom lo indexen tal cual.
7
+ * - Redacción automática de secretos: nunca queremos una API key en los logs.
8
+ *
9
+ * Uso típico en un Worker:
10
+ *
11
+ * const log = createLogger({ level: config.LOG_LEVEL, service: 'mi-activo' });
12
+ * log.info('petición recibida', { path: '/v1/generar' });
13
+ */
14
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
15
+ /** Campos arbitrarios que acompañan a un registro. */
16
+ export type LogContext = Readonly<Record<string, unknown>>;
17
+ /** Un registro ya estructurado, listo para enviarse al transporte. */
18
+ export interface LogRecord {
19
+ readonly timestamp: string;
20
+ readonly level: LogLevel;
21
+ readonly message: string;
22
+ readonly service: string;
23
+ readonly context: Record<string, unknown>;
24
+ }
25
+ /** Destino de los registros. Implementa uno propio para Axiom, Better Stack, etc. */
26
+ export interface Transport {
27
+ emit(record: LogRecord): void;
28
+ }
29
+ /**
30
+ * Recorre un valor y redacta los campos sensibles.
31
+ *
32
+ * Limita la profundidad para no atascarse con estructuras enormes o cíclicas
33
+ * (un log nunca debe ser el cuello de botella de una petición).
34
+ */
35
+ export declare function redact(value: unknown, depth?: number): unknown;
36
+ /** Transporte por defecto: una línea JSON por registro en la consola. */
37
+ export declare const consoleTransport: Transport;
38
+ /**
39
+ * Transporte HTTP genérico para Better Stack, Axiom o cualquier colector que
40
+ * acepte JSON por POST.
41
+ *
42
+ * Los envíos son "fire and forget": si el colector falla no queremos tumbar la
43
+ * petición del usuario. Pasa el `Promise` a `ctx.waitUntil()` en un Worker si
44
+ * necesitas garantizar la entrega antes de que muera el aislado.
45
+ */
46
+ export declare function createHttpTransport(options: {
47
+ readonly url: string;
48
+ readonly headers?: Readonly<Record<string, string>>;
49
+ readonly onFlush?: (promise: Promise<unknown>) => void;
50
+ }): Transport;
51
+ /** Emite cada registro a varios destinos a la vez. */
52
+ export declare function combineTransports(...transports: readonly Transport[]): Transport;
53
+ export interface Logger {
54
+ debug(message: string, context?: LogContext): void;
55
+ info(message: string, context?: LogContext): void;
56
+ warn(message: string, context?: LogContext): void;
57
+ error(message: string, context?: LogContext): void;
58
+ /** Deriva un logger hijo que arrastra contexto fijo (petición, usuario...). */
59
+ child(context: LogContext): Logger;
60
+ /** Registra una métrica puntual (contador, duración, etc.). */
61
+ metric(name: string, value: number, context?: LogContext): void;
62
+ }
63
+ export interface LoggerOptions {
64
+ /** Umbral mínimo: los registros por debajo se descartan. */
65
+ readonly level?: LogLevel;
66
+ /** Nombre del activo, para distinguir orígenes en el colector. */
67
+ readonly service?: string;
68
+ /** Destino de los registros. Por defecto, la consola. */
69
+ readonly transport?: Transport;
70
+ /** Contexto fijo que acompaña a todos los registros. */
71
+ readonly context?: LogContext;
72
+ /** Fuente de la marca de tiempo. Inyectable para los tests. */
73
+ readonly now?: () => number;
74
+ }
75
+ /** Crea un logger estructurado. */
76
+ export declare function createLogger(options?: LoggerOptions): Logger;
77
+ /** Logger silencioso, útil en tests que no deben ensuciar la salida. */
78
+ export declare const silentLogger: Logger;