Skip to content

refactor: split index.ts into modular files by responsibility#7

Merged
Yeom-JinHo merged 1 commit intomainfrom
refactor
Jan 26, 2026
Merged

refactor: split index.ts into modular files by responsibility#7
Yeom-JinHo merged 1 commit intomainfrom
refactor

Conversation

@Yeom-JinHo
Copy link
Collaborator

@Yeom-JinHo Yeom-JinHo commented Jan 26, 2026

Description

This PR focuses on refactoring the codebase structure by splitting the monolithic index.ts file 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.ts file 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 instance
    • index.ts: Simplified to entry point only (17 lines), just imports and runs the server
  • Code Organization:

    • Each module exports a registration function (registerTools(), registerPrompts(), createServer())
    • Clear separation of concerns: tools, prompts, server setup, and entry point
    • Improved import/export structure for better dependency management
  • Maintainability:

    • Adding new tools or prompts now only requires modifying the respective module file
    • Easier to test individual modules in isolation
    • Better code navigation and understanding
  • No Functional Changes:

    • All existing functionality remains identical
    • This is a pure refactoring with no breaking changes to the API

Motivation

  • Maintainability: The monolithic file was becoming difficult to navigate and maintain as it grew to 529 lines
  • Scalability: Makes it easier to add new tools or prompts without cluttering a single file
  • Best Practices: Aligns with common code organization patterns where related functionality is grouped into modules
  • Developer Experience: Improves code readability and makes it easier for contributors to understand the codebase structure

Breaking Changes

  • None: This is a pure refactoring with no API changes
    • All MCP tools and prompts continue to work exactly as before
    • No migration required for consumers of this MCP server
    • Internal code structure only

Summary by CodeRabbit

  • New Features

    • Added prompts to find components matching your requirements and generate implementation guides with framework-specific instructions
    • Introduced tools for searching components by name and tags, accessing documentation in multiple formats, retrieving component metadata and source code
  • Refactor

    • Simplified core server initialization and architecture

✏️ Tip: You can customize this high-level summary in your review settings.

@Yeom-JinHo Yeom-JinHo self-assigned this Jan 26, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

The 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 createServer() factory function. New modules provide createServer(), registerTools(), and registerPrompts() to modularize the server configuration and tool/prompt definitions.

Changes

Cohort / File(s) Summary
Entry Point Simplification
src/index.ts
Removes 514 lines of multi-tool integration logic and replaces with a compact runtime that calls createServer(), connects to StdIO transport, and invokes main().catch() for error handling.
Server Factory
src/server.ts
New module that exports createServer(): McpServer, instantiating and configuring the server with version from package.json, then wiring in tools and prompts via registerTools(server) and registerPrompts(server).
Tools Registration
src/tools.ts
New module that exports registerTools(server: McpServer), registering five tools: search_components (fuzzy search over navigation), get_docs (fetch component docs in multiple formats), get_component_meta (parse metadata), get_source_code (fetch TSX bundle from ui-layouts), with internal helpers for HTTP operations and text extraction.
Prompts Registration
src/prompts.ts
New module that exports registerPrompts(server: McpServer), wiring two user-facing prompts: find_component (accepts requirements, tags, maxCandidates; returns multi-step instruction set) and implement_component (accepts componentKey/href/framework; resolves from navigation and generates implementation guide with tool calls and framework-specific guidance).

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly Related PRs

Suggested Reviewers

  • naymurdev

Poem

🐰 Hops excitedly

Once tangled code, now modular and clean,
Tools and prompts split—the finest we've seen!
Index so dainty, server so spry,
A refactored warren that leaps to the sky! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: refactoring a monolithic index.ts file into modular files organized by responsibility, which aligns with the changeset that splits the code into tools.ts, prompts.ts, and server.ts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the as any cast if the SDK's McpServer type definitions support empty capabilities at construction.

The as any cast bypasses type checking for the McpServer constructor options. With strict: true enabled 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 via registerTools() and registerPrompts(), verify whether the SDK provides a type definition that allows initializing empty capabilities, or if there's a more specific type than any that matches this pattern.

Comment on lines +252 to +302
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 }] };
}
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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:

  1. Updating the description to reflect current behavior, or
  2. 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.

Suggested change
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.

@Yeom-JinHo Yeom-JinHo merged commit a547481 into main Jan 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant