-
Notifications
You must be signed in to change notification settings - Fork 18
Fix: Resolve MCP SDK compatibility issues and improve tool handlers #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…/sdk to ^1.20.1\n- convert Zod input schemas to JSON Schema for registerTool\n- fallback to minimal {type:'object'} schema for safety\n- ensure getManifest emits JSON-Schema-friendly tool definitions\n
…efault zod for legacy clients\n- json for MCP SDK 1.19+ validators\n
- Goose 1.11+ requires JSON Schema (no more Zod shapes) - Remove conditional logic; toObjectJsonSchema() always applied - Simplifies codebase and aligns with MCP SDK 1.19+ requirements
- zod-to-json-schema 3.24.6 is incompatible with Zod 4.x - Zod 4.x was producing invalid JSON Schema (type: 'string' for z.object()) - Downgraded zod from ^4.1.9 to ^3.23.8 - Simplified toObjectJsonSchema() function - now works correctly - Validated with MCP Inspector: all tools pass schema validation - Goose 1.11.1 still fails with 'keyValidator._parse' error (client bug)
- Fix tool handler return types to use structuredContent wrapper for consistency - Fix collection update to support parentId parameter - Rewrite bulk edit raindrops handler to use RaindropService instead of direct API calls - Fix schema registration to extract Zod schema shape directly instead of converting to JSON Schema - Improve error handling in bulk operations with per-item error tracking These changes ensure proper compatibility with MCP SDK >= 1.19 and improve reliability of bulk operations.
Summary of ChangesHello @rhumphries7, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request primarily focuses on enhancing compatibility with the latest MCP SDK (version 1.19+) and improving the robustness of various tool handlers. The changes ensure that the system's interactions with the SDK are consistent and reliable, particularly for collection management and bulk operations, while also refining how schemas are registered. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
Summary of ChangesHello @rhumphries7, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request focuses on a critical update to ensure full compatibility with the latest MCP SDK (version 1.19+), while also significantly enhancing the robustness and functionality of several Raindrop tool handlers. The changes standardize tool output formats, introduce new capabilities for collection management, and drastically improve the reliability of bulk operations. These updates aim to provide a more stable and predictable experience when interacting with the Raindrop.io API through the MCP server. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request addresses several compatibility issues with the MCP SDK and improves the reliability of tool handlers. Key changes include updating dependency versions, ensuring tool handlers return a consistent structuredContent wrapper, adding support for parentId in collection updates, and rewriting the handleBulkEditRaindrops function to use RaindropService for improved error handling. The schema registration has also been fixed to correctly extract Zod schema shapes.
While the changes significantly improve compatibility and robustness, there are a few areas that could be further optimized for performance and type safety, particularly in the handleBulkEditRaindrops function and the use of any for return types.
| async function handleBulkEditRaindrops(args: z.infer<typeof BulkEditRaindropsInputSchema>, { raindropService }: ToolHandlerContext) { | ||
| try { | ||
| const response = await fetch(url, { | ||
| method: 'PUT', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify(body), | ||
| }); | ||
| const result = await response.json() as { result: boolean; errorMessage?: string; modified?: number }; | ||
| if (!result.result) { | ||
| throw new Error(result.errorMessage || 'Bulk edit failed'); | ||
| // Get all bookmarks from the source collection | ||
| const bookmarks = await raindropService.getBookmarks({ collection: args.collectionId }); | ||
| const ids = args.ids || bookmarks.items.map(b => b._id); | ||
|
|
||
| if (args.collection && ids.length > 0) { | ||
| // Move bookmarks one by one (more reliable than batch endpoint) | ||
| let moved = 0; | ||
| let errors = 0; | ||
|
|
||
| for (const id of ids) { | ||
| try { | ||
| await raindropService.updateBookmark(id, { | ||
| collection: { $id: args.collection.$id } as any | ||
| }); | ||
| moved++; | ||
| } catch (err) { | ||
| errors++; | ||
| // Continue with other bookmarks even if one fails | ||
| } | ||
| } | ||
|
|
||
| if (errors > 0) { | ||
| throw new Error(`Moved ${moved} bookmarks, ${errors} failed`); | ||
| } | ||
|
|
||
| const returnValue = { | ||
| content: [{ | ||
| type: 'text', | ||
| text: `Bulk edit successful. Moved ${moved} bookmarks to collection ${args.collection.$id}`, | ||
| }], | ||
| }; | ||
| return { | ||
| content: returnValue.content, | ||
| structuredContent: returnValue, | ||
| }; | ||
| } else { | ||
| throw new Error('Collection destination and bookmark IDs are required'); | ||
| } | ||
| return { | ||
| content: [{ | ||
| type: 'text', | ||
| text: `Bulk edit successful. Modified: ${result.modified ?? 'unknown'}`, | ||
| }], | ||
| }; | ||
| } catch (err) { | ||
| return { | ||
| const returnValue = { | ||
| content: [{ | ||
| type: 'text', | ||
| text: `Bulk edit error: ${(err as Error).message}`, | ||
| }], | ||
| }; | ||
| return { | ||
| content: returnValue.content, | ||
| structuredContent: returnValue, | ||
| isError: true, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rewrite of handleBulkEditRaindrops currently only handles moving bookmarks to another collection (args.collection). It appears to ignore other bulk edit parameters defined in BulkEditRaindropsInputSchema, such as important, tags, media, cover, and nested. This constitutes a functional regression as these capabilities are no longer supported by the tool.
Additionally, the new implementation fetches all bookmarks and then updates them one by one. While this might be more reliable if the batch endpoint had issues, it can be significantly less efficient for large collections, leading to many individual API calls and potential rate limiting issues. If the Raindrop.io API has a functional batch update endpoint, it would be more performant to utilize it for these other bulk operations.
| }; | ||
| } | ||
| } catch (err) { | ||
| console.error('Error converting Zod schema to JSON Schema:', err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using console.error directly for logging errors within toObjectJsonSchema can lead to inconsistent logging practices. It's generally better to use a centralized logger (like the logger instance used elsewhere in the project) for all error reporting to ensure consistency and proper log management.
| console.error('Error converting Zod schema to JSON Schema:', err); | |
| logger.error('Error converting Zod schema to JSON Schema:', err); |
|
|
||
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<z.infer<typeof DiagnosticsOutputSchema>> { | ||
| return { | ||
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<any> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing the return type of handleDiagnostics to Promise<any> reduces type safety. While the structuredContent wrapper is necessary for MCP SDK compatibility, the internal result variable could still adhere to DiagnosticsOutputSchema to maintain type safety within the function. The structuredContent wrapper can then be applied at the very end, ensuring the function's core logic remains strongly typed.
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<any> { | |
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<z.infer<typeof DiagnosticsOutputSchema>> { |
| const result = { content }; | ||
| return { | ||
| content: result.content, | ||
| structuredContent: result, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to handleDiagnostics, changing the return type to Promise<any> for handleCollectionList reduces type safety. Consider defining an internal type for returnValue that aligns with CollectionListOutputSchema before wrapping it in structuredContent.
| const result = { content }; | |
| return { | |
| content: result.content, | |
| structuredContent: result, | |
| }; | |
| const result: z.infer<typeof CollectionListOutputSchema> = { content }; | |
| return { | |
| content: result.content, | |
| structuredContent: result, | |
| }; |
| const returnValue = { content }; | ||
| return { | ||
| content: returnValue.content, | ||
| structuredContent: returnValue, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type for handleBookmarkSearch is implicitly any due to the structuredContent wrapper. To maintain type safety, you could define an intermediate variable with the expected BookmarkSearchOutputSchema type before wrapping it for the final return.
| const returnValue = { content }; | |
| return { | |
| content: returnValue.content, | |
| structuredContent: returnValue, | |
| }; | |
| const returnValue: z.infer<typeof BookmarkSearchOutputSchema> = { content }; | |
| return { | |
| content: returnValue.content, | |
| structuredContent: returnValue, | |
| }; |
| const result = { | ||
| content: [makeBookmarkLink(bookmark)], | ||
| }; | ||
| return { | ||
| content: result.content, | ||
| structuredContent: result, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type for handleGetRaindrop is implicitly any. To improve type safety, define an intermediate variable with the expected GetRaindropOutputSchema type before wrapping it in structuredContent.
| const result = { | |
| content: [makeBookmarkLink(bookmark)], | |
| }; | |
| return { | |
| content: result.content, | |
| structuredContent: result, | |
| }; | |
| const result: z.infer<typeof GetRaindropOutputSchema> = { | |
| content: [makeBookmarkLink(bookmark)], | |
| }; | |
| return { | |
| content: result.content, | |
| structuredContent: result, | |
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request successfully addresses several compatibility issues with the MCP SDK by updating dependency versions, tool handler return structures, and schema registration logic. The refactoring of handleBulkEditRaindrops to use the RaindropService is a positive architectural change. However, this refactoring has led to a critical feature regression, as the new implementation only supports moving bookmarks and has lost other bulk update capabilities. I have also identified a high-severity security concern in a fallback schema generation case and a few medium-severity issues related to type safety and code consistency that should be addressed.
| } catch (err) { | ||
| console.error('Error converting Zod schema to JSON Schema:', err); | ||
| } | ||
| return { type: 'object', properties: {}, additionalProperties: true }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback return value in case of an error during Zod to JSON schema conversion is too permissive. Returning a schema with additionalProperties: true could allow arbitrary and unvalidated data to be passed to tools if schema generation fails, which poses a security and data integrity risk. It would be safer to return a more restrictive schema that accepts no properties.
| return { type: 'object', properties: {}, additionalProperties: true }; | |
| return { type: 'object', properties: {}, additionalProperties: false }; |
|
|
||
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<z.infer<typeof DiagnosticsOutputSchema>> { | ||
| return { | ||
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<any> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return type of this handler (and others in this file) has been changed to Promise<any>. While this might be for compatibility, using any weakens type safety and makes the code harder to maintain. Consider using a more specific type for the structured response to improve robustness.
For example, a more specific type would be:
Promise<{ content: McpContent[], structuredContent: z.infer<typeof DiagnosticsOutputSchema> }>This approach can be applied to all other tool handlers to avoid using any.
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<any> { | |
| async function handleDiagnostics(_args?: z.infer<typeof DiagnosticsInputSchema>, _context?: ToolHandlerContext): Promise<{ content: any[], structuredContent: any }> { |
| const returnValue = { content }; | ||
| return { | ||
| content: returnValue.content, | ||
| structuredContent: returnValue, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an inconsistent use of variable names for the result object across different tool handlers. This function uses returnValue, while others like handleCollectionList and handleGetRaindrop use result. For better code consistency and readability, it would be good to standardize on one name, for example result, across all handlers. This also applies to handleListRaindrops and handleBulkEditRaindrops.
| const returnValue = { content }; | |
| return { | |
| content: returnValue.content, | |
| structuredContent: returnValue, | |
| }; | |
| const result = { content }; | |
| return { | |
| content: result.content, | |
| structuredContent: result, | |
| }; |
Summary
This PR fixes several MCP SDK compatibility issues and improves the reliability of tool handlers.
Changes
structuredContentwrapper instead of strict Zod inference, ensuring compatibility with MCP SDK >= 1.19parentIdparameter when updating collectionshandleBulkEditRaindropsto useRaindropServiceinstead of direct API calls, improving error handling and reliabilityzodSchema.shape) instead of converting to JSON Schema, resolving compatibility issues with MCP SDK wrapperTesting
✅ All tool handlers now return consistent response structures
✅ Bulk operations handle errors gracefully with per-item tracking
✅ Schema registration works correctly with MCP SDK >= 1.19
Related Issues
Fixes compatibility issues with MCP SDK 1.19+ and improves reliability of bulk operations.