@ttoss/http-server-mcp-openapi 0.7.2 → 0.8.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/MIGRATIONS.md +35 -0
- package/README.md +81 -30
- package/dist/index.cjs +71 -56
- package/dist/index.d.cts +39 -21
- package/dist/index.d.mts +39 -21
- package/dist/index.mjs +72 -57
- package/package.json +5 -4
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
|
|
5
|
-
|
|
6
|
-
|
|
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 {
|
|
24
|
-
|
|
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
|
-
|
|
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,13 +60,14 @@ 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)
|
|
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 (quotes/newlines sanitised) |
|
|
70
|
+
| operation `tags` | tool `tags`, for the deferred `search` filter |
|
|
65
71
|
|
|
66
72
|
Path params are always required strings. Query and body params carry their
|
|
67
73
|
declared type and `required` flag. Array params keep their `items` schema. A
|
|
@@ -87,24 +93,63 @@ repeats array values, `spaceDelimited`/`pipeDelimited` join them, and
|
|
|
87
93
|
`deepObject` emits bracketed keys — including nested objects and arrays, so
|
|
88
94
|
`{ documentId: { $eq: 'doc_1' } }` becomes `filters[documentId][$eq]=doc_1`.
|
|
89
95
|
|
|
90
|
-
## `
|
|
96
|
+
## `openApiToTools`
|
|
91
97
|
|
|
92
98
|
| Field | Description |
|
|
93
99
|
| ---------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
94
|
-
| `server` | The `McpServer` to register tools on. |
|
|
95
100
|
| `spec` | One OpenAPI document, or an array of them (tools are flattened). |
|
|
96
101
|
| `callApi` | Runs the resolved `{ method, url, body, tool, headers }` request and returns the raw data. |
|
|
97
102
|
| `toText?` | Serialises the raw data into the tool's text payload. Defaults to pretty JSON; strings pass through verbatim. |
|
|
98
103
|
| `serverParameters?` | Supplies server-managed path/query parameter values. See [Server-managed values](#server-managed-values). |
|
|
99
104
|
| `toStructuredContent?` | `({ data, tool })` → the result's `structuredContent`, sent beside the text. `undefined` keeps it text-only. |
|
|
100
105
|
| `toolMeta?` | `({ tool })` → the tool's `_meta` on `tools/list`. See [MCP Apps views](#mcp-apps-views). |
|
|
106
|
+
| `validateArguments?` | Enforce each generated `inputSchema` before `callApi` runs. Default `false`; see the caveat below. |
|
|
101
107
|
| `options?` | See [Options](#options). |
|
|
102
108
|
|
|
103
109
|
The default `toText` answers `NO_CONTENT_TEXT` (`Succeeded. The operation
|
|
104
110
|
returned no content.`) when `callApi` resolves `undefined` or `''`, so a `204`
|
|
105
111
|
reaches the client as a success.
|
|
106
112
|
|
|
107
|
-
Returns
|
|
113
|
+
Returns one `OpenApiTool` per operation: a `Tool` with the operation's `tags`,
|
|
114
|
+
a `summary` of `METHOD /path — description`, and the `definition` it was
|
|
115
|
+
derived from. Enable `validateArguments` only when the spec describes every
|
|
116
|
+
value the API accepts: a generated schema is easily narrower than the API, and
|
|
117
|
+
validating against it rejects calls the API would have taken.
|
|
118
|
+
|
|
119
|
+
### Deferring the tools of a large API
|
|
120
|
+
|
|
121
|
+
Every tool definition ships to the model on every turn, so a large API
|
|
122
|
+
overflows the client's context. [Defer](https://github.com/ttoss/ttoss/tree/main/packages/http-server-mcp#deferring-tools-for-large-tool-sets)
|
|
123
|
+
the same tools behind `search` / `describe` / `call`, for example on a second
|
|
124
|
+
endpoint next to the full one:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
registerTools({ server: fullServer, tools });
|
|
128
|
+
registerTools({ server: deferredServer, tools, defer: true });
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`search` filters by the operations' tags and shows each tool's route; tools
|
|
132
|
+
linked to an MCP Apps view stay registered directly.
|
|
133
|
+
|
|
134
|
+
### Gating generated tools
|
|
135
|
+
|
|
136
|
+
A generated tool is an ordinary `Tool`, so a gate wraps it like any other.
|
|
137
|
+
`definition` carries what the operation declares, such as a custom extension
|
|
138
|
+
naming the scope:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const gate = createToolGate({ gates: [subscriptionGate] });
|
|
142
|
+
|
|
143
|
+
registerTools({
|
|
144
|
+
server,
|
|
145
|
+
tools: tools.map((tool) => {
|
|
146
|
+
return gate({
|
|
147
|
+
tool,
|
|
148
|
+
requiredScope: tool.definition.extensions['x-required-scope'] as string,
|
|
149
|
+
});
|
|
150
|
+
}),
|
|
151
|
+
});
|
|
152
|
+
```
|
|
108
153
|
|
|
109
154
|
### MCP Apps views
|
|
110
155
|
|
|
@@ -112,7 +157,7 @@ A generated tool links to a view through `toolMeta`, and the view reads the
|
|
|
112
157
|
result from `structuredContent`:
|
|
113
158
|
|
|
114
159
|
```typescript
|
|
115
|
-
import { registerAppResource } from '@ttoss/http-server-mcp';
|
|
160
|
+
import { registerAppResource, registerTools } from '@ttoss/http-server-mcp';
|
|
116
161
|
|
|
117
162
|
const agentCard = registerAppResource({
|
|
118
163
|
server,
|
|
@@ -121,8 +166,7 @@ const agentCard = registerAppResource({
|
|
|
121
166
|
html: agentCardHtml,
|
|
122
167
|
});
|
|
123
168
|
|
|
124
|
-
|
|
125
|
-
server,
|
|
169
|
+
const tools = openApiToTools({
|
|
126
170
|
spec,
|
|
127
171
|
callApi,
|
|
128
172
|
toolMeta: ({ tool }) => {
|
|
@@ -134,6 +178,8 @@ registerOpenApiTools({
|
|
|
134
178
|
: undefined;
|
|
135
179
|
},
|
|
136
180
|
});
|
|
181
|
+
|
|
182
|
+
registerTools({ server, tools });
|
|
137
183
|
```
|
|
138
184
|
|
|
139
185
|
Keep the text payload: a host without MCP Apps support renders only that.
|
|
@@ -151,11 +197,10 @@ forwarded onto the dispatched request.
|
|
|
151
197
|
```typescript
|
|
152
198
|
import {
|
|
153
199
|
createInProcessCallApi,
|
|
154
|
-
|
|
200
|
+
openApiToTools,
|
|
155
201
|
} from '@ttoss/http-server-mcp-openapi';
|
|
156
202
|
|
|
157
|
-
|
|
158
|
-
server,
|
|
203
|
+
const tools = openApiToTools({
|
|
159
204
|
spec,
|
|
160
205
|
callApi: createInProcessCallApi({
|
|
161
206
|
app, // or () => app, when the app is built after the tools
|
|
@@ -163,6 +208,8 @@ registerOpenApiTools({
|
|
|
163
208
|
}),
|
|
164
209
|
});
|
|
165
210
|
|
|
211
|
+
registerTools({ server, tools });
|
|
212
|
+
|
|
166
213
|
const router = createMcpRouter(server, {
|
|
167
214
|
getApiHeaders: (ctx) => ({ authorization: ctx.headers.authorization ?? '' }),
|
|
168
215
|
});
|
|
@@ -178,7 +225,7 @@ back to `HTTP <status>`. Pass `toError` to build the error yourself.
|
|
|
178
225
|
## `openApiToToolDefinitions`
|
|
179
226
|
|
|
180
227
|
Use the lower-level function when you want the tool definitions without
|
|
181
|
-
|
|
228
|
+
handlers — to inspect, filter, or wire handlers yourself:
|
|
182
229
|
|
|
183
230
|
```typescript
|
|
184
231
|
import { openApiToToolDefinitions } from '@ttoss/http-server-mcp-openapi';
|
|
@@ -193,14 +240,13 @@ for (const tool of tools) {
|
|
|
193
240
|
```
|
|
194
241
|
|
|
195
242
|
Each `ToolDefinition` exposes `name`, `description`, `inputSchema`, `method`,
|
|
196
|
-
`pathTemplate`, `operationId`, the `path`/`query`/`body` builders,
|
|
243
|
+
`pathTemplate`, `operationId`, `tags`, the `path`/`query`/`body` builders,
|
|
197
244
|
`acceptedBodyFields`, `extensions`, and `serverManagedParameters`.
|
|
198
245
|
|
|
199
246
|
## Options
|
|
200
247
|
|
|
201
248
|
```typescript
|
|
202
|
-
|
|
203
|
-
server,
|
|
249
|
+
openApiToTools({
|
|
204
250
|
spec,
|
|
205
251
|
callApi,
|
|
206
252
|
options: {
|
|
@@ -259,12 +305,11 @@ A value flagged with `serverManagedExtension` is never offered to the model:
|
|
|
259
305
|
never sent (the API sets it itself). It still appears in
|
|
260
306
|
`acceptedBodyFields`.
|
|
261
307
|
- A **path or query parameter** is hidden from `inputSchema` and listed in
|
|
262
|
-
`tool.serverManagedParameters`. `
|
|
308
|
+
`tool.serverManagedParameters`. `openApiToTools` discards anything the
|
|
263
309
|
model sent for it and fills it from `serverParameters`, keyed by spec name:
|
|
264
310
|
|
|
265
311
|
```typescript
|
|
266
|
-
|
|
267
|
-
server,
|
|
312
|
+
openApiToTools({
|
|
268
313
|
spec,
|
|
269
314
|
callApi,
|
|
270
315
|
serverParameters: ({ tool, headers }) => ({
|
|
@@ -299,6 +344,12 @@ const tools = openApiToToolDefinitions({ spec: openApiDocument });
|
|
|
299
344
|
const iamAction = tools[0].extensions['x-iam-action'];
|
|
300
345
|
```
|
|
301
346
|
|
|
347
|
+
## Migrations
|
|
348
|
+
|
|
349
|
+
Breaking changes and what they require of consumers are listed in
|
|
350
|
+
[MIGRATIONS.md](https://github.com/ttoss/ttoss/blob/main/packages/http-server-mcp-openapi/MIGRATIONS.md),
|
|
351
|
+
newest first.
|
|
352
|
+
|
|
302
353
|
## Related Packages
|
|
303
354
|
|
|
304
355
|
- [@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 `
|
|
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
|
|
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
|
-
*
|
|
827
|
+
* registerTools({
|
|
828
828
|
* server,
|
|
829
|
-
* spec,
|
|
830
|
-
* callApi: createInProcessCallApi({ app }),
|
|
829
|
+
* tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
|
|
831
830
|
* });
|
|
832
831
|
* ```
|
|
833
832
|
*/
|
|
@@ -992,6 +991,12 @@ var extractExtensions = operation => {
|
|
|
992
991
|
for (const [key, value] of Object.entries(operation)) if (key.startsWith("x-")) extensions[key] = value;
|
|
993
992
|
return extensions;
|
|
994
993
|
};
|
|
994
|
+
/** The operation's tags, ignoring entries a malformed spec typed otherwise. */
|
|
995
|
+
var operationTags = operation => {
|
|
996
|
+
return (operation.tags ?? []).filter(tag => {
|
|
997
|
+
return typeof tag === "string";
|
|
998
|
+
});
|
|
999
|
+
};
|
|
995
1000
|
var processOperation = args => {
|
|
996
1001
|
const httpMethod = args.method.toUpperCase();
|
|
997
1002
|
if (!SUPPORTED_METHODS.includes(httpMethod) || !args.operation.operationId || args.operation[args.options.excludeExtension]) return null;
|
|
@@ -1042,6 +1047,7 @@ var processOperation = args => {
|
|
|
1042
1047
|
method: httpMethod,
|
|
1043
1048
|
pathTemplate: args.pathTemplate,
|
|
1044
1049
|
operationId: args.operation.operationId,
|
|
1050
|
+
tags: operationTags(args.operation),
|
|
1045
1051
|
path: withPinned({
|
|
1046
1052
|
build: buildPathFn(args.pathTemplate, pathParams),
|
|
1047
1053
|
pinned
|
|
@@ -1120,7 +1126,7 @@ var openApiToToolDefinitions = args => {
|
|
|
1120
1126
|
};
|
|
1121
1127
|
|
|
1122
1128
|
//#endregion
|
|
1123
|
-
//#region src/
|
|
1129
|
+
//#region src/openApiToTools.ts
|
|
1124
1130
|
/** The text the default `toText` answers when the API returned no body. */
|
|
1125
1131
|
var NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
|
|
1126
1132
|
var defaultToText = data => {
|
|
@@ -1146,21 +1152,21 @@ var applyServerParameters = async args => {
|
|
|
1146
1152
|
return result;
|
|
1147
1153
|
};
|
|
1148
1154
|
/**
|
|
1149
|
-
* Derives MCP tools from OpenAPI document(s)
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1155
|
+
* Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
|
|
1156
|
+
* the incoming args into a concrete HTTP request and delegates execution to
|
|
1157
|
+
* `callApi`. Register the result with `registerTools` from
|
|
1158
|
+
* `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
|
|
1152
1159
|
*
|
|
1153
|
-
* @returns
|
|
1160
|
+
* @returns One {@link OpenApiTool} per translatable operation.
|
|
1154
1161
|
*
|
|
1155
1162
|
* @example
|
|
1156
1163
|
* ```typescript
|
|
1157
|
-
* import { McpServer } from '@ttoss/http-server-mcp';
|
|
1158
|
-
* import {
|
|
1164
|
+
* import { McpServer, registerTools } from '@ttoss/http-server-mcp';
|
|
1165
|
+
* import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
|
|
1159
1166
|
*
|
|
1160
1167
|
* const server = new McpServer({ name: 'my-api', version: '1.0.0' });
|
|
1161
1168
|
*
|
|
1162
|
-
*
|
|
1163
|
-
* server,
|
|
1169
|
+
* const tools = openApiToTools({
|
|
1164
1170
|
* spec: myOpenApiDocument,
|
|
1165
1171
|
* callApi: async ({ method, url, body, headers }) => {
|
|
1166
1172
|
* const res = await fetch(`https://api.example.com${url}`, {
|
|
@@ -1171,53 +1177,62 @@ var applyServerParameters = async args => {
|
|
|
1171
1177
|
* return res.json();
|
|
1172
1178
|
* },
|
|
1173
1179
|
* });
|
|
1180
|
+
*
|
|
1181
|
+
* registerTools({ server, tools });
|
|
1174
1182
|
* ```
|
|
1175
1183
|
*/
|
|
1176
|
-
var
|
|
1184
|
+
var openApiToTools = args => {
|
|
1177
1185
|
const toText = args.toText ?? defaultToText;
|
|
1178
|
-
|
|
1186
|
+
return openApiToToolDefinitions({
|
|
1179
1187
|
spec: args.spec,
|
|
1180
1188
|
options: args.options
|
|
1189
|
+
}).map(definition => {
|
|
1190
|
+
const route = `${definition.method} ${definition.pathTemplate}`;
|
|
1191
|
+
return {
|
|
1192
|
+
name: definition.name,
|
|
1193
|
+
description: definition.description,
|
|
1194
|
+
inputSchema: definition.inputSchema,
|
|
1195
|
+
validateArguments: args.validateArguments ?? false,
|
|
1196
|
+
...(definition.tags.length > 0 ? {
|
|
1197
|
+
tags: definition.tags
|
|
1198
|
+
} : {}),
|
|
1199
|
+
summary: definition.description ? `${route} — ${definition.description}` : route,
|
|
1200
|
+
_meta: args.toolMeta?.({
|
|
1201
|
+
tool: definition
|
|
1202
|
+
}),
|
|
1203
|
+
definition,
|
|
1204
|
+
handler: async rawArgs => {
|
|
1205
|
+
const headers = (0, _ttoss_http_server_mcp.getApiHeaders)();
|
|
1206
|
+
const handlerArgs = await applyServerParameters({
|
|
1207
|
+
tool: definition,
|
|
1208
|
+
handlerArgs: rawArgs,
|
|
1209
|
+
headers,
|
|
1210
|
+
serverParameters: args.serverParameters
|
|
1211
|
+
});
|
|
1212
|
+
const url = definition.path(handlerArgs) + (definition.query ? definition.query(handlerArgs) : "");
|
|
1213
|
+
const data = await args.callApi({
|
|
1214
|
+
method: definition.method,
|
|
1215
|
+
url,
|
|
1216
|
+
body: definition.body ? definition.body(handlerArgs) : void 0,
|
|
1217
|
+
tool: definition,
|
|
1218
|
+
headers
|
|
1219
|
+
});
|
|
1220
|
+
const structuredContent = args.toStructuredContent?.({
|
|
1221
|
+
data,
|
|
1222
|
+
tool: definition
|
|
1223
|
+
});
|
|
1224
|
+
return {
|
|
1225
|
+
content: [{
|
|
1226
|
+
type: "text",
|
|
1227
|
+
text: toText(data)
|
|
1228
|
+
}],
|
|
1229
|
+
...(structuredContent === void 0 ? {} : {
|
|
1230
|
+
structuredContent
|
|
1231
|
+
})
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1181
1235
|
});
|
|
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
1236
|
};
|
|
1222
1237
|
|
|
1223
1238
|
//#endregion
|
|
@@ -1239,10 +1254,10 @@ exports.extractPinnedBody = extractPinnedBody;
|
|
|
1239
1254
|
exports.extractQueryParams = extractQueryParams;
|
|
1240
1255
|
exports.getJsonSchemaType = getJsonSchemaType;
|
|
1241
1256
|
exports.openApiToToolDefinitions = openApiToToolDefinitions;
|
|
1257
|
+
exports.openApiToTools = openApiToTools;
|
|
1242
1258
|
exports.operationIdToToolName = operationIdToToolName;
|
|
1243
1259
|
exports.processOperation = processOperation;
|
|
1244
1260
|
exports.processPath = processPath;
|
|
1245
|
-
exports.registerOpenApiTools = registerOpenApiTools;
|
|
1246
1261
|
exports.resolveParameter = resolveParameter;
|
|
1247
1262
|
exports.resolveSchema = resolveSchema;
|
|
1248
1263
|
exports.snakeToCamel = snakeToCamel;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { JsonObjectSchema,
|
|
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,6 +49,8 @@ interface ToolDefinition {
|
|
|
49
49
|
pathTemplate: string;
|
|
50
50
|
/** The operation's `operationId`. */
|
|
51
51
|
operationId: string;
|
|
52
|
+
/** The operation's `tags`, e.g. to filter the deferred `search` by module. */
|
|
53
|
+
tags: string[];
|
|
52
54
|
/** Builds the request path, substituting path params from the args. */
|
|
53
55
|
path: (args: Record<string, unknown>) => string;
|
|
54
56
|
/** Builds the query string (including leading `?`), or `undefined` if none. */
|
|
@@ -73,7 +75,7 @@ interface ToolDefinition {
|
|
|
73
75
|
* Path and query parameters flagged with the server-managed extension. They
|
|
74
76
|
* are absent from `inputSchema`, but `path` / `query` still read them from
|
|
75
77
|
* the args under `argName`, so the consumer fills them before building the
|
|
76
|
-
* request — `
|
|
78
|
+
* request — `openApiToTools` does it through `serverParameters`.
|
|
77
79
|
*/
|
|
78
80
|
serverManagedParameters: ServerManagedParameter[];
|
|
79
81
|
}
|
|
@@ -122,6 +124,7 @@ type RequestBodySpec = {
|
|
|
122
124
|
};
|
|
123
125
|
interface OperationSpec {
|
|
124
126
|
operationId?: string;
|
|
127
|
+
tags?: string[];
|
|
125
128
|
summary?: string;
|
|
126
129
|
description?: string;
|
|
127
130
|
parameters?: Array<{
|
|
@@ -342,7 +345,7 @@ declare const buildFullInputSchema: (args: {
|
|
|
342
345
|
bodyProps: FullBodyProp[];
|
|
343
346
|
}) => JsonObjectSchema;
|
|
344
347
|
//#endregion
|
|
345
|
-
//#region src/
|
|
348
|
+
//#region src/openApiToTools.d.ts
|
|
346
349
|
/** The resolved HTTP request a tool call maps to, before transport concerns. */
|
|
347
350
|
interface ResolvedRequest {
|
|
348
351
|
/** Uppercase HTTP method. */
|
|
@@ -360,9 +363,7 @@ interface ResolvedRequest {
|
|
|
360
363
|
*/
|
|
361
364
|
headers: Record<string, string>;
|
|
362
365
|
}
|
|
363
|
-
interface
|
|
364
|
-
/** The MCP server the generated tools are registered on. */
|
|
365
|
-
server: McpServer;
|
|
366
|
+
interface OpenApiToToolsArgs {
|
|
366
367
|
/** One or more OpenAPI documents to derive tools from. */
|
|
367
368
|
spec: OpenApiSpec | OpenApiSpec[];
|
|
368
369
|
/** Tuning options forwarded to {@link openApiToToolDefinitions}. */
|
|
@@ -399,7 +400,7 @@ interface RegisterOpenApiToolsArgs {
|
|
|
399
400
|
/**
|
|
400
401
|
* Builds each tool's `_meta`, advertised on `tools/list`. This is how a
|
|
401
402
|
* generated tool links to an MCP Apps view: return the bag from
|
|
402
|
-
* `registerAppResource(...).toolMeta()`. `undefined`
|
|
403
|
+
* `registerAppResource(...).toolMeta()`. `undefined` sets no `_meta`.
|
|
403
404
|
*
|
|
404
405
|
* @example
|
|
405
406
|
* ```typescript
|
|
@@ -427,25 +428,41 @@ interface RegisterOpenApiToolsArgs {
|
|
|
427
428
|
tool: ToolDefinition;
|
|
428
429
|
headers: Record<string, string>;
|
|
429
430
|
}) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
431
|
+
/**
|
|
432
|
+
* Sets every tool's `validateArguments`: enforce the generated
|
|
433
|
+
* `inputSchema` before `callApi` runs. Leave it off unless the spec
|
|
434
|
+
* describes every value the API accepts — see `Tool.validateArguments`.
|
|
435
|
+
* @default false
|
|
436
|
+
*/
|
|
437
|
+
validateArguments?: boolean;
|
|
438
|
+
}
|
|
439
|
+
/** A {@link Tool} derived from one OpenAPI operation. */
|
|
440
|
+
interface OpenApiTool extends Tool {
|
|
441
|
+
/**
|
|
442
|
+
* The operation it was derived from — method, path, extensions — for
|
|
443
|
+
* decisions that key on the API rather than the tool name, such as which
|
|
444
|
+
* scope a gate requires.
|
|
445
|
+
*/
|
|
446
|
+
definition: ToolDefinition;
|
|
430
447
|
}
|
|
431
448
|
/** The text the default `toText` answers when the API returned no body. */
|
|
432
449
|
declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
|
|
433
450
|
/**
|
|
434
|
-
* Derives MCP tools from OpenAPI document(s)
|
|
435
|
-
*
|
|
436
|
-
*
|
|
451
|
+
* Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
|
|
452
|
+
* the incoming args into a concrete HTTP request and delegates execution to
|
|
453
|
+
* `callApi`. Register the result with `registerTools` from
|
|
454
|
+
* `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
|
|
437
455
|
*
|
|
438
|
-
* @returns
|
|
456
|
+
* @returns One {@link OpenApiTool} per translatable operation.
|
|
439
457
|
*
|
|
440
458
|
* @example
|
|
441
459
|
* ```typescript
|
|
442
|
-
* import { McpServer } from '@ttoss/http-server-mcp';
|
|
443
|
-
* import {
|
|
460
|
+
* import { McpServer, registerTools } from '@ttoss/http-server-mcp';
|
|
461
|
+
* import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
|
|
444
462
|
*
|
|
445
463
|
* const server = new McpServer({ name: 'my-api', version: '1.0.0' });
|
|
446
464
|
*
|
|
447
|
-
*
|
|
448
|
-
* server,
|
|
465
|
+
* const tools = openApiToTools({
|
|
449
466
|
* spec: myOpenApiDocument,
|
|
450
467
|
* callApi: async ({ method, url, body, headers }) => {
|
|
451
468
|
* const res = await fetch(`https://api.example.com${url}`, {
|
|
@@ -456,9 +473,11 @@ declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
|
|
|
456
473
|
* return res.json();
|
|
457
474
|
* },
|
|
458
475
|
* });
|
|
476
|
+
*
|
|
477
|
+
* registerTools({ server, tools });
|
|
459
478
|
* ```
|
|
460
479
|
*/
|
|
461
|
-
declare const
|
|
480
|
+
declare const openApiToTools: (args: OpenApiToToolsArgs) => OpenApiTool[];
|
|
462
481
|
//#endregion
|
|
463
482
|
//#region src/inProcessCallApi.d.ts
|
|
464
483
|
interface CreateInProcessCallApiArgs {
|
|
@@ -487,7 +506,7 @@ interface CreateInProcessCallApiArgs {
|
|
|
487
506
|
*/
|
|
488
507
|
declare const errorMessageOf: (body: unknown) => string | null;
|
|
489
508
|
/**
|
|
490
|
-
* A `callApi` for {@link
|
|
509
|
+
* A `callApi` for {@link openApiToTools} that serves each tool call
|
|
491
510
|
* against the REST app in this same process, with no socket: validation,
|
|
492
511
|
* authorization and error handling run once, in the routes, for both
|
|
493
512
|
* surfaces.
|
|
@@ -499,10 +518,9 @@ declare const errorMessageOf: (body: unknown) => string | null;
|
|
|
499
518
|
*
|
|
500
519
|
* @example
|
|
501
520
|
* ```typescript
|
|
502
|
-
*
|
|
521
|
+
* registerTools({
|
|
503
522
|
* server,
|
|
504
|
-
* spec,
|
|
505
|
-
* callApi: createInProcessCallApi({ app }),
|
|
523
|
+
* tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
|
|
506
524
|
* });
|
|
507
525
|
* ```
|
|
508
526
|
*/
|
|
@@ -650,4 +668,4 @@ declare const openApiToToolDefinitions: (args: {
|
|
|
650
668
|
options?: OpenApiToToolsOptions;
|
|
651
669
|
}) => ToolDefinition[];
|
|
652
670
|
//#endregion
|
|
653
|
-
export { type CreateInProcessCallApiArgs, DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsOptions, type
|
|
671
|
+
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,
|
|
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,6 +49,8 @@ interface ToolDefinition {
|
|
|
49
49
|
pathTemplate: string;
|
|
50
50
|
/** The operation's `operationId`. */
|
|
51
51
|
operationId: string;
|
|
52
|
+
/** The operation's `tags`, e.g. to filter the deferred `search` by module. */
|
|
53
|
+
tags: string[];
|
|
52
54
|
/** Builds the request path, substituting path params from the args. */
|
|
53
55
|
path: (args: Record<string, unknown>) => string;
|
|
54
56
|
/** Builds the query string (including leading `?`), or `undefined` if none. */
|
|
@@ -73,7 +75,7 @@ interface ToolDefinition {
|
|
|
73
75
|
* Path and query parameters flagged with the server-managed extension. They
|
|
74
76
|
* are absent from `inputSchema`, but `path` / `query` still read them from
|
|
75
77
|
* the args under `argName`, so the consumer fills them before building the
|
|
76
|
-
* request — `
|
|
78
|
+
* request — `openApiToTools` does it through `serverParameters`.
|
|
77
79
|
*/
|
|
78
80
|
serverManagedParameters: ServerManagedParameter[];
|
|
79
81
|
}
|
|
@@ -122,6 +124,7 @@ type RequestBodySpec = {
|
|
|
122
124
|
};
|
|
123
125
|
interface OperationSpec {
|
|
124
126
|
operationId?: string;
|
|
127
|
+
tags?: string[];
|
|
125
128
|
summary?: string;
|
|
126
129
|
description?: string;
|
|
127
130
|
parameters?: Array<{
|
|
@@ -342,7 +345,7 @@ declare const buildFullInputSchema: (args: {
|
|
|
342
345
|
bodyProps: FullBodyProp[];
|
|
343
346
|
}) => JsonObjectSchema;
|
|
344
347
|
//#endregion
|
|
345
|
-
//#region src/
|
|
348
|
+
//#region src/openApiToTools.d.ts
|
|
346
349
|
/** The resolved HTTP request a tool call maps to, before transport concerns. */
|
|
347
350
|
interface ResolvedRequest {
|
|
348
351
|
/** Uppercase HTTP method. */
|
|
@@ -360,9 +363,7 @@ interface ResolvedRequest {
|
|
|
360
363
|
*/
|
|
361
364
|
headers: Record<string, string>;
|
|
362
365
|
}
|
|
363
|
-
interface
|
|
364
|
-
/** The MCP server the generated tools are registered on. */
|
|
365
|
-
server: McpServer;
|
|
366
|
+
interface OpenApiToToolsArgs {
|
|
366
367
|
/** One or more OpenAPI documents to derive tools from. */
|
|
367
368
|
spec: OpenApiSpec | OpenApiSpec[];
|
|
368
369
|
/** Tuning options forwarded to {@link openApiToToolDefinitions}. */
|
|
@@ -399,7 +400,7 @@ interface RegisterOpenApiToolsArgs {
|
|
|
399
400
|
/**
|
|
400
401
|
* Builds each tool's `_meta`, advertised on `tools/list`. This is how a
|
|
401
402
|
* generated tool links to an MCP Apps view: return the bag from
|
|
402
|
-
* `registerAppResource(...).toolMeta()`. `undefined`
|
|
403
|
+
* `registerAppResource(...).toolMeta()`. `undefined` sets no `_meta`.
|
|
403
404
|
*
|
|
404
405
|
* @example
|
|
405
406
|
* ```typescript
|
|
@@ -427,25 +428,41 @@ interface RegisterOpenApiToolsArgs {
|
|
|
427
428
|
tool: ToolDefinition;
|
|
428
429
|
headers: Record<string, string>;
|
|
429
430
|
}) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
431
|
+
/**
|
|
432
|
+
* Sets every tool's `validateArguments`: enforce the generated
|
|
433
|
+
* `inputSchema` before `callApi` runs. Leave it off unless the spec
|
|
434
|
+
* describes every value the API accepts — see `Tool.validateArguments`.
|
|
435
|
+
* @default false
|
|
436
|
+
*/
|
|
437
|
+
validateArguments?: boolean;
|
|
438
|
+
}
|
|
439
|
+
/** A {@link Tool} derived from one OpenAPI operation. */
|
|
440
|
+
interface OpenApiTool extends Tool {
|
|
441
|
+
/**
|
|
442
|
+
* The operation it was derived from — method, path, extensions — for
|
|
443
|
+
* decisions that key on the API rather than the tool name, such as which
|
|
444
|
+
* scope a gate requires.
|
|
445
|
+
*/
|
|
446
|
+
definition: ToolDefinition;
|
|
430
447
|
}
|
|
431
448
|
/** The text the default `toText` answers when the API returned no body. */
|
|
432
449
|
declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
|
|
433
450
|
/**
|
|
434
|
-
* Derives MCP tools from OpenAPI document(s)
|
|
435
|
-
*
|
|
436
|
-
*
|
|
451
|
+
* Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
|
|
452
|
+
* the incoming args into a concrete HTTP request and delegates execution to
|
|
453
|
+
* `callApi`. Register the result with `registerTools` from
|
|
454
|
+
* `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
|
|
437
455
|
*
|
|
438
|
-
* @returns
|
|
456
|
+
* @returns One {@link OpenApiTool} per translatable operation.
|
|
439
457
|
*
|
|
440
458
|
* @example
|
|
441
459
|
* ```typescript
|
|
442
|
-
* import { McpServer } from '@ttoss/http-server-mcp';
|
|
443
|
-
* import {
|
|
460
|
+
* import { McpServer, registerTools } from '@ttoss/http-server-mcp';
|
|
461
|
+
* import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
|
|
444
462
|
*
|
|
445
463
|
* const server = new McpServer({ name: 'my-api', version: '1.0.0' });
|
|
446
464
|
*
|
|
447
|
-
*
|
|
448
|
-
* server,
|
|
465
|
+
* const tools = openApiToTools({
|
|
449
466
|
* spec: myOpenApiDocument,
|
|
450
467
|
* callApi: async ({ method, url, body, headers }) => {
|
|
451
468
|
* const res = await fetch(`https://api.example.com${url}`, {
|
|
@@ -456,9 +473,11 @@ declare const NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
|
|
|
456
473
|
* return res.json();
|
|
457
474
|
* },
|
|
458
475
|
* });
|
|
476
|
+
*
|
|
477
|
+
* registerTools({ server, tools });
|
|
459
478
|
* ```
|
|
460
479
|
*/
|
|
461
|
-
declare const
|
|
480
|
+
declare const openApiToTools: (args: OpenApiToToolsArgs) => OpenApiTool[];
|
|
462
481
|
//#endregion
|
|
463
482
|
//#region src/inProcessCallApi.d.ts
|
|
464
483
|
interface CreateInProcessCallApiArgs {
|
|
@@ -487,7 +506,7 @@ interface CreateInProcessCallApiArgs {
|
|
|
487
506
|
*/
|
|
488
507
|
declare const errorMessageOf: (body: unknown) => string | null;
|
|
489
508
|
/**
|
|
490
|
-
* A `callApi` for {@link
|
|
509
|
+
* A `callApi` for {@link openApiToTools} that serves each tool call
|
|
491
510
|
* against the REST app in this same process, with no socket: validation,
|
|
492
511
|
* authorization and error handling run once, in the routes, for both
|
|
493
512
|
* surfaces.
|
|
@@ -499,10 +518,9 @@ declare const errorMessageOf: (body: unknown) => string | null;
|
|
|
499
518
|
*
|
|
500
519
|
* @example
|
|
501
520
|
* ```typescript
|
|
502
|
-
*
|
|
521
|
+
* registerTools({
|
|
503
522
|
* server,
|
|
504
|
-
* spec,
|
|
505
|
-
* callApi: createInProcessCallApi({ app }),
|
|
523
|
+
* tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
|
|
506
524
|
* });
|
|
507
525
|
* ```
|
|
508
526
|
*/
|
|
@@ -650,4 +668,4 @@ declare const openApiToToolDefinitions: (args: {
|
|
|
650
668
|
options?: OpenApiToToolsOptions;
|
|
651
669
|
}) => ToolDefinition[];
|
|
652
670
|
//#endregion
|
|
653
|
-
export { type CreateInProcessCallApiArgs, DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, NO_CONTENT_TEXT, type OpenApiDocuments, type OpenApiSpec, type OpenApiToToolsOptions, type
|
|
671
|
+
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
|
|
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 `
|
|
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
|
|
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
|
-
*
|
|
824
|
+
* registerTools({
|
|
825
825
|
* server,
|
|
826
|
-
* spec,
|
|
827
|
-
* callApi: createInProcessCallApi({ app }),
|
|
826
|
+
* tools: openApiToTools({ spec, callApi: createInProcessCallApi({ app }) }),
|
|
828
827
|
* });
|
|
829
828
|
* ```
|
|
830
829
|
*/
|
|
@@ -989,6 +988,12 @@ var extractExtensions = operation => {
|
|
|
989
988
|
for (const [key, value] of Object.entries(operation)) if (key.startsWith("x-")) extensions[key] = value;
|
|
990
989
|
return extensions;
|
|
991
990
|
};
|
|
991
|
+
/** The operation's tags, ignoring entries a malformed spec typed otherwise. */
|
|
992
|
+
var operationTags = operation => {
|
|
993
|
+
return (operation.tags ?? []).filter(tag => {
|
|
994
|
+
return typeof tag === "string";
|
|
995
|
+
});
|
|
996
|
+
};
|
|
992
997
|
var processOperation = args => {
|
|
993
998
|
const httpMethod = args.method.toUpperCase();
|
|
994
999
|
if (!SUPPORTED_METHODS.includes(httpMethod) || !args.operation.operationId || args.operation[args.options.excludeExtension]) return null;
|
|
@@ -1039,6 +1044,7 @@ var processOperation = args => {
|
|
|
1039
1044
|
method: httpMethod,
|
|
1040
1045
|
pathTemplate: args.pathTemplate,
|
|
1041
1046
|
operationId: args.operation.operationId,
|
|
1047
|
+
tags: operationTags(args.operation),
|
|
1042
1048
|
path: withPinned({
|
|
1043
1049
|
build: buildPathFn(args.pathTemplate, pathParams),
|
|
1044
1050
|
pinned
|
|
@@ -1117,7 +1123,7 @@ var openApiToToolDefinitions = args => {
|
|
|
1117
1123
|
};
|
|
1118
1124
|
|
|
1119
1125
|
//#endregion
|
|
1120
|
-
//#region src/
|
|
1126
|
+
//#region src/openApiToTools.ts
|
|
1121
1127
|
/** The text the default `toText` answers when the API returned no body. */
|
|
1122
1128
|
var NO_CONTENT_TEXT = "Succeeded. The operation returned no content.";
|
|
1123
1129
|
var defaultToText = data => {
|
|
@@ -1143,21 +1149,21 @@ var applyServerParameters = async args => {
|
|
|
1143
1149
|
return result;
|
|
1144
1150
|
};
|
|
1145
1151
|
/**
|
|
1146
|
-
* Derives MCP tools from OpenAPI document(s)
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1152
|
+
* Derives MCP tools from OpenAPI document(s). Each tool's handler resolves
|
|
1153
|
+
* the incoming args into a concrete HTTP request and delegates execution to
|
|
1154
|
+
* `callApi`. Register the result with `registerTools` from
|
|
1155
|
+
* `@ttoss/http-server-mcp` — one MCP tool each, or deferred.
|
|
1149
1156
|
*
|
|
1150
|
-
* @returns
|
|
1157
|
+
* @returns One {@link OpenApiTool} per translatable operation.
|
|
1151
1158
|
*
|
|
1152
1159
|
* @example
|
|
1153
1160
|
* ```typescript
|
|
1154
|
-
* import { McpServer } from '@ttoss/http-server-mcp';
|
|
1155
|
-
* import {
|
|
1161
|
+
* import { McpServer, registerTools } from '@ttoss/http-server-mcp';
|
|
1162
|
+
* import { openApiToTools } from '@ttoss/http-server-mcp-openapi';
|
|
1156
1163
|
*
|
|
1157
1164
|
* const server = new McpServer({ name: 'my-api', version: '1.0.0' });
|
|
1158
1165
|
*
|
|
1159
|
-
*
|
|
1160
|
-
* server,
|
|
1166
|
+
* const tools = openApiToTools({
|
|
1161
1167
|
* spec: myOpenApiDocument,
|
|
1162
1168
|
* callApi: async ({ method, url, body, headers }) => {
|
|
1163
1169
|
* const res = await fetch(`https://api.example.com${url}`, {
|
|
@@ -1168,54 +1174,63 @@ var applyServerParameters = async args => {
|
|
|
1168
1174
|
* return res.json();
|
|
1169
1175
|
* },
|
|
1170
1176
|
* });
|
|
1177
|
+
*
|
|
1178
|
+
* registerTools({ server, tools });
|
|
1171
1179
|
* ```
|
|
1172
1180
|
*/
|
|
1173
|
-
var
|
|
1181
|
+
var openApiToTools = args => {
|
|
1174
1182
|
const toText = args.toText ?? defaultToText;
|
|
1175
|
-
|
|
1183
|
+
return openApiToToolDefinitions({
|
|
1176
1184
|
spec: args.spec,
|
|
1177
1185
|
options: args.options
|
|
1186
|
+
}).map(definition => {
|
|
1187
|
+
const route = `${definition.method} ${definition.pathTemplate}`;
|
|
1188
|
+
return {
|
|
1189
|
+
name: definition.name,
|
|
1190
|
+
description: definition.description,
|
|
1191
|
+
inputSchema: definition.inputSchema,
|
|
1192
|
+
validateArguments: args.validateArguments ?? false,
|
|
1193
|
+
...(definition.tags.length > 0 ? {
|
|
1194
|
+
tags: definition.tags
|
|
1195
|
+
} : {}),
|
|
1196
|
+
summary: definition.description ? `${route} — ${definition.description}` : route,
|
|
1197
|
+
_meta: args.toolMeta?.({
|
|
1198
|
+
tool: definition
|
|
1199
|
+
}),
|
|
1200
|
+
definition,
|
|
1201
|
+
handler: async rawArgs => {
|
|
1202
|
+
const headers = getApiHeaders();
|
|
1203
|
+
const handlerArgs = await applyServerParameters({
|
|
1204
|
+
tool: definition,
|
|
1205
|
+
handlerArgs: rawArgs,
|
|
1206
|
+
headers,
|
|
1207
|
+
serverParameters: args.serverParameters
|
|
1208
|
+
});
|
|
1209
|
+
const url = definition.path(handlerArgs) + (definition.query ? definition.query(handlerArgs) : "");
|
|
1210
|
+
const data = await args.callApi({
|
|
1211
|
+
method: definition.method,
|
|
1212
|
+
url,
|
|
1213
|
+
body: definition.body ? definition.body(handlerArgs) : void 0,
|
|
1214
|
+
tool: definition,
|
|
1215
|
+
headers
|
|
1216
|
+
});
|
|
1217
|
+
const structuredContent = args.toStructuredContent?.({
|
|
1218
|
+
data,
|
|
1219
|
+
tool: definition
|
|
1220
|
+
});
|
|
1221
|
+
return {
|
|
1222
|
+
content: [{
|
|
1223
|
+
type: "text",
|
|
1224
|
+
text: toText(data)
|
|
1225
|
+
}],
|
|
1226
|
+
...(structuredContent === void 0 ? {} : {
|
|
1227
|
+
structuredContent
|
|
1228
|
+
})
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1178
1232
|
});
|
|
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
1233
|
};
|
|
1219
1234
|
|
|
1220
1235
|
//#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,
|
|
1236
|
+
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.
|
|
3
|
+
"version": "0.8.0",
|
|
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,18 +32,19 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
+
"MIGRATIONS.md",
|
|
35
36
|
"dist"
|
|
36
37
|
],
|
|
37
38
|
"dependencies": {
|
|
38
|
-
"@ttoss/http-server": "^0.
|
|
39
|
-
"@ttoss/http-server
|
|
39
|
+
"@ttoss/http-server-mcp": "^0.34.0",
|
|
40
|
+
"@ttoss/http-server": "^0.11.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
43
44
|
"jest": "^30.4.2",
|
|
44
45
|
"supertest": "^7.2.2",
|
|
45
46
|
"tsdown": "^0.22.2",
|
|
46
|
-
"@ttoss/config": "^1.
|
|
47
|
+
"@ttoss/config": "^1.41.0"
|
|
47
48
|
},
|
|
48
49
|
"publishConfig": {
|
|
49
50
|
"access": "public",
|