Skip to content

Commit c2e5ebf

Browse files
refactor(mcp): extract types to types.ts, simplify comments, and make MCP setup client‑agnostic
1 parent 0778b4d commit c2e5ebf

File tree

8 files changed

+142
-44
lines changed

8 files changed

+142
-44
lines changed

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,17 @@ npm run build
4747

4848
The build step produces `dist/mcp/index.js`, the entry point used by MCP clients.
4949

50-
### Claude Desktop setup
50+
### MCP client setup
5151

52-
1. Open `%AppData%/Claude/claude_desktop_config.json`.
53-
2. Add a server entry pointing at the built file. Example:
52+
1. The configuration is the same across operating systems as long as your MCP client is installed and supports external servers (Claude Desktop, Cursor, Windsurf, etc.).
53+
54+
2. Add a server entry pointing at the built file by pasting the JSON below into your MCP client's config file — for example:
55+
56+
- `claude_desktop_config.json` for Claude Desktop
57+
- `mcp.json` for Cursor
58+
- the equivalent JSON config file for other MCP clients
59+
60+
Example (paste into the appropriate file):
5461

5562
```json
5663
{
@@ -63,7 +70,7 @@ The build step produces `dist/mcp/index.js`, the entry point used by MCP clients
6370
}
6471
```
6572

66-
3. Restart Claude Desktop. A "Search & tools" toggle appears once the server launches successfully.
73+
3. Restart your MCP client. A "Search & tools" toggle (or similar UI element) should appear once the server launches successfully.
6774

6875
To run only a subset of tools, append the module name (`users`, `problems`, or `discussions`) as an extra argument or set the `MCP_SERVER_MODE` environment variable.
6976

mcp/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import { DiscussionToolsModule } from './modules/discussionTools';
22
import { ProblemToolsModule } from './modules/problemTools';
33
import { UserToolsModule } from './modules/userTools';
4-
import { SERVER_VERSION, startServer, ToolModule } from './serverUtils';
5-
6-
type Mode = 'all' | 'users' | 'problems' | 'discussions';
7-
8-
type ModuleConfig = {
9-
modules: ToolModule[];
10-
name: string;
11-
};
4+
import { SERVER_VERSION, startServer } from './serverUtils';
5+
import { Mode, ModuleConfig, ToolModule } from './types';
126

137
const modulesByKey: Record<Exclude<Mode, 'all'>, ToolModule> = {
148
users: new UserToolsModule(),
159
problems: new ProblemToolsModule(),
1610
discussions: new DiscussionToolsModule(),
1711
};
1812

13+
// Normalizes the input mode string to a valid Mode type.
1914
function normalizeMode(input: string | undefined): Mode {
2015
if (!input) {
2116
return 'all';
@@ -36,6 +31,7 @@ function normalizeMode(input: string | undefined): Mode {
3631
process.exit(1);
3732
}
3833

34+
// Resolves the modules and server name based on the selected mode.
3935
function resolveModules(mode: Mode): ModuleConfig {
4036
if (mode === 'all') {
4137
return {
@@ -50,6 +46,7 @@ function resolveModules(mode: Mode): ModuleConfig {
5046
};
5147
}
5248

49+
// Main entry point for the MCP server. Parses command line arguments, sets up modules, and starts the server.
5350
async function main() {
5451
const modeInput = process.env.MCP_SERVER_MODE ?? process.argv[2];
5552
const mode = normalizeMode(modeInput);

mcp/leetCodeService.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,9 @@ import type {
4343
UserData,
4444
} from '../src/types';
4545
import { executeGraphQL } from './serverUtils';
46+
import { SubmissionArgs, CalendarArgs, ProblemArgs, DiscussCommentsArgs, Variables } from './types';
4647

47-
type SubmissionArgs = { username: string; limit?: number };
48-
type CalendarArgs = { username: string; year: number };
49-
type ProblemArgs = { limit?: number; skip?: number; tags?: string; difficulty?: string };
50-
type DiscussCommentsArgs = { topicId: number; orderBy?: string; pageNo?: number; numPerPage?: number };
51-
52-
type Variables = Record<string, unknown>;
53-
48+
// Builds GraphQL variables by filtering out undefined, null, and NaN values.
5449
function buildVariables(input: Record<string, unknown>): Variables {
5550
const result: Variables = {};
5651
for (const [key, value] of Object.entries(input)) {
@@ -61,82 +56,98 @@ function buildVariables(input: Record<string, unknown>): Variables {
6156
return result;
6257
}
6358

59+
// Retrieves the formatted user profile summary.
6460
export async function getUserProfileSummary(username: string) {
6561
const data = await executeGraphQL(userProfileQuery, { username });
6662
return formatUserData(data as UserData);
6763
}
6864

65+
// Retrieves the formatted user badges data.
6966
export async function getUserBadges(username: string) {
7067
const data = await executeGraphQL(userProfileQuery, { username });
7168
return formatBadgesData(data as UserData);
7269
}
7370

71+
// Retrieves the formatted user contest data.
7472
export async function getUserContest(username: string) {
7573
const data = await executeGraphQL(contestQuery, { username });
7674
return formatContestData(data as UserData);
7775
}
7876

77+
// Retrieves the formatted user contest history.
7978
export async function getUserContestHistory(username: string) {
8079
const data = await executeGraphQL(contestQuery, { username });
8180
return formatContestHistoryData(data as UserData);
8281
}
8382

83+
// Retrieves the formatted solved problems statistics.
8484
export async function getSolvedProblems(username: string) {
8585
const data = await executeGraphQL(userProfileQuery, { username });
8686
return formatSolvedProblemsData(data as UserData);
8787
}
8888

89+
// Retrieves recent submissions for a user.
8990
export async function getRecentSubmission(args: SubmissionArgs) {
9091
const variables = buildVariables({ username: args.username, limit: args.limit });
9192
const data = await executeGraphQL(submissionQuery, variables);
9293
return formatSubmissionData(data as UserData);
9394
}
9495

96+
// Retrieves recent accepted submissions for a user.
9597
export async function getRecentAcSubmission(args: SubmissionArgs) {
9698
const variables = buildVariables({ username: args.username, limit: args.limit });
9799
const data = await executeGraphQL(AcSubmissionQuery, variables);
98100
return formatAcSubmissionData(data as UserData);
99101
}
100102

103+
// Retrieves the submission calendar for a user in a given year.
101104
export async function getSubmissionCalendar(args: CalendarArgs) {
102105
const variables = buildVariables({ username: args.username, year: args.year });
103106
const data = await executeGraphQL(userProfileCalendarQuery, variables);
104107
return formatSubmissionCalendarData(data as UserData);
105108
}
106109

110+
// Retrieves the aggregated user profile data.
107111
export async function getUserProfileAggregate(username: string) {
108112
const data = await executeGraphQL(getUserProfileQuery, { username });
109113
return formatUserProfileData(data);
110114
}
111115

116+
// Retrieves the language statistics for a user.
112117
export async function getLanguageStats(username: string) {
113118
const data = await executeGraphQL(languageStatsQuery, { username });
114119
return formatLanguageStats(data as UserData);
115120
}
116121

122+
// Retrieves the skill statistics for a user.
117123
export async function getSkillStats(username: string) {
118124
const data = await executeGraphQL(skillStatsQuery, { username });
119125
return formatSkillStats(data as UserData);
120126
}
121127

128+
// Retrieves the daily problem.
122129
export async function getDailyProblem() {
123130
const data = await executeGraphQL(dailyProblemQuery, {});
124131
return formatDailyData(data as DailyProblemData);
125132
}
126133

134+
// Retrieves the raw daily problem data.
127135
export async function getDailyProblemRaw() {
128136
return executeGraphQL(dailyProblemQuery, {});
129137
}
130138

139+
// Retrieves a selected problem by title slug.
131140
export async function getSelectProblem(titleSlug: string) {
132141
const data = await executeGraphQL(selectProblemQuery, { titleSlug });
133142
return formatQuestionData(data as SelectProblemData);
134143
}
135144

145+
// Retrieves the raw data for a selected problem by title slug.
136146
export async function getSelectProblemRaw(titleSlug: string) {
137147
return executeGraphQL(selectProblemQuery, { titleSlug });
138148
}
139149

150+
// Retrieves a list of problems based on the given arguments.
140151
export async function getProblemSet(args: ProblemArgs) {
141152
const limit = args.skip !== undefined && args.limit === undefined ? 1 : args.limit ?? 20;
142153
const skip = args.skip ?? 0;
@@ -155,19 +166,23 @@ export async function getProblemSet(args: ProblemArgs) {
155166
return formatProblemsData(data as ProblemSetQuestionListData);
156167
}
157168

169+
// Retrieves the official solution for a problem.
158170
export async function getOfficialSolution(titleSlug: string) {
159171
return executeGraphQL(officialSolutionQuery, { titleSlug });
160172
}
161173

174+
// Retrieves trending discussion topics.
162175
export async function getTrendingTopics(first: number) {
163176
const data = await executeGraphQL(trendingDiscussQuery, { first });
164177
return formatTrendingCategoryTopicData(data as TrendingDiscussionObject);
165178
}
166179

180+
// Retrieves a discussion topic by ID.
167181
export async function getDiscussTopic(topicId: number) {
168182
return executeGraphQL(discussTopicQuery, { topicId });
169183
}
170184

185+
// Retrieves comments for a discussion topic.
171186
export async function getDiscussComments(args: DiscussCommentsArgs) {
172187
const variables = buildVariables({
173188
topicId: args.topicId,
@@ -178,36 +193,44 @@ export async function getDiscussComments(args: DiscussCommentsArgs) {
178193
return executeGraphQL(discussCommentsQuery, variables);
179194
}
180195

196+
// Retrieves raw language statistics for a user.
181197
export async function getLanguageStatsRaw(username: string) {
182198
return executeGraphQL(languageStatsQuery, { username });
183199
}
184200

201+
// Retrieves raw submission calendar data for a user.
185202
export async function getUserProfileCalendarRaw(args: CalendarArgs) {
186203
const variables = buildVariables({ username: args.username, year: args.year });
187204
return executeGraphQL(userProfileCalendarQuery, variables);
188205
}
189206

207+
// Retrieves raw user profile data.
190208
export async function getUserProfileRaw(username: string) {
191209
return executeGraphQL(getUserProfileQuery, { username });
192210
}
193211

212+
// Retrieves the legacy daily problem data.
194213
export async function getDailyProblemLegacy() {
195214
return executeGraphQL(dailyProblemQuery, {});
196215
}
197216

217+
// Retrieves raw skill statistics for a user.
198218
export async function getSkillStatsRaw(username: string) {
199219
return executeGraphQL(skillStatsQuery, { username });
200220
}
201221

222+
// Retrieves the question progress for a user.
202223
export async function getUserProgress(username: string) {
203224
const data = await executeGraphQL(userQuestionProgressQuery, { username });
204225
return formatProgressStats(data as UserData);
205226
}
206227

228+
// Retrieves the contest ranking information for a user.
207229
export async function getUserContestRankingInfo(username: string) {
208230
return executeGraphQL(userContestRankingInfoQuery, { username });
209231
}
210232

233+
// Retrieves raw question progress data for a user.
211234
export async function getUserProgressRaw(username: string) {
212235
return executeGraphQL(userQuestionProgressQuery, { username });
213236
}

mcp/modules/discussionTools.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
22
import { z } from 'zod';
33
import { getDiscussComments, getDiscussTopic, getTrendingTopics } from '../leetCodeService';
4-
import { runTool, ToolModule } from '../serverUtils';
4+
import { runTool } from '../serverUtils';
5+
import { ToolModule } from '../types';
56

67
export class DiscussionToolsModule implements ToolModule {
8+
// Registers discussion-related tools with the MCP server.
79
register(server: McpServer): void {
810
server.registerTool(
911
'leetcode_discuss_trending',

mcp/modules/problemTools.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import {
99
getSelectProblem,
1010
getSelectProblemRaw,
1111
} from '../leetCodeService';
12-
import { runTool, ToolModule } from '../serverUtils';
12+
import { runTool } from '../serverUtils';
13+
import { ToolModule } from '../types';
1314

1415
export class ProblemToolsModule implements ToolModule {
16+
// Registers problem-related tools with the MCP server.
1517
register(server: McpServer): void {
1618
server.registerTool(
1719
'leetcode_problem_daily',

mcp/modules/userTools.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import {
2020
getUserProgress,
2121
getUserProgressRaw,
2222
} from '../leetCodeService';
23-
import { runTool, ToolModule } from '../serverUtils';
23+
import { runTool } from '../serverUtils';
24+
import { ToolModule } from '../types';
2425

2526
const usernameSchema = z.string();
2627
const limitSchema = z.number().int().positive().max(50).optional();
2728

2829
export class UserToolsModule implements ToolModule {
30+
// Registers user-related tools with the MCP server.
2931
register(server: McpServer): void {
3032
server.registerTool(
3133
'leetcode_user_data',

mcp/serverUtils.ts

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
11
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
22
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3-
4-
type GraphQLParams = Record<string, unknown>;
3+
import { GraphQLParams, GraphQLClientError, ToolResponse, ToolExecutor, ToolModule } from './types';
54

65
const GRAPHQL_ENDPOINT = 'https://leetcode.com/graphql';
7-
export const SERVER_VERSION = '2.0.1';
8-
9-
export class GraphQLClientError extends Error {
10-
readonly status: number;
11-
readonly body: unknown;
12-
13-
constructor(message: string, status: number, body: unknown) {
14-
super(message);
15-
this.status = status;
16-
this.body = body;
17-
}
18-
}
6+
export const SERVER_VERSION = '1.0.0';
197

8+
// Executes a GraphQL query against the LeetCode API.
209
export async function executeGraphQL(query: string, variables: GraphQLParams = {}): Promise<unknown> {
2110
const requestInit: RequestInit = {
2211
method: 'POST',
@@ -41,10 +30,12 @@ export async function executeGraphQL(query: string, variables: GraphQLParams = {
4130
return payload.data;
4231
}
4332

33+
// Converts data to tool content format.
4434
export function toToolContent(data: unknown): { type: 'text'; text: string }[] {
4535
return [{ type: 'text', text: JSON.stringify(data, null, 2) }];
4636
}
4737

38+
// Creates a tool result from data.
4839
export function createToolResult(data: unknown): {
4940
content: { type: 'text'; text: string }[];
5041
} {
@@ -53,6 +44,7 @@ export function createToolResult(data: unknown): {
5344
};
5445
}
5546

47+
// Creates an error tool result from an error.
5648
export function createErrorResult(error: unknown): {
5749
content: { type: 'text'; text: string }[];
5850
} {
@@ -72,10 +64,7 @@ export function createErrorResult(error: unknown): {
7264
return createToolResult({ message: 'Unknown error', detail: error });
7365
}
7466

75-
export type ToolResponse = { content: { type: 'text'; text: string }[] };
76-
77-
export type ToolExecutor = () => Promise<unknown>;
78-
67+
// Runs a tool executor and handles errors.
7968
export async function runTool(executor: ToolExecutor): Promise<ToolResponse> {
8069
try {
8170
const data = await executor();
@@ -85,10 +74,7 @@ export async function runTool(executor: ToolExecutor): Promise<ToolResponse> {
8574
}
8675
}
8776

88-
export interface ToolModule {
89-
register(server: McpServer): void;
90-
}
91-
77+
// Starts the MCP server with the given modules.
9278
export async function startServer(
9379
serverInfo: { name: string; version?: string },
9480
modules: ToolModule[],

0 commit comments

Comments
 (0)