Skip to content

Commit 74883a4

Browse files
committed
feat: Add custom MCP server support
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.
1 parent 24481d4 commit 74883a4

File tree

7 files changed

+1042
-3
lines changed

7 files changed

+1042
-3
lines changed

CLAUDE.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,61 @@ blocked_commands:
325325
- `examples/org_config.yaml` - Org config example (all commented by default)
326326
- `examples/README.md` - Comprehensive guide with use cases, testing, and troubleshooting
327327

328+
#### Custom MCP Servers
329+
330+
The agent supports adding custom MCP (Model Context Protocol) servers via configuration files. This allows extending the agent's capabilities with additional tools.
331+
332+
**Configuration Locations:**
333+
- **Org-level**: `~/.autocoder/config.yaml` - Available to ALL projects
334+
- **Project-level**: `{project}/.autocoder/allowed_commands.yaml` - Project-specific
335+
336+
**MCP Server Config Schema:**
337+
338+
```yaml
339+
# ~/.autocoder/config.yaml (org-level)
340+
version: 1
341+
342+
mcp_servers:
343+
- name: filesystem
344+
command: npx
345+
args:
346+
- "@anthropic/mcp-server-filesystem"
347+
- "${PROJECT_DIR}" # Variable substitution
348+
env:
349+
SOME_VAR: value
350+
allowed_tools: # REQUIRED - explicit list of tools to allow
351+
- read_file
352+
- list_directory
353+
354+
# Org-level can block specific tools across ALL projects
355+
blocked_mcp_tools:
356+
- filesystem__write_file # Block write across all projects
357+
- filesystem__delete_file
358+
```
359+
360+
**Variable Substitution:**
361+
- `${PROJECT_DIR}` - Absolute path to project directory
362+
- `${HOME}` - User's home directory
363+
364+
**Tool Naming Convention:**
365+
- MCP tools follow the pattern `mcp__{server}__{tool}`
366+
- Config uses short names: `allowed_tools: [read_file]`
367+
- Becomes: `mcp__filesystem__read_file`
368+
369+
**Key Behaviors:**
370+
- `allowed_tools` is REQUIRED for each MCP server - must explicitly list tools
371+
- Org `blocked_mcp_tools` takes precedence - projects cannot allow blocked tools
372+
- Environment variables merge with parent environment
373+
- Project can override org MCP server by using the same name
374+
375+
**Example Output:**
376+
377+
```
378+
Created security settings at /path/to/project/.claude_settings.json
379+
- MCP servers: playwright (browser), features (database), filesystem (custom)
380+
- Blocked MCP tools (org): filesystem__write_file, filesystem__delete_file
381+
```
382+
328383
### Ollama Local Models (Optional)
329384
330385
Run coding agents using local models via Ollama v0.14.0+:

client.py

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""
22
Claude SDK Client Configuration
3+
Claude SDK Client Configuration
34
===============================
45
56
Functions for creating and configuring the Claude Agent SDK client.
@@ -11,13 +12,19 @@
1112
import shutil
1213
import sys
1314
from pathlib import Path
15+
from typing import Any
1416

1517
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
1618
from claude_agent_sdk.types import HookContext, HookInput, HookMatcher, SyncHookJSONOutput
1719
from dotenv import load_dotenv
1820

1921
from env_constants import API_ENV_VARS
20-
from security import SENSITIVE_DIRECTORIES, bash_security_hook
22+
from security import (
23+
SENSITIVE_DIRECTORIES,
24+
bash_security_hook,
25+
get_effective_mcp_servers,
26+
get_effective_mcp_tools,
27+
)
2128

2229
# Load environment variables from .env file if present
2330
load_dotenv()
@@ -182,6 +189,37 @@ def get_extra_read_paths() -> list[Path]:
182189
return validated_paths
183190

184191

192+
def substitute_variables(value: str, project_dir: Path) -> str:
193+
"""
194+
Substitute variables in a configuration value.
195+
196+
Supported variables:
197+
- ${PROJECT_DIR} - Absolute path to project directory
198+
- ${HOME} - User's home directory
199+
200+
Args:
201+
value: String value that may contain variables
202+
project_dir: Path to the project directory
203+
204+
Returns:
205+
String with variables substituted
206+
"""
207+
result = value
208+
result = result.replace("${PROJECT_DIR}", str(project_dir.resolve()))
209+
result = result.replace("${HOME}", str(Path.home()))
210+
return result
211+
212+
213+
def substitute_variables_in_list(values: list[str], project_dir: Path) -> list[str]:
214+
"""Substitute variables in a list of strings."""
215+
return [substitute_variables(v, project_dir) for v in values]
216+
217+
218+
def substitute_variables_in_dict(values: dict[str, str], project_dir: Path) -> dict[str, str]:
219+
"""Substitute variables in a dict of strings."""
220+
return {k: substitute_variables(v, project_dir) for k, v in values.items()}
221+
222+
185223
# Per-agent-type MCP tool lists.
186224
# Only expose the tools each agent type actually needs, reducing tool schema
187225
# overhead and preventing agents from calling tools meant for other roles.
@@ -309,6 +347,7 @@ def create_client(
309347
Note: Authentication is handled by start.bat/start.sh before this runs.
310348
The Claude SDK auto-detects credentials from the Claude CLI configuration
311349
"""
350+
312351
# Select the feature MCP tools appropriate for this agent type
313352
feature_tools_map = {
314353
"coding": CODING_AGENT_TOOLS,
@@ -327,12 +366,21 @@ def create_client(
327366
}
328367
max_turns = max_turns_map.get(agent_type, 300)
329368

369+
# Load user-configured MCP servers from org and project configs
370+
user_mcp_servers, blocked_mcp_tools = get_effective_mcp_servers(project_dir)
371+
user_mcp_tools, user_mcp_permissions = get_effective_mcp_tools(
372+
user_mcp_servers, blocked_mcp_tools
373+
)
374+
330375
# Build allowed tools list based on mode and agent type.
331376
# In YOLO mode, exclude Playwright tools for faster prototyping.
332377
allowed_tools = [*BUILTIN_TOOLS, *feature_tools]
333378
if not yolo_mode:
334379
allowed_tools.extend(PLAYWRIGHT_TOOLS)
335380

381+
# Add user-configured MCP tools
382+
allowed_tools.extend(user_mcp_tools)
383+
336384
# Build permissions list.
337385
# We permit ALL feature MCP tools at the security layer (so the MCP server
338386
# can respond if called), but the LLM only *sees* the agent-type-specific
@@ -367,6 +415,9 @@ def create_client(
367415
# Allow Playwright MCP tools for browser automation (standard mode only)
368416
permissions_list.extend(PLAYWRIGHT_TOOLS)
369417

418+
# Add user-configured MCP tool permissions
419+
permissions_list.extend(user_mcp_permissions)
420+
370421
# Create comprehensive security settings
371422
# Note: Using relative paths ("./**") restricts access to project directory
372423
# since cwd is set to project_dir
@@ -394,10 +445,22 @@ def create_client(
394445
if extra_read_paths:
395446
print(f" - Extra read paths (validated): {', '.join(str(p) for p in extra_read_paths)}")
396447
print(" - Bash commands restricted to allowlist (see security.py)")
397-
if yolo_mode:
448+
449+
# Report MCP servers
450+
builtin_servers = ["features (database)"]
451+
if not yolo_mode:
452+
builtin_servers.insert(0, "playwright (browser)")
453+
user_server_names = [s["name"] for s in user_mcp_servers]
454+
if user_server_names:
455+
all_servers = builtin_servers + [f"{name} (custom)" for name in user_server_names]
456+
print(f" - MCP servers: {', '.join(all_servers)}")
457+
elif yolo_mode:
398458
print(" - MCP servers: features (database) - YOLO MODE (no Playwright)")
399459
else:
400460
print(" - MCP servers: playwright (browser), features (database)")
461+
462+
if blocked_mcp_tools:
463+
print(f" - Blocked MCP tools (org): {', '.join(sorted(blocked_mcp_tools))}")
401464
print(" - Project settings enabled (skills, commands, CLAUDE.md)")
402465
print()
403466

@@ -448,6 +511,35 @@ def create_client(
448511
"args": playwright_args,
449512
}
450513

514+
# Add user-configured MCP servers from org and project configs
515+
for server_config in user_mcp_servers:
516+
server_name = server_config["name"]
517+
518+
# Skip if server name conflicts with built-in servers
519+
if server_name in mcp_servers:
520+
print(f" - Warning: Custom MCP server '{server_name}' conflicts with built-in, skipping")
521+
continue
522+
523+
# Build server entry with variable substitution
524+
server_entry: dict[str, Any] = {
525+
"command": substitute_variables(server_config["command"], project_dir),
526+
}
527+
528+
# Add args with variable substitution
529+
if "args" in server_config:
530+
server_entry["args"] = substitute_variables_in_list(
531+
server_config["args"], project_dir
532+
)
533+
534+
# Add env with variable substitution, merging with parent environment
535+
if "env" in server_config:
536+
server_entry["env"] = substitute_variables_in_dict(
537+
server_config["env"], project_dir
538+
)
539+
540+
mcp_servers[server_name] = server_entry
541+
print(f" - Custom MCP server '{server_name}': {server_config['command']}")
542+
451543
# Build environment overrides for API endpoint configuration
452544
# These override system env vars for the Claude CLI subprocess,
453545
# allowing AutoCoder to use alternative APIs (e.g., GLM) without

examples/org_config.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,78 @@ blocked_commands: []
9191
# - puppet
9292

9393

94+
# ==========================================
95+
# Custom MCP Servers
96+
# ==========================================
97+
# Configure additional MCP (Model Context Protocol) servers that will be
98+
# available to the agent in ALL projects.
99+
#
100+
# Each MCP server MUST specify:
101+
# - name: Unique identifier for the server
102+
# - command: Command to run (e.g., "npx", "python")
103+
# - allowed_tools: REQUIRED list of tools to allow (explicit allowlist)
104+
#
105+
# Optional fields:
106+
# - args: List of command arguments
107+
# - env: Environment variables (merged with parent environment)
108+
#
109+
# Variable substitution is supported:
110+
# - ${PROJECT_DIR} - Absolute path to current project directory
111+
# - ${HOME} - User's home directory
112+
#
113+
# By default, no custom MCP servers are configured.
114+
115+
mcp_servers: []
116+
117+
# Example: Filesystem MCP server (read-only)
118+
# - name: filesystem
119+
# command: npx
120+
# args:
121+
# - "@anthropic/mcp-server-filesystem"
122+
# - "${PROJECT_DIR}"
123+
# allowed_tools:
124+
# - read_file
125+
# - list_directory
126+
# - search_files
127+
128+
# Example: Memory MCP server for persistent context
129+
# - name: memory
130+
# command: npx
131+
# args:
132+
# - "@anthropic/mcp-server-memory"
133+
# env:
134+
# MEMORY_DIR: "${HOME}/.autocoder/memory"
135+
# allowed_tools:
136+
# - store_memory
137+
# - retrieve_memory
138+
# - search_memory
139+
140+
141+
# ==========================================
142+
# Blocked MCP Tools (Organization-Wide)
143+
# ==========================================
144+
# Tools listed here are BLOCKED across ALL projects.
145+
# Projects CANNOT override these blocks.
146+
#
147+
# Tool naming convention: {server}__{tool} or mcp__{server}__{tool}
148+
# Examples:
149+
# - filesystem__write_file (blocks filesystem server's write_file tool)
150+
# - filesystem__delete_file (blocks filesystem server's delete_file tool)
151+
# - myserver__* (blocks ALL tools from myserver - wildcard)
152+
#
153+
# By default, no tools are blocked.
154+
155+
blocked_mcp_tools: []
156+
157+
# Block dangerous filesystem operations
158+
# - filesystem__write_file
159+
# - filesystem__delete_file
160+
# - filesystem__move_file
161+
162+
# Block all tools from a specific server (wildcard)
163+
# - dangerous_server__*
164+
165+
94166
# ==========================================
95167
# Global Settings (Phase 3 feature)
96168
# ==========================================

examples/project_allowed_commands.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,53 @@ commands: []
103103
# description: Deploy to staging environment
104104

105105

106+
# ==========================================
107+
# Custom MCP Servers
108+
# ==========================================
109+
# Configure additional MCP (Model Context Protocol) servers for THIS PROJECT.
110+
# These are added to any org-level MCP servers defined in ~/.autocoder/config.yaml
111+
#
112+
# Each MCP server MUST specify:
113+
# - name: Unique identifier for the server
114+
# - command: Command to run (e.g., "npx", "python")
115+
# - allowed_tools: REQUIRED list of tools to allow (explicit allowlist)
116+
#
117+
# Optional fields:
118+
# - args: List of command arguments
119+
# - env: Environment variables (merged with parent environment)
120+
#
121+
# Variable substitution is supported:
122+
# - ${PROJECT_DIR} - Absolute path to current project directory
123+
# - ${HOME} - User's home directory
124+
#
125+
# By default, no custom MCP servers are configured.
126+
127+
mcp_servers: []
128+
129+
# Example: SQLite MCP server for this project's database
130+
# - name: sqlite
131+
# command: npx
132+
# args:
133+
# - "@anthropic/mcp-server-sqlite"
134+
# - "${PROJECT_DIR}/data.db"
135+
# allowed_tools:
136+
# - read_query
137+
# - list_tables
138+
# - describe_table
139+
140+
# Example: Custom project-specific MCP server
141+
# - name: my_project_tools
142+
# command: python
143+
# args:
144+
# - "${PROJECT_DIR}/tools/mcp_server.py"
145+
# env:
146+
# PROJECT_ROOT: "${PROJECT_DIR}"
147+
# allowed_tools:
148+
# - build_project
149+
# - run_tests
150+
# - deploy_staging
151+
152+
106153
# ==========================================
107154
# Notes and Best Practices
108155
# ==========================================

0 commit comments

Comments
 (0)