Model Context Protocol server with GitHub API
_Exported on 12/24/2025 at 14:51:35 GMT+5:30 from Cursor (2.2.43)_
Model Context Protocol server with GitHub API
Exported on 12/24/2025 at 14:51:35 GMT+5:30 from Cursor (2.2.43)
User
I need to build a Model Context Protocol (MCP) server from scratch using TypeScript and Node.js. This server will connect to the GitHub API.
Please guide me through the setup and write the complete code.
Tech Stack Requirements:
- Language: TypeScript
- Runtime: Node.js (ES Modules)
- Libraries:
@modelcontextprotocol/sdk(for the server and types)octokit(for GitHub API calls)zod(for input validation)
Project Requirements:
- Setup: Help me initialize the
package.jsonandtsconfig.jsonconfigured for ES2022 and Node16 module resolution. - Authentication: The server must use a
GITHUB_TOKENfrom environment variables. - Transport: Use
StdioServerTransportto communicate via standard input/output.
Tool Definitions (Endpoints):
Please implement the following two tools:
-
get_user_repositories- Goal: Fetch a list of public repositories for a specific GitHub username.
- Input:
username(string) - e.g., "sushant" or "torvalds". - Logic: Use Octokit to list public repos for that user. Return the repo name, description, star count, and HTML URL.
-
get_repo_readme- Goal: Fetch the README.md content of a specific repository.
- Input:
owner(string),repo(string). - Logic: Use Octokit to get the README content. Decode the base64 content returned by GitHub and return it as plain text.
Output:
- Provide the shell commands to install dependencies.
- Provide the full content for
src/index.ts. - Provide the build script commands.
Cursor
Here’s everything you need to set up and run the GitHub MCP server.
1) Init & install (run in project root)
npm init -ynpm pkg set type=modulenpm install @modelcontextprotocol/sdk octokit zodnpm install -D typescript tsx @types/nodenpm pkg set scripts.build="tsc -p tsconfig.json" scripts.dev="tsx src/index.ts" scripts.start="node dist/index.js"
2) tsconfig.json (ES2022, Node16 resolution)
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
3) src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/transports/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new Server(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.tool(
"get_user_repositories",
"List public repositories for a GitHub username.",
z.object({
username: z.string().min(1, "username is required")
}),
async (input) => {
const { username } = input;
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return {
content: [
{
type: "text",
text: JSON.stringify(repos, null, 2)
}
]
};
}
);
server.tool(
"get_repo_readme",
"Fetch README.md content for a repository.",
z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
}),
async (input) => {
const { owner, repo } = input;
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
const decoded = Buffer.from(data.content, data.encoding as BufferEncoding).toString("utf8");
return {
content: [
{
type: "text",
text: decoded
}
]
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
4) Build/run
- Build:
npm run build - Run compiled:
npm start - Dev (TS directly):
npm run dev
Remember to export GITHUB_TOKEN before running.
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/transports/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new Server(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.tool(
"get_user_repositories",
"List public repositories for a GitHub username.",
z.object({
username: z.string().min(1, "username is required")
}),
async (input) => {
const { username } = input;
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return {
content: [
{
type: "text",
text: JSON.stringify(repos, null, 2)
}
]
};
}
);
server.tool(
"get_repo_readme",
"Fetch README.md content for a repository.",
z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
}),
async (input) => {
const { owner, repo } = input;
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
const decoded = Buffer.from(data.content, data.encoding as BufferEncoding).toString("utf8");
return {
content: [
{
type: "text",
text: decoded
}
]
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
User
do it on auto
Cursor
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/transports/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new Server(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.tool(
"get_user_repositories",
"List public repositories for a GitHub username.",
z.object({
username: z.string().min(1, "username is required")
}),
async (input) => {
const { username } = input;
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return {
content: [
{
type: "text",
text: JSON.stringify(repos, null, 2)
}
]
};
}
);
server.tool(
"get_repo_readme",
"Fetch README.md content for a repository.",
z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
}),
async (input) => {
const { owner, repo } = input;
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return {
content: [
{
type: "text",
text: decoded
}
]
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
MCP GitHub server is set up and built. Key pieces are below.
Code highlights
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({ username: z.string().min(1, "username is required") })
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({ username, per_page: 100, sort: "updated" });
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(data.content, data.encoding as BufferEncoding).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
What I set up
- Initialized npm project and set
type=module. - Installed
@modelcontextprotocol/sdk,octokit,zodplus dev deps (typescript,tsx,@types/node). - Added scripts:
build(tsc -p tsconfig.json),dev(tsx src/index.ts),start(node dist/index.js). - Created
tsconfig.jsontargeting ES2022 with Node16 module resolution,rootDirsrc,outDirdist. - Implemented MCP server using
McpServer+StdioServerTransportwith the two tools.
How to run
- Set
GITHUB_TOKENin your environment. - Dev (TS directly):
npm run dev - Build:
npm run build - Run compiled:
npm start
Build status: npm run build passes. Note: the SDK requires Node 18+.
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({ username: z.string().min(1, "username is required") })
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({ username, per_page: 100, sort: "updated" });
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(data.content, data.encoding as BufferEncoding).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
User
I need to build a Model Context Protocol (MCP) server from scratch using TypeScript and Node.js. This server will connect to the GitHub API.
Please guide me through the setup and write the complete code.
Tech Stack Requirements:
- Language: TypeScript
- Runtime: Node.js (ES Modules)
- Libraries:
@modelcontextprotocol/sdk(for the server and types)octokit(for GitHub API calls)zod(for input validation)
Project Requirements:
- Setup: Help me initialize the
package.jsonandtsconfig.jsonconfigured for ES2022 and Node16 module resolution. - Authentication: The server must use a
GITHUB_TOKENfrom environment variables. - Transport: Use
StdioServerTransportto communicate via standard input/output.
Tool Definitions (Endpoints):
Please implement the following two tools:
-
get_user_repositories- Goal: Fetch a list of public repositories for a specific GitHub username.
- Input:
username(string) - e.g., "sushant" or "torvalds". - Logic: Use Octokit to list public repos for that user. Return the repo name, description, star count, and HTML URL.
-
get_repo_readme- Goal: Fetch the README.md content of a specific repository.
- Input:
owner(string),repo(string). - Logic: Use Octokit to get the README content. Decode the base64 content returned by GitHub and return it as plain text.
Output:
- Provide the shell commands to install dependencies.
- Provide the full content for
src/index.ts. - Provide the build script commands.
Cursor
All done—here are the exact commands, config, and full code you need.
Shell commands (run in project root)
npm init -ynpm pkg set type=modulenpm install @modelcontextprotocol/sdk octokit zodnpm install -D typescript tsx @types/nodenpm pkg set scripts.build="tsc -p tsconfig.json" scripts.dev="tsx src/index.ts" scripts.start="node dist/index.js"
tsconfig.json (ES2022, Node16 resolution)
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Build scripts (already set via npm pkg set)
- Build:
npm run build - Dev (TS directly):
npm run dev - Run compiled:
npm start
Reminder: set GITHUB_TOKEN in your environment before running. Node 18+ is required by the SDK.
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
User
our current arcitecture
I need to build a Model Context Protocol (MCP) server from scratch using TypeScript and Node.js. This server will connect to the GitHub API.
Please guide me through the setup and write the complete code.
Tech Stack Requirements:
- Language: TypeScript
- Runtime: Node.js (ES Modules)
- Libraries:
@modelcontextprotocol/sdk(for the server and types)octokit(for GitHub API calls)zod(for input validation)
Project Requirements:
- Setup: Help me initialize the
package.jsonandtsconfig.jsonconfigured for ES2022 and Node16 module resolution. - Authentication: The server must use a
GITHUB_TOKENfrom environment variables. - Transport: Use
StdioServerTransportto communicate via standard input/output.
Tool Definitions (Endpoints):
Please implement the following two tools:
-
get_user_repositories- Goal: Fetch a list of public repositories for a specific GitHub username.
- Input:
username(string) - e.g., "sushant" or "torvalds". - Logic: Use Octokit to list public repos for that user. Return the repo name, description, star count, and HTML URL.
-
get_repo_readme- Goal: Fetch the README.md content of a specific repository.
- Input:
owner(string),repo(string). - Logic: Use Octokit to get the README content. Decode the base64 content returned by GitHub and return it as plain text.
Output:
- Provide the shell commands to install dependencies.
- Provide the full content for
src/index.ts. - Provide the build script commands. fix it
Cursor
Here’s the full setup and code you asked for, ready to run.
Shell commands (run in project root)
npm init -ynpm pkg set type=modulenpm install @modelcontextprotocol/sdk octokit zodnpm install -D typescript tsx @types/nodenpm pkg set scripts.build="tsc -p tsconfig.json" scripts.dev="tsx src/index.ts" scripts.start="node dist/index.js"
tsconfig.json (ES2022, Node16 resolution)
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Build/run
- Build:
npm run build - Dev (TS directly):
npm run dev - Run compiled:
npm start
Remember to set GITHUB_TOKEN in your environment. The SDK requires Node 18+.
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"lib": ["ES2022"],
"types": ["node"],
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return { content: [{ type: "text", text: JSON.stringify(repos, null, 2) }] };
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return { content: [{ type: "text", text: decoded }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
User
not able to run server
Cursor
The server is crashing because GITHUB_TOKEN isn’t set. Set it before running:
PowerShell (current session):
$env:GITHUB_TOKEN = "ghp_your_token_here"
npm run dev # or npm start after build
To persist for future shells:
setx GITHUB_TOKEN "ghp_your_token_here"
Then open a new terminal and run npm run dev (or npm start after npm run build).
$env:GITHUB_TOKEN = "ghp_your_token_here"
npm run dev # or npm start after build
setx GITHUB_TOKEN "ghp_your_token_here"
User
create helper funtion and tool
Cursor
import "dotenv/config";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const requireOctokit = () => {
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
return new Octokit({ auth: githubToken });
};
const textContent = (text: string) => ({
content: [{ type: "text", text }]
});
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return textContent(JSON.stringify(repos, null, 2));
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return textContent(decoded);
}
);
server.registerTool(
"get_rate_limit",
{
description: "Check remaining GitHub API rate limit for the current token."
},
async () => {
const octokit = requireOctokit();
const { data } = await octokit.rest.rateLimit.get();
const core = data.resources.core;
const search = data.resources.search;
const graphql = data.resources.graphql;
return textContent(
JSON.stringify(
{
core: {
remaining: core.remaining,
limit: core.limit,
reset: new Date(core.reset * 1000).toISOString()
},
search: {
remaining: search.remaining,
limit: search.limit,
reset: new Date(search.reset * 1000).toISOString()
},
graphql: graphql
? {
remaining: graphql.remaining,
limit: graphql.limit,
reset: new Date(graphql.reset * 1000).toISOString()
}
: undefined
},
null,
2
)
);
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
import "dotenv/config";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const requireOctokit = () => {
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
return new Octokit({ auth: githubToken });
};
const textContent = (text: string) => ({
content: [{ type: "text" as const, text }]
});
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return textContent(JSON.stringify(repos, null, 2));
}
);
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return textContent(decoded);
}
);
server.registerTool(
"get_rate_limit",
{
description: "Check remaining GitHub API rate limit for the current token."
},
async () => {
const octokit = requireOctokit();
const { data } = await octokit.rest.rateLimit.get();
const core = data.resources.core;
const search = data.resources.search;
const graphql = data.resources.graphql;
return textContent(
JSON.stringify(
{
core: {
remaining: core.remaining,
limit: core.limit,
reset: new Date(core.reset * 1000).toISOString()
},
search: {
remaining: search.remaining,
limit: search.limit,
reset: new Date(search.reset * 1000).toISOString()
},
graphql: graphql
? {
remaining: graphql.remaining,
limit: graphql.limit,
reset: new Date(graphql.reset * 1000).toISOString()
}
: undefined
},
null,
2
)
);
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
I added a helper and a new tool, and enabled .env loading.
What changed
- Added
dotenvload so.envis picked up automatically. - Helper
requireOctokit()ensuresGITHUB_TOKENis present and returns a configured Octokit. - Helper
textContent()standardizes MCP text responses. - Existing tools now reuse the helper.
- New tool
get_rate_limitreports remaining GitHub API limits for the current token.
Current src/index.ts (key parts)
import "dotenv/config";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const requireOctokit = () => {
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) throw new Error("GITHUB_TOKEN is required in the environment.");
return new Octokit({ auth: githubToken });
};
const textContent = (text: string) => ({ content: [{ type: "text" as const, text }] });
const server = new McpServer({ name: "github-mcp-server", version: "0.1.0" }, { capabilities: { tools: {} } });
server.registerTool(
"get_user_repositories",
{ description: "List public repositories for a GitHub username.", inputSchema: z.object({ username: z.string().min(1) }) },
async ({ username }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.listForUser({ username, per_page: 100, sort: "updated" });
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return textContent(JSON.stringify(repos, null, 2));
}
);
server.registerTool(
"get_repo_readme",
{ description: "Fetch README.md content for a repository.", inputSchema: z.object({ owner: z.string().min(1), repo: z.string().min(1) }) },
async ({ owner, repo }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(data.content, data.encoding as BufferEncoding).toString("utf8");
return textContent(decoded);
}
);
server.registerTool(
"get_rate_limit",
{ description: "Check remaining GitHub API rate limit for the current token." },
async () => {
const octokit = requireOctokit();
const { data } = await octokit.rest.rateLimit.get();
const core = data.resources.core;
const search = data.resources.search;
const graphql = data.resources.graphql;
return textContent(
JSON.stringify(
{
core: { remaining: core.remaining, limit: core.limit, reset: new Date(core.reset * 1000).toISOString() },
search: { remaining: search.remaining, limit: search.limit, reset: new Date(search.reset * 1000).toISOString() },
graphql: graphql
? { remaining: graphql.remaining, limit: graphql.limit, reset: new Date(graphql.reset * 1000).toISOString() }
: undefined
},
null,
2
)
);
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Build status: npm run build passes. To run, ensure .env has GITHUB_TOKEN=... (already present), then npm run dev (or npm start after npm run build).
import "dotenv/config";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
const requireOctokit = () => {
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) throw new Error("GITHUB_TOKEN is required in the environment.");
return new Octokit({ auth: githubToken });
};
const textContent = (text: string) => ({ content: [{ type: "text" as const, text }] });
const server = new McpServer({ name: "github-mcp-server", version: "0.1.0" }, { capabilities: { tools: {} } });
server.registerTool(
"get_user_repositories",
{ description: "List public repositories for a GitHub username.", inputSchema: z.object({ username: z.string().min(1) }) },
async ({ username }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.listForUser({ username, per_page: 100, sort: "updated" });
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return textContent(JSON.stringify(repos, null, 2));
}
);
server.registerTool(
"get_repo_readme",
{ description: "Fetch README.md content for a repository.", inputSchema: z.object({ owner: z.string().min(1), repo: z.string().min(1) }) },
async ({ owner, repo }) => {
const octokit = requireOctokit();
const { data } = await octokit.rest.repos.getReadme({ owner, repo });
const decoded = Buffer.from(data.content, data.encoding as BufferEncoding).toString("utf8");
return textContent(decoded);
}
);
server.registerTool(
"get_rate_limit",
{ description: "Check remaining GitHub API rate limit for the current token." },
async () => {
const octokit = requireOctokit();
const { data } = await octokit.rest.rateLimit.get();
const core = data.resources.core;
const search = data.resources.search;
const graphql = data.resources.graphql;
return textContent(
JSON.stringify(
{
core: { remaining: core.remaining, limit: core.limit, reset: new Date(core.reset * 1000).toISOString() },
search: { remaining: search.remaining, limit: search.limit, reset: new Date(search.reset * 1000).toISOString() },
graphql: graphql
? { remaining: graphql.remaining, limit: graphql.limit, reset: new Date(graphql.reset * 1000).toISOString() }
: undefined
},
null,
2
)
);
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
User
I need to build a Model Context Protocol (MCP) server from scratch using TypeScript and Node.js. This server will connect to the GitHub API.
Please guide me through the setup and write the complete code.
Tech Stack Requirements:
- Language: TypeScript
- Runtime: Node.js (ES Modules)
- Libraries:
@modelcontextprotocol/sdk(for the server and types)octokit(for GitHub API calls)zod(for input validation)
Project Requirements:
- Setup: Help me initialize the
package.jsonandtsconfig.jsonconfigured for ES2022 and Node16 module resolution. - Authentication: The server must use a
GITHUB_TOKENfrom environment variables. - Transport: Use
StdioServerTransportto communicate via standard input/output.
Tool Definitions (Endpoints):
Please implement the following two tools:
-
get_user_repositories- Goal: Fetch a list of public repositories for a specific GitHub username.
- Input:
username(string) - e.g., "sushant" or "torvalds". - Logic: Use Octokit to list public repos for that user. Return the repo name, description, star count, and HTML URL.
-
get_repo_readme- Goal: Fetch the README.md content of a specific repository.
- Input:
owner(string),repo(string). - Logic: Use Octokit to get the README content. Decode the base64 content returned by GitHub and return it as plain text.
Output:
- Provide the shell commands to install dependencies.
- Provide the full content for
src/index.ts. - Provide the build script commands.
Cursor
Reviewing the current setup and providing the complete code with just the two required tools.
Providing the complete setup with just the two required tools. The project is already configured; here's the summary and the minimal code:
Updating src/index.ts to include only the two required tools, keeping the code clean:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Octokit } from "octokit";
// Authentication: Get GITHUB_TOKEN from environment variables
const githubToken = process.env.GITHUB_TOKEN;
if (!githubToken) {
throw new Error("GITHUB_TOKEN is required in the environment.");
}
const octokit = new Octokit({ auth: githubToken });
// Initialize MCP Server
const server = new McpServer(
{ name: "github-mcp-server", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
// Tool 1: get_user_repositories
// Fetch a list of public repositories for a specific GitHub username
server.registerTool(
"get_user_repositories",
{
description: "List public repositories for a GitHub username.",
inputSchema: z.object({
username: z.string().min(1, "username is required")
})
},
async ({ username }) => {
const { data } = await octokit.rest.repos.listForUser({
username,
per_page: 100,
sort: "updated"
});
// Return repo name, description, star count, and HTML URL
const repos = data.map((repo) => ({
name: repo.name,
description: repo.description,
stars: repo.stargazers_count,
html_url: repo.html_url
}));
return {
content: [
{
type: "text",
text: JSON.stringify(repos, null, 2)
}
]
};
}
);
// Tool 2: get_repo_readme
// Fetch the README.md content of a specific repository
server.registerTool(
"get_repo_readme",
{
description: "Fetch README.md content for a repository.",
inputSchema: z.object({
owner: z.string().min(1, "owner is required"),
repo: z.string().min(1, "repo is required")
})
},
async ({ owner, repo }) => {
const { data } = await octokit.rest.repos.getReadme({
owner,
repo
});
// Decode the base64 content returned by GitHub and return as plain text
const decoded = Buffer.from(
data.content,
data.encoding as BufferEncoding
).toString("utf8");
return {
content: [
{
type: "text",
text: decoded
}
]
};
}
);
// Connect to transport using StdioServerTransport
const transport = new StdioServerTransport();
await server.connect(transport);
Setup complete. Summary:
Project setup
1. Shell commands (already executed)
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk octokit zod
npm install -D typescript tsx @types/node
npm pkg set scripts.build="tsc -p tsconfig.json" scripts.dev="tsx src/index.ts" scripts.start="node dist/index.js"
2. Configuration files
package.json — Already configured with:
"type": "module"for ES Modules- Dependencies:
@modelcontextprotocol/sdk,octokit,zod - Dev dependencies:
typescript,tsx,@types/node - Scripts:
build,dev,start
tsconfig.json — Already configured with:
target: "ES2022"module: "Node16"moduleResolution: "Node16"rootDir: "src",outDir: "dist"
3. Complete src/index.ts
The file now contains:
- Authentication using
GITHUB_TOKENfrom environment variables - Two tools:
get_user_repositories— Lists public repos for a usernameget_repo_readme— Fetches README content for a repo
StdioServerTransportfor communication
4. Build scripts
- Build:
npm run build(compiles TypeScript todist/) - Dev:
npm run dev(runs TypeScript directly withtsx) - Start:
npm start(runs compiled JavaScript fromdist/)
Running the server
-
Set the environment variable:
$env:GITHUB_TOKEN = "your_token_here" -
Run the server:
npm run dev # Development mode # OR npm run build # Build first npm start # Run compiled version
Build verified: TypeScript compilation succeeds. The server is ready to use.
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk octokit zod
npm install -D typescript tsx @types/node
npm pkg set scripts.build="tsc -p tsconfig.json" scripts.dev="tsx src/index.ts" scripts.start="node dist/index.js"
$env:GITHUB_TOKEN = "your_token_here"
npm run dev # Development mode
# OR
npm run build # Build first
npm start # Run compiled version
User
You are a senior backend engineer.
Build a simple MCP (Model Context Protocol) server in Node.js (TypeScript) that exposes a tool to fetch GitHub repositories for a given user.
Requirements
- MCP Protocol
Use JSON-RPC 2.0
Single endpoint: POST /mcp
Support these MCP methods:
tools/list
tools/call
- Tool to Implement
Create an MCP tool:
Tool name: getGithubRepos
Description: Fetch public GitHub repositories for a given username.
Input schema:
{ "type": "object", "properties": { "username": { "type": "string" } }, "required": ["username"] }
Behavior:
Call GitHub public REST API https://api.github.com/users/{username}/repos
Return a simplified response:
name
html_url
description
stargazers_count
- Folder Structure (keep it simple)
Use this structure exactly:
src/ ├── index.ts ├── server.ts ├── mcp.ts ├── tools/ │ ├── github.ts │ └── index.ts └── services/ └── github.service.ts
- Architectural Rules
server.ts → HTTP server only
mcp.ts → MCP router (method dispatch)
tools/ → MCP tool definitions only
services/ → External API logic (GitHub API)
No unnecessary abstractions
Clear separation of concerns
- Error Handling
Return JSON-RPC compliant errors
Handle:
Invalid tool name
Missing username
GitHub API errors (404, rate limit)
- Tech Constraints
Node.js + TypeScript
Use express
Use native fetch or axios
No authentication required (public GitHub API)
Code should be clean, readable, and production-style
- Output Expectations
Generate all required files
Include example JSON-RPC requests for:
tools/list
tools/call
Do NOT include explanations unless necessary
Focus on correct, working implementation
Goal
At the end, I should be able to:
Run the server
Call /mcp
Discover the tool
Fetch GitHub repos via MCP
Cursor
Building a custom MCP server with Express and JSON-RPC 2.0. Creating the required files with the specified structure.
Creating the required files:
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos = await response.json();
return repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
}
import { GitHubService } from "../services/github.service.js";
export interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: string;
properties: Record<string, any>;
required: string[];
};
}
export interface ToolCallResult {
content: Array<{
type: string;
text: string;
}>;
}
export class GithubTool {
private githubService: GitHubService;
constructor() {
this.githubService = new GitHubService();
}
getDefinition(): ToolDefinition {
return {
name: "getGithubRepos",
description: "Fetch public GitHub repositories for a given username.",
inputSchema: {
type: "object",
properties: {
username: {
type: "string",
},
},
required: ["username"],
},
};
}
async call(params: { username: string }): Promise<ToolCallResult> {
if (!params.username) {
throw new Error("username is required");
}
const repos = await this.githubService.getUserRepos(params.username);
return {
content: [
{
type: "text",
text: JSON.stringify(repos, null, 2),
},
],
};
}
}
import { GithubTool } from "./github.js";
import type { ToolDefinition, ToolCallResult } from "./github.js";
export class ToolRegistry {
private tools: Map<string, GithubTool>;
constructor() {
this.tools = new Map();
this.registerTool(new GithubTool());
}
private registerTool(tool: GithubTool): void {
const definition = tool.getDefinition();
this.tools.set(definition.name, tool);
}
listTools(): ToolDefinition[] {
return Array.from(this.tools.values()).map((tool) => tool.getDefinition());
}
async callTool(name: string, params: any): Promise<ToolCallResult> {
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`Tool '${name}' not found`);
}
return tool.call(params);
}
hasTool(name: string): boolean {
return this.tools.has(name);
}
}
import { ToolRegistry } from "./tools/index.js";
export interface JsonRpcRequest {
jsonrpc: "2.0";
id: string | number | null;
method: string;
params?: any;
}
export interface JsonRpcResponse {
jsonrpc: "2.0";
id: string | number | null;
result?: any;
error?: {
code: number;
message: string;
data?: any;
};
}
export class MCPRouter {
private toolRegistry: ToolRegistry;
constructor() {
this.toolRegistry = new ToolRegistry();
}
async handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
const { method, params, id } = request;
try {
let result: any;
switch (method) {
case "tools/list":
result = await this.handleToolsList();
break;
case "tools/call":
result = await this.handleToolsCall(params);
break;
default:
throw {
code: -32601,
message: `Method not found: ${method}`,
};
}
return {
jsonrpc: "2.0",
id,
result,
};
} catch (error: any) {
return {
jsonrpc: "2.0",
id,
error: {
code: error.code || -32000,
message: error.message || "Internal error",
data: error.data,
},
};
}
}
private async handleToolsList(): Promise<{ tools: any[] }> {
const tools = this.toolRegistry.listTools();
return {
tools: tools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
};
}
private async handleToolsCall(params: any): Promise<any> {
if (!params || typeof params !== "object") {
throw {
code: -32602,
message: "Invalid params",
};
}
const { name, arguments: args } = params;
if (!name || typeof name !== "string") {
throw {
code: -32602,
message: "Tool name is required",
};
}
if (!this.toolRegistry.hasTool(name)) {
throw {
code: -32601,
message: `Tool '${name}' not found`,
};
}
try {
const result = await this.toolRegistry.callTool(name, args || {});
return result;
} catch (error: any) {
throw {
code: -32000,
message: error.message || "Tool execution failed",
data: error,
};
}
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /health - Health check`);
});
}
}
import { HTTPServer } from "./server.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
const server = new HTTPServer(PORT);
server.start();
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos: any[] = await response.json();
return repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
}
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos = (await response.json()) as any[];
return repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
}
Creating example JSON-RPC requests:
{
"tools_list_request": {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
},
"tools_list_response": {
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "getGithubRepos",
"description": "Fetch public GitHub repositories for a given username.",
"inputSchema": {
"type": "object",
"properties": {
"username": {
"type": "string"
}
},
"required": ["username"]
}
}
]
}
},
"tools_call_request": {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "getGithubRepos",
"arguments": {
"username": "torvalds"
}
}
},
"tools_call_response": {
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "[\n {\n \"name\": \"linux\",\n \"html_url\": \"https://github.com/torvalds/linux\",\n \"description\": \"Linux kernel source tree\",\n \"stargazers_count\": 123456\n }\n]"
}
]
}
},
"error_example": {
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32601,
"message": "Tool 'invalidTool' not found"
}
}
}
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos = (await response.json()) as any[];
return repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
}
Verifying the folder structure:
Project Structure
src/
├── index.ts
├── server.ts
├── mcp.ts
├── tools/
│ ├── github.ts
│ └── index.ts
└── services/
└── github.service.ts
Run the Server
npm run dev # Development mode
# OR
npm run build # Build
npm start # Production mode
Server runs on http://localhost:3000
Example JSON-RPC Requests
tools/list:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
tools/call:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "getGithubRepos",
"arguments": {
"username": "torvalds"
}
}
}
Send POST requests to http://localhost:3000/mcp with these JSON-RPC payloads.
All files are implemented with error handling, JSON-RPC 2.0 compliance, and clean separation of concerns. Build verified.
src/
├── index.ts
├── server.ts
├── mcp.ts
├── tools/
│ ├── github.ts
│ └── index.ts
└── services/
└── github.service.ts
npm run dev # Development mode
# OR
npm run build # Build
npm start # Production mode
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "getGithubRepos",
"arguments": {
"username": "torvalds"
}
}
}
User
create get endpoint to get any github repo via macp server
Cursor
Adding a GET endpoint to fetch any GitHub repository. Updating the service and server:
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export interface GitHubRepoDetails extends GitHubRepo {
full_name: string;
owner: {
login: string;
html_url: string;
};
language: string | null;
forks_count: number;
open_issues_count: number;
created_at: string;
updated_at: string;
default_branch: string;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos = (await response.json()) as any[];
return repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
}
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export interface GitHubRepoDetails extends GitHubRepo {
full_name: string;
owner: {
login: string;
html_url: string;
};
language: string | null;
forks_count: number;
open_issues_count: number;
created_at: string;
updated_at: string;
default_branch: string;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos = (await response.json()) as any[];
return repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
async getRepo(owner: string, repo: string): Promise<GitHubRepoDetails> {
const url = `${this.baseUrl}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Repository '${owner}/${repo}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repoData = (await response.json()) as any;
return {
name: repoData.name,
html_url: repoData.html_url,
description: repoData.description,
stargazers_count: repoData.stargazers_count,
full_name: repoData.full_name,
owner: {
login: repoData.owner.login,
html_url: repoData.owner.html_url,
},
language: repoData.language,
forks_count: repoData.forks_count,
open_issues_count: repoData.open_issues_count,
created_at: repoData.created_at,
updated_at: repoData.updated_at,
default_branch: repoData.default_branch,
};
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /repos/:owner/:repo - Get GitHub repository`);
console.log(`GET /health - Health check`);
});
}
}
Added a GET endpoint to fetch any GitHub repository. Summary:
New GET Endpoint
Endpoint: GET /repos/:owner/:repo
Example Usage:
GET http://localhost:3000/repos/torvalds/linux
GET http://localhost:3000/repos/microsoft/vscode
What Was Added
-
Enhanced GitHub Service (
src/services/github.service.ts):- Added
getRepo(owner, repo)method - Returns detailed repository information including:
- Basic info (name, description, stars)
- Owner details
- Language, forks, issues
- Timestamps and default branch
- Added
-
New REST Endpoint (
src/server.ts):GET /repos/:owner/:repoendpoint- Uses the GitHub service directly
- Handles errors (404, 429, 500)
- Returns JSON response
Response Format
{
"name": "linux",
"html_url": "https://github.com/torvalds/linux",
"description": "Linux kernel source tree",
"stargazers_count": 123456,
"full_name": "torvalds/linux",
"owner": {
"login": "torvalds",
"html_url": "https://github.com/torvalds"
},
"language": "C",
"forks_count": 45678,
"open_issues_count": 123,
"created_at": "2011-09-04T22:48:12Z",
"updated_at": "2024-01-15T10:30:00Z",
"default_branch": "master"
}
Build verified. The endpoint is ready to use.
GET http://localhost:3000/repos/torvalds/linux
GET http://localhost:3000/repos/microsoft/vscode
{
"name": "linux",
"html_url": "https://github.com/torvalds/linux",
"description": "Linux kernel source tree",
"stargazers_count": 123456,
"full_name": "torvalds/linux",
"owner": {
"login": "torvalds",
"html_url": "https://github.com/torvalds"
},
"language": "C",
"forks_count": 45678,
"open_issues_count": 123,
"created_at": "2011-09-04T22:48:12Z",
"updated_at": "2024-01-15T10:30:00Z",
"default_branch": "master"
}
User
Cannot GET /repos/torvalds/linux
http://localhost:3000/repos/torvalds/linux
Cursor
Checking the server code to diagnose the routing issue:
Checking if the server is running and verifying the route is registered correctly:
Route is in the compiled code. Rebuilding and checking if the server needs a restart:
Adding a catch-all route for debugging and verifying the route order:
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /repos/:owner/:repo",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /repos/:owner/:repo - Get GitHub repository`);
console.log(`GET /health - Health check`);
});
}
}
Route is in the code. Restart the server to load the changes:
- Stop the current server (Ctrl+C if running in terminal)
- Restart it:
or if using the compiled version:npm run devnpm run build npm start
The route /repos/:owner/:repo is registered. After restarting, test:
http://localhost:3000/repos/torvalds/linuxhttp://localhost:3000/health(to verify the server is running)
If it still doesn't work after restart, checking for a potential route registration issue:
npm run dev
npm run build
npm start
Route is correctly defined. Restart the server to load the changes. Adding a simple test to verify the route is working:
The route is correctly defined. Restart the server to load the changes.
Steps to fix:
-
Stop the current server (press
Ctrl+Cin the terminal where it's running) -
Rebuild and restart:
npm run build npm startOr for development mode:
npm run dev -
Test the endpoint:
http://localhost:3000/repos/torvalds/linuxhttp://localhost:3000/health(to verify server is running)
The route /repos/:owner/:repo is registered at line 70. A catch-all 404 handler was added to show available routes if a route isn't found.
After restarting, the endpoint should work. If you still see "Cannot GET", check the server console output—it should show:
GET /repos/:owner/:repo - Get GitHub repository
This confirms the route is registered.
npm run build
npm start
npm run dev
npm run build
npm start
npm run dev
GET /repos/:owner/:repo - Get GitHub repository
User
http://localhost:3000/repos/torvalds/linux with this endpoin can i get any repo like any username
Cursor
Yes. The endpoint GET /repos/:owner/:repo works for any GitHub repository. Replace :owner and :repo with any GitHub username/organization and repository name.
Examples:
http://localhost:3000/repos/torvalds/linux
http://localhost:3000/repos/microsoft/vscode
http://localhost:3000/repos/facebook/react
http://localhost:3000/repos/vercel/next.js
http://localhost:3000/repos/nodejs/node
http://localhost:3000/repos/your-username/your-repo
How it works:
The route uses Express path parameters:
:owner= GitHub username or organization name:repo= Repository name
So http://localhost:3000/repos/torvalds/linux becomes:
owner= "torvalds"repo= "linux"
The server extracts these from the URL and calls the GitHub API to fetch that repository's data.
Try it:
You can test with any public GitHub repository:
http://localhost:3000/repos/microsoft/vscodehttp://localhost:3000/repos/facebook/reacthttp://localhost:3000/repos/your-username/your-repo-name
The endpoint returns repository details including name, description, stars, forks, language, and more.
http://localhost:3000/repos/torvalds/linux
http://localhost:3000/repos/microsoft/vscode
http://localhost:3000/repos/facebook/react
http://localhost:3000/repos/vercel/next.js
http://localhost:3000/repos/nodejs/node
http://localhost:3000/repos/your-username/your-repo
User
how is mcp helping in this
Cursor
Explaining how MCP is used in this setup:
How MCP helps in this setup:
-
Standardized AI/LLM integration
- MCP provides a standard protocol for AI assistants (like Claude, ChatGPT) to discover and use tools
- The
POST /mcpendpoint handles MCP protocol requests
-
Tool discovery (
tools/list)- AI models can query available tools:
POST /mcp { "jsonrpc": "2.0", "id": 1, "method": "tools/list" }- Returns tool definitions with schemas so the AI knows what parameters are needed
-
Tool execution (
tools/call)- AI models can call tools with validated parameters:
POST /mcp { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "getGithubRepos", "arguments": { "username": "torvalds" } } } -
Benefits over REST
- Self-describing tools: schemas tell the AI what's available
- Standardized protocol: works with any MCP-compatible AI
- Structured responses: consistent format for AI processing
- Error handling: JSON-RPC error codes
-
Current architecture
AI Assistant → POST /mcp (tools/call) → MCP Router → Tool Registry → GitHub Service → GitHub API Browser/App → GET /repos/:owner/:repo → GitHub Service → GitHub API
The REST endpoint (GET /repos) is a convenience for direct HTTP calls. MCP enables AI assistants to:
- Discover available GitHub tools
- Understand required parameters from schemas
- Call tools programmatically
- Get structured, consistent responses
This makes your server compatible with AI assistants that support MCP, allowing them to fetch GitHub data through a standardized interface.
POST /mcp
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
POST /mcp
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "getGithubRepos",
"arguments": {
"username": "torvalds"
}
}
}
AI Assistant → POST /mcp (tools/call) → MCP Router → Tool Registry → GitHub Service → GitHub API
Browser/App → GET /repos/:owner/:repo → GitHub Service → GitHub API
User
like i want to fetch all repo available on githubmane
Cursor
Adding a tool and endpoint to search all GitHub repositories (not just by username). Implementing:
Adding search functionality to find all repositories on GitHub:
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export interface GitHubRepoDetails extends GitHubRepo {
full_name: string;
owner: {
login: string;
html_url: string;
};
language: string | null;
forks_count: number;
open_issues_count: number;
created_at: string;
updated_at: string;
default_branch: string;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos = (await response.json()) as any[];
return repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
async getRepo(owner: string, repo: string): Promise<GitHubRepoDetails> {
const url = `${this.baseUrl}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Repository '${owner}/${repo}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repoData = (await response.json()) as any;
return {
name: repoData.name,
html_url: repoData.html_url,
description: repoData.description,
stargazers_count: repoData.stargazers_count,
full_name: repoData.full_name,
owner: {
login: repoData.owner.login,
html_url: repoData.owner.html_url,
},
language: repoData.language,
forks_count: repoData.forks_count,
open_issues_count: repoData.open_issues_count,
created_at: repoData.created_at,
updated_at: repoData.updated_at,
default_branch: repoData.default_branch,
};
}
async searchRepos(query: string, sort: string = "stars", order: string = "desc", perPage: number = 30): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/search/repositories?q=${encodeURIComponent(query)}&sort=${sort}&order=${order}&per_page=${perPage}`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
if (response.status === 422) {
throw new Error("Invalid search query");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const data = (await response.json()) as any;
return data.items.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
}
import { GitHubService } from "../services/github.service.js";
import type { ToolDefinition, ToolCallResult } from "./github.js";
export class SearchReposTool {
private githubService: GitHubService;
constructor() {
this.githubService = new GitHubService();
}
getDefinition(): ToolDefinition {
return {
name: "searchGithubRepos",
description: "Search all public GitHub repositories by query (language, topic, name, etc.).",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query (e.g., 'language:javascript', 'topic:react', 'nodejs', etc.)",
},
sort: {
type: "string",
enum: ["stars", "forks", "help-wanted-issues", "updated"],
description: "Sort results by: stars, forks, help-wanted-issues, or updated",
default: "stars",
},
order: {
type: "string",
enum: ["desc", "asc"],
description: "Sort order: desc (descending) or asc (ascending)",
default: "desc",
},
perPage: {
type: "number",
description: "Number of results per page (max 100)",
default: 30,
minimum: 1,
maximum: 100,
},
},
required: ["query"],
},
};
}
async call(params: { query: string; sort?: string; order?: string; perPage?: number }): Promise<ToolCallResult> {
if (!params.query) {
throw new Error("query is required");
}
const repos = await this.githubService.searchRepos(
params.query,
params.sort || "stars",
params.order || "desc",
params.perPage || 30
);
return {
content: [
{
type: "text",
text: JSON.stringify(repos, null, 2),
},
],
};
}
}
import { GithubTool } from "./github.js";
import { SearchReposTool } from "./search.js";
import type { ToolDefinition, ToolCallResult } from "./github.js";
interface Tool {
getDefinition(): ToolDefinition;
call(params: any): Promise<ToolCallResult>;
}
export class ToolRegistry {
private tools: Map<string, Tool>;
constructor() {
this.tools = new Map();
this.registerTool(new GithubTool());
this.registerTool(new SearchReposTool());
}
private registerTool(tool: Tool): void {
const definition = tool.getDefinition();
this.tools.set(definition.name, tool);
}
listTools(): ToolDefinition[] {
return Array.from(this.tools.values()).map((tool) => tool.getDefinition());
}
async callTool(name: string, params: any): Promise<ToolCallResult> {
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`Tool '${name}' not found`);
}
return tool.call(params);
}
hasTool(name: string): boolean {
return this.tools.has(name);
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /repos/:owner/:repo",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /repos/:owner/:repo - Get GitHub repository`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /repos/:owner/:repo",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /repos/:owner/:repo - Get GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /repos/:owner/:repo",
"GET /search/repos?q=query",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /repos/:owner/:repo - Get GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
Added search functionality to fetch repositories from GitHub. Here's what's available:
New Features
1. REST Endpoint - Search All Repositories
GET /search/repos?q=query&sort=stars&order=desc&per_page=30
Examples:
http://localhost:3000/search/repos?q=javascript
http://localhost:3000/search/repos?q=language:python
http://localhost:3000/search/repos?q=topic:react
http://localhost:3000/search/repos?q=nodejs+stars:>1000
http://localhost:3000/search/repos?q=machine+learning&sort=stars&order=desc&per_page=50
2. MCP Tool - searchGithubRepos
Available via the MCP protocol at POST /mcp:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "searchGithubRepos",
"arguments": {
"query": "language:javascript",
"sort": "stars",
"order": "desc",
"perPage": 30
}
}
}
Search Query Examples
language:javascript- Repositories in JavaScripttopic:react- Repositories with "react" topicnodejs- Repositories with "nodejs" in name/descriptionstars:>1000- Repositories with more than 1000 starsmachine learning- General searchuser:torvalds- Repositories by specific user
Available Tools
getGithubRepos- Get repos for a specific usernamesearchGithubRepos- Search all GitHub repositories
Restart your server to use the new search functionality.
GET /search/repos?q=query&sort=stars&order=desc&per_page=30
http://localhost:3000/search/repos?q=javascript
http://localhost:3000/search/repos?q=language:python
http://localhost:3000/search/repos?q=topic:react
http://localhost:3000/search/repos?q=nodejs+stars:>1000
http://localhost:3000/search/repos?q=machine+learning&sort=stars&order=desc&per_page=50
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "searchGithubRepos",
"arguments": {
"query": "language:javascript",
"sort": "stars",
"order": "desc",
"perPage": 30
}
}
}
User
endpoint should work like it can fetch all github repos get by giveving only username
Cursor
Adding a REST endpoint that accepts a username and returns all their repositories:
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/users/:username/repos", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /repos/:owner/:repo",
"GET /search/repos?q=query",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /repos/:owner/:repo - Get GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/users/:username/repos", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /users/:username/repos",
"GET /repos/:owner/:repo",
"GET /search/repos?q=query",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /repos/:owner/:repo - Get GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/users/:username/repos", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /users/:username/repos",
"GET /repos/:owner/:repo",
"GET /search/repos?q=query",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /users/:username/repos - Get all repositories for a user`);
console.log(`GET /repos/:owner/:repo - Get specific GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
Added an endpoint to fetch all repositories for a username.
New Endpoint
GET /users/:username/repos
Usage Examples:
http://localhost:3000/users/torvalds/repos
http://localhost:3000/users/microsoft/repos
http://localhost:3000/users/facebook/repos
http://localhost:3000/users/your-username/repos
Response Format:
{
"username": "torvalds",
"total": 5,
"repositories": [
{
"name": "linux",
"html_url": "https://github.com/torvalds/linux",
"description": "Linux kernel source tree",
"stargazers_count": 123456
},
{
"name": "subsurface",
"html_url": "https://github.com/torvalds/subsurface",
"description": "Subsurface divelog program",
"stargazers_count": 5432
}
// ... more repos
]
}
All Available Endpoints:
GET /users/:username/repos- Get all repos for a usernameGET /repos/:owner/:repo- Get a specific repositoryGET /search/repos?q=query- Search all GitHub repositoriesPOST /mcp- MCP protocol endpoint
Restart your server and use /users/:username/repos with any GitHub username.
http://localhost:3000/users/torvalds/repos
http://localhost:3000/users/microsoft/repos
http://localhost:3000/users/facebook/repos
http://localhost:3000/users/your-username/repos
{
"username": "torvalds",
"total": 5,
"repositories": [
{
"name": "linux",
"html_url": "https://github.com/torvalds/linux",
"description": "Linux kernel source tree",
"stargazers_count": 123456
},
{
"name": "subsurface",
"html_url": "https://github.com/torvalds/subsurface",
"description": "Subsurface divelog program",
"stargazers_count": 5432
}
// ... more repos
]
}
User
http://localhost:3000/repos/sushant6095 why its not able to get my repos { "error": "Cannot GET /repos/sushant6095", "availableRoutes": [ "POST /mcp", "GET /users/:username/repos", "GET /repos/:owner/:repo", "GET /search/repos?q=query", "GET /health" ] }
Cursor
The route /repos/sushant6095 doesn't match any endpoint. Use /users/sushant6095/repos instead. Adding a simpler route /repos/:username for convenience:
Adding a simpler route /repos/:username that works with just a username. Placing it before the two-parameter route to avoid conflicts:
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/users/:username/repos", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:username", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /users/:username/repos",
"GET /repos/:owner/:repo",
"GET /search/repos?q=query",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /users/:username/repos - Get all repositories for a user`);
console.log(`GET /repos/:owner/:repo - Get specific GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/users/:username/repos", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:username", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /users/:username/repos",
"GET /repos/:username",
"GET /repos/:owner/:repo",
"GET /search/repos?q=query",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /users/:username/repos - Get all repositories for a user`);
console.log(`GET /repos/:owner/:repo - Get specific GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
import express, { Request, Response } from "express";
import { MCPRouter, JsonRpcRequest } from "./mcp.js";
import { GitHubService } from "./services/github.service.js";
export class HTTPServer {
private app: express.Application;
private mcpRouter: MCPRouter;
private githubService: GitHubService;
private port: number;
constructor(port: number = 3000) {
this.app = express();
this.mcpRouter = new MCPRouter();
this.githubService = new GitHubService();
this.port = port;
this.setupMiddleware();
this.setupRoutes();
}
private setupMiddleware(): void {
this.app.use(express.json());
}
private setupRoutes(): void {
this.app.post("/mcp", async (req: Request, res: Response) => {
try {
const request: JsonRpcRequest = req.body;
if (!request.jsonrpc || request.jsonrpc !== "2.0") {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: jsonrpc must be '2.0'",
},
});
}
if (!request.method) {
return res.status(400).json({
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32600,
message: "Invalid Request: method is required",
},
});
}
const response = await this.mcpRouter.handleRequest(request);
res.json(response);
} catch (error: any) {
res.status(500).json({
jsonrpc: "2.0",
id: req.body.id || null,
error: {
code: -32603,
message: "Internal error",
data: error.message,
},
});
}
});
this.app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok" });
});
this.app.get("/users/:username/repos", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:username", async (req: Request, res: Response) => {
try {
const { username } = req.params;
if (!username) {
return res.status(400).json({
error: "Username parameter is required",
});
}
const repos = await this.githubService.getUserRepos(username);
res.json({
username,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/repos/:owner/:repo", async (req: Request, res: Response) => {
try {
const { owner, repo } = req.params;
if (!owner || !repo) {
return res.status(400).json({
error: "Owner and repo parameters are required",
});
}
const repoData = await this.githubService.getRepo(owner, repo);
res.json(repoData);
} catch (error: any) {
if (error.message.includes("not found")) {
return res.status(404).json({
error: error.message,
});
}
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.get("/search/repos", async (req: Request, res: Response) => {
try {
const { q, sort, order, per_page } = req.query;
if (!q || typeof q !== "string") {
return res.status(400).json({
error: "Query parameter 'q' is required",
});
}
const repos = await this.githubService.searchRepos(
q,
(sort as string) || "stars",
(order as string) || "desc",
per_page ? parseInt(per_page as string, 10) : 30
);
res.json({
query: q,
total: repos.length,
repositories: repos,
});
} catch (error: any) {
if (error.message.includes("rate limit")) {
return res.status(429).json({
error: error.message,
});
}
if (error.message.includes("Invalid search query")) {
return res.status(422).json({
error: error.message,
});
}
res.status(500).json({
error: "Internal server error",
message: error.message,
});
}
});
this.app.use((req: Request, res: Response) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`,
availableRoutes: [
"POST /mcp",
"GET /users/:username/repos",
"GET /repos/:username",
"GET /repos/:owner/:repo",
"GET /search/repos?q=query",
"GET /health",
],
});
});
}
start(): void {
this.app.listen(this.port, () => {
console.log(`MCP Server running on http://localhost:${this.port}`);
console.log(`POST /mcp - MCP endpoint`);
console.log(`GET /users/:username/repos - Get all repositories for a user`);
console.log(`GET /repos/:username - Get all repositories for a user (short form)`);
console.log(`GET /repos/:owner/:repo - Get specific GitHub repository`);
console.log(`GET /search/repos?q=query - Search all GitHub repositories`);
console.log(`GET /health - Health check`);
});
}
}
Added the route /repos/:username. Restart your server and use:
Now you can use:
http://localhost:3000/repos/sushant6095
This will return all repositories for the username sushant6095.
Available endpoints for getting user repos:
-
GET /repos/:username- Short form (what you wanted)- Example:
http://localhost:3000/repos/sushant6095
- Example:
-
GET /users/:username/repos- Alternative form- Example:
http://localhost:3000/users/sushant6095/repos
- Example:
Both endpoints return the same data:
{
"username": "sushant6095",
"total": 10,
"repositories": [
{
"name": "repo-name",
"html_url": "https://github.com/sushant6095/repo-name",
"description": "Repo description",
"stargazers_count": 5
}
// ... more repos
]
}
Restart your server and try http://localhost:3000/repos/sushant6095 again. It should work now.
http://localhost:3000/repos/sushant6095
{
"username": "sushant6095",
"total": 10,
"repositories": [
{
"name": "repo-name",
"html_url": "https://github.com/sushant6095/repo-name",
"description": "Repo description",
"stargazers_count": 5
}
// ... more repos
]
}
User
should fetch all the repos
Cursor
Updating the service to fetch all repositories across all pages (handle pagination):
Updating the service to fetch all repositories across all pages:
export interface GitHubRepo {
name: string;
html_url: string;
description: string | null;
stargazers_count: number;
}
export interface GitHubRepoDetails extends GitHubRepo {
full_name: string;
owner: {
login: string;
html_url: string;
};
language: string | null;
forks_count: number;
open_issues_count: number;
created_at: string;
updated_at: string;
default_branch: string;
}
export class GitHubService {
private readonly baseUrl = "https://api.github.com";
async getUserRepos(username: string): Promise<GitHubRepo[]> {
const allRepos: GitHubRepo[] = [];
let page = 1;
const perPage = 100; // Maximum items per page
let hasMore = true;
while (hasMore) {
const url = `${this.baseUrl}/users/${encodeURIComponent(username)}/repos?page=${page}&per_page=${perPage}&sort=updated`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`User '${username}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repos = (await response.json()) as any[];
if (repos.length === 0) {
hasMore = false;
} else {
const mappedRepos = repos.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
allRepos.push(...mappedRepos);
// Check if there are more pages by checking the Link header
const linkHeader = response.headers.get("link");
if (linkHeader && linkHeader.includes('rel="next"')) {
page++;
} else {
hasMore = false;
}
}
}
return allRepos;
}
async getRepo(owner: string, repo: string): Promise<GitHubRepoDetails> {
const url = `${this.baseUrl}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Repository '${owner}/${repo}' not found`);
}
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const repoData = (await response.json()) as any;
return {
name: repoData.name,
html_url: repoData.html_url,
description: repoData.description,
stargazers_count: repoData.stargazers_count,
full_name: repoData.full_name,
owner: {
login: repoData.owner.login,
html_url: repoData.owner.html_url,
},
language: repoData.language,
forks_count: repoData.forks_count,
open_issues_count: repoData.open_issues_count,
created_at: repoData.created_at,
updated_at: repoData.updated_at,
default_branch: repoData.default_branch,
};
}
async searchRepos(query: string, sort: string = "stars", order: string = "desc", perPage: number = 30): Promise<GitHubRepo[]> {
const url = `${this.baseUrl}/search/repositories?q=${encodeURIComponent(query)}&sort=${sort}&order=${order}&per_page=${perPage}`;
const response = await fetch(url, {
headers: {
Accept: "application/vnd.github.v3+json",
},
});
if (!response.ok) {
if (response.status === 403) {
throw new Error("GitHub API rate limit exceeded");
}
if (response.status === 422) {
throw new Error("Invalid search query");
}
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const data = (await response.json()) as any;
return data.items.map((repo: any) => ({
name: repo.name,
html_url: repo.html_url,
description: repo.description,
stargazers_count: repo.stargazers_count,
}));
}
}
Updated the service to fetch all repositories across all pages. Changes:
What Changed:
- Pagination handling: Fetches all pages automatically
- Maximum items per page: Uses 100 items per page (GitHub's maximum)
- Link header detection: Checks for more pages using the
Linkheader - Complete results: Combines all pages into a single array
How it works:
- Starts at page 1 with 100 items per page
- Fetches each page sequentially
- Checks the
Linkheader to see if there's anextpage - Continues until all pages are fetched
- Returns all repositories in one array
Example:
When you call:
http://localhost:3000/repos/sushant6095
It will now return ALL repositories, not just the first 30. If you have 150 repositories, it will fetch all 150 across 2 pages.
Restart your server and try again. It should now fetch all your repositories.
http://localhost:3000/repos/sushant6095
Related Documents
GPU Selection Guide for Large Language Models (LLMs)
This guide helps you choose the right GPU for running Large Language Models, whether you're using them for inference, fine-tuning, or training.
Community AI Agent Skills Discovery Sources
**Research Date:** 2026-03-26
ReleaseKit - Technical Requirements Document
ReleaseKit provides **two interfaces** to the same underlying functionality:
api_llm Specification
Provide direct, transparent HTTP API bindings for major LLM providers without abstraction layers or automatic behaviors.