Skip to content

Commit d2be6ac

Browse files
committed
add tests
1 parent 001555d commit d2be6ac

3 files changed

Lines changed: 88 additions & 129 deletions

File tree

packages/core/test/lib/integrations/mcp-server/piiFiltering.test.ts

Lines changed: 24 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,31 @@ describe('MCP Server PII Filtering', () => {
1313
vi.clearAllMocks();
1414
});
1515

16-
describe('Integration Tests', () => {
16+
describe('Integration Tests - Network PII', () => {
1717
let mockMcpServer: ReturnType<typeof createMockMcpServer>;
18-
let wrappedMcpServer: ReturnType<typeof createMockMcpServer>;
1918
let mockTransport: ReturnType<typeof createMockTransport>;
2019

2120
beforeEach(() => {
2221
mockMcpServer = createMockMcpServer();
23-
wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
2422
mockTransport = createMockTransport();
2523
mockTransport.sessionId = 'test-session-123';
2624
});
2725

28-
it('should include PII data when sendDefaultPii is true', async () => {
29-
// Mock client with sendDefaultPii: true
26+
it('should include network PII when sendDefaultPii is true', async () => {
3027
getClientSpy.mockReturnValue({
3128
getOptions: () => ({ sendDefaultPii: true }),
3229
getDsn: () => ({ publicKey: 'test-key', host: 'test-host' }),
3330
emit: vi.fn(),
3431
} as unknown as ReturnType<typeof currentScopes.getClient>);
3532

33+
const wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
3634
await wrappedMcpServer.connect(mockTransport);
3735

3836
const jsonRpcRequest = {
3937
jsonrpc: '2.0',
4038
method: 'tools/call',
4139
id: 'req-pii-true',
42-
params: { name: 'weather', arguments: { location: 'London', units: 'metric' } },
40+
params: { name: 'weather', arguments: { location: 'London' } },
4341
};
4442

4543
const extraWithClientInfo = {
@@ -51,35 +49,31 @@ describe('MCP Server PII Filtering', () => {
5149

5250
mockTransport.onmessage?.(jsonRpcRequest, extraWithClientInfo);
5351

54-
expect(startInactiveSpanSpy).toHaveBeenCalledWith({
55-
name: 'tools/call weather',
56-
op: 'mcp.server',
57-
forceTransaction: true,
58-
attributes: expect.objectContaining({
59-
'client.address': '192.168.1.100',
60-
'client.port': 54321,
61-
'mcp.request.argument.location': '"London"',
62-
'mcp.request.argument.units': '"metric"',
63-
'mcp.tool.name': 'weather',
52+
expect(startInactiveSpanSpy).toHaveBeenCalledWith(
53+
expect.objectContaining({
54+
attributes: expect.objectContaining({
55+
'client.address': '192.168.1.100',
56+
'client.port': 54321,
57+
}),
6458
}),
65-
});
59+
);
6660
});
6761

68-
it('should exclude PII data when sendDefaultPii is false', async () => {
69-
// Mock client with sendDefaultPii: false
62+
it('should exclude network PII when sendDefaultPii is false', async () => {
7063
getClientSpy.mockReturnValue({
7164
getOptions: () => ({ sendDefaultPii: false }),
7265
getDsn: () => ({ publicKey: 'test-key', host: 'test-host' }),
7366
emit: vi.fn(),
7467
} as unknown as ReturnType<typeof currentScopes.getClient>);
7568

69+
const wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
7670
await wrappedMcpServer.connect(mockTransport);
7771

7872
const jsonRpcRequest = {
7973
jsonrpc: '2.0',
8074
method: 'tools/call',
8175
id: 'req-pii-false',
82-
params: { name: 'weather', arguments: { location: 'London', units: 'metric' } },
76+
params: { name: 'weather', arguments: { location: 'London' } },
8377
};
8478

8579
const extraWithClientInfo = {
@@ -96,8 +90,6 @@ describe('MCP Server PII Filtering', () => {
9690
attributes: expect.not.objectContaining({
9791
'client.address': expect.anything(),
9892
'client.port': expect.anything(),
99-
'mcp.request.argument.location': expect.anything(),
100-
'mcp.request.argument.units': expect.anything(),
10193
}),
10294
}),
10395
);
@@ -111,130 +103,41 @@ describe('MCP Server PII Filtering', () => {
111103
}),
112104
);
113105
});
114-
115-
it('should filter tool result content when sendDefaultPii is false', async () => {
116-
// Mock client with sendDefaultPii: false
117-
getClientSpy.mockReturnValue({
118-
getOptions: () => ({ sendDefaultPii: false }),
119-
} as ReturnType<typeof currentScopes.getClient>);
120-
121-
await wrappedMcpServer.connect(mockTransport);
122-
123-
const mockSpan = {
124-
setAttributes: vi.fn(),
125-
setStatus: vi.fn(),
126-
end: vi.fn(),
127-
} as unknown as ReturnType<typeof tracingModule.startInactiveSpan>;
128-
startInactiveSpanSpy.mockReturnValueOnce(mockSpan);
129-
130-
const toolCallRequest = {
131-
jsonrpc: '2.0',
132-
method: 'tools/call',
133-
id: 'req-tool-result-filtered',
134-
params: { name: 'weather-lookup' },
135-
};
136-
137-
mockTransport.onmessage?.(toolCallRequest, {});
138-
139-
const toolResponse = {
140-
jsonrpc: '2.0',
141-
id: 'req-tool-result-filtered',
142-
result: {
143-
content: [{ type: 'text', text: 'Sensitive weather data for London' }],
144-
isError: false,
145-
},
146-
};
147-
148-
mockTransport.send?.(toolResponse);
149-
150-
// Tool result content should be filtered out, but metadata should remain
151-
const setAttributesCall = mockSpan.setAttributes.mock.calls[0]?.[0];
152-
expect(setAttributesCall).toBeDefined();
153-
expect(setAttributesCall).not.toHaveProperty('mcp.tool.result.content');
154-
expect(setAttributesCall).toHaveProperty('mcp.tool.result.is_error', false);
155-
expect(setAttributesCall).toHaveProperty('mcp.tool.result.content_count', 1);
156-
});
157106
});
158107

159108
describe('filterMcpPiiFromSpanData Function', () => {
160109
it('should preserve all data when sendDefaultPii is true', () => {
161110
const spanData = {
162111
'client.address': '192.168.1.100',
163112
'client.port': 54321,
164-
'mcp.request.argument.location': '"San Francisco"',
165-
'mcp.tool.result.content': 'Weather data: 18°C',
166-
'mcp.tool.result.content_count': 1,
167-
'mcp.prompt.result.description': 'Code review prompt for sensitive analysis',
168-
'mcp.prompt.result.message_content': 'Please review this confidential code.',
169-
'mcp.prompt.result.message_count': 1,
170-
'mcp.resource.result.content': 'Sensitive resource content',
171-
'mcp.logging.message': 'User requested weather',
172113
'mcp.resource.uri': 'file:///private/docs/secret.txt',
173-
'mcp.method.name': 'tools/call', // Non-PII should remain
114+
'mcp.method.name': 'tools/call',
115+
'mcp.tool.name': 'weather',
174116
};
175117

176118
const result = filterMcpPiiFromSpanData(spanData, true);
177119

178-
expect(result).toEqual(spanData); // All data preserved
120+
expect(result).toEqual(spanData);
179121
});
180122

181-
it('should remove PII data when sendDefaultPii is false', () => {
123+
it('should only remove network PII when sendDefaultPii is false', () => {
182124
const spanData = {
183125
'client.address': '192.168.1.100',
184126
'client.port': 54321,
185-
'mcp.request.argument.location': '"San Francisco"',
186-
'mcp.request.argument.units': '"celsius"',
187-
'mcp.tool.result.content': 'Weather data: 18°C',
188-
'mcp.tool.result.content_count': 1,
189-
'mcp.prompt.result.description': 'Code review prompt for sensitive analysis',
190-
'mcp.prompt.result.message_count': 2,
191-
'mcp.prompt.result.0.role': 'user',
192-
'mcp.prompt.result.0.content': 'Sensitive prompt content',
193-
'mcp.prompt.result.1.role': 'assistant',
194-
'mcp.prompt.result.1.content': 'Another sensitive response',
195-
'mcp.resource.result.content_count': 1,
196-
'mcp.resource.result.uri': 'file:///private/file.txt',
197-
'mcp.resource.result.content': 'Sensitive resource content',
198-
'mcp.logging.message': 'User requested weather',
199127
'mcp.resource.uri': 'file:///private/docs/secret.txt',
200-
'mcp.method.name': 'tools/call', // Non-PII should remain
201-
'mcp.session.id': 'test-session-123', // Non-PII should remain
128+
'mcp.method.name': 'tools/call',
129+
'mcp.tool.name': 'weather',
130+
'mcp.session.id': 'test-session-123',
202131
};
203132

204133
const result = filterMcpPiiFromSpanData(spanData, false);
205134

206-
// Client info should be filtered
207135
expect(result).not.toHaveProperty('client.address');
208136
expect(result).not.toHaveProperty('client.port');
209-
210-
// Request arguments should be filtered
211-
expect(result).not.toHaveProperty('mcp.request.argument.location');
212-
expect(result).not.toHaveProperty('mcp.request.argument.units');
213-
214-
// Specific PII content attributes should be filtered
215-
expect(result).not.toHaveProperty('mcp.tool.result.content');
216-
expect(result).not.toHaveProperty('mcp.prompt.result.description');
217-
218-
// Count attributes should remain as they don't contain sensitive content
219-
expect(result).toHaveProperty('mcp.tool.result.content_count', 1);
220-
expect(result).toHaveProperty('mcp.prompt.result.message_count', 2);
221-
222-
// All tool and prompt result content should be filtered (including indexed attributes)
223-
expect(result).not.toHaveProperty('mcp.prompt.result.0.role');
224-
expect(result).not.toHaveProperty('mcp.prompt.result.0.content');
225-
expect(result).not.toHaveProperty('mcp.prompt.result.1.role');
226-
expect(result).not.toHaveProperty('mcp.prompt.result.1.content');
227-
228-
expect(result).toHaveProperty('mcp.resource.result.content_count', 1);
229-
expect(result).toHaveProperty('mcp.resource.result.uri', 'file:///private/file.txt');
230-
expect(result).toHaveProperty('mcp.resource.result.content', 'Sensitive resource content');
231-
232-
// Other PII attributes should be filtered
233-
expect(result).not.toHaveProperty('mcp.logging.message');
234137
expect(result).not.toHaveProperty('mcp.resource.uri');
235138

236-
// Non-PII attributes should remain
237139
expect(result).toHaveProperty('mcp.method.name', 'tools/call');
140+
expect(result).toHaveProperty('mcp.tool.name', 'weather');
238141
expect(result).toHaveProperty('mcp.session.id', 'test-session-123');
239142
});
240143

@@ -243,10 +146,11 @@ describe('MCP Server PII Filtering', () => {
243146
expect(result).toEqual({});
244147
});
245148

246-
it('should handle span data with no PII attributes', () => {
149+
it('should handle span data with no network PII attributes', () => {
247150
const spanData = {
248151
'mcp.method.name': 'tools/list',
249152
'mcp.session.id': 'test-session',
153+
'mcp.tool.name': 'weather',
250154
};
251155

252156
const result = filterMcpPiiFromSpanData(spanData, false);

packages/core/test/lib/integrations/mcp-server/semanticConventions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('MCP Server Semantic Conventions', () => {
2626

2727
beforeEach(() => {
2828
mockMcpServer = createMockMcpServer();
29-
wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
29+
wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer, { recordInputs: true, recordOutputs: true });
3030
mockTransport = createMockTransport();
3131
mockTransport.sessionId = 'test-session-123';
3232
});

packages/core/test/lib/integrations/mcp-server/transportInstrumentation.test.ts

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ describe('MCP Server Transport Instrumentation', () => {
190190

191191
beforeEach(() => {
192192
mockMcpServer = createMockMcpServer();
193-
wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
193+
wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer, { recordInputs: true });
194194
mockStdioTransport = createMockStdioTransport();
195195
mockStdioTransport.sessionId = 'stdio-session-456';
196196
});
@@ -308,15 +308,15 @@ describe('MCP Server Transport Instrumentation', () => {
308308
it('should test wrapTransportOnMessage directly', () => {
309309
const originalOnMessage = mockTransport.onmessage;
310310

311-
wrapTransportOnMessage(mockTransport);
311+
wrapTransportOnMessage(mockTransport, false);
312312

313313
expect(mockTransport.onmessage).not.toBe(originalOnMessage);
314314
});
315315

316316
it('should test wrapTransportSend directly', () => {
317317
const originalSend = mockTransport.send;
318318

319-
wrapTransportSend(mockTransport);
319+
wrapTransportSend(mockTransport, false);
320320

321321
expect(mockTransport.send).not.toBe(originalSend);
322322
});
@@ -345,12 +345,17 @@ describe('MCP Server Transport Instrumentation', () => {
345345
params: { name: 'test-tool', arguments: { input: 'test' } },
346346
};
347347

348-
const config = buildMcpServerSpanConfig(jsonRpcRequest, mockTransport, {
349-
requestInfo: {
350-
remoteAddress: '127.0.0.1',
351-
remotePort: 8080,
348+
const config = buildMcpServerSpanConfig(
349+
jsonRpcRequest,
350+
mockTransport,
351+
{
352+
requestInfo: {
353+
remoteAddress: '127.0.0.1',
354+
remotePort: 8080,
355+
},
352356
},
353-
});
357+
true, // recordInputs
358+
);
354359

355360
expect(config).toEqual({
356361
name: 'tools/call test-tool',
@@ -655,4 +660,54 @@ describe('MCP Server Transport Instrumentation', () => {
655660
expect(mockSpan.end).toHaveBeenCalled();
656661
});
657662
});
663+
664+
describe('Wrapper Options', () => {
665+
it('should NOT capture request arguments by default (recordInputs: false)', async () => {
666+
const mockMcpServer = createMockMcpServer();
667+
const wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
668+
const transport = createMockTransport();
669+
670+
await wrappedMcpServer.connect(transport);
671+
672+
transport.onmessage?.(
673+
{
674+
jsonrpc: '2.0',
675+
method: 'tools/call',
676+
id: 'tool-1',
677+
params: { name: 'weather', arguments: { location: 'London' } },
678+
},
679+
{},
680+
);
681+
682+
expect(startInactiveSpanSpy).toHaveBeenCalledWith(
683+
expect.objectContaining({
684+
attributes: expect.not.objectContaining({
685+
'mcp.request.argument.location': expect.anything(),
686+
}),
687+
}),
688+
);
689+
});
690+
691+
it('should NOT capture tool outputs by default (recordOutputs: false)', async () => {
692+
const mockMcpServer = createMockMcpServer();
693+
const wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
694+
const transport = createMockTransport();
695+
696+
await wrappedMcpServer.connect(transport);
697+
698+
const setAttributesSpy = vi.fn();
699+
const mockSpan = { setAttributes: setAttributesSpy, end: vi.fn() };
700+
startInactiveSpanSpy.mockReturnValueOnce(mockSpan as any);
701+
702+
transport.onmessage?.({ jsonrpc: '2.0', method: 'tools/call', id: 'tool-1', params: { name: 'weather' } }, {});
703+
704+
await transport.send?.({
705+
jsonrpc: '2.0',
706+
id: 'tool-1',
707+
result: { content: [{ type: 'text', text: 'Sunny' }], isError: false },
708+
});
709+
710+
expect(setAttributesSpy).not.toHaveBeenCalled();
711+
});
712+
});
658713
});

0 commit comments

Comments
 (0)