Skip to content

Agent integration (MCP)

PyCharter ships an MCP server that serves governed context packs from your vocabulary and contract store to AI agents — Claude Code, Claude Desktop, or any MCP client. The context an agent receives is bound to the same concepts and contracts that PyCharter enforces at runtime, so what the agent is told about your data is what your validators actually check.

Several vendors ship agent-context surfaces over their metadata; PyCharter's is the open-source one tied to runtime-enforced contracts.

Status: supported

The MCP server is a supported surface of the frozen interface: the tool names and result shapes documented here follow the API Stability & Deprecation Policy like any other public API.

Prerequisites

pip install pycharter[ai]

Start it with pycharter mcp serve (equivalently python -m pycharter.mcp). The vocabulary/contract tools need a configured PyCharter database (standard configuration, e.g. PYCHARTER_DATABASE_URL); the introspection tools — list_capabilities, describe_capability, get_schema, validate_pipeline_config — work without one:

pycharter db init sqlite:///pycharter.db        # once
export PYCHARTER_DATABASE_URL=sqlite:///pycharter.db
python -m pycharter.mcp                          # JSON-RPC over stdio

The nine tools

Introspection & validation (no database required):

Tool What it returns Required arguments
list_capabilities Registered step / extractor / loader types with aliases and summaries — discover what a pipeline can be built from — plus the agent tools (kind: agent_tool) with their read/write tier — (kind filter optional)
describe_capability One capability with its config JSON Schema when a strict model exists kind, name
get_schema The published pycharter.io/v2 pipeline-manifest JSON Schema — generate configs against it, validate before running artifact ("pipeline")
validate_pipeline_config {valid: true, steps: N} or {valid: false, issues: [...]} with machine-repairable {code, step_id, field, allowed, fix} entries (including did-you-mean suggestions) config (object or YAML text)

Vocabulary & contracts (require a configured database):

Tool What it returns Required arguments
fetch_concept_pack A governed context pack for one concept — neighbours, relationships, current bindings concept_id (UUID)
list_concepts Paginated concept listing; discovery step before fetching a full pack — (scheme_id filter and limit ≤ 500 optional)
query_context Context pack of concepts whose names match a substring — deterministic, case-insensitive, no LLM query (limit ≤ 200 optional)
validate_record Verdict for one JSON record against a stored contract — is_valid plus per-field diagnostics contract_ref, record (namespace optional)
explain_field What a contract field means — its concept binding, the bound concept's card, and the field's governed context pack contract_ref, field_path

Guarded writes (opt-in, approval-gated): propose_contract_version, commit_contract_version, and bind_field_to_concept are neither advertised nor callable unless the server runs with a policy that sets allow_write: true (--config agent-tools.yaml). See Agent tool policy & guarded writes.

Both contract tools address contracts by contract_ref — "name" or "name@version", the same store-reference grammar used by Validator.from_store and pipeline store refs. A bare name resolves to the latest version.

Results are filtered by PyCharter's governance policy — the agent only sees what its role and workspace are allowed to see. That identity is bound when the server starts, not passed per call:

pycharter mcp serve --role reviewer --workspace <workspace-id>

A tool argument is filled in by the language model, so an identity the model can set is an identity it can set to admin. The default is the least-privileged viewer, and widening it is an operator action.

Authentication itself is delegated to the process boundary (stdio MCP servers run under the client's credentials); the role/workspace context shapes what is served, not who may connect.

Errors come back as structured JSON ({"error": ...}), never stack traces.

Self-checking data: validate_record

The examples/agent_readiness/ runner uses a real stdio MCP client to call prepare_contract_context and the exact-version validate_record path, then shows a rejected record and an accepted repair. It uses the same read-only policy and two unrelated contract domains without a model or hosted account.

Agents that generate data can check it against the governed contract before handing it on. A failed validation is a normal result — the tool only errors for an unknown contract or malformed arguments:

// call
{"name": "validate_record",
 "arguments": {"contract_ref": "orders@1.2.0",
               "record": {"sku": "A-100", "qty": "three"}}}

// result
{"is_valid": false,
 "contract": {"name": "orders", "version": "1.2.0", "namespace": null},
 "errors": ["Column 'qty': expected integer, found string 'three'"],
 "diagnostics": [
   {"row": null, "field": "qty", "code": "int_parsing",
    "expected": "integer", "offending_value": "three",
    "message": "Column 'qty': expected integer, found string 'three'",
    "severity": "error"}]}

Diagnostics mirror the runtime validator's FieldDiagnostic shape (field, code, expected, offending value, message, severity), so an agent can repair the exact fields that failed. Warning-severity rules appear in diagnostics without flipping is_valid.

Looking up field meanings: explain_field

explain_field walks field → binding → concept and returns the bound concept's card alongside the same governed context pack served by the GET /contracts/{id}/fields/{path} API route (policy-filtered by the role and workspace bound at server start):

// call
{"name": "explain_field",
 "arguments": {"contract_ref": "orders", "field_path": "sku"}}

// result (abridged)
{"contract": {"name": "orders", "version": "1.2.0"},
 "field_path": "sku",
 "bound": true,
 "concept_cards": [
   {"concept_id": "…", "concept_key": "commerce:sku",
    "name": "Stock keeping unit",
    "description": "Vendor-unique product identifier.",
    "tier": "free", "uri": null, "notation": null,
    "deprecated": false}],
 "context_pack": {"entity_kind": "contract_field",
                  "entity_ref": "orders::sku",
                  "bindings": [{"field_path": "sku",
                                "concept_key": "commerce:sku",
                                "state": "approved", "…": "…"}],
                  "…": "…"}}

field_path is the dotted path within the contract (address.zip; / separators are also accepted). An unbound field is an honest bound: false answer, not an error; only a contract with no recorded field bindings at all errors. The context_pack payload is the full pack projection, so enrichments to the pack (such as observed-evidence fields) flow through this tool automatically.

Client configuration

Claude Desktop / Claude Code (or any stdio MCP client):

{
  "mcpServers": {
    "pycharter-kb": {
      "command": "python",
      "args": ["-m", "pycharter.mcp"],
      "env": {
        "PYCHARTER_DATABASE_URL": "postgresql://localhost/pycharter"
      }
    }
  }
}

An agent session can then ground itself before touching data: list_concepts to discover the vocabulary, query_context("order") to find the concepts behind a term, fetch_concept_pack to pull the full governed definition — including which contract fields are bound to each concept. When it produces or consumes records, explain_field tells it what each field means and validate_record lets it self-check output against the same contract the runtime enforces.

Why serve context from the contract engine

A semantic layer that merely describes data drifts from the systems that process it. PyCharter's context packs are projections of the same store that drives runtime validation: field bindings link concepts to contract fields, and those contracts gate APIs, streams, and pipelines. When the contract changes, the agent's context and the enforcement change together.

See also: Vocabulary for authoring concept schemes and bindings, and the governance policy guide for the role/workspace model the tools respect.