Skip to content

feat: Add custom MCP server support#150

Open
derhally wants to merge 6 commits intoAutoForgeAI:masterfrom
derhally:zd/extra-tools
Open

feat: Add custom MCP server support#150
derhally wants to merge 6 commits intoAutoForgeAI:masterfrom
derhally:zd/extra-tools

Conversation

@derhally
Copy link
Contributor

@derhally derhally commented Feb 1, 2026

Introduces the ability to configure additional MCP (Model Context Protocol) servers through organization and project configuration files. This feature extends the agent's capabilities by allowing users to define custom tools.

Implements a robust configuration system that:

  • Loads and validates MCP server definitions and org-level blocked tools.
  • Supports variable substitution in commands, arguments, and environment variables.
  • Resolves configuration hierarchy, allowing project-level definitions to override organization-level ones.
  • Integrates custom tools into the agent's security context, enforcing explicit tool allowlists and organization-wide blocks.

Updates CLAUDE.md with detailed documentation on configuring and managing custom MCP servers, including schema and key behaviors. Provides example configuration files.

Summary by CodeRabbit

  • New Features

    • Custom MCP server configuration at org and project levels with runtime loading and inclusion
    • Tool allow/block controls for MCP servers with effective permission resolution
    • Variable substitution (e.g., ${PROJECT_DIR}, ${HOME}) in server configs
    • Runtime reporting of configured and blocked MCP servers/tools
  • Documentation

    • Expanded MCP docs with schema, examples, behavior notes, and sample configs
  • Tests

    • Added tests for MCP validation, loading, hierarchy resolution, and pattern matching

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

📝 Walkthrough

Walkthrough

Adds runtime support for user-configurable "Custom MCP Servers" with org- and project-level config loading/validation/merging, pattern-based blocked tools, variable-substitution helpers, client integration to register custom servers/tools, example configs, tests, and documentation. Duplicate MCP-related definitions were introduced.

Changes

Cohort / File(s) Summary
Documentation
CLAUDE.md
Adds "Custom MCP Servers" docs describing org/project config locations, schema, variable substitution, tool naming, precedence, and examples; content duplicated.
Configuration examples
examples/org_config.yaml, examples/project_allowed_commands.yaml
Adds mcp_servers and blocked_mcp_tools scaffolding, extensive commented examples and field docs for org- and project-level declarations.
Client integration
client.py
Adds variable-substitution helpers (substitute_variables, substitute_variables_in_list, substitute_variables_in_dict), loads effective MCP servers/tools, merges user MCP tools and permissions into runtime allowed_tools/permissions, substitutes command/args/env for custom servers, and logs/registers custom MCP servers alongside built-ins.
Security / MCP core
security.py
Introduces MCPServerConfig TypedDict, validation helpers, org/project config loaders, merging/precedence logic, blocked-tool pattern handling, and functions to compute effective MCP servers and effective tool names (mcp__server__tool). Note: many MCP-related functions/definitions are duplicated within the module.
Tests
test_security.py
Adds MCP-focused tests for server validation, pattern matching, org/project loading, hierarchy resolution, and blocked-tool enforcement; imports expanded.

Sequence Diagram

sequenceDiagram
    participant Client as create_client()
    participant OrgCfg as Org Config
    participant ProjCfg as Project Config
    participant Security as Security Module
    participant Subst as Substitution Helpers
    participant MCPMap as Runtime MCP Map

    Client->>OrgCfg: read org config (mcp_servers, blocked_mcp_tools)
    Client->>ProjCfg: read project config (mcp_servers)
    Client->>Security: get_effective_mcp_servers(project_dir)
    Security->>Security: validate & merge org + project servers
    Security-->>Client: merged servers[], blocked_tools set

    Client->>Security: get_effective_mcp_tools(servers, blocked_tools)
    Security->>Security: expand patterns → allowed tool names & permissions
    Security-->>Client: allowed_tools[], permissions

    Client->>Subst: substitute_variables_in_dict(command/args/env) for each custom server
    Subst-->>Client: substituted server configs
    Client->>MCPMap: register substituted custom MCP servers
    MCPMap-->>Client: updated runtime MCP servers list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐇 I nibble configs, stitch vars with flair,
Org and project share tools in the lair,
I patch their commands and guard with a pattern,
Merge, validate, then give runtime a lantern,
Hop—servers live, ready to care.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature being added: custom MCP server support. It directly corresponds to the primary changes across multiple files implementing this functionality.
Docstring Coverage ✅ Passed Docstring coverage is 95.45% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@client.py`:
- Around line 369-373: The reported issue is a 3-space indentation on the line
that calls get_effective_mcp_servers; change the indentation to standard 4
spaces so the block aligns with surrounding code (ensure the lines initializing
user_mcp_servers, blocked_mcp_tools and the subsequent call to
get_effective_mcp_tools all use 4-space indentation). Locate the lines with
user_mcp_servers, blocked_mcp_tools and user_mcp_tools/user_mcp_permissions and
adjust whitespace to 4 spaces to conform to Python style.
- Around line 2-3: The module docstring at the top of client.py contains a
duplicated line ("Claude SDK Client Configuration")—remove the redundant second
occurrence so the file retains a single top-level docstring line; locate the
duplicate in the module docstring and delete the extra line, leaving one clear
docstring entry for the module.
- Line 350: Remove the trailing whitespace on the blank line reported in
client.py (around line 350) so the line is a true empty line; edit the file to
delete any spaces or tabs on that blank line and re-run the linter (ruff) to
confirm the pipeline error is resolved.
🧹 Nitpick comments (2)
CLAUDE.md (1)

377-381: Add language specifier to fenced code block.

The example output code block is missing a language specifier. For consistency and to satisfy markdownlint (MD040), add a language hint.

📝 Suggested fix
-```
+```text
 Created security settings at /path/to/project/.claude_settings.json
    - MCP servers: playwright (browser), features (database), filesystem (custom)
    - Blocked MCP tools (org): filesystem__write_file, filesystem__delete_file
</details>

</blockquote></details>
<details>
<summary>security.py (1)</summary><blockquote>

`19-26`: **Consider using `Required[]` for mandatory TypedDict fields.**

The `MCPServerConfig` TypedDict uses `total=False` which makes all fields optional at the type level, but the docstring indicates `name`, `command`, and `allowed_tools` are required. For better type safety, consider using `Required[]` (Python 3.11+) for mandatory fields.


<details>
<summary>♻️ Suggested improvement</summary>

```diff
-from typing import Any, Optional, TypedDict
+from typing import Any, Optional, Required, TypedDict


-class MCPServerConfig(TypedDict, total=False):
+class MCPServerConfig(TypedDict, total=False):
     """Configuration for a custom MCP server."""
-    name: str  # Required: unique identifier for the server
-    command: str  # Required: command to run (e.g., "npx", "python")
+    name: Required[str]  # Required: unique identifier for the server
+    command: Required[str]  # Required: command to run (e.g., "npx", "python")
     args: list[str]  # Optional: command arguments
     env: dict[str, str]  # Optional: environment variables
-    allowed_tools: list[str]  # Required: explicit list of tools to allow
+    allowed_tools: Required[list[str]]  # Required: explicit list of tools to allow

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client.py (1)

369-418: ⚠️ Potential issue | 🟠 Major

Prevent custom configs from expanding built‑in MCP tool access.

get_effective_mcp_tools runs before built‑in name conflicts are filtered, so a user can define a server named "features"/"playwright" and inject extra tool names into allowed_tools and permissions_list, bypassing the agent‑type tool restrictions even though the server is later skipped. Filter out reserved names before computing tools, or recompute after conflict handling.

✅ Suggested fix (filter before computing tools)
     # Load user-configured MCP servers from org and project configs
     user_mcp_servers, blocked_mcp_tools = get_effective_mcp_servers(project_dir)
+    reserved_server_names = {"features"}
+    if not yolo_mode:
+        reserved_server_names.add("playwright")
+    user_mcp_servers_for_tools = [
+        s for s in user_mcp_servers if s["name"] not in reserved_server_names
+    ]
     user_mcp_tools, user_mcp_permissions = get_effective_mcp_tools(
-        user_mcp_servers, blocked_mcp_tools
+        user_mcp_servers_for_tools, blocked_mcp_tools
     )
🤖 Fix all issues with AI agents
In `@client.py`:
- Around line 533-538: Update the comment to "# Add env with variable
substitution (defensive merge with parent)"; then defensively merge the
substituted env into the existing parent env instead of replacing it — call
substitute_variables_in_dict(server_config["env"], project_dir) to get
substituted, obtain the parent env from server_entry.get("env",
os.environ.copy()) (import os if not already imported), update a copy of that
parent with the substituted values (so substituted values override), and assign
the merged dict back to server_entry["env"]; reference server_config,
server_entry, substitute_variables_in_dict, and project_dir when making the
change.

Comment on lines +533 to +537
# Add env with variable substitution, merging with parent environment
if "env" in server_config:
server_entry["env"] = substitute_variables_in_dict(
server_config["env"], project_dir
)

Copy link

@coderabbitai coderabbitai bot Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine client.py to understand the context
fd -t f "client.py" | head -20

Repository: leonvanzyl/autocoder

Length of output: 88


🏁 Script executed:

# Once found, let's examine the relevant section with more context
cat -n client.py | sed -n '520,550p'

Repository: leonvanzyl/autocoder

Length of output: 1468


🌐 Web query:

Claude Agent SDK MCP environment variables merge parent environment

💡 Result:

In the Claude Agent SDK, the env objects you set:

  • at the query level (options.env, e.g. ENABLE_TOOL_SEARCH) and
  • per MCP stdio server (options.mcpServers.<name>.env, e.g. GITHUB_TOKEN)

are intended to be additive overrides on top of the runtime’s existing environment (so you generally don’t need to restate things like PATH; you just set/override the specific variables you need). The Agent SDK docs show using env specifically to pass credentials and feature flags this way. [1]

Notes / gotchas:

  • Claude Code may still adjust some variables (people have reported extra path segments being prepended rather than the environment being replaced wholesale). [2]
  • In some Claude Desktop/Claude Code integration scenarios, there have been bugs where variables from an env section were not passed through to MCP servers as expected. [3]

Sources:
[1] Anthropic “Connect to external tools with MCP” (Agent SDK) docs https://platform.claude.com/docs/en/agent-sdk/mcp
[2] anthropics/claude-code issue #5202 (env vars like PATH/PYTHONPATH not preserved exactly) anthropics/claude-code#5202
[3] anthropics/claude-code issue #1254 (env vars in config not passed to MCP servers) anthropics/claude-code#1254


🌐 Web query:

MCP Model Context Protocol environment configuration SDK implementation

💡 Result:

MCP environment configuration (what to implement)

1) Configure MCP servers + per-server environment (.mcp.json)

In Claude/Agent SDK flows, MCP servers are commonly declared in a project-root .mcp.json, and each server can receive an env object (passed to the MCP server process). Example shown in the Claude docs: env: { "ALLOWED_PATHS": "..." }. [1]

2) Allow shared configs with environment-variable expansion

If you want portable configs (no secrets committed), implement/expect ${VAR} and ${VAR:-default} expansion in .mcp.json fields including command, args, env, url, and headers (Claude Code supports this). [2]

3) SDK-side “in code” configuration (instead of a file)

Claude’s Agent SDK allows passing MCP server definitions directly via an options.mcpServers object when calling query(). [1]

4) Application environment loading (Node/TS example)

A typical Node implementation loads environment variables (e.g., from .env) before constructing clients/transports (the MCP Node client tutorial uses dotenv.config() and reads ANTHROPIC_API_KEY from .env). [3]


Minimal reference implementations

A) .mcp.json (stdio server with env)

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem"],
      "env": { "ALLOWED_PATHS": "/Users/me/projects" }
    }
  }
}

[1]

B) .mcp.json with env-var expansion (remote HTTP/SSE auth)

{
  "mcpServers": {
    "api-server": {
      "type": "http",
      "url": "${API_BASE_URL:-https://api.example.com}/mcp",
      "headers": { "Authorization": "Bearer ${API_KEY}" }
    }
  }
}

[2]

C) Node client setup (dotenv + MCP TS SDK transport)

import dotenv from "dotenv";
dotenv.config(); // loads ANTHROPIC_API_KEY from .env

[3]


SDKs you can base your implementation on

  • Official MCP Swift SDK (client + server, transports, spec-versioned). [4]

Sources: [1] Claude Agent SDK “MCP in the SDK” docs, [2] Claude Code MCP docs (env expansion), [3] modelcontextprotocol.io Node client tutorial, [4] modelcontextprotocol/swift-sdk README.


🏁 Script executed:

# Search for where mcp_servers is used after construction
cat -n client.py | sed -n '539,600p'

Repository: leonvanzyl/autocoder

Length of output: 3494


🏁 Script executed:

# Look for SDK initialization and how mcp_servers is passed
rg -n "mcp_servers" client.py -A 3 -B 3

Repository: leonvanzyl/autocoder

Length of output: 2911


🏁 Script executed:

# Check if there are any imports or SDK references that show how env is handled
head -100 client.py | cat -n

Repository: leonvanzyl/autocoder

Length of output: 4333


Update comment accuracy and apply defensive env merge to handle known SDK issues.

The comment at line 533 claims "merging with parent environment" but the code only performs variable substitution without actually merging. The Claude Agent SDK is documented to treat env as additive overrides, however there are known issues in the SDK (#5202, #1254) where environment variables from MCP server configs are not correctly passed through. To ensure custom servers receive required variables like PATH, apply the defensive merge:

Fix: Explicitly merge env with parent
        if "env" in server_config:
-            server_entry["env"] = substitute_variables_in_dict(
-                server_config["env"], project_dir
-            )
+            server_entry["env"] = {
+                **os.environ,
+                **substitute_variables_in_dict(server_config["env"], project_dir),
+            }

Also update the comment on line 533 to: # Add env with variable substitution (defensive merge with parent)

🤖 Prompt for AI Agents
In `@client.py` around lines 533 - 538, Update the comment to "# Add env with
variable substitution (defensive merge with parent)"; then defensively merge the
substituted env into the existing parent env instead of replacing it — call
substitute_variables_in_dict(server_config["env"], project_dir) to get
substituted, obtain the parent env from server_entry.get("env",
os.environ.copy()) (import os if not already imported), update a copy of that
parent with the substituted values (so substituted values override), and assign
the merged dict back to server_entry["env"]; reference server_config,
server_entry, substitute_variables_in_dict, and project_dir when making the
change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leonvanzyl would you be ok with this suggest change?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@client.py`:
- Around line 1-8: The module-level docstring in client.py is malformed: there's
an early closing triple-quote that breaks the header (the text including "Claude
SDK Client Configuration" and the separators should be inside one continuous
triple-quoted string). Fix by making a single valid triple-quoted module
docstring at the top of client.py containing the header text (remove the stray
closing """ and delete the duplicated separator line) so the entire
title/description is enclosed correctly.

derhally and others added 5 commits February 1, 2026 12:18
Introduces the ability to configure additional MCP (Model Context Protocol) servers through organization and project configuration files. This feature extends the agent's capabilities by allowing users to define custom tools.

Implements a robust configuration system that:
- Loads and validates MCP server definitions and org-level blocked tools.
- Supports variable substitution in commands, arguments, and environment variables.
- Resolves configuration hierarchy, allowing project-level definitions to override organization-level ones.
- Integrates custom tools into the agent's security context, enforcing explicit tool allowlists and organization-wide blocks.

Updates `CLAUDE.md` with detailed documentation on configuring and managing custom MCP servers, including schema and key behaviors. Provides example configuration files.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@client.py`:
- Around line 367-371: The comment preceding the MCP server loading calls is
misaligned (tab + 3 spaces) and should match the indentation of the following
function calls (tab + 4 spaces); update the comment line above the calls to
get_effective_mcp_servers and get_effective_mcp_tools so it has one additional
space of indentation to align with those lines.
🧹 Nitpick comments (1)
CLAUDE.md (1)

424-428: Add language specification to fenced code block.

The example output code block is missing a language specification, which triggers a markdownlint warning (MD040). Since this is plain text output, use text or plaintext.

-```
+```text
 Created security settings at /path/to/project/.claude_settings.json

rudiheydra added a commit to rudiheydra/AutoBuildr that referenced this pull request Feb 2, 2026
AutoForgeAI#150)

Previously, HarnessKernel's _record_tool_call_event() and
_record_tool_result_event() truncated payloads over 4KB inline without
creating artifact records, causing data loss. The EventRecorder correctly
created artifacts, but the kernel's own truncation path did not.

Changes:
- Added _create_payload_artifact() helper to HarnessKernel that creates
  an Artifact record with content_inline storing the full payload
- Modified _record_tool_call_event() to create artifact and set
  artifact_ref when truncating large payloads
- Modified _record_tool_result_event() with the same fix
- Truncated payloads now include _artifact_ref and _note fields
  indicating the full content is in the referenced artifact
- Added hashlib import for SHA256 content hashing

Tests: 31 new tests, 56 existing kernel tests pass (0 regressions)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
rudiheydra added a commit to rudiheydra/AutoBuildr that referenced this pull request Feb 2, 2026
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant