Skip to content
This repository was archived by the owner on Nov 14, 2025. It is now read-only.

Commit e98603f

Browse files
sapientpantsclaude
andcommitted
fix: resolve mcp tool registration issue with zod v3 compatibility
- Convert tool schemas from ZodObject to ZodRawShape format - Update ToolDefinition interface to accept both ZodType and ZodRawShape - Fix nested z.object() calls in tool-definitions to have proper parentheses - Add runtime wrapping of ZodRawShape to ZodObject for validation - Ensure transport is set before tool registration - Update tests to handle ZodRawShape format with parseInput helper This resolves the issue where tools were successfully registered internally but not exposed through the MCP protocol. The MCP SDK expects inputSchema and outputSchema to be ZodRawShape objects, not ZodObject instances. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d7a7d5b commit e98603f

File tree

5 files changed

+319
-222
lines changed

5 files changed

+319
-222
lines changed

src/__tests__/server/tool-definitions.test.ts

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @fileoverview Tests for tool definitions and schemas
33
*/
44

5-
// import { z } from 'zod'; // Not needed for these tests
5+
import { z } from 'zod';
66
import {
77
projectsToolSchema,
88
qualityMetricsToolSchema,
@@ -17,6 +17,19 @@ import {
1717
toolSchemas,
1818
} from '../../server/tool-definitions.js';
1919

20+
// Helper function to parse input with ZodRawShape
21+
function parseInput(schema: { inputSchema?: z.ZodRawShape | z.ZodSchema }, input: unknown) {
22+
if (!schema.inputSchema) return undefined;
23+
24+
// Check if it's already a ZodSchema (has safeParse method)
25+
if ('safeParse' in schema.inputSchema) {
26+
return (schema.inputSchema as z.ZodSchema).safeParse(input);
27+
}
28+
29+
// Otherwise, wrap it in z.object()
30+
return z.object(schema.inputSchema as z.ZodRawShape).safeParse(input);
31+
}
32+
2033
describe('Tool Definitions', () => {
2134
describe('projectsToolSchema', () => {
2235
it('should have correct name and description', () => {
@@ -26,7 +39,9 @@ describe('Tool Definitions', () => {
2639

2740
it('should have empty input schema', () => {
2841
expect(projectsToolSchema.inputSchema).toBeDefined();
29-
expect(projectsToolSchema.inputSchema.parse({})).toEqual({});
42+
const result = parseInput(projectsToolSchema, {});
43+
expect(result?.success).toBe(true);
44+
expect(result?.data).toEqual({});
3045
});
3146

3247
it('should have output schema', () => {
@@ -46,7 +61,7 @@ describe('Tool Definitions', () => {
4661
shortcodeIn: ['LCV', 'BCV'],
4762
};
4863

49-
const result = qualityMetricsToolSchema.inputSchema?.safeParse(validInput);
64+
const result = parseInput(qualityMetricsToolSchema, validInput);
5065
expect(result?.success).toBe(true);
5166
});
5267

@@ -55,7 +70,7 @@ describe('Tool Definitions', () => {
5570
projectKey: 'test-project',
5671
};
5772

58-
const result = qualityMetricsToolSchema.inputSchema?.safeParse(validInput);
73+
const result = parseInput(qualityMetricsToolSchema, validInput);
5974
expect(result?.success).toBe(true);
6075
});
6176

@@ -64,7 +79,7 @@ describe('Tool Definitions', () => {
6479
shortcodeIn: ['LCV'],
6580
};
6681

67-
const result = qualityMetricsToolSchema.inputSchema?.safeParse(invalidInput);
82+
const result = parseInput(qualityMetricsToolSchema, invalidInput);
6883
expect(result?.success).toBe(false);
6984
});
7085

@@ -74,7 +89,7 @@ describe('Tool Definitions', () => {
7489
shortcodeIn: ['INVALID'],
7590
};
7691

77-
const result = qualityMetricsToolSchema.inputSchema?.safeParse(invalidInput);
92+
const result = parseInput(qualityMetricsToolSchema, invalidInput);
7893
expect(result?.success).toBe(false);
7994
});
8095
});
@@ -94,7 +109,7 @@ describe('Tool Definitions', () => {
94109
thresholdValue: 80,
95110
};
96111

97-
const result = updateMetricThresholdToolSchema.inputSchema?.safeParse(validInput);
112+
const result = parseInput(updateMetricThresholdToolSchema, validInput);
98113
expect(result?.success).toBe(true);
99114
});
100115

@@ -107,7 +122,7 @@ describe('Tool Definitions', () => {
107122
thresholdValue: null,
108123
};
109124

110-
const result = updateMetricThresholdToolSchema.inputSchema?.safeParse(validInput);
125+
const result = parseInput(updateMetricThresholdToolSchema, validInput);
111126
expect(result?.success).toBe(true);
112127
});
113128

@@ -117,7 +132,7 @@ describe('Tool Definitions', () => {
117132
metricShortcode: 'LCV',
118133
};
119134

120-
const result = updateMetricThresholdToolSchema.inputSchema?.safeParse(invalidInput);
135+
const result = parseInput(updateMetricThresholdToolSchema, invalidInput);
121136
expect(result?.success).toBe(false);
122137
});
123138
});
@@ -137,7 +152,7 @@ describe('Tool Definitions', () => {
137152
isThresholdEnforced: false,
138153
};
139154

140-
const result = updateMetricSettingToolSchema.inputSchema?.safeParse(validInput);
155+
const result = parseInput(updateMetricSettingToolSchema, validInput);
141156
expect(result?.success).toBe(true);
142157
});
143158

@@ -150,7 +165,7 @@ describe('Tool Definitions', () => {
150165
isThresholdEnforced: 0,
151166
};
152167

153-
const result = updateMetricSettingToolSchema.inputSchema?.safeParse(invalidInput);
168+
const result = parseInput(updateMetricSettingToolSchema, invalidInput);
154169
expect(result?.success).toBe(false);
155170
});
156171
});
@@ -179,7 +194,7 @@ describe('Tool Definitions', () => {
179194
reportType,
180195
};
181196

182-
const result = complianceReportToolSchema.inputSchema?.safeParse(input);
197+
const result = parseInput(complianceReportToolSchema, input);
183198
expect(result?.success).toBe(true);
184199
});
185200
});
@@ -190,7 +205,7 @@ describe('Tool Definitions', () => {
190205
reportType: 'INVALID_TYPE',
191206
};
192207

193-
const result = complianceReportToolSchema.inputSchema?.safeParse(invalidInput);
208+
const result = parseInput(complianceReportToolSchema, invalidInput);
194209
expect(result?.success).toBe(false);
195210
});
196211
});
@@ -211,7 +226,7 @@ describe('Tool Definitions', () => {
211226
after: 'cursor123',
212227
};
213228

214-
const result = projectIssuesToolSchema.inputSchema?.safeParse(validInput);
229+
const result = parseInput(projectIssuesToolSchema, validInput);
215230
expect(result?.success).toBe(true);
216231
});
217232

@@ -220,7 +235,7 @@ describe('Tool Definitions', () => {
220235
projectKey: 'test-project',
221236
};
222237

223-
const result = projectIssuesToolSchema.inputSchema?.safeParse(validInput);
238+
const result = parseInput(projectIssuesToolSchema, validInput);
224239
expect(result?.success).toBe(true);
225240
});
226241

@@ -230,7 +245,7 @@ describe('Tool Definitions', () => {
230245
first: -5,
231246
};
232247

233-
const result = projectIssuesToolSchema.inputSchema?.safeParse(invalidInput);
248+
const result = parseInput(projectIssuesToolSchema, invalidInput);
234249
// Note: The schema uses z.number() without validation, so negative values pass
235250
// This is a known limitation in the schema
236251
expect(result?.success).toBe(true);
@@ -251,7 +266,7 @@ describe('Tool Definitions', () => {
251266
before: 'cursor456',
252267
};
253268

254-
const result = runsToolSchema.inputSchema?.safeParse(validInput);
269+
const result = parseInput(runsToolSchema, validInput);
255270
expect(result?.success).toBe(true);
256271
});
257272

@@ -268,8 +283,8 @@ describe('Tool Definitions', () => {
268283
before: 'cursorB',
269284
};
270285

271-
expect(runsToolSchema.inputSchema?.safeParse(forwardInput)?.success).toBe(true);
272-
expect(runsToolSchema.inputSchema?.safeParse(backwardInput)?.success).toBe(true);
286+
expect(parseInput(runsToolSchema, forwardInput)?.success).toBe(true);
287+
expect(parseInput(runsToolSchema, backwardInput)?.success).toBe(true);
273288
});
274289
});
275290

@@ -287,7 +302,7 @@ describe('Tool Definitions', () => {
287302
runIdentifier: 'run-uid-123',
288303
};
289304

290-
const result = runToolSchema.inputSchema?.safeParse(validInput);
305+
const result = parseInput(runToolSchema, validInput);
291306
expect(result?.success).toBe(true);
292307
});
293308

@@ -298,7 +313,7 @@ describe('Tool Definitions', () => {
298313
isCommitOid: true,
299314
};
300315

301-
const result = runToolSchema.inputSchema?.safeParse(validInput);
316+
const result = parseInput(runToolSchema, validInput);
302317
expect(result?.success).toBe(true);
303318
});
304319

@@ -309,7 +324,7 @@ describe('Tool Definitions', () => {
309324
isCommitOid: false,
310325
};
311326

312-
const result = runToolSchema.inputSchema?.safeParse(validInput);
327+
const result = parseInput(runToolSchema, validInput);
313328
expect(result?.success).toBe(true);
314329
});
315330
});
@@ -327,7 +342,7 @@ describe('Tool Definitions', () => {
327342
first: 50,
328343
};
329344

330-
const result = recentRunIssuesToolSchema.inputSchema?.safeParse(validInput);
345+
const result = parseInput(recentRunIssuesToolSchema, validInput);
331346
expect(result?.success).toBe(true);
332347
});
333348

@@ -340,8 +355,8 @@ describe('Tool Definitions', () => {
340355
branchName: 'main',
341356
};
342357

343-
expect(recentRunIssuesToolSchema.inputSchema?.safeParse(missingBranch)?.success).toBe(false);
344-
expect(recentRunIssuesToolSchema.inputSchema?.safeParse(missingProject)?.success).toBe(false);
358+
expect(parseInput(recentRunIssuesToolSchema, missingBranch)?.success).toBe(false);
359+
expect(parseInput(recentRunIssuesToolSchema, missingProject)?.success).toBe(false);
345360
});
346361
});
347362

@@ -360,7 +375,7 @@ describe('Tool Definitions', () => {
360375
after: 'vuln-cursor',
361376
};
362377

363-
const result = dependencyVulnerabilitiesToolSchema.inputSchema?.safeParse(validInput);
378+
const result = parseInput(dependencyVulnerabilitiesToolSchema, validInput);
364379
expect(result?.success).toBe(true);
365380
});
366381

@@ -369,7 +384,7 @@ describe('Tool Definitions', () => {
369384
projectKey: 'test-project',
370385
};
371386

372-
const result = dependencyVulnerabilitiesToolSchema.inputSchema?.safeParse(validInput);
387+
const result = parseInput(dependencyVulnerabilitiesToolSchema, validInput);
373388
expect(result?.success).toBe(true);
374389
});
375390
});
@@ -412,14 +427,16 @@ describe('Tool Definitions', () => {
412427
tags: [],
413428
};
414429

415-
const result = projectIssuesToolSchema.inputSchema?.safeParse(input);
430+
const result = parseInput(projectIssuesToolSchema, input);
416431
expect(result?.success).toBe(true);
417432
});
418433

419434
it('should handle very long project keys', () => {
420435
// projectsToolSchema has empty input schema
421436
expect(projectsToolSchema.inputSchema).toBeDefined();
422-
expect(projectsToolSchema.inputSchema.parse({})).toEqual({});
437+
const result = parseInput(projectsToolSchema, {});
438+
expect(result?.success).toBe(true);
439+
expect(result?.data).toEqual({});
423440
});
424441

425442
it('should handle special characters in strings', () => {
@@ -428,7 +445,7 @@ describe('Tool Definitions', () => {
428445
path: '/path/with spaces/and-special!@#$%.py',
429446
};
430447

431-
const result = projectIssuesToolSchema.inputSchema?.safeParse(input);
448+
const result = parseInput(projectIssuesToolSchema, input);
432449
expect(result?.success).toBe(true);
433450
});
434451

@@ -441,7 +458,7 @@ describe('Tool Definitions', () => {
441458
shortcodeIn: [code],
442459
};
443460

444-
const result = qualityMetricsToolSchema.inputSchema?.safeParse(input);
461+
const result = parseInput(qualityMetricsToolSchema, input);
445462
expect(result?.success).toBe(true);
446463
});
447464
});

src/server/mcp-server.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,25 +75,55 @@ export class DeepSourceMCPServer {
7575
version: this.config.version,
7676
});
7777

78+
// Set transport FIRST (default to stdio if not provided)
79+
// This must be set before registering tools
80+
this.transport = this.config.transport || new StdioServerTransport();
81+
7882
// Initialize tool registry
7983
const handlerDeps = this.config.handlerDeps || createDefaultHandlerDeps();
8084
this.toolRegistry = new ToolRegistry(this.mcpServer, handlerDeps);
8185

82-
// Auto-register tools if configured
86+
// Auto-register tools if configured (AFTER transport is set)
8387
if (this.config.autoRegisterTools) {
8488
this.registerDefaultTools();
8589
}
86-
87-
// Set transport (default to stdio if not provided)
88-
this.transport = this.config.transport || new StdioServerTransport();
8990
}
9091

9192
/**
9293
* Registers the default DeepSource tools
9394
*/
9495
private registerDefaultTools(): void {
95-
logger.info('Registering default DeepSource tools');
96-
registerDeepSourceTools(this.toolRegistry);
96+
try {
97+
logger.info('Starting registration of default DeepSource tools', {
98+
mcpServerType: typeof this.mcpServer,
99+
mcpServerExists: !!this.mcpServer,
100+
toolRegistryExists: !!this.toolRegistry,
101+
});
102+
103+
registerDeepSourceTools(this.toolRegistry);
104+
105+
const registeredTools = this.toolRegistry.getToolNames();
106+
logger.info('Successfully registered DeepSource tools', {
107+
registeredTools,
108+
toolCount: registeredTools.length,
109+
});
110+
111+
// Verify tools are actually registered with the MCP server
112+
// The MCP server doesn't expose a direct way to query tools,
113+
// but we can log what we've registered
114+
logger.info('Tool registration verification', {
115+
toolRegistryCount: registeredTools.length,
116+
mcpServerConnected: this.isConnected,
117+
transportSet: !!this.transport,
118+
});
119+
} catch (error) {
120+
logger.error('Failed to register DeepSource tools', {
121+
error,
122+
message: error instanceof Error ? error.message : String(error),
123+
stack: error instanceof Error ? error.stack : undefined,
124+
});
125+
throw error;
126+
}
97127
}
98128

99129
/**

0 commit comments

Comments
 (0)