@ttoss/http-server-mcp-openapi 0.5.7 → 0.7.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/README.md CHANGED
@@ -89,14 +89,16 @@ repeats array values, `spaceDelimited`/`pipeDelimited` join them, and
89
89
 
90
90
  ## `registerOpenApiTools`
91
91
 
92
- | Field | Description |
93
- | ------------------- | ------------------------------------------------------------------------------------------------------------- |
94
- | `server` | The `McpServer` to register tools on. |
95
- | `spec` | One OpenAPI document, or an array of them (tools are flattened). |
96
- | `callApi` | Runs the resolved `{ method, url, body, tool, headers }` request and returns the raw data. |
97
- | `toText?` | Serialises the raw data into the tool's text payload. Defaults to pretty JSON; strings pass through verbatim. |
98
- | `serverParameters?` | Supplies server-managed path/query parameter values. See [Server-managed values](#server-managed-values). |
99
- | `options?` | See [Options](#options). |
92
+ | Field | Description |
93
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
94
+ | `server` | The `McpServer` to register tools on. |
95
+ | `spec` | One OpenAPI document, or an array of them (tools are flattened). |
96
+ | `callApi` | Runs the resolved `{ method, url, body, tool, headers }` request and returns the raw data. |
97
+ | `toText?` | Serialises the raw data into the tool's text payload. Defaults to pretty JSON; strings pass through verbatim. |
98
+ | `serverParameters?` | Supplies server-managed path/query parameter values. See [Server-managed values](#server-managed-values). |
99
+ | `toStructuredContent?` | `({ data, tool })` → the result's `structuredContent`, sent beside the text. `undefined` keeps it text-only. |
100
+ | `toolMeta?` | `({ tool })` → the tool's `_meta` on `tools/list`. See [MCP Apps views](#mcp-apps-views). |
101
+ | `options?` | See [Options](#options). |
100
102
 
101
103
  The default `toText` answers `NO_CONTENT_TEXT` (`Succeeded. The operation
102
104
  returned no content.`) when `callApi` resolves `undefined` or `''`, so a `204`
@@ -104,6 +106,75 @@ reaches the client as a success.
104
106
 
105
107
  Returns the list of `ToolDefinition`s that were registered.
106
108
 
109
+ ### MCP Apps views
110
+
111
+ A generated tool links to a view through `toolMeta`, and the view reads the
112
+ result from `structuredContent`:
113
+
114
+ ```typescript
115
+ import { registerAppResource } from '@ttoss/http-server-mcp';
116
+
117
+ const agentCard = registerAppResource({
118
+ server,
119
+ name: 'agent_card',
120
+ uri: 'ui://agents/card',
121
+ html: agentCardHtml,
122
+ });
123
+
124
+ registerOpenApiTools({
125
+ server,
126
+ spec,
127
+ callApi,
128
+ toolMeta: ({ tool }) => {
129
+ return tool.name === 'get-agent' ? agentCard.toolMeta() : undefined;
130
+ },
131
+ toStructuredContent: ({ data, tool }) => {
132
+ return tool.name === 'get-agent'
133
+ ? (data as Record<string, unknown>)
134
+ : undefined;
135
+ },
136
+ });
137
+ ```
138
+
139
+ Keep the text payload: a host without MCP Apps support renders only that.
140
+
141
+ ## Calling the API In-Process
142
+
143
+ When the REST API runs in the same process as the MCP server,
144
+ `createInProcessCallApi` dispatches each tool call through the app's own
145
+ middleware chain with no socket (see `dispatchInProcess` in
146
+ [@ttoss/http-server](https://ttoss.dev/docs/modules/packages/http-server)), so
147
+ validation, authorization and error handling exist once, in the routes. The MCP
148
+ request's headers — what `createMcpRouter`'s `getApiHeaders` produced — are
149
+ forwarded onto the dispatched request.
150
+
151
+ ```typescript
152
+ import {
153
+ createInProcessCallApi,
154
+ registerOpenApiTools,
155
+ } from '@ttoss/http-server-mcp-openapi';
156
+
157
+ registerOpenApiTools({
158
+ server,
159
+ spec,
160
+ callApi: createInProcessCallApi({
161
+ app, // or () => app, when the app is built after the tools
162
+ headers: () => ({ 'x-via': 'mcp' }), // optional, added to every call
163
+ }),
164
+ });
165
+
166
+ const router = createMcpRouter(server, {
167
+ getApiHeaders: (ctx) => ({ authorization: ctx.headers.authorization ?? '' }),
168
+ });
169
+ app.use(router.routes());
170
+ ```
171
+
172
+ A 2xx answers its body. Anything else throws, so the client sees a tool error
173
+ rather than an error body rendered as a result: the message is read from a
174
+ string body, `{ error: '…' }`, `{ error: { code, message } }` (as
175
+ `code: message`) or `{ message: '…' }` (exported as `errorMessageOf`), falling
176
+ back to `HTTP <status>`. Pass `toError` to build the error yourself.
177
+
107
178
  ## `openApiToToolDefinitions`
108
179
 
109
180
  Use the lower-level function when you want the tool definitions without
@@ -137,6 +208,8 @@ registerOpenApiTools({
137
208
  serverManagedExtension: 'x-mcp-server-managed', // or several: ['x-a', 'x-b']
138
209
  argumentNames: 'camelCase', // or 'verbatim'
139
210
  documents: { './tags.yaml': tagsDocument }, // targets of cross-file $refs
211
+ schemaDetail: 'full', // or 'compact' (default)
212
+ describe: ({ operation, method, pathTemplate }) => operation.summary ?? '',
140
213
  },
141
214
  });
142
215
  ```
@@ -153,6 +226,31 @@ registerOpenApiTools({
153
226
  a sibling resolve against that sibling. A ref to a file missing from the map
154
227
  resolves to an empty schema, which accepts any value.
155
228
 
229
+ - **`schemaDetail`** (default `compact`) — see [Schema detail](#schema-detail).
230
+ - **`describe`** — builds each tool's description from `{ operation, method, pathTemplate }`
231
+ (method uppercase). The default is the operation's `description` flattened to one line.
232
+
233
+ ### Schema detail
234
+
235
+ `compact` gives each top-level argument its `type`, `items` and `description`,
236
+ with descriptions flattened to one line. It is the smallest surface, and the
237
+ model learns nothing about which values are allowed.
238
+
239
+ `full` gives each argument its whole schema: `enum`, `format`, `pattern`,
240
+ `minimum`/`maximum`, `default`, nested `properties` and `required`, `oneOf`,
241
+ `additionalProperties`, and descriptions verbatim. It changes only what JSON
242
+ Schema cannot express:
243
+
244
+ - `allOf` is merged — the properties and `required` of an object composition,
245
+ or a single referenced scalar with the wrapper's own `description` winning;
246
+ - `nullable: true` adds `'null'` to the `type`, and `null` to an `enum`;
247
+ - OpenAPI-only keywords (`example`, `discriminator`, `xml`, `externalDocs`) and
248
+ `x-` extensions are dropped;
249
+ - a path or query parameter's own `description` wins over its schema's.
250
+
251
+ `properties` is always present in `full`, even when empty. The same
252
+ transformation is exported as `toToolSchema`, for a schema you derive yourself.
253
+
156
254
  ### Server-managed values
157
255
 
158
256
  A value flagged with `serverManagedExtension` is never offered to the model:
package/dist/index.cjs CHANGED
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, {
3
3
  value: 'Module'
4
4
  });
5
+ let _ttoss_http_server = require("@ttoss/http-server");
5
6
  let _ttoss_http_server_mcp = require("@ttoss/http-server-mcp");
6
7
 
7
8
  //#region src/schema.ts
8
- var isRecord = value => {
9
+ var isRecord$1 = value => {
9
10
  return typeof value === "object" && value !== null && !Array.isArray(value);
10
11
  };
11
12
  var getAlternativeSchemas = schema => {
@@ -42,7 +43,7 @@ var followPointer = args => {
42
43
  const tokens = args.pointer.split("/").slice(1);
43
44
  let current = args.root;
44
45
  for (const rawToken of tokens) {
45
- if (!isRecord(current) && !Array.isArray(current)) return void 0;
46
+ if (!isRecord$1(current) && !Array.isArray(current)) return void 0;
46
47
  const token = decodeURIComponent(rawToken).replace(/~1/g, "/").replace(/~0/g, "~");
47
48
  current = current[token];
48
49
  }
@@ -101,7 +102,7 @@ var dereferenceValue = args => {
101
102
  seenRefs
102
103
  });
103
104
  });
104
- if (isRecord(value)) {
105
+ if (isRecord$1(value)) {
105
106
  if (typeof value.$ref === "string") {
106
107
  const target = resolveRef({
107
108
  ref: value.$ref,
@@ -181,7 +182,7 @@ var resolveParameter = (param, spec, documents) => {
181
182
  documents
182
183
  }
183
184
  });
184
- return isRecord(target?.value) ? target.value : {};
185
+ return isRecord$1(target?.value) ? target.value : {};
185
186
  }
186
187
  return param;
187
188
  };
@@ -220,7 +221,7 @@ var appendDeepObject = args => {
220
221
  });
221
222
  return;
222
223
  }
223
- if (isRecord(args.value)) {
224
+ if (isRecord$1(args.value)) {
224
225
  for (const [property, propertyValue] of Object.entries(args.value)) appendDeepObject({
225
226
  search: args.search,
226
227
  key: `${args.key}[${property}]`,
@@ -272,7 +273,7 @@ var appendQueryValue = args => {
272
273
  });
273
274
  return;
274
275
  }
275
- if (isRecord(args.value)) {
276
+ if (isRecord$1(args.value)) {
276
277
  appendObjectValue({
277
278
  ...serialization,
278
279
  value: args.value
@@ -501,6 +502,8 @@ var extractPathParams = args => {
501
502
  return {
502
503
  name: p.name || "",
503
504
  argName: toArgName(p.name || ""),
505
+ description: p.description,
506
+ schema: dereferenceSchema(p.schema, args.spec, args.documents),
504
507
  ...managedFields(readServerManaged({
505
508
  node: p,
506
509
  extension: flag
@@ -522,6 +525,7 @@ var extractQueryParams = args => {
522
525
  description: p.description || "",
523
526
  required: p.required || false,
524
527
  type: p.schema?.type || "string",
528
+ schema: dereferenceSchema(p.schema, args.spec, args.documents),
525
529
  style: p.style,
526
530
  explode: p.explode,
527
531
  ...managedFields(readServerManaged({
@@ -614,7 +618,8 @@ var extractBodyProps = args => {
614
618
  nullable: val.nullable === true,
615
619
  oneOf: Array.isArray(val.oneOf) ? val.oneOf : void 0,
616
620
  anyOf: Array.isArray(val.anyOf) ? val.anyOf : void 0,
617
- allOf: Array.isArray(val.allOf) ? val.allOf : void 0
621
+ allOf: Array.isArray(val.allOf) ? val.allOf : void 0,
622
+ schema: value
618
623
  };
619
624
  });
620
625
  };
@@ -644,6 +649,209 @@ var extractPinnedBody = args => {
644
649
  return pinned;
645
650
  };
646
651
 
652
+ //#endregion
653
+ //#region src/fullSchema.ts
654
+ var isRecord = value => {
655
+ return typeof value === "object" && value !== null && !Array.isArray(value);
656
+ };
657
+ var OPENAPI_ONLY_KEYWORDS = new Set(["discriminator", "example", "externalDocs", "xml"]);
658
+ var SCHEMA_MAPS = new Set(["$defs", "definitions", "dependentSchemas", "patternProperties", "properties"]);
659
+ var SCHEMA_VALUES = new Set(["additionalProperties", "contains", "else", "if", "items", "not", "propertyNames", "then", "unevaluatedItems", "unevaluatedProperties"]);
660
+ var SCHEMA_LISTS = new Set(["anyOf", "oneOf", "prefixItems"]);
661
+ /**
662
+ * OpenAPI 3.0's `nullable: true` in JSON Schema: `'null'` joins the `type`,
663
+ * and `null` joins an `enum`, which would otherwise still refuse it.
664
+ */
665
+ var withNull = schema => {
666
+ const result = {
667
+ ...schema
668
+ };
669
+ if (typeof result.type === "string") result.type = [result.type, "null"];else if (Array.isArray(result.type) && !result.type.includes("null")) result.type = [...result.type, "null"];
670
+ if (Array.isArray(result.enum) && !result.enum.includes(null)) result.enum = [...result.enum, null];
671
+ return result;
672
+ };
673
+ /**
674
+ * Merges an `allOf` into one schema, the shape a tool argument needs.
675
+ *
676
+ * Members that declare `properties` are an object composition — a write body
677
+ * that is a patch plus two fields — and merge their properties and `required`.
678
+ * Otherwise the `allOf` only wraps a referenced scalar so it can carry its own
679
+ * `description` (the one way OpenAPI 3.0 allows beside a `$ref`), and the
680
+ * members are merged as they are. Sibling keys win in both, because the
681
+ * wrapper exists to say something the referenced schema does not.
682
+ */
683
+ var mergeAllOf = args => {
684
+ const {
685
+ members,
686
+ siblings
687
+ } = args;
688
+ if (!members.some(member => {
689
+ return member.properties !== void 0;
690
+ })) return Object.assign({}, ...members, siblings);
691
+ const properties = {};
692
+ const required = [];
693
+ for (const member of members) {
694
+ Object.assign(properties, member.properties);
695
+ if (Array.isArray(member.required)) required.push(...member.required);
696
+ }
697
+ return {
698
+ ...siblings,
699
+ type: "object",
700
+ properties,
701
+ ...(required.length > 0 ? {
702
+ required: [...new Set(required)]
703
+ } : {})
704
+ };
705
+ };
706
+ var normalizeChildren = args => {
707
+ const {
708
+ schema,
709
+ next
710
+ } = args;
711
+ const result = {};
712
+ for (const [key, value] of Object.entries(schema)) {
713
+ if (OPENAPI_ONLY_KEYWORDS.has(key) || key.startsWith("x-")) continue;
714
+ if (SCHEMA_MAPS.has(key) && isRecord(value)) result[key] = Object.fromEntries(Object.entries(value).map(([name, child]) => {
715
+ return [name, next(child)];
716
+ }));else if (SCHEMA_VALUES.has(key) && isRecord(value)) result[key] = next(value);else if (SCHEMA_LISTS.has(key) && Array.isArray(value)) result[key] = value.map(next);else result[key] = value;
717
+ }
718
+ return result;
719
+ };
720
+ /**
721
+ * Turns an already-dereferenced OpenAPI schema into the JSON Schema a tool
722
+ * argument carries, keeping every constraint it declares — `enum`, `format`,
723
+ * `pattern`, `minimum`, `default`, nested `properties` and `required`,
724
+ * `oneOf` — and changing only what JSON Schema cannot express: `allOf` is
725
+ * merged, `nullable` becomes a `'null'` type, and OpenAPI-only keywords and
726
+ * `x-` extensions are dropped. Descriptions are kept verbatim.
727
+ */
728
+ var toToolSchema = value => {
729
+ if (!isRecord(value)) return value;
730
+ const {
731
+ allOf,
732
+ nullable,
733
+ ...rest
734
+ } = value;
735
+ const own = normalizeChildren({
736
+ schema: rest,
737
+ next: toToolSchema
738
+ });
739
+ const merged = Array.isArray(allOf) ? mergeAllOf({
740
+ members: allOf.filter(isRecord).map(member => {
741
+ return toToolSchema(member);
742
+ }),
743
+ siblings: own
744
+ }) : own;
745
+ return nullable === true ? withNull(merged) : merged;
746
+ };
747
+ /** A path or query parameter's schema, with the parameter's own description. */
748
+ var paramProperty = param => {
749
+ return {
750
+ ...(toToolSchema(param.schema) ?? {}),
751
+ ...(param.description ? {
752
+ description: param.description
753
+ } : {})
754
+ };
755
+ };
756
+ /**
757
+ * The `inputSchema` of a tool under `schemaDetail: 'full'`: every parameter
758
+ * and body property with its whole schema. `properties` is always present,
759
+ * even when empty, because some clients refuse an object schema without it.
760
+ */
761
+ var buildFullInputSchema = args => {
762
+ const params = [...args.pathParams, ...args.queryParams].filter(param => {
763
+ return !param.serverManaged;
764
+ });
765
+ const properties = {};
766
+ const required = [];
767
+ for (const param of params) {
768
+ properties[param.argName] = paramProperty(param);
769
+ if (param.required) required.push(param.argName);
770
+ }
771
+ for (const prop of args.bodyProps) {
772
+ properties[prop.argName] = toToolSchema(prop.schema);
773
+ if (prop.required) required.push(prop.argName);
774
+ }
775
+ return {
776
+ type: "object",
777
+ properties,
778
+ ...(required.length > 0 ? {
779
+ required: [...new Set(required)]
780
+ } : {})
781
+ };
782
+ };
783
+
784
+ //#endregion
785
+ //#region src/inProcessCallApi.ts
786
+ var nestedMessageOf = error => {
787
+ if (!error || typeof error !== "object") return null;
788
+ const {
789
+ code,
790
+ message
791
+ } = error;
792
+ if (typeof message !== "string") return null;
793
+ return typeof code === "string" ? `${code}: ${message}` : message;
794
+ };
795
+ /**
796
+ * The message an error response carries, read from the envelopes REST APIs
797
+ * commonly answer with: a plain string, `{ error: '…' }`,
798
+ * `{ error: { code, message } }` (as `code: message`) or `{ message: '…' }`.
799
+ * `null` when the body is none of them.
800
+ */
801
+ var errorMessageOf = body => {
802
+ if (typeof body === "string") return body || null;
803
+ if (!body || typeof body !== "object") return null;
804
+ const {
805
+ error,
806
+ message
807
+ } = body;
808
+ if (typeof error === "string") return error;
809
+ return nestedMessageOf(error) ?? (typeof message === "string" ? message : null);
810
+ };
811
+ var defaultToError = response => {
812
+ return new Error(errorMessageOf(response.body) ?? `HTTP ${response.status}`);
813
+ };
814
+ /**
815
+ * A `callApi` for {@link registerOpenApiTools} that serves each tool call
816
+ * against the REST app in this same process, with no socket: validation,
817
+ * authorization and error handling run once, in the routes, for both
818
+ * surfaces.
819
+ *
820
+ * The MCP request's headers (what `createMcpRouter`'s `getApiHeaders`
821
+ * produced — typically the caller's `Authorization`) are forwarded onto the
822
+ * dispatched request. A 2xx answers its body; anything else throws, so the
823
+ * client sees a tool error rather than an error body rendered as a result.
824
+ *
825
+ * @example
826
+ * ```typescript
827
+ * registerOpenApiTools({
828
+ * server,
829
+ * spec,
830
+ * callApi: createInProcessCallApi({ app }),
831
+ * });
832
+ * ```
833
+ */
834
+ var createInProcessCallApi = ({
835
+ app,
836
+ headers,
837
+ toError = defaultToError
838
+ }) => {
839
+ return async request => {
840
+ const response = await (0, _ttoss_http_server.dispatchInProcess)({
841
+ app: typeof app === "function" ? await app() : app,
842
+ method: request.method,
843
+ path: request.url,
844
+ headers: {
845
+ ...request.headers,
846
+ ...headers?.(request)
847
+ },
848
+ body: request.body
849
+ });
850
+ if (response.status < 200 || response.status >= 300) throw toError(response, request);
851
+ return response.body;
852
+ };
853
+ };
854
+
647
855
  //#endregion
648
856
  //#region src/toolDefinitions.ts
649
857
  /** Converts a camelCase `operationId` to a kebab-case tool name. */
@@ -660,6 +868,11 @@ var getJsonSchemaType = schemaType => {
660
868
  var sanitizeDescription = description => {
661
869
  return (description || "").replace(/'/g, "\\'").replace(/\n/g, " ").trim();
662
870
  };
871
+ var defaultDescribe = ({
872
+ operation
873
+ }) => {
874
+ return sanitizeDescription(operation.description);
875
+ };
663
876
  /**
664
877
  * Forwards a property's `oneOf` / `anyOf` / multi-entry `allOf` verbatim, or
665
878
  * returns `undefined` when it declares none.
@@ -716,8 +929,7 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
716
929
  const modelQueryParams = queryParams.filter(p => {
717
930
  return !p.serverManaged;
718
931
  });
719
- const allParams = [...modelPathParams, ...modelQueryParams, ...bodyProps];
720
- if (allParams.length === 0) return {
932
+ if ([...modelPathParams, ...modelQueryParams, ...bodyProps].length === 0) return {
721
933
  type: "object"
722
934
  };
723
935
  const requiredFields = [...modelPathParams.map(p => {
@@ -732,10 +944,11 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
732
944
  return p.argName;
733
945
  })];
734
946
  const properties = {};
735
- for (const param of allParams) properties[param.argName] = "description" in param ? normalizeNullable(buildTypedProperty(param)) : {
947
+ for (const param of modelPathParams) properties[param.argName] = {
736
948
  type: "string",
737
949
  description: ""
738
950
  };
951
+ for (const param of [...modelQueryParams, ...bodyProps]) properties[param.argName] = normalizeNullable(buildTypedProperty(param));
739
952
  return {
740
953
  type: "object",
741
954
  properties,
@@ -744,6 +957,35 @@ var buildInputSchema = (pathParams, queryParams, bodyProps) => {
744
957
  } : {})
745
958
  };
746
959
  };
960
+ var SUPPORTED_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
961
+ /** The tool description, from `options.describe` or the default. */
962
+ var describeOperation = args => {
963
+ const {
964
+ options,
965
+ operation,
966
+ method,
967
+ pathTemplate
968
+ } = args;
969
+ return (options.describe ?? defaultDescribe)({
970
+ operation,
971
+ method,
972
+ pathTemplate
973
+ });
974
+ };
975
+ /** The `inputSchema` at the detail {@link OpenApiToToolsOptions.schemaDetail} asks for. */
976
+ var selectInputSchema = args => {
977
+ if (args.schemaDetail !== "full") return buildInputSchema(args.pathParams, args.queryParams, args.bodyProps);
978
+ return buildFullInputSchema({
979
+ pathParams: args.pathParams.map(param => {
980
+ return {
981
+ ...param,
982
+ required: true
983
+ };
984
+ }),
985
+ queryParams: args.queryParams,
986
+ bodyProps: args.bodyProps
987
+ });
988
+ };
747
989
  /** Collects every `x-` prefixed extension declared on the operation. */
748
990
  var extractExtensions = operation => {
749
991
  const extensions = {};
@@ -752,9 +994,7 @@ var extractExtensions = operation => {
752
994
  };
753
995
  var processOperation = args => {
754
996
  const httpMethod = args.method.toUpperCase();
755
- if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(httpMethod)) return null;
756
- if (!args.operation.operationId) return null;
757
- if (args.operation[args.options.excludeExtension]) return null;
997
+ if (!SUPPORTED_METHODS.includes(httpMethod) || !args.operation.operationId || args.operation[args.options.excludeExtension]) return null;
758
998
  const toolName = operationIdToToolName(args.operation.operationId);
759
999
  const parameters = [...(args.pathItemParameters ?? []), ...(args.operation.parameters ?? [])];
760
1000
  const {
@@ -762,20 +1002,15 @@ var processOperation = args => {
762
1002
  serverManagedExtension
763
1003
  } = args.options;
764
1004
  const toArgName = argNameMapper(args.options.argumentNames);
765
- const pathParams = extractPathParams({
1005
+ const paramArgs = {
766
1006
  parameters,
767
1007
  spec: args.spec,
768
1008
  documents,
769
1009
  toArgName,
770
1010
  serverManagedExtension
771
- });
772
- const queryParams = extractQueryParams({
773
- parameters,
774
- spec: args.spec,
775
- documents,
776
- toArgName,
777
- serverManagedExtension
778
- });
1011
+ };
1012
+ const pathParams = extractPathParams(paramArgs);
1013
+ const queryParams = extractQueryParams(paramArgs);
779
1014
  const bodyArgs = {
780
1015
  requestBody: args.operation.requestBody,
781
1016
  spec: args.spec,
@@ -786,7 +1021,12 @@ var processOperation = args => {
786
1021
  ...bodyArgs,
787
1022
  toArgName
788
1023
  });
789
- const inputSchema = buildInputSchema(pathParams, queryParams, bodyProps);
1024
+ const inputSchema = selectInputSchema({
1025
+ schemaDetail: args.options.schemaDetail,
1026
+ pathParams,
1027
+ queryParams,
1028
+ bodyProps
1029
+ });
790
1030
  const serverManagedParameters = collectServerManagedParameters({
791
1031
  pathParams,
792
1032
  queryParams
@@ -794,7 +1034,10 @@ var processOperation = args => {
794
1034
  const pinned = pinnedArgs(serverManagedParameters);
795
1035
  return {
796
1036
  name: toolName,
797
- description: sanitizeDescription(args.operation.description),
1037
+ description: describeOperation({
1038
+ ...args,
1039
+ method: httpMethod
1040
+ }),
798
1041
  inputSchema,
799
1042
  method: httpMethod,
800
1043
  pathTemplate: args.pathTemplate,
@@ -843,6 +1086,8 @@ var resolveOptions = (options = {}) => {
843
1086
  excludeExtension: options.excludeExtension ?? "x-mcp-exclude",
844
1087
  serverManagedExtension: options.serverManagedExtension ?? "x-mcp-server-managed",
845
1088
  argumentNames: options.argumentNames ?? "camelCase",
1089
+ schemaDetail: options.schemaDetail,
1090
+ describe: options.describe,
846
1091
  documents: options.documents
847
1092
  };
848
1093
  };
@@ -938,6 +1183,9 @@ var registerOpenApiTools = args => {
938
1183
  name: tool.name,
939
1184
  description: tool.description,
940
1185
  inputSchema: tool.inputSchema,
1186
+ _meta: args.toolMeta?.({
1187
+ tool
1188
+ }),
941
1189
  handler: async rawArgs => {
942
1190
  const headers = (0, _ttoss_http_server_mcp.getApiHeaders)();
943
1191
  const handlerArgs = await applyServerParameters({
@@ -947,17 +1195,25 @@ var registerOpenApiTools = args => {
947
1195
  serverParameters: args.serverParameters
948
1196
  });
949
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
+ });
950
1209
  return {
951
1210
  content: [{
952
1211
  type: "text",
953
- text: toText(await args.callApi({
954
- method: tool.method,
955
- url,
956
- body: tool.body ? tool.body(handlerArgs) : void 0,
957
- tool,
958
- headers
959
- }))
960
- }]
1212
+ text: toText(data)
1213
+ }],
1214
+ ...(structuredContent === void 0 ? {} : {
1215
+ structuredContent
1216
+ })
961
1217
  };
962
1218
  }
963
1219
  });
@@ -969,10 +1225,13 @@ exports.DEFAULT_EXCLUDE_EXTENSION = DEFAULT_EXCLUDE_EXTENSION;
969
1225
  exports.DEFAULT_SERVER_MANAGED_EXTENSION = DEFAULT_SERVER_MANAGED_EXTENSION;
970
1226
  exports.NO_CONTENT_TEXT = NO_CONTENT_TEXT;
971
1227
  exports.buildBodyFn = buildBodyFn;
1228
+ exports.buildFullInputSchema = buildFullInputSchema;
972
1229
  exports.buildInputSchema = buildInputSchema;
973
1230
  exports.buildPathFn = buildPathFn;
974
1231
  exports.buildQueryFn = buildQueryFn;
1232
+ exports.createInProcessCallApi = createInProcessCallApi;
975
1233
  exports.dereferenceSchema = dereferenceSchema;
1234
+ exports.errorMessageOf = errorMessageOf;
976
1235
  exports.extractAcceptedBodyFields = extractAcceptedBodyFields;
977
1236
  exports.extractBodyProps = extractBodyProps;
978
1237
  exports.extractPathParams = extractPathParams;
@@ -986,4 +1245,5 @@ exports.processPath = processPath;
986
1245
  exports.registerOpenApiTools = registerOpenApiTools;
987
1246
  exports.resolveParameter = resolveParameter;
988
1247
  exports.resolveSchema = resolveSchema;
989
- exports.snakeToCamel = snakeToCamel;
1248
+ exports.snakeToCamel = snakeToCamel;
1249
+ exports.toToolSchema = toToolSchema;