Options for making a CodeExecutionServer¶
CodeExecutionServer is the base class for all MCP code execution servers in Agora Workbench. This guide covers the different ways to configure and instantiate one.
New to Agora Workbench?¶
The Your First Server tutorial walks you through building a server with a real tool step-by-step before diving into the full reference below.
Minimal server¶
The simplest server needs only a ServerConfig with a name, description, environment type, and dependency specification:
import asyncio
from agora_workbench.code_execution import CodeExecutionServer, ServerConfig
from agora_workbench.code_execution.auth import create_noop_auth_config
config = ServerConfig(
name="myserver",
description="Execute Python code with numpy and pandas.",
type="uv",
dependency_file="numpy\npandas\n",
)
server = CodeExecutionServer(server_config=config, auth_config=create_noop_auth_config())
if __name__ == "__main__":
asyncio.run(server.run_http(host="0.0.0.0", port=8000))
This gives you an MCP server with an execute_myserver_code tool that runs Python in an isolated environment with numpy and pandas available.
Environment types¶
The type field on ServerConfig controls how the Python environment is managed:
| Type | Dependency format | Best for |
|---|---|---|
"uv" |
requirements.txt content |
Fast installs, pure-Python packages |
"conda" |
environment.yml content |
Scientific packages with native deps (RDKit, GDAL, etc.) |
"pip" |
requirements.txt content |
Legacy environments or Docker-baked deps |
Environment model¶
CodeExecutionServer always runs code in an isolated kernel environment built from ServerConfig.dependency_file, regardless of type (uv, conda, or pip).
- For
type="uv"andtype="pip",dependency_filecontainsrequirements.txtcontent - For
type="conda",dependency_filecontainsenvironment.ymlcontent - For native/compiled dependencies in conda environments (for example
ngspice,gdal,netcdf4), useconda-forgerather than system package managers likeapt-get
Heavy models load per session
Each execute_{name}_code session runs in its own kernel process, so
anything a tool loads — including a multi-gigabyte model — is loaded once
per session. N concurrent sessions means N copies in RAM, which readily
exhausts a modest host. If your environment includes a large model or other
expensive process-global state, load it once in a sidecar
and share it across sessions instead.
Example: conda environment¶
config = ServerConfig(
name="chemistry",
description="RDKit cheminformatics environment.",
type="conda",
dependency_file="""\
name: chemistry
channels:
- conda-forge
dependencies:
- python=3.11
- rdkit
- numpy
- pandas
""",
)
Minimal native-dependency example:
config = ServerConfig(
name="circuits",
description="Conda environment with compiled/native dependencies.",
type="conda",
dependency_file="""\
name: circuits
channels:
- conda-forge
dependencies:
- python=3.11
- ngspice
- gdal
""",
)
Adding domain tools¶
Pass a ToolRegistry to expose typed domain tools that are discoverable via search_{name}_tools and callable through code execution:
from agora_workbench.code_execution import CodeExecutionServer, ServerConfig, ToolRegistry
from agora_workbench.code_execution.auth import create_noop_auth_config
from my_domain.tools import MY_TOOLS # list of ToolDefinition objects
registry = ToolRegistry()
for tool in MY_TOOLS:
registry.register_tool(tool)
server = CodeExecutionServer(
server_config=config,
tool_registry=registry,
auth_config=create_noop_auth_config(),
)
See Tool pattern for how to define ToolDefinition objects.
Subclassing for custom behavior¶
Subclass CodeExecutionServer to add preprocessing, custom hooks, or environment setup:
class ChemistryServer(CodeExecutionServer):
"""Inject common imports before every code execution."""
def preprocess_code(self, code: str) -> str:
return "from rdkit import Chem\nimport numpy as np\n" + code
Configuration reference¶
ServerConfig has six groups of settings:
Identity¶
| Field | Description |
|---|---|
name |
Server/environment name (becomes part of the MCP tool name) |
description |
Capabilities description (shown to the agent in the tool description) |
server_description |
Optional server-level description (FastMCP instructions) |
entra_client_id |
Entra ID app registration client ID |
entra_tenant_id |
Azure AD tenant ID |
Environment¶
| Field | Description |
|---|---|
type |
"uv", "conda", or "pip" |
dependency_file |
Content of the dependency file |
auto_build |
Build environment on startup if missing (default: True) |
build_dir |
Custom build directory (default: ~/.cache/mcp-envs/{name}) |
additional_commands |
Extra shell commands after env setup |
Assets¶
| Field | Description |
|---|---|
assets |
List of AssetSpec objects provisioned into the env cache at startup (see Working with data) |
auto_provision |
Fetch the declared assets on startup (default: True) |
Sidecars¶
| Field | Description |
|---|---|
sidecars |
List of SidecarConfig objects — long-lived helper processes launched at startup and stopped on shutdown, used to load an expensive resource (e.g. a model) once and share it across all kernel sessions over loopback HTTP (see Sidecars) |
Execution¶
| Field | Description |
|---|---|
execution_mode |
"sync" (default), "async_only", or "adaptive" — controls whether execute_{name}_code blocks, always backgrounds, or auto-promotes long-running calls |
promotion_threshold_s |
Seconds before adaptive mode promotes to background (default: 60) |
max_timeout |
Maximum allowed timeout per execution (default: 600s) |
default_timeout |
Default timeout (default: 300s) |
output_truncation_threshold |
Max chars in stdout/stderr before truncation |
parallel_max_concurrency |
Max parallel executions (0 = unlimited) |
Features¶
| Field | Description |
|---|---|
tool_search_backend |
"bm25" (default) or "azure_ai_search" |
Authentication¶
Pass an AuthConfig to control how requests are authenticated:
from agora_workbench.code_execution.auth import create_noop_auth_config
from agora_workbench.code_execution.auth.entra import create_entra_auth_config
# For local development (no auth):
server = CodeExecutionServer(server_config=config, auth_config=create_noop_auth_config())
# For production with Entra ID:
server = CodeExecutionServer(server_config=config, auth_config=create_entra_auth_config())
See Authentication options for the full auth guide.
Publishers (artifact output)¶
Configure publishers to allow the agent to publish artifacts (files, plots) from code execution:
from agora_workbench.code_execution.auth import create_noop_auth_config
from agora_workbench.code_execution.data_access import LocalFilePublisher, BlobPublisher
publishers = [
LocalFilePublisher(base_dir="/tmp/artifacts"),
BlobPublisher(account_url="https://myaccount.blob.core.windows.net", container="outputs"),
]
server = CodeExecutionServer(
server_config=config,
auth_config=create_noop_auth_config(),
publishers=publishers,
)
Running the server¶
import asyncio
# HTTP mode (standard deployment)
asyncio.run(server.run_http(host="0.0.0.0", port=8000))
# Warm up environment without serving (useful in Docker builds)
asyncio.run(server.warm())
The server exposes:
/mcp— MCP endpoint (SSE/streamable HTTP)/health— health check/.well-known/oauth-protected-resource— RFC 9728 OAuth protected resource metadata (when auth is enabled)