item-wms-public-api-tool 2.0.4

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.
Files changed (110) hide show
  1. package/AI_INTEGRATION_GUIDE.md +492 -0
  2. package/HELP.md +297 -0
  3. package/README.md +259 -0
  4. package/api/README.md +9 -0
  5. package/api/openapi.apifox.yaml +20771 -0
  6. package/dist/cli.d.ts +10 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +326 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/client.d.ts +8 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +76 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/config.d.ts +32 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +109 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +31 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/openapi-tools.d.ts +25 -0
  23. package/dist/openapi-tools.d.ts.map +1 -0
  24. package/dist/openapi-tools.js +287 -0
  25. package/dist/openapi-tools.js.map +1 -0
  26. package/dist/server.d.ts +5 -0
  27. package/dist/server.d.ts.map +1 -0
  28. package/dist/server.js +442 -0
  29. package/dist/server.js.map +1 -0
  30. package/dist/tools/batchImportItemMaster.d.ts +59 -0
  31. package/dist/tools/batchImportItemMaster.d.ts.map +1 -0
  32. package/dist/tools/batchImportItemMaster.js +27 -0
  33. package/dist/tools/batchImportItemMaster.js.map +1 -0
  34. package/dist/tools/batchUploadOrderFiles.d.ts +29 -0
  35. package/dist/tools/batchUploadOrderFiles.d.ts.map +1 -0
  36. package/dist/tools/batchUploadOrderFiles.js +96 -0
  37. package/dist/tools/batchUploadOrderFiles.js.map +1 -0
  38. package/dist/tools/importInbound.d.ts +62 -0
  39. package/dist/tools/importInbound.d.ts.map +1 -0
  40. package/dist/tools/importInbound.js +22 -0
  41. package/dist/tools/importInbound.js.map +1 -0
  42. package/dist/tools/importOutbound.d.ts +76 -0
  43. package/dist/tools/importOutbound.d.ts.map +1 -0
  44. package/dist/tools/importOutbound.js +25 -0
  45. package/dist/tools/importOutbound.js.map +1 -0
  46. package/dist/tools/itemGet.d.ts +15 -0
  47. package/dist/tools/itemGet.d.ts.map +1 -0
  48. package/dist/tools/itemGet.js +12 -0
  49. package/dist/tools/itemGet.js.map +1 -0
  50. package/dist/tools/itemSearch.d.ts +8 -0
  51. package/dist/tools/itemSearch.d.ts.map +1 -0
  52. package/dist/tools/itemSearch.js +17 -0
  53. package/dist/tools/itemSearch.js.map +1 -0
  54. package/dist/tools/login.d.ts +26 -0
  55. package/dist/tools/login.d.ts.map +1 -0
  56. package/dist/tools/login.js +15 -0
  57. package/dist/tools/login.js.map +1 -0
  58. package/dist/tools/queryItemMaster.d.ts +63 -0
  59. package/dist/tools/queryItemMaster.d.ts.map +1 -0
  60. package/dist/tools/queryItemMaster.js +9 -0
  61. package/dist/tools/queryItemMaster.js.map +1 -0
  62. package/dist/tools/queryOrder.d.ts +74 -0
  63. package/dist/tools/queryOrder.d.ts.map +1 -0
  64. package/dist/tools/queryOrder.js +19 -0
  65. package/dist/tools/queryOrder.js.map +1 -0
  66. package/dist/tools/queryOrderDC.d.ts +25 -0
  67. package/dist/tools/queryOrderDC.d.ts.map +1 -0
  68. package/dist/tools/queryOrderDC.js +19 -0
  69. package/dist/tools/queryOrderDC.js.map +1 -0
  70. package/dist/tools/queryOrderDetail.d.ts +70 -0
  71. package/dist/tools/queryOrderDetail.d.ts.map +1 -0
  72. package/dist/tools/queryOrderDetail.js +19 -0
  73. package/dist/tools/queryOrderDetail.js.map +1 -0
  74. package/dist/tools/queryReceipt.d.ts +70 -0
  75. package/dist/tools/queryReceipt.d.ts.map +1 -0
  76. package/dist/tools/queryReceipt.js +19 -0
  77. package/dist/tools/queryReceipt.js.map +1 -0
  78. package/dist/tools/queryReceiptDetail.d.ts +70 -0
  79. package/dist/tools/queryReceiptDetail.d.ts.map +1 -0
  80. package/dist/tools/queryReceiptDetail.js +19 -0
  81. package/dist/tools/queryReceiptDetail.js.map +1 -0
  82. package/dist/tools/queryReceiptRC.d.ts +25 -0
  83. package/dist/tools/queryReceiptRC.d.ts.map +1 -0
  84. package/dist/tools/queryReceiptRC.js +19 -0
  85. package/dist/tools/queryReceiptRC.js.map +1 -0
  86. package/dist/tools/uploadOrderFile.d.ts +25 -0
  87. package/dist/tools/uploadOrderFile.d.ts.map +1 -0
  88. package/dist/tools/uploadOrderFile.js +94 -0
  89. package/dist/tools/uploadOrderFile.js.map +1 -0
  90. package/dist/tools/validateInbound.d.ts +13 -0
  91. package/dist/tools/validateInbound.d.ts.map +1 -0
  92. package/dist/tools/validateInbound.js +18 -0
  93. package/dist/tools/validateInbound.js.map +1 -0
  94. package/llms.txt +23 -0
  95. package/mcp.json +6562 -0
  96. package/mcp.schema.json +100 -0
  97. package/openapi.yaml +20685 -0
  98. package/package.json +34 -0
  99. package/scripts/sync-openapi.js +289 -0
  100. package/src/cli.ts +353 -0
  101. package/src/client.ts +90 -0
  102. package/src/config.ts +126 -0
  103. package/src/index.ts +31 -0
  104. package/src/openapi-tools.ts +346 -0
  105. package/src/server.ts +472 -0
  106. package/test-ai-integration.js +128 -0
  107. package/test-fix.js +68 -0
  108. package/test-mcp.js +92 -0
  109. package/test-order.json +25 -0
  110. package/tsconfig.json +18 -0
package/src/client.ts ADDED
@@ -0,0 +1,90 @@
1
+ // src/client.ts
2
+ import { DEFAULT_BASE_URL } from "./config";
3
+
4
+ export type ClientOptions = {
5
+ baseUrl?: string;
6
+ token?: string;
7
+ };
8
+
9
+ function resolveAuthHeader(token?: string): string | undefined {
10
+ if (!token) {
11
+ return undefined;
12
+ }
13
+ const trimmed = token.trim();
14
+ if (!trimmed) {
15
+ return undefined;
16
+ }
17
+ if (/^bearer\s+/i.test(trimmed)) {
18
+ return trimmed;
19
+ }
20
+ return `Bearer ${trimmed}`;
21
+ }
22
+
23
+ function resolveBaseUrl(opts: ClientOptions): string {
24
+ const baseUrl = opts.baseUrl || process.env.WMS_BASE_URL || DEFAULT_BASE_URL;
25
+ return baseUrl.replace(/\/$/, '');
26
+ }
27
+
28
+ function buildUrl(baseUrl: string, path: string, query?: Record<string, any>): string {
29
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
30
+ const url = new URL(`${baseUrl}${normalizedPath}`);
31
+ if (query) {
32
+ for (const [key, value] of Object.entries(query)) {
33
+ if (value === undefined || value === null) {
34
+ continue;
35
+ }
36
+ if (Array.isArray(value)) {
37
+ value.forEach((item) => {
38
+ if (item !== undefined && item !== null) {
39
+ url.searchParams.append(key, String(item));
40
+ }
41
+ });
42
+ } else {
43
+ url.searchParams.append(key, String(value));
44
+ }
45
+ }
46
+ }
47
+ return url.toString();
48
+ }
49
+
50
+ export async function request<T>(
51
+ method: string,
52
+ path: string,
53
+ opts: ClientOptions,
54
+ body?: any,
55
+ extraHeaders?: Record<string, string>,
56
+ query?: Record<string, any>
57
+ ): Promise<T> {
58
+ const baseUrl = resolveBaseUrl(opts);
59
+ const url = buildUrl(baseUrl, path, query);
60
+ const authHeader = resolveAuthHeader(opts.token);
61
+ const headers: Record<string, string> = {
62
+ ...(authHeader ? { Authorization: authHeader } : {}),
63
+ ...(extraHeaders || {})
64
+ };
65
+
66
+ const methodUpper = method.toUpperCase();
67
+ const canSendBody = methodUpper !== 'GET' && methodUpper !== 'DELETE' && methodUpper !== 'HEAD';
68
+ if (canSendBody) {
69
+ headers['content-type'] = 'application/json';
70
+ }
71
+
72
+ const resp = await fetch(url, {
73
+ method: methodUpper,
74
+ headers,
75
+ body: canSendBody && body !== undefined ? JSON.stringify(body) : undefined
76
+ });
77
+
78
+ if (!resp.ok) {
79
+ throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
80
+ }
81
+ return resp.json();
82
+ }
83
+
84
+ export async function get<T>(path: string, opts: ClientOptions): Promise<T> {
85
+ return request<T>('GET', path, opts);
86
+ }
87
+
88
+ export async function post<T>(path: string, body: any, opts: ClientOptions): Promise<T> {
89
+ return request<T>('POST', path, opts, body);
90
+ }
package/src/config.ts ADDED
@@ -0,0 +1,126 @@
1
+ // src/config.ts
2
+
3
+ /**
4
+ * CLI配置选项接口
5
+ */
6
+ export interface CliConfig {
7
+ baseUrl?: string;
8
+ token?: string;
9
+ companyId?: string;
10
+ facilityId?: string;
11
+ customerId?: string;
12
+ }
13
+
14
+ export const DEFAULT_BASE_URL = "https://wms-staging.item.com/api/public";
15
+ export const DEFAULT_COMPANY_ID = "LT";
16
+ export const DEFAULT_FACILITY_ID = "889";
17
+ export const DEFAULT_CUSTOMER_ID = "FLAANT0001";
18
+
19
+ export type DefaultParams = {
20
+ CompanyID?: string;
21
+ FacilityID?: string;
22
+ CustomerID?: string;
23
+ };
24
+
25
+ function normalizeConfigValue(value?: string): string | undefined {
26
+ if (!value) {
27
+ return undefined;
28
+ }
29
+ const trimmed = value.trim();
30
+ return trimmed.length > 0 ? trimmed : undefined;
31
+ }
32
+
33
+ export function resolveDefaultParams(): DefaultParams {
34
+ const companyId = normalizeConfigValue(process.env.WMS_COMPANY_ID) ?? DEFAULT_COMPANY_ID;
35
+ const facilityId = normalizeConfigValue(process.env.WMS_FACILITY_ID) ?? DEFAULT_FACILITY_ID;
36
+ const customerId = normalizeConfigValue(process.env.WMS_CUSTOMER_ID) ?? DEFAULT_CUSTOMER_ID;
37
+
38
+ return {
39
+ CompanyID: companyId,
40
+ FacilityID: facilityId,
41
+ CustomerID: customerId
42
+ };
43
+ }
44
+
45
+ /**
46
+ * 解析命令行参数
47
+ * @param argv 命令行参数数组
48
+ * @returns CLI配置对象
49
+ */
50
+ export function parseArgs(argv: string[]): CliConfig {
51
+ const config: CliConfig = {};
52
+
53
+ // 简单的命令行参数解析
54
+ for (let i = 0; i < argv.length; i++) {
55
+ const arg = argv[i];
56
+ const eqIndex = arg.indexOf('=');
57
+
58
+ if (arg.startsWith('--') && eqIndex > 2) {
59
+ const flag = arg.slice(0, eqIndex);
60
+ const value = arg.slice(eqIndex + 1);
61
+ if (flag === '--baseUrl') {
62
+ config.baseUrl = value;
63
+ continue;
64
+ }
65
+ if (flag === '--token') {
66
+ config.token = value;
67
+ continue;
68
+ }
69
+ if (flag === '--companyId') {
70
+ config.companyId = value;
71
+ continue;
72
+ }
73
+ if (flag === '--facilityId') {
74
+ config.facilityId = value;
75
+ continue;
76
+ }
77
+ if (flag === '--customerId') {
78
+ config.customerId = value;
79
+ continue;
80
+ }
81
+ }
82
+
83
+ if (arg === '--baseUrl' && i + 1 < argv.length) {
84
+ config.baseUrl = argv[++i];
85
+ } else if (arg === '--token' && i + 1 < argv.length) {
86
+ config.token = argv[++i];
87
+ } else if (arg === '--companyId' && i + 1 < argv.length) {
88
+ config.companyId = argv[++i];
89
+ } else if (arg === '--facilityId' && i + 1 < argv.length) {
90
+ config.facilityId = argv[++i];
91
+ } else if (arg === '--customerId' && i + 1 < argv.length) {
92
+ config.customerId = argv[++i];
93
+ }
94
+ }
95
+
96
+ return config;
97
+ }
98
+
99
+ /**
100
+ * 应用CLI配置
101
+ * @param config CLI配置对象
102
+ */
103
+ export function applyCliConfig(config: CliConfig): void {
104
+ // 将CLI配置应用到环境变量
105
+ const baseUrl = normalizeConfigValue(config.baseUrl);
106
+ const token = normalizeConfigValue(config.token);
107
+ const companyId = normalizeConfigValue(config.companyId);
108
+ const facilityId = normalizeConfigValue(config.facilityId);
109
+ const customerId = normalizeConfigValue(config.customerId);
110
+
111
+ if (baseUrl) {
112
+ process.env.WMS_BASE_URL = baseUrl;
113
+ }
114
+ if (token) {
115
+ process.env.WMS_TOKEN = token;
116
+ }
117
+ if (companyId) {
118
+ process.env.WMS_COMPANY_ID = companyId;
119
+ }
120
+ if (facilityId) {
121
+ process.env.WMS_FACILITY_ID = facilityId;
122
+ }
123
+ if (customerId) {
124
+ process.env.WMS_CUSTOMER_ID = customerId;
125
+ }
126
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ // src/index.ts
3
+ import { applyCliConfig, parseArgs } from "./config";
4
+ import { startServer } from "./server";
5
+ import { parseAndExecuteCli } from "./cli";
6
+
7
+ /**
8
+ * 主入口函数
9
+ */
10
+ async function main(): Promise<void> {
11
+ // 解析命令行参数
12
+ const cliConfig = parseArgs(process.argv.slice(2));
13
+
14
+ // 应用CLI配置到环境变量
15
+ applyCliConfig(cliConfig);
16
+
17
+ // 支持两种模式:CLI模式和stdio模式
18
+ if (process.stdin.isTTY) {
19
+ // CLI模式:直接执行CLI命令
20
+ await parseAndExecuteCli();
21
+ } else {
22
+ // stdio模式:启动MCP服务器
23
+ await startServer();
24
+ }
25
+ }
26
+
27
+ // 执行主函数
28
+ main().catch((error) => {
29
+ console.error("Unexpected error:", error);
30
+ process.exit(1);
31
+ });
@@ -0,0 +1,346 @@
1
+ // src/openapi-tools.ts
2
+ import fs from 'fs';
3
+ import YAML from 'yaml';
4
+ import type { ClientOptions } from './client';
5
+ import { request } from './client';
6
+ import { resolveDefaultParams } from './config';
7
+
8
+ export type McpTool = {
9
+ name: string;
10
+ description: string;
11
+ inputSchema: Record<string, any>;
12
+ };
13
+
14
+ export type OpenApiOperation = {
15
+ name: string;
16
+ method: string;
17
+ path: string;
18
+ description: string;
19
+ parameters: any[];
20
+ requestBody?: any;
21
+ inputSchema?: Record<string, any>;
22
+ };
23
+
24
+ export type OpenApiIndex = {
25
+ tools: McpTool[];
26
+ operations: Record<string, OpenApiOperation>;
27
+ };
28
+
29
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
30
+ const DEFAULT_PARAM_KEYS = ['CompanyID', 'FacilityID', 'CustomerID'] as const;
31
+
32
+ type DefaultParamKey = typeof DEFAULT_PARAM_KEYS[number];
33
+
34
+ function isJsonLike(raw: string): boolean {
35
+ const trimmed = raw.trimStart();
36
+ return trimmed.startsWith('{') || trimmed.startsWith('[');
37
+ }
38
+
39
+ function isPlainObject(value: any): value is Record<string, any> {
40
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
41
+ }
42
+
43
+ function hasOwnProperty(target: object, key: string): boolean {
44
+ return Object.prototype.hasOwnProperty.call(target, key);
45
+ }
46
+
47
+ function applyDefaultParams(
48
+ operation: OpenApiOperation,
49
+ args: Record<string, any>
50
+ ): Record<string, any> {
51
+ const defaults = resolveDefaultParams();
52
+ const schemaProps = operation.inputSchema?.properties ?? {};
53
+ const paramNames = new Set<string>();
54
+
55
+ for (const param of operation.parameters || []) {
56
+ if (param && typeof param.name === 'string' && param.name) {
57
+ paramNames.add(param.name);
58
+ }
59
+ }
60
+
61
+ const bodyProvided = hasOwnProperty(args, 'body');
62
+ const bodyIsObject = isPlainObject(args.body);
63
+ const body = bodyIsObject ? { ...args.body } : undefined;
64
+ let bodyModified = false;
65
+
66
+ const nextArgs: Record<string, any> = { ...args };
67
+
68
+ for (const key of DEFAULT_PARAM_KEYS) {
69
+ const defaultValue = defaults[key as DefaultParamKey];
70
+ if (!defaultValue) {
71
+ continue;
72
+ }
73
+ if (hasOwnProperty(args, key)) {
74
+ continue;
75
+ }
76
+
77
+ if (paramNames.has(key)) {
78
+ nextArgs[key] = defaultValue;
79
+ continue;
80
+ }
81
+
82
+ if (!hasOwnProperty(schemaProps, key)) {
83
+ continue;
84
+ }
85
+
86
+ if (bodyProvided && !bodyIsObject) {
87
+ continue;
88
+ }
89
+
90
+ if (bodyIsObject && hasOwnProperty(args.body, key)) {
91
+ continue;
92
+ }
93
+
94
+ if (bodyIsObject && body) {
95
+ body[key] = defaultValue;
96
+ bodyModified = true;
97
+ continue;
98
+ }
99
+
100
+ nextArgs[key] = defaultValue;
101
+ }
102
+
103
+ if (bodyModified) {
104
+ nextArgs.body = body;
105
+ }
106
+
107
+ return nextArgs;
108
+ }
109
+
110
+ function normalizeToolName(method: string, pathName: string): string {
111
+ const normalizedPath = pathName.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
112
+ return `${method.toLowerCase()}_${normalizedPath.toLowerCase()}`;
113
+ }
114
+
115
+ function mergeSchema(base: Record<string, any>, extra?: Record<string, any>): Record<string, any> {
116
+ if (!extra) {
117
+ return base;
118
+ }
119
+ const merged = { ...base, ...extra };
120
+ merged.properties = {
121
+ ...(base.properties || {}),
122
+ ...(extra.properties || {})
123
+ };
124
+ if (extra.anyOf) {
125
+ merged.anyOf = extra.anyOf;
126
+ } else if (base.anyOf) {
127
+ merged.anyOf = base.anyOf;
128
+ }
129
+ if (extra.required) {
130
+ merged.required = extra.required;
131
+ } else if (base.required) {
132
+ merged.required = base.required;
133
+ }
134
+ return merged;
135
+ }
136
+
137
+ function resolveSchema(schema: any, components?: any, seen = new Set<string>()): any {
138
+ if (!schema || typeof schema !== 'object') {
139
+ return schema;
140
+ }
141
+
142
+ if (schema.$ref && typeof schema.$ref === 'string') {
143
+ const match = schema.$ref.match(/^#\/components\/schemas\/(.+)$/);
144
+ if (!match) {
145
+ return { ...schema };
146
+ }
147
+ const name = match[1];
148
+ if (seen.has(name)) {
149
+ return { ...schema };
150
+ }
151
+ const target = components && components.schemas ? components.schemas[name] : undefined;
152
+ if (!target) {
153
+ return { ...schema };
154
+ }
155
+ seen.add(name);
156
+ const resolved = resolveSchema(target, components, seen);
157
+ seen.delete(name);
158
+ return resolved;
159
+ }
160
+
161
+ if (Array.isArray(schema.allOf)) {
162
+ const merged = schema.allOf.reduce(
163
+ (acc: Record<string, any>, item: any) => mergeSchema(acc, resolveSchema(item, components, seen)),
164
+ { type: 'object', properties: {} }
165
+ );
166
+ const combined = mergeSchema(merged, schema);
167
+ delete combined.allOf;
168
+ return combined;
169
+ }
170
+
171
+ const copy: Record<string, any> = { ...schema };
172
+ if (Array.isArray(copy.anyOf)) {
173
+ copy.anyOf = copy.anyOf.map((item: any) => resolveSchema(item, components, seen));
174
+ }
175
+ if (Array.isArray(copy.oneOf)) {
176
+ copy.oneOf = copy.oneOf.map((item: any) => resolveSchema(item, components, seen));
177
+ }
178
+ if (copy.properties && typeof copy.properties === 'object') {
179
+ const nextProps: Record<string, any> = {};
180
+ for (const [key, value] of Object.entries(copy.properties)) {
181
+ nextProps[key] = resolveSchema(value, components, seen);
182
+ }
183
+ copy.properties = nextProps;
184
+ }
185
+ if (copy.items) {
186
+ copy.items = resolveSchema(copy.items, components, seen);
187
+ }
188
+
189
+ return copy;
190
+ }
191
+
192
+ function ensureObjectSchema(schema: any): Record<string, any> {
193
+ if (!schema || typeof schema !== 'object') {
194
+ return { type: 'object', properties: {} };
195
+ }
196
+ if (!schema.type && !schema.properties) {
197
+ return { type: 'object', properties: {}, ...schema };
198
+ }
199
+ if (schema.type && schema.type !== 'object' && !schema.properties) {
200
+ return {
201
+ type: 'object',
202
+ properties: { body: schema },
203
+ required: ['body']
204
+ };
205
+ }
206
+ if (!schema.properties) {
207
+ return { ...schema, properties: {} };
208
+ }
209
+ return schema;
210
+ }
211
+
212
+ function buildInputSchema(operation: any, parameters: any[], components?: any): Record<string, any> {
213
+ const requestSchema = operation.requestBody?.content?.['application/json']?.schema;
214
+ const resolvedSchema = resolveSchema(requestSchema, components);
215
+ const baseSchema = ensureObjectSchema(resolvedSchema);
216
+ const requiredSet = new Set<string>(baseSchema.required || []);
217
+ const properties: Record<string, any> = { ...(baseSchema.properties || {}) };
218
+
219
+ for (const param of parameters) {
220
+ if (!param || !param.name) {
221
+ continue;
222
+ }
223
+ if (!properties[param.name]) {
224
+ properties[param.name] = resolveSchema(param.schema, components) || { type: 'string' };
225
+ }
226
+ if (param.required) {
227
+ requiredSet.add(param.name);
228
+ }
229
+ }
230
+
231
+ const inputSchema: Record<string, any> = { ...baseSchema, properties };
232
+ if (requiredSet.size > 0) {
233
+ inputSchema.required = Array.from(requiredSet);
234
+ } else {
235
+ delete inputSchema.required;
236
+ }
237
+ return inputSchema;
238
+ }
239
+
240
+ export function loadOpenApiSpec(openApiPath: string): any {
241
+ const raw = fs.readFileSync(openApiPath, 'utf8');
242
+ if (isJsonLike(raw)) {
243
+ return JSON.parse(raw);
244
+ }
245
+ return YAML.parse(raw);
246
+ }
247
+
248
+ export function buildOpenApiIndex(spec: any, options?: { pathPrefix?: string }): OpenApiIndex {
249
+ const tools: McpTool[] = [];
250
+ const operations: Record<string, OpenApiOperation> = {};
251
+ const prefix = options?.pathPrefix;
252
+
253
+ for (const [pathName, pathItem] of Object.entries<any>(spec.paths || {})) {
254
+ if (prefix && !pathName.startsWith(prefix)) {
255
+ continue;
256
+ }
257
+ const commonParams = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
258
+ for (const method of HTTP_METHODS) {
259
+ const operation = pathItem[method];
260
+ if (!operation) {
261
+ continue;
262
+ }
263
+ const name = normalizeToolName(method, pathName);
264
+ const parameters = [
265
+ ...commonParams,
266
+ ...(Array.isArray(operation.parameters) ? operation.parameters : [])
267
+ ];
268
+ const description = operation.summary || operation.description || `${method.toUpperCase()} ${pathName}`;
269
+ const inputSchema = buildInputSchema(operation, parameters, spec.components);
270
+
271
+ let finalName = name;
272
+ let counter = 2;
273
+ while (operations[finalName]) {
274
+ finalName = `${name}_${counter}`;
275
+ counter += 1;
276
+ }
277
+
278
+ operations[finalName] = {
279
+ name: finalName,
280
+ method,
281
+ path: pathName,
282
+ description,
283
+ parameters,
284
+ requestBody: operation.requestBody,
285
+ inputSchema
286
+ };
287
+ tools.push({
288
+ name: finalName,
289
+ description,
290
+ inputSchema
291
+ });
292
+ }
293
+ }
294
+
295
+ tools.sort((a, b) => a.name.localeCompare(b.name));
296
+ return { tools, operations };
297
+ }
298
+
299
+ export async function callOpenApiOperation(
300
+ operation: OpenApiOperation,
301
+ args: Record<string, any>,
302
+ clientOpts: ClientOptions
303
+ ): Promise<any> {
304
+ const params = applyDefaultParams(operation, args || {});
305
+ const method = operation.method.toUpperCase();
306
+ let pathName = operation.path;
307
+ const headers: Record<string, string> = {};
308
+ const query: Record<string, any> = {};
309
+ const remaining: Record<string, any> = { ...params };
310
+
311
+ for (const param of operation.parameters) {
312
+ const name = param.name;
313
+ if (!name) {
314
+ continue;
315
+ }
316
+ const value = params[name];
317
+ if (value === undefined || value === null) {
318
+ if (param.required && param.in === 'path') {
319
+ throw new Error(`Missing required path parameter: ${name}`);
320
+ }
321
+ continue;
322
+ }
323
+ if (param.in === 'path') {
324
+ pathName = pathName.replace(new RegExp(`{${name}}`, 'g'), encodeURIComponent(String(value)));
325
+ delete remaining[name];
326
+ } else if (param.in === 'query') {
327
+ query[name] = value;
328
+ delete remaining[name];
329
+ } else if (param.in === 'header') {
330
+ headers[name] = String(value);
331
+ delete remaining[name];
332
+ }
333
+ }
334
+
335
+ let body: any = undefined;
336
+ const hasBody = Boolean(operation.requestBody);
337
+ if (hasBody) {
338
+ body = Object.prototype.hasOwnProperty.call(params, 'body') ? params.body : remaining;
339
+ }
340
+
341
+ const methodLower = method.toLowerCase();
342
+ const bodyAllowed = methodLower !== 'get' && methodLower !== 'delete' && methodLower !== 'head';
343
+ const finalBody = bodyAllowed ? body : undefined;
344
+
345
+ return request(method, pathName, clientOpts, finalBody, headers, query);
346
+ }