GitHub Mapper MCP Server
by dazeb
Verified
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { Octokit } from "@octokit/rest";
let githubToken: string | null = null;
let octokit: Octokit | null = null;
const server = new Server(
{
name: "github-mapper-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "set-github-token",
description: "Set the GitHub Personal Access Token for authentication",
inputSchema: {
type: "object",
properties: {
token: {
type: "string",
description: "GitHub Personal Access Token",
},
},
required: ["token"],
},
},
{
name: "map-github-repo",
description: "Map a GitHub repository structure and provide summary information",
inputSchema: {
type: "object",
properties: {
repoUrl: {
type: "string",
description: "URL of the GitHub repository (e.g., https://github.com/username/repo)",
},
},
required: ["repoUrl"],
},
},
],
};
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "set-github-token") {
const { token } = args as { token: string };
githubToken = token;
octokit = new Octokit({ auth: githubToken });
return {
content: [
{
type: "text",
text: "GitHub Personal Access Token has been set successfully.",
},
],
};
} else if (name === "map-github-repo") {
if (!githubToken || !octokit) {
throw new Error("GitHub token not set. Please use the set-github-token tool first.");
}
const { repoUrl } = args as { repoUrl: string };
try {
const { owner, repo } = parseGitHubUrl(repoUrl);
const repoInfo = await getRepoInfo(owner, repo);
const repoStructure = await getRepoStructure(owner, repo);
const formattedOutput = formatOutput(repoInfo, repoStructure);
return {
content: [
{
type: "text",
text: formattedOutput,
},
],
};
} catch (error: unknown) {
console.error("Error mapping repository:", error);
const errorMessage = error instanceof Error ? error.message : 'An unknown error occurred';
return {
content: [
{
type: "text",
text: `Error mapping repository: ${errorMessage}`,
},
],
};
}
} else {
throw new Error(`Unknown tool: ${name}`);
}
});
function parseGitHubUrl(url: string): { owner: string; repo: string } {
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/);
if (!match) {
throw new Error("Invalid GitHub URL format");
}
return { owner: match[1], repo: match[2] };
}
async function getRepoInfo(owner: string, repo: string) {
if (!octokit) {
throw new Error("GitHub client not initialized");
}
const { data } = await octokit.repos.get({ owner, repo });
return {
name: data.name,
description: data.description,
stars: data.stargazers_count,
forks: data.forks_count,
language: data.language,
createdAt: data.created_at,
updatedAt: data.updated_at,
};
}
async function getRepoStructure(owner: string, repo: string, path = "") {
if (!octokit) {
throw new Error("GitHub client not initialized");
}
const { data } = await octokit.repos.getContent({ owner, repo, path });
if (!Array.isArray(data)) {
throw new Error("Unable to retrieve repository structure");
}
const structure: { [key: string]: any } = {};
for (const item of data) {
if (item.type === "file") {
structure[item.name] = null;
} else if (item.type === "dir") {
structure[item.name] = await getRepoStructure(owner, repo, item.path);
}
}
return structure;
}
function formatOutput(repoInfo: any, repoStructure: any): string {
const structureString = JSON.stringify(repoStructure, null, 2);
return `
Repository Analysis Summary:
Name: ${repoInfo.name}
Description: ${repoInfo.description || "No description provided"}
Stars: ${repoInfo.stars}
Forks: ${repoInfo.forks}
Primary Language: ${repoInfo.language}
Created: ${new Date(repoInfo.createdAt).toLocaleDateString()}
Last Updated: ${new Date(repoInfo.updatedAt).toLocaleDateString()}
Repository Structure:
${structureString}
`.trim();
}
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("GitHub Mapper MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});