@ttoss/http-server-mcp-openapi 0.7.3 → 0.8.1

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/MIGRATIONS.md ADDED
@@ -0,0 +1,35 @@
1
+ # Migrations
2
+
3
+ ## `openApiToTools` replaces `registerOpenApiTools`
4
+
5
+ The package now derives tools and leaves registering them to `registerTools`
6
+ from `@ttoss/http-server-mcp`, which can also
7
+ [defer](./README.md#deferring-the-tools-of-a-large-api) them behind
8
+ `search` / `describe` / `call`.
9
+
10
+ ```diff
11
+ -import { registerOpenApiTools } from '@ttoss/http-server-mcp-openapi';
12
+ +import { registerTools } from '@ttoss/http-server-mcp';
13
+ +import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
14
+
15
+ -registerOpenApiTools({ server, spec, callApi, serverParameters });
16
+ +registerTools({
17
+ + server,
18
+ + tools: openApiToTools({ spec, callApi, serverParameters }),
19
+ +});
20
+ ```
21
+
22
+ Every other argument keeps its name and meaning; only `server` moves to
23
+ `registerTools`. `RegisterOpenApiToolsArgs` is now `OpenApiToToolsArgs`,
24
+ without `server`.
25
+
26
+ The return value changed: `registerOpenApiTools` answered the
27
+ `ToolDefinition`s, `openApiToTools` answers `OpenApiTool`s, each carrying its
28
+ `ToolDefinition` as `definition`. Replace `tool.method` with
29
+ `tool.definition.method`, and so on, wherever you read the result.
30
+
31
+ `ToolDefinition` gained `tags`, copied from the operation. A consumer that
32
+ builds `ToolDefinition` objects by hand must add it.
33
+
34
+ **What you will observe if you miss this.** The build fails on the missing
35
+ `registerOpenApiTools` import. Generated tools answer exactly as before.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @ttoss/http-server-mcp-openapi
2
2
 
3
3
  Generate [Model Context Protocol (MCP)](https://modelcontextprotocol.io) tools
4
- from an [OpenAPI](https://www.openapis.org/) specification and register them on
5
- a [@ttoss/http-server-mcp](https://ttoss.dev/docs/modules/packages/http-server-mcp)
6
- server.
4
+ from an [OpenAPI](https://www.openapis.org/) specification, ready for
5
+ [@ttoss/http-server-mcp](https://ttoss.dev/docs/modules/packages/http-server-mcp)'s
6
+ `registerTools`.
7
7
 
8
8
  Point it at your existing OpenAPI document and every operation becomes an MCP
9
9
  tool whose handler resolves the incoming arguments into an HTTP request against
@@ -20,15 +20,18 @@ pnpm add @ttoss/http-server-mcp-openapi @ttoss/http-server-mcp
20
20
 
21
21
  ```typescript
22
22
  import { App, bodyParser } from '@ttoss/http-server';
23
- import { createMcpRouter, McpServer } from '@ttoss/http-server-mcp';
24
- import { registerOpenApiTools } from '@ttoss/http-server-mcp-openapi';
23
+ import {
24
+ createMcpRouter,
25
+ McpServer,
26
+ registerTools,
27
+ } from '@ttoss/http-server-mcp';
28
+ import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
25
29
 
26
30
  import openApiDocument from './openapi.json' with { type: 'json' };
27
31
 
28
32
  const server = new McpServer({ name: 'my-api', version: '1.0.0' });
29
33
 
30
- registerOpenApiTools({
31
- server,
34
+ const tools = openApiToTools({
32
35
  spec: openApiDocument,
33
36
  // You own how the request is executed — base URL, auth, fetch impl.
34
37
  // `headers` is what createMcpRouter's `getApiHeaders` produced for this
@@ -44,6 +47,8 @@ registerOpenApiTools({
44
47
  },
45
48
  });
46
49
 
50
+ registerTools({ server, tools });
51
+
47
52
  const app = new App();
48
53
  app.use(bodyParser());
49
54
  app.use(createMcpRouter(server).routes());
@@ -55,15 +60,18 @@ app.listen(3000);
55
60
  Each OpenAPI operation with an `operationId` and a supported HTTP method
56
61
  (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`) becomes one tool:
57
62
 
58
- | OpenAPI | MCP tool |
59
- | ------------------------- | -------------------------------------------- |
60
- | `operationId: listAgents` | tool name `list-agents` (kebab-case) |
61
- | path/query/body params | a single `inputSchema` object |
62
- | `$ref`, `oneOf`, `anyOf` | dereferenced and merged into a flat schema |
63
- | snake_case names | camelCase tool inputs, mapped back on call |
64
- | operation `description` | tool description (quotes/newlines sanitised) |
65
-
66
- Path params are always required strings. Query and body params carry their
63
+ | OpenAPI | MCP tool |
64
+ | ------------------------- | --------------------------------------------- |
65
+ | `operationId: listAgents` | tool name `list-agents` (kebab-case) |
66
+ | path/query/body params | a single `inputSchema` object |
67
+ | `$ref`, `oneOf`, `anyOf` | dereferenced and merged into a flat schema |
68
+ | snake_case names | camelCase tool inputs, mapped back on call |
69
+ | operation `description` | tool description, newlines flattened |
70
+ | operation `tags` | tool `tags`, for the deferred `search` filter |
71
+
72
+ Path params are always required strings, carrying their own `description`; a
73
+ call missing one is refused before `callApi` runs, whatever
74
+ `validateArguments` says. Query and body params carry their
67
75
  declared type and `required` flag. Array params keep their `items` schema. A
68
76
  body property declared as a single-entry `allOf` (usually `allOf: [{ $ref }]`
69
77
  beside its own `description`) takes `type`, `nullable` and `items` from the
@@ -87,24 +95,63 @@ repeats array values, `spaceDelimited`/`pipeDelimited` join them, and
87
95
  `deepObject` emits bracketed keys — including nested objects and arrays, so
88
96
  `{ documentId: { $eq: 'doc_1' } }` becomes `filters[documentId][$eq]=doc_1`.
89
97
 
90
- ## `registerOpenApiTools`
98
+ ## `openApiToTools`
91
99
 
92
100
  | Field | Description |
93
101
  | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
94
- | `server` | The `McpServer` to register tools on. |
95
102
  | `spec` | One OpenAPI document, or an array of them (tools are flattened). |
96
103
  | `callApi` | Runs the resolved `{ method, url, body, tool, headers }` request and returns the raw data. |
97
104
  | `toText?` | Serialises the raw data into the tool's text payload. Defaults to pretty JSON; strings pass through verbatim. |
98
105
  | `serverParameters?` | Supplies server-managed path/query parameter values. See [Server-managed values](#server-managed-values). |
99
106
  | `toStructuredContent?` | `({ data, tool })` → the result's `structuredContent`, sent beside the text. `undefined` keeps it text-only. |
100
107
  | `toolMeta?` | `({ tool })` → the tool's `_meta` on `tools/list`. See [MCP Apps views](#mcp-apps-views). |
108
+ | `validateArguments?` | Enforce each generated `inputSchema` before `callApi` runs. Default `false`; see the caveat below. |
101
109
  | `options?` | See [Options](#options). |
102
110
 
103
111
  The default `toText` answers `NO_CONTENT_TEXT` (`Succeeded. The operation
104
112
  returned no content.`) when `callApi` resolves `undefined` or `''`, so a `204`
105
113
  reaches the client as a success.
106
114
 
107
- Returns the list of `ToolDefinition`s that were registered.
115
+ Returns one `OpenApiTool` per operation: a `Tool` with the operation's `tags`,
116
+ a `summary` of `METHOD /path — description`, and the `definition` it was
117
+ derived from. Enable `validateArguments` only when the spec describes every
118
+ value the API accepts: a generated schema is easily narrower than the API, and
119
+ validating against it rejects calls the API would have taken.
120
+
121
+ ### Deferring the tools of a large API
122
+
123
+ Every tool definition ships to the model on every turn, so a large API
124
+ overflows the client's context. [Defer](https://github.com/ttoss/ttoss/tree/main/packages/http-server-mcp#deferring-tools-for-large-tool-sets)
125
+ the same tools behind `search` / `describe` / `call`, for example on a second
126
+ endpoint next to the full one:
127
+
128
+ ```typescript
129
+ registerTools({ server: fullServer, tools });
130
+ registerTools({ server: deferredServer, tools, defer: true });
131
+ ```
132
+
133
+ `search` filters by the operations' tags and shows each tool's route; tools
134
+ linked to an MCP Apps view stay registered directly.
135
+
136
+ ### Gating generated tools
137
+
138
+ A generated tool is an ordinary `Tool`, so a gate wraps it like any other.
139
+ `definition` carries what the operation declares, such as a custom extension
140
+ naming the scope:
141
+
142
+ ```typescript
143
+ const gate = createToolGate({ gates: [subscriptionGate] });
144
+
145
+ registerTools({
146
+ server,
147
+ tools: tools.map((tool) => {
148
+ return gate({
149
+ tool,
150
+ requiredScope: tool.definition.extensions['x-required-scope'] as string,
151
+ });
152
+ }),
153
+ });
154
+ ```
108
155
 
109
156
  ### MCP Apps views
110
157
 
@@ -112,7 +159,7 @@ A generated tool links to a view through `toolMeta`, and the view reads the
112
159
  result from `structuredContent`:
113
160
 
114
161
  ```typescript
115
- import { registerAppResource } from '@ttoss/http-server-mcp';
162
+ import { registerAppResource, registerTools } from '@ttoss/http-server-mcp';
116
163
 
117
164
  const agentCard = registerAppResource({
118
165
  server,
@@ -121,8 +168,7 @@ const agentCard = registerAppResource({
121
168
  html: agentCardHtml,
122
169
  });
123
170
 
124
- registerOpenApiTools({
125
- server,
171
+ const tools = openApiToTools({
126
172
  spec,
127
173
  callApi,
128
174
  toolMeta: ({ tool }) => {
@@ -134,6 +180,8 @@ registerOpenApiTools({
134
180
  : undefined;
135
181
  },
136
182
  });
183
+
184
+ registerTools({ server, tools });
137
185
  ```
138
186
 
139
187
  Keep the text payload: a host without MCP Apps support renders only that.
@@ -151,11 +199,10 @@ forwarded onto the dispatched request.
151
199
  ```typescript
152
200
  import {
153
201
  createInProcessCallApi,
154
- registerOpenApiTools,
202
+ openApiToTools,
155
203
  } from '@ttoss/http-server-mcp-openapi';
156
204
 
157
- registerOpenApiTools({
158
- server,
205
+ const tools = openApiToTools({
159
206
  spec,
160
207
  callApi: createInProcessCallApi({
161
208
  app, // or () => app, when the app is built after the tools
@@ -163,6 +210,8 @@ registerOpenApiTools({
163
210
  }),
164
211
  });
165
212
 
213
+ registerTools({ server, tools });
214
+
166
215
  const router = createMcpRouter(server, {
167
216
  getApiHeaders: (ctx) => ({ authorization: ctx.headers.authorization ?? '' }),
168
217
  });
@@ -178,7 +227,7 @@ back to `HTTP <status>`. Pass `toError` to build the error yourself.
178
227
  ## `openApiToToolDefinitions`
179
228
 
180
229
  Use the lower-level function when you want the tool definitions without
181
- registering them — to inspect, filter, or wire handlers yourself:
230
+ handlers — to inspect, filter, or wire handlers yourself:
182
231
 
183
232
  ```typescript
184
233
  import { openApiToToolDefinitions } from '@ttoss/http-server-mcp-openapi';
@@ -193,14 +242,13 @@ for (const tool of tools) {
193
242
  ```
194
243
 
195
244
  Each `ToolDefinition` exposes `name`, `description`, `inputSchema`, `method`,
196
- `pathTemplate`, `operationId`, the `path`/`query`/`body` builders,
245
+ `pathTemplate`, `operationId`, `tags`, the `path`/`query`/`body` builders,
197
246
  `acceptedBodyFields`, `extensions`, and `serverManagedParameters`.
198
247
 
199
248
  ## Options
200
249
 
201
250
  ```typescript
202
- registerOpenApiTools({
203
- server,
251
+ openApiToTools({
204
252
  spec,
205
253
  callApi,
206
254
  options: {
@@ -259,12 +307,11 @@ A value flagged with `serverManagedExtension` is never offered to the model:
259
307
  never sent (the API sets it itself). It still appears in
260
308
  `acceptedBodyFields`.
261
309
  - A **path or query parameter** is hidden from `inputSchema` and listed in
262
- `tool.serverManagedParameters`. `registerOpenApiTools` discards anything the
310
+ `tool.serverManagedParameters`. `openApiToTools` discards anything the
263
311
  model sent for it and fills it from `serverParameters`, keyed by spec name:
264
312
 
265
313
  ```typescript
266
- registerOpenApiTools({
267
- server,
314
+ openApiToTools({
268
315
  spec,
269
316
  callApi,
270
317
  serverParameters: ({ tool, headers }) => ({
@@ -299,6 +346,12 @@ const tools = openApiToToolDefinitions({ spec: openApiDocument });
299
346
  const iamAction = tools[0].extensions['x-iam-action'];
300
347
  ```
301
348
 
349
+ ## Migrations
350
+
351
+ Breaking changes and what they require of consumers are listed in
352
+ [MIGRATIONS.md](https://github.com/ttoss/ttoss/blob/main/packages/http-server-mcp-openapi/MIGRATIONS.md),
353
+ newest first.
354
+
302
355
  ## Related Packages
303
356
 
304
357
  - [@ttoss/http-server-mcp](https://ttoss.dev/docs/modules/packages/http-server-mcp) - MCP server integration for @ttoss/http-server
package/dist/index.cjs CHANGED
@@ -354,7 +354,7 @@ var pinnedArgs = parameters => {
354
354
  /**
355
355
  * Wraps a `path` / `query` builder so pinned values replace whatever the args
356
356
  * carry. Applied in the builder itself, so every consumer of the tool
357
- * definition sends them, not only `registerOpenApiTools`.
357
+ * definition sends them, not only `openApiToTools`.
358
358
  */
359
359
  var withPinned = args => {
360
360
  const {
@@ -812,7 +812,7 @@ var defaultToError = response => {
812
812
  return new Error(errorMessageOf(response.body) ?? `HTTP ${response.status}`);
813
813
  };
814
814
  /**
815
- * A `callApi` for {@link registerOpenApiTools} that serves each tool call
815
+ * A `callApi` for {@link openApiToTools} that serves each tool call
816
816
  * against the REST app in this same process, with no socket: validation,
817
817
  * authorization and error handling run once, in the routes, for both
818
818
  * surfaces.
@@ -824,10 +824,9 @@ var defaultToError = response => {
824
824
  *
825
825
  * @example
826
826
  * ```typescript
827
- * registerOpenApiTools({
827
+ * registerTools({
828
828
  * server,
829
- * spec,
830
- * callApi: createInProcessCallApi({ app }),
829
+ * tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
831
830
  * });
832
831
  * ```
833
832
  */
@@ -866,7 +865,7 @@ var getJsonSchemaType = schemaType => {
866
865
  return "string";
867
866
  };
868
867
  var sanitizeDescription = description => {
869
- return (description || "").replace(/'/g, "\\'").replace(/\n/g, " ").trim();
868
+ return (description || "").replace(/\n/g, " ").trim();
870
869
  };
871
870
  var defaultDescribe = ({
872
871
  operation
@@ -946,7 +945,7 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
946
945
  const properties = {};
947
946
  for (const param of modelPathParams) properties[param.argName] = {
948
947
  type: "string",
949
- description: ""
948
+ description: sanitizeDescription(param.description)
950
949
  };
951
950
  for (const param of [...modelQueryParams, ...bodyProps]) properties[param.argName] = normalizeNullable(buildTypedProperty(param));
952
951
  return {
@@ -957,6 +956,25 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
957
956
  } : {})
958
957
  };
959
958
  };
959
+ /**
960
+ * Builds the request path. A path without one of its params is another route —
961
+ * `/projects/{id}/members` reaches the API as a `404` naming the resource, not
962
+ * the missing argument — so the call is refused instead.
963
+ */
964
+ var pathFn = args => {
965
+ const build = buildPathFn(args.pathTemplate, args.pathParams);
966
+ const required = values => {
967
+ const missing = args.pathParams.find(param => {
968
+ return !param.serverManaged && values[param.argName] === void 0;
969
+ });
970
+ if (missing) throw new Error(`Missing required argument "${missing.argName}".`);
971
+ return build(values);
972
+ };
973
+ return withPinned({
974
+ build: required,
975
+ pinned: args.pinned
976
+ });
977
+ };
960
978
  var SUPPORTED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
961
979
  /** The tool description, from `options.describe` or the default. */
962
980
  var describeOperation = args => {
@@ -992,6 +1010,12 @@ var extractExtensions = operation => {
992
1010
  for (const [key, value] of Object.entries(operation)) if (key.startsWith("x-")) extensions[key] = value;
993
1011
  return extensions;
994
1012
  };
1013
+ /** The operation's tags, ignoring entries a malformed spec typed otherwise. */
1014
+ var operationTags = operation => {
1015
+ return (operation.tags ?? []).filter(tag => {
1016
+ return typeof tag === "string";
1017
+ });
1018
+ };
995
1019
  var processOperation = args => {
996
1020
  const httpMethod = args.method.toUpperCase();
997
1021
  if (!SUPPORTED_METHODS.includes(httpMethod) || !args.operation.operationId || args.operation[args.options.excludeExtension]) return null;
@@ -1042,8 +1066,10 @@ var processOperation = args => {
1042
1066
  method: httpMethod,
1043
1067
  pathTemplate: args.pathTemplate,
1044
1068
  operationId: args.operation.operationId,
1045
- path: withPinned({
1046
- build: buildPathFn(args.pathTemplate, pathParams),
1069
+ tags: operationTags(args.operation),
1070
+ path: pathFn({
1071
+ pathTemplate: args.pathTemplate,
1072
+ pathParams,
1047
1073
  pinned
1048
1074
  }),
1049
1075
  query: withPinned({
@@ -1120,7 +1146,7 @@ var openApiToToolDefinitions = args => {
1120
1146
  };
1121
1147
 
1122
1148
  //#endregion
1123
- //#region src/registerOpenApiTools.ts
1149
+ //#region src/openApiToTools.ts
1124
1150
  /** The text the default `toText` answers when the API returned no body. */
1125
1151
  var NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
1126
1152
  var defaultToText = data => {
@@ -1146,21 +1172,21 @@ var applyServerParameters = async args => {
1146
1172
  return result;
1147
1173
  };
1148
1174
  /**
1149
- * Derives MCP tools from OpenAPI document(s) and registers each on the given
1150
- * MCP server. Every tool's handler resolves the incoming args into a concrete
1151
- * HTTP request and delegates execution to `callApi`.
1175
+ * Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
1176
+ * the incoming args into a concrete HTTP request and delegates execution to
1177
+ * `callApi`. Register the result with `registerTools` from
1178
+ * `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
1152
1179
  *
1153
- * @returns The list of {@link ToolDefinition} that were registered.
1180
+ * @returns One {@link OpenApiTool} per translatable operation.
1154
1181
  *
1155
1182
  * @example
1156
1183
  * ```typescript
1157
- * import { McpServer } from '@ttoss/http-server-mcp';
1158
- * import { registerOpenApiTools } from '@ttoss/http-server-mcp-openapi';
1184
+ * import { McpServer, registerTools } from '@ttoss/http-server-mcp';
1185
+ * import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
1159
1186
  *
1160
1187
  * const server = new McpServer({ name: 'my-api', version: '1.0.0' });
1161
1188
  *
1162
- * registerOpenApiTools({
1163
- * server,
1189
+ * const tools = openApiToTools({
1164
1190
  * spec: myOpenApiDocument,
1165
1191
  * callApi: async ({ method, url, body, headers }) => {
1166
1192
  * const res = await fetch(`https://api.example.com${url}`, {
@@ -1171,53 +1197,62 @@ var applyServerParameters = async args => {
1171
1197
  * return res.json();
1172
1198
  * },
1173
1199
  * });
1200
+ *
1201
+ * registerTools({ server, tools });
1174
1202
  * ```
1175
1203
  */
1176
- var registerOpenApiTools = args => {
1204
+ var openApiToTools = args => {
1177
1205
  const toText = args.toText ?? defaultToText;
1178
- const tools = openApiToToolDefinitions({
1206
+ return openApiToToolDefinitions({
1179
1207
  spec: args.spec,
1180
1208
  options: args.options
1209
+ }).map(definition => {
1210
+ const route = `${definition.method} ${definition.pathTemplate}`;
1211
+ return {
1212
+ name: definition.name,
1213
+ description: definition.description,
1214
+ inputSchema: definition.inputSchema,
1215
+ validateArguments: args.validateArguments ?? false,
1216
+ ...(definition.tags.length > 0 ? {
1217
+ tags: definition.tags
1218
+ } : {}),
1219
+ summary: definition.description ? `${route} — ${definition.description}` : route,
1220
+ _meta: args.toolMeta?.({
1221
+ tool: definition
1222
+ }),
1223
+ definition,
1224
+ handler: async rawArgs => {
1225
+ const headers = (0, _ttoss_http_server_mcp.getApiHeaders)();
1226
+ const handlerArgs = await applyServerParameters({
1227
+ tool: definition,
1228
+ handlerArgs: rawArgs,
1229
+ headers,
1230
+ serverParameters: args.serverParameters
1231
+ });
1232
+ const url = definition.path(handlerArgs) + (definition.query ? definition.query(handlerArgs) : "");
1233
+ const data = await args.callApi({
1234
+ method: definition.method,
1235
+ url,
1236
+ body: definition.body ? definition.body(handlerArgs) : void 0,
1237
+ tool: definition,
1238
+ headers
1239
+ });
1240
+ const structuredContent = args.toStructuredContent?.({
1241
+ data,
1242
+ tool: definition
1243
+ });
1244
+ return {
1245
+ content: [{
1246
+ type: "text",
1247
+ text: toText(data)
1248
+ }],
1249
+ ...(structuredContent === void 0 ? {} : {
1250
+ structuredContent
1251
+ })
1252
+ };
1253
+ }
1254
+ };
1181
1255
  });
1182
- for (const tool of tools) (0, _ttoss_http_server_mcp.registerToolFromSchema)(args.server, {
1183
- name: tool.name,
1184
- description: tool.description,
1185
- inputSchema: tool.inputSchema,
1186
- _meta: args.toolMeta?.({
1187
- tool
1188
- }),
1189
- handler: async rawArgs => {
1190
- const headers = (0, _ttoss_http_server_mcp.getApiHeaders)();
1191
- const handlerArgs = await applyServerParameters({
1192
- tool,
1193
- handlerArgs: rawArgs,
1194
- headers,
1195
- serverParameters: args.serverParameters
1196
- });
1197
- const url = tool.path(handlerArgs) + (tool.query ? tool.query(handlerArgs) : "");
1198
- const data = await args.callApi({
1199
- method: tool.method,
1200
- url,
1201
- body: tool.body ? tool.body(handlerArgs) : void 0,
1202
- tool,
1203
- headers
1204
- });
1205
- const structuredContent = args.toStructuredContent?.({
1206
- data,
1207
- tool
1208
- });
1209
- return {
1210
- content: [{
1211
- type: "text",
1212
- text: toText(data)
1213
- }],
1214
- ...(structuredContent === void 0 ? {} : {
1215
- structuredContent
1216
- })
1217
- };
1218
- }
1219
- });
1220
- return tools;
1221
1256
  };
1222
1257
 
1223
1258
  //#endregion
@@ -1239,10 +1274,10 @@ exports.extractPinnedBody = extractPinnedBody;
1239
1274
  exports.extractQueryParams = extractQueryParams;
1240
1275
  exports.getJsonSchemaType = getJsonSchemaType;
1241
1276
  exports.openApiToToolDefinitions = openApiToToolDefinitions;
1277
+ exports.openApiToTools = openApiToTools;
1242
1278
  exports.operationIdToToolName = operationIdToToolName;
1243
1279
  exports.processOperation = processOperation;
1244
1280
  exports.processPath = processPath;
1245
- exports.registerOpenApiTools = registerOpenApiTools;
1246
1281
  exports.resolveParameter = resolveParameter;
1247
1282
  exports.resolveSchema = resolveSchema;
1248
1283
  exports.snakeToCamel = snakeToCamel;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { JsonObjectSchema, McpServer } from "@ttoss/http-server-mcp";
2
+ import { JsonObjectSchema, Tool } from "@ttoss/http-server-mcp";
3
3
  import { App, InProcessResponse } from "@ttoss/http-server";
4
4
 
5
5
  //#region src/types.d.ts
@@ -49,7 +49,12 @@ interface ToolDefinition {
49
49
  pathTemplate: string;
50
50
  /** The operation's `operationId`. */
51
51
  operationId: string;
52
- /** Builds the request path, substituting path params from the args. */
52
+ /** The operation's `tags`, e.g. to filter the deferred `search` by module. */
53
+ tags: string[];
54
+ /**
55
+ * Builds the request path, substituting path params from the args. Throws
56
+ * when an argument a path param needs is missing.
57
+ */
53
58
  path: (args: Record<string, unknown>) => string;
54
59
  /** Builds the query string (including leading `?`), or `undefined` if none. */
55
60
  query?: (args: Record<string, unknown>) => string;
@@ -73,7 +78,7 @@ interface ToolDefinition {
73
78
  * Path and query parameters flagged with the server-managed extension. They
74
79
  * are absent from `inputSchema`, but `path` / `query` still read them from
75
80
  * the args under `argName`, so the consumer fills them before building the
76
- * request — `registerOpenApiTools` does it through `serverParameters`.
81
+ * request — `openApiToTools` does it through `serverParameters`.
77
82
  */
78
83
  serverManagedParameters: ServerManagedParameter[];
79
84
  }
@@ -122,6 +127,7 @@ type RequestBodySpec = {
122
127
  };
123
128
  interface OperationSpec {
124
129
  operationId?: string;
130
+ tags?: string[];
125
131
  summary?: string;
126
132
  description?: string;
127
133
  parameters?: Array<{
@@ -342,7 +348,7 @@ declare const buildFullInputSchema: (args: {
342
348
  bodyProps: FullBodyProp[];
343
349
  }) => JsonObjectSchema;
344
350
  //#endregion
345
- //#region src/registerOpenApiTools.d.ts
351
+ //#region src/openApiToTools.d.ts
346
352
  /** The resolved HTTP request a tool call maps to, before transport concerns. */
347
353
  interface ResolvedRequest {
348
354
  /** Uppercase HTTP method. */
@@ -360,9 +366,7 @@ interface ResolvedRequest {
360
366
  */
361
367
  headers: Record<string, string>;
362
368
  }
363
- interface RegisterOpenApiToolsArgs {
364
- /** The MCP server the generated tools are registered on. */
365
- server: McpServer;
369
+ interface OpenApiToToolsArgs {
366
370
  /** One or more OpenAPI documents to derive tools from. */
367
371
  spec: OpenApiSpec | OpenApiSpec[];
368
372
  /** Tuning options forwarded to {@link openApiToToolDefinitions}. */
@@ -399,7 +403,7 @@ interface RegisterOpenApiToolsArgs {
399
403
  /**
400
404
  * Builds each tool's `_meta`, advertised on `tools/list`. This is how a
401
405
  * generated tool links to an MCP Apps view: return the bag from
402
- * `registerAppResource(...).toolMeta()`. `undefined` registers no `_meta`.
406
+ * `registerAppResource(...).toolMeta()`. `undefined` sets no `_meta`.
403
407
  *
404
408
  * @example
405
409
  * ```typescript
@@ -427,25 +431,41 @@ interface RegisterOpenApiToolsArgs {
427
431
  tool: ToolDefinition;
428
432
  headers: Record<string, string>;
429
433
  }) => Record<string, unknown> | Promise<Record<string, unknown>>;
434
+ /**
435
+ * Sets every tool's `validateArguments`: enforce the generated
436
+ * `inputSchema` before `callApi` runs. Leave it off unless the spec
437
+ * describes every value the API accepts — see `Tool.validateArguments`.
438
+ * @default false
439
+ */
440
+ validateArguments?: boolean;
441
+ }
442
+ /** A {@link Tool} derived from one OpenAPI operation. */
443
+ interface OpenApiTool extends Tool {
444
+ /**
445
+ * The operation it was derived from — method, path, extensions — for
446
+ * decisions that key on the API rather than the tool name, such as which
447
+ * scope a gate requires.
448
+ */
449
+ definition: ToolDefinition;
430
450
  }
431
451
  /** The text the default `toText` answers when the API returned no body. */
432
452
  declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
433
453
  /**
434
- * Derives MCP tools from OpenAPI document(s) and registers each on the given
435
- * MCP server. Every tool's handler resolves the incoming args into a concrete
436
- * HTTP request and delegates execution to `callApi`.
454
+ * Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
455
+ * the incoming args into a concrete HTTP request and delegates execution to
456
+ * `callApi`. Register the result with `registerTools` from
457
+ * `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
437
458
  *
438
- * @returns The list of {@link ToolDefinition} that were registered.
459
+ * @returns One {@link OpenApiTool} per translatable operation.
439
460
  *
440
461
  * @example
441
462
  * ```typescript
442
- * import { McpServer } from '@ttoss/http-server-mcp';
443
- * import { registerOpenApiTools } from '@ttoss/http-server-mcp-openapi';
463
+ * import { McpServer, registerTools } from '@ttoss/http-server-mcp';
464
+ * import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
444
465
  *
445
466
  * const server = new McpServer({ name: 'my-api', version: '1.0.0' });
446
467
  *
447
- * registerOpenApiTools({
448
- * server,
468
+ * const tools = openApiToTools({
449
469
  * spec: myOpenApiDocument,
450
470
  * callApi: async ({ method, url, body, headers }) => {
451
471
  * const res = await fetch(`https://api.example.com${url}`, {
@@ -456,9 +476,11 @@ declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
456
476
  * return res.json();
457
477
  * },
458
478
  * });
479
+ *
480
+ * registerTools({ server, tools });
459
481
  * ```
460
482
  */
461
- declare const registerOpenApiTools: (args: RegisterOpenApiToolsArgs) => ToolDefinition[];
483
+ declare const openApiToTools: (args: OpenApiToToolsArgs) => OpenApiTool[];
462
484
  //#endregion
463
485
  //#region src/inProcessCallApi.d.ts
464
486
  interface CreateInProcessCallApiArgs {
@@ -487,7 +509,7 @@ interface CreateInProcessCallApiArgs {
487
509
  */
488
510
  declare const errorMessageOf: (body: unknown) => string | null;
489
511
  /**
490
- * A `callApi` for {@link registerOpenApiTools} that serves each tool call
512
+ * A `callApi` for {@link openApiToTools} that serves each tool call
491
513
  * against the REST app in this same process, with no socket: validation,
492
514
  * authorization and error handling run once, in the routes, for both
493
515
  * surfaces.
@@ -499,10 +521,9 @@ declare const errorMessageOf: (body: unknown) => string | null;
499
521
  *
500
522
  * @example
501
523
  * ```typescript
502
- * registerOpenApiTools({
524
+ * registerTools({
503
525
  * server,
504
- * spec,
505
- * callApi: createInProcessCallApi({ app }),
526
+ * tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
506
527
  * });
507
528
  * ```
508
529
  */
@@ -590,6 +611,7 @@ declare const getJsonSchemaType: (schemaType: string | undefined) => JsonSchemaP
590
611
  declare const buildInputSchema: (pathParams: Array<{
591
612
  name: string;
592
613
  argName: string;
614
+ description?: string;
593
615
  serverManaged?: boolean;
594
616
  }>, queryParams: Array<{
595
617
  name: string;
@@ -650,4 +672,4 @@ declare const openApiToToolDefinitions: (args: {
650
672
  options?: OpenApiToToolsOptions;
651
673
  }) => ToolDefinition[];
652
674
  //#endregion
653
- export { type CreateInProcessCallApiArgs, 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, buildFullInputSchema, buildInputSchema, buildPathFn, buildQueryFn, createInProcessCallApi, dereferenceSchema, errorMessageOf, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel, toToolSchema };
675
+ export { type CreateInProcessCallApiArgs, DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsArgs, type OpenApiToToolsOptions, type OpenApiTool, type OperationSpec, type QueryParamSerialization, type RequestBodySpec, type ResolvedParameter, type ResolvedRequest, type ResolvedToolOptions, type ServerManagedParameter, type ToolDefinition, buildBodyFn, buildFullInputSchema, buildInputSchema, buildPathFn, buildQueryFn, createInProcessCallApi, dereferenceSchema, errorMessageOf, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, openApiToTools, operationIdToToolName, processOperation, processPath, resolveParameter, resolveSchema, snakeToCamel, toToolSchema };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { App, InProcessResponse } from "@ttoss/http-server";
3
- import { JsonObjectSchema, McpServer } from "@ttoss/http-server-mcp";
3
+ import { JsonObjectSchema, Tool } from "@ttoss/http-server-mcp";
4
4
 
5
5
  //#region src/types.d.ts
6
6
  /** The JSON Schema primitive types this generator can derive from an OpenAPI `type`. */
@@ -49,7 +49,12 @@ interface ToolDefinition {
49
49
  pathTemplate: string;
50
50
  /** The operation's `operationId`. */
51
51
  operationId: string;
52
- /** Builds the request path, substituting path params from the args. */
52
+ /** The operation's `tags`, e.g. to filter the deferred `search` by module. */
53
+ tags: string[];
54
+ /**
55
+ * Builds the request path, substituting path params from the args. Throws
56
+ * when an argument a path param needs is missing.
57
+ */
53
58
  path: (args: Record<string, unknown>) => string;
54
59
  /** Builds the query string (including leading `?`), or `undefined` if none. */
55
60
  query?: (args: Record<string, unknown>) => string;
@@ -73,7 +78,7 @@ interface ToolDefinition {
73
78
  * Path and query parameters flagged with the server-managed extension. They
74
79
  * are absent from `inputSchema`, but `path` / `query` still read them from
75
80
  * the args under `argName`, so the consumer fills them before building the
76
- * request — `registerOpenApiTools` does it through `serverParameters`.
81
+ * request — `openApiToTools` does it through `serverParameters`.
77
82
  */
78
83
  serverManagedParameters: ServerManagedParameter[];
79
84
  }
@@ -122,6 +127,7 @@ type RequestBodySpec = {
122
127
  };
123
128
  interface OperationSpec {
124
129
  operationId?: string;
130
+ tags?: string[];
125
131
  summary?: string;
126
132
  description?: string;
127
133
  parameters?: Array<{
@@ -342,7 +348,7 @@ declare const buildFullInputSchema: (args: {
342
348
  bodyProps: FullBodyProp[];
343
349
  }) => JsonObjectSchema;
344
350
  //#endregion
345
- //#region src/registerOpenApiTools.d.ts
351
+ //#region src/openApiToTools.d.ts
346
352
  /** The resolved HTTP request a tool call maps to, before transport concerns. */
347
353
  interface ResolvedRequest {
348
354
  /** Uppercase HTTP method. */
@@ -360,9 +366,7 @@ interface ResolvedRequest {
360
366
  */
361
367
  headers: Record<string, string>;
362
368
  }
363
- interface RegisterOpenApiToolsArgs {
364
- /** The MCP server the generated tools are registered on. */
365
- server: McpServer;
369
+ interface OpenApiToToolsArgs {
366
370
  /** One or more OpenAPI documents to derive tools from. */
367
371
  spec: OpenApiSpec | OpenApiSpec[];
368
372
  /** Tuning options forwarded to {@link openApiToToolDefinitions}. */
@@ -399,7 +403,7 @@ interface RegisterOpenApiToolsArgs {
399
403
  /**
400
404
  * Builds each tool's `_meta`, advertised on `tools/list`. This is how a
401
405
  * generated tool links to an MCP Apps view: return the bag from
402
- * `registerAppResource(...).toolMeta()`. `undefined` registers no `_meta`.
406
+ * `registerAppResource(...).toolMeta()`. `undefined` sets no `_meta`.
403
407
  *
404
408
  * @example
405
409
  * ```typescript
@@ -427,25 +431,41 @@ interface RegisterOpenApiToolsArgs {
427
431
  tool: ToolDefinition;
428
432
  headers: Record<string, string>;
429
433
  }) => Record<string, unknown> | Promise<Record<string, unknown>>;
434
+ /**
435
+ * Sets every tool's `validateArguments`: enforce the generated
436
+ * `inputSchema` before `callApi` runs. Leave it off unless the spec
437
+ * describes every value the API accepts — see `Tool.validateArguments`.
438
+ * @default false
439
+ */
440
+ validateArguments?: boolean;
441
+ }
442
+ /** A {@link Tool} derived from one OpenAPI operation. */
443
+ interface OpenApiTool extends Tool {
444
+ /**
445
+ * The operation it was derived from — method, path, extensions — for
446
+ * decisions that key on the API rather than the tool name, such as which
447
+ * scope a gate requires.
448
+ */
449
+ definition: ToolDefinition;
430
450
  }
431
451
  /** The text the default `toText` answers when the API returned no body. */
432
452
  declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
433
453
  /**
434
- * Derives MCP tools from OpenAPI document(s) and registers each on the given
435
- * MCP server. Every tool's handler resolves the incoming args into a concrete
436
- * HTTP request and delegates execution to `callApi`.
454
+ * Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
455
+ * the incoming args into a concrete HTTP request and delegates execution to
456
+ * `callApi`. Register the result with `registerTools` from
457
+ * `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
437
458
  *
438
- * @returns The list of {@link ToolDefinition} that were registered.
459
+ * @returns One {@link OpenApiTool} per translatable operation.
439
460
  *
440
461
  * @example
441
462
  * ```typescript
442
- * import { McpServer } from '@ttoss/http-server-mcp';
443
- * import { registerOpenApiTools } from '@ttoss/http-server-mcp-openapi';
463
+ * import { McpServer, registerTools } from '@ttoss/http-server-mcp';
464
+ * import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
444
465
  *
445
466
  * const server = new McpServer({ name: 'my-api', version: '1.0.0' });
446
467
  *
447
- * registerOpenApiTools({
448
- * server,
468
+ * const tools = openApiToTools({
449
469
  * spec: myOpenApiDocument,
450
470
  * callApi: async ({ method, url, body, headers }) => {
451
471
  * const res = await fetch(`https://api.example.com${url}`, {
@@ -456,9 +476,11 @@ declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
456
476
  * return res.json();
457
477
  * },
458
478
  * });
479
+ *
480
+ * registerTools({ server, tools });
459
481
  * ```
460
482
  */
461
- declare const registerOpenApiTools: (args: RegisterOpenApiToolsArgs) => ToolDefinition[];
483
+ declare const openApiToTools: (args: OpenApiToToolsArgs) => OpenApiTool[];
462
484
  //#endregion
463
485
  //#region src/inProcessCallApi.d.ts
464
486
  interface CreateInProcessCallApiArgs {
@@ -487,7 +509,7 @@ interface CreateInProcessCallApiArgs {
487
509
  */
488
510
  declare const errorMessageOf: (body: unknown) => string | null;
489
511
  /**
490
- * A `callApi` for {@link registerOpenApiTools} that serves each tool call
512
+ * A `callApi` for {@link openApiToTools} that serves each tool call
491
513
  * against the REST app in this same process, with no socket: validation,
492
514
  * authorization and error handling run once, in the routes, for both
493
515
  * surfaces.
@@ -499,10 +521,9 @@ declare const errorMessageOf: (body: unknown) => string | null;
499
521
  *
500
522
  * @example
501
523
  * ```typescript
502
- * registerOpenApiTools({
524
+ * registerTools({
503
525
  * server,
504
- * spec,
505
- * callApi: createInProcessCallApi({ app }),
526
+ * tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
506
527
  * });
507
528
  * ```
508
529
  */
@@ -590,6 +611,7 @@ declare const getJsonSchemaType: (schemaType: string | undefined) => JsonSchemaP
590
611
  declare const buildInputSchema: (pathParams: Array<{
591
612
  name: string;
592
613
  argName: string;
614
+ description?: string;
593
615
  serverManaged?: boolean;
594
616
  }>, queryParams: Array<{
595
617
  name: string;
@@ -650,4 +672,4 @@ declare const openApiToToolDefinitions: (args: {
650
672
  options?: OpenApiToToolsOptions;
651
673
  }) => ToolDefinition[];
652
674
  //#endregion
653
- export { type CreateInProcessCallApiArgs, 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, buildFullInputSchema, buildInputSchema, buildPathFn, buildQueryFn, createInProcessCallApi, dereferenceSchema, errorMessageOf, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel, toToolSchema };
675
+ export { type CreateInProcessCallApiArgs, DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsArgs, type OpenApiToToolsOptions, type OpenApiTool, type OperationSpec, type QueryParamSerialization, type RequestBodySpec, type ResolvedParameter, type ResolvedRequest, type ResolvedToolOptions, type ServerManagedParameter, type ToolDefinition, buildBodyFn, buildFullInputSchema, buildInputSchema, buildPathFn, buildQueryFn, createInProcessCallApi, dereferenceSchema, errorMessageOf, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, openApiToTools, operationIdToToolName, processOperation, processPath, resolveParameter, resolveSchema, snakeToCamel, toToolSchema };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
2
  import { dispatchInProcess } from "@ttoss/http-server";
3
- import { getApiHeaders, registerToolFromSchema } from "@ttoss/http-server-mcp";
3
+ import { getApiHeaders } from "@ttoss/http-server-mcp";
4
4
 
5
5
  //#region src/schema.ts
6
6
  var isRecord$1 = value => {
@@ -351,7 +351,7 @@ var pinnedArgs = parameters => {
351
351
  /**
352
352
  * Wraps a `path` / `query` builder so pinned values replace whatever the args
353
353
  * carry. Applied in the builder itself, so every consumer of the tool
354
- * definition sends them, not only `registerOpenApiTools`.
354
+ * definition sends them, not only `openApiToTools`.
355
355
  */
356
356
  var withPinned = args => {
357
357
  const {
@@ -809,7 +809,7 @@ var defaultToError = response => {
809
809
  return new Error(errorMessageOf(response.body) ?? `HTTP ${response.status}`);
810
810
  };
811
811
  /**
812
- * A `callApi` for {@link registerOpenApiTools} that serves each tool call
812
+ * A `callApi` for {@link openApiToTools} that serves each tool call
813
813
  * against the REST app in this same process, with no socket: validation,
814
814
  * authorization and error handling run once, in the routes, for both
815
815
  * surfaces.
@@ -821,10 +821,9 @@ var defaultToError = response => {
821
821
  *
822
822
  * @example
823
823
  * ```typescript
824
- * registerOpenApiTools({
824
+ * registerTools({
825
825
  * server,
826
- * spec,
827
- * callApi: createInProcessCallApi({ app }),
826
+ * tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
828
827
  * });
829
828
  * ```
830
829
  */
@@ -863,7 +862,7 @@ var getJsonSchemaType = schemaType => {
863
862
  return "string";
864
863
  };
865
864
  var sanitizeDescription = description => {
866
- return (description || "").replace(/'/g, "\\'").replace(/\n/g, " ").trim();
865
+ return (description || "").replace(/\n/g, " ").trim();
867
866
  };
868
867
  var defaultDescribe = ({
869
868
  operation
@@ -943,7 +942,7 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
943
942
  const properties = {};
944
943
  for (const param of modelPathParams) properties[param.argName] = {
945
944
  type: "string",
946
- description: ""
945
+ description: sanitizeDescription(param.description)
947
946
  };
948
947
  for (const param of [...modelQueryParams, ...bodyProps]) properties[param.argName] = normalizeNullable(buildTypedProperty(param));
949
948
  return {
@@ -954,6 +953,25 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
954
953
  } : {})
955
954
  };
956
955
  };
956
+ /**
957
+ * Builds the request path. A path without one of its params is another route —
958
+ * `/projects/{id}/members` reaches the API as a `404` naming the resource, not
959
+ * the missing argument — so the call is refused instead.
960
+ */
961
+ var pathFn = args => {
962
+ const build = buildPathFn(args.pathTemplate, args.pathParams);
963
+ const required = values => {
964
+ const missing = args.pathParams.find(param => {
965
+ return !param.serverManaged && values[param.argName] === void 0;
966
+ });
967
+ if (missing) throw new Error(`Missing required argument "${missing.argName}".`);
968
+ return build(values);
969
+ };
970
+ return withPinned({
971
+ build: required,
972
+ pinned: args.pinned
973
+ });
974
+ };
957
975
  var SUPPORTED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
958
976
  /** The tool description, from `options.describe` or the default. */
959
977
  var describeOperation = args => {
@@ -989,6 +1007,12 @@ var extractExtensions = operation => {
989
1007
  for (const [key, value] of Object.entries(operation)) if (key.startsWith("x-")) extensions[key] = value;
990
1008
  return extensions;
991
1009
  };
1010
+ /** The operation's tags, ignoring entries a malformed spec typed otherwise. */
1011
+ var operationTags = operation => {
1012
+ return (operation.tags ?? []).filter(tag => {
1013
+ return typeof tag === "string";
1014
+ });
1015
+ };
992
1016
  var processOperation = args => {
993
1017
  const httpMethod = args.method.toUpperCase();
994
1018
  if (!SUPPORTED_METHODS.includes(httpMethod) || !args.operation.operationId || args.operation[args.options.excludeExtension]) return null;
@@ -1039,8 +1063,10 @@ var processOperation = args => {
1039
1063
  method: httpMethod,
1040
1064
  pathTemplate: args.pathTemplate,
1041
1065
  operationId: args.operation.operationId,
1042
- path: withPinned({
1043
- build: buildPathFn(args.pathTemplate, pathParams),
1066
+ tags: operationTags(args.operation),
1067
+ path: pathFn({
1068
+ pathTemplate: args.pathTemplate,
1069
+ pathParams,
1044
1070
  pinned
1045
1071
  }),
1046
1072
  query: withPinned({
@@ -1117,7 +1143,7 @@ var openApiToToolDefinitions = args => {
1117
1143
  };
1118
1144
 
1119
1145
  //#endregion
1120
- //#region src/registerOpenApiTools.ts
1146
+ //#region src/openApiToTools.ts
1121
1147
  /** The text the default `toText` answers when the API returned no body. */
1122
1148
  var NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
1123
1149
  var defaultToText = data => {
@@ -1143,21 +1169,21 @@ var applyServerParameters = async args => {
1143
1169
  return result;
1144
1170
  };
1145
1171
  /**
1146
- * Derives MCP tools from OpenAPI document(s) and registers each on the given
1147
- * MCP server. Every tool's handler resolves the incoming args into a concrete
1148
- * HTTP request and delegates execution to `callApi`.
1172
+ * Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
1173
+ * the incoming args into a concrete HTTP request and delegates execution to
1174
+ * `callApi`. Register the result with `registerTools` from
1175
+ * `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
1149
1176
  *
1150
- * @returns The list of {@link ToolDefinition} that were registered.
1177
+ * @returns One {@link OpenApiTool} per translatable operation.
1151
1178
  *
1152
1179
  * @example
1153
1180
  * ```typescript
1154
- * import { McpServer } from '@ttoss/http-server-mcp';
1155
- * import { registerOpenApiTools } from '@ttoss/http-server-mcp-openapi';
1181
+ * import { McpServer, registerTools } from '@ttoss/http-server-mcp';
1182
+ * import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
1156
1183
  *
1157
1184
  * const server = new McpServer({ name: 'my-api', version: '1.0.0' });
1158
1185
  *
1159
- * registerOpenApiTools({
1160
- * server,
1186
+ * const tools = openApiToTools({
1161
1187
  * spec: myOpenApiDocument,
1162
1188
  * callApi: async ({ method, url, body, headers }) => {
1163
1189
  * const res = await fetch(`https://api.example.com${url}`, {
@@ -1168,54 +1194,63 @@ var applyServerParameters = async args => {
1168
1194
  * return res.json();
1169
1195
  * },
1170
1196
  * });
1197
+ *
1198
+ * registerTools({ server, tools });
1171
1199
  * ```
1172
1200
  */
1173
- var registerOpenApiTools = args => {
1201
+ var openApiToTools = args => {
1174
1202
  const toText = args.toText ?? defaultToText;
1175
- const tools = openApiToToolDefinitions({
1203
+ return openApiToToolDefinitions({
1176
1204
  spec: args.spec,
1177
1205
  options: args.options
1206
+ }).map(definition => {
1207
+ const route = `${definition.method} ${definition.pathTemplate}`;
1208
+ return {
1209
+ name: definition.name,
1210
+ description: definition.description,
1211
+ inputSchema: definition.inputSchema,
1212
+ validateArguments: args.validateArguments ?? false,
1213
+ ...(definition.tags.length > 0 ? {
1214
+ tags: definition.tags
1215
+ } : {}),
1216
+ summary: definition.description ? `${route} — ${definition.description}` : route,
1217
+ _meta: args.toolMeta?.({
1218
+ tool: definition
1219
+ }),
1220
+ definition,
1221
+ handler: async rawArgs => {
1222
+ const headers = getApiHeaders();
1223
+ const handlerArgs = await applyServerParameters({
1224
+ tool: definition,
1225
+ handlerArgs: rawArgs,
1226
+ headers,
1227
+ serverParameters: args.serverParameters
1228
+ });
1229
+ const url = definition.path(handlerArgs) + (definition.query ? definition.query(handlerArgs) : "");
1230
+ const data = await args.callApi({
1231
+ method: definition.method,
1232
+ url,
1233
+ body: definition.body ? definition.body(handlerArgs) : void 0,
1234
+ tool: definition,
1235
+ headers
1236
+ });
1237
+ const structuredContent = args.toStructuredContent?.({
1238
+ data,
1239
+ tool: definition
1240
+ });
1241
+ return {
1242
+ content: [{
1243
+ type: "text",
1244
+ text: toText(data)
1245
+ }],
1246
+ ...(structuredContent === void 0 ? {} : {
1247
+ structuredContent
1248
+ })
1249
+ };
1250
+ }
1251
+ };
1178
1252
  });
1179
- for (const tool of tools) registerToolFromSchema(args.server, {
1180
- name: tool.name,
1181
- description: tool.description,
1182
- inputSchema: tool.inputSchema,
1183
- _meta: args.toolMeta?.({
1184
- tool
1185
- }),
1186
- handler: async rawArgs => {
1187
- const headers = getApiHeaders();
1188
- const handlerArgs = await applyServerParameters({
1189
- tool,
1190
- handlerArgs: rawArgs,
1191
- headers,
1192
- serverParameters: args.serverParameters
1193
- });
1194
- const url = tool.path(handlerArgs) + (tool.query ? tool.query(handlerArgs) : "");
1195
- const data = await args.callApi({
1196
- method: tool.method,
1197
- url,
1198
- body: tool.body ? tool.body(handlerArgs) : void 0,
1199
- tool,
1200
- headers
1201
- });
1202
- const structuredContent = args.toStructuredContent?.({
1203
- data,
1204
- tool
1205
- });
1206
- return {
1207
- content: [{
1208
- type: "text",
1209
- text: toText(data)
1210
- }],
1211
- ...(structuredContent === void 0 ? {} : {
1212
- structuredContent
1213
- })
1214
- };
1215
- }
1216
- });
1217
- return tools;
1218
1253
  };
1219
1254
 
1220
1255
  //#endregion
1221
- export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, NO_CONTENT_TEXT, buildBodyFn, buildFullInputSchema, buildInputSchema, buildPathFn, buildQueryFn, createInProcessCallApi, dereferenceSchema, errorMessageOf, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel, toToolSchema };
1256
+ export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, NO_CONTENT_TEXT, buildBodyFn, buildFullInputSchema, buildInputSchema, buildPathFn, buildQueryFn, createInProcessCallApi, dereferenceSchema, errorMessageOf, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractPinnedBody, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, openApiToTools, operationIdToToolName, processOperation, processPath, resolveParameter, resolveSchema, snakeToCamel, toToolSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp-openapi",
3
- "version": "0.7.3",
3
+ "version": "0.8.1",
4
4
  "description": "Generate Model Context Protocol (MCP) tools from an OpenAPI specification for @ttoss/http-server-mcp",
5
5
  "keywords": [
6
6
  "ai",
@@ -32,11 +32,12 @@
32
32
  }
33
33
  },
34
34
  "files": [
35
+ "MIGRATIONS.md",
35
36
  "dist"
36
37
  ],
37
38
  "dependencies": {
38
- "@ttoss/http-server": "^0.11.1",
39
- "@ttoss/http-server-mcp": "^0.33.2"
39
+ "@ttoss/http-server-mcp": "^0.34.1",
40
+ "@ttoss/http-server": "^0.11.1"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@modelcontextprotocol/server": "^2.0.0",