Repository navigation
Description
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Filing gate: ③ a task the maintainer ordered, verbatim 「立一张维护者处理卡,让维护者在本机测试」 (2026-11-04, in this seat's session). Child of #27293 (E4). It carries the one step of #28647 that a cloud seat cannot do. ⛔ Not a claim.
Ruling-ref: 6556740185
Maintainer-action: on your own machine, run the script below with a read-only GitHub token in GITHUB_MCP_TOKEN, and post its stdout as a comment on this card — done when a comment on this card carries the script's JSON with a toolsSnapshot digest for both search_issues and issue_read
维护者速读
- 要你做什么:在自己电脑上跑下面这段脚本(Node 18 以上,不用装依赖,不用拉仓库),把它输出的 JSON 原样贴到本卡评论里。
- 为什么要你做:connector-github: ship
providerConfig.toolsSnapshotforsearch_issuesandissue_read, digests measured against GitHub's live MCP server, so the listing's sha256 is the pin set (ruling 6556740185, #28590 B) #28647 要给 GitHub 连接器锁定两个工具,锁定值必须对着 GitHub 的真实 MCP 服务器测出来。云端席位的网络连不上api.githubcopilot.com,也没有测量用的 token,按规定不能填假值。 - token:一个只读的 GitHub 个人访问 token,能读 issue 即可(fine-grained:Issues 和 Metadata 设为 Read)。只放在本机环境变量里,⛔ 不要贴到卡上或聊天里。脚本不会打印它,输出里也没有它。
- 输出里有什么:测量时间、服务器地址、两个工具各自的 sha256,以及这两个工具的公开定义(名称、描述、参数 schema),没有任何密钥。
- 跑失败时:退出码 3 是服务器拒绝了(401 一般是 token 不对);退出码 4 是 token 权限不够,看不到某个工具,换一个能读 issue 的 token 再跑。
- 之后:总监席核验评论后摘掉本卡的
pm:awaiting-maintainer并关卡,epic 席位随即派发 connector-github: shipproviderConfig.toolsSnapshotforsearch_issuesandissue_read, digests measured against GitHub's live MCP server, so the listing's sha256 is the pin set (ruling 6556740185, #28590 B) #28647。开发会在容器里用仓库自带的computeMcpToolsSnapshot对你贴的工具定义重算一遍,两边一致才提交。
The script
Save as measure-github-mcp-pins.mjs, then run GITHUB_MCP_TOKEN=<token> node measure-github-mcp-pins.mjs. Its canonical JSON and digest are the ones packages/connectors/connector-mcp/src/tool-snapshot.ts defines. The seat checked the script against a local mock MCP server (SSE answers, session header, a paged tools/list, a 401 on a wrong token), and computeMcpToolsSnapshot reproduced its digests exactly.
// Measure the toolsSnapshot pins of packages/apps/connector-github (#28647).
// Node >= 18, no dependencies. Reads the token from GITHUB_MCP_TOKEN and never prints it.
import { createHash } from 'node:crypto';
const URL_ = process.env.MCP_URL ?? 'https://api.githubcopilot.com/mcp/';
const INCLUDE = ['search_issues', 'issue_read'];
const token = process.env.GITHUB_MCP_TOKEN;
if (!token) { console.error('GITHUB_MCP_TOKEN is not set'); process.exit(2); }
// The same canonical JSON and digest as packages/connectors/connector-mcp/src/tool-snapshot.ts.
const canonicalJson = (v) => {
if (Array.isArray(v)) return `[${v.map((e) => (e === undefined ? 'null' : canonicalJson(e))).join(',')}]`;
if (v !== null && typeof v === 'object') {
return `{${Object.keys(v).filter((k) => v[k] !== undefined).sort()
.map((k) => `${JSON.stringify(k)}:${canonicalJson(v[k])}`).join(',')}}`;
}
return JSON.stringify(v);
};
const digest = (t) => createHash('sha256')
.update(canonicalJson({ name: t.name, description: t.description, inputSchema: t.inputSchema }), 'utf8')
.digest('hex');
let sessionId; let nextId = 1;
async function rpc(method, params, notify = false) {
const headers = {
'content-type': 'application/json',
accept: 'application/json, text/event-stream',
authorization: `Bearer ${token}`,
'x-mcp-readonly': 'true',
};
if (sessionId) headers['mcp-session-id'] = sessionId;
const id = notify ? undefined : nextId++;
const res = await fetch(URL_, { method: 'POST', headers, body: JSON.stringify({ jsonrpc: '2.0', method, params, ...(notify ? {} : { id }) }) });
sessionId = res.headers.get('mcp-session-id') ?? sessionId;
if (notify) return undefined;
if (!res.ok) { console.error(`${method}: HTTP ${res.status}`); process.exit(3); }
const text = await res.text();
const bodies = (res.headers.get('content-type') ?? '').includes('text/event-stream')
? text.split(/\r?\n/).filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()).filter(Boolean)
: [text];
const msg = bodies.map((b) => JSON.parse(b)).find((m) => m.id === id);
if (!msg) { console.error(`${method}: no response with id ${id}`); process.exit(3); }
if (msg.error) { console.error(`${method}: ${JSON.stringify(msg.error)}`); process.exit(3); }
return msg.result;
}
await rpc('initialize', { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'objectstack-pin-measure', version: '1' } });
await rpc('notifications/initialized', undefined, true);
const tools = []; let cursor;
do { const r = await rpc('tools/list', cursor ? { cursor } : {}); tools.push(...(r.tools ?? [])); cursor = r.nextCursor; } while (cursor);
const picked = INCLUDE.map((n) => tools.find((t) => t.name === n));
const missing = INCLUDE.filter((n, i) => !picked[i]);
if (missing.length) { console.error(`not listed for this token: ${missing.join(', ')} (served ${tools.length} tools) — the token is under-scoped`); process.exit(4); }
console.log(JSON.stringify({
measuredAt: new Date().toISOString(),
url: URL_,
headers: { 'X-MCP-Readonly': 'true' },
toolsSnapshot: Object.fromEntries(picked.map((t) => [t.name, digest(t)])),
tools: picked.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })),
}, null, 2));Business reason
#27293's Done-when gives each shelf listing a verifiable upstream, commit and sha256. Ruling 6556740185 (#28590 B) makes the pin set the sha256 of an MCP app. The GitHub app is the last E4 card left, and the measurement is the only part of it that needs the live server. Since 2026-11-01 the cloud environment has neither the network route nor a measurement token (#28647 comment 6557674668). Without this step, the epic cannot close.
Reader
- The maintainer runs the script and posts the output.
- The director seat checks the comment, then removes
pm:awaiting-maintainerand closes this card with an evidence comment, in the same stroke. - The epic seat then dispatches connector-github: ship
providerConfig.toolsSnapshotforsearch_issuesandissue_read, digests measured against GitHub's live MCP server, so the listing's sha256 is the pin set (ruling 6556740185, #28590 B) #28647 (Blocked-by:this card). That dev commits the posted digests, recomputes them offline withcomputeMcpToolsSnapshotover the postedtools, and adds the posted definitions as a test fixture. No live call is made in CI.
Dedupe
Searched open and closed issues on 2026-11-04T14:55Z: toolsSnapshot 12 hits, GITHUB_MCP_TOKEN 3 hits, connector-github pins 21 hits. None asks for an off-cloud measurement. The nearest is #28647, which this card unblocks. Control connector: 768 hits. New.
Activity