Custom MCP Tools
Extend the agent’s capabilities by adding custom MCP (Model Context Protocol) tools to your sandbox. This lets agents interact with databases, APIs, deployment systems, or any custom logic.
Prerequisites
- Caged CLI installed
- A running sandbox
- Understanding of MCP protocol
How MCP Works in Caged
Every Caged sandbox runs the Caged MCP Server by default, providing filesystem, terminal, and git tools. You can:
- Add tools to the built-in server via configuration
- Run additional MCP servers alongside the built-in one
- Connect external MCP servers via WebSocket
Adding a Custom MCP Server
1. Create Your MCP Server
// tools/database-tools/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({ name: "database-tools", version: "1.0.0" }, {
capabilities: { tools: {} }
});
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "query_database",
description: "Run a read-only SQL query against the project database",
inputSchema: {
type: "object",
properties: {
sql: { type: "string", description: "SQL SELECT query" }
},
required: ["sql"]
}
},
{
name: "list_tables",
description: "List all tables in the database",
inputSchema: { type: "object", properties: {} }
}
]
}));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
if (name === "query_database") {
// Only allow SELECT queries (safety!)
if (!args.sql.trim().toUpperCase().startsWith("SELECT")) {
return { content: [{ type: "text", text: "Error: Only SELECT queries allowed" }] };
}
// Execute against your database...
const result = await db.query(args.sql);
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
}
if (name === "list_tables") {
const tables = await db.query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
return { content: [{ type: "text", text: JSON.stringify(tables, null, 2) }] };
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
2. Include in Your Sandbox Config
# .caged.yaml
template: node-20
resources:
cpu: 2
memory: 2048
secrets:
- ANTHROPIC_API_KEY
- DATABASE_URL
init_script: |
npm install
cd tools/database-tools && npm install
# Custom MCP servers to start alongside the built-in one
mcp_servers:
- name: database-tools
command: "node tools/database-tools/index.ts"
env:
DATABASE_URL: "${DATABASE_URL}"
When Claude Code (or any MCP-compatible agent) connects to the sandbox, it discovers your custom tools alongside the built-in ones:
Available tools:
- read_file (built-in)
- write_file (built-in)
- run_terminal (built-in)
- query_database (custom) ← your tool!
- list_tables (custom) ← your tool!
Give the agent the ability to deploy (with guardrails):
// tools/deploy/index.ts
const tools = [
{
name: "deploy_staging",
description: "Deploy the current branch to staging environment",
inputSchema: {
type: "object",
properties: {
branch: { type: "string" },
message: { type: "string", description: "Deploy message" }
},
required: ["branch", "message"]
}
},
{
name: "check_staging_health",
description: "Check if staging is healthy after deploy",
inputSchema: { type: "object", properties: {} }
}
];
// Handler
if (name === "deploy_staging") {
// Safety: only allow staging, never production
const result = await fetch("https://your-ci.com/api/deploy", {
method: "POST",
headers: { "Authorization": `Bearer ${process.env.CI_TOKEN}` },
body: JSON.stringify({
environment: "staging", // hardcoded — agent can't deploy to prod
branch: args.branch,
message: args.message
})
});
return { content: [{ type: "text", text: `Deploy triggered: ${await result.text()}` }] };
}
Let the agent check service health:
# .caged.yaml
mcp_servers:
- name: monitoring
command: "node tools/monitoring/index.ts"
env:
DATADOG_API_KEY: "${DATADOG_API_KEY}"
GRAFANA_URL: "${GRAFANA_URL}"
// tools/monitoring/index.ts — Agent can check metrics
const tools = [
{ name: "get_error_rate", description: "Get current error rate for a service" },
{ name: "get_latency_p99", description: "Get P99 latency for a service" },
{ name: "get_recent_alerts", description: "Get alerts from the last hour" }
];
Connecting External MCP Servers via WebSocket
For tools that can’t run inside the sandbox (e.g., they need access to your internal network):
# Get the sandbox's MCP WebSocket endpoint
curl https://api.caged.dev/v1/sandboxes/cage-a1b2c3d4/mcp \
-H "Authorization: Bearer caged_sk_..."
# Response:
# {"websocket_url": "wss://mcp.caged.dev/cage-a1b2c3d4"}
Connect your external MCP server to this WebSocket to make its tools available inside the sandbox.
Tips
Security first: Custom tools run inside the sandbox. They can only access what you explicitly provide (env vars, network access). Use network_mode: allowlist to restrict what external systems the tool can reach.
Read-only by default: Make your tools read-only unless the agent truly needs write access. A query_database tool should only allow SELECT.
Hardcode safety limits: Don’t let the agent choose deployment targets or delete production data. Hardcode environment names and add confirmation steps.
Test tools independently: Run your MCP server locally with npx @modelcontextprotocol/inspector before deploying it to a sandbox.