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¶
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:
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.