Download server.js from mmty/MCP: direct link, hf CLI and curl.
- Browser
- Download file 11.3 kB
-
https://huggingface.co/spaces/mmty/MCP/resolve/main/server.js
- Command line
-
hf download hf://spaces/mmty/MCP/server.js
-
curl -L -o server.js https://huggingface.co/spaces/mmty/MCP/resolve/main/server.js
11.3 kB
| const express = require('express'); | |
| const cors = require('cors'); | |
| // Dynamic import for node-fetch | |
| const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); | |
| const app = express(); | |
| const PORT = process.env.PORT || 7860; | |
| // --- 1. CONFIGURATION --- | |
| app.set('trust proxy', 1); // Required for HF Spaces | |
| app.use(express.json({ limit: '10mb' })); | |
| // --- 2. CORS (Global) --- | |
| app.use(cors({ | |
| origin: '*', | |
| methods: ['POST', 'OPTIONS'], | |
| allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With', 'MCP-Protocol-Version'], | |
| })); | |
| // --- 3. AUTH MIDDLEWARE --- | |
| // Secures the MCP server so only you (via Gemini CLI) can use it. | |
| const authMiddleware = (req, res, next) => { | |
| const expectedSecret = process.env.MCP_AUTH_TOKEN; | |
| // If no secret is set in env, allow open access (Warning logged) | |
| if (!expectedSecret) { | |
| // console.warn("[WARN] No MCP_AUTH_TOKEN set. Server is open."); | |
| return next(); | |
| } | |
| const authHeader = req.headers['authorization']; | |
| if (!authHeader || !authHeader.startsWith('Bearer ') || authHeader.split(' ')[1] !== expectedSecret) { | |
| console.error(`[AUTH] Unauthorized access attempt`); | |
| return res.status(401).json({ | |
| jsonrpc: "2.0", | |
| id: null, | |
| error: { code: -32000, message: "Unauthorized: Invalid or missing MCP_AUTH_TOKEN" } | |
| }); | |
| } | |
| next(); | |
| }; | |
| // --- 4. LOGGING --- | |
| app.use((req, res, next) => { | |
| console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); | |
| next(); | |
| }); | |
| // --- 5. GITHUB API HELPER --- | |
| const callGitHub = async (endpoint, method = 'GET', body = null) => { | |
| const token = process.env.GH_TOKEN; | |
| if (!token) throw new Error("Server configuration error: GH_TOKEN is missing."); | |
| const url = endpoint.startsWith('http') ? endpoint : `https://api.github.com${endpoint}`; | |
| const headers = { | |
| 'Authorization': `Bearer ${token}`, | |
| 'Accept': 'application/vnd.github.v3+json', | |
| 'User-Agent': 'HuggingFace-MCP-Server', | |
| 'X-GitHub-Api-Version': '2022-11-28' | |
| }; | |
| const options = { | |
| method, | |
| headers, | |
| body: body ? JSON.stringify(body) : undefined | |
| }; | |
| const response = await fetch(url, options); | |
| if (!response.ok) { | |
| const errorText = await response.text(); | |
| throw new Error(`GitHub API ${response.status}: ${errorText}`); | |
| } | |
| return response.json(); | |
| }; | |
| // --- 6. MAIN MCP ENDPOINT --- | |
| app.post('/mcp', authMiddleware, async (req, res) => { | |
| try { | |
| const { id, method, params } = req.body; | |
| const requestId = id !== undefined ? id : null; | |
| // Helper to send success | |
| const send = (result) => res.json({ jsonrpc: "2.0", id: requestId, result }); | |
| // Helper to send error | |
| const error = (code, msg) => res.json({ jsonrpc: "2.0", id: requestId, error: { code, message: msg } }); | |
| console.log(`[POST] Method: ${method}`); | |
| // --- A. HANDSHAKE & LIFECYCLE --- | |
| if (method === 'initialize') { | |
| return send({ | |
| protocolVersion: "2024-11-05", | |
| capabilities: { tools: {}, resources: {}, prompts: {} }, | |
| serverInfo: { name: "github-mcp-versatile", version: "2.0.0" } | |
| }); | |
| } | |
| if (method === 'notifications/initialized') return send(true); | |
| // Crucial for Gemini CLI connection stability | |
| if (method === 'ping') return send({}); | |
| // --- B. TOOL LIST --- | |
| if (method === 'tools/list') { | |
| return send({ | |
| tools: [ | |
| // 1. ISSUES | |
| { | |
| name: "list_issues", | |
| description: "List issues in a repo.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { | |
| owner: { type: "string" }, | |
| repo: { type: "string" }, | |
| state: { type: "string", enum: ["open", "closed", "all"], default: "open" } | |
| }, | |
| required: ["owner", "repo"] | |
| } | |
| }, | |
| { | |
| name: "create_issue", | |
| description: "Create a new issue.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { | |
| owner: { type: "string" }, | |
| repo: { type: "string" }, | |
| title: { type: "string" }, | |
| body: { type: "string" } | |
| }, | |
| required: ["owner", "repo", "title"] | |
| } | |
| }, | |
| // 2. FILES & CODE | |
| { | |
| name: "read_file", | |
| description: "Read the contents of a specific file.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { | |
| owner: { type: "string" }, | |
| repo: { type: "string" }, | |
| path: { type: "string", description: "Path to file (e.g. src/index.js)" }, | |
| branch: { type: "string", description: "Branch or commit SHA (default: main)" } | |
| }, | |
| required: ["owner", "repo", "path"] | |
| } | |
| }, | |
| { | |
| name: "list_files", | |
| description: "Get the file tree (recursive) to understand repo structure.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { | |
| owner: { type: "string" }, | |
| repo: { type: "string" }, | |
| branch: { type: "string", default: "main" }, | |
| recursive: { type: "boolean", default: true } | |
| }, | |
| required: ["owner", "repo"] | |
| } | |
| }, | |
| { | |
| name: "search_code", | |
| description: "Search for code within a repository.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { | |
| q: { type: "string", description: "Search query (e.g. 'function login')" }, | |
| owner: { type: "string" }, | |
| repo: { type: "string" } | |
| }, | |
| required: ["q", "owner", "repo"] | |
| } | |
| }, | |
| // 3. PULL REQUESTS | |
| { | |
| name: "list_prs", | |
| description: "List Pull Requests.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { | |
| owner: { type: "string" }, | |
| repo: { type: "string" }, | |
| state: { type: "string", enum: ["open", "closed", "all"], default: "open" } | |
| }, | |
| required: ["owner", "repo"] | |
| } | |
| }, | |
| { | |
| name: "create_pr", | |
| description: "Create a Pull Request.", | |
| inputSchema: { | |
| type: "object", | |
| properties: { | |
| owner: { type: "string" }, | |
| repo: { type: "string" }, | |
| title: { type: "string" }, | |
| head: { type: "string", description: "The name of the branch where your changes are implemented." }, | |
| base: { type: "string", description: "The name of the branch you want the changes pulled into." }, | |
| body: { type: "string" } | |
| }, | |
| required: ["owner", "repo", "title", "head", "base"] | |
| } | |
| } | |
| ] | |
| }); | |
| } | |
| // --- C. TOOL EXECUTION --- | |
| if (method === 'tools/call') { | |
| const { name, arguments: args } = params || {}; | |
| try { | |
| let resultData; | |
| // --- Logic: ISSUES --- | |
| if (name === "list_issues") { | |
| const data = await callGitHub(`/repos/${args.owner}/${args.repo}/issues?state=${args.state || 'open'}&per_page=10`); | |
| resultData = data.map(i => ({ number: i.number, title: i.title, state: i.state, url: i.html_url })); | |
| } | |
| else if (name === "create_issue") { | |
| const data = await callGitHub(`/repos/${args.owner}/${args.repo}/issues`, 'POST', { | |
| title: args.title, body: args.body | |
| }); | |
| resultData = { message: "Issue Created", url: data.html_url, number: data.number }; | |
| } | |
| // --- Logic: FILES --- | |
| else if (name === "read_file") { | |
| // GitHub API returns content in Base64 | |
| const ref = args.branch ? `?ref=${args.branch}` : ''; | |
| const data = await callGitHub(`/repos/${args.owner}/${args.repo}/contents/${args.path}${ref}`); | |
| if (Array.isArray(data)) return error(-32000, "Path is a directory, not a file."); | |
| if (!data.content) return error(-32000, "File content empty or binary."); | |
| const decodedContent = Buffer.from(data.content, 'base64').toString('utf-8'); | |
| // Truncate if huge to save tokens | |
| const safeContent = decodedContent.length > 20000 ? decodedContent.substring(0, 20000) + "\n...[Truncated]" : decodedContent; | |
| resultData = safeContent; | |
| } | |
| else if (name === "list_files") { | |
| const ref = args.branch || 'main'; | |
| // Get the tree SHA first, or just assume main/master | |
| // To be simple, we use the trees API which requires the SHA of the branch. | |
| // Fallback: Use the 'contents' API recursively? No, that's heavy. | |
| // Better approach for LLM: Get the HEAD commit SHA first. | |
| // 1. Get Branch SHA | |
| const branchData = await callGitHub(`/repos/${args.owner}/${args.repo}/git/ref/heads/${ref}`); | |
| const sha = branchData.object.sha; | |
| // 2. Get Tree | |
| const recursive = args.recursive ? '?recursive=1' : ''; | |
| const treeData = await callGitHub(`/repos/${args.owner}/${args.repo}/git/trees/${sha}${recursive}`); | |
| // Filter to just paths to save tokens | |
| resultData = treeData.tree.map(t => t.path).slice(0, 200); // Limit to 200 files | |
| } | |
| else if (name === "search_code") { | |
| const q = `${args.q} repo:${args.owner}/${args.repo}`; | |
| const data = await callGitHub(`/search/code?q=${encodeURIComponent(q)}&per_page=5`); | |
| resultData = data.items.map(i => ({ name: i.name, path: i.path, url: i.html_url })); | |
| } | |
| // --- Logic: PULL REQUESTS --- | |
| else if (name === "list_prs") { | |
| const data = await callGitHub(`/repos/${args.owner}/${args.repo}/pulls?state=${args.state || 'open'}&per_page=10`); | |
| resultData = data.map(i => ({ number: i.number, title: i.title, state: i.state, url: i.html_url })); | |
| } | |
| else if (name === "create_pr") { | |
| const data = await callGitHub(`/repos/${args.owner}/${args.repo}/pulls`, 'POST', { | |
| title: args.title, head: args.head, base: args.base, body: args.body | |
| }); | |
| resultData = { message: "PR Created", url: data.html_url, number: data.number }; | |
| } | |
| else { | |
| return error(-32601, `Tool ${name} not found`); | |
| } | |
| // Return Standard Success Response | |
| return send({ | |
| content: [{ type: "text", text: typeof resultData === 'string' ? resultData : JSON.stringify(resultData, null, 2) }], | |
| isError: false | |
| }); | |
| } catch (err) { | |
| return error(-32000, `Execution Error: ${err.message}`); | |
| } | |
| } | |
| return error(-32601, "Method not found"); | |
| } catch (err) { | |
| console.error(err); | |
| res.status(500).json({ jsonrpc: "2.0", id: null, error: { code: -32603, message: "Internal Server Error" } }); | |
| } | |
| }); | |
| // 7. HEALTH CHECK | |
| app.use((req, res) => res.status(200).send("GitHub MCP Active")); | |
| app.listen(PORT, '0.0.0.0', () => console.log(`Server running on ${PORT}`)); |