Skip to content
Agent

Custom Tools

There are two common patterns for sandbox-local custom tooling:

MCP ServerSkill
How it worksAgent connects to an MCP server (mcpServers)Agent follows SKILL.md instructions and runs scripts
Best forTyped tool calls and structured protocolsLightweight task-specific guidance
RequiresMCP server process (stdio/http/sse)Script + SKILL.md
  1. Write and bundle your MCP server

    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import { z } from "zod";
    const server = new McpServer({ name: "rand", version: "1.0.0" });
    server.tool(
    "random_number",
    "Generate a random integer between min and max",
    {
    min: z.number(),
    max: z.number(),
    },
    async ({ min, max }) => ({
    content: [{ type: "text", text: String(Math.floor(Math.random() * (max - min + 1)) + min) }],
    }),
    );
    await server.connect(new StdioServerTransport());
    Terminal window
    npx esbuild src/mcp-server.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/mcp-server.cjs
  2. Upload it into the sandbox

    import { SandboxAgent } from "sandbox-agent";
    import fs from "node:fs";
    const sdk = await SandboxAgent.connect({ baseUrl: "http://127.0.0.1:2468" });
    const content = await fs.promises.readFile("./dist/mcp-server.cjs");
    await sdk.writeFsFile({ path: "/opt/mcp/custom-tools/mcp-server.cjs" }, content);
    Terminal window
    curl -X PUT "http://127.0.0.1:2468/v1/fs/file?path=/opt/mcp/custom-tools/mcp-server.cjs" \
    --data-binary @./dist/mcp-server.cjs
  3. Register MCP config and create a session

    await sdk.setMcpConfig(
    {
    directory: "/workspace",
    mcpName: "customTools",
    },
    {
    type: "local",
    command: "node",
    args: ["/opt/mcp/custom-tools/mcp-server.cjs"],
    },
    );
    const session = await sdk.createSession({
    agent: "claude",
    cwd: "/workspace",
    });
    await session.prompt([
    { type: "text", text: "Use the random_number tool with min=1 and max=10." },
    ]);
  1. Write script + skill file

    const min = Number(process.argv[2]);
    const max = Number(process.argv[3]);
    if (Number.isNaN(min) || Number.isNaN(max)) {
    console.error("Usage: random-number <min> <max>");
    process.exit(1);
    }
    console.log(Math.floor(Math.random() * (max - min + 1)) + min);
    ---
    name: random-number
    description: Generate a random integer between min and max.
    ---
    Run:
    ```bash
    node /opt/skills/random-number/random-number.cjs <min> <max>
    ```
    Terminal window
    npx esbuild src/random-number.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/random-number.cjs
  2. Upload files

    import fs from "node:fs";
    const script = await fs.promises.readFile("./dist/random-number.cjs");
    await sdk.writeFsFile({ path: "/opt/skills/random-number/random-number.cjs" }, script);
    const skill = await fs.promises.readFile("./SKILL.md");
    await sdk.writeFsFile({ path: "/opt/skills/random-number/SKILL.md" }, skill);
  3. Use in a session

    const session = await sdk.createSession({
    agent: "claude",
    cwd: "/workspace",
    });
    await session.prompt([
    { type: "text", text: "Use the random-number skill to pick a number from 1 to 100." },
    ]);
  • The sandbox runtime must include Node.js (or your chosen runtime).
  • For persistent skill-source wiring by directory, see Skills.