@ttoss/http-server-mcp-openapi 0.2.12 → 0.2.14

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
@@ -69,6 +69,11 @@ path-item one with the same `name`+`in`.
69
69
  Tool arguments are **camelCase** (`agentId`, `projectId`); the generated
70
70
  request path, query string, and body use the original **snake_case** names.
71
71
 
72
+ Query params honour their declared `style` and `explode`. `form` (the default)
73
+ repeats array values, `spaceDelimited`/`pipeDelimited` join them, and
74
+ `deepObject` emits bracketed keys — including nested objects and arrays, so
75
+ `{ documentId: { $eq: 'doc_1' } }` becomes `filters[documentId][$eq]=doc_1`.
76
+
72
77
  ## `registerOpenApiTools`
73
78
 
74
79
  | Field | Description |
package/dist/index.cjs CHANGED
@@ -117,22 +117,108 @@ var buildPathFn = (pathTemplate, pathParams) => {
117
117
  return result;
118
118
  };
119
119
  };
120
+ var NON_EXPLODED_DELIMITERS = {
121
+ form: ",",
122
+ spaceDelimited: " ",
123
+ pipeDelimited: "|"
124
+ };
125
+ var isRecord = value => {
126
+ return typeof value === "object" && value !== null && !Array.isArray(value);
127
+ };
128
+ /**
129
+ * Appends a `deepObject` value as bracketed keys — `filters[documentId][$eq]`.
130
+ * OpenAPI only defines one level, but the APIs that ask for `deepObject`
131
+ * (Strapi, Directus) nest, so nested objects and arrays recurse instead of
132
+ * being stringified into `[object Object]`.
133
+ */
134
+ var appendDeepObject = args => {
135
+ if (args.value === void 0 || args.value === null) return;
136
+ if (Array.isArray(args.value)) {
137
+ for (const [index, item] of args.value.entries()) appendDeepObject({
138
+ search: args.search,
139
+ key: `${args.key}[${index}]`,
140
+ value: item
141
+ });
142
+ return;
143
+ }
144
+ if (isRecord(args.value)) {
145
+ for (const [property, propertyValue] of Object.entries(args.value)) appendDeepObject({
146
+ search: args.search,
147
+ key: `${args.key}[${property}]`,
148
+ value: propertyValue
149
+ });
150
+ return;
151
+ }
152
+ args.search.append(args.key, String(args.value));
153
+ };
154
+ var appendArrayValue = args => {
155
+ if (args.explode) {
156
+ for (const item of args.value) args.search.append(args.name, String(item));
157
+ return;
158
+ }
159
+ args.search.append(args.name, args.value.map(String).join(args.delimiter));
160
+ };
161
+ var appendObjectValue = args => {
162
+ const entries = Object.entries(args.value).filter(([, entryValue]) => {
163
+ return entryValue !== void 0 && entryValue !== null;
164
+ });
165
+ if (args.explode) {
166
+ for (const [property, entryValue] of entries) args.search.append(property, String(entryValue));
167
+ return;
168
+ }
169
+ args.search.append(args.name, entries.flatMap(([property, entryValue]) => {
170
+ return [property, String(entryValue)];
171
+ }).join(args.delimiter));
172
+ };
173
+ var appendQueryValue = args => {
174
+ const style = args.param.style ?? "form";
175
+ if (style === "deepObject") {
176
+ appendDeepObject({
177
+ search: args.search,
178
+ key: args.param.name,
179
+ value: args.value
180
+ });
181
+ return;
182
+ }
183
+ const serialization = {
184
+ search: args.search,
185
+ name: args.param.name,
186
+ explode: args.param.explode ?? style === "form",
187
+ delimiter: NON_EXPLODED_DELIMITERS[style] ?? ","
188
+ };
189
+ if (Array.isArray(args.value)) {
190
+ appendArrayValue({
191
+ ...serialization,
192
+ value: args.value
193
+ });
194
+ return;
195
+ }
196
+ if (isRecord(args.value)) {
197
+ appendObjectValue({
198
+ ...serialization,
199
+ value: args.value
200
+ });
201
+ return;
202
+ }
203
+ args.search.append(args.param.name, String(args.value));
204
+ };
120
205
  /**
121
206
  * Builds a function that serialises query params into a query string
122
- * (including the leading `?`). Returns `undefined` when the op has no query
123
- * params. Array values are appended once per element.
207
+ * (including the leading `?`), honouring each param's OpenAPI `style` and
208
+ * `explode`. Returns `undefined` when the op has no query params.
124
209
  */
125
210
  var buildQueryFn = queryParams => {
126
211
  if (queryParams.length === 0) return void 0;
127
212
  return args => {
128
213
  const search = new URLSearchParams();
129
- for (const {
130
- name,
131
- camelName
132
- } of queryParams) {
133
- const value = args[camelName];
214
+ for (const param of queryParams) {
215
+ const value = args[param.camelName];
134
216
  if (value === void 0 || value === null) continue;
135
- if (Array.isArray(value)) for (const item of value) search.append(name, String(item));else search.append(name, String(value));
217
+ appendQueryValue({
218
+ search,
219
+ param,
220
+ value
221
+ });
136
222
  }
137
223
  const qs = search.toString();
138
224
  return qs ? `?${qs}` : "";
@@ -275,7 +361,9 @@ var extractQueryParams = args => {
275
361
  camelName: snakeToCamel(p.name || ""),
276
362
  description: p.description || "",
277
363
  required: p.required || false,
278
- type: p.schema?.type || "string"
364
+ type: p.schema?.type || "string",
365
+ style: p.style,
366
+ explode: p.explode
279
367
  };
280
368
  }));
281
369
  };
package/dist/index.d.cts CHANGED
@@ -93,7 +93,9 @@ interface OperationSpec {
93
93
  name?: string;
94
94
  in?: string;
95
95
  required?: boolean;
96
- description?: string;
96
+ description?: string; /** Query serialisation style, e.g. `form` (default) or `deepObject`. */
97
+ style?: string; /** Whether each value gets its own key. Defaults to `true` for `form`. */
98
+ explode?: boolean;
97
99
  schema?: {
98
100
  type?: string;
99
101
  items?: {
@@ -212,6 +214,8 @@ declare const resolveParameter: (param: Record<string, unknown> | undefined, spe
212
214
  in?: string;
213
215
  required?: boolean;
214
216
  description?: string;
217
+ style?: string;
218
+ explode?: boolean;
215
219
  schema?: {
216
220
  type?: string;
217
221
  items?: {
@@ -224,15 +228,19 @@ declare const buildPathFn: (pathTemplate: string, pathParams: Array<{
224
228
  name: string;
225
229
  camelName: string;
226
230
  }>) => ((args: Record<string, unknown>) => string);
231
+ /** Query parameter with the OpenAPI serialisation rules declared for it. */
232
+ type QueryParamSerialization = {
233
+ name: string;
234
+ camelName: string; /** OpenAPI `style` (`form`, `deepObject`, `spaceDelimited`, `pipeDelimited`). */
235
+ style?: string; /** OpenAPI `explode`. Defaults to `true` for `form`, `false` otherwise. */
236
+ explode?: boolean;
237
+ };
227
238
  /**
228
239
  * Builds a function that serialises query params into a query string
229
- * (including the leading `?`). Returns `undefined` when the op has no query
230
- * params. Array values are appended once per element.
240
+ * (including the leading `?`), honouring each param's OpenAPI `style` and
241
+ * `explode`. Returns `undefined` when the op has no query params.
231
242
  */
232
- declare const buildQueryFn: (queryParams: Array<{
233
- name: string;
234
- camelName: string;
235
- }>) => ((args: Record<string, unknown>) => string) | undefined;
243
+ declare const buildQueryFn: (queryParams: QueryParamSerialization[]) => ((args: Record<string, unknown>) => string) | undefined;
236
244
  /**
237
245
  * Builds a function that maps camelCase args back to a snake_case request
238
246
  * body, skipping `undefined` args. Returns `undefined` when the op has no body.
@@ -296,6 +304,8 @@ declare const extractQueryParams: (args: {
296
304
  description: string;
297
305
  required: boolean;
298
306
  type: string;
307
+ style?: string;
308
+ explode?: boolean;
299
309
  }>;
300
310
  /**
301
311
  * snake_case names of every top-level property an operation's request schema
@@ -360,4 +370,4 @@ declare const openApiToToolDefinitions: (args: {
360
370
  options?: OpenApiToToolsOptions;
361
371
  }) => ToolDefinition[];
362
372
  //#endregion
363
- export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
373
+ export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
package/dist/index.d.mts CHANGED
@@ -93,7 +93,9 @@ interface OperationSpec {
93
93
  name?: string;
94
94
  in?: string;
95
95
  required?: boolean;
96
- description?: string;
96
+ description?: string; /** Query serialisation style, e.g. `form` (default) or `deepObject`. */
97
+ style?: string; /** Whether each value gets its own key. Defaults to `true` for `form`. */
98
+ explode?: boolean;
97
99
  schema?: {
98
100
  type?: string;
99
101
  items?: {
@@ -212,6 +214,8 @@ declare const resolveParameter: (param: Record<string, unknown> | undefined, spe
212
214
  in?: string;
213
215
  required?: boolean;
214
216
  description?: string;
217
+ style?: string;
218
+ explode?: boolean;
215
219
  schema?: {
216
220
  type?: string;
217
221
  items?: {
@@ -224,15 +228,19 @@ declare const buildPathFn: (pathTemplate: string, pathParams: Array<{
224
228
  name: string;
225
229
  camelName: string;
226
230
  }>) => ((args: Record<string, unknown>) => string);
231
+ /** Query parameter with the OpenAPI serialisation rules declared for it. */
232
+ type QueryParamSerialization = {
233
+ name: string;
234
+ camelName: string; /** OpenAPI `style` (`form`, `deepObject`, `spaceDelimited`, `pipeDelimited`). */
235
+ style?: string; /** OpenAPI `explode`. Defaults to `true` for `form`, `false` otherwise. */
236
+ explode?: boolean;
237
+ };
227
238
  /**
228
239
  * Builds a function that serialises query params into a query string
229
- * (including the leading `?`). Returns `undefined` when the op has no query
230
- * params. Array values are appended once per element.
240
+ * (including the leading `?`), honouring each param's OpenAPI `style` and
241
+ * `explode`. Returns `undefined` when the op has no query params.
231
242
  */
232
- declare const buildQueryFn: (queryParams: Array<{
233
- name: string;
234
- camelName: string;
235
- }>) => ((args: Record<string, unknown>) => string) | undefined;
243
+ declare const buildQueryFn: (queryParams: QueryParamSerialization[]) => ((args: Record<string, unknown>) => string) | undefined;
236
244
  /**
237
245
  * Builds a function that maps camelCase args back to a snake_case request
238
246
  * body, skipping `undefined` args. Returns `undefined` when the op has no body.
@@ -296,6 +304,8 @@ declare const extractQueryParams: (args: {
296
304
  description: string;
297
305
  required: boolean;
298
306
  type: string;
307
+ style?: string;
308
+ explode?: boolean;
299
309
  }>;
300
310
  /**
301
311
  * snake_case names of every top-level property an operation's request schema
@@ -360,4 +370,4 @@ declare const openApiToToolDefinitions: (args: {
360
370
  options?: OpenApiToToolsOptions;
361
371
  }) => ToolDefinition[];
362
372
  //#endregion
363
- export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
373
+ export { DEFAULT_EXCLUDE_EXTENSION, DEFAULT_SERVER_MANAGED_EXTENSION, type JsonSchemaProperty, type OpenApiSpec, type OpenApiToToolsOptions, type OperationSpec, type QueryParamSerialization, type RegisterOpenApiToolsArgs, type RequestBodySpec, type ResolvedRequest, type ToolDefinition, buildBodyFn, buildInputSchema, buildPathFn, buildQueryFn, dereferenceSchema, extractAcceptedBodyFields, extractBodyProps, extractPathParams, extractQueryParams, getJsonSchemaType, openApiToToolDefinitions, operationIdToToolName, processOperation, processPath, registerOpenApiTools, resolveParameter, resolveSchema, snakeToCamel };
package/dist/index.mjs CHANGED
@@ -114,22 +114,108 @@ var buildPathFn = (pathTemplate, pathParams) => {
114
114
  return result;
115
115
  };
116
116
  };
117
+ var NON_EXPLODED_DELIMITERS = {
118
+ form: ",",
119
+ spaceDelimited: " ",
120
+ pipeDelimited: "|"
121
+ };
122
+ var isRecord = value => {
123
+ return typeof value === "object" && value !== null && !Array.isArray(value);
124
+ };
125
+ /**
126
+ * Appends a `deepObject` value as bracketed keys — `filters[documentId][$eq]`.
127
+ * OpenAPI only defines one level, but the APIs that ask for `deepObject`
128
+ * (Strapi, Directus) nest, so nested objects and arrays recurse instead of
129
+ * being stringified into `[object Object]`.
130
+ */
131
+ var appendDeepObject = args => {
132
+ if (args.value === void 0 || args.value === null) return;
133
+ if (Array.isArray(args.value)) {
134
+ for (const [index, item] of args.value.entries()) appendDeepObject({
135
+ search: args.search,
136
+ key: `${args.key}[${index}]`,
137
+ value: item
138
+ });
139
+ return;
140
+ }
141
+ if (isRecord(args.value)) {
142
+ for (const [property, propertyValue] of Object.entries(args.value)) appendDeepObject({
143
+ search: args.search,
144
+ key: `${args.key}[${property}]`,
145
+ value: propertyValue
146
+ });
147
+ return;
148
+ }
149
+ args.search.append(args.key, String(args.value));
150
+ };
151
+ var appendArrayValue = args => {
152
+ if (args.explode) {
153
+ for (const item of args.value) args.search.append(args.name, String(item));
154
+ return;
155
+ }
156
+ args.search.append(args.name, args.value.map(String).join(args.delimiter));
157
+ };
158
+ var appendObjectValue = args => {
159
+ const entries = Object.entries(args.value).filter(([, entryValue]) => {
160
+ return entryValue !== void 0 && entryValue !== null;
161
+ });
162
+ if (args.explode) {
163
+ for (const [property, entryValue] of entries) args.search.append(property, String(entryValue));
164
+ return;
165
+ }
166
+ args.search.append(args.name, entries.flatMap(([property, entryValue]) => {
167
+ return [property, String(entryValue)];
168
+ }).join(args.delimiter));
169
+ };
170
+ var appendQueryValue = args => {
171
+ const style = args.param.style ?? "form";
172
+ if (style === "deepObject") {
173
+ appendDeepObject({
174
+ search: args.search,
175
+ key: args.param.name,
176
+ value: args.value
177
+ });
178
+ return;
179
+ }
180
+ const serialization = {
181
+ search: args.search,
182
+ name: args.param.name,
183
+ explode: args.param.explode ?? style === "form",
184
+ delimiter: NON_EXPLODED_DELIMITERS[style] ?? ","
185
+ };
186
+ if (Array.isArray(args.value)) {
187
+ appendArrayValue({
188
+ ...serialization,
189
+ value: args.value
190
+ });
191
+ return;
192
+ }
193
+ if (isRecord(args.value)) {
194
+ appendObjectValue({
195
+ ...serialization,
196
+ value: args.value
197
+ });
198
+ return;
199
+ }
200
+ args.search.append(args.param.name, String(args.value));
201
+ };
117
202
  /**
118
203
  * Builds a function that serialises query params into a query string
119
- * (including the leading `?`). Returns `undefined` when the op has no query
120
- * params. Array values are appended once per element.
204
+ * (including the leading `?`), honouring each param's OpenAPI `style` and
205
+ * `explode`. Returns `undefined` when the op has no query params.
121
206
  */
122
207
  var buildQueryFn = queryParams => {
123
208
  if (queryParams.length === 0) return void 0;
124
209
  return args => {
125
210
  const search = new URLSearchParams();
126
- for (const {
127
- name,
128
- camelName
129
- } of queryParams) {
130
- const value = args[camelName];
211
+ for (const param of queryParams) {
212
+ const value = args[param.camelName];
131
213
  if (value === void 0 || value === null) continue;
132
- if (Array.isArray(value)) for (const item of value) search.append(name, String(item));else search.append(name, String(value));
214
+ appendQueryValue({
215
+ search,
216
+ param,
217
+ value
218
+ });
133
219
  }
134
220
  const qs = search.toString();
135
221
  return qs ? `?${qs}` : "";
@@ -272,7 +358,9 @@ var extractQueryParams = args => {
272
358
  camelName: snakeToCamel(p.name || ""),
273
359
  description: p.description || "",
274
360
  required: p.required || false,
275
- type: p.schema?.type || "string"
361
+ type: p.schema?.type || "string",
362
+ style: p.style,
363
+ explode: p.explode
276
364
  };
277
365
  }));
278
366
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp-openapi",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Generate Model Context Protocol (MCP) tools from an OpenAPI specification for @ttoss/http-server-mcp",
5
5
  "keywords": [
6
6
  "ai",
@@ -35,7 +35,7 @@
35
35
  "dist"
36
36
  ],
37
37
  "dependencies": {
38
- "@ttoss/http-server-mcp": "^0.28.1"
38
+ "@ttoss/http-server-mcp": "^0.28.2"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@modelcontextprotocol/server": "^2.0.0",