Study guides / CCAR-F / Domain 2

Tool Design & MCP Integration · Lesson 4 of 5

2.4 - MCP Server Integration

Integrating MCP servers into Claude Code and agent workflows with proper scoping and configuration

MCP (Model Context Protocol) servers extend Claude's capabilities by connecting it to external systems - databases, APIs, development tools, issue trackers. Configuring them correctly determines whether your team shares a consistent toolset or descends into configuration chaos.

The Scoping Hierarchy

MCP server configuration lives at two levels, and mixing them up is where most setup problems start.

Project-level: .mcp.json Lives in the project repository root. Version-controlled. Shared with every team member who clones or pulls the repository. Use this for servers that the entire team needs - your Jira integration, your GitHub tools, your internal API connectors.

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

User-level: ~/.claude.json Lives in the user's home directory. Personal. NOT version-controlled. NOT shared with teammates. Use this for experimental servers, personal integrations, or servers you're testing before proposing them to the team.

Key principle: all tools from all configured servers (both project-level and user-level) are discovered at connection time and available simultaneously. There's no manual activation step - if a server is configured and reachable, its tools appear in the agent's toolkit.

Environment Variable Expansion

The .mcp.json file supports ${VARIABLE_NAME} syntax for environment variable expansion. This is how you keep credentials out of version control whilst still sharing server configuration with your team.

{
  "env": {
    "GITHUB_TOKEN": "${GITHUB_TOKEN}",
    "DATABASE_URL": "${DATABASE_URL}"
  }
}

Each developer sets their own tokens locally (in their shell profile, .env file, or secrets manager). The .mcp.json file references the variable names, not the values. This means:

MCP Resources

MCP resources expose content catalogues to agents without requiring exploratory tool calls. Instead of calling a tool to discover what data exists, the agent gets that information upfront.

Examples of what to expose as resources:

The payoff is fewer wasted calls. Without resources, an agent might call list_tables, then describe_table for every table, burning tool calls just to get its bearings. With a database schema resource, it knows immediately.

Resources show agents what data is available. Tools let them act on it.

The Build-vs-Use Decision

This decision comes up constantly, in the exam and in real work. Your team needs to integrate with an external system: build a custom MCP server, or use an existing community one?

Use community servers for standard integrations:

Build custom servers only when:

The exam consistently favours the pragmatic choice. "Evaluate community servers first" is always correct when a standard integration is involved. "Build custom" is only correct when the scenario explicitly describes team-specific requirements that community servers cannot meet.

Enhancing MCP Tool Descriptions

Here's a subtle one: when an MCP tool has a sparse description, the agent may prefer built-in tools (like Grep) even when the MCP tool is more capable. The model simply has better context about built-in tools - their descriptions are rich and detailed.

The fix: enhance your MCP tool descriptions to explain capabilities and outputs in detail. Instead of:

search_codebase: "Searches code"

Write:

search_codebase: "Performs semantic code search across the
entire repository using AST-aware indexing. Returns matching
functions, classes, and methods with full context including
file path, line numbers, and surrounding code. More accurate
than text-based grep for finding code by intent rather than
exact string match. Use this instead of Grep when searching
for code by what it does rather than what it contains."

The enhanced description gives the model enough context to prefer the MCP tool when it's genuinely more capable than the built-in alternative.

Key Concept

Project-level .mcp.json is version-controlled and shared with the team. User-level ~/.claude.json is personal and not shared. Use ${ENV_VAR} syntax to keep credentials out of version control.

Exam traps

Practice question

A team needs to integrate with Jira for issue tracking in their Claude Code workflow. A developer proposes building a custom MCP server. What is the correct first step?

  • A Build a custom MCP server exposing exactly the Jira API endpoints the team needs, so the integration matches their workflow precisely.

    Building custom is premature. Community MCP servers for Jira already exist and cover standard use cases. Custom builds should be reserved for team-specific workflows that community servers cannot handle.

  • B Add the Jira integration to ~/.claude.json so that each developer can configure their own connection to Jira independently.

    A team-wide integration should be in project-level .mcp.json so it is version-controlled and shared with all developers. ~/.claude.json is for personal servers.

  • C Use the Jira REST API directly from Bash commands instead of MCP, which avoids the whole server setup and its configuration.

    Direct API calls bypass the MCP tool interface, losing the benefits of tool descriptions, structured responses, and agent-native integration.

  • D Evaluate existing community MCP servers for Jira and only build custom if they cannot handle team-specific workflows. Correct

    Community servers should always be the first choice for standard integrations. They are maintained, tested, and cover common use cases. Custom builds are justified only when community servers cannot meet team-specific requirements.

Build exercise: Configure MCP Servers with Scoping and Environment Variables

Beginner · 30 minutes

You'll practice:

  1. Create a .mcp.json file in your project root configuring a community MCP server (e.g. GitHub) with command and args

    Project-level .mcp.json is version-controlled and shared with every team member who clones the repository. The exam tests whether you know that team-wide servers belong here, not in ~/.claude.json. Using community servers for standard integrations is always the correct first choice.

    You should see: A .mcp.json file at the project root containing an mcpServers object with at least one server entry specifying command (e.g. npx) and args (e.g. -y @modelcontextprotocol/server-github).

    Hints
    1. The file must be valid JSON with a top-level mcpServers key. Each server entry needs command and args at minimum.
    2. Use npx with the -y flag so the package installs automatically without prompting. The server package name follows the @modelcontextprotocol/server-<name> convention.
    3. Create .mcp.json with this content:
      {
        "mcpServers": {
          "github": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-github"],
            "env": {
              "GITHUB_TOKEN": "${GITHUB_TOKEN}"
            }
          }
        }
      }
  2. Use ${GITHUB_TOKEN} environment variable expansion for authentication credentials

    Committing credentials directly in .mcp.json is a security risk the exam penalises. The ${VARIABLE_NAME} syntax lets the configuration file reference environment variables without containing the actual values, keeping secrets out of repository history.

    You should see: The env section of your server configuration contains ${GITHUB_TOKEN} (not an actual token value). Running git diff confirms no secrets are staged for commit. Each developer sets their own token locally.

    Hints
    1. Check that your .mcp.json contains literal ${GITHUB_TOKEN} strings, not actual token values. The expansion happens at runtime, not in the file.
    2. Set the token in your shell profile (e.g. export GITHUB_TOKEN=ghp_xxx in ~/.zshrc) or a local .env file that is in your .gitignore.
    3. # In your shell profile (~/.zshrc or ~/.bashrc):
      export GITHUB_TOKEN=ghp_your_actual_token_here
      
      # Verify the .mcp.json contains only variable references:
      # "GITHUB_TOKEN": "${GITHUB_TOKEN}"  <-- correct
      # "GITHUB_TOKEN": "ghp_abc123"       <-- WRONG, this is a leaked secret
  3. Add a personal or experimental MCP server to ~/.claude.json for user-level configuration

    User-level configuration in ~/.claude.json is personal, not version-controlled, and not shared with teammates. The exam tests whether you know the scoping hierarchy: .mcp.json for team servers, ~/.claude.json for personal or experimental servers.

    You should see: A ~/.claude.json file with an mcpServers entry for a personal server (e.g. an experimental integration you are testing). This file is NOT in your project repository and NOT in version control.

    Hints
    1. User-level configuration follows the same format as project-level but lives in your home directory. Use it for servers you want to test before proposing to the team.
    2. The file structure mirrors .mcp.json but is located at ~/.claude.json. Tools from both files are discovered at connection time and available simultaneously.
    3. # ~/.claude.json
      {
        "mcpServers": {
          "experimental-search": {
            "command": "node",
            "args": ["/path/to/my/experimental-search-server.js"],
            "env": {
              "API_KEY": "${EXPERIMENTAL_API_KEY}"
            }
          }
        }
      }
  4. Expose a content catalogue (e.g. a documentation hierarchy or database schema) as an MCP resource

    MCP resources give agents visibility into available data without requiring exploratory tool calls. Without resources, an agent might call list_tables then describe_table for every table, wasting multiple tool calls. A schema resource makes that information available immediately.

    You should see: An MCP resource definition that exposes structured data (e.g. a list of database tables with column types, or a documentation table of contents) accessible at a URI like db://schema/main. The resource should have a name, description, and mimeType.

    Hints
    1. Think of resources as read-only data catalogues. They answer the question: what data is available? Tools then let the agent act on that data.
    2. Define resources using the MCP SDK server.resource() method with a URI template, name, and a handler that returns the structured content.
    3. import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
      const server = new McpServer({ name: "db-tools", version: "1.0.0" });
      server.resource(
        "db-schema",
        "db://schema/main",
        { description: "Database schema with all tables, columns, and types", mimeType: "application/json" },
        async () => ({
          contents: [{
            uri: "db://schema/main",
            text: JSON.stringify({
              tables: [
                { name: "customers", columns: ["id", "email", "name", "tier"] },
                { name: "orders", columns: ["id", "customer_id", "status", "total"] }
              ]
            })
          }]
        })
      );
  5. Enhance the tool descriptions for your configured MCP server to explain capabilities and outputs in detail, preventing the agent from preferring built-in tools

    When an MCP tool has a sparse description, the agent prefers built-in tools like Grep because their descriptions are richer and more detailed. The exam tests whether you know that enhanced MCP descriptions are required to compete with built-in tools for selection priority.

    You should see: Tool descriptions that are 3-5 sentences long, explaining what the tool does, what it returns, when to use it, and how it compares to built-in alternatives. For example, a search_codebase tool description that explicitly states it is more accurate than Grep for semantic searches.

    Hints
    1. Compare your MCP tool description side by side with the built-in Grep description. If Grep has more detail, the model will prefer Grep even when your MCP tool is more capable.
    2. Include four elements: what the tool does, what it returns (format and fields), when to use it, and when to use the built-in alternative instead.
    3. // Before (sparse - agent will prefer Grep):
      // search_codebase: "Searches code"
      
      // After (enhanced - agent can make an informed choice):
      server.tool("search_codebase",
        "Performs semantic code search across the entire repository using AST-aware indexing. Returns matching functions, classes, and methods with full context including file path, line numbers, and surrounding code. More accurate than text-based Grep for finding code by intent rather than exact string match. Use this instead of Grep when searching for code by what it does rather than what it contains.",
        { query: { type: "string" } },
        async ({ query }) => {
          return { content: [{ type: "text", text: `Results for: ${query}` }] };
        }
      );

Sources