Skip to main content

MCP server overview

Every Metatate Cloud workspace exposes a hosted MCP (Model Context Protocol) server. It gives AI agents — coding assistants, analytics copilots, review bots — governed answers about your data at the point of use: what an asset means, which rules apply, whether a proposed use is allowed, for what purpose, and why. Instead of pasting policy documents into prompts, an agent asks Metatate and gets a typed answer derived from what your workspace has actually published, with the evidence cited.

The server speaks MCP over HTTP (POST /mcp) and authenticates with a workspace access token. See Connect an agent for setup, or the Claude Code plugin for a packaged integration.

The nine toolsDirect link to The nine tools

ToolWhat it answersRequired scope
discover_contextWhich assets are governed, with per-asset instruction counts and the canonical scenario keys that are valid in follow-up calls. Filterable and paginated.read
get_decision_contextThe full decision picture for an asset: every applicable instruction ranked, the effective decision, and published business context.read
inspect_data_meaningWhat a table or column means: classification, sensitivity, PII, and masking facts. Facts only — not a decision.read
inspect_governance_rulesThe active governance rules for an asset: instructions with their conditions, obligations, and policy provenance.read
authorize_useWhether a proposed use of an asset is allowed, denied, or conditional — purpose-aware, actor-aware, and destination-aware.read
validate_query_contextA pass / warn / fail verdict for a SQL query: the query is parsed and every referenced asset is evaluated individually.read
explain_whyThe evidence behind a prior decision — a published rule, a recorded authorization, or a recorded validation.read
request_accessSubmits a durable steward review request citing a prior authorize_use answer. This is the one tool that writes.request
check_requestThe status of a request this token submitted, and the granted exception when there is one.read

Each tool also accepts a 1:1 hyphenated protocol alias (discover-context for discover_context) — underscores replaced by hyphens, nothing more. The underscore names are canonical.

What is read-only, preciselyDirect link to What is read-only, precisely

"Everything is read-only" is not accurate, and Metatate does not advertise it that way. Here is the honest breakdown of the nine tools:

  • Eight of nine advertise readOnlyHint: true in their MCP registration — every tool except request_access.
  • Six are operationally pure reads. discover_context, get_decision_context, inspect_data_meaning, inspect_governance_rules, explain_why, and check_request read and return; nothing is persisted as a result of the call.
  • authorize_use and validate_query_context write durable evidence. Each answer is recorded as a citable decision-ledger record before the answer is returned. They advertise readOnlyHint: true because they never change your governance — but they are not side-effect-free, and if the evidence write fails the call fails with internal_error rather than returning an uncited answer.
  • request_access is the sole workflow-state write and advertises readOnlyHint: false, with destructiveHint: false and idempotentHint: true set explicitly (the MCP spec would otherwise default destructiveHint to true for a non-read-only tool). It requires a separate request token scope, so a default token cannot reach it at all.

What holds across all nine, without exception:

  • No tool mutates governance state. Policies, instructions, deployment publications, and served decision rows are never written by a tool call. request_access writes a review request — a workflow row for a human steward — and nothing else.
  • No tool touches your data platforms. Metatate never enforces, masks, blocks, executes, or pushes anything into your systems. Answers are inputs to your agent's judgment; enforcement stays where it always was.
  • No tool takes a decision on your behalf. Every decision-bearing answer carries advisory: true.

What tools readDirect link to What tools read

Decision-bearing tools read exactly one source: the deployed instruction decisions of your workspace's current deployment publication. Draft policies, unpublished edits, and superseded publications are never consulted. inspect_data_meaning reads published data-meaning facts and is not a decision-bearing path.

Because answers come only from published state, they are typed. A tool returns answered, review_required, or not_enough_published_state — missing, conflicted, unresolved, or out-of-window serving state is reported as a typed answer state with a stable reason code, never as a fabricated decision. Zero applicable published rows is not_enough_published_state, by design. See Typed answer states for the full model, and the quick start to get a first publication live.

The in-app preview (the Tools tab of your workspace's MCP module) and the live endpoint share the same answer model, so what you try in the app is what agents get.

Who is asking, for what, and whenDirect link to Who is asking, for what, and when

authorize_use and validate_query_context answer three questions the decision function used to ignore. All three are derived server-side; none is a caller assertion.

Actor. The evaluated role is the token's own bound_role, read from the verified token row. Tool inputs are strict, so an invented bound_role, actor_role, or role key is invalid_parameters. Matching is trimmed, case-sensitive byte equality — Analyst and analyst are different roles, and there is no role hierarchy. A token with no bound role fails closed: role-addressed allows become require_review, while restrictive outcomes are untouched.

Purpose. Both tools accept an optional purpose_key from a global, versioned, closed registry. It is decision-bearing, but only where a usage_guidance rule participates in the answer. See Purpose keys for the complete semantics, including what purpose can and cannot do.

Time. A policy version may declare a validity window. Every tool that serves instruction rows applies the same half-open predicate effective_from <= evaluated_at < effective_until. When rows exist for the asset and scenario but none is in force, the answer is not_enough_published_state with reason not_currently_effective — never a silent absence and never an invented decision. discover_context is deliberately validity-blind: it reports published coverage, not current effectiveness.

Every authorize_use and validate_query_context answer, in all three states, states the context it was evaluated in:

FieldMeaning
evaluated_atRFC 3339 UTC instant, minted once per call and identical to the ledger record's.
evaluated_purpose{purpose_key, purpose_category, registry_version}, or null when the call stated no purpose.
evaluated_actor_roleThe token's bound role, or null.

get_decision_context and inspect_governance_rules carry evaluated_at (they filter validity windows by it) and serve rows un-substituted — inspection always shows the authored rules, not a purpose-adjusted view. Every served instruction row carries its own effective_from and effective_until.

on_behalf_of is an optional audit-only field on both tools: trimmed, single-line, 1–320 code points. It is decision-neutral by contract — no evaluator reads it, and a role name placed there changes nothing — and it is never echoed back in any answer.

Decision evidence you can cite laterDirect link to Decision evidence you can cite later

Every authorize_use and validate_query_context answer is recorded as a durable record before it is returned:

  • authorize_use answers carry authorization_id. This is not the same as decision_id, which identifies a published serving row. The two have distinct explain surfaces.
  • validate_query_context answers carry validation_id, plus a finding_id on every finding.

Pass any of them to explain_why, in this session or months later. Ledger records store only normalized, redacted evidence — no raw SQL and no raw use text ever appears in an explain output. Raw content is stored only in a protected admin/owner-readable table, and only when your workspace turns on the corresponding privacy switch (both default off).

Closing the loop: the request laneDirect link to Closing the loop: the request lane

A non-allow answer used to be a dead end for an agent. It no longer is:

  1. authorize_use returns conditional, review_required, a deny, or an insufficient-state answer — with an authorization_id.
  2. The agent calls request_access citing that authorization_id, which writes a durable review request for a workspace steward. Confirm with your human first — see the note below.
  3. A steward reviews it in the app and resolves it — granting a scoped exception, denying, or flagging that the policy itself needs changing.
  4. The agent polls check_request. When an exception was granted, it gets the exception_id.
  5. The agent retries authorize_use with satisfied_conditions: [{kind: "approval", exception_id}]. The server verifies the exception in SQL — subject, liveness, and scope — before anything is satisfied.

Two things to design for. First, request_access requires the request token scope, which is granted only by deliberate issuance or rotation — a default token cannot reach the lane at all. Second, Metatate performs no server-side confirmation before a request is written: there is no elicitation round-trip and no two-phase commit. The honest readOnlyHint: false annotation is what a compliant MCP client uses to prompt its user before invoking a write tool, and that client-side prompt is the only place a human sees the request before it is filed. If you are building a custom client, implement it.

See Request access for the full contract and the worked example, and Review requests in the app for the steward's side.

Naming conventionsDirect link to Naming conventions

Every tool input and output key is snake_case (scenario_key, decision_id, can_proceed_now, publication.publication_id) — there are no camelCase aliases. The one documented exception is the contents of a served instruction's parameters object (and the nested payloads inside obligation projections): those are your authored policy DSL carried through verbatim.

MeteringDirect link to Metering

Every accepted tool call consumes one metered MCP call against your workspace's plan quota. Calls refused before input parsing — invalid_parameters and insufficient_scope — are not metered. When the quota is exhausted, calls fail with a typed quota_exceeded error rather than degrading silently. See Plans for quotas.