Skip to content

Commit bcf082a

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 dc5bcc4 commit bcf082a

File tree

7 files changed

+1039
-3
lines changed

7 files changed

+1039
-3
lines changed

CLAUDE.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,61 @@ blocked_commands:
326326
- `examples/README.md` - Comprehensive guide with use cases, testing, and troubleshooting
327327
- `PHASE3_SPEC.md` - Specification for mid-session approval feature (future enhancement)
328328

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

client.py

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,17 @@
1111
import shutil
1212
import sys
1313
from pathlib import Path
14+
from typing import Any
1415

1516
from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
1617
from claude_agent_sdk.types import HookContext, HookInput, HookMatcher, SyncHookJSONOutput
1718
from dotenv import load_dotenv
1819

19-
from security import bash_security_hook
20+
from security import (
21+
bash_security_hook,
22+
get_effective_mcp_servers,
23+
get_effective_mcp_tools,
24+
)
2025

2126
# Load environment variables from .env file if present
2227
load_dotenv()
@@ -209,6 +214,37 @@ def get_extra_read_paths() -> list[Path]:
209214
return validated_paths
210215

211216

217+
def substitute_variables(value: str, project_dir: Path) -> str:
218+
"""
219+
Substitute variables in a configuration value.
220+
221+
Supported variables:
222+
- ${PROJECT_DIR} - Absolute path to project directory
223+
- ${HOME} - User's home directory
224+
225+
Args:
226+
value: String value that may contain variables
227+
project_dir: Path to the project directory
228+
229+
Returns:
230+
String with variables substituted
231+
"""
232+
result = value
233+
result = result.replace("${PROJECT_DIR}", str(project_dir.resolve()))
234+
result = result.replace("${HOME}", str(Path.home()))
235+
return result
236+
237+
238+
def substitute_variables_in_list(values: list[str], project_dir: Path) -> list[str]:
239+
"""Substitute variables in a list of strings."""
240+
return [substitute_variables(v, project_dir) for v in values]
241+
242+
243+
def substitute_variables_in_dict(values: dict[str, str], project_dir: Path) -> dict[str, str]:
244+
"""Substitute variables in a dict of strings."""
245+
return {k: substitute_variables(v, project_dir) for k, v in values.items()}
246+
247+
212248
# Feature MCP tools for feature/test management
213249
FEATURE_MCP_TOOLS = [
214250
# Core feature operations
@@ -308,12 +344,21 @@ def create_client(
308344
Note: Authentication is handled by start.bat/start.sh before this runs.
309345
The Claude SDK auto-detects credentials from the Claude CLI configuration
310346
"""
347+
# Load user-configured MCP servers from org and project configs
348+
user_mcp_servers, blocked_mcp_tools = get_effective_mcp_servers(project_dir)
349+
user_mcp_tools, user_mcp_permissions = get_effective_mcp_tools(
350+
user_mcp_servers, blocked_mcp_tools
351+
)
352+
311353
# Build allowed tools list based on mode
312354
# In YOLO mode, exclude Playwright tools for faster prototyping
313355
allowed_tools = [*BUILTIN_TOOLS, *FEATURE_MCP_TOOLS]
314356
if not yolo_mode:
315357
allowed_tools.extend(PLAYWRIGHT_TOOLS)
316358

359+
# Add user-configured MCP tools
360+
allowed_tools.extend(user_mcp_tools)
361+
317362
# Build permissions list
318363
permissions_list = [
319364
# Allow all file operations within the project directory
@@ -345,6 +390,9 @@ def create_client(
345390
# Allow Playwright MCP tools for browser automation (standard mode only)
346391
permissions_list.extend(PLAYWRIGHT_TOOLS)
347392

393+
# Add user-configured MCP tool permissions
394+
permissions_list.extend(user_mcp_permissions)
395+
348396
# Create comprehensive security settings
349397
# Note: Using relative paths ("./**") restricts access to project directory
350398
# since cwd is set to project_dir
@@ -372,10 +420,22 @@ def create_client(
372420
if extra_read_paths:
373421
print(f" - Extra read paths (validated): {', '.join(str(p) for p in extra_read_paths)}")
374422
print(" - Bash commands restricted to allowlist (see security.py)")
375-
if yolo_mode:
423+
424+
# Report MCP servers
425+
builtin_servers = ["features (database)"]
426+
if not yolo_mode:
427+
builtin_servers.insert(0, "playwright (browser)")
428+
user_server_names = [s["name"] for s in user_mcp_servers]
429+
if user_server_names:
430+
all_servers = builtin_servers + [f"{name} (custom)" for name in user_server_names]
431+
print(f" - MCP servers: {', '.join(all_servers)}")
432+
elif yolo_mode:
376433
print(" - MCP servers: features (database) - YOLO MODE (no Playwright)")
377434
else:
378435
print(" - MCP servers: playwright (browser), features (database)")
436+
437+
if blocked_mcp_tools:
438+
print(f" - Blocked MCP tools (org): {', '.join(sorted(blocked_mcp_tools))}")
379439
print(" - Project settings enabled (skills, commands, CLAUDE.md)")
380440
print()
381441

@@ -426,6 +486,35 @@ def create_client(
426486
"args": playwright_args,
427487
}
428488

489+
# Add user-configured MCP servers from org and project configs
490+
for server_config in user_mcp_servers:
491+
server_name = server_config["name"]
492+
493+
# Skip if server name conflicts with built-in servers
494+
if server_name in mcp_servers:
495+
print(f" - Warning: Custom MCP server '{server_name}' conflicts with built-in, skipping")
496+
continue
497+
498+
# Build server entry with variable substitution
499+
server_entry: dict[str, Any] = {
500+
"command": substitute_variables(server_config["command"], project_dir),
501+
}
502+
503+
# Add args with variable substitution
504+
if "args" in server_config:
505+
server_entry["args"] = substitute_variables_in_list(
506+
server_config["args"], project_dir
507+
)
508+
509+
# Add env with variable substitution, merging with parent environment
510+
if "env" in server_config:
511+
server_entry["env"] = substitute_variables_in_dict(
512+
server_config["env"], project_dir
513+
)
514+
515+
mcp_servers[server_name] = server_entry
516+
print(f" - Custom MCP server '{server_name}': {server_config['command']}")
517+
429518
# Build environment overrides for API endpoint configuration
430519
# These override system env vars for the Claude CLI subprocess,
431520
# 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)