Skip to main content

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:
  1. Add tools to the built-in server via configuration
  2. Run additional MCP servers alongside the built-in one
  3. 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}"

3. Agent Uses Your Tools Automatically

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!

Example: Deployment 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()}` }] };
}

Example: Monitoring Tool

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.