Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesPull RequestsActionsSecurityInsightsSettings
✨ AI
More
Blame · Line-by-line history

mcp-private-read.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

mcp-private-read.test.tsBlame73 lines · 1 contributor
85fc0efccantynz-alt1/**
2 * Regression: the v1 "repo:*" READ tools must NOT be hardcoded public-only.
3 *
4 * They used to reject EVERY private repo with
5 * `${owner}/${repo} is private; v1 MCP read tool is public-only`
6 * even for the authenticated owner. That meant a user who signed into the
7 * OAuth connector (e.g. Claude Desktop) could not read their own private
8 * repos at all — Vapron never showed up. Now they gate on the caller's
9 * actual access via resolveReadableRepo(): public, OR private + caller can
10 * read (owner / collaborator / org member).
11 *
12 * Two invariants are locked here without needing a live DB:
13 * 1. No read-tool description still advertises "public-only".
14 * 2. Privacy contract: an unknown repo throws METHOD_NOT_FOUND (the same
15 * error a private repo the caller can't see would throw — we never
16 * confirm a private repo's existence).
17 */
18import { describe, it, expect } from "bun:test";
19import { ERR_METHOD_NOT_FOUND, McpError } from "../lib/mcp";
20import { defaultTools } from "../lib/mcp-tools";
21
22const HAS_DB = Boolean(process.env.DATABASE_URL);
23const anonCtx = { userId: null, scopes: [] };
24
25const READ_TOOLS = [
26 "gluecron_repo_read_file",
27 "gluecron_repo_list_issues",
28 "gluecron_repo_explain_codebase",
29 "gluecron_repo_health",
30 "gluecron_repo_search",
31];
32
33describe("v1 MCP read tools are no longer public-only", () => {
34 it("no read-tool description advertises 'public-only'", () => {
35 const tools = defaultTools();
36 for (const name of READ_TOOLS) {
37 const handler = tools[name];
38 expect(handler).toBeDefined();
39 expect(handler.tool.description.toLowerCase()).not.toContain("public-only");
40 }
41 });
42
43 it.skipIf(!HAS_DB)(
44 "unknown repo → METHOD_NOT_FOUND (privacy contract), for the read tools that resolve a specific repo",
45 async () => {
46 const tools = defaultTools();
47 const cases: Array<{ name: string; args: Record<string, unknown> }> = [
48 {
49 name: "gluecron_repo_read_file",
50 args: { owner: "nobody-xyz-fixture", repo: "nope", path: "README.md" },
51 },
52 {
53 name: "gluecron_repo_list_issues",
54 args: { owner: "nobody-xyz-fixture", repo: "nope" },
55 },
56 {
57 name: "gluecron_repo_explain_codebase",
58 args: { owner: "nobody-xyz-fixture", repo: "nope" },
59 },
60 {
61 name: "gluecron_repo_health",
62 args: { owner: "nobody-xyz-fixture", repo: "nope" },
63 },
64 ];
65 for (const tc of cases) {
66 const handler = tools[tc.name];
67 await expect(handler.run(tc.args, anonCtx)).rejects.toMatchObject({
68 code: ERR_METHOD_NOT_FOUND,
69 });
70 }
71 }
72 );
73});