@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAWnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAUvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAG5D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EACL,OAAO,EACP,SAAS,EACT,OAAO,EACP,cAAc,GACf,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tipos y utilidades del protocolo JSON-RPC 2.0, que es la base del transporte
|
|
3
|
+
* MCP. Se mantienen mínimos y sin dependencias: sólo lo que el servidor usa.
|
|
4
|
+
*/
|
|
5
|
+
export interface JsonRpcRequest {
|
|
6
|
+
readonly jsonrpc: '2.0';
|
|
7
|
+
readonly id: string | number;
|
|
8
|
+
readonly method: string;
|
|
9
|
+
readonly params?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface JsonRpcNotification {
|
|
12
|
+
readonly jsonrpc: '2.0';
|
|
13
|
+
readonly method: string;
|
|
14
|
+
readonly params?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export type JsonRpcIncoming = JsonRpcRequest | JsonRpcNotification;
|
|
17
|
+
export interface JsonRpcSuccess {
|
|
18
|
+
readonly jsonrpc: '2.0';
|
|
19
|
+
readonly id: string | number;
|
|
20
|
+
readonly result: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface JsonRpcErrorResponse {
|
|
23
|
+
readonly jsonrpc: '2.0';
|
|
24
|
+
readonly id: string | number | null;
|
|
25
|
+
readonly error: {
|
|
26
|
+
readonly code: number;
|
|
27
|
+
readonly message: string;
|
|
28
|
+
readonly data?: unknown;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export type JsonRpcResponse = JsonRpcSuccess | JsonRpcErrorResponse;
|
|
32
|
+
/** Códigos de error estándar de JSON-RPC 2.0. */
|
|
33
|
+
export declare const JSON_RPC_ERROR: {
|
|
34
|
+
readonly parse: -32700;
|
|
35
|
+
readonly invalidRequest: -32600;
|
|
36
|
+
readonly methodNotFound: -32601;
|
|
37
|
+
readonly invalidParams: -32602;
|
|
38
|
+
readonly internal: -32603;
|
|
39
|
+
};
|
|
40
|
+
export declare function success(id: string | number, result: unknown): JsonRpcSuccess;
|
|
41
|
+
export declare function failure(id: string | number | null, code: number, message: string, data?: unknown): JsonRpcErrorResponse;
|
|
42
|
+
/** Distingue una petición (con `id`) de una notificación (sin `id`). */
|
|
43
|
+
export declare function isRequest(message: JsonRpcIncoming): message is JsonRpcRequest;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tipos y utilidades del protocolo JSON-RPC 2.0, que es la base del transporte
|
|
3
|
+
* MCP. Se mantienen mínimos y sin dependencias: sólo lo que el servidor usa.
|
|
4
|
+
*/
|
|
5
|
+
/** Códigos de error estándar de JSON-RPC 2.0. */
|
|
6
|
+
export const JSON_RPC_ERROR = {
|
|
7
|
+
parse: -32700,
|
|
8
|
+
invalidRequest: -32600,
|
|
9
|
+
methodNotFound: -32601,
|
|
10
|
+
invalidParams: -32602,
|
|
11
|
+
internal: -32603,
|
|
12
|
+
};
|
|
13
|
+
export function success(id, result) {
|
|
14
|
+
return { jsonrpc: '2.0', id, result };
|
|
15
|
+
}
|
|
16
|
+
export function failure(id, code, message, data) {
|
|
17
|
+
return {
|
|
18
|
+
jsonrpc: '2.0',
|
|
19
|
+
id,
|
|
20
|
+
error: { code, message, ...(data === undefined ? {} : { data }) },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Distingue una petición (con `id`) de una notificación (sin `id`). */
|
|
24
|
+
export function isRequest(message) {
|
|
25
|
+
return 'id' in message && message.id !== undefined;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=jsonrpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonrpc.js","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+BH,iDAAiD;AACjD,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,CAAC,KAAK;IACb,cAAc,EAAE,CAAC,KAAK;IACtB,cAAc,EAAE,CAAC,KAAK;IACtB,aAAa,EAAE,CAAC,KAAK;IACrB,QAAQ,EAAE,CAAC,KAAK;CACR,CAAC;AAEX,MAAM,UAAU,OAAO,CAAC,EAAmB,EAAE,MAAe;IAC1D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,EAA0B,EAC1B,IAAY,EACZ,OAAe,EACf,IAAc;IAEd,OAAO;QACL,OAAO,EAAE,KAAK;QACd,EAAE;QACF,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;KAClE,CAAC;AACJ,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,OAAwB;IAChD,OAAO,IAAI,IAAI,OAAO,IAAK,OAA0B,CAAC,EAAE,KAAK,SAAS,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Logger } from '../core/telemetry/index.js';
|
|
2
|
+
import type { FetchLike } from '../http/client.js';
|
|
3
|
+
import type { FormatMode } from '../http/format.js';
|
|
4
|
+
import type { ToolDefinition } from '../generator/operations.js';
|
|
5
|
+
import type { JsonRpcIncoming, JsonRpcResponse } from './jsonrpc.js';
|
|
6
|
+
/** Versión del protocolo MCP que anunciamos en `initialize`. */
|
|
7
|
+
export declare const PROTOCOL_VERSION = "2024-11-05";
|
|
8
|
+
export interface McpServerOptions {
|
|
9
|
+
readonly tools: readonly ToolDefinition[];
|
|
10
|
+
readonly baseUrl: string;
|
|
11
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
12
|
+
readonly fetchFn: FetchLike;
|
|
13
|
+
readonly formatMode?: FormatMode;
|
|
14
|
+
readonly serverInfo?: {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly version: string;
|
|
17
|
+
};
|
|
18
|
+
readonly logger?: Logger;
|
|
19
|
+
}
|
|
20
|
+
export interface McpServer {
|
|
21
|
+
/** Procesa un mensaje entrante y devuelve la respuesta, o `null` si era una notificación. */
|
|
22
|
+
handle(message: JsonRpcIncoming): Promise<JsonRpcResponse | null>;
|
|
23
|
+
readonly tools: readonly ToolDefinition[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Crea el servidor MCP a partir de las herramientas generadas. Es agnóstico del
|
|
27
|
+
* transporte: recibe mensajes ya interpretados y devuelve respuestas, de modo
|
|
28
|
+
* que puede probarse sin tocar stdin/stdout ni la red.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createMcpServer(options: McpServerOptions): McpServer;
|
|
31
|
+
/** Logger de consola para el modo servidor (los logs van a stderr en la CLI). */
|
|
32
|
+
export declare function defaultServerLogger(): Logger;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createLogger, silentLogger } from '../core/telemetry/index.js';
|
|
3
|
+
import { executeTool } from '../http/client.js';
|
|
4
|
+
import { formatResult } from '../http/format.js';
|
|
5
|
+
import { failure, isRequest, JSON_RPC_ERROR, success, } from './jsonrpc.js';
|
|
6
|
+
/** Versión del protocolo MCP que anunciamos en `initialize`. */
|
|
7
|
+
export const PROTOCOL_VERSION = '2024-11-05';
|
|
8
|
+
const callParamsSchema = z.object({
|
|
9
|
+
name: z.string(),
|
|
10
|
+
arguments: z.record(z.unknown()).optional(),
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Crea el servidor MCP a partir de las herramientas generadas. Es agnóstico del
|
|
14
|
+
* transporte: recibe mensajes ya interpretados y devuelve respuestas, de modo
|
|
15
|
+
* que puede probarse sin tocar stdin/stdout ni la red.
|
|
16
|
+
*/
|
|
17
|
+
export function createMcpServer(options) {
|
|
18
|
+
const logger = options.logger ?? silentLogger;
|
|
19
|
+
const byName = new Map(options.tools.map((tool) => [tool.name, tool]));
|
|
20
|
+
const serverInfo = options.serverInfo ?? { name: 'openapi-to-mcp', version: '0.1.0' };
|
|
21
|
+
async function handle(message) {
|
|
22
|
+
if (!isRequest(message)) {
|
|
23
|
+
// Notificaciones (p. ej. `notifications/initialized`): sin respuesta.
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const { id, method } = message;
|
|
27
|
+
switch (method) {
|
|
28
|
+
case 'initialize':
|
|
29
|
+
return success(id, {
|
|
30
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
31
|
+
capabilities: { tools: {} },
|
|
32
|
+
serverInfo,
|
|
33
|
+
});
|
|
34
|
+
case 'ping':
|
|
35
|
+
return success(id, {});
|
|
36
|
+
case 'tools/list':
|
|
37
|
+
return success(id, {
|
|
38
|
+
tools: options.tools.map((tool) => ({
|
|
39
|
+
name: tool.name,
|
|
40
|
+
description: tool.description,
|
|
41
|
+
inputSchema: tool.inputSchema,
|
|
42
|
+
})),
|
|
43
|
+
});
|
|
44
|
+
case 'tools/call':
|
|
45
|
+
return handleToolCall(id, message.params);
|
|
46
|
+
default:
|
|
47
|
+
return failure(id, JSON_RPC_ERROR.methodNotFound, `Método no soportado: ${method}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function handleToolCall(id, params) {
|
|
51
|
+
const parsedParams = callParamsSchema.safeParse(params);
|
|
52
|
+
if (!parsedParams.success) {
|
|
53
|
+
return failure(id, JSON_RPC_ERROR.invalidParams, 'Parámetros de tools/call inválidos.');
|
|
54
|
+
}
|
|
55
|
+
const tool = byName.get(parsedParams.data.name);
|
|
56
|
+
if (tool === undefined) {
|
|
57
|
+
return failure(id, JSON_RPC_ERROR.invalidParams, `Herramienta desconocida: "${parsedParams.data.name}".`);
|
|
58
|
+
}
|
|
59
|
+
const args = parsedParams.data.arguments ?? {};
|
|
60
|
+
const validated = tool.input.safeParse(args);
|
|
61
|
+
if (!validated.success) {
|
|
62
|
+
// Se devuelve como resultado de herramienta (isError), no como error de
|
|
63
|
+
// protocolo, para que el modelo lea el motivo y reintente corrigiendo.
|
|
64
|
+
return success(id, toolError(`Argumentos inválidos: ${formatZodError(validated.error)}`));
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const result = await executeTool(tool, validated.data, {
|
|
68
|
+
baseUrl: options.baseUrl,
|
|
69
|
+
...(options.headers === undefined ? {} : { headers: options.headers }),
|
|
70
|
+
fetchFn: options.fetchFn,
|
|
71
|
+
});
|
|
72
|
+
logger.info('tools/call', { tool: tool.name, status: result.status });
|
|
73
|
+
const text = formatResult(result, options.formatMode ?? 'markdown');
|
|
74
|
+
return success(id, {
|
|
75
|
+
content: [{ type: 'text', text }],
|
|
76
|
+
isError: !result.ok,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (cause) {
|
|
80
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
81
|
+
logger.error('fallo al ejecutar la herramienta', { tool: tool.name, error: message });
|
|
82
|
+
return success(id, toolError(`Error al llamar a la API: ${message}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { handle, tools: options.tools };
|
|
86
|
+
}
|
|
87
|
+
function toolError(text) {
|
|
88
|
+
return { content: [{ type: 'text', text }], isError: true };
|
|
89
|
+
}
|
|
90
|
+
function formatZodError(error) {
|
|
91
|
+
return error.issues
|
|
92
|
+
.map((issue) => `${issue.path.join('.') || '(raíz)'}: ${issue.message}`)
|
|
93
|
+
.join('; ');
|
|
94
|
+
}
|
|
95
|
+
/** Logger de consola para el modo servidor (los logs van a stderr en la CLI). */
|
|
96
|
+
export function defaultServerLogger() {
|
|
97
|
+
return createLogger({ service: 'openapi-to-mcp', level: 'info' });
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { McpServer } from './server.js';
|
|
2
|
+
export interface StdioOptions {
|
|
3
|
+
readonly input?: NodeJS.ReadableStream;
|
|
4
|
+
readonly output?: NodeJS.WritableStream;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Conecta un servidor MCP al transporte stdio: mensajes JSON-RPC delimitados por
|
|
8
|
+
* saltos de línea, uno por línea, tal como define el transporte stdio de MCP.
|
|
9
|
+
*/
|
|
10
|
+
export declare function startStdioServer(server: McpServer, options?: StdioOptions): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { failure, JSON_RPC_ERROR } from './jsonrpc.js';
|
|
2
|
+
/**
|
|
3
|
+
* Conecta un servidor MCP al transporte stdio: mensajes JSON-RPC delimitados por
|
|
4
|
+
* saltos de línea, uno por línea, tal como define el transporte stdio de MCP.
|
|
5
|
+
*/
|
|
6
|
+
export function startStdioServer(server, options = {}) {
|
|
7
|
+
const input = options.input ?? process.stdin;
|
|
8
|
+
const output = options.output ?? process.stdout;
|
|
9
|
+
let buffer = '';
|
|
10
|
+
input.setEncoding('utf8');
|
|
11
|
+
input.on('data', (chunk) => {
|
|
12
|
+
buffer += chunk;
|
|
13
|
+
let newlineIndex = buffer.indexOf('\n');
|
|
14
|
+
while (newlineIndex !== -1) {
|
|
15
|
+
const line = buffer.slice(0, newlineIndex).trim();
|
|
16
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
17
|
+
if (line !== '')
|
|
18
|
+
void processLine(line);
|
|
19
|
+
newlineIndex = buffer.indexOf('\n');
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
async function processLine(line) {
|
|
23
|
+
let message;
|
|
24
|
+
try {
|
|
25
|
+
message = JSON.parse(line);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
write(failure(null, JSON_RPC_ERROR.parse, 'Mensaje JSON-RPC no interpretable.'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const response = await server.handle(message);
|
|
33
|
+
if (response !== null)
|
|
34
|
+
write(response);
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
const id = readId(message);
|
|
38
|
+
write(failure(id, JSON_RPC_ERROR.internal, cause instanceof Error ? cause.message : 'Error interno.'));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function write(value) {
|
|
42
|
+
output.write(`${JSON.stringify(value)}\n`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function readId(message) {
|
|
46
|
+
const id = message.id;
|
|
47
|
+
return typeof id === 'string' || typeof id === 'number' ? id : null;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/mcp/stdio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AASvD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,UAAwB,EAAE;IAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAChD,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;QACjC,MAAM,IAAI,KAAK,CAAC;QAChB,IAAI,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;YAClD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC;YACxC,IAAI,IAAI,KAAK,EAAE;gBAAE,KAAK,WAAW,CAAC,IAAI,CAAC,CAAC;YACxC,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,KAAK,UAAU,WAAW,CAAC,IAAY;QACrC,IAAI,OAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,KAAK,EAAE,oCAAoC,CAAC,CAAC,CAAC;YACjF,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,QAAQ,KAAK,IAAI;gBAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;YAC3B,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,cAAc,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACzG,CAAC;IACH,CAAC;IAED,SAAS,KAAK,CAAC,KAAc;QAC3B,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,OAAwB;IACtC,MAAM,EAAE,GAAI,OAA4B,CAAC,EAAE,CAAC;IAC5C,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FetchLike } from '../http/client.js';
|
|
2
|
+
import type { OpenApiDocument } from './types.js';
|
|
3
|
+
export interface LoadOptions {
|
|
4
|
+
/** Cliente HTTP para especificaciones remotas. Inyectable para los tests. */
|
|
5
|
+
readonly fetchFn?: FetchLike;
|
|
6
|
+
/** Lector de ficheros. Inyectable para los tests. */
|
|
7
|
+
readonly readFileFn?: (path: string) => Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Carga una especificación OpenAPI desde una ruta local o una URL, en JSON o
|
|
11
|
+
* YAML, y la devuelve como documento tras validar que es OpenAPI 3.x.
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadSpec(source: string, options?: LoadOptions): Promise<OpenApiDocument>;
|
|
14
|
+
/** Interpreta el contenido de una especificación (JSON o YAML) y lo valida. */
|
|
15
|
+
export declare function parseSpecContent(text: string, source?: string): OpenApiDocument;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
import { badRequest } from '../utils/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Carga una especificación OpenAPI desde una ruta local o una URL, en JSON o
|
|
6
|
+
* YAML, y la devuelve como documento tras validar que es OpenAPI 3.x.
|
|
7
|
+
*/
|
|
8
|
+
export async function loadSpec(source, options = {}) {
|
|
9
|
+
const text = await readSource(source, options);
|
|
10
|
+
return parseSpecContent(text, source);
|
|
11
|
+
}
|
|
12
|
+
/** Interpreta el contenido de una especificación (JSON o YAML) y lo valida. */
|
|
13
|
+
export function parseSpecContent(text, source = '<inline>') {
|
|
14
|
+
const trimmed = text.trimStart();
|
|
15
|
+
const looksLikeJson = trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
16
|
+
let parsed;
|
|
17
|
+
try {
|
|
18
|
+
parsed = looksLikeJson ? JSON.parse(text) : parseYaml(text);
|
|
19
|
+
}
|
|
20
|
+
catch (cause) {
|
|
21
|
+
throw badRequest(`No se ha podido interpretar la especificación de "${source}": no es JSON ni YAML válido.`, cause instanceof Error ? cause.message : undefined);
|
|
22
|
+
}
|
|
23
|
+
return assertOpenApiDocument(parsed, source);
|
|
24
|
+
}
|
|
25
|
+
/** Valida la forma mínima de un documento OpenAPI 3.x. */
|
|
26
|
+
function assertOpenApiDocument(value, source) {
|
|
27
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
28
|
+
throw badRequest(`La especificación de "${source}" no es un objeto OpenAPI.`);
|
|
29
|
+
}
|
|
30
|
+
const doc = value;
|
|
31
|
+
const version = doc['openapi'];
|
|
32
|
+
if (typeof version !== 'string' || !version.startsWith('3.')) {
|
|
33
|
+
throw badRequest(`Sólo se admite OpenAPI 3.x. La especificación de "${source}" declara openapi="${String(version)}".`);
|
|
34
|
+
}
|
|
35
|
+
if (doc['paths'] !== undefined && (typeof doc['paths'] !== 'object' || doc['paths'] === null)) {
|
|
36
|
+
throw badRequest(`El campo "paths" de "${source}" no es un objeto.`);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
/** Obtiene el texto de la especificación desde el origen indicado. */
|
|
41
|
+
async function readSource(source, options) {
|
|
42
|
+
if (/^https?:\/\//i.test(source)) {
|
|
43
|
+
const fetchFn = options.fetchFn ?? defaultFetch;
|
|
44
|
+
const response = await fetchFn(source, { method: 'GET' });
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw badRequest(`No se ha podido descargar la especificación de "${source}": HTTP ${response.status}.`);
|
|
47
|
+
}
|
|
48
|
+
return response.text();
|
|
49
|
+
}
|
|
50
|
+
const readFileFn = options.readFileFn ?? ((path) => readFile(path, 'utf8'));
|
|
51
|
+
try {
|
|
52
|
+
return await readFileFn(source);
|
|
53
|
+
}
|
|
54
|
+
catch (cause) {
|
|
55
|
+
throw badRequest(`No se ha podido leer la especificación en "${source}".`, cause instanceof Error ? cause.message : undefined);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Envoltorio del `fetch` global con la forma desacoplada `FetchLike`. */
|
|
59
|
+
const defaultFetch = (url, init) => fetch(url, init);
|
|
60
|
+
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/openapi/load.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OpenApiDocument, SchemaObject } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resuelve referencias locales `$ref` (`#/components/schemas/...`) contra el
|
|
4
|
+
* documento raíz.
|
|
5
|
+
*
|
|
6
|
+
* Sólo se soportan referencias internas: las externas (a otros ficheros o URLs)
|
|
7
|
+
* quedan fuera del alcance de esta versión y provocan un error explícito, en vez
|
|
8
|
+
* de un fallo silencioso más difícil de diagnosticar.
|
|
9
|
+
*/
|
|
10
|
+
export declare class RefResolver {
|
|
11
|
+
private readonly root;
|
|
12
|
+
constructor(root: OpenApiDocument);
|
|
13
|
+
/** Devuelve el nodo apuntado por un `$ref`, o lanza si no resuelve. */
|
|
14
|
+
resolve(ref: string): SchemaObject;
|
|
15
|
+
/**
|
|
16
|
+
* Sigue la cadena de `$ref` hasta llegar a un schema concreto. Un ciclo de
|
|
17
|
+
* referencias (poco habitual, pero posible) se corta lanzando un error.
|
|
18
|
+
*/
|
|
19
|
+
deref(schema: SchemaObject): SchemaObject;
|
|
20
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { badRequest } from '../utils/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resuelve referencias locales `$ref` (`#/components/schemas/...`) contra el
|
|
4
|
+
* documento raíz.
|
|
5
|
+
*
|
|
6
|
+
* Sólo se soportan referencias internas: las externas (a otros ficheros o URLs)
|
|
7
|
+
* quedan fuera del alcance de esta versión y provocan un error explícito, en vez
|
|
8
|
+
* de un fallo silencioso más difícil de diagnosticar.
|
|
9
|
+
*/
|
|
10
|
+
export class RefResolver {
|
|
11
|
+
root;
|
|
12
|
+
constructor(root) {
|
|
13
|
+
this.root = root;
|
|
14
|
+
}
|
|
15
|
+
/** Devuelve el nodo apuntado por un `$ref`, o lanza si no resuelve. */
|
|
16
|
+
resolve(ref) {
|
|
17
|
+
if (!ref.startsWith('#/')) {
|
|
18
|
+
throw badRequest(`Referencia $ref no soportada: "${ref}". Sólo se admiten referencias locales que empiezan por "#/".`);
|
|
19
|
+
}
|
|
20
|
+
const segments = ref
|
|
21
|
+
.slice(2)
|
|
22
|
+
.split('/')
|
|
23
|
+
.map(decodePointerSegment);
|
|
24
|
+
let node = this.root;
|
|
25
|
+
for (const segment of segments) {
|
|
26
|
+
if (node === null || typeof node !== 'object') {
|
|
27
|
+
throw badRequest(`La referencia $ref "${ref}" no resuelve a ningún nodo.`);
|
|
28
|
+
}
|
|
29
|
+
node = node[segment];
|
|
30
|
+
}
|
|
31
|
+
if (node === null || typeof node !== 'object') {
|
|
32
|
+
throw badRequest(`La referencia $ref "${ref}" no apunta a un objeto Schema.`);
|
|
33
|
+
}
|
|
34
|
+
return node;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sigue la cadena de `$ref` hasta llegar a un schema concreto. Un ciclo de
|
|
38
|
+
* referencias (poco habitual, pero posible) se corta lanzando un error.
|
|
39
|
+
*/
|
|
40
|
+
deref(schema) {
|
|
41
|
+
const visited = new Set();
|
|
42
|
+
let current = schema;
|
|
43
|
+
while (current.$ref !== undefined) {
|
|
44
|
+
if (visited.has(current.$ref)) {
|
|
45
|
+
throw badRequest(`Ciclo de referencias $ref detectado en "${current.$ref}".`);
|
|
46
|
+
}
|
|
47
|
+
visited.add(current.$ref);
|
|
48
|
+
current = this.resolve(current.$ref);
|
|
49
|
+
}
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Decodifica el escape de JSON Pointer: `~1` → `/`, `~0` → `~`. */
|
|
54
|
+
function decodePointerSegment(segment) {
|
|
55
|
+
return segment.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/openapi/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD;;;;;;;GAOG;AACH,MAAM,OAAO,WAAW;IACO;IAA7B,YAA6B,IAAqB;QAArB,SAAI,GAAJ,IAAI,CAAiB;IAAG,CAAC;IAEtD,uEAAuE;IACvE,OAAO,CAAC,GAAW;QACjB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,UAAU,CACd,kCAAkC,GAAG,+DAA+D,CACrG,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,GAAG;aACjB,KAAK,CAAC,CAAC,CAAC;aACR,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAE7B,IAAI,IAAI,GAAY,IAAI,CAAC,IAAI,CAAC;QAC9B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC9C,MAAM,UAAU,CAAC,uBAAuB,GAAG,8BAA8B,CAAC,CAAC;YAC7E,CAAC;YACD,IAAI,GAAI,IAAgC,CAAC,OAAO,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9C,MAAM,UAAU,CAAC,uBAAuB,GAAG,iCAAiC,CAAC,CAAC;QAChF,CAAC;QAED,OAAO,IAAoB,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAoB;QACxB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,IAAI,OAAO,GAAG,MAAM,CAAC;QACrB,OAAO,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9B,MAAM,UAAU,CAAC,2CAA2C,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;YAChF,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1B,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AAED,oEAAoE;AACpE,SAAS,oBAAoB,CAAC,OAAe;IAC3C,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modelo interno mínimo de OpenAPI 3.0 / 3.1.
|
|
3
|
+
*
|
|
4
|
+
* No pretende cubrir la especificación entera: sólo las piezas que el generador
|
|
5
|
+
* consume para producir herramientas MCP. Todos los campos son opcionales porque
|
|
6
|
+
* las especificaciones del mundo real son irregulares; la validación estricta se
|
|
7
|
+
* hace en la frontera de entrada (`load.ts`) y al construir cada herramienta.
|
|
8
|
+
*/
|
|
9
|
+
/** Objeto Schema de OpenAPI (superconjunto práctico de JSON Schema). */
|
|
10
|
+
export interface SchemaObject {
|
|
11
|
+
$ref?: string;
|
|
12
|
+
type?: string | readonly string[];
|
|
13
|
+
format?: string;
|
|
14
|
+
enum?: readonly unknown[];
|
|
15
|
+
const?: unknown;
|
|
16
|
+
/** OpenAPI 3.0: marca el valor como anulable. En 3.1 se usa `type: [..,'null']`. */
|
|
17
|
+
nullable?: boolean;
|
|
18
|
+
items?: SchemaObject;
|
|
19
|
+
properties?: Readonly<Record<string, SchemaObject>>;
|
|
20
|
+
required?: readonly string[];
|
|
21
|
+
additionalProperties?: boolean | SchemaObject;
|
|
22
|
+
allOf?: readonly SchemaObject[];
|
|
23
|
+
oneOf?: readonly SchemaObject[];
|
|
24
|
+
anyOf?: readonly SchemaObject[];
|
|
25
|
+
description?: string;
|
|
26
|
+
default?: unknown;
|
|
27
|
+
minimum?: number;
|
|
28
|
+
maximum?: number;
|
|
29
|
+
minLength?: number;
|
|
30
|
+
maxLength?: number;
|
|
31
|
+
minItems?: number;
|
|
32
|
+
maxItems?: number;
|
|
33
|
+
pattern?: string;
|
|
34
|
+
example?: unknown;
|
|
35
|
+
}
|
|
36
|
+
/** Objeto Parameter de OpenAPI. */
|
|
37
|
+
export interface ParameterObject {
|
|
38
|
+
$ref?: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
in?: 'path' | 'query' | 'header' | 'cookie';
|
|
41
|
+
required?: boolean;
|
|
42
|
+
description?: string;
|
|
43
|
+
schema?: SchemaObject;
|
|
44
|
+
}
|
|
45
|
+
/** Cuerpo de la petición. */
|
|
46
|
+
export interface RequestBodyObject {
|
|
47
|
+
required?: boolean;
|
|
48
|
+
description?: string;
|
|
49
|
+
content?: Readonly<Record<string, {
|
|
50
|
+
schema?: SchemaObject;
|
|
51
|
+
}>>;
|
|
52
|
+
}
|
|
53
|
+
/** Una operación (método) sobre una ruta. */
|
|
54
|
+
export interface OperationObject {
|
|
55
|
+
operationId?: string;
|
|
56
|
+
summary?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
tags?: readonly string[];
|
|
59
|
+
parameters?: readonly ParameterObject[];
|
|
60
|
+
requestBody?: RequestBodyObject;
|
|
61
|
+
deprecated?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Un elemento de `paths`: agrupa las operaciones por método. */
|
|
64
|
+
export interface PathItemObject {
|
|
65
|
+
parameters?: readonly ParameterObject[];
|
|
66
|
+
get?: OperationObject;
|
|
67
|
+
put?: OperationObject;
|
|
68
|
+
post?: OperationObject;
|
|
69
|
+
delete?: OperationObject;
|
|
70
|
+
patch?: OperationObject;
|
|
71
|
+
head?: OperationObject;
|
|
72
|
+
options?: OperationObject;
|
|
73
|
+
trace?: OperationObject;
|
|
74
|
+
}
|
|
75
|
+
/** Servidor declarado en la especificación. */
|
|
76
|
+
export interface ServerObject {
|
|
77
|
+
url?: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
}
|
|
80
|
+
/** Documento OpenAPI completo (lo que consumimos de él). */
|
|
81
|
+
export interface OpenApiDocument {
|
|
82
|
+
openapi?: string;
|
|
83
|
+
info?: {
|
|
84
|
+
title?: string;
|
|
85
|
+
version?: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
};
|
|
88
|
+
servers?: readonly ServerObject[];
|
|
89
|
+
paths?: Readonly<Record<string, PathItemObject>>;
|
|
90
|
+
components?: Readonly<Record<string, unknown>>;
|
|
91
|
+
}
|
|
92
|
+
/** Los verbos HTTP que se convierten en herramientas. */
|
|
93
|
+
export declare const HTTP_METHODS: readonly ["get", "put", "post", "delete", "patch", "head", "options", "trace"];
|
|
94
|
+
export type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modelo interno mínimo de OpenAPI 3.0 / 3.1.
|
|
3
|
+
*
|
|
4
|
+
* No pretende cubrir la especificación entera: sólo las piezas que el generador
|
|
5
|
+
* consume para producir herramientas MCP. Todos los campos son opcionales porque
|
|
6
|
+
* las especificaciones del mundo real son irregulares; la validación estricta se
|
|
7
|
+
* hace en la frontera de entrada (`load.ts`) y al construir cada herramienta.
|
|
8
|
+
*/
|
|
9
|
+
/** Los verbos HTTP que se convierten en herramientas. */
|
|
10
|
+
export const HTTP_METHODS = [
|
|
11
|
+
'get',
|
|
12
|
+
'put',
|
|
13
|
+
'post',
|
|
14
|
+
'delete',
|
|
15
|
+
'patch',
|
|
16
|
+
'head',
|
|
17
|
+
'options',
|
|
18
|
+
'trace',
|
|
19
|
+
];
|
|
20
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/openapi/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsFH,yDAAyD;AACzD,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK;IACL,KAAK;IACL,MAAM;IACN,QAAQ;IACR,OAAO;IACP,MAAM;IACN,SAAS;IACT,OAAO;CACC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RefResolver } from '../openapi/resolve.js';
|
|
2
|
+
import type { SchemaObject } from '../openapi/types.js';
|
|
3
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
4
|
+
[key: string]: JsonValue;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Devuelve una copia del schema con todas las referencias `$ref` locales ya
|
|
8
|
+
* resueltas, apta para exponer como `inputSchema` de una herramienta MCP (JSON
|
|
9
|
+
* Schema plano, sin `$ref` que el cliente tendría que resolver por su cuenta).
|
|
10
|
+
*
|
|
11
|
+
* Los ciclos de referencias se cortan sustituyendo la rama por `{}` (equivalente
|
|
12
|
+
* a "cualquier valor"), en vez de expandir infinitamente.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toJsonSchema(schema: SchemaObject, resolver: RefResolver): JsonValue;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const MAX_DEPTH = 40;
|
|
2
|
+
/**
|
|
3
|
+
* Devuelve una copia del schema con todas las referencias `$ref` locales ya
|
|
4
|
+
* resueltas, apta para exponer como `inputSchema` de una herramienta MCP (JSON
|
|
5
|
+
* Schema plano, sin `$ref` que el cliente tendría que resolver por su cuenta).
|
|
6
|
+
*
|
|
7
|
+
* Los ciclos de referencias se cortan sustituyendo la rama por `{}` (equivalente
|
|
8
|
+
* a "cualquier valor"), en vez de expandir infinitamente.
|
|
9
|
+
*/
|
|
10
|
+
export function toJsonSchema(schema, resolver) {
|
|
11
|
+
return deref(schema, resolver, new Set(), 0);
|
|
12
|
+
}
|
|
13
|
+
function deref(node, resolver, path, depth) {
|
|
14
|
+
if (depth > MAX_DEPTH)
|
|
15
|
+
return {};
|
|
16
|
+
if (Array.isArray(node)) {
|
|
17
|
+
return node.map((item) => deref(item, resolver, path, depth + 1));
|
|
18
|
+
}
|
|
19
|
+
if (node !== null && typeof node === 'object') {
|
|
20
|
+
const obj = node;
|
|
21
|
+
const ref = obj['$ref'];
|
|
22
|
+
if (typeof ref === 'string') {
|
|
23
|
+
if (path.has(ref))
|
|
24
|
+
return {};
|
|
25
|
+
const nextPath = new Set(path);
|
|
26
|
+
nextPath.add(ref);
|
|
27
|
+
return deref(resolver.resolve(ref), resolver, nextPath, depth + 1);
|
|
28
|
+
}
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
31
|
+
if (value === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
out[key] = deref(value, resolver, path, depth + 1);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
if (node === undefined)
|
|
38
|
+
return null;
|
|
39
|
+
return node;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=to-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-json.js","sourceRoot":"","sources":["../../src/schema/to-json.ts"],"names":[],"mappings":"AAWA,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB,EAAE,QAAqB;IACtE,OAAO,KAAK,CAAC,MAAiB,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,KAAK,CAAC,IAAa,EAAE,QAAqB,EAAE,IAAyB,EAAE,KAAa;IAC3F,IAAI,KAAK,GAAG,SAAS;QAAE,OAAO,EAAE,CAAC;IAEjC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,IAA+B,CAAC;QAE5C,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;QACxB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;YAC/B,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,GAAG,GAA8B,EAAE,CAAC;QAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,IAAI,KAAK,KAAK,SAAS;gBAAE,SAAS;YAClC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,IAAiB,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { RefResolver } from '../openapi/resolve.js';
|
|
3
|
+
import type { SchemaObject } from '../openapi/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Convierte un Objeto Schema de OpenAPI en un validador Zod para comprobar los
|
|
6
|
+
* argumentos entrantes de una herramienta antes de tocar la API subyacente.
|
|
7
|
+
*/
|
|
8
|
+
export declare function schemaToZod(schema: SchemaObject, resolver: RefResolver, depth?: number): z.ZodTypeAny;
|