Home › Study guides › CCAR-F › Domain 2 › Lesson 2.4
CCAR-F · Domain 2 · 18% of the exam · Lesson 2.4 · 21 min read
Integrating MCP servers: scopes, secrets and resources
Project .mcp.json versus user ~/.claude.json, ${GITHUB_TOKEN} expansion so no secret is committed, all servers' tools at once, and resources as catalogues.
Written against task statement 2.4 of the official CCAR-F exam guide (Version 1.0, effective July 2026). An independent resource, not affiliated with Anthropic; the practice questions are written from scratch.
2.4.1 Why the built-in tools run out
Picture the developer productivity agent your team is building with the Claude Agent SDK. An engineer asks it to "fix the bug in ENG-4521 and open a pull request". The agent has the built-in tools Read, Write, Edit, Bash, Grep and Glob, so it can search the copy of the code on the engineer's laptop, change files and run the tests. But it cannot read the Jira ticket, and it cannot open the pull request (the proposed code change that colleagues review). Everything it knows about the bug is whatever the engineer pasted into the prompt.
The gap is not intelligence. It is reach. The built-in tools stop at the edge of the engineer's own machine. The ticket lives in Jira, the team's issue tracker; the pull request lives in GitHub. Until the agent can touch those systems, "fix ENG-4521" is really three tasks with a human copying text between them.
That is what an MCP server is for. The Model Context Protocol (MCP) is an open standard for packaging tools, and content to read, so that Claude Code and Agent SDK agents can discover and use them. An MCP server is a small program that speaks this protocol on behalf of one system. A Jira server offers tools such as search_issues and get_issue; a GitHub server offers tools such as list_issues and create_pull_request. Once connected, those tools sit beside Grep and Read, and the model chooses between all of them the same way: by reading their names and descriptions. The model only asks for a call. The server does the actual work in Jira or GitHub.
2.4.2 Where the configuration lives: project scope or user scope
Here is the first question every team hits: where do you write a server's configuration so that the right people get it? Claude Code answers with scopes. A scope is the place a server's definition is stored, and it decides two things at once: which projects the server loads in, and whether your teammates get it too.
Project scope is a file called .mcp.json in the root folder of the repository, the shared store of the team's code and its full history. You commit it: you record it in that history, so it travels with the code. Anyone who clones the repository, that is, downloads their own copy, gets the same servers under the same names. This is where the Jira and GitHub servers belong, because every engineer needs to read tickets and open pull requests. Both Claude Code and the team's Agent SDK agent read this file by default.
User scope lives in ~/.claude.json, a file in your home directory (that is what the ~ means) that Claude Code maintains for itself. A server defined there loads in every project on your machine and stays private to you; it never enters version control. This is the place for personal utilities and experiments: a server you are trying out for a week, or a tool nobody else has asked for.
Think of a shared kitchen. The knives everyone cooks with hang on the wall, where any cook can reach them; that is project scope. Your favourite peeler stays in your apron pocket and goes with you to every kitchen you work in; that is user scope. A new hire should never have to guess which colleague's pocket holds the knives.
The docs name a third scope, local scope, and it is the default when you add a server with claude mcp add and no --scope flag. It is also stored in ~/.claude.json, but filed under the current project's folder, so the server loads only in that project and stays private.
| Scope | Stored in | Who gets it, and where |
|---|---|---|
| Project | .mcp.json in the repository root, committed |
Everyone who clones the repository, in that project |
| User | ~/.claude.json in your home directory |
Only you, in every project on your machine |
Local (the claude mcp add default) |
~/.claude.json, under this project's folder |
Only you, in this one project |
Memorise the first two rows; they are the pair the exam contrasts. Recognise the third, because it explains a common surprise: a server added without --scope project never reaches your teammates.
2.4.3 Keeping the token out of the repository
Now look at the problem that committing .mcp.json creates. The GitHub server needs a personal access token, a password-like key that lets a program act on GitHub as you. The Jira server needs an API token of its own. Paste them into the file and you have committed two secrets to a repository that every engineer, every automated build and every backup can read. Deleting them later does not help, because version control keeps every earlier version of the file. The only real fix is to cancel both tokens and issue new ones.
The better answer is environment variable expansion. An environment variable is a named value set on one machine, outside any file, such as GITHUB_TOKEN. Inside .mcp.json, you write ${GITHUB_TOKEN} where the token would go. When Claude Code loads the file, it replaces that reference with the value of GITHUB_TOKEN on the machine it is running on. The committed file holds a placeholder; each engineer's machine holds the real value.
It works like a form letter with a blank in it. The letter is printed once and shared; each person fills in the blank when they use it. Precisely: ${VAR} expands to the value of the variable VAR, and ${VAR:-default} expands to VAR if it is set and to default otherwise. Expansion works in command, args, env, url and headers. That covers both a local server that Claude Code starts with a command and a remote server it reaches over the web.
Here is the team's .mcp.json. The GitHub entry starts GitHub's published server on the engineer's machine (with Docker, a tool that runs packaged programs); the Jira entry points at a remote server. Look at the two lines that carry a credential, the GitHub env entry and the Jira Authorization header: neither contains a real token, only a reference each machine fills in.
{
"mcpServers": {
"github": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}" }
},
"jira": {
"type": "http",
"url": "${JIRA_MCP_URL:-https://jira.example.com/mcp}",
"headers": { "Authorization": "Bearer ${JIRA_API_TOKEN}" }
}
}
}
| You write | What Claude Code puts there | Use it for |
|---|---|---|
${GITHUB_TOKEN} |
The value of GITHUB_TOKEN on this machine |
Secrets: tokens, API keys, passwords |
${JIRA_MCP_URL:-https://jira.example.com/mcp} |
The variable if it is set, otherwise the default | Machine-specific paths and URLs with a sensible fallback |
${VAR} with VAR unset and no default |
The literal text ${VAR}, and claude mcp list warns |
A sign that someone forgot to set the variable |
Memorise the first row; it is the pattern the exam uses. Recognise the other two. The unset case explains the classic "works for me, fails for the new hire" symptom: a variable nobody told the new hire to set, not a broken server. One caution: the default after :- is part of the committed file, so it is for harmless values like a URL, never for a token.
2.4.4 Connection time: every server's tools, all at once
The next question sounds naive and matters a lot: with a Jira server, a GitHub server and the built-in tools, does the agent have to "switch" between them? No. There is no mode to select and no server to activate. When a session starts (one run of Claude Code or the agent), Claude Code reads every scope and connects each configured server. It then asks each server what it offers, with a discovery request called tools/list. The tools that come back join the built-in set.
Each discovered tool gets a full name of the form mcp__<server>__<tool>, such as mcp__jira__get_issue or mcp__github__create_pull_request. The prefix keeps two servers' tools apart even when both have a tool called search, and it is the name you use when you grant a tool permission. From then on, every tool from every server is available at the same time, and the model chooses from the whole set at each step.
Watch the ENG-4521 request run. The model calls mcp__jira__get_issue to read the ticket. It calls Grep and Read to find the code the ticket describes, Edit to fix it and Bash to run the tests. Then it calls mcp__github__create_pull_request. Three sources of tools, one uninterrupted loop, no reconfiguration between steps.
What happens at connection time
jira, githubtools/list on each servermcp__jira__*, mcp__github__*, Grep, Read2.4.5 Resources: a catalogue instead of a treasure hunt
Here is a failure that shows up once the servers work. An engineer asks, "Which open tickets touch the payments service?" The agent calls mcp__jira__search_issues with a guessed query and gets nothing useful. It tries another wording, then a third, then lists a whole project and reads through it. Five calls, a context window full of ticket text, and still no confidence that it saw everything. The agent is exploring, because nobody told it what is there.
Tools are things the agent does. A resource is a thing the agent can read: a piece of content the server exposes under a stable address. The Jira server can expose each issue as a resource and, more usefully, a summary of the open issues in the current sprint, the team's current work cycle. A documentation server can expose the hierarchy of its pages. A database server can expose its schema, the list of tables and what each one holds. These are content catalogues: a compact view of what exists, offered up front, so the agent does not have to discover it by calling tools on speculation.
It is the difference between a library with a catalogue and one without. Without a catalogue you open books at random until one looks right. With one, you read a single page and walk straight to the right shelf. Precisely: in Claude Code you attach a resource to a prompt with an @ mention of the form @server:protocol://path, such as @jira:issue://ENG-4521 (each server chooses its own addresses). Claude Code fetches the resource and includes it with the prompt. Typing @ lists the resources of every connected server. Claude Code also gives the agent tools to list and read resources on servers that offer them, so it can consult the catalogue on its own.
Exploratory tool calls versus an exposed catalogue
Exploratory calls
search_issues "payments"nothing usefulsearch_issues "payment service"partialResources exposed
jira:sprint://currentone summary of open issuesget_issue on eachtargeted callsSo when an agent burns many calls working out what data exists, the fix is not a better search tool or a bigger context window. It is a resource that exposes the catalogue.
2.4.6 Build or reuse, and descriptions that beat Grep
Two more decisions shape whether the servers earn their keep. The first is whether to write a server at all. Jira is a standard system that thousands of teams use. Existing servers for it, from the community or from the vendor, already handle sign-in, paging through long result lists and error reporting, and many users exercise them every day. Writing your own reproduces that work and hands you its maintenance. Reserve a custom server for knowledge or a workflow that belongs to your team alone. This team has one: a services server that knows which team owns each of the company's forty services, which repository holds its code, and who is on call. No community server can know that.
| Integration | Reach for | Why |
|---|---|---|
| Jira, GitHub, a Postgres database | An existing community or vendor server | Standard systems; sign-in, paging and errors are already solved and widely used |
| Which team owns which service | A custom server | The knowledge exists nowhere but your team |
| Your release checklist and deployment rules | A custom server | A team-specific workflow, not a standard system |
The rule to memorise: an existing server for the standard integration, a custom server for the team-specific workflow. The tempting mistake is building your own Jira server for "more control". It buys you the maintenance, not better tools.
The second decision is about words, and the custom server shows why. Its main tool, find_service, can locate any service's code across all forty repositories, including those not on the engineer's laptop. Its description says "Service lookup." And the agent never uses it. Asked where the payment retry logic lives, it reaches for Grep, searches only the local copy of the code, finds nothing, and reports that the code does not exist.
The model chooses between tools on their descriptions alone, and Grep arrives with a clear, familiar description while find_service has two words. Enrich the MCP tool's description until a new colleague would know when to prefer it: what it covers, what it returns, and when to choose it over local search. A version that can compete:
Finds any of the company's services, including code not cloned locally.
Returns the owning team, repository, main code paths and on-call contact.
Prefer this over Grep to find where a service's code lives or who owns it.
Anthropic's guidance for tool authors is to describe a tool as you would to a new hire on your team, and it notes that even small refinements to descriptions can yield dramatic improvements. Detailed does not mean endless: Claude Code cuts off very long descriptions, so put the deciding facts first.
2.4.7 The exam traps
Every mistake in this task statement is a mismatch between what a piece of configuration is for and what it was used for. Questions describe the symptom and ask for the fix. Learn the pairs and the symptom points at the answer.
- ✗ Putting the team's Jira server in
~/.claude.json. ✓ Put it in project-scoped.mcp.jsonand commit it. The symptom is a new engineer who "does not have the tools"; a user-scoped server never left the first engineer's machine. - ✗ Pasting the token into
.mcp.json. ✓ Write${GITHUB_TOKEN}and set the variable on each machine. Once a secret is committed, every clone and every backup has it. - ✗ Believing the agent uses one server at a time. ✓ Tools from all configured servers are discovered at connection time and available simultaneously; the model chooses at each step. There is no server to switch to.
- ✗ Letting the agent discover what data exists by repeated searches. ✓ Expose the catalogue as an MCP resource. A bigger context window or a better search tool leaves the guessing in place.
- ✗ Writing a custom Jira server. ✓ Choose an existing community server for a standard integration; write custom servers only for team-specific workflows.
- ✗ A two-word MCP tool description that loses to
Grep. ✓ Describe the MCP tool's capabilities and outputs in detail so the model can see why it is the better choice. The fix is in the text, not in takingGrepaway.
2.4.8 Put it together: wire in two servers and break them
You now have every piece. The quickest way to make it stick is to configure two servers by hand and watch each mistake fail in its own recognisable way.
The rest of this domain and the next lean on what you just built. The built-in tools (2.5) are the ones your enriched MCP descriptions compete with, so knowing exactly what Grep and Glob do tells you what an MCP tool must promise to win. The same shared-versus-personal split reappears in the CLAUDE.md hierarchy (3.1), where the project file is committed and your personal one is not. And when Claude Code runs in a CI pipeline (3.6), the same committed .mcp.json works unchanged, with the pipeline's secret store supplying the variables.
Key takeaways
- ✓ An MCP server extends an agent's reach beyond the engineer's machine; the model chooses its tools beside the built-in ones by name and description.
- ✓ Project scope is
.mcp.jsonin the repository, committed so the whole team gets the same servers; user scope is~/.claude.json, private and available in all your projects. - ✓ Write
${GITHUB_TOKEN}in.mcp.jsoninstead of the token; each machine expands the variable at load time and no secret is committed. - ✓ At connection time Claude Code asks every configured server for its tools, names them
mcp__<server>__<tool>, and makes all of them available at once. - ✓ MCP resources expose content catalogues such as issue summaries, documentation hierarchies and database schemas, so the agent does not need exploratory tool calls to learn what exists.
- ✓ Use an existing community server for a standard integration like Jira; reserve custom servers for team-specific workflows.
- ✓ Enrich MCP tool descriptions with capabilities and outputs, or the agent will prefer built-in tools such as
Grep.
Check your understanding
4 questions written for this lesson, then one from the CCAR-F question bank on the same topic. Every answer option is explained, including the ones you did not pick. Nothing is stored.
66 CCAR-F questions on Domain 2, free
Every question in the bank is tagged to a domain, so you can drill 66 questions on Tool Design & MCP Integration alone, or sit the full 60-question timed simulator.
Open the CCAR-F question bank → Back to Domain 2 →
The question bank is free. It asks for an account only because the quiz engine has to store answers to score them and show which domains are weak. The questions on this page need nothing.