Primus MCP / Tactus Runtime

Connect an MCP client to Primus through one programmable tool: execute_tactus. The tool runs sandboxed Tactus snippets that call the host-provided primus runtime module.

Why one tool?

Earlier MCP integrations often exposed every application operation as a separate tool. That works for small systems, but Primus has a broad surface area: scorecards, scores, feedback, evaluations, reports, datasets, procedures, documentation, budgets, and handles. Loading all of that as individual tool schemas consumes model context on every call, adding latency and cost while displacing the user task and other useful context.

Primus now exposes a compact gateway instead. The MCP client calls execute_tactus, and the submitted Tactus code composes the primus APIs it needs for the current task. The assistant writes a small program instead of choosing from a long menu of fine-grained tools.

The gateway also supports progressive disclosure. The base MCP context only needs to describe execute_tactus and the discovery path: use primus.api.list() to inspect the available API surface, then use primus.docs.list() and primus.docs.get{ ... } to load focused docs and examples for the workflow at hand.

This is the same pattern described on the Tactus site: One Tool For Everything.

Runtime model

Inside execute_tactus, primus is available as an injected global host module. It delegates to Primus SDK code, services, documentation, task dispatch, and handle storage. The Tactus runtime provides the controlled execution boundary around those calls.

  • Use primus.api.list() to discover namespaces and methods.
  • Use primus.docs.list() and primus.docs.get{ ... } to read focused docs during the session.
  • Use explicit return values when you want a custom result shape.
  • Long-running calls can return handles that are polled, awaited, or cancelled later.

Client setup

Configure your MCP client to launch the Primus wrapper from your local Primus checkout. Replace the placeholder paths with your Python environment and project path.

{
  "mcpServers": {
    "primus": {
      "command": "/path/to/python",
      "args": [
        "/path/to/Primus/MCP/primus_fastmcp_wrapper.py",
        "--transport", "stdio",
        "--target-cwd", "/path/to/Primus"
      ],
      "env": {
        "PYTHONUNBUFFERED": "1",
        "PYTHONPATH": "/path/to/Primus"
      }
    }
  }
}

Credentials are loaded from your Primus environment and config files. Keep API keys on the host side; they are not passed into the Tactus snippet.

Start with discovery

When unsure what the runtime supports, ask Primus from inside the runtime instead of guessing tool names.

return {
  apis = primus.api.list(),
  docs = primus.docs.list(),
  overview = primus.docs.get{ id = "mcp.execute-tactus-overview" },
}

Common examples

Inspect a score by scorecard and score identifiers:

return primus.score.info{
  scorecard_identifier = "Quality Assurance",
  score_identifier = "Compliance",
}

Combine feedback summary and item search in one call:

local summary = primus.feedback.alignment{
  scorecard_name = "Quality Assurance",
  score_name = "Compliance",
  days = 30,
  output_format = "json",
}

local false_negatives = primus.feedback.find{
  scorecard_name = "Quality Assurance",
  score_name = "Compliance",
  initial_value = "No",
  final_value = "Yes",
  limit = 5,
  days = 30,
}

return {
  summary = summary,
  false_negatives = false_negatives,
}

Async handles and budgets

Evaluations, reports, and procedures can be long-running. Use async = true to dispatch the work and return a handle. Include an explicit child budget so background work remains bounded.

local handle = primus.evaluation.run{
  scorecard_name = "Quality Assurance",
  score_name = "Compliance",
  n_samples = 200,
  yaml = true,
  async = true,
  budget = {
    usd = 1.0,
    wallclock_seconds = 900,
    depth = 1,
    tool_calls = 20,
  },
}

return {
  handle_id = handle.id,
  status = handle.status,
}

Later, use the handle APIs from another execute_tactuscall:

return primus.handle.await{
  id = "<handle-id>",
  timeout = "PT10M",
}

Safety contract

  • The MCP surface stays small: clients only need to know execute_tactus.
  • The runtime returns structured envelopes with success, value, error, cost, trace, partial, and API-call data.
  • Destructive operations request human approval before committing changes.
  • Traces and handles let operators inspect what happened and resume long-running work.
  • Primus keeps credentials, SDK implementation, policy, and persistence on the trusted host side.

Troubleshooting

  • If the MCP client cannot connect, verify the Python path, wrapper path, --target-cwd, and PYTHONPATH.
  • If a Primus call fails, inspect the returned structured error and trace ID before retrying.
  • If a snippet needs a capability you cannot find, call primus.api.list() and then read the relevant docs with primus.docs.get{ id = "..." }.