@ttoss/http-server-mcp-openapi 0.2.15 → 0.3.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/dist/index.d.mts CHANGED
@@ -30,8 +30,8 @@ type JsonSchemaProperty = {
30
30
  /**
31
31
  * A REST-backed MCP tool derived from a single OpenAPI operation.
32
32
  *
33
- * The `path` / `query` / `body` builders turn the camelCase arguments an MCP
34
- * client sends into the pieces of an HTTP request against the original REST
33
+ * The `path` / `query` / `body` builders turn the arguments an MCP client
34
+ * sends (named per {@link OpenApiToToolsOptions.argumentNames}) into the pieces of an HTTP request against the original REST
35
35
  * API. They intentionally hold no transport concerns (base URL, auth); the
36
36
  * caller wires those in when it performs the request.
37
37
  */
@@ -40,7 +40,7 @@ interface ToolDefinition {
40
40
  name: string;
41
41
  /** Sanitised operation description (quotes escaped, newlines flattened). */
42
42
  description: string;
43
- /** Plain JSON Schema describing the tool's camelCase input object. */
43
+ /** Plain JSON Schema describing the tool's input object. */
44
44
  inputSchema: JsonObjectSchema;
45
45
  /** Uppercase HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`). */
46
46
  method: string;
@@ -52,10 +52,13 @@ interface ToolDefinition {
52
52
  path: (args: Record<string, unknown>) => string;
53
53
  /** Builds the query string (including leading `?`), or `undefined` if none. */
54
54
  query?: (args: Record<string, unknown>) => string;
55
- /** Builds the snake_case request body, or `undefined` if the op has no body. */
55
+ /**
56
+ * Builds the request body keyed by the spec's property names, or `undefined`
57
+ * if the op has no body.
58
+ */
56
59
  body?: (args: Record<string, unknown>) => Record<string, unknown>;
57
60
  /**
58
- * snake_case names of every top-level request-body property the operation's
61
+ * Spec names of every top-level request-body property the operation's
59
62
  * schema declares, including server-managed ones hidden from `inputSchema`.
60
63
  */
61
64
  acceptedBodyFields: string[];
@@ -65,6 +68,22 @@ interface ToolDefinition {
65
68
  * without this package needing to know about it.
66
69
  */
67
70
  extensions: Record<string, unknown>;
71
+ /**
72
+ * Path and query parameters flagged with the server-managed extension. They
73
+ * are absent from `inputSchema`, but `path` / `query` still read them from
74
+ * the args under `argName`, so the consumer fills them before building the
75
+ * request — `registerOpenApiTools` does it through `serverParameters`.
76
+ */
77
+ serverManagedParameters: ServerManagedParameter[];
78
+ }
79
+ /** A path or query parameter the server sets, never offered to the model. */
80
+ interface ServerManagedParameter {
81
+ /** The parameter's name in the spec. */
82
+ name: string;
83
+ /** Where the parameter goes. */
84
+ in: 'path' | 'query';
85
+ /** The args key `path` / `query` read the value from. */
86
+ argName: string;
68
87
  }
69
88
  /** Minimal shape of an OpenAPI document consumed by the generator. */
70
89
  interface OpenApiSpec {
@@ -74,6 +93,12 @@ interface OpenApiSpec {
74
93
  parameters?: Record<string, unknown>;
75
94
  };
76
95
  }
96
+ /**
97
+ * Documents a `$ref` may point into, keyed by the file part of the ref exactly
98
+ * as the spec writes it (`./tags.yaml` in `./tags.yaml#/components/schemas/Tag`).
99
+ * A leading `./` is optional on either side.
100
+ */
101
+ type OpenApiDocuments = Record<string, OpenApiSpec>;
77
102
  type RequestBodySpec = {
78
103
  required?: boolean;
79
104
  content?: {
@@ -119,16 +144,80 @@ interface OpenApiToToolsOptions {
119
144
  */
120
145
  excludeExtension?: string;
121
146
  /**
122
- * Body-property extension flag that, when truthy, hides the property from the
123
- * generated `inputSchema` while keeping it in `acceptedBodyFields` (for
124
- * server-managed fields the API sets itself).
147
+ * Extension flag that, when truthy, hides a value the server sets from the
148
+ * generated `inputSchema`.
149
+ *
150
+ * - On a **request-body property**, the property stays in
151
+ * `acceptedBodyFields` and is never sent (the API sets it itself).
152
+ * - On a **path or query parameter**, the parameter is listed in
153
+ * `serverManagedParameters` and the consumer supplies its value.
154
+ *
125
155
  * @default 'x-mcp-server-managed'
126
156
  */
127
157
  serverManagedExtension?: string;
158
+ /**
159
+ * How tool argument names are derived from the spec's parameter and
160
+ * body-property names.
161
+ *
162
+ * - `'camelCase'` folds `_` / `-` separators (`agent_id` → `agentId`) and maps
163
+ * them back when building the request.
164
+ * - `'verbatim'` uses the spec's names unchanged, so the MCP contract matches
165
+ * the REST contract exactly.
166
+ *
167
+ * @default 'camelCase'
168
+ */
169
+ argumentNames?: 'camelCase' | 'verbatim';
170
+ /**
171
+ * Documents that `$ref`s with a file part resolve against, keyed by that
172
+ * file part as the spec writes it — `./tags.yaml` for
173
+ * `./tags.yaml#/components/schemas/Tag`. Refs inside a document resolve
174
+ * relative to that document. A ref to a file absent from this map resolves
175
+ * to an empty schema, which accepts any value.
176
+ */
177
+ documents?: OpenApiDocuments;
128
178
  }
179
+ /** {@link OpenApiToToolsOptions} with every default applied. */
180
+ type ResolvedToolOptions = Required<Omit<OpenApiToToolsOptions, 'documents'>> & Pick<OpenApiToToolsOptions, 'documents'>;
129
181
  declare const DEFAULT_EXCLUDE_EXTENSION = "x-mcp-exclude";
130
182
  declare const DEFAULT_SERVER_MANAGED_EXTENSION = "x-mcp-server-managed";
131
183
  //#endregion
184
+ //#region src/parameters.d.ts
185
+ /**
186
+ * Folds `_` and `-` separators into camelCase. OpenAPI operation and parameter
187
+ * names may be snake_case (`agent_id`) or kebab-case (`list-tools`); with the
188
+ * default `argumentNames: 'camelCase'` both are folded into tool arguments.
189
+ */
190
+ declare const snakeToCamel: (str: string) => string;
191
+ /** Maps a spec parameter or property name to a tool argument name. */
192
+ type ToArgName = (name: string) => string;
193
+ /** Arguments shared by the parameter extractors. */
194
+ type ExtractParamsArgs = {
195
+ parameters?: Array<{
196
+ name?: string;
197
+ in?: string;
198
+ [key: string]: unknown;
199
+ }>;
200
+ spec: OpenApiSpec; /** Sibling documents cross-file `$ref`s resolve against. */
201
+ documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
202
+ toArgName?: ToArgName; /** @default DEFAULT_SERVER_MANAGED_EXTENSION */
203
+ serverManagedExtension?: string;
204
+ };
205
+ declare const extractPathParams: (args: ExtractParamsArgs) => Array<{
206
+ name: string;
207
+ argName: string;
208
+ serverManaged: boolean;
209
+ }>;
210
+ declare const extractQueryParams: (args: ExtractParamsArgs) => Array<{
211
+ name: string;
212
+ argName: string;
213
+ description: string;
214
+ required: boolean;
215
+ type: string;
216
+ style?: string;
217
+ explode?: boolean;
218
+ serverManaged: boolean;
219
+ }>;
220
+ //#endregion
132
221
  //#region src/registerOpenApiTools.d.ts
133
222
  /** The resolved HTTP request a tool call maps to, before transport concerns. */
134
223
  interface ResolvedRequest {
@@ -136,10 +225,16 @@ interface ResolvedRequest {
136
225
  method: string;
137
226
  /** Request path including the query string, e.g. `/agents/agt_1?limit=10`. */
138
227
  url: string;
139
- /** snake_case request body, or `undefined` when the operation has none. */
228
+ /** Request body keyed by the spec's names, or `undefined` when the operation has none. */
140
229
  body?: Record<string, unknown>;
141
230
  /** The tool definition the call resolved to (for auth/metadata lookups). */
142
231
  tool: ToolDefinition;
232
+ /**
233
+ * The headers `createMcpRouter`'s `getApiHeaders` produced for the MCP
234
+ * request this call belongs to — typically the caller's credentials. `{}`
235
+ * when `getApiHeaders` is not configured.
236
+ */
237
+ headers: Record<string, string>;
143
238
  }
144
239
  interface RegisterOpenApiToolsArgs {
145
240
  /** The MCP server the generated tools are registered on. */
@@ -155,15 +250,35 @@ interface RegisterOpenApiToolsArgs {
155
250
  */
156
251
  callApi: (request: ResolvedRequest) => Promise<unknown> | unknown;
157
252
  /**
158
- * Serialises the raw API data into the MCP tool's text payload.
159
- * @default (data) => JSON.stringify(data, null, 2)
253
+ * Serialises the raw API data into the MCP tool's text payload. The default
254
+ * passes strings through, pretty-prints anything else as JSON, and answers
255
+ * {@link NO_CONTENT_TEXT} for an empty body (e.g. a `204`).
160
256
  */
161
257
  toText?: (data: unknown) => string;
258
+ /**
259
+ * Supplies the values of the tool's server-managed path and query
260
+ * parameters (`tool.serverManagedParameters`), keyed by their spec name.
261
+ * Runs on every call, after any value the model sent for those parameters
262
+ * has been discarded.
263
+ *
264
+ * @example
265
+ * ```typescript
266
+ * serverParameters: ({ headers }) => ({
267
+ * project_id: projectIdFromToken(headers.Authorization),
268
+ * }),
269
+ * ```
270
+ */
271
+ serverParameters?: (args: {
272
+ tool: ToolDefinition;
273
+ headers: Record<string, string>;
274
+ }) => Record<string, unknown> | Promise<Record<string, unknown>>;
162
275
  }
276
+ /** The text the default `toText` answers when the API returned no body. */
277
+ declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
163
278
  /**
164
279
  * Derives MCP tools from OpenAPI document(s) and registers each on the given
165
- * MCP server. Every tool's handler resolves the incoming camelCase args into a
166
- * concrete HTTP request and delegates execution to `callApi`.
280
+ * MCP server. Every tool's handler resolves the incoming args into a concrete
281
+ * HTTP request and delegates execution to `callApi`.
167
282
  *
168
283
  * @returns The list of {@link ToolDefinition} that were registered.
169
284
  *
@@ -177,10 +292,10 @@ interface RegisterOpenApiToolsArgs {
177
292
  * registerOpenApiTools({
178
293
  * server,
179
294
  * spec: myOpenApiDocument,
180
- * callApi: async ({ method, url, body }) => {
295
+ * callApi: async ({ method, url, body, headers }) => {
181
296
  * const res = await fetch(`https://api.example.com${url}`, {
182
297
  * method,
183
- * headers: { 'Content-Type': 'application/json' },
298
+ * headers: { ...headers, 'Content-Type': 'application/json' },
184
299
  * body: body ? JSON.stringify(body) : undefined,
185
300
  * });
186
301
  * return res.json();
@@ -203,16 +318,18 @@ type ResolvedSchema = {
203
318
  * `properties`, `items`, `oneOf`, `anyOf`, etc.), producing a self-contained
204
319
  * schema safe to hand to an MCP client or LLM provider as a tool definition —
205
320
  * provider tool schemas have no `components` section to resolve refs against.
321
+ *
322
+ * Refs into other files resolve against `documents`; see {@link OpenApiDocuments}.
206
323
  */
207
- declare const dereferenceSchema: (schema: Record<string, unknown> | undefined, spec: OpenApiSpec) => Record<string, unknown> | undefined;
324
+ declare const dereferenceSchema: (schema: Record<string, unknown> | undefined, spec: OpenApiSpec, documents?: OpenApiDocuments) => Record<string, unknown> | undefined;
208
325
  /**
209
326
  * Resolves a schema down to a single object shape: follows a top-level `$ref`
210
327
  * and merges `oneOf` / `anyOf` alternatives (union of properties, intersection
211
328
  * of `required`) so the caller sees one flat property set.
212
329
  */
213
- declare const resolveSchema: (schema: Record<string, unknown> | undefined, spec: OpenApiSpec) => ResolvedSchema;
214
- /** Follows a parameter `$ref` into `components.parameters`, if present. */
215
- declare const resolveParameter: (param: Record<string, unknown> | undefined, spec: OpenApiSpec) => {
330
+ declare const resolveSchema: (schema: Record<string, unknown> | undefined, spec: OpenApiSpec, documents?: OpenApiDocuments) => ResolvedSchema;
331
+ /** A parameter object after its `$ref` (if any) is followed. */
332
+ type ResolvedParameter = {
216
333
  name?: string;
217
334
  in?: string;
218
335
  required?: boolean;
@@ -225,16 +342,22 @@ declare const resolveParameter: (param: Record<string, unknown> | undefined, spe
225
342
  type?: string;
226
343
  };
227
344
  };
345
+ [extension: string]: unknown;
228
346
  };
347
+ /**
348
+ * Follows a parameter `$ref` — into `components.parameters`, or into another
349
+ * file through `documents` — if present.
350
+ */
351
+ declare const resolveParameter: (param: Record<string, unknown> | undefined, spec: OpenApiSpec, documents?: OpenApiDocuments) => ResolvedParameter;
229
352
  /** Builds a function that substitutes path params into the path template. */
230
353
  declare const buildPathFn: (pathTemplate: string, pathParams: Array<{
231
354
  name: string;
232
- camelName: string;
355
+ argName: string;
233
356
  }>) => ((args: Record<string, unknown>) => string);
234
357
  /** Query parameter with the OpenAPI serialisation rules declared for it. */
235
358
  type QueryParamSerialization = {
236
359
  name: string;
237
- camelName: string; /** OpenAPI `style` (`form`, `deepObject`, `spaceDelimited`, `pipeDelimited`). */
360
+ argName: string; /** OpenAPI `style` (`form`, `deepObject`, `spaceDelimited`, `pipeDelimited`). */
238
361
  style?: string; /** OpenAPI `explode`. Defaults to `true` for `form`, `false` otherwise. */
239
362
  explode?: boolean;
240
363
  };
@@ -245,36 +368,32 @@ type QueryParamSerialization = {
245
368
  */
246
369
  declare const buildQueryFn: (queryParams: QueryParamSerialization[]) => ((args: Record<string, unknown>) => string) | undefined;
247
370
  /**
248
- * Builds a function that maps camelCase args back to a snake_case request
249
- * body, skipping `undefined` args. Returns `undefined` when the op has no body.
371
+ * Builds a function that maps tool args back to a request body keyed by the
372
+ * spec's property names, skipping `undefined` args. Returns `undefined` when the op has no body.
250
373
  */
251
374
  declare const buildBodyFn: (bodyProps: Array<{
252
375
  snakeName: string;
253
- camelName: string;
376
+ argName: string;
254
377
  }>) => ((args: Record<string, unknown>) => Record<string, unknown>) | undefined;
255
378
  //#endregion
256
379
  //#region src/toolDefinitions.d.ts
257
- /**
258
- * Folds `_` and `-` separators into camelCase. OpenAPI operation and parameter
259
- * names may be snake_case (`agent_id`) or kebab-case (`list-tools`), and MCP
260
- * tool inputs are camelCase by convention, so both are folded here.
261
- */
262
- declare const snakeToCamel: (str: string) => string;
263
380
  /** Converts a camelCase `operationId` to a kebab-case tool name. */
264
381
  declare const operationIdToToolName: (operationId: string) => string;
265
382
  declare const getJsonSchemaType: (schemaType: string | undefined) => JsonSchemaProperty["type"];
266
383
  declare const buildInputSchema: (pathParams: Array<{
267
384
  name: string;
268
- camelName: string;
385
+ argName: string;
386
+ serverManaged?: boolean;
269
387
  }>, queryParams: Array<{
270
388
  name: string;
271
- camelName: string;
389
+ argName: string;
272
390
  description: string;
273
391
  required: boolean;
274
392
  type: string;
393
+ serverManaged?: boolean;
275
394
  }>, bodyProps: Array<{
276
395
  snakeName: string;
277
- camelName: string;
396
+ argName: string;
278
397
  description: string;
279
398
  required: boolean;
280
399
  type?: string;
@@ -284,33 +403,6 @@ declare const buildInputSchema: (pathParams: Array<{
284
403
  anyOf?: unknown[];
285
404
  allOf?: unknown[];
286
405
  }>) => JsonObjectSchema;
287
- declare const extractPathParams: (args: {
288
- parameters?: Array<{
289
- name?: string;
290
- in?: string;
291
- [key: string]: unknown;
292
- }>;
293
- spec: OpenApiSpec;
294
- }) => Array<{
295
- name: string;
296
- camelName: string;
297
- }>;
298
- declare const extractQueryParams: (args: {
299
- parameters?: Array<{
300
- name?: string;
301
- in?: string;
302
- [key: string]: unknown;
303
- }>;
304
- spec: OpenApiSpec;
305
- }) => Array<{
306
- name: string;
307
- camelName: string;
308
- description: string;
309
- required: boolean;
310
- type: string;
311
- style?: string;
312
- explode?: boolean;
313
- }>;
314
406
  /**
315
407
  * snake_case names of every top-level property an operation's request schema
316
408
  * declares, including server-managed ones.
@@ -318,14 +410,17 @@ declare const extractQueryParams: (args: {
318
410
  declare const extractAcceptedBodyFields: (args: {
319
411
  requestBody?: RequestBodySpec;
320
412
  spec: OpenApiSpec;
413
+ documents?: OpenApiDocuments;
321
414
  }) => string[];
322
415
  declare const extractBodyProps: (args: {
323
416
  requestBody?: RequestBodySpec;
324
417
  spec: OpenApiSpec;
325
418
  serverManagedExtension: string;
419
+ documents?: OpenApiDocuments; /** Maps a spec name to its tool argument name. @default snakeToCamel */
420
+ toArgName?: ToArgName;
326
421
  }) => Array<{
327
422
  snakeName: string;
328
- camelName: string;
423
+ argName: string;
329
424
  description: string;
330
425
  required: boolean;
331
426
  type?: string;
@@ -340,7 +435,7 @@ declare const processOperation: (args: {
340
435
  method: string;
341
436
  operation: OperationSpec;
342
437
  spec: OpenApiSpec;
343
- options: Required<OpenApiToToolsOptions>;
438
+ options: ResolvedToolOptions;
344
439
  /**
345
440
  * Parameters declared at the path-item level (shared by every operation on
346
441
  * the path). Merged ahead of the operation's own parameters so operation-level
@@ -356,7 +451,7 @@ declare const processPath: (args: {
356
451
  pathTemplate: string;
357
452
  pathItem: Record<string, OperationSpec>;
358
453
  spec: OpenApiSpec;
359
- options: Required<OpenApiToToolsOptions>;
454
+ options: ResolvedToolOptions;
360
455
  }) => ToolDefinition[];
361
456
  /**
362
457
  * Translates one or more OpenAPI documents into REST-backed MCP tool
@@ -375,4 +470,4 @@ declare const openApiToToolDefinitions: (args: {
375
470
  options?: OpenApiToToolsOptions;
376
471
  }) => ToolDefinition[];
377
472
  //#endregion
378
- export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
473
+ export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedParameter, type ResolvedRequest, type ResolvedToolOptions, type ServerManagedParameter, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };