A\
Lessons/Domain 2
2.412 min

Integrate MCP servers into Claude Code and agent workflows

MCP servers are how you give Claude Code and Agent SDK workflows access to your real systems (GitHub, Jira, databases, internal APIs) as first-class tools. The architect's job is knowing where to configure a server so the right people get it, how to inject credentials without committing secrets, how tools and resources become available at connect time, and how to write descriptions good enough that the agent actually prefers your MCP tools over built-ins like Grep. Getting scope, secrets, and descriptions right is what makes the difference between a server that quietly works for the whole team and one that leaks tokens or never gets used.

MCP server scoping and discovery.mcp.json (project scope, committed)Shared team tooling. Everyone who clones gets it."github": { "command": "npx", "args": [...], "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" } }No secret in file, expanded at connect time.~/.claude.json (user scope, personal)Personal / experimental servers.Not shared with teammates via version control.Team server placed here by mistake =>teammates never receive the tool.Shell environment (per user)export GITHUB_TOKEN=ghp_... -> expands ${...}At connection time: tools/list + resources/list on every serverTools (all available at once): mcp__github__create_issue, mcp__jira__search_issues ...Resources (content catalog): issue summaries, doc index, DB schema -> fewer exploratory callsRich tool descriptions keep the agent from falling back to built-in Grep.Two common mistakes1. Hardcoding a raw token in committed .mcp.json (leak). Use ${VAR} expansion instead.2. Putting a shared server in ~/.claude.json so teammates never get it. Use project .mcp.json.Adopt community servers for standard integrations; custom only for team-specific workflows.
MCP server scoping, environment-variable secret injection, and what becomes available to the agent at connection time, plus the two most common mistakes.

Project scope vs user scope: where the server lives decides who gets it

MCP servers are declared in JSON config, and the file you put them in determines who and what can see them. Two scopes matter for the exam:

  • Project scope (.mcp.json at the repo root): this is shared team tooling. The file is checked into version control, so every teammate who clones or pulls the repo automatically gets the same servers. Put the servers your whole team depends on (the shared GitHub, Jira, or internal-API servers) here.
  • User scope (~/.claude.json): this is personal and experimental. It applies only to you, is not shared through the repository, and is the right home for a server you are trying out or a personal tool your teammates should not inherit.

The classic diagnostic question is: a teammate does not see a server that works for you. Root cause is almost always that it was configured in your user-scoped ~/.claude.json when it should have been in the project-scoped .mcp.json that ships with the repo. This mirrors the CLAUDE.md hierarchy from Domain 3, user-level config is never shared via version control.

You can add servers with claude mcp add, and the CLI exposes a --scope flag (for example --scope project writes to .mcp.json). MCP tools are namespaced as mcp__<server>__<tool> so they never collide with built-ins.

Environment variable expansion keeps secrets out of the repo

Because .mcp.json is committed, you must never write a raw token into it. Claude Code expands environment variables inside the config at load time using ${VAR} syntax (and ${VAR:-default} for a fallback). The committed file references the variable name; the actual secret lives in each developer's shell environment or secrets manager and is substituted when the server connects.

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
    }
  }
}

Here the repo contains only the string ${GITHUB_TOKEN}. Each engineer exports their own GITHUB_TOKEN, and expansion pulls it in at connect time. Expansion works in command, args, env, and (for remote servers) url and headers, so you can parameterize endpoints and auth headers the same way.

This gives you one shared, version-controlled server definition with per-user credentials, no secret ever committed, and no need for each teammate to hand-edit the config.

Tools are discovered at connection time and are all available at once

When Claude Code or an Agent SDK session starts, it connects to every configured MCP server and asks each one for its capabilities (an MCP tools/list call under the hood). The tools returned by all connected servers are registered together and become available to the agent simultaneously, alongside the built-in tools. There is no per-turn loading or manual enabling of individual tools once a server is connected.

This matters for design. It ties directly back to task 2.3: because every connected server's tools land in the same shared tool space, connecting many broad servers can push the agent past the point where tool selection stays reliable. Connect the servers a workflow actually needs rather than wiring up everything available.

It also means a server that fails to start (bad command, missing token, unreachable URL) simply contributes no tools, the agent will not see them, which is another reason a teammate might report that an MCP tool 'does not exist' when the real problem is a connection or credential failure at startup.

MCP resources expose content catalogs to cut exploratory tool calls

MCP servers can expose more than tools. Resources are read-only content items, each identified by a URI, that the server advertises to the agent (via an MCP resources/list call). Think of them as a catalog the agent can see up front: issue summaries, a documentation hierarchy, database schemas, a list of available reports.

The value is reducing exploratory tool calls. Without resources, an agent that needs to know which tables exist has to call a tool, read the result, call another, and so on, burning turns and context just to discover what is available. When that catalog is exposed as resources, the agent can see the landscape directly and jump to the right action.

github://issues/summary        -> open issues with titles and labels
db://schema/orders             -> columns and types for the orders table
docs://architecture/index      -> table of contents for internal docs

So the rule of thumb: expose stable, browseable inventories (schemas, catalogs, indexes) as resources so the agent gains visibility without spending tool calls to explore, and reserve tools for actions and parameterized queries.

Write rich tool descriptions or the agent falls back to built-ins

An MCP tool competes for selection against Claude Code's built-in tools. If your MCP tool has a thin description, the model often defaults to a familiar built-in, most commonly reaching for Grep to search text when a purpose-built MCP tool (say, a Jira search or a semantic code-search server) would give a far better result. The built-in wins by default because it is well understood and the MCP tool undersold itself.

The fix is the same principle as task 2.1: descriptions are the primary signal the model uses to choose a tool. Spell out what the MCP tool does, what inputs it takes, what it returns, and when to prefer it over the obvious built-in.

search_issues: Full-text and label search over the team's Jira project.
Returns structured issues (key, status, assignee, updated date). Prefer this
over Grep for anything about tickets, bugs, or sprint work; Grep only sees
files in the working tree, not Jira.

Calling out the boundary explicitly ('Grep only sees files, not Jira') is what nudges the agent away from the built-in and toward the more capable server.

Prefer community servers for standard integrations, build custom only for team-specific needs

For standard, widely-used integrations, Jira, GitHub, Slack, Google Drive, Postgres, a maintained community or official MCP server almost always exists. Adopt it rather than writing your own. You inherit maintenance, auth handling, and API-change tracking for free, and you avoid reimplementing a solved problem.

Reserve custom MCP servers for workflows that are specific to your team: a proprietary internal service, a bespoke deployment pipeline, a company-specific data store that no public server covers. That is where a custom server earns its keep, because there is no off-the-shelf alternative and the integration encodes knowledge only your team has.

The decision rule for the exam: standard integration and a good community server exists, use it; team-specific workflow with no existing coverage, build a custom server. Building a custom Jira server from scratch when a community one exists is the wrong answer, it is effort spent maintaining what someone else already maintains.

Anti-patterns to avoid

avoid
Hardcoding a real token or API key directly inside the committed .mcp.json.

Why it fails: The secret is now in version control history for anyone with repo access, and rotating it means editing and re-committing the config. It is a credential leak waiting to happen.

instead Reference the variable with ${GITHUB_TOKEN} in .mcp.json and let each user supply the actual value from their own environment; expansion substitutes it at connect time.

avoid
Putting a server the whole team needs into user-scoped ~/.claude.json.

Why it fails: User scope is not shared via version control, so teammates never receive the server and report that the tool 'does not exist' even though it works for you.

instead Declare shared team servers in the project-scoped .mcp.json at the repo root so everyone who clones or pulls gets them automatically.

avoid
Building a custom MCP server for a standard integration like Jira when a community server already exists.

Why it fails: You take on ongoing maintenance, auth, and API-change burden to reimplement something already solved and maintained by others.

instead Adopt the existing community/official server for standard integrations; reserve custom servers for genuinely team-specific workflows with no off-the-shelf option.

avoid
Registering an MCP tool with a one-line description and assuming the agent will use it.

Why it fails: Descriptions are the primary selection signal; with a thin one the model defaults to a familiar built-in (often Grep) and your more capable MCP tool sits unused.

instead Write a detailed description covering inputs, outputs, and when to prefer the tool over the obvious built-in, e.g. 'Grep only sees local files, not Jira.'

Worked example: Developer productivity: wiring a shared Jira + GitHub MCP setup into Claude Code (Scenario 4)

Your team uses Claude Code to explore unfamiliar code and automate repetitive work, and you want it to reach into GitHub and Jira. You start by running claude mcp add on your own machine, which lands the servers in your ~/.claude.json. It works great for you. A week later a new engineer says the search_issues tool 'does not exist' for them.

Root cause: the servers are in your user-scoped ~/.claude.json, which is personal and not shared through the repo. The fix is to move them to a project-scoped .mcp.json committed to the repository so anyone who clones or pulls gets them.

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
    },
    "jira": {
      "command": "npx",
      "args": ["-y", "@team/mcp-jira"],
      "env": { "JIRA_TOKEN": "${JIRA_TOKEN}", "JIRA_BASE_URL": "${JIRA_BASE_URL}" }
    }
  }
}

Note there are no secrets in the file, only ${JIRA_TOKEN} and ${GITHUB_TOKEN} references. Each engineer exports those in their own shell, so the one committed file serves everyone with per-user credentials.

Second problem: engineers notice that when they ask 'what tickets are open on the checkout flow?', Claude keeps running Grep over the codebase instead of querying Jira, and returns nothing useful. The Jira tool's description was just 'Search Jira.' You enrich it: 'Full-text and label search over the team's Jira project; returns key, status, assignee, and updated date. Prefer this over Grep for anything about tickets or bugs, Grep only sees files in the working tree, not Jira.' Now the agent routes ticket questions to the MCP tool.

Third improvement: the Jira server can expose open-issue summaries as MCP resources. By advertising a jira://issues/summary resource, the agent sees the current ticket catalog up front and stops making round-trip exploratory tool calls just to discover what exists, it jumps straight to the relevant ticket.

A note on build-vs-adopt: GitHub and Jira are standard integrations with maintained community servers, so you adopt those. If you later need Claude to hit your proprietary internal deployment service, that is when a custom MCP server is justified, because no community server covers it.

Exam tips

  • Project-scoped .mcp.json (repo root, committed) = shared team servers; user-scoped ~/.claude.json = personal/experimental servers not shared via version control. A teammate missing a server usually means it was put in user scope.
  • Never commit raw secrets. Use ${VAR} expansion (e.g. ${GITHUB_TOKEN}) in .mcp.json so the file is safe to check in and each user supplies the value from their environment.
  • All tools from all connected MCP servers are discovered at connection time and are available to the agent simultaneously, namespaced as mcp__<server>__<tool>.
  • MCP resources expose read-only content catalogs (issue summaries, doc hierarchies, DB schemas) so the agent gains visibility without spending exploratory tool calls.
  • A weak MCP tool description makes the agent fall back to built-ins like Grep; write detailed descriptions (inputs, outputs, when to prefer it) so the capable MCP tool wins selection.
  • Adopt existing community/official servers for standard integrations (Jira, GitHub); build custom servers only for team-specific workflows with no off-the-shelf option.
Official exam objectives for 2.4
Knowledge of
  • MCP server scoping: project-level (.mcp.json) for shared team tooling vs user-level (~/.claude.json) for personal/experimental servers
  • Environment variable expansion in .mcp.json (e.g., ${GITHUB_TOKEN}) for credential management without committing secrets
  • That tools from all configured MCP servers are discovered at connection time and available simultaneously to the agent
  • MCP resources as a mechanism for exposing content catalogs (e.g., issue summaries, documentation hierarchies, database schemas) to reduce exploratory tool calls
Skills in
  • Configuring shared MCP servers in project-scoped .mcp.json with environment variable expansion for authentication tokens
  • Configuring personal/experimental MCP servers in user-scoped ~/.claude.json
  • Enhancing MCP tool descriptions to explain capabilities and outputs in detail, preventing the agent from preferring built-in tools (like Grep) over more capable MCP tools
  • Choosing existing community MCP servers over custom implementations for standard integrations (e.g., Jira), reserving custom servers for team-specific workflows
  • Exposing content catalogs as MCP resources to give agents visibility into available data without requiring exploratory tool calls

Flashcards from this lesson

Where do you put an MCP server so the whole team gets it, versus a personal experimental one?

Shared team servers go in project-scoped .mcp.json at the repo root (committed, everyone who clones gets it). Personal/experimental servers go in user-scoped ~/.claude.json, which is not shared via version control.

A teammate says an MCP tool that works for you 'does not exist' for them. Most likely cause?

The server was configured in your user-scoped ~/.claude.json instead of the project-scoped .mcp.json. User scope is not shared through the repo, so move it to .mcp.json.

How do you reference a credential in .mcp.json without committing the secret?

Use environment variable expansion, e.g. "GITHUB_TOKEN": "${GITHUB_TOKEN}" (with ${VAR:-default} for a fallback). The committed file holds only the variable name; each user's environment supplies the value at connect time.

When are MCP tools discovered and how many are available at once?

At connection time Claude queries every configured server for its tools; the tools from all connected servers are registered together and available to the agent simultaneously, namespaced mcp____.

What are MCP resources and why use them?

Read-only content items identified by URIs (issue summaries, doc hierarchies, DB schemas) that a server advertises as a catalog. They give the agent visibility into available data without burning exploratory tool calls.

Why might the agent keep using Grep instead of a capable MCP tool, and how do you fix it?

The MCP tool's description is too thin, so the model defaults to the familiar built-in. Write a detailed description covering inputs, outputs, and when to prefer it (e.g. 'Grep only sees local files, not Jira').

When should you build a custom MCP server versus adopt a community one?

Adopt an existing community/official server for standard integrations (Jira, GitHub, Slack). Build custom only for team-specific workflows with no off-the-shelf coverage, like a proprietary internal service.

Study all flashcards with spaced repetition