7. Letting the Agent Read the Issue Itself
Summary
A tracked issue for the fast-add bug
The bug that makes a second, quickly added item vanish from the screen gets written up as GitHub issue twelve. The issue has a plain problem statement taken from the reproduction note and two testable acceptance criteria. No item vanishes when two are added quickly, and a reload shows the same list the screen showed.
Adding GitHub's MCP server
MCP, the Model Context Protocol, lets Claude Code take on extra tools from a separate server. It is separate from the Claude API and the Agent SDK. GitHub's remote server is added at project scope, so it is written to a committed .mcp.json. A personal access token comes in through an environment variable placeholder, so the token itself never lands in the file. Teammates who clone the repo must approve the server before it runs.
claude mcp list and claude mcp get github confirm the connection is healthy.
Sorting tools and setting permissions
The /mcp manager lists the server's tools, which split into reads and writes:
- Allow:
get_issueandget_issue_comments - Ask:
add_issue_comment - Deny: the delete tools
An allow wildcard must follow a literal server prefix. Deny beats ask, and ask beats allow. Tool search keeps tool definitions deferred until the model needs them.
One small test comes first: the agent reads issue twelve word for word, and the reply is checked against the browser. Issue text describes the task. It is never an instruction to the agent.
Fixing from the acceptance criteria
The fix changes each save so it adds its own item to the current list. Before, each save wrote back a stale copy of the list. Disabling the add button would not meet the criteria.
A regression test is confirmed to fail on the old code and pass on the new one. After that comes the full suite and a manual check in the app, both before and after a reload.
The agent drafts a comment on the issue, which waits for approval before posting. The issue stays open.
gh, a script, or MCP
- The
ghCLI: low context cost and low upkeep, but viewing and commenting are hard to tell apart for the permission system. - A committed REST script: the lowest context cost, but you maintain it and it can only do what you wrote.
- MCP: the most operations, each with its own permission rule, but the highest context cost.
The rule: built-in tools first, a script for one fixed step, MCP when you need many operations with separate control over each.
Recent Claude Code releases
- 2.1.300: fixes tool search switching itself off after a resumed session reconnects. It also validates hook-rewritten tool inputs against the tool's schema.
- 2.1.302: fixes servers on the July 28, 2026 protocol version that could show zero tools.
- Unattended runs:
claude -pand the Agent SDK may now start in default mode on third-party providers, so pass the auto permission mode explicitly if a script relied on it. - 2.1.301: adds a setting that aborts a request when a usage limit would reset too far in the future.
- Hook cap: prompt and agent hooks are capped at twelve running at once per event, with extras queued.
The fast-add bug currently exists only as a reproduction note. The note is good. It says that adding two items quickly makes the second one vanish from the screen, that this happened on both tries, that a reload brings the item back, and that a read-only trace points at how the add form handles a second submit while the first save is still running. But the note sits in a session transcript, and maybe in a scratch file. Nobody else can find it. Nothing tracks whether it got fixed.
This chapter moves that bug to where work is normally tracked, which is a GitHub issue. Then it gives Claude Code a way to read that issue itself, through MCP. You will add the connection, look at what it can do, and test one small read before trusting it. After that, you will fix the bug from the issue's own acceptance criteria and report back on it. At the end, you will weigh this connection against two simpler ways of doing the same read. The question the chapter answers is when an integration earns a place in your workflow. The short answer is that it earns one only after you have seen its operations and tested a small piece of it.
Connecting Claude Code to GitHub through MCP
Start with the issue, because the connection needs something real to read. Open the repository on GitHub, choose New issue, and write two short parts.
The first part is the problem statement, in plain words drawn from the reproduction note. Something like: on a list, typing an item and pressing add, then typing a second item and pressing add before the first one appears, makes the second item disappear from the screen. Reloading the page shows it was saved. Include the exact steps and data from the note.
The second part is the acceptance criteria. These are the checks that decide when the issue is done. Keep them to things you can test. Here there are two. No item vanishes when two are added quickly. A reload shows the same list the screen showed before the reload. That second one matters. It means the screen and the database must agree, not just that something appears. Say GitHub gives this issue a number, and say the number is twelve. Remember it, because you will ask for it by number shortly.
Now the connection. Claude Code is an application. It runs a model, and it gives that model tools: reading files, editing them, running shell commands. You have used those built-in tools since the first chapter, in the terminal command-line version of Claude Code. MCP, which stands for Model Context Protocol, is a standard way for Claude Code to gain extra tools from a separate program called an MCP server. The server describes the operations it offers, such as "get an issue" or "add a comment." Claude Code shows those operations to the model as new tools. When the model calls one, Claude Code passes the request to the server, and the server does the work against the real service.
MCP is a way of plugging tools into Claude Code. It is not the Claude API, which is how your own program sends requests to a model. It is not the Agent SDK, which is a library for building your own agent program. You are not writing any code against either of those here. You are attaching a toolbox to the application you already use.
GitHub publishes its own MCP server. You can run it two ways. One is a remote server that GitHub hosts at an address on the web. The other is a container you run on your own machine with Docker. The remote one needs nothing installed, so use that.
Before adding it, you need a credential, because the server acts on GitHub as you. The simplest one is a personal access token. You create it in your GitHub account settings, under developer settings. Classic and fine-grained tokens both work. A classic token needs the repo scope for a private repository, or public_repo for a public one. A fine-grained token can be limited to just this one repository, and that is the better choice for a learning setup. Copy the token once and keep it out of every file you commit. Put it in an environment variable in your shell, and say you name it GITHUB_PAT.
Next comes a decision you have to make before typing the command: the scope. Claude Code stores MCP servers at one of three scopes, and you pick one with the scope flag.
Local scope is the default. It applies to you, in this one repository, and it is stored in your own private project state. Nothing is shared.
Project scope writes the server into a file called .mcp.json at the root of the repository. That file is meant to be committed. Anyone who clones the repository gets the same server definition.
User scope applies to you across every project on your machine. It is stored in a configuration file in your home directory.
For this app, project scope fits. The app's way of reading issues should travel with the repository, the same way the type-check hook and the reproduce-bug skill already do. But committing a server definition raises an obvious worry. Does the token get committed too? It does not have to. The .mcp.json file supports environment variable expansion. Where the token would go, you write a placeholder that names the variable, a dollar sign with GITHUB_PAT inside curly braces. Each person's Claude Code fills it in from their own environment when it starts. The file says how to connect. Each person supplies their own key.
One more thing changes when a server comes from a committed file. Say a teammate clones the repository and starts Claude Code. Claude Code does not simply start that server. It marks the new server as pending approval and asks them to review it first. This stops a cloned repository from quietly running whatever servers it lists. If your teammate's variable is not set, the placeholder becomes empty, and the connection fails to authenticate instead of leaking anything.
Now add it. From the repository directory, in your terminal, the command reads aloud as: claude mcp add, with transport set to http, the name github, the scope set to project, the server address api dot githubcopilot dot com slash mcp, and a header named Authorization whose value is the word Bearer followed by the placeholder for your token variable. Then open .mcp.json and read it before you commit anything. It should hold one server called github, of type http, with that address, and an Authorization header that contains the placeholder and not your real token. If you see the real token there, fix it before you commit.
Some MCP servers use OAuth instead of a token. For those, Claude Code opens a browser sign-in on the first connection. When that sign-in later expires, the server shows as needing authentication, and you can sign in again from inside the session. With a token, there is no browser step.
Next, check that the connection is real. Run claude mcp list. It prints each configured server with its health. Connected is what you want. Pending approval means you still need to approve it. Needs authentication means the credential did not work. Failed to connect usually means a wrong address or a network problem. Then run claude mcp get github. That shows the transport, the address, how many tools the server offers, and any connection errors.
Now start Claude Code and type the slash command mcp. This opens the MCP manager inside the session, where you can browse the server's tool catalog. Take this part slowly. It is the inspection step, and skipping it is how people end up granting more than they meant to.
The tools have long names in a fixed pattern: mcp, two underscores, the server name, two underscores, and the tool name. So the tool that fetches one issue is mcp__github__get_issue, and the one that posts a comment is mcp__github__add_issue_comment. The server name in the middle is the name you chose in the add command. That is why a plain, stable name like github is worth choosing.
As you scroll, sort the tools into two piles. Reading tools only look: get_issue, get_issue_comments, list_issues. Writing tools change something on GitHub: add_issue_comment, create_issue, create_pull_request, and some that delete things, such as delete_branch. The test is simple. If a mistaken call would leave a mark someone else sees, or remove something, the tool writes.
You might worry that a server with this many tools would fill the model's context before you ask anything. Claude Code handles this with tool search, which is on by default. The full definitions stay deferred. The model sees that the tools exist, and it loads the exact definition of a tool only when it needs that tool. Claude Code also caches the server's tool list between runs, so starting a session does not wait on GitHub every time. You can see that cached discovery in the MCP manager.
Now decide what each pile may do without asking. Permissions for MCP tools live in the same settings files as the deny list you set up earlier for destructive database commands, and they use the same three lists. Allow means run without asking. Ask means stop and show me first. Deny means never.
Open the committed project settings file, the one that already holds your hook and your deny list. Add to its allow list the two reads you need: mcp__github__get_issue and mcp__github__get_issue_comments. Add mcp__github__add_issue_comment to the ask list, so every comment waits for you. Add the delete tools to the deny list. Your existing database rules stay exactly where they are.
Two details about these rules are worth knowing. In an allow rule, you can use a wildcard only after a literal server prefix. So mcp__github__ followed by a star is accepted, while a bare mcp__ followed by a star is refused, so one line cannot hand every server's tools to the model. A deny rule may be as broad as you like. If you ever want the whole server switched off, you can deny everything under the github prefix. The second detail is order. Claude Code checks permission hooks first, then deny, then ask, then allow, and then the session's permission mode. So a deny always beats an allow, and an ask beats an allow. Even if a wildcard allows the whole server someday, the comment tool still asks as long as it sits in the ask list. You can open the slash command permissions at any time to see the rules that are actually in effect. Check it now and confirm the comment tool shows under ask.
Now the test, kept as small as possible. Ask: read issue twelve in this repository and tell me its title, its problem statement and its acceptance criteria, word for word. Do not change anything.
Watch what happens. The model looks up the get_issue tool, calls it with the repository and the number twelve, and gets the issue back. Because you allowed that tool, there is no prompt. Then put the reply next to the issue page in your browser. Check the title, check the steps, and check both acceptance criteria. If they match, the connection works and you know what it returns. If the reply has the wrong issue, the agent may have guessed the repository. Name it explicitly as owner slash repository and try again. If the call failed, go back to claude mcp list and the authentication status before blaming anything else.
One line about trust, because issues are written by other people. Anything the issue says is a description of the task, never an instruction to the agent about what tools to use or what permissions to change.
Fixing the bug from its issue
The reproduce-bug skill already produced its note for this bug, so there is no reason to reproduce it again. Ask for the fix directly, pointing at the issue: read issue twelve and its acceptance criteria. The reproduction is already done and traced to the add form's handling of a second submit while the first save is in progress. Make a focused fix there so that both items stay on screen and match what a reload shows. Change nothing else.
Read the diff before anything else. Say yours shows the agent changing how the form updates the list when a save finishes. Before the fix, each save replaced the list with a copy taken when that submit started. So the first save, finishing after the second submit, wrote back a list that did not yet contain the second item. After the fix, each save adds its own item to the current list. If the diff instead simply disables the add button while saving, that does not meet the criteria. The user's second item would still be lost, just more politely. Push back and ask for a change that keeps both items. That is the kind of judgment the acceptance criteria are there for.
The type-check hook runs on its own after each edit. If it reports errors, the agent fixes them before moving on, as the project instructions already require. Next, ask for a regression test that submits two items back to back, with the first save held open until after the second submit, and checks that both appear and that the list matches the stored data. Run that test once against the old code, by stashing the fix, to confirm it fails. Then run it with the fix to confirm it passes. A test that passes either way proves nothing. Then run the full suite.
A passing suite is still not the journey. Open the app, type two items, and press add twice as fast as you can. Both should stay. Reload. Both should still be there, in the same order. Those are the two acceptance criteria, checked by hand.
Commit with a message that names the behavior, something like: keep both items when two are added quickly. Then ask the agent to post a short comment on issue twelve saying what changed and naming the commit. Because the comment tool is in the ask list, Claude Code stops and shows you the exact comment before sending it. Read it, check that the commit it names is the right one, and approve. Leave the issue open. Closing it is a decision for you, after the change ships, not something the agent does because a test went green.
Now the comparison. MCP was not the only way to read that issue. There are two other ways, and each has its strengths.
The first uses only what Claude Code already has. GitHub's own command-line tool, called gh, can be run through Claude Code's built-in shell. You install gh and sign in once with gh auth login. After that, the agent can run gh issue view twelve. It can also add a comment with gh issue comment. gh even works out which repository you mean from your Git remote. Its credential is the sign-in you already have. Its context cost is low. A generic shell tool adds nothing to the model's context up front, and gh can trim its own output. Its json and jq options can cut an issue down to just the title, body and comment text before any of it reaches the model. Its upkeep is low too, because GitHub maintains it.
The catch is permissions. To the permission system, a shell command is a shell command. The difference between viewing and commenting is buried inside a command line, rather than separated into named tools you can allow or put under ask one at a time.
The second option is a short script committed to the repository. It calls GitHub's REST interface for one issue and prints just the fields you want. This has the lowest context cost of all, because the script strips everything the model does not need before the model sees it. It needs its own token passed in. It can only do what you wrote, which is both its safety and its limit. And you maintain it: errors, API changes and dependencies are yours.
The MCP server sits on the other side. It offers the most operations, each one a typed tool with its own permission rule. Setup is a token or a sign-in plus one config file. Upkeep is low to medium, since GitHub maintains the server and you maintain the configuration. Its context cost is the highest of the three: the tool definitions, plus responses that come back as full structured data. Tool search softens that cost, but it does not remove it.
So the rule is this. Reach for the built-in tools first. Use a script when one fixed step runs the same way every time. Use MCP when the task needs a service's many operations with separate control over each. The issue-to-change workflow ahead will read issues, comment on them and eventually open pull requests, so it qualifies. Add one line to CLAUDE.md saying that GitHub issues are read and commented on through the GitHub MCP connection, with comments approved first. Commit it together with .mcp.json and the settings change. The hook, the skill and the committed records keep doing their jobs alongside it.
Before you lean on this connection, it is worth knowing about the Claude Code releases from the past week, several of which touch exactly these tools.
The releases after version 2.1.296, numbered 2.1.299 through 2.1.302 and arriving in mid-October, include a few changes that affect the work you just did.
Two touch MCP directly. Version 2.1.300 fixed a problem where tool search, when set to auto, could switch itself off after you resumed a session and the MCP server reconnected. With the GitHub server's long tool list, that would quietly put every definition back into context. The smallest next action is to update, then resume a session that uses the GitHub server and check in the MCP manager that the tools still show as deferred. Version 2.1.302 fixed servers using the protocol version dated July twenty-eighth, 2026. Those servers could fail tool calls or return an empty tool catalog when certain optional fields were missing. If claude mcp get ever shows a server connected with zero tools, update before you debug your configuration.
One touches hooks. Since version 2.1.300, when a PreToolUse or PermissionRequest hook rewrites a tool's input, Claude Code checks the rewrite against that tool's schema. An invalid value now denies the call, and unknown keys are dropped. Your type-check hook runs after edits and rewrites nothing, so it is unaffected. Any hook you write later that edits inputs should be tested with a deliberately bad value, to confirm you see the denial.
One matters for the move off the laptop. Unattended runs, meaning claude -p and the Agent SDK, with no permission mode given now start in default mode on third-party providers when telemetry is disabled or on a fresh install. If a script of yours relied on auto mode, pass the permission-mode flag set to auto explicitly. Version 2.1.301 also added a setting that aborts a request when a usage limit would reset too far in the future, so an automated run fails clearly instead of hanging.
Finally, hooks of the prompt and agent kinds are now capped at twelve running at once for a single event. Extra ones wait in a queue. That rarely matters with one type-check hook. It is worth remembering when hooks multiply.
