Loading...
Loading...
Loading...
_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:
1. **Setup:** Help me initialize the `package.json` and `tsconfig.json` configured for ES2022 and Node16 module resolution.
2. **Authentication:** The server must use a `GITHUB_TOKEN` from environment variables.
3. **Transport:** Use `StdioServerTransport` to communicate via standard input/output.
### Tool Definitions (Endpoints):
Please implement the following two tools:
1. **`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.
2. **`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 -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) `tsconfig.json` (ES2022, Node16 resolution)
```json
{
"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`
```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.
```json
{
"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"]
}
```
```typescript
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**
```jsonc
{
"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"]
}
```
```typescript
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);
```
```typescript
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);
```
```typescript
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
```18:42:src/index.ts
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) }] };
}
);
```
```44:66:src/index.ts
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`, `zod` plus 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.json` targeting ES2022 with Node16 module resolution, `rootDir` `src`, `outDir` `dist`.
- Implemented MCP server using `McpServer` + `StdioServerTransport` with the two tools.
How to run
- Set `GITHUB_TOKEN` in 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+.
```typescript
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) }] };
}
);
```
```typescript
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:
1. **Setup:** Help me initialize the `package.json` and `tsconfig.json` configured for ES2022 and Node16 module resolution.
2. **Authentication:** The server must use a `GITHUB_TOKEN` from environment variables.
3. **Transport:** Use `StdioServerTransport` to communicate via standard input/output.
### Tool Definitions (Endpoints):
Please implement the following two tools:
1. **`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.
2. **`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 -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"`
`tsconfig.json` (ES2022, Node16 resolution)
```json
{
"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`
```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.
```json
{
"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"]
}
```
```typescript
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:
1. **Setup:** Help me initialize the `package.json` and `tsconfig.json` configured for ES2022 and Node16 module resolution.
2. **Authentication:** The server must use a `GITHUB_TOKEN` from environment variables.
3. **Transport:** Use `StdioServerTransport` to communicate via standard input/output.
### Tool Definitions (Endpoints):
Please implement the following two tools:
1. **`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.
2. **`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 -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"`
`tsconfig.json` (ES2022, Node16 resolution)
```json
{
"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`
```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+.
```json
{
"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"]
}
```
```typescript
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):
```powershell
$env:GITHUB_TOKEN = "ghp_your_token_here"
npm run dev # or npm start after build
```
To persist for future shells:
```powershell
setx GITHUB_TOKEN "ghp_your_token_here"
```
Then open a new terminal and run `npm run dev` (or `npm start` after `npm run build`).
```powershell
$env:GITHUB_TOKEN = "ghp_your_token_here"
npm run dev # or npm start after build
```
```powershell
setx GITHUB_TOKEN "ghp_your_token_here"
```
---
**User**
create helper funtion and tool
---
**Cursor**
```typescript
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);
```
```typescript
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 `dotenv` load so `.env` is picked up automatically.
- Helper `requireOctokit()` ensures `GITHUB_TOKEN` is present and returns a configured Octokit.
- Helper `textContent()` standardizes MCP text responses.
- Existing tools now reuse the helper.
- New tool `get_rate_limit` reports remaining GitHub API limits for the current token.
Current `src/index.ts` (key parts)
```ts
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`).
```typescript
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:
1. **Setup:** Help me initialize the `package.json` and `tsconfig.json` configured for ES2022 and Node16 module resolution.
2. **Authentication:** The server must use a `GITHUB_TOKEN` from environment variables.
3. **Transport:** Use `StdioServerTransport` to communicate via standard input/output.
### Tool Definitions (Endpoints):
Please implement the following two tools:
1. **`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.
2. **`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:
```typescript
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)
```bash
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_TOKEN` from environment variables
- Two tools:
1. `get_user_repositories` — Lists public repos for a username
2. `get_repo_readme` — Fetches README content for a repo
- `StdioServerTransport` for communication
### 4. Build scripts
- Build: `npm run build` (compiles TypeScript to `dist/`)
- Dev: `npm run dev` (runs TypeScript directly with `tsx`)
- Start: `npm start` (runs compiled JavaScript from `dist/`)
### Running the server
1. Set the environment variable:
```powershell
$env:GITHUB_TOKEN = "your_token_here"
```
2. Run the server:
```bash
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.
```shellscript
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"
```
```powershell
$env:GITHUB_TOKEN = "your_token_here"
```
```shellscript
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
1. MCP Protocol
Use JSON-RPC 2.0
Single endpoint: POST /mcp
Support these MCP methods:
tools/list
tools/call
2. 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
3. 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
4. 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
5. Error Handling
Return JSON-RPC compliant errors
Handle:
Invalid tool name
Missing username
GitHub API errors (404, rate limit)
6. 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
7. 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:
```typescript
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,
}));
}
}
```
```typescript
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),
},
],
};
}
}
```
```typescript
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);
}
}
```
```typescript
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,
};
}
}
}
```
```typescript
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`);
});
}
}
```
```typescript
import { HTTPServer } from "./server.js";
const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
const server = new HTTPServer(PORT);
server.start();
```
```typescript
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,
}));
}
}
```
```typescript
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:
```json
{
"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"
}
}
}
```
```typescript
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
```bash
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:**
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
```
**tools/call:**
```json
{
"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.
```plaintext
src/
├── index.ts
├── server.ts
├── mcp.ts
├── tools/
│ ├── github.ts
│ └── index.ts
└── services/
└── github.service.ts
```
```shellscript
npm run dev # Development mode
# OR
npm run build # Build
npm start # Production mode
```
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
```
```json
{
"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:
```typescript
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,
}));
}
}
```
```typescript
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,
};
}
}
```
```typescript
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`);
});
}
}
```
```typescript
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`);
});
}
}
```
```typescript
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`);
});
}
}
```
```typescript
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:**
```bash
GET http://localhost:3000/repos/torvalds/linux
GET http://localhost:3000/repos/microsoft/vscode
```
## What Was Added
1. **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
2. **New REST Endpoint** (`src/server.ts`):
- `GET /repos/:owner/:repo` endpoint
- Uses the GitHub service directly
- Handles errors (404, 429, 500)
- Returns JSON response
## Response Format
```json
{
"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.
```shellscript
GET http://localhost:3000/repos/torvalds/linux
GET http://localhost:3000/repos/microsoft/vscode
```
```json
{
"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:
```typescript
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:
1. Stop the current server (Ctrl+C if running in terminal)
2. Restart it:
```bash
npm run dev
```
or if using the compiled version:
```bash
npm run build
npm start
```
The route `/repos/:owner/:repo` is registered. After restarting, test:
- `http://localhost:3000/repos/torvalds/linux`
- `http://localhost:3000/health` (to verify the server is running)
If it still doesn't work after restart, checking for a potential route registration issue:
```shellscript
npm run dev
```
```shellscript
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:**
1. **Stop the current server** (press `Ctrl+C` in the terminal where it's running)
2. **Rebuild and restart:**
```bash
npm run build
npm start
```
Or for development mode:
```bash
npm run dev
```
3. **Test the endpoint:**
- `http://localhost:3000/repos/torvalds/linux`
- `http://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.
```shellscript
npm run build
npm start
```
```shellscript
npm run dev
```
```shellscript
npm run build
npm start
```
```shellscript
npm run dev
```
```plaintext
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/vscode`
- `http://localhost:3000/repos/facebook/react`
- `http://localhost:3000/repos/your-username/your-repo-name`
The endpoint returns repository details including name, description, stars, forks, language, and more.
```plaintext
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:
1. Standardized AI/LLM integration
- MCP provides a standard protocol for AI assistants (like Claude, ChatGPT) to discover and use tools
- The `POST /mcp` endpoint handles MCP protocol requests
2. Tool discovery (`tools/list`)
- AI models can query available tools:
```json
POST /mcp
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
```
- Returns tool definitions with schemas so the AI knows what parameters are needed
3. Tool execution (`tools/call`)
- AI models can call tools with validated parameters:
```json
POST /mcp
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "getGithubRepos",
"arguments": {
"username": "torvalds"
}
}
}
```
4. 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
5. 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.
```json
POST /mcp
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
```
```json
POST /mcp
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "getGithubRepos",
"arguments": {
"username": "torvalds"
}
}
}
```
```plaintext
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:
```typescript
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,
}));
}
}
```
```typescript
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),
},
],
};
}
}
```
```typescript
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);
}
}
```
```typescript
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`);
});
}
}
```
```typescript
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`);
});
}
}
```
```typescript
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`:
```json
{
"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 JavaScript
- `topic:react` - Repositories with "react" topic
- `nodejs` - Repositories with "nodejs" in name/description
- `stars:>1000` - Repositories with more than 1000 stars
- `machine learning` - General search
- `user:torvalds` - Repositories by specific user
## Available Tools
1. `getGithubRepos` - Get repos for a specific username
2. `searchGithubRepos` - Search all GitHub repositories
Restart your server to use the new search functionality.
```plaintext
GET /search/repos?q=query&sort=stars&order=desc&per_page=30
```
```plaintext
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
```
```json
{
"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:
```typescript
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`);
});
}
}
```
```typescript
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`);
});
}
}
```
```typescript
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:
```json
{
"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:
1. **`GET /users/:username/repos`** - Get all repos for a username
2. **`GET /repos/:owner/:repo`** - Get a specific repository
3. **`GET /search/repos?q=query`** - Search all GitHub repositories
4. **`POST /mcp`** - MCP protocol endpoint
Restart your server and use `/users/:username/repos` with any GitHub username.
```plaintext
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
```
```json
{
"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:
```typescript
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`);
});
}
}
```
```typescript
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`);
});
}
}
```
```typescript
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:
1. **`GET /repos/:username`** - Short form (what you wanted)
- Example: `http://localhost:3000/repos/sushant6095`
2. **`GET /users/:username/repos`** - Alternative form
- Example: `http://localhost:3000/users/sushant6095/repos`
Both endpoints return the same data:
```json
{
"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.
```plaintext
http://localhost:3000/repos/sushant6095
```
```json
{
"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:
```typescript
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:
1. **Pagination handling**: Fetches all pages automatically
2. **Maximum items per page**: Uses 100 items per page (GitHub's maximum)
3. **Link header detection**: Checks for more pages using the `Link` header
4. **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 `Link` header to see if there's a `next` page
- 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.
```plaintext
http://localhost:3000/repos/sushant6095
```
You are an autonomous senior full-stack engineer responsible for building and maintaining a complete SaaS product. You operate with minimal supervision, making independent decisions while consulting on major strategic changes.
<author>blefnk/rules</author>
trigger: model_decision
description: Authoritative guide for all software-writing agents in this repository