Skip to content

Commit 55454b3

Browse files
authored
Merge pull request #972 from Kyoungwoong/fix/969-secure-token-issus
feat[vibe]: Config 싱글톤 패턴으로 GitHub 토큰 중앙 관리
2 parents 0bc317f + 14c2765 commit 55454b3

13 files changed

Lines changed: 142 additions & 131 deletions

packages/mcp/descriptions/githru-dev-description.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ You need to modify the path inside args in ```githru-mcp/claude_desktop_config.j
7777
"AbsolutePath/githru-vscode-ext/packages/mcp/dist/server.js"
7878
],
7979
"env": {
80-
"NODE_NO_WARNINGS": "1"
80+
"NODE_NO_WARNINGS": "1",
81+
"GITHUB_TOKEN": "ghphvhvhvhvhvhvhvhvhvhvhvhvhvhvhv"
8182
}
8283
}
8384
}

packages/mcp/src/common/config.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export class Config {
2+
private static instance: Config;
3+
4+
public readonly githubToken: string;
5+
6+
private constructor() {
7+
this.githubToken = process.env.GITHUB_TOKEN || '';
8+
}
9+
10+
public static getInstance(): Config {
11+
if (!Config.instance) {
12+
Config.instance = new Config();
13+
}
14+
return Config.instance;
15+
}
16+
17+
public getGithubToken(): string {
18+
if (!this.githubToken) {
19+
throw new Error("github token missing");
20+
}
21+
return this.githubToken;
22+
}
23+
}

packages/mcp/src/common/types.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export interface GitHubApiInputs {
1212
export interface FeatureImpactAnalyzerInputs {
1313
repoUrl: string;
1414
prNumber: number;
15-
githubToken: string;
1615
locale?: string;
1716
}
1817
export interface ContributorRecommenderInputs {
@@ -22,9 +21,8 @@ export interface ContributorRecommenderInputs {
2221
branch?: string;
2322
since?: string;
2423
until?: string;
25-
githubToken: string;
2624
locale?: string;
27-
chart?: boolean;
25+
isChart?: boolean;
2826
}
2927

3028
export interface ContributorCandidate {
@@ -47,7 +45,6 @@ export interface CSMDictGeneratorInputs {
4745
githubToken: string;
4846
baseBranchName?: string;
4947
locale?: string;
50-
debug?: boolean;
5148
}
5249

5350
export interface GitUser {

packages/mcp/src/tool/authorWorkPattern.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { fileURLToPath } from "url";
44
import type { RestEndpointMethodTypes } from "@octokit/rest";
55
import { GitHubUtils } from "../common/utils.js";
66
import { I18n } from "../common/i18n.js";
7+
import { Config } from "../common/config.js";
78

89
const __filename = fileURLToPath(import.meta.url);
910
const __dirname = path.dirname(__filename);
@@ -17,7 +18,6 @@ export interface AuthorWorkPatternArgs {
1718
branch?: string;
1819
since?: string;
1920
until?: string;
20-
githubToken: string;
2121
locale?: "en" | "ko";
2222
chart?: boolean;
2323
}
@@ -59,9 +59,12 @@ export class AuthorWorkPatternAnalyzer {
5959

6060
constructor(private args: AuthorWorkPatternArgs) {
6161
const { owner, repo } = GitHubUtils.parseRepoUrl(args.repoPath);
62+
const config = Config.getInstance();
63+
const githubToken = config.getGithubToken();
64+
6265
this.owner = owner;
6366
this.repo = repo;
64-
this.token = args.githubToken;
67+
this.token = githubToken;
6568
this.authorQuery = args.author;
6669
this.baseBranch = args.branch;
6770
this.since = args.since;

packages/mcp/src/tool/contributorRecommender.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
ContributorCandidate,
1111
ContributorRecommendation
1212
} from "../common/types.js";
13+
import { Config } from "../common/config.js";
1314

1415
// ES modules에서 __dirname 대체
1516
const __filename = fileURLToPath(import.meta.url);
@@ -31,11 +32,14 @@ export class ContributorRecommender {
3132
private until: string;
3233

3334
constructor(inputs: ContributorRecommenderInputs) {
35+
const config = Config.getInstance();
36+
const githubToken = config.getGithubToken();
37+
3438
if (inputs.locale) {
3539
I18n.setLocale(inputs.locale);
3640
}
3741

38-
this.octokit = GitHubUtils.createGitHubAPIClient(inputs.githubToken);
42+
this.octokit = GitHubUtils.createGitHubAPIClient(githubToken);
3943

4044
const { owner, repo } = GitHubUtils.parseRepoUrl(inputs.repoPath);
4145
this.owner = owner;

packages/mcp/src/tool/featureImpactAnalyzer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as fs from "fs";
55
import * as path from "path";
66
import { fileURLToPath } from "url";
77
import type { FeatureImpactAnalyzerInputs } from "../common/types.js";
8+
import { Config } from "../common/config.js";
89

910
const __filename = fileURLToPath(import.meta.url);
1011
const __dirname = path.dirname(__filename);
@@ -55,10 +56,13 @@ export class McpReportGenerator {
5556
private owner: string;
5657
private repo: string;
5758

58-
constructor({ repoUrl, prNumber, githubToken, locale }: FeatureImpactAnalyzerInputs) {
59+
constructor({ repoUrl, prNumber, locale }: FeatureImpactAnalyzerInputs) {
5960
if (locale) {
6061
I18n.setLocale(locale);
6162
}
63+
const config = Config.getInstance();
64+
const githubToken = config.getGithubToken();
65+
6266
this.repoUrl = repoUrl;
6367
this.prNumber = prNumber;
6468
this.octokit = GitHubUtils.createGitHubAPIClient(githubToken);

packages/mcp/src/tool/generateNewViz.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,9 @@ class EngineGenerator {
5252
} catch (cleanupError) {
5353
// ignore cleanup errors
5454
}
55-
56-
57-
const originalConsoleLog = console.log;
58-
const originalConsoleError = console.error;
59-
if (!this.inputs.debug) {
60-
console.log = () => {};
61-
console.error = () => {};
62-
}
6355

6456
try {
6557
const analysisEngine = new AnalysisEngine({
66-
isDebugMode: this.inputs.debug || false,
6758
gitLog: this.gitLog,
6859
owner: this.repoInfo.owner,
6960
repo: this.repoInfo.repo,
@@ -73,8 +64,6 @@ class EngineGenerator {
7364

7465
this.analysisResult = await analysisEngine.analyzeGit();
7566
} finally {
76-
console.log = originalConsoleLog;
77-
console.error = originalConsoleError;
7867
}
7968
}
8069
}
@@ -87,16 +76,14 @@ class NewViz {
8776
}
8877

8978
async generate(): Promise<any> {
90-
const { debug = false } = this.engine.inputs;
91-
9279
if (!this.engine.repoInfo || !this.engine.analysisResult) {
9380
throw new Error('Engine not initialized');
9481
}
9582

96-
return this.buildResult(debug);
83+
return this.buildResult();
9784
}
9885

99-
private buildResult(debug: boolean): any {
86+
private buildResult(): any {
10087
if (!this.engine.repoInfo || !this.engine.analysisResult) {
10188
throw new Error('Required data not available');
10289
}

packages/mcp/src/tool/storylineRenderer.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,23 @@ import ServerSideFolderActivityFlow from '../component/FolderActivityFlow/Server
55
import { analyzeReleaseBasedFolders, extractReleaseBasedContributorActivities, generateReleaseFlowLineData, findFirstReleaseContributorNodes } from '../component/FolderActivityFlow/FolderActivityFlow.util.js';
66
// @ts-ignore
77
import sharp from 'sharp';
8+
import { Config } from "../common/config.js";
89

910
interface StorylineInputs {
1011
repo: string;
11-
githubToken: string;
1212
baseBranchName?: string;
1313
locale?: string;
14-
debug?: boolean;
1514
}
1615

1716
export async function renderStorylineUI(inputs: StorylineInputs): Promise<{ type: 'image'; data: string; mimeType: string; annotations?: any } | { type: 'text'; data: string }> {
1817
try {
18+
const config = Config.getInstance();
19+
const githubToken = config.getGithubToken();
1920

2021
const totalData = await generateNewViz({
2122
repo: inputs.repo,
22-
githubToken: inputs.githubToken,
23+
githubToken: githubToken,
2324
baseBranchName: inputs.baseBranchName,
24-
debug: inputs.debug || false
2525
});
2626

2727

packages/mcp/src/tools/analysis/authorWorkPatternTool.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export function registerAuthorWorkPatternTool(server: McpServer) {
1616
branch: z.string().optional().describe("브랜치/커밋 SHA (기본: 기본 브랜치)"),
1717
since: z.string().optional().describe('시작 시점 (예: "2025-09-01" 또는 "30d")'),
1818
until: z.string().optional().describe("종료 시점 (미지정 시 현재)"),
19-
githubToken: z.string().describe("GitHub Personal Access Token"),
2019
locale: z.enum(["en", "ko"]).default("en").describe("응답 언어"),
2120
chart: z.boolean().default(false).describe("HTML 차트 리포트 반환 여부"),
2221
},
@@ -28,7 +27,6 @@ export function registerAuthorWorkPatternTool(server: McpServer) {
2827
branch,
2928
since,
3029
until,
31-
githubToken,
3230
locale,
3331
chart,
3432
}: AuthorWorkPatternArgs & { locale?: "en" | "ko"; chart?: boolean }) => {
@@ -41,7 +39,6 @@ export function registerAuthorWorkPatternTool(server: McpServer) {
4139
branch,
4240
since,
4341
until,
44-
githubToken,
4542
locale,
4643
chart,
4744
});

packages/mcp/src/tools/analysis/contributorRecommenderTool.ts

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,62 +10,60 @@ export function registerContributorRecommenderTool(server: McpServer) {
1010
title: "Code Contributor Recommender",
1111
description: "Recommends contributors who have contributed most to specific files/branches/PR areas by aggregating recent contribution data.",
1212
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)"),
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+
locale: z.enum(["en", "ko"]).default("en").describe("Response language (en: English, ko: Korean)"),
20+
isChart: z.boolean().default(false).describe("Generate interactive chart visualization (true: HTML chart display, false: JSON data)"),
2221
},
2322
},
2423

25-
async ({ repoPath, pr, paths, branch, since, until, githubToken, locale, chart }: ContributorRecommenderInputs & { locale?: string; chart?: boolean }) => {
24+
async ({ repoPath, pr, paths, branch, since, until, locale, isChart }: ContributorRecommenderInputs & { locale?: string; chart?: boolean }) => {
2625
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-
});
26+
I18n.setLocale(locale || 'en');
27+
28+
const { ContributorRecommender } = await import('../../tool/contributorRecommender.js');
29+
const recommender = new ContributorRecommender({
30+
repoPath,
31+
pr,
32+
paths,
33+
branch,
34+
since,
35+
until,
36+
locale,
37+
});
4038

41-
const recommendation = await recommender.analyze();
39+
const recommendation = await recommender.analyze();
4240

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-
}
41+
if (isChart) {
42+
const chartHtml = recommender.generateChart(recommendation);
43+
return {
44+
content: [
45+
{
46+
type: "text",
47+
text: chartHtml,
48+
},
49+
],
50+
};
51+
} else {
52+
return {
53+
content: [
54+
{
55+
type: "text",
56+
text: JSON.stringify(recommendation, null, 2),
57+
},
58+
],
59+
};
60+
}
6361
} catch (err: any) {
64-
return {
65-
content: [
66-
{ type: "text", text: `Contributor recommendation error occurred: ${err?.message ?? String(err)}` },
67-
],
68-
};
62+
return {
63+
content: [
64+
{ type: "text", text: `Contributor recommendation error occurred: ${err?.message ?? String(err)}` },
65+
],
66+
};
6967
}
7068
}
7169
);

0 commit comments

Comments
 (0)