# MCP in Claude Code

Connect GitHub's remote MCP server to ticket-service as a project-scoped entry in .mcp.json, with the token read from GITHUB_MCP_TOKEN and only read tools pre-approved.

Source: https://ai-sw-factory.mellicci.dev/fundamentals/mcp/claude-code

## At a glance

| | Claude Code |
|---|---|
| Term | MCP server, added with `claude mcp add` |
| Configured in | `.mcp.json` (project), `~/.claude.json` (local and user) |
| Loads / runs | Connects at session start; tool names are listed, schemas load on demand |
| Scope & precedence | Local, then project, then user, then plugin servers, then claude.ai connectors. The whole entry of the winner is used, never merged |

## Build the scenario

| Step | What you write or run | Where |
|---|---|---|
| 1 | Export the token: `export GITHUB_MCP_TOKEN=...` | Your shell, not the repo |
| 2 | Add the server at project scope | Terminal, `claude mcp add` |
| 3 | Commit the resulting file | `.mcp.json` |
| 4 | Pre-approve the read tools | `.claude/settings.json` |
| 5 | Start `claude`, approve the server, check `/mcp`, then ask for issue #42 | Claude Code session |

```bash
claude mcp add --transport http --scope project github \
  https://api.githubcopilot.com/mcp/ \
  --header 'Authorization: Bearer ${GITHUB_MCP_TOKEN}'
```

Single quotes stop your shell from expanding `${GITHUB_MCP_TOKEN}`, so the file keeps the reference. The command writes `.mcp.json`:

```json
{
  "mcpServers": {
    "github": {
      "type": "http",
      "url": "https://api.githubcopilot.com/mcp/",
      "headers": {
        "Authorization": "Bearer ${GITHUB_MCP_TOKEN}"
      }
    }
  }
}
```

`.claude/settings.json` (committed) pre-approves the read tools:

```json
{ "permissions": { "allow": ["mcp__github__get_*"] } }
```

**First use.** Claude Code asks you to approve a project-scoped server before it uses it, so a cloned repository can't start servers without consent. Run `/mcp` to approve later or to see the status; `connected` means it works, `failed` includes the HTTP status (for example 401). Then type `Implement issue #42`: each MCP tool call in the output is labeled with the server name, which shows the issue came from the server.

The documentation doesn't describe a read-only header or toolset in the GitHub URL, and doesn't list GitHub's tool names. The `get_*` rule follows the docs' own `mcp__github__get_*` example; read `/mcp` to see the real names and adjust. Tools the rule doesn't match still prompt.

## Specifics

### Config files and scopes

| Scope | Stored in | Shared | Flag |
|---|---|---|---|
| Local (default) | `~/.claude.json`, under the project's path | No | `--scope local` |
| Project | `.mcp.json` in the project root | Yes, via version control | `--scope project` |
| User | `~/.claude.json` | No, all your projects | `--scope user` |

Duplicates are matched by name across these three scopes; the order is local, project, user. "Local" MCP servers live in `~/.claude.json`, unlike `.claude/settings.local.json`, which holds general local settings. `claude mcp list`, `get`, `remove` and `reset-project-choices` manage entries.

### Local and remote servers

| Transport | Add with | Notes |
|---|---|---|
| HTTP | `claude mcp add --transport http <name> <url>` | Recommended for remote servers; `streamable-http` is an accepted alias in JSON |
| SSE | `--transport sse` | Deprecated; use HTTP where available |
| stdio | `claude mcp add <name> -- <command> [args]` | Local child process; `--` separates Claude's flags from the server's |
| WebSocket | `claude mcp add-json` or `.mcp.json` | Header-only auth; not accepted by `--transport` |

A JSON entry with a `url` but no `type` is read as stdio and fails, so always write `"type": "http"`.

### Authentication and secrets

| Method | How | Use when |
|---|---|---|
| OAuth 2.0 | `/mcp`, select the server, follow the browser flow (or `claude mcp login <name>`) | The server supports it; tokens are stored and refreshed for you |
| Static header | `--header "Authorization: Bearer ..."` or `headers` in JSON | Token-based servers such as this scenario |
| Dynamic header | `headersHelper` | Short-lived credentials generated at connect time |

Expansion in `.mcp.json`: `${VAR}` and `${VAR:-default}`, in `command`, `args`, `env`, `url` and `headers`. An unset variable without a default produces a warning and leaves the literal `${VAR}`. In a remote server's `url` and `headers`, credential variables such as `ANTHROPIC_API_KEY` and `NPM_TOKEN` read as empty; if the server answers 401, check that. The list gives examples only, so this page uses a variable name of its own, `GITHUB_MCP_TOKEN`, rather than a common one such as `GITHUB_TOKEN`.

### Tool allow and deny

| Rule | Matches |
|---|---|
| `mcp__github` | Every tool from the `github` server |
| `mcp__github__*` | The same, with wildcard syntax |
| `mcp__github__get_*` | Tools starting with `get_`; allow globs work only after a literal `mcp__<server>__` prefix |
| `mcp__github__<tool>` | One tool |
| `mcp__*` (deny or ask only) | Every MCP tool; an unanchored allow glob is skipped with a warning |

Rules go under `permissions.allow`, `ask` or `deny` in settings. A rule with parentheses, such as `mcp__github__get_issue(...)`, is skipped, so you can't match arguments; use `--disallowedTools` for that. Subagents and skills reference the same full names.

### Context cost and inspection

| Topic | Behavior |
|---|---|
| Tool search | On by default: only tool names and server instructions load at start; schemas load when needed |
| `ENABLE_TOOL_SEARCH` | `false` loads all upfront; `auto` or `auto:N` loads upfront while definitions fit under 10% (or N%) of the window |
| `alwaysLoad: true` | Per-server entry field that keeps that server's tools loaded at start |
| Descriptions | Each tool description and server instruction is truncated at 2,048 characters |
| Output | Warning above 10,000 tokens; default cap 25,000, raised with `MAX_MCP_OUTPUT_TOKENS`; larger results are saved to a file and referenced |
| Inspect | `/mcp` (status, tool count, sign-in, reconnect, disable), `claude mcp list` and `get <name>` |

Managed MCP: administrators can deploy a fixed set with `managed-mcp.json` (`/etc/claude-code/` on Linux), provide servers with `managedMcpServers`, or filter with `allowedMcpServers` and `deniedMcpServers`.

## Gotchas

- Committing `.mcp.json` shares the definition, not the approval: each teammate is prompted once, and approvals committed in a repository's own settings are ignored until the workspace is trusted. In `claude -p`, SDK and cloud sessions there is no prompt and project servers load without asking.
- `claude mcp add` doesn't validate the token. A missing `GITHUB_MCP_TOKEN` shows up later as a failed connection, or a literal `${GITHUB_MCP_TOKEN}` with a warning.
- The server uses your token's permissions. The documentation doesn't describe a read-only mode for this server, so scope a fine-grained token to the repositories and permissions you want; the allow rule only removes prompts and doesn't restrict the server.
- Tool output enters the context, including text written by other people in issues. Treat it as untrusted input, as described in the [security model](https://ai-sw-factory.mellicci.dev/fundamentals/security-model).
- If the same name exists in local and project scope, the local definition wins silently for you; `claude mcp list` warns when the endpoints differ.
