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