# Custom Tools > Source: `docs/custom-tools.mdx` > Canonical URL: https://sandboxagent.dev/docs/custom-tools > Description: Give agents custom tools inside the sandbox using MCP servers or skills. --- There are two common patterns for sandbox-local custom tooling: | | MCP Server | Skill | |---|---|---| | **How it works** | Agent connects to an MCP server (`mcpServers`) | Agent follows `SKILL.md` instructions and runs scripts | | **Best for** | Typed tool calls and structured protocols | Lightweight task-specific guidance | | **Requires** | MCP server process (stdio/http/sse) | Script + `SKILL.md` | ## Option A: MCP server (stdio) ### Write and bundle your MCP server ```ts src/mcp-server.ts 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()); ``` ```bash npx esbuild src/mcp-server.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/mcp-server.cjs ``` ### Upload it into the sandbox ```ts 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); ``` ```bash 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 ``` ### Register MCP config and create a session ```ts 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." }, ]); ``` ## Option B: Skills ### Write script + skill file ```ts src/random-number.ts const min = Number(process.argv[2]); const max = Number(process.argv[3]); if (Number.isNaN(min) || Number.isNaN(max)) { console.error("Usage: random-number "); process.exit(1); } console.log(Math.floor(Math.random() * (max - min + 1)) + min); ``` ````md SKILL.md --- name: random-number description: Generate a random integer between min and max. --- Run: ```bash node /opt/skills/random-number/random-number.cjs ``` ```` ```bash npx esbuild src/random-number.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/random-number.cjs ``` ### Upload files ```ts 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); ``` ### Use in a session ```ts 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." }, ]); ``` ## Notes - The sandbox runtime must include Node.js (or your chosen runtime). - For persistent skill-source wiring by directory, see [Skills](/skills-config).