ACE MCP Server

March 27, 2026 · View on GitHub

ACE (Agentic Context Engine) provides an optional MCP server that exposes ACE as a tool provider over the Model Context Protocol. Orchestration frameworks, IDEs (Cursor, Windsurf, Claude Code), and other MCP clients can connect to the server and use ACE skills at runtime.

The integration is fully opt-in. Installing ACE without the mcp extra does not pull in the MCP SDK, and attempting to start ace-mcp without that extra will fail with an install hint instead of breaking normal ACE imports.

Need client-specific setup steps? See the MCP Client Setup guide.

Installation

uv add "ace-framework[mcp]"

Running the Server

Start the server using the provided CLI entrypoint:

ace-mcp

By default it communicates over stdio, making it ready for integration as a local tool provider in any MCP-compatible client.

Example with a specific model:

ACE_MCP_DEFAULT_MODEL=bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0 ace-mcp

Configuration

All settings are read from environment variables with the ACE_MCP_ prefix:

VariableDefaultDescription
ACE_MCP_DEFAULT_MODELgpt-4o-miniLiteLLM model identifier used when creating new sessions. Any LiteLLM-supported model works (e.g. bedrock/..., anthropic/..., openai/...).
ACE_MCP_SAFE_MODEfalseWhen true, blocks ace.learn.sample, ace.learn.feedback, ace.skillbook.save, and ace.skillbook.load. Read-only tools (ace.ask, ace.skillbook.get) remain available.
ACE_MCP_ALLOW_SAVE_LOADtrueWhen false, blocks ace.skillbook.save and ace.skillbook.load independently of safe mode.
ACE_MCP_MAX_SAMPLES_PER_CALL25Maximum number of samples accepted in a single ace.learn.sample call.
ACE_MCP_MAX_PROMPT_CHARS100000Maximum total characters across question + context fields.
ACE_MCP_SESSION_TTL_SECONDS3600Idle time (seconds) before a session is garbage-collected.
ACE_MCP_LEARN_TIMEOUT_SECONDS300Maximum seconds for a single ace.learn.sample or ace.learn.feedback call before returning ACE_MCP_TIMEOUT.
ACE_MCP_SKILLBOOK_ROOTunsetIf set, ace.skillbook.save and ace.skillbook.load reject paths outside this directory.
ACE_MCP_LOG_LEVELINFOServer log level (DEBUG, INFO, WARNING, ERROR). Logs go to stderr.

Tools

ace.ask

Ask a question using the current skillbook. Does not mutate the skillbook.

FieldTypeRequiredDescription
session_idstringyesSession identifier for state isolation.
questionstringyesThe question to answer.
contextstringnoAdditional context for the question.
session_configobjectnoOverride model, temperature, max_tokens for this session.

Returns: answer, skill_count.

ace.learn.sample

Provide sample question/answer pairs for ACE to learn from. Runs the full ACE pipeline (Agent, Evaluate, Reflect, Update, Apply).

FieldTypeRequiredDescription
session_idstringyesSession identifier.
samplesarrayyesList of {question, context?, ground_truth?, metadata?} items (1–25).
epochsintnoNumber of learning passes (default: 1, max: 20).
session_configobjectnoOverride model settings.

Returns: processed, failed, skill_count_before, skill_count_after, new_skill_count.

Blocked by: ACE_MCP_SAFE_MODE=true.

ace.learn.feedback

Provide feedback on a previous answer. If a prior ace.ask exists for the session, learns directly from that interaction. Otherwise, builds a trace from the provided fields and learns from it.

FieldTypeRequiredDescription
session_idstringyesSession identifier.
questionstringyesThe original question.
answerstringyesThe answer being evaluated.
feedbackstringyesFeedback about answer quality.
contextstringnoOriginal context.
ground_truthstringnoThe correct answer.
session_configobjectnoOverride model settings.

Returns: learned (always true on success — the learning path executed), skill_count_before, skill_count_after, new_skill_count.

Blocked by: ACE_MCP_SAFE_MODE=true.

ace.skillbook.get

Retrieve skills and statistics from the active skillbook.

FieldTypeRequiredDescription
session_idstringyesSession identifier.
limitintnoMax skills to return (default: 20, max: 200).
include_invalidboolnoInclude invalidated skills (default: false).

Returns: stats, skills[] (each with id, content, topic, helpful, harmful, neutral).

ace.skillbook.save

Save the session's skillbook to a file on disk.

FieldTypeRequiredDescription
session_idstringyesSession identifier.
pathstringyesFile path to save to.

Returns: path (resolved absolute path), saved_skill_count.

Blocked by: ACE_MCP_SAFE_MODE=true (ACE_MCP_FORBIDDEN_IN_SAFE_MODE) or ACE_MCP_ALLOW_SAVE_LOAD=false (ACE_MCP_SAVE_LOAD_DISABLED). The path is resolved to a canonical absolute path before validation and file I/O. Must be under ACE_MCP_SKILLBOOK_ROOT if configured.

ace.skillbook.load

Load a skillbook from disk into the session, replacing the current one.

FieldTypeRequiredDescription
session_idstringyesSession identifier.
pathstringyesFile path to load from.

Returns: path (resolved absolute path), skill_count.

Blocked by: ACE_MCP_SAFE_MODE=true (ACE_MCP_FORBIDDEN_IN_SAFE_MODE) or ACE_MCP_ALLOW_SAVE_LOAD=false (ACE_MCP_SAVE_LOAD_DISABLED). The path is resolved to a canonical absolute path before validation and file I/O. Must be under ACE_MCP_SKILLBOOK_ROOT if configured.

Session Model

All state is isolated by session_id. Each session holds its own ACELiteLLM runner with an independent skillbook. Pass the same session_id across calls to persist context within server memory.

Sessions are garbage-collected after ACE_MCP_SESSION_TTL_SECONDS of inactivity. Per-session locks ensure concurrent requests to the same session are serialised.

Any tool that accepts session_config can override the model, temperature, and max_tokens for that session's runner on first creation. Once a session exists, session_config on subsequent calls is used only if it creates a new session.

Architecture

The MCP server does not use custom pipeline steps. It is a thin async layer over ACELiteLLM:

MCP Client (stdio)
  → MCP SDK (handles JSON-RPC framing)
    → adapters.py (tool registration, schema generation, error mapping)
      → handlers.py (validation, session management, safety guards)
        → ACELiteLLM (sync runner — bridged via asyncio.to_thread)
          → Pipeline (internal — handles step execution, async_boundary, background learning)

The handlers use asyncio.to_thread() to call the sync ACELiteLLM methods from the async MCP event loop. This is the standard Python pattern for bridging async callers to sync APIs. The pipeline engine handles all internal async concerns (step-level to_thread, async_boundary for background learning) transparently.

Testing with the MCP Inspector

The MCP Inspector provides a web UI for testing MCP servers interactively:

npx @modelcontextprotocol/inspector uv run ace-mcp

Set ACE_MCP_DEFAULT_MODEL in the Inspector's environment variables panel before connecting.

File Layout

ace/integrations/mcp/
  __init__.py       ← Package marker
  server.py         ← Server creation and CLI entrypoint
  config.py         ← MCPServerConfig (pydantic-settings, env vars)
  registry.py       ← SessionRegistry (session lifecycle, TTL sweep)
  handlers.py       ← MCPHandlers (validation, safety, delegation to ACELiteLLM)
  adapters.py       ← MCP SDK glue (tool registration, schema inlining, error mapping)
  models.py         ← Pydantic request/response models for all six tools
  errors.py         ← ACEMCPError hierarchy and MCP error mapping