@shipfox/api-integration-core 12.4.0 → 12.6.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.
Files changed (36) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +32 -0
  3. package/dist/metrics/instance.d.ts +5 -0
  4. package/dist/metrics/instance.d.ts.map +1 -1
  5. package/dist/metrics/instance.js +21 -1
  6. package/dist/metrics/instance.js.map +1 -1
  7. package/dist/presentation/routes/agent-tools-gateway/audit.d.ts +4 -1
  8. package/dist/presentation/routes/agent-tools-gateway/audit.d.ts.map +1 -1
  9. package/dist/presentation/routes/agent-tools-gateway/audit.js +6 -1
  10. package/dist/presentation/routes/agent-tools-gateway/audit.js.map +1 -1
  11. package/dist/presentation/routes/agent-tools-gateway/dispatch.d.ts +9 -1
  12. package/dist/presentation/routes/agent-tools-gateway/dispatch.d.ts.map +1 -1
  13. package/dist/presentation/routes/agent-tools-gateway/dispatch.js +55 -17
  14. package/dist/presentation/routes/agent-tools-gateway/dispatch.js.map +1 -1
  15. package/dist/presentation/routes/agent-tools-gateway/index.d.ts.map +1 -1
  16. package/dist/presentation/routes/agent-tools-gateway/index.js +6 -5
  17. package/dist/presentation/routes/agent-tools-gateway/index.js.map +1 -1
  18. package/dist/presentation/routes/agent-tools-gateway/mcp-server.d.ts.map +1 -1
  19. package/dist/presentation/routes/agent-tools-gateway/mcp-server.js +27 -5
  20. package/dist/presentation/routes/agent-tools-gateway/mcp-server.js.map +1 -1
  21. package/dist/presentation/routes/errors.d.ts.map +1 -1
  22. package/dist/presentation/routes/errors.js +1 -1
  23. package/dist/presentation/routes/errors.js.map +1 -1
  24. package/dist/tsconfig.test.tsbuildinfo +1 -1
  25. package/package.json +10 -10
  26. package/src/metrics/instance.ts +40 -1
  27. package/src/presentation/routes/agent-tools-gateway/agent-tools-gateway.route.test.ts +11 -4
  28. package/src/presentation/routes/agent-tools-gateway/audit.test.ts +51 -0
  29. package/src/presentation/routes/agent-tools-gateway/audit.ts +8 -0
  30. package/src/presentation/routes/agent-tools-gateway/dispatch.test.ts +167 -0
  31. package/src/presentation/routes/agent-tools-gateway/dispatch.ts +93 -20
  32. package/src/presentation/routes/agent-tools-gateway/index.ts +3 -4
  33. package/src/presentation/routes/agent-tools-gateway/mcp-server.test.ts +41 -4
  34. package/src/presentation/routes/agent-tools-gateway/mcp-server.ts +35 -1
  35. package/src/presentation/routes/errors.ts +1 -0
  36. package/tsconfig.build.tsbuildinfo +1 -1
@@ -54,6 +54,7 @@ describe('buildAgentToolsMcpServer', () => {
54
54
  }),
55
55
  method: 'get',
56
56
  outcome: 'success',
57
+ errorCode: 'none',
57
58
  },
58
59
  ]);
59
60
  });
@@ -93,6 +94,7 @@ describe('buildAgentToolsMcpServer', () => {
93
94
  {
94
95
  method: expectedMethod,
95
96
  outcome: 'invalid-request',
97
+ errorCode: 'invalid-request',
96
98
  },
97
99
  ]);
98
100
  if (expectedToolId) {
@@ -128,7 +130,9 @@ describe('buildAgentToolsMcpServer', () => {
128
130
  arguments: {method: 'ignored'},
129
131
  }),
130
132
  );
131
- expect(records).toMatchObject([{method: NO_METHOD_LABEL, outcome: 'success'}]);
133
+ expect(records).toMatchObject([
134
+ {method: NO_METHOD_LABEL, outcome: 'success', errorCode: 'none'},
135
+ ]);
132
136
  });
133
137
 
134
138
  it('defaults omitted arguments to an empty object for optional-argument tools', async () => {
@@ -179,7 +183,7 @@ describe('buildAgentToolsMcpServer', () => {
179
183
  arguments: expect.objectContaining({issue_number: 'not-an-integer'}),
180
184
  }),
181
185
  );
182
- expect(records).toMatchObject([{method: 'get', outcome: 'success'}]);
186
+ expect(records).toMatchObject([{method: 'get', outcome: 'success', errorCode: 'none'}]);
183
187
  });
184
188
 
185
189
  it('records tool-error when dispatch returns an error result', async () => {
@@ -204,7 +208,7 @@ describe('buildAgentToolsMcpServer', () => {
204
208
  await close();
205
209
 
206
210
  expect(result.isError).toBe(true);
207
- expect(records).toMatchObject([{method: 'get', outcome: 'tool-error'}]);
211
+ expect(records).toMatchObject([{method: 'get', outcome: 'tool-error', errorCode: 'unknown'}]);
208
212
  });
209
213
 
210
214
  it('records exception before rethrowing dispatcher failures', async () => {
@@ -227,7 +231,40 @@ describe('buildAgentToolsMcpServer', () => {
227
231
  ).rejects.toThrow('MCP error -32603');
228
232
  await close();
229
233
 
230
- expect(records).toMatchObject([{method: 'get', outcome: 'exception'}]);
234
+ expect(records).toMatchObject([{method: 'get', outcome: 'exception', errorCode: 'unknown'}]);
235
+ });
236
+
237
+ it('records bounded provider error details returned by the dispatcher', async () => {
238
+ const dispatch = vi.fn(async () => ({
239
+ isError: true,
240
+ content: [{type: 'text' as const, text: 'provider rejected call'}],
241
+ structuredContent: {code: 'provider-rejected', status: 422},
242
+ }));
243
+ const records: Parameters<
244
+ NonNullable<Parameters<typeof buildAgentToolsMcpServer>[0]['recordCall']>
245
+ >[0][] = [];
246
+ const {client, close} = await connectClient(dispatch, defaultAuthorizedTools(), (record) =>
247
+ records.push(record),
248
+ );
249
+
250
+ const result = await client.callTool(
251
+ {
252
+ name: 'github_main__issue_read',
253
+ arguments: {method: 'get', owner: 'shipfox', repo: 'platform', issue_number: 1},
254
+ },
255
+ CallToolResultSchema,
256
+ );
257
+ await close();
258
+
259
+ expect(result.isError).toBe(true);
260
+ expect(records).toMatchObject([
261
+ {
262
+ method: 'get',
263
+ outcome: 'tool-error',
264
+ errorCode: 'provider-rejected',
265
+ providerStatus: 422,
266
+ },
267
+ ]);
231
268
  });
232
269
  });
233
270
 
@@ -6,7 +6,13 @@ import {
6
6
  } from '@modelcontextprotocol/sdk/types.js';
7
7
  import {reportError} from '@shipfox/node-error-monitoring';
8
8
  import {logger} from '@shipfox/node-opentelemetry';
9
- import {INVALID_METHOD_LABEL, type IntegrationToolCallRecorder, NO_METHOD_LABEL} from './audit.js';
9
+ import {normalizeIntegrationAgentToolCallErrorCode} from '#metrics/index.js';
10
+ import {
11
+ INVALID_METHOD_LABEL,
12
+ type IntegrationAgentToolCallErrorCode,
13
+ type IntegrationToolCallRecorder,
14
+ NO_METHOD_LABEL,
15
+ } from './audit.js';
10
16
  import type {
11
17
  AuthorizedIntegrationTool,
12
18
  AuthorizedIntegrationToolMap,
@@ -61,6 +67,7 @@ export function buildAgentToolsMcpServer(params: BuildAgentToolsMcpServerParams)
61
67
  arguments: request.params.arguments ?? {},
62
68
  method: NO_METHOD_LABEL,
63
69
  outcome: 'invalid-request',
70
+ errorCode: 'invalid-request',
64
71
  });
65
72
  return toolError(`Unknown integration tool: ${request.params.name}`);
66
73
  }
@@ -72,6 +79,7 @@ export function buildAgentToolsMcpServer(params: BuildAgentToolsMcpServerParams)
72
79
  arguments: args,
73
80
  method: NO_METHOD_LABEL,
74
81
  outcome: 'invalid-request',
82
+ errorCode: 'invalid-request',
75
83
  });
76
84
  return toolError('Tool arguments must be an object');
77
85
  }
@@ -83,6 +91,7 @@ export function buildAgentToolsMcpServer(params: BuildAgentToolsMcpServerParams)
83
91
  arguments: args,
84
92
  method: INVALID_METHOD_LABEL,
85
93
  outcome: 'invalid-request',
94
+ errorCode: 'invalid-request',
86
95
  });
87
96
  return toolError(methodValidation.message);
88
97
  }
@@ -99,6 +108,7 @@ export function buildAgentToolsMcpServer(params: BuildAgentToolsMcpServerParams)
99
108
  arguments: args,
100
109
  method,
101
110
  outcome: result.isError === true ? 'tool-error' : 'success',
111
+ ...toolCallErrorDetails(result),
102
112
  });
103
113
  return result;
104
114
  } catch (error) {
@@ -107,6 +117,7 @@ export function buildAgentToolsMcpServer(params: BuildAgentToolsMcpServerParams)
107
117
  arguments: args,
108
118
  method,
109
119
  outcome: 'exception',
120
+ errorCode: 'unknown',
110
121
  });
111
122
  throw error;
112
123
  }
@@ -115,6 +126,23 @@ export function buildAgentToolsMcpServer(params: BuildAgentToolsMcpServerParams)
115
126
  return server;
116
127
  }
117
128
 
129
+ function toolCallErrorDetails(result: CallToolResult): {
130
+ errorCode: IntegrationAgentToolCallErrorCode | 'none';
131
+ providerStatus?: number | undefined;
132
+ } {
133
+ if (result.isError !== true) return {errorCode: 'none'};
134
+
135
+ const structuredContent = isRecord(result.structuredContent)
136
+ ? result.structuredContent
137
+ : undefined;
138
+ const providerStatus = statusCode(structuredContent?.status);
139
+
140
+ return {
141
+ errorCode: normalizeIntegrationAgentToolCallErrorCode(structuredContent?.code),
142
+ ...(providerStatus === undefined ? {} : {providerStatus}),
143
+ };
144
+ }
145
+
118
146
  function recordToolCall(
119
147
  recordCall: IntegrationToolCallRecorder | undefined,
120
148
  record: Parameters<IntegrationToolCallRecorder>[0],
@@ -157,3 +185,9 @@ function toolError(message: string): CallToolResult {
157
185
  function isRecord(value: unknown): value is Record<string, unknown> {
158
186
  return typeof value === 'object' && value !== null && !Array.isArray(value);
159
187
  }
188
+
189
+ function statusCode(value: unknown): number | undefined {
190
+ return typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599
191
+ ? value
192
+ : undefined;
193
+ }
@@ -28,6 +28,7 @@ function isProviderError(error: unknown): error is IntegrationProviderError {
28
28
  error.reason === 'rate-limited' ||
29
29
  error.reason === 'timeout' ||
30
30
  error.reason === 'provider-unavailable' ||
31
+ error.reason === 'provider-rejected' ||
31
32
  error.reason === 'malformed-provider-response' ||
32
33
  error.reason === 'content-too-large' ||
33
34
  error.reason === 'too-many-files'))