Skip to main content
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

export DECLAW_API_KEY="your-api-key"
export DECLAW_DOMAIN="your-declaw-instance.example.com:8080"

Code

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()

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."}]}}
The server listens on 127.0.0.1 inside the sandbox — traffic stays entirely within the microVM. 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.