refactor: split index.ts into modular files by responsibility#7
refactor: split index.ts into modular files by responsibility#7Yeom-JinHo merged 1 commit intomainfrom
Conversation
📝 WalkthroughWalkthroughThe PR refactors the MCP server structure by extracting tools and prompts into separate modules. The index.ts entry point is simplified from 516 lines to a minimal 2-line runtime that uses a Changes
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly Related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/tools.ts`:
- Around line 252-302: The tool "get_source_code" currently only returns the
first file via json?.files?.[0]?.content which contradicts the description;
either update the tool description to state it returns only the first file, or
implement full file listing and selection by adding an input (e.g., fileName or
fileIndex) to the server.tool schema, read json.files (iterate to produce a list
of filenames and metadata), return that list when no specific file is requested,
and when fileName/fileIndex is provided return the matching file's content
(respecting maxChars and timeoutMs) — update references in the handler
(componentName, jsonUrl, fetchJson, and the content variable) and the returned
content shape accordingly.
🧹 Nitpick comments (1)
src/server.ts (1)
6-21: Remove theas anycast if the SDK's McpServer type definitions support empty capabilities at construction.The
as anycast bypasses type checking for the McpServer constructor options. Withstrict: trueenabled in tsconfig.json, this likely indicates a mismatch between the expected type structure and the empty capabilities objects (resources: {},tools: {},prompts: {}). Since tools and prompts are registered after construction viaregisterTools()andregisterPrompts(), verify whether the SDK provides a type definition that allows initializing empty capabilities, or if there's a more specific type thananythat matches this pattern.
| server.tool( | ||
| "get_source_code", | ||
| "Fetch component source code bundle from https://ui-layouts.com/r/{key}.json (or custom filename), list files and optionally return a specific file content.", | ||
| { | ||
| componentName: z | ||
| .string() | ||
| .optional() | ||
| .describe( | ||
| "Component name (e.g. 'liquid-glass-weather', 'single-img-ripple-effect')" | ||
| ), | ||
| maxChars: z.number().int().min(200).max(200000).optional().default(20000), | ||
| timeoutMs: z.number().int().min(1000).max(20000).optional().default(7000), | ||
| }, | ||
| async ({ componentName, timeoutMs, maxChars }) => { | ||
| if (!componentName) { | ||
| return { | ||
| content: [{ type: "text", text: `⚠️ Component name is required` }], | ||
| }; | ||
| } | ||
|
|
||
| const jsonUrl = `${BASE_URL}/r/${componentName}.json`; | ||
|
|
||
| const json = await fetchJson<any>(jsonUrl, timeoutMs); | ||
| if (!json) { | ||
| return { | ||
| content: [ | ||
| { type: "text", text: `⚠️ Failed to fetch from: ${jsonUrl}` }, | ||
| ], | ||
| }; | ||
| } | ||
|
|
||
| const content = json?.files?.[0]?.content; | ||
| if (!content) { | ||
| return { | ||
| content: [{ type: "text", text: `⚠️ No content found in ${jsonUrl}` }], | ||
| }; | ||
| } | ||
|
|
||
| const header = [ | ||
| `# Source Code`, | ||
| `- **componentName**: \`${componentName}\``, | ||
| `- **url**: ${jsonUrl}`, | ||
| `- **maxChars**: ${maxChars}`, | ||
| "", | ||
| ].join("\n"); | ||
|
|
||
| const body = ["```tsx", content.slice(0, maxChars), "```"].join("\n"); | ||
|
|
||
| return { content: [{ type: "text", text: header + body }] }; | ||
| } | ||
| ); |
There was a problem hiding this comment.
Tool description doesn't match implementation: only first file is returned.
The tool description says "list files and optionally return a specific file content," but the implementation only extracts json?.files?.[0]?.content (the first file). If the bundle contains multiple files, users cannot access them.
Consider either:
- Updating the description to reflect current behavior, or
- Implementing file listing and selection as described.
Option 1: Update description to match implementation
server.tool(
"get_source_code",
- "Fetch component source code bundle from https://ui-layouts.com/r/{key}.json (or custom filename), list files and optionally return a specific file content.",
+ "Fetch the primary source code file for a component from https://ui-layouts.com/r/{componentName}.json.",
{📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| server.tool( | |
| "get_source_code", | |
| "Fetch component source code bundle from https://ui-layouts.com/r/{key}.json (or custom filename), list files and optionally return a specific file content.", | |
| { | |
| componentName: z | |
| .string() | |
| .optional() | |
| .describe( | |
| "Component name (e.g. 'liquid-glass-weather', 'single-img-ripple-effect')" | |
| ), | |
| maxChars: z.number().int().min(200).max(200000).optional().default(20000), | |
| timeoutMs: z.number().int().min(1000).max(20000).optional().default(7000), | |
| }, | |
| async ({ componentName, timeoutMs, maxChars }) => { | |
| if (!componentName) { | |
| return { | |
| content: [{ type: "text", text: `⚠️ Component name is required` }], | |
| }; | |
| } | |
| const jsonUrl = `${BASE_URL}/r/${componentName}.json`; | |
| const json = await fetchJson<any>(jsonUrl, timeoutMs); | |
| if (!json) { | |
| return { | |
| content: [ | |
| { type: "text", text: `⚠️ Failed to fetch from: ${jsonUrl}` }, | |
| ], | |
| }; | |
| } | |
| const content = json?.files?.[0]?.content; | |
| if (!content) { | |
| return { | |
| content: [{ type: "text", text: `⚠️ No content found in ${jsonUrl}` }], | |
| }; | |
| } | |
| const header = [ | |
| `# Source Code`, | |
| `- **componentName**: \`${componentName}\``, | |
| `- **url**: ${jsonUrl}`, | |
| `- **maxChars**: ${maxChars}`, | |
| "", | |
| ].join("\n"); | |
| const body = ["```tsx", content.slice(0, maxChars), "```"].join("\n"); | |
| return { content: [{ type: "text", text: header + body }] }; | |
| } | |
| ); | |
| server.tool( | |
| "get_source_code", | |
| "Fetch the primary source code file for a component from https://ui-layouts.com/r/{componentName}.json.", | |
| { | |
| componentName: z | |
| .string() | |
| .optional() | |
| .describe( | |
| "Component name (e.g. 'liquid-glass-weather', 'single-img-ripple-effect')" | |
| ), | |
| maxChars: z.number().int().min(200).max(200000).optional().default(20000), | |
| timeoutMs: z.number().int().min(1000).max(20000).optional().default(7000), | |
| }, | |
| async ({ componentName, timeoutMs, maxChars }) => { | |
| if (!componentName) { | |
| return { | |
| content: [{ type: "text", text: `⚠️ Component name is required` }], | |
| }; | |
| } | |
| const jsonUrl = `${BASE_URL}/r/${componentName}.json`; | |
| const json = await fetchJson<any>(jsonUrl, timeoutMs); | |
| if (!json) { | |
| return { | |
| content: [ | |
| { type: "text", text: `⚠️ Failed to fetch from: ${jsonUrl}` }, | |
| ], | |
| }; | |
| } | |
| const content = json?.files?.[0]?.content; | |
| if (!content) { | |
| return { | |
| content: [{ type: "text", text: `⚠️ No content found in ${jsonUrl}` }], | |
| }; | |
| } | |
| const header = [ | |
| `# Source Code`, | |
| `- **componentName**: \`${componentName}\``, | |
| `- **url**: ${jsonUrl}`, | |
| `- **maxChars**: ${maxChars}`, | |
| "", | |
| ].join("\n"); | |
| const body = [" |
🤖 Prompt for AI Agents
In `@src/tools.ts` around lines 252 - 302, The tool "get_source_code" currently
only returns the first file via json?.files?.[0]?.content which contradicts the
description; either update the tool description to state it returns only the
first file, or implement full file listing and selection by adding an input
(e.g., fileName or fileIndex) to the server.tool schema, read json.files
(iterate to produce a list of filenames and metadata), return that list when no
specific file is requested, and when fileName/fileIndex is provided return the
matching file's content (respecting maxChars and timeoutMs) — update references
in the handler (componentName, jsonUrl, fetchJson, and the content variable) and
the returned content shape accordingly.
Description
This PR focuses on refactoring the codebase structure by splitting the monolithic
index.tsfile into modular, role-based files. This improves maintainability, separation of concerns, and makes it easier to add new tools or prompts in the future.Changes
Modularization (major refactor): Split the 529-line
index.tsfile into separate modules by responsibility:tools.ts: Contains all MCP tool implementations (search_components,get_docs,get_component_meta,get_source_code)prompts.ts: Contains all MCP prompt templates (find_component,implement_component)server.ts: Server factory function that creates and configures the MCP server instanceindex.ts: Simplified to entry point only (17 lines), just imports and runs the serverCode Organization:
registerTools(),registerPrompts(),createServer())Maintainability:
No Functional Changes:
Motivation
Breaking Changes
Summary by CodeRabbit
New Features
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.