---
title: "Custom Tools"
description: "Give agents custom tools inside the sandbox using MCP servers or skills."
sidebarTitle: "Custom Tools"
icon: "wrench"
---

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)

<Steps>
  <Step title="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
```
  </Step>

  <Step title="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
```
  </Step>

  <Step title="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",
  sessionInit: {
    cwd: "/workspace",
  },
});

await session.prompt([
  { type: "text", text: "Use the random_number tool with min=1 and max=10." },
]);
```
  </Step>
</Steps>

## Option B: Skills

<Steps>
  <Step title="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 <min> <max>");
  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 <min> <max>
```
````

```bash
npx esbuild src/random-number.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/random-number.cjs
```
  </Step>

  <Step title="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);
```
  </Step>

  <Step title="Use in a session">

```ts
const session = await sdk.createSession({
  agent: "claude",
  sessionInit: {
    cwd: "/workspace",
  },
});

await session.prompt([
  { type: "text", text: "Use the random-number skill to pick a number from 1 to 100." },
]);
```
  </Step>
</Steps>

## Notes

- The sandbox runtime must include Node.js (or your chosen runtime).
- For persistent skill-source wiring by directory, see [Skills](/skills-config).
