@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
@@ -0,0 +1,131 @@
1
+ import { z } from 'zod';
2
+ /** Profundidad máxima de recursión, como red de seguridad ante schemas cíclicos. */
3
+ const MAX_DEPTH = 40;
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 function schemaToZod(schema, resolver, depth = 0) {
9
+ if (depth > MAX_DEPTH)
10
+ return z.any();
11
+ if (schema.$ref !== undefined) {
12
+ return schemaToZod(resolver.resolve(schema.$ref), resolver, depth + 1);
13
+ }
14
+ const nullableByType = Array.isArray(schema.type) && schema.type.includes('null');
15
+ const isNullable = schema.nullable === true || nullableByType;
16
+ let zod = buildBase(schema, resolver, depth);
17
+ if (isNullable)
18
+ zod = zod.nullable();
19
+ if (schema.description !== undefined)
20
+ zod = zod.describe(schema.description);
21
+ return zod;
22
+ }
23
+ function buildBase(schema, resolver, depth) {
24
+ // Combinadores primero: mandan sobre `type`.
25
+ if (schema.enum !== undefined && schema.enum.length > 0) {
26
+ return literalUnion(schema.enum);
27
+ }
28
+ if (schema.allOf !== undefined && schema.allOf.length > 0) {
29
+ return schema.allOf
30
+ .map((sub) => schemaToZod(sub, resolver, depth + 1))
31
+ .reduce((acc, next) => z.intersection(acc, next));
32
+ }
33
+ if (schema.oneOf !== undefined && schema.oneOf.length > 0) {
34
+ return unionOf(schema.oneOf.map((sub) => schemaToZod(sub, resolver, depth + 1)));
35
+ }
36
+ if (schema.anyOf !== undefined && schema.anyOf.length > 0) {
37
+ return unionOf(schema.anyOf.map((sub) => schemaToZod(sub, resolver, depth + 1)));
38
+ }
39
+ const type = normalizeType(schema.type);
40
+ switch (type) {
41
+ case 'string':
42
+ return buildString(schema);
43
+ case 'integer':
44
+ return applyNumberBounds(z.number().int(), schema);
45
+ case 'number':
46
+ return applyNumberBounds(z.number(), schema);
47
+ case 'boolean':
48
+ return z.boolean();
49
+ case 'array': {
50
+ const items = schema.items ? schemaToZod(schema.items, resolver, depth + 1) : z.any();
51
+ return applyArrayBounds(z.array(items), schema);
52
+ }
53
+ case 'object':
54
+ return buildObject(schema, resolver, depth);
55
+ default:
56
+ // Sin `type` pero con `properties`: se trata como objeto.
57
+ if (schema.properties !== undefined)
58
+ return buildObject(schema, resolver, depth);
59
+ return z.any();
60
+ }
61
+ }
62
+ function buildString(schema) {
63
+ let str = z.string();
64
+ if (typeof schema.minLength === 'number')
65
+ str = str.min(schema.minLength);
66
+ if (typeof schema.maxLength === 'number')
67
+ str = str.max(schema.maxLength);
68
+ switch (schema.format) {
69
+ case 'email':
70
+ return str.email();
71
+ case 'uri':
72
+ case 'url':
73
+ return str.url();
74
+ case 'uuid':
75
+ return str.uuid();
76
+ default:
77
+ return str;
78
+ }
79
+ }
80
+ function applyNumberBounds(base, schema) {
81
+ let num = base;
82
+ if (typeof schema.minimum === 'number')
83
+ num = num.min(schema.minimum);
84
+ if (typeof schema.maximum === 'number')
85
+ num = num.max(schema.maximum);
86
+ return num;
87
+ }
88
+ function applyArrayBounds(base, schema) {
89
+ let arr = base;
90
+ if (typeof schema.minItems === 'number')
91
+ arr = arr.min(schema.minItems);
92
+ if (typeof schema.maxItems === 'number')
93
+ arr = arr.max(schema.maxItems);
94
+ return arr;
95
+ }
96
+ function buildObject(schema, resolver, depth) {
97
+ const properties = schema.properties ?? {};
98
+ const required = new Set(schema.required ?? []);
99
+ const shape = {};
100
+ for (const [key, propSchema] of Object.entries(properties)) {
101
+ const zod = schemaToZod(propSchema, resolver, depth + 1);
102
+ shape[key] = required.has(key) ? zod : zod.optional();
103
+ }
104
+ const base = z.object(shape);
105
+ // Permisivo por defecto: una API puede añadir campos y no queremos rechazar
106
+ // argumentos válidos. Sólo se cierra si el schema prohíbe extras explícitamente.
107
+ return schema.additionalProperties === false ? base.strict() : base.passthrough();
108
+ }
109
+ function normalizeType(type) {
110
+ if (type === undefined)
111
+ return undefined;
112
+ if (Array.isArray(type))
113
+ return type.find((t) => t !== 'null');
114
+ return type;
115
+ }
116
+ function literalUnion(values) {
117
+ const literals = values
118
+ .filter((v) => ['string', 'number', 'boolean'].includes(typeof v))
119
+ .map((v) => z.literal(v));
120
+ if (literals.length === 0)
121
+ return z.any();
122
+ return unionOf(literals);
123
+ }
124
+ function unionOf(members) {
125
+ if (members.length === 0)
126
+ return z.any();
127
+ if (members.length === 1)
128
+ return members[0];
129
+ return z.union(members);
130
+ }
131
+ //# sourceMappingURL=to-zod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-zod.js","sourceRoot":"","sources":["../../src/schema/to-zod.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Errores tipados de la factoría.
3
+ *
4
+ * Todo error que atraviese la frontera HTTP debe ser un `AppError`: así el
5
+ * manejador central sabe qué código devolver y qué exponer al cliente sin
6
+ * filtrar detalles internos.
7
+ */
8
+ /** Códigos de error estables que el cliente puede tratar programáticamente. */
9
+ export declare const ERROR_CODES: readonly ["bad_request", "unauthorized", "forbidden", "not_found", "conflict", "payload_too_large", "rate_limited", "internal_error", "not_implemented", "service_unavailable"];
10
+ export type ErrorCode = (typeof ERROR_CODES)[number];
11
+ export interface AppErrorOptions {
12
+ /** Detalles adicionales seguros de exponer al cliente (p. ej. errores de validación). */
13
+ readonly details?: unknown;
14
+ /** Error original que provocó este, para la traza interna. Nunca se expone. */
15
+ readonly cause?: unknown;
16
+ /** Cabeceras extra a incluir en la respuesta (p. ej. `Retry-After`). */
17
+ readonly headers?: Readonly<Record<string, string>>;
18
+ }
19
+ /**
20
+ * Error de aplicación con código estable y estado HTTP asociado.
21
+ *
22
+ * El `message` se considera seguro de mostrar al cliente; usa `cause` para
23
+ * todo lo que deba quedarse en los logs.
24
+ */
25
+ export declare class AppError extends Error {
26
+ readonly code: ErrorCode;
27
+ readonly status: number;
28
+ readonly details: unknown;
29
+ readonly headers: Readonly<Record<string, string>>;
30
+ constructor(code: ErrorCode, message: string, options?: AppErrorOptions);
31
+ /** Indica si el error es responsabilidad del cliente (4xx) o nuestra (5xx). */
32
+ get isClientError(): boolean;
33
+ }
34
+ /** Comprueba si un valor desconocido es un `AppError`. */
35
+ export declare function isAppError(value: unknown): value is AppError;
36
+ /**
37
+ * Normaliza cualquier valor lanzado a un `AppError`.
38
+ *
39
+ * Los errores inesperados se convierten en `internal_error` con un mensaje
40
+ * genérico: el detalle real viaja en `cause` para la telemetría, no al cliente.
41
+ */
42
+ export declare function toAppError(value: unknown): AppError;
43
+ export declare const badRequest: (message: string, details?: unknown) => AppError;
44
+ export declare const unauthorized: (message?: string) => AppError;
45
+ export declare const forbidden: (message?: string) => AppError;
46
+ export declare const notFound: (message?: string) => AppError;
47
+ export declare const rateLimited: (message: string, retryAfterSeconds: number) => AppError;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Errores tipados de la factoría.
3
+ *
4
+ * Todo error que atraviese la frontera HTTP debe ser un `AppError`: así el
5
+ * manejador central sabe qué código devolver y qué exponer al cliente sin
6
+ * filtrar detalles internos.
7
+ */
8
+ /** Códigos de error estables que el cliente puede tratar programáticamente. */
9
+ export const ERROR_CODES = [
10
+ 'bad_request',
11
+ 'unauthorized',
12
+ 'forbidden',
13
+ 'not_found',
14
+ 'conflict',
15
+ 'payload_too_large',
16
+ 'rate_limited',
17
+ 'internal_error',
18
+ 'not_implemented',
19
+ 'service_unavailable',
20
+ ];
21
+ /** Correspondencia entre código de error y estado HTTP. */
22
+ const HTTP_STATUS_BY_CODE = {
23
+ bad_request: 400,
24
+ unauthorized: 401,
25
+ forbidden: 403,
26
+ not_found: 404,
27
+ conflict: 409,
28
+ payload_too_large: 413,
29
+ rate_limited: 429,
30
+ internal_error: 500,
31
+ not_implemented: 501,
32
+ service_unavailable: 503,
33
+ };
34
+ /**
35
+ * Error de aplicación con código estable y estado HTTP asociado.
36
+ *
37
+ * El `message` se considera seguro de mostrar al cliente; usa `cause` para
38
+ * todo lo que deba quedarse en los logs.
39
+ */
40
+ export class AppError extends Error {
41
+ code;
42
+ status;
43
+ details;
44
+ headers;
45
+ constructor(code, message, options = {}) {
46
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
47
+ this.name = 'AppError';
48
+ this.code = code;
49
+ this.status = HTTP_STATUS_BY_CODE[code];
50
+ this.details = options.details;
51
+ this.headers = options.headers ?? {};
52
+ }
53
+ /** Indica si el error es responsabilidad del cliente (4xx) o nuestra (5xx). */
54
+ get isClientError() {
55
+ return this.status >= 400 && this.status < 500;
56
+ }
57
+ }
58
+ /** Comprueba si un valor desconocido es un `AppError`. */
59
+ export function isAppError(value) {
60
+ return value instanceof AppError;
61
+ }
62
+ /**
63
+ * Normaliza cualquier valor lanzado a un `AppError`.
64
+ *
65
+ * Los errores inesperados se convierten en `internal_error` con un mensaje
66
+ * genérico: el detalle real viaja en `cause` para la telemetría, no al cliente.
67
+ */
68
+ export function toAppError(value) {
69
+ if (isAppError(value))
70
+ return value;
71
+ return new AppError('internal_error', 'Se ha producido un error interno.', { cause: value });
72
+ }
73
+ // --- Atajos de uso frecuente -------------------------------------------------
74
+ export const badRequest = (message, details) => new AppError('bad_request', message, details === undefined ? {} : { details });
75
+ export const unauthorized = (message = 'Credenciales ausentes o no válidas.') => new AppError('unauthorized', message);
76
+ export const forbidden = (message = 'No tienes permiso para realizar esta acción.') => new AppError('forbidden', message);
77
+ export const notFound = (message = 'Recurso no encontrado.') => new AppError('not_found', message);
78
+ export const rateLimited = (message, retryAfterSeconds) => new AppError('rate_limited', message, {
79
+ headers: { 'Retry-After': String(Math.max(0, Math.ceil(retryAfterSeconds))) },
80
+ });
81
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Utilidades de respuesta HTTP.
3
+ *
4
+ * Toda la factoría habla el mismo dialecto JSON, de modo que cualquier cliente
5
+ * (o cualquier activo que consuma a otro) pueda tratar las respuestas igual:
6
+ *
7
+ * Éxito: { "ok": true, "data": ... }
8
+ * Error: { "ok": false, "error": { "code": ..., "message": ..., "details"?: ... } }
9
+ */
10
+ export interface SuccessBody<T> {
11
+ readonly ok: true;
12
+ readonly data: T;
13
+ }
14
+ export interface ErrorBody {
15
+ readonly ok: false;
16
+ readonly error: {
17
+ readonly code: string;
18
+ readonly message: string;
19
+ readonly details?: unknown;
20
+ };
21
+ }
22
+ export type ApiBody<T> = SuccessBody<T> | ErrorBody;
23
+ export interface ResponseOptions {
24
+ readonly status?: number;
25
+ readonly headers?: Readonly<Record<string, string>>;
26
+ }
27
+ /** Construye una respuesta JSON con las cabeceras correctas. */
28
+ export declare function json<T>(body: T, options?: ResponseOptions): Response;
29
+ /** Respuesta de éxito envuelta en el sobre estándar. */
30
+ export declare function ok<T>(data: T, options?: ResponseOptions): Response;
31
+ /** Respuesta 204 sin cuerpo, para operaciones que no devuelven nada. */
32
+ export declare function noContent(headers?: Readonly<Record<string, string>>): Response;
33
+ /**
34
+ * Respuesta de error a partir de cualquier valor lanzado.
35
+ *
36
+ * Los errores 5xx nunca exponen el mensaje original: se sustituye por uno
37
+ * genérico para no filtrar detalles de implementación.
38
+ */
39
+ export declare function fail(error: unknown, options?: ResponseOptions): Response;
40
+ /**
41
+ * Lee y parsea el cuerpo JSON de una petición.
42
+ *
43
+ * Lanza `bad_request` si el cuerpo no es JSON válido, en lugar de dejar
44
+ * escapar un `SyntaxError` crudo hasta el manejador de errores.
45
+ */
46
+ export declare function readJson(request: Request): Promise<unknown>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Utilidades de respuesta HTTP.
3
+ *
4
+ * Toda la factoría habla el mismo dialecto JSON, de modo que cualquier cliente
5
+ * (o cualquier activo que consuma a otro) pueda tratar las respuestas igual:
6
+ *
7
+ * Éxito: { "ok": true, "data": ... }
8
+ * Error: { "ok": false, "error": { "code": ..., "message": ..., "details"?: ... } }
9
+ */
10
+ import { AppError, toAppError } from './errors.js';
11
+ /** Construye una respuesta JSON con las cabeceras correctas. */
12
+ export function json(body, options = {}) {
13
+ return new Response(JSON.stringify(body), {
14
+ status: options.status ?? 200,
15
+ headers: {
16
+ 'Content-Type': 'application/json; charset=utf-8',
17
+ ...options.headers,
18
+ },
19
+ });
20
+ }
21
+ /** Respuesta de éxito envuelta en el sobre estándar. */
22
+ export function ok(data, options = {}) {
23
+ const body = { ok: true, data };
24
+ return json(body, options);
25
+ }
26
+ /** Respuesta 204 sin cuerpo, para operaciones que no devuelven nada. */
27
+ export function noContent(headers = {}) {
28
+ return new Response(null, { status: 204, headers: { ...headers } });
29
+ }
30
+ /**
31
+ * Respuesta de error a partir de cualquier valor lanzado.
32
+ *
33
+ * Los errores 5xx nunca exponen el mensaje original: se sustituye por uno
34
+ * genérico para no filtrar detalles de implementación.
35
+ */
36
+ export function fail(error, options = {}) {
37
+ const appError = toAppError(error);
38
+ const safeMessage = appError.isClientError
39
+ ? appError.message
40
+ : 'Se ha producido un error interno.';
41
+ const body = {
42
+ ok: false,
43
+ error: {
44
+ code: appError.code,
45
+ message: safeMessage,
46
+ ...(appError.isClientError && appError.details !== undefined
47
+ ? { details: appError.details }
48
+ : {}),
49
+ },
50
+ };
51
+ return json(body, {
52
+ status: options.status ?? appError.status,
53
+ headers: { ...appError.headers, ...options.headers },
54
+ });
55
+ }
56
+ /**
57
+ * Lee y parsea el cuerpo JSON de una petición.
58
+ *
59
+ * Lanza `bad_request` si el cuerpo no es JSON válido, en lugar de dejar
60
+ * escapar un `SyntaxError` crudo hasta el manejador de errores.
61
+ */
62
+ export async function readJson(request) {
63
+ try {
64
+ return await request.json();
65
+ }
66
+ catch (cause) {
67
+ throw new AppError('bad_request', 'El cuerpo de la petición no es JSON válido.', { cause });
68
+ }
69
+ }
70
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuBnD,gEAAgE;AAChE,MAAM,UAAU,IAAI,CAAI,IAAO,EAAE,UAA2B,EAAE;IAC5D,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG;QAC7B,OAAO,EAAE;YACP,cAAc,EAAE,iCAAiC;YACjD,GAAG,OAAO,CAAC,OAAO;SACnB;KACF,CAAC,CAAC;AACL,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,EAAE,CAAI,IAAO,EAAE,UAA2B,EAAE;IAC1D,MAAM,IAAI,GAAmB,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAChD,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,UAA4C,EAAE;IACtE,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,KAAc,EAAE,UAA2B,EAAE;IAChE,MAAM,QAAQ,GAAa,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,WAAW,GAAG,QAAQ,CAAC,aAAa;QACxC,CAAC,CAAC,QAAQ,CAAC,OAAO;QAClB,CAAC,CAAC,mCAAmC,CAAC;IAExC,MAAM,IAAI,GAAc;QACtB,EAAE,EAAE,KAAK;QACT,KAAK,EAAE;YACL,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,OAAO,EAAE,WAAW;YACpB,GAAG,CAAC,QAAQ,CAAC,aAAa,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS;gBAC1D,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,OAAO,EAAE;gBAC/B,CAAC,CAAC,EAAE,CAAC;SACR;KACF,CAAC;IAEF,OAAO,IAAI,CAAC,IAAI,EAAE;QAChB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM;QACzC,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE;KACrD,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAgB;IAC7C,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,QAAQ,CAAC,aAAa,EAAE,6CAA6C,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Punto de entrada de las utilidades comunes de la factoría. */
2
+ export * from './errors.js';
3
+ export * from './http.js';
4
+ export * from './time.js';
@@ -0,0 +1,5 @@
1
+ /** Punto de entrada de las utilidades comunes de la factoría. */
2
+ export * from './errors.js';
3
+ export * from './http.js';
4
+ export * from './time.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AAEjE,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Utilidades de tiempo.
3
+ *
4
+ * Todo el código que necesite "ahora" debe pedirlo por inyección (`Clock`) en
5
+ * lugar de llamar a `Date.now()` directamente: así los tests controlan el paso
6
+ * del tiempo sin recurrir a temporizadores falsos.
7
+ */
8
+ /** Fuente de tiempo inyectable. Devuelve milisegundos desde la época Unix. */
9
+ export interface Clock {
10
+ now(): number;
11
+ }
12
+ /** Reloj real del sistema. Es el valor por defecto en producción. */
13
+ export declare const systemClock: Clock;
14
+ /**
15
+ * Reloj controlable para tests: avanza sólo cuando se lo pides.
16
+ *
17
+ * @param startMs Instante inicial en milisegundos (por defecto 0).
18
+ */
19
+ export declare function createTestClock(startMs?: number): Clock & {
20
+ advance(ms: number): void;
21
+ };
22
+ /** Convierte segundos a milisegundos. */
23
+ export declare const seconds: (n: number) => number;
24
+ /** Convierte minutos a milisegundos. */
25
+ export declare const minutes: (n: number) => number;
26
+ /** Convierte horas a milisegundos. */
27
+ export declare const hours: (n: number) => number;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Utilidades de tiempo.
3
+ *
4
+ * Todo el código que necesite "ahora" debe pedirlo por inyección (`Clock`) en
5
+ * lugar de llamar a `Date.now()` directamente: así los tests controlan el paso
6
+ * del tiempo sin recurrir a temporizadores falsos.
7
+ */
8
+ /** Reloj real del sistema. Es el valor por defecto en producción. */
9
+ export const systemClock = {
10
+ now: () => Date.now(),
11
+ };
12
+ /**
13
+ * Reloj controlable para tests: avanza sólo cuando se lo pides.
14
+ *
15
+ * @param startMs Instante inicial en milisegundos (por defecto 0).
16
+ */
17
+ export function createTestClock(startMs = 0) {
18
+ let current = startMs;
19
+ return {
20
+ now: () => current,
21
+ advance: (ms) => {
22
+ current += ms;
23
+ },
24
+ };
25
+ }
26
+ /** Convierte segundos a milisegundos. */
27
+ export const seconds = (n) => n * 1000;
28
+ /** Convierte minutos a milisegundos. */
29
+ export const minutes = (n) => n * 60_000;
30
+ /** Convierte horas a milisegundos. */
31
+ export const hours = (n) => n * 3_600_000;
32
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,qEAAqE;AACrE,MAAM,CAAC,MAAM,WAAW,GAAU;IAChC,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;CACtB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAO,GAAG,CAAC;IACzC,IAAI,OAAO,GAAG,OAAO,CAAC;IACtB,OAAO;QACL,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO;QAClB,OAAO,EAAE,CAAC,EAAU,EAAE,EAAE;YACtB,OAAO,IAAI,EAAE,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,yCAAyC;AACzC,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAEvD,wCAAwC;AACxC,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC;AAEzD,sCAAsC;AACtC,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@karlangas12/openapi-to-mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Turn any OpenAPI 3.x spec into a live Model Context Protocol (MCP) server: every endpoint becomes a validated tool your AI agent can call.",
6
+ "license": "MIT",
7
+ "author": "Karlangas12",
8
+ "homepage": "https://github.com/Karlangas12/openapi-to-mcp#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Karlangas12/openapi-to-mcp.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Karlangas12/openapi-to-mcp/issues"
15
+ },
16
+ "keywords": [
17
+ "openapi",
18
+ "swagger",
19
+ "mcp",
20
+ "model-context-protocol",
21
+ "openapi-to-mcp",
22
+ "ai",
23
+ "agent",
24
+ "tools",
25
+ "claude",
26
+ "cursor",
27
+ "json-rpc",
28
+ "zod",
29
+ "codegen",
30
+ "developer-tools",
31
+ "llm"
32
+ ],
33
+ "bin": {
34
+ "openapi-to-mcp": "./dist/cli.js"
35
+ },
36
+ "main": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/index.js"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "README.md",
48
+ "LICENSE"
49
+ ],
50
+ "engines": {
51
+ "node": ">=20"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "provenance": true
56
+ },
57
+ "scripts": {
58
+ "build": "tsc -p tsconfig.build.json",
59
+ "typecheck": "tsc --noEmit",
60
+ "test": "vitest run",
61
+ "test:watch": "vitest",
62
+ "prepack": "npm run build",
63
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
64
+ },
65
+ "dependencies": {
66
+ "yaml": "^2.6.1",
67
+ "zod": "^3.24.1"
68
+ },
69
+ "devDependencies": {
70
+ "@types/node": "^22.10.5",
71
+ "typescript": "^5.7.2",
72
+ "vitest": "^2.1.8"
73
+ }
74
+ }