> ## Documentation Index
> Fetch the complete documentation index at: https://docs.declaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# mcp-server — FastMCP hello-tool

> Boot a FastMCP server inside a sandbox, register a tool, and call it over HTTP — the minimum end-to-end MCP flow.

The `mcp-server` template ships `Python 3`, `Node.js 20`, `fastmcp`, the
reference `mcp` SDK, `uvicorn`, `FastAPI`, `Flask`, `requests`, `httpx`,
and `pydantic`. Pick it when you need an isolated runtime to host or
validate an MCP server — a PR-review tool, an internal-docs reader, a
per-tenant agent tool surface, etc.

## What you'll learn

* Picking `template="mcp-server"` so the MCP toolchain is already installed
* Writing a tiny FastMCP server with one tool
* Starting it as a background process and calling it over HTTP inside the sandbox

## Prerequisites

<Snippet file="snippets/env-setup.mdx" />

## Code

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import textwrap
    import time

    from declaw import Sandbox


    SERVER = textwrap.dedent("""
        # /tmp/server.py — minimal FastMCP server with one tool.
        from fastmcp import FastMCP

        mcp = FastMCP("declaw-demo")

        @mcp.tool
        def greet(name: str) -> str:
            \"\"\"Return a friendly greeting.\"\"\"
            return f"Hello, {name}! From the declaw mcp-server sandbox."

        if __name__ == "__main__":
            mcp.run(transport="http", host="127.0.0.1", port=8765)
    """)

    CLIENT = textwrap.dedent("""
        # /tmp/client.py — calls the running server's greet tool via JSON-RPC.
        import json, sys, urllib.request

        def rpc(method, params=None, _id=1):
            req = {"jsonrpc": "2.0", "id": _id, "method": method}
            if params is not None:
                req["params"] = params
            body = json.dumps(req).encode()
            r = urllib.request.Request(
                "http://127.0.0.1:8765/mcp",
                data=body,
                headers={"Content-Type": "application/json", "Accept": "application/json, text/event-stream"},
                method="POST",
            )
            with urllib.request.urlopen(r, timeout=5) as resp:
                return resp.read().decode()

        print("-- initialize --")
        print(rpc("initialize", {"protocolVersion": "2024-11-05",
                                  "capabilities": {}, "clientInfo": {"name": "demo", "version": "0"}}))
        print("-- tools/call greet --")
        print(rpc("tools/call",
                  {"name": "greet", "arguments": {"name": "world"}},
                  _id=2))
    """)


    def main() -> None:
        sbx = Sandbox.create(template="mcp-server", timeout=180)
        try:
            sbx.files.write("/tmp/server.py", SERVER)
            sbx.files.write("/tmp/client.py", CLIENT)

            # Start the server in the background, then wait a beat for boot.
            handle = sbx.commands.run("python3 /tmp/server.py", background=True)
            print(f"server pid: {handle.pid}")
            time.sleep(2.0)

            r = sbx.commands.run("python3 /tmp/client.py", timeout=15)
            print(r.stdout)
            if r.exit_code != 0:
                print("client errors:", r.stderr)
        finally:
            sbx.kill()


    if __name__ == "__main__":
        main()
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    import "dotenv/config";
    import { Sandbox } from "@declaw/sdk";

    const SERVER = `
    from fastmcp import FastMCP

    mcp = FastMCP("declaw-demo")

    @mcp.tool
    def greet(name: str) -> str:
        """Return a friendly greeting."""
        return f"Hello, {name}! From the declaw mcp-server sandbox."

    if __name__ == "__main__":
        mcp.run(transport="http", host="127.0.0.1", port=8765)
    `;

    const CLIENT = `
    import json, urllib.request

    def rpc(method, params=None, _id=1):
        req = {"jsonrpc": "2.0", "id": _id, "method": method}
        if params is not None:
            req["params"] = params
        body = json.dumps(req).encode()
        r = urllib.request.Request(
            "http://127.0.0.1:8765/mcp",
            data=body,
            headers={"Content-Type": "application/json", "Accept": "application/json, text/event-stream"},
            method="POST",
        )
        with urllib.request.urlopen(r, timeout=5) as resp:
            return resp.read().decode()

    print("-- initialize --")
    print(rpc("initialize", {"protocolVersion": "2024-11-05",
                              "capabilities": {}, "clientInfo": {"name": "demo", "version": "0"}}))
    print("-- tools/call greet --")
    print(rpc("tools/call",
              {"name": "greet", "arguments": {"name": "world"}},
              _id=2))
    `;

    async function main(): Promise<void> {
      const sbx = await Sandbox.create({ template: "mcp-server", timeout: 180 });
      try {
        await sbx.files.write("/tmp/server.py", SERVER);
        await sbx.files.write("/tmp/client.py", CLIENT);

        const handle = await sbx.commands.run("python3 /tmp/server.py", { background: true });
        console.log(`server pid: ${handle.pid}`);
        await new Promise((r) => setTimeout(r, 2000));

        const r = await sbx.commands.run("python3 /tmp/client.py", { timeout: 15 });
        console.log(r.stdout);
        if (r.exitCode !== 0) {
          console.log("client errors:", r.stderr);
        }
      } finally {
        await sbx.kill();
      }
    }

    main().catch(console.error);
    ```
  </Tab>
</Tabs>

## Expected output

The second block is the `tools/call` response — note the `greet` output
inside `result.content[0].text`:

```
-- initialize --
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{...}, ...}}
-- tools/call greet --
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Hello, world! From the declaw mcp-server sandbox."}]}}
```

<Note>
  The server listens on `127.0.0.1` inside the sandbox — traffic stays
  entirely within the sandbox. To expose the MCP server to an external
  agent you'd typically run it behind an HTTPS reverse proxy or embed
  the tool via the declaw sandbox's own `commands.run`.
</Note>
