guanghulab/mcp-servers/github-server.js
2026-05-10 13:12:44 +08:00

190 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* MCP Server: GitHub 工具集
*
* 提供 GitHub 仓库操作工具,供 AGE OS 对话界面调用。
* 所有工具调用经过配额守卫检查 + 权限隔离。
*
* 指令ZY-AGEOS-TOWER-2026-0326-001
* 版权:国作登字-2026-A-00037559
*/
'use strict';
/**
* GitHub MCP Server tool definitions
*/
var tools = [
{
name: 'github_create_branch',
description: '在指定仓库创建新分支',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
branch: { type: 'string', description: '新分支名' },
from: { type: 'string', description: '基于哪个分支', default: 'main' }
},
required: ['repo', 'branch']
}
},
{
name: 'github_commit_file',
description: '在指定仓库提交文件',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
path: { type: 'string', description: '文件路径' },
content: { type: 'string', description: '文件内容' },
message: { type: 'string', description: '提交信息' },
branch: { type: 'string', description: '目标分支', default: 'main' }
},
required: ['repo', 'path', 'content', 'message']
}
},
{
name: 'github_trigger_workflow',
description: '触发 GitHub Actions workflow',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
workflow_id: { type: 'string', description: 'workflow 文件名' },
ref: { type: 'string', description: '触发分支', default: 'main' }
},
required: ['repo', 'workflow_id']
}
},
{
name: 'github_create_pr',
description: '创建 Pull Request',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
title: { type: 'string', description: 'PR 标题' },
head: { type: 'string', description: '源分支' },
base: { type: 'string', description: '目标分支', default: 'main' },
body: { type: 'string', description: 'PR 描述' }
},
required: ['repo', 'title', 'head']
}
},
{
name: 'github_list_workflows',
description: '列出仓库所有 workflow 及最近运行状态',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' }
},
required: ['repo']
}
},
{
name: 'github_read_file',
description: '读取仓库文件内容',
inputSchema: {
type: 'object',
properties: {
repo: { type: 'string', description: '仓库全名 owner/repo' },
path: { type: 'string', description: '文件路径' },
ref: { type: 'string', description: '分支/标签/SHA', default: 'main' }
},
required: ['repo', 'path']
}
}
];
// System admin IDs with full access to all repos
var ADMIN_IDS = ['TCS-0002', 'DEV-000'];
/**
* Permission matrix for GitHub tools
* @param {string} devId - Developer ID
* @param {string} repo - Repository full name (owner/repo)
* @returns {{ allowed: boolean, reason?: string }}
*/
function checkPermission(devId, repo) {
// Admin users have access to all repos
if (ADMIN_IDS.indexOf(devId) >= 0) {
return { allowed: true };
}
// Main repo is protected — only TCS-0002 can operate
if (repo === 'qinfendebingshuo/guanghulab') {
return {
allowed: false,
reason: '主仓库仅系统层可操作,请在自己的行业仓库中执行'
};
}
// Other developers can only operate their own repos
// (Repo ownership validated via industry-repo-map at call time)
return { allowed: true };
}
/**
* Execute a GitHub MCP tool
* @param {string} toolName
* @param {object} params
* @param {object} context - { devId, pat }
* @returns {Promise<object>}
*/
async function executeTool(toolName, params, context) {
var devId = context.devId;
var pat = context.pat;
// Permission check
if (params.repo) {
var permResult = checkPermission(devId, params.repo);
if (!permResult.allowed) {
return { error: true, code: 'PERMISSION_DENIED', message: permResult.reason };
}
}
var baseUrl = 'https://api.github.com';
var headers = {
'Authorization': 'Bearer ' + pat,
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28'
};
switch (toolName) {
case 'github_read_file': {
var ref = params.ref || 'main';
var url = baseUrl + '/repos/' + params.repo + '/contents/' + params.path + '?ref=' + encodeURIComponent(ref);
var res = await fetch(url, { headers: headers });
if (!res.ok) return { error: true, status: res.status, message: 'Failed to read file' };
var data = await res.json();
return {
path: data.path,
content: data.encoding === 'base64' ? Buffer.from(data.content, 'base64').toString('utf8') : data.content,
sha: data.sha
};
}
case 'github_list_workflows': {
var wfUrl = baseUrl + '/repos/' + params.repo + '/actions/workflows';
var wfRes = await fetch(wfUrl, { headers: headers });
if (!wfRes.ok) return { error: true, status: wfRes.status, message: 'Failed to list workflows' };
var wfData = await wfRes.json();
return {
total_count: wfData.total_count,
workflows: (wfData.workflows || []).map(function(w) {
return { id: w.id, name: w.name, state: w.state, path: w.path };
})
};
}
default:
return { error: true, code: 'NOT_IMPLEMENTED', message: toolName + ' 尚未实现,待后续开发' };
}
}
module.exports = {
tools: tools,
checkPermission: checkPermission,
executeTool: executeTool
};