Skip to content

Commit 7b4b766

Browse files
feat: include data in JSON-RPC error response (#774)
1 parent 0e482db commit 7b4b766

File tree

2 files changed

+118
-1
lines changed

2 files changed

+118
-1
lines changed

src/shared/protocol.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,122 @@ describe('protocol tests', () => {
653653
expect(sendSpy).toHaveBeenCalledTimes(2);
654654
});
655655
});
656+
657+
describe('Error data propagation', () => {
658+
test('should include error data in response when request handler throws error with data', async () => {
659+
await protocol.connect(transport);
660+
661+
// Set up request handler that throws error with data
662+
const RequestSchema = z.object({
663+
method: z.literal('test/error-with-data')
664+
});
665+
666+
protocol.setRequestHandler(RequestSchema, () => {
667+
const error = new Error('Test error');
668+
error['code'] = ErrorCode.InvalidParams;
669+
error['data'] = { details: 'Additional context', errorCode: 42 };
670+
throw error;
671+
});
672+
673+
// Simulate incoming request
674+
transport.onmessage?.({
675+
jsonrpc: '2.0',
676+
id: 1,
677+
method: 'test/error-with-data'
678+
});
679+
680+
// Wait for async error handling
681+
await new Promise(resolve => setImmediate(resolve));
682+
683+
// Verify error response includes data
684+
expect(sendSpy).toHaveBeenCalledWith({
685+
jsonrpc: '2.0',
686+
id: 1,
687+
error: {
688+
code: ErrorCode.InvalidParams,
689+
message: 'Test error',
690+
data: { details: 'Additional context', errorCode: 42 }
691+
}
692+
});
693+
});
694+
695+
test('should not include data field when request handler throws error without data', async () => {
696+
await protocol.connect(transport);
697+
698+
// Set up request handler that throws error without data
699+
const RequestSchema = z.object({
700+
method: z.literal('test/error-without-data')
701+
});
702+
703+
protocol.setRequestHandler(RequestSchema, () => {
704+
const error = new Error('Test error');
705+
error['code'] = ErrorCode.InternalError;
706+
throw error;
707+
});
708+
709+
// Simulate incoming request
710+
transport.onmessage?.({
711+
jsonrpc: '2.0',
712+
id: 2,
713+
method: 'test/error-without-data'
714+
});
715+
716+
// Wait for async error handling
717+
await new Promise(resolve => setImmediate(resolve));
718+
719+
// Verify error response does not include data field
720+
expect(sendSpy).toHaveBeenCalledWith({
721+
jsonrpc: '2.0',
722+
id: 2,
723+
error: {
724+
code: ErrorCode.InternalError,
725+
message: 'Test error'
726+
}
727+
});
728+
});
729+
730+
test('should include error data when request handler throws McpError with data', async () => {
731+
await protocol.connect(transport);
732+
733+
// Set up request handler that throws McpError with data
734+
const RequestSchema = z.object({
735+
method: z.literal('test/mcperror-with-data')
736+
});
737+
738+
protocol.setRequestHandler(RequestSchema, () => {
739+
throw new McpError(ErrorCode.InvalidParams, 'Invalid parameter', {
740+
paramName: 'userId',
741+
expectedType: 'string',
742+
actualType: 'number'
743+
});
744+
});
745+
746+
// Simulate incoming request
747+
transport.onmessage?.({
748+
jsonrpc: '2.0',
749+
id: 3,
750+
method: 'test/mcperror-with-data'
751+
});
752+
753+
// Wait for async error handling
754+
await new Promise(resolve => setImmediate(resolve));
755+
756+
// Verify error response includes data from McpError
757+
expect(sendSpy).toHaveBeenCalledWith({
758+
jsonrpc: '2.0',
759+
id: 3,
760+
error: {
761+
code: ErrorCode.InvalidParams,
762+
message: 'Invalid parameter',
763+
data: {
764+
paramName: 'userId',
765+
expectedType: 'string',
766+
actualType: 'number'
767+
}
768+
}
769+
});
770+
});
771+
});
656772
});
657773

658774
describe('mergeCapabilities', () => {

src/shared/protocol.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,8 @@ export abstract class Protocol<SendRequestT extends Request, SendNotificationT e
396396
id: request.id,
397397
error: {
398398
code: Number.isSafeInteger(error['code']) ? error['code'] : ErrorCode.InternalError,
399-
message: error.message ?? 'Internal error'
399+
message: error.message ?? 'Internal error',
400+
...(error.data && { data: error.data })
400401
}
401402
});
402403
}

0 commit comments

Comments
 (0)