@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.
- package/LICENSE +21 -0
- package/README.es.md +130 -0
- package/README.md +222 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +169 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/telemetry/index.d.ts +78 -0
- package/dist/core/telemetry/index.js +152 -0
- package/dist/core/telemetry/index.js.map +1 -0
- package/dist/generator/codegen.d.ts +15 -0
- package/dist/generator/codegen.js +33 -0
- package/dist/generator/codegen.js.map +1 -0
- package/dist/generator/operations.d.ts +47 -0
- package/dist/generator/operations.js +143 -0
- package/dist/generator/operations.js.map +1 -0
- package/dist/http/client.d.ts +47 -0
- package/dist/http/client.js +92 -0
- package/dist/http/client.js.map +1 -0
- package/dist/http/format.d.ts +10 -0
- package/dist/http/format.js +22 -0
- package/dist/http/format.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/jsonrpc.d.ts +43 -0
- package/dist/mcp/jsonrpc.js +27 -0
- package/dist/mcp/jsonrpc.js.map +1 -0
- package/dist/mcp/server.d.ts +32 -0
- package/dist/mcp/server.js +99 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +10 -0
- package/dist/mcp/stdio.js +49 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/openapi/load.d.ts +15 -0
- package/dist/openapi/load.js +60 -0
- package/dist/openapi/load.js.map +1 -0
- package/dist/openapi/resolve.d.ts +20 -0
- package/dist/openapi/resolve.js +57 -0
- package/dist/openapi/resolve.js.map +1 -0
- package/dist/openapi/types.d.ts +94 -0
- package/dist/openapi/types.js +20 -0
- package/dist/openapi/types.js.map +1 -0
- package/dist/schema/to-json.d.ts +14 -0
- package/dist/schema/to-json.js +41 -0
- package/dist/schema/to-json.js.map +1 -0
- package/dist/schema/to-zod.d.ts +8 -0
- package/dist/schema/to-zod.js +131 -0
- package/dist/schema/to-zod.js.map +1 -0
- package/dist/utils/errors.d.ts +47 -0
- package/dist/utils/errors.js +81 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/http.d.ts +46 -0
- package/dist/utils/http.js +70 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/time.d.ts +27 -0
- package/dist/utils/time.js +32 -0
- package/dist/utils/time.js.map +1 -0
- 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
|
+
[](https://github.com/Karlangas12/openapi-to-mcp/actions/workflows/ci.yml)
|
|
11
|
+
[](https://www.npmjs.com/package/@karlangas12/openapi-to-mcp)
|
|
12
|
+
[](#pruebas)
|
|
13
|
+
[](https://nodejs.org)
|
|
14
|
+
[](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
|
+
[](https://github.com/Karlangas12/openapi-to-mcp/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/@karlangas12/openapi-to-mcp)
|
|
11
|
+
[](#testing)
|
|
12
|
+
[](https://nodejs.org)
|
|
13
|
+
[](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
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
|
package/dist/cli.js.map
ADDED
|
@@ -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;
|