Every tool the GitHub MCP server offered was sitting right there in the list β all 26 were recognized correctly. Call any one of them and it failed in under 1 second, every time, with one line on screen: Tool execution failed
. No reason attached. Even reading a fully public repository β something that needs no permission at all β failed the exact same way.
TL;DR: my AI assistant's first guess β "maybe the token's gone bad" β was right the whole time. I ran an experiment that looked like it ruled that out, filed the token away as cleared, and spent the next few hours on a different trail. The experiment couldn't have ruled it out. I just misread what a failed test actually proved.
Everything below was measured on one machine: Windows 11, Claude Desktop 1.28929.0, on 2026-08-12. Versions move fast and behavior changes between them β read this as a dated data point, not a permanent description of the product.
tools/list
for the GitHub MCP server correctly returned all Tool execution failed
. No code, no detail, nothing to search for.If the tools simply hadn't shown up, this would be a short investigation. What made it strange was that they were all visible and all still failed.
I described the symptom to my AI assistant, and its first suggestion was ordinary: maybe the token has gone bad. Reasonable enough to check first. So I checked it β three different ways, in the wrong order.
Full permissions, redone from scratch. 3 times. The symptom didn't move by a millimeter.
This felt like the deciding test. If the problem were insufficient scope on the token, a repository anyone can read without authenticating should still work fine.
It didn't. Same Tool execution failed
.
At that point I concluded the problem wasn't permissions-related, and folded "the token has gone bad" into that same conclusion β discarded, both at once, in one motion. That fold was the mistake, and I'll come back to exactly why.
GitHub's tools disappeared from the list entirely on the web. That told me something true and useful β the GitHub connector is Desktop-local configuration, not something shared across clients. It told me nothing about the actual cause. I didn't know that yet, so I took it as forward progress and started searching for a matching known issue instead.
Before writing any of this up, I went back over what I was about to claim and checked it piece by piece. That's when I noticed something I hadn't done once in several hours of troubleshooting: open my own mcp.log
and look at whether tools/call
was actually leaving the client.
Windows keeps it at %APPDATA%\Claude\logs\
β a mcp.log
, plus a mcp-server-<name>.log
per server. macOS keeps the equivalent under ~/Library/Logs/Claude/
. Opening it surfaced what the UI had been swallowing:
2026-08-12T02:58:07.223Z [info] [github] Message from client: method="tools/list" id=1 params
2026-08-12T02:58:07.242Z [info] [github] Message from server: id=1 result
2026-08-12T07:06:06.486Z [info] [github] Message from client: method="tools/call" id=2 params
2026-08-12T07:06:06.816Z [info] [github] Message from server: id=2 error(code=-32603)
2026-08-12T07:06:11.249Z [info] [github] Message from client: method="tools/call" id=3 params
2026-08-12T07:06:11.482Z [info] [github] Message from server: id=3 error(code=-32603)
Three things came out of that immediately:
tools/call
Message from client
).Message from server
) β not hanging, not timing out.-32603
β JSON-RPC 2.0's generic So this was never "the call vanishes somewhere." The server was doing work and returning an error. One look at the log said more than the previous few hours of repeating the same fix had.
By that point I'd found two open issues on anthropics/claude-code
that looked close enough to be worth writing up as the cause:
| Issue | What it reports |
|---|---|
tools/call
ever firesBoth were still open on the day I checked. #79986's own title is specific about the count that matters: zero tools/call
. Mine wasn't zero β it was 11 calls, all through the same log window. The surface symptom rhymed ("tools are visible, calls fail"), but the layer where it actually broke was the opposite of what #79986 describes. Writing this up as the same root cause would have been wrong.
One thread on #80094 speculates that Desktop refuses to dispatch tools whose schema declares outputSchema
/structuredContent
β worth flagging that this is the reporter's theory, not something Anthropic confirmed. It also doesn't apply here regardless: out of my 26 tools, 0 were found to have declared an outputSchema
.
-32603
only says "internal," so I went and got the actual content. I ran the same MCP server Desktop was running β same command, same environment variables β directly over stdio, and sent it one tools/call
:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32603,
"message": "Authentication Failed: Bad credentials"
}
}
An authentication error. So I took the same token straight to the GitHub API, no MCP layer involved:
GET https://api.github.com/user -> 401 Bad credentials
GET https://api.github.com/repos/modelcontextprotocol/servers -> 401 Bad credentials
The second line is the one that matters. modelcontextprotocol/servers
is a fully public repository β anyone can read it with no credentials at all. It still came back 401
.
I'd been treating "the token doesn't have the right permissions" as one hypothesis. It's really two, and a public-repo test tells them apart very differently:
| Hypothesis | Result on a public repo |
|---|---|
Token's scope is too narrow (missing repo , etc.) |
|
| Dies. Insufficient scope still reads public data fine | |
| Token itself is invalid (whatever the reason) | |
Survives. A broken Authorization header gets 401 even on public resources |
Anonymous, no-header requests can read public repos all day. That's not what I sent. I sent a request carrying a token β a bad one β and GitHub answers a bad token with 401
regardless of whether the resource behind it needed a token in the first place.
An experiment can only kill one hypothesis at a time. "Still failed on a public repo" killed the scope hypothesis. It said nothing about the invalid-token hypothesis, which was still standing the entire time. I filed both under "not a permissions problem" and moved on.
I thought I'd disproven the hypothesis. What I was actually looking at was a result the hypothesis predicted.
What I redid, three times, was the GitHub App / connector authorization. What the server was actually reading was a Personal Access Token hardcoded into claude_desktop_config.json:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." }
}
}
}
Those are two separate credentials. No amount of re-authorizing the App reaches into that config file and touches the PAT sitting in env
. Three attempts, zero change in symptom β that flat result wasn't "not quite enough yet," it was "wrong target" from the start. A third identical retry almost never teaches you anything a first one didn't.
Desktop β web losing the GitHub tools was real, and it correctly told me the connector was local Desktop configuration. It just had nothing to do with why calls were failing β that was authentication, unrelated to which client I had open. The search space genuinely got smaller. That's not the same as getting closer to the answer, and the feeling that it was is exactly what sent me toward known-issue-hunting instead of my own logs.
The piece I was missing wasn't a debugging technique, it was a question to ask before running the experiment, not after:
If this experiment fails, which hypothesis does that kill β and which one survives?
"What happens if this hypothesis is true" isn't enough on its own. It misses exactly the case I hit here, where the failure of the experiment was itself evidence for the hypothesis I thought I'd just ruled out.
The other half of it: an AI assistant will not stop you from repeating the same fix. "Let's try re-authorizing with full permissions again" is available as a suggestion an unlimited number of times. Capping the repeat count is a job only the human in the loop actually does.
Tool execution failed
? Open the log before touching anything.mcp.log
plus mcp-server-<name>.log
tell you in one read whether the break is clientβserver or serverβclient β that alone would have saved the first hour here.curl -H "Authorization: Bearer $TOKEN" https://api.github.com/user
settles it in one request. Every layer you go through instead is one more place to misread the result.@modelcontextprotocol/server-github
is deprecated on npm β "Package no longer supported. Contact Support at gh
.Rotating the token would have brought GitHub's tools back on its own. It wouldn't have addressed the actual design problem: as long as a long-lived PAT sits in a config file in plaintext, this happens again the next time it lapses. So I switched servers instead of just rotating the secret.
One thing in GitHub's own docs is easy to miss here. Claude Desktop can't use GitHub's remote MCP server at all:
However, the GitHub remote MCP server requires OAuth authentication through a registered GitHub App (or OAuth App), which is not currently supported. Use the local Docker setup instead.
β[github/github-mcp-server, Claude installation guide]
Other clients can reach https://api.githubcopilot.com/mcp/
directly; Desktop can't, which is an easy assumption to carry over by mistake. The local stdio server is the supported path.
The official release binaries β and the official Docker image β ship with a GitHub OAuth app already registered and baked in, so they start up with no PAT at all. The first tool call triggers a browser-based authorization, and the token that comes back is kept in memory only, never written to disk. The docs recommend the native binary specifically, which picks a random local callback port and opens your browser automatically.
{
"mcpServers": {
"github": {
"command": "<path to the github-mcp-server binary>",
"args": ["stdio"]
}
}
}
The config file no longer holds a secret at all. That's the part of this that actually matters going forward β nothing left in the file to go bad.
In the interest of not overstating this:
tools/call
at all. Whether this used to work and broke, or this was simply the first time it was ever exercised, isn't something this record can answer.Squirrel-Update.log
). The timing is close, but since the authentication error reproduces regardless, that's not a basis for tying the update to the failure. "There was an update right before" isn't evidence of a cause.Doing the same thing three times isn't verification. When you think you've disproven something, say out loud what you actually disproved before you act on it. And once you're in the mode of crossing hypotheses off a list, check your own arithmetic on each cross-off β don't just trust the motion of crossing things off.
Half a day of investigation brought me back to what the AI told me after just 1 minute. What actually went wrong wasn't the hypothesis. It was the experiment I ran to try to kill a hypothesis that happened to be correct.
The two open issues referenced above are anthropics/claude-code#79986 and anthropics/claude-code#80094. The GitHub MCP server docs cited here are the Claude installation guide and the OAuth login guide.
Sho Naka (nomurasan). Originally investigated and written up in Japanese; this English version is an AI-assisted rewrite, not a translation β the logs, the commands, and the conclusions are mine.