Commit0e06362
fix(mcp): read from the repo's real default branch, not a hardcoded "main"
fix(mcp): read from the repo's real default branch, not a hardcoded "main" THE actual reason Vapron wouldn't read in Claude Desktop. Server-side auth, case-insensitive repo lookup, and owner access all verified working on prod — but gluecron_repo_read_file defaulted ref to the literal string "main", and Vapron's default branch is "Main" (capital M). Git refs are case-sensitive, so every read without an explicit ref hit refs/heads/main (which doesn't exist) -> "path not found" -> Desktop reports it can't access the repo. Verified on the box: reading @Main returns package.json, @main 404s; HEAD -> refs/heads/Main; no lowercase main branch exists. Fix: resolveReadRef() picks the ref the caller meant — no ref given uses the repo's real default branch (git HEAD via getDefaultBranch), a given ref that doesn't resolve falls back to a case-insensitive branch match (main -> Main) then the default branch. Schema/description updated to say the default is the repo's actual default branch, which may not be "main". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 file changed+34−30e063622a8127fcced54b1a9943f315726bc787d
1 changed file+34−3
Modifiedsrc/lib/mcp-tools.ts+34−3View fileUnifiedSplit
@@ -26,7 +26,7 @@ import {
2626 users,
2727 codebaseExplanations,
2828} from "../db/schema";
29import { getBlob, repoExists, resolveRef, getRepoPath } from "../git/repository";
29import { getBlob, repoExists, resolveRef, getRepoPath, getDefaultBranch, listBranches } from "../git/repository";
3030import { computeHealthScore } from "./intelligence";
3131import { McpError, ERR_INVALID_PARAMS, ERR_METHOD_NOT_FOUND } from "./mcp";
3232import type { McpContext } from "./mcp";
@@ -164,6 +164,30 @@ async function resolveReadableRepo(
164164 return r;
165165}
166166
167/**
168 * Pick the ref to read from. Git refs are case-sensitive and a repo's default
169 * branch is not always "main" (e.g. Vapron's is "Main"). So:
170 * - no ref requested -> the repo's real default branch (git HEAD)
171 * - requested ref resolves as-is -> use it
172 * - otherwise -> a case-insensitive branch match (main -> Main), else the
173 * default branch, else the original ref (so getBlob reports the honest 404)
174 */
175async function resolveReadRef(
176 owner: string,
177 name: string,
178 requested: string
179): Promise<string> {
180 const def = (await getDefaultBranch(owner, name)) || "main";
181 if (!requested) return def;
182 if (await resolveRef(owner, name, requested)) return requested;
183 const branches = await listBranches(owner, name).catch(() => [] as string[]);
184 const ci = branches.find(
185 (b) => b.toLowerCase() === requested.toLowerCase()
186 );
187 if (ci) return ci;
188 return def;
189}
190
167191// ---------------------------------------------------------------------------
168192// gluecron_repo_search
169193// ---------------------------------------------------------------------------
@@ -241,7 +265,7 @@ const repoReadFile: McpToolHandler = {
241265 properties: {
242266 owner: { type: "string", description: "Repo owner username" },
243267 repo: { type: "string", description: "Repo name" },
244 ref: { type: "string", description: "Branch / tag / commit (default: main)" },
268 ref: { type: "string", description: "Branch / tag / commit. Defaults to the repository's actual default branch (which may not be 'main' — e.g. 'Main')." },
245269 path: { type: "string", description: "File path within the repo" },
246270 },
247271 required: ["owner", "repo", "path"],
@@ -250,13 +274,20 @@ const repoReadFile: McpToolHandler = {
250274 async run(args, ctx) {
251275 const owner = argString(args, "owner");
252276 const repo = argString(args, "repo");
253 const ref = argString(args, "ref", "main");
277 const refArg = argString(args, "ref", "");
254278 const path = argString(args, "path");
255279
256280 // Readable if public, or private + the authenticated caller has access.
257281 // Use the CANONICAL owner/name for the on-disk read (case-sensitive FS).
258282 const canon = await resolveReadableRepo(owner, repo, ctx);
259283
284 // Resolve the ref the caller actually meant. Git refs are case-sensitive
285 // and a repo's default branch is NOT always "main" (Vapron's is "Main"),
286 // so: (1) no ref given -> use the repo's real default branch from HEAD;
287 // (2) given ref doesn't resolve -> try a case-insensitive branch match
288 // before giving up. This stops "@main" 404ing on a "Main" repo.
289 const ref = await resolveReadRef(canon.owner, canon.name, refArg);
290
260291 const blob = await getBlob(canon.owner, canon.name, ref, path);
261292 if (!blob) {
262293 throw new McpError(
263294