Skip to content

Commit 0121d58

Browse files
authored
Merge pull request #968 from Zy0ung/vibe
refac: MCP 서버 확장에 따른 구조 리팩토링 추가 작업2
2 parents bbe049e + d278772 commit 0121d58

4 files changed

Lines changed: 206 additions & 188 deletions

File tree

packages/mcp/src/server.ts

Lines changed: 6 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3-
import { z } from "zod";
4-
import { renderStorylineUI } from "./tool/storylineRenderer.js";
5-
import type { FeatureImpactAnalyzerInputs, ContributorRecommenderInputs, ReactComponentTestInputs, DataDrivenComponentInputs } from "./common/types.js";
6-
import { I18n } from "./common/i18n.js";
73
import { registerTestTools } from "./tools/test/testTools.js";
84
import { registerVisualizationTools } from "./tools/visualization/register.js";
95
import { registerReactTools } from "./tools/react/register.js";
6+
import { registerContributorRecommenderTool } from "./tools/analysis/contributorRecommenderTool.js";
7+
import { registerFeatureImpactTool } from "./tools/analysis/featureImpactTool.js";
8+
import { registerStorylineUITool } from "./tools/storyLineUI/storyLineUITool.js";
109

1110
const server = new McpServer({
1211
name: "githru-mcp",
@@ -16,191 +15,10 @@ const server = new McpServer({
1615
registerTestTools(server);
1716
registerVisualizationTools(server);
1817
registerReactTools(server);
18+
registerContributorRecommenderTool(server);
19+
registerFeatureImpactTool(server);
20+
registerStorylineUITool(server);
1921

20-
// ✨ Feature Impact Analyzer
21-
server.registerTool(
22-
"feature_impact_analyzer",
23-
{
24-
title: "Feature Integration Impact Analyzer",
25-
description: `
26-
Takes a GitHub repository URL, Pull Request number, and authentication token as input.
27-
Analyzes the PR’s commits and changed files to compute impact metrics — scale, dispersion,
28-
chaos, isolation, lag, and coupling — and outputs a detailed HTML report highlighting
29-
long-tail file path outliers.
30-
`.trim(),
31-
inputSchema: {
32-
repoUrl: z.string().url().describe("Full URL of GitHub repository to analyze (e.g. https://github.com/owner/repo)"),
33-
prNumber: z.number().int().positive().describe("Pull Request number to analyze"),
34-
githubToken: z.string().describe("GitHub authentication token"),
35-
locale: z.enum(["en", "ko"]).default("en").describe("Response language (en: English, ko: Korean)"),
36-
isChart: z.boolean().default(false).describe("Return HTML chart (true) or JSON (false, default)"),
37-
},
38-
},
39-
40-
async ({ repoUrl, prNumber, githubToken, locale, isChart }: FeatureImpactAnalyzerInputs & { locale?: string; isChart?: boolean }) => {
41-
try {
42-
I18n.setLocale(locale || 'en');
43-
44-
const { McpReportGenerator } = await import("./tool/featureImpactAnalyzer.js");
45-
const analyzeFeatureImpact = new McpReportGenerator({ repoUrl, prNumber, githubToken, locale });
46-
47-
const payload = await analyzeFeatureImpact.generateWithOutlierRatings();
48-
49-
if (isChart) {
50-
const chartHtml = analyzeFeatureImpact.generateReport(payload);
51-
return {
52-
content: [
53-
{
54-
type: "text",
55-
text: JSON.stringify(payload, null, 2) + "\n created chart:" + chartHtml
56-
},
57-
],
58-
};
59-
} else {
60-
return {
61-
content: [
62-
{
63-
type: "text",
64-
text: JSON.stringify(payload, null, 2),
65-
},
66-
],
67-
};
68-
}
69-
} catch (err: any) {
70-
return {
71-
content: [
72-
{ type: "text", text: `Analysis error occurred: ${err?.message ?? String(err)}` },
73-
],
74-
};
75-
}
76-
}
77-
);
78-
79-
// 🏆 Contributor Recommender
80-
server.registerTool(
81-
"contributor_recommender",
82-
{
83-
title: "Code Contributor Recommender",
84-
description: "Recommends contributors who have contributed most to specific files/branches/PR areas by aggregating recent contribution data.",
85-
inputSchema: {
86-
repoPath: z.string().describe("GitHub repository path (e.g: owner/repo or https://github.com/owner/repo)"),
87-
pr: z.union([z.string(), z.number()]).optional().describe("PR-based recommendation (PR number)"),
88-
paths: z.array(z.string()).optional().describe("File/directory path array (supports glob patterns)"),
89-
branch: z.string().optional().describe("Branch-based recommendation (default: main)"),
90-
since: z.string().optional().describe("Analysis period start (default 90 days, 30d/ISO date etc.)"),
91-
until: z.string().optional().describe("Analysis period end (current if unspecified)"),
92-
githubToken: z.string().describe("GitHub authentication token"),
93-
locale: z.enum(["en", "ko"]).default("en").describe("Response language (en: English, ko: Korean)"),
94-
chart: z.boolean().default(false).describe("Generate interactive chart visualization (true: HTML chart display, false: JSON data)"),
95-
},
96-
},
97-
98-
async ({ repoPath, pr, paths, branch, since, until, githubToken, locale, chart }: ContributorRecommenderInputs & { locale?: string; chart?: boolean }) => {
99-
try {
100-
I18n.setLocale(locale || 'en');
101-
102-
const { ContributorRecommender } = await import('./tool/contributorRecommender.js');
103-
const recommender = new ContributorRecommender({
104-
repoPath,
105-
pr,
106-
paths,
107-
branch,
108-
since,
109-
until,
110-
githubToken,
111-
locale,
112-
});
113-
114-
const recommendation = await recommender.analyze();
115-
116-
if (chart) {
117-
const chartHtml = recommender.generateChart(recommendation);
118-
return {
119-
content: [
120-
{
121-
type: "text",
122-
text: chartHtml,
123-
},
124-
],
125-
};
126-
} else {
127-
return {
128-
content: [
129-
{
130-
type: "text",
131-
text: JSON.stringify(recommendation, null, 2),
132-
},
133-
],
134-
};
135-
}
136-
} catch (err: any) {
137-
return {
138-
content: [
139-
{ type: "text", text: `Contributor recommendation error occurred: ${err?.message ?? String(err)}` },
140-
],
141-
};
142-
}
143-
}
144-
);
145-
146-
server.registerTool(
147-
"render_storyline_ui",
148-
{
149-
title: "Render Storyline UI",
150-
description: "Generate storyline visualization using FolderActivityFlow component with CSMDict data",
151-
inputSchema: {
152-
repo: z.string().describe("GitHub repository in format 'owner/repo'"),
153-
githubToken: z.string().describe("GitHub personal access token"),
154-
baseBranchName: z.string().optional().describe("Base branch name (default: main)"),
155-
locale: z.enum(["en", "ko"]).default("en").describe("Response language"),
156-
debug: z.boolean().default(false).describe("Enable debug logging")
157-
}
158-
},
159-
160-
async ({ repo, githubToken, baseBranchName, locale, debug }) => {
161-
try {
162-
const result = await renderStorylineUI({
163-
repo,
164-
githubToken,
165-
baseBranchName,
166-
locale,
167-
debug
168-
});
169-
170-
if (result.type === 'image') {
171-
return {
172-
content: [
173-
{
174-
type: "text",
175-
text: "# 📊 Storyline Visualization\n\nGenerated storyline chart showing contributor activity flow across releases:",
176-
},
177-
{
178-
type: "image",
179-
data: result.data,
180-
mimeType: result.mimeType,
181-
annotations: result.annotations
182-
}
183-
],
184-
};
185-
} else {
186-
return {
187-
content: [
188-
{
189-
type: "text",
190-
text: `# 📊 Storyline Visualization\n\n${result.data}`,
191-
},
192-
],
193-
};
194-
}
195-
} catch (err: any) {
196-
return {
197-
content: [
198-
{ type: "text", text: `Storyline UI rendering error: ${err?.message ?? String(err)}` },
199-
],
200-
};
201-
}
202-
}
203-
);
20422

20523
async function main() {
20624
const transport = new StdioServerTransport();
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { z } from "zod";
3+
import { I18n } from "../../common/i18n.js";
4+
import type { ContributorRecommenderInputs } from "../../common/types.js";
5+
6+
export function registerContributorRecommenderTool(server: McpServer) {
7+
server.registerTool(
8+
"contributor_recommender",
9+
{
10+
title: "Code Contributor Recommender",
11+
description: "Recommends contributors who have contributed most to specific files/branches/PR areas by aggregating recent contribution data.",
12+
inputSchema: {
13+
repoPath: z.string().describe("GitHub repository path (e.g: owner/repo or https://github.com/owner/repo)"),
14+
pr: z.union([z.string(), z.number()]).optional().describe("PR-based recommendation (PR number)"),
15+
paths: z.array(z.string()).optional().describe("File/directory path array (supports glob patterns)"),
16+
branch: z.string().optional().describe("Branch-based recommendation (default: main)"),
17+
since: z.string().optional().describe("Analysis period start (default 90 days, 30d/ISO date etc.)"),
18+
until: z.string().optional().describe("Analysis period end (current if unspecified)"),
19+
githubToken: z.string().describe("GitHub authentication token"),
20+
locale: z.enum(["en", "ko"]).default("en").describe("Response language (en: English, ko: Korean)"),
21+
chart: z.boolean().default(false).describe("Generate interactive chart visualization (true: HTML chart display, false: JSON data)"),
22+
},
23+
},
24+
25+
async ({ repoPath, pr, paths, branch, since, until, githubToken, locale, chart }: ContributorRecommenderInputs & { locale?: string; chart?: boolean }) => {
26+
try {
27+
I18n.setLocale(locale || 'en');
28+
29+
const { ContributorRecommender } = await import('../../tool/contributorRecommender.js');
30+
const recommender = new ContributorRecommender({
31+
repoPath,
32+
pr,
33+
paths,
34+
branch,
35+
since,
36+
until,
37+
githubToken,
38+
locale,
39+
});
40+
41+
const recommendation = await recommender.analyze();
42+
43+
if (chart) {
44+
const chartHtml = recommender.generateChart(recommendation);
45+
return {
46+
content: [
47+
{
48+
type: "text",
49+
text: chartHtml,
50+
},
51+
],
52+
};
53+
} else {
54+
return {
55+
content: [
56+
{
57+
type: "text",
58+
text: JSON.stringify(recommendation, null, 2),
59+
},
60+
],
61+
};
62+
}
63+
} catch (err: any) {
64+
return {
65+
content: [
66+
{ type: "text", text: `Contributor recommendation error occurred: ${err?.message ?? String(err)}` },
67+
],
68+
};
69+
}
70+
}
71+
);
72+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2+
import { z } from "zod";
3+
import { I18n } from "../../common/i18n.js";
4+
import type { FeatureImpactAnalyzerInputs } from "../../common/types.js";
5+
6+
export function registerFeatureImpactTool(server: McpServer) {
7+
server.registerTool(
8+
"feature_impact_analyzer",
9+
{
10+
title: "Feature Integration Impact Analyzer",
11+
description: `
12+
Takes a GitHub repository URL, Pull Request number, and authentication token as input.
13+
Analyzes the PR’s commits and changed files to compute impact metrics — scale, dispersion,
14+
chaos, isolation, lag, and coupling — and outputs a detailed HTML report highlighting
15+
long-tail file path outliers.
16+
`.trim(),
17+
inputSchema: {
18+
repoUrl: z.string().url().describe("Full URL of GitHub repository to analyze (e.g. https://github.com/owner/repo)"),
19+
prNumber: z.number().int().positive().describe("Pull Request number to analyze"),
20+
githubToken: z.string().describe("GitHub authentication token"),
21+
locale: z.enum(["en", "ko"]).default("en").describe("Response language (en: English, ko: Korean)"),
22+
isChart: z.boolean().default(false).describe("Return HTML chart (true) or JSON (false, default)"),
23+
},
24+
},
25+
26+
async ({ repoUrl, prNumber, githubToken, locale, isChart }: FeatureImpactAnalyzerInputs & { locale?: string; isChart?: boolean }) => {
27+
try {
28+
I18n.setLocale(locale || 'en');
29+
30+
const { McpReportGenerator } = await import("../../tool/featureImpactAnalyzer.js");
31+
const analyzeFeatureImpact = new McpReportGenerator({ repoUrl, prNumber, githubToken, locale });
32+
33+
const payload = await analyzeFeatureImpact.generateWithOutlierRatings();
34+
35+
if (isChart) {
36+
const chartHtml = analyzeFeatureImpact.generateReport(payload);
37+
return {
38+
content: [
39+
{
40+
type: "text",
41+
text: JSON.stringify(payload, null, 2) + "\n created chart:" + chartHtml
42+
},
43+
],
44+
};
45+
} else {
46+
return {
47+
content: [
48+
{
49+
type: "text",
50+
text: JSON.stringify(payload, null, 2),
51+
},
52+
],
53+
};
54+
}
55+
} catch (err: any) {
56+
return {
57+
content: [
58+
{ type: "text", text: `Analysis error occurred: ${err?.message ?? String(err)}` },
59+
],
60+
};
61+
}
62+
}
63+
);
64+
}

0 commit comments

Comments
 (0)