Appearance
Using mmune from an AI agent
What this page covers
This page explains how an external AI agent (Claude, GPT, or a custom LLM agent) should read from mmune, and how a person can let an agent do so safely. It documents the mmune-trust MCP server tool by tool, including how an agent connects to it, the way to authenticate an agent, which REST endpoints are safe for read-only agents and which need write permission, recommended agent behaviour, a worked tool-calling loop in Python, and a capabilities map of the most common tasks an agent can perform. The map is a curated subset of the API, not a full route list.
Prerequisites: a running mmune installation you can reach (this page uses https://mmune.example.com as the example host), and an account that can sign in to it. Use a placeholder such as <TOKEN> in anything you commit. Never put a real token or password in a prompt, a config file in version control, or a log.
Related pages: the REST reference starts at API overview, with the groups integrations and discovery, mapping, lineage and semantic, health and monitoring, security and platform and agents and workflows. Push notifications are covered in webhooks and events. Environment settings for the installation are in configuration.
Choosing an interface
An agent has two ways in. The MCP server is a small, curated, read-only tool surface designed for agents. It answers questions such as "is this table safe to build on" and "what is broken right now", and the nine data tools return JSON that includes a link to the matching place in the mmune console. The two trust tools and the two server tools return a bare document. The REST API is the full product surface. It is larger, includes write operations, and is what the console itself uses.
Start with MCP when the agent only needs to look. Use REST when the agent needs something MCP does not offer, such as starting a discovery scan or reading incidents.
The mmune-trust MCP server
The server name advertised to MCP clients is mmune-trust. It speaks MCP over stdio only, with no HTTP or SSE listener, and it makes no outbound calls of its own, so it works in an air-gapped installation.
The MCP server is available on request and is set up by your administrator. It runs next to your mmune installation, and your mmune administrator sets it up together with the mmune team. As an agent builder, you are given the way to connect to it and a token for a dedicated read-only account. Never commit the token, and never put it in a prompt or a log.
Your agent host starts the connection as a local command and talks to it over standard input and output, so do not redirect either stream. Log output goes to standard error. Your administrator tells you the exact command and settings to use.
How the session is authenticated
The token is checked once, when the server starts, and the resulting account is bound to the session. Restart the MCP process whenever you rotate or revoke the account.
Read-only tools
Every tool is read-only. None of them changes state in mmune or in your systems. If you call a tool name that is not in the list below, the server reports an unknown tool.
Tool list
The server advertises 13 tools. Two are server tools, two compose trust signals, and nine are read-only data tools.
| Tool | Group | One-line purpose |
|---|---|---|
mmune_whoami | server | Show the account the session is bound to. |
mmune_server_info | server | Show the server name and read-only posture. |
get_trust_verdict | trust | Give a safe, caution or unsafe verdict for an integration, object or node. |
get_active_findings | trust | List findings across drift, watchdog connections, zombie flows, duplicates, PII, stale feeds and containment. |
lookup_column | data | Find where a column is mapped. |
list_integrations | data | List discovered and registered integrations. |
get_broken_connections | data | List unreachable integration connections. |
get_drift_reports | data | List schema and value drift findings. |
get_health_summary | data | Summarize deployment health. |
get_lineage_for | data | Return the lineage graph, optionally around one node. |
get_impact_for | data | Compute the downstream blast radius of changing a node. |
get_compliance_posture | data | Return per-framework compliance posture. |
get_compliance_findings | data | List unprotected PII and PHI findings from the latest scan. |
Response shapes
The two server tools and the two trust tools return a JSON document as text. The nine data tools return a common envelope, also as JSON text:
json
{
"ok": true,
"summary": "One-line human-readable answer.",
"data": {},
"console_location": {
"label": "Data Explorer → Lineage",
"route": "/data",
"tab": "lineage",
"focus_node_id": null
},
"blocks": [],
"error": null
}ok is false when the tool could not complete, in which case error holds the reason and summary is a sentence you can show to a person. data is the structured payload to reason over. blocks repeats the same information as display tables for a UI and can be ignored by an agent. console_location tells you where in the mmune console a person can look at the same thing, using one of the five routes /, /alerts, /data, /compliance and /workspace.
Data tools return an ok: false envelope instead of a protocol error for unknown or missing arguments, for example "error": "missing required parameter(s): field".
Example values are illustrative.
mmune_whoami
Input: none.
Output keys: user_id, email, name, role, permissions.
json
{"name": "mmune_whoami", "arguments": {}}json
{"user_id": "2", "email": "you@example.com", "name": "Example User", "role": "user", "permissions": ["read"]}mmune_server_info
Input: none.
Output keys: server, read_only_enforced.
json
{"name": "mmune_server_info", "arguments": {}}json
{"server": "mmune-trust", "read_only_enforced": true}get_trust_verdict
Returns a deterministic verdict. At least one of the three inputs is needed. With none, the tool returns a caution verdict whose single reason asks for one.
| Input | Type | Required | Meaning |
|---|---|---|---|
integration_id | string | No | Registered integration id. |
object_key | string | No | Table or column identifier, matched loosely against drift and security findings. |
node_key | string | No | Lineage node key. |
Output: verdict (safe, caution or unsafe), reasons (list of strings, always present), signals, and console_location. signals holds health_status (healthy, degraded or broken), open_drift_count, open_security_findings, downstream_affected_count, and impact_tier (low, medium, high, critical or null).
The rules are: unsafe when health is broken, any HIGH security finding is open, or the impact tier is critical. caution when health is degraded, drift is open, the impact tier is high or medium, LOW or MEDIUM security findings are open, or the object or node could not be resolved. safe otherwise.
json
{"name": "get_trust_verdict", "arguments": {"integration_id": "pg-orders-prod", "object_key": "orders.customer_email"}}json
{
"verdict": "caution",
"reasons": ["Open schema drift on orders.customer_email"],
"signals": {
"health_status": "healthy",
"open_drift_count": 1,
"open_security_findings": 0,
"downstream_affected_count": 4,
"impact_tier": "medium"
},
"console_location": {"route": "/alerts", "tab": null, "label": "Health Center (Drift)"}
}get_active_findings
A time-sorted feed of findings, newest first. The dimension of each finding is one of schema_drift, value_drift, watchdog_connection, zombie_flow, duplicate, security_pii, stale_feed and containment.
| Input | Type | Required | Meaning |
|---|---|---|---|
integration_id | string | No | Limit the feed to one integration. |
limit | integer | No | Default 50, minimum 1, maximum 500. |
Output: count, limit, integration_id, findings, console_location. Each finding has id, dimension, integration_id, object_key, severity, detected_at (ISO 8601), payload, impact_estimate and source_component.
json
{"name": "get_active_findings", "arguments": {"integration_id": "pg-orders-prod", "limit": 5}}json
{
"count": 1,
"limit": 5,
"integration_id": "pg-orders-prod",
"findings": [
{
"id": "security:17",
"dimension": "security_pii",
"integration_id": "pg-orders-prod",
"object_key": "orders.customer_email",
"severity": "HIGH",
"detected_at": "2026-10-03T09:12:44+00:00",
"payload": {"table": "orders", "column": "customer_email", "pii_types": ["email"], "suggested_action": "mask"},
"source_component": "security_findings",
"impact_estimate": null
}
],
"console_location": {"route": "/compliance", "tab": null, "label": "Compliance"}
}A count of zero means no findings were returned, which is not proof that nothing is wrong. Cross-check with get_health_summary when the answer matters.
lookup_column
Finds the semantic mappings for a column, ordered by confidence.
| Input | Type | Required | Meaning |
|---|---|---|---|
field | string | Yes | Column name, for example id. |
table | string | No | Source table. |
vendor | string | No | Source vendor or system. |
data is a list of mapping records with keys such as source_vendor, source_table, source_field, concept, business_intent_category and confidence_score. At most 25 are returned.
json
{"name": "lookup_column", "arguments": {"field": "customer_email", "table": "orders"}}json
{
"ok": true,
"summary": "`orders.customer_email` appears as postgres / orders.customer_email → mapped to *contact_email* (confidence 0.93).",
"data": [{"source_vendor": "postgres", "source_table": "orders", "source_field": "customer_email", "concept": "contact_email", "confidence_score": 0.93}],
"console_location": {"label": "Data Explorer → Semantic", "route": "/data", "tab": "semantic", "focus_node_id": null},
"blocks": [],
"error": null
}When nothing matches, ok is true and data is an empty list, which usually means the integration has not been introspected yet.
list_integrations
| Input | Type | Required | Meaning |
|---|---|---|---|
state | string, one of discovered, configured, registered | No | Filter by configuration state. |
data is a list of integration records with keys including id, integration_id, integration_type, provider_name, host, port, endpoint, configuration_state, first_seen_at and last_seen_at. Credentials are not part of this record.
json
{"name": "list_integrations", "arguments": {"state": "registered"}}json
{
"ok": true,
"summary": "1 integration(s): 1 registered",
"data": [{"id": 3, "integration_id": "pg-orders-prod", "provider_name": "postgres", "host": "10.0.4.12", "port": 5432, "integration_type": "database", "configuration_state": "registered"}],
"console_location": {"label": "Data Explorer → Integrations", "route": "/data", "tab": "integrations", "focus_node_id": null},
"blocks": [],
"error": null
}get_broken_connections
| Input | Type | Required | Meaning |
|---|---|---|---|
integration_id | string | No | Limit to one integration. |
data is a list with integration_id, provider, error_type, error_message and failure_count. It is the same list the Health Center shows from GET /api/v1/healing/broken-connections. An empty list with ok: true means no broken connections.
json
{"name": "get_broken_connections", "arguments": {}}json
{
"ok": true,
"summary": "1 broken connection(s) detected.",
"data": [{"integration_id": "sqlserver-billing", "provider": "sql_server", "error_type": "timeout", "error_message": "connection timed out", "failure_count": 6}],
"console_location": {"label": "Health Center", "route": "/alerts", "tab": null, "focus_node_id": null},
"blocks": [],
"error": null
}get_drift_reports
| Input | Type | Required | Meaning |
|---|---|---|---|
integration_id | string | No | Limit to one integration. |
data is a list of drift findings (field renames, removals, type changes). The exact keys depend on the drift type, so read them defensively. Up to 25 findings are returned.
json
{"name": "get_drift_reports", "arguments": {"integration_id": "pg-orders-prod"}}json
{
"ok": true,
"summary": "1 drift finding(s).",
"data": [{"integration_id": "pg-orders-prod", "type": "column_removed", "summary": "orders.legacy_flag removed", "detected_at": "2026-10-03T08:00:11+00:00"}],
"console_location": {"label": "Health Center (Drift)", "route": "/alerts", "tab": null, "focus_node_id": null},
"blocks": [],
"error": null
}get_health_summary
Input: none.
data has integrations (count), by_state (map of state to count), broken_connections and drift_findings.
json
{"name": "get_health_summary", "arguments": {}}json
{
"ok": true,
"summary": "6 integration(s), 1 broken connection(s), 2 drift finding(s).",
"data": {"integrations": 6, "by_state": {"registered": 5, "discovered": 1}, "broken_connections": 1, "drift_findings": 2},
"console_location": {"label": "Health Center", "route": "/alerts", "tab": null, "focus_node_id": null},
"blocks": [],
"error": null
}Counts reflect the monitoring sessions currently running.
get_lineage_for
| Input | Type | Required | Meaning |
|---|---|---|---|
node_key | string | No | Return only this node and its direct neighbours. Omit for the whole graph. |
data is {"nodes": [...], "edges": [...]}. Nodes carry id and type. Edges carry source and target.
json
{"name": "get_lineage_for", "arguments": {"node_key": "pg-orders-prod.public.orders"}}json
{
"ok": true,
"summary": "3 node(s) and 2 edge(s) around `pg-orders-prod.public.orders`.",
"data": {
"nodes": [{"id": "pg-orders-prod.public.orders", "type": "table"}, {"id": "pg-orders-prod", "type": "database"}, {"id": "warehouse.orders_fact", "type": "table"}],
"edges": [{"source": "pg-orders-prod", "target": "pg-orders-prod.public.orders"}, {"source": "pg-orders-prod.public.orders", "target": "warehouse.orders_fact"}]
},
"console_location": {"label": "Data Explorer → Lineage", "route": "/data", "tab": "lineage", "focus_node_id": null},
"blocks": [],
"error": null
}get_impact_for
Computes everything downstream of a node that would be affected if it changed.
| Input | Type | Required | Meaning |
|---|---|---|---|
node_key | string | Yes | Lineage node key, or a unique fragment of a table or system name. |
If the key is not exact, the tool tries a substring match. A single match is used. Several matches return ok: true with data of the form {"candidates": [...]} and a summary saying the name is ambiguous, so the agent should pick one and call again. No match returns {"candidates": []}. A resolved node returns origin, affected_nodes, edges and truncated. When truncated is true the traversal reached its depth limit and the real set may be larger. console_location.focus_node_id is set to the resolved node.
json
{"name": "get_impact_for", "arguments": {"node_key": "pg-orders-prod.public.orders"}}json
{
"ok": true,
"summary": "A change to `pg-orders-prod.public.orders` affects 2 downstream node(s) (2x snowflake).",
"data": {
"origin": "pg-orders-prod.public.orders",
"affected_nodes": [{"id": "warehouse.orders_fact", "type": "table", "provider": "snowflake"}, {"id": "warehouse.orders_daily", "type": "table", "provider": "snowflake"}],
"edges": [{"id": "pg-orders-prod.public.orders-warehouse.orders_fact", "source": "pg-orders-prod.public.orders", "target": "warehouse.orders_fact", "label": "mapping"}],
"truncated": false
},
"console_location": {"label": "Data Explorer → Lineage", "route": "/data", "tab": "lineage", "focus_node_id": "pg-orders-prod.public.orders"},
"blocks": [],
"error": null
}get_compliance_posture
Input: none.
data is a map keyed by framework name: GDPR, HIPAA, SOC2 and PCI-DSS. Each entry has framework, status, controls_passed, controls_total, controls (the per-control results, each with id, title, status and evidence), scan_job_id and assessed_at. The example below leaves out controls, scan_job_id and assessed_at to stay short. When no assessment exists yet data is empty and the summary says to run a security scan. This is automated posture monitoring over observable controls and the tool states that it is not a certification audit. An agent should repeat that caveat when it reports results.
json
{"name": "get_compliance_posture", "arguments": {}}json
{
"ok": true,
"summary": "Compliance posture - GDPR: gaps_found (7/9), HIPAA: warning (5/8). This is automated posture monitoring over observable controls, not a certification audit.",
"data": {
"GDPR": {"framework": "GDPR", "status": "gaps_found", "controls_passed": 7, "controls_total": 9},
"HIPAA": {"framework": "HIPAA", "status": "warning", "controls_passed": 5, "controls_total": 8}
},
"console_location": {"label": "Compliance", "route": "/compliance", "tab": null, "focus_node_id": null},
"blocks": [],
"error": null
}Example values are illustrative. A framework status is one of not_assessed (no control could be assessed), gaps_found (at least one control failed), warning (no failures but at least one warning) or no_gaps. Each control inside controls has a status of pass, warning, fail or not_assessed.
get_compliance_findings
| Input | Type | Required | Meaning |
|---|---|---|---|
sensitivity | string, one of HIGH, MEDIUM, LOW | No | Filter the findings returned. |
data is {"findings": [...], "summary": {"high": n, "medium": n, "low": n, "total": n}}. The summary counts describe the latest scan as a whole, so the sensitivity filter does not change them. Each finding has id, table, column, pii_types, sensitivity, suggested_action and reasoning. When no scan has run, findings is empty and the summary says so.
json
{"name": "get_compliance_findings", "arguments": {"sensitivity": "HIGH"}}json
{
"ok": true,
"summary": "1 HIGH finding(s) (1 HIGH, 0 MEDIUM, 0 LOW total) - unprotected PII/PHI columns from the latest security scan.",
"data": {
"findings": [{"id": 17, "table": "orders", "column": "customer_email", "pii_types": ["email"], "sensitivity": "HIGH", "suggested_action": "mask", "reasoning": "Column holds email addresses in clear text."}],
"summary": {"high": 1, "medium": 0, "low": 0, "total": 1}
},
"console_location": {"label": "Compliance", "route": "/compliance", "tab": null, "focus_node_id": null},
"blocks": [],
"error": null
}Authenticating an agent
Both interfaces use the same JSON Web Token. Sign in once and send the token as a bearer credential.
bash
curl -sS -X POST https://mmune.example.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "<PASSWORD>"}'The response contains access_token, token_type (bearer), expires_in in seconds, and a user object. The default lifetime is 30 minutes. Send it on every call:
http
Authorization: Bearer <TOKEN>If the account has MFA enabled, login returns a short-lived mfa_token instead of an access token, and you must complete POST /api/v1/auth/mfa/verify-login. An agent cannot do this unattended, so use a dedicated read-only account for agents, or have a person supply the token.
Repeated failed logins for the same address and client are blocked for about a minute with a 429 and a Retry-After: 60 header.
Which account to use
Permissions decide what an agent can do. A token with only read can call every GET endpoint and a short list of read-only POST endpoints. A token with write can also change state. A token with admin can additionally run security scans and change estate-wide healing and containment settings. For an agent that should only look, ask your mmune administrator for an account that holds only read, and never use an admin account for an agent that does not need it.
Agents sign in as an ordinary user account. Sign in again when you receive a 401.
Errors to handle
| Status | Meaning | What the agent should do |
|---|---|---|
| 401 | No token, bad token or expired token. The body is {"detail": "Not authenticated"} or similar. | Sign in again, once. If it fails again, stop and report. |
| 403 | Authenticated but lacking a permission. The body is {"detail": {"code": "insufficient_permission", "required": "write", "message": "..."}}. | Do not retry. Tell the user which permission is required. |
| 429 | Rate limited. | Wait for the Retry-After header, then retry with backoff. |
REST endpoints: read-only and write-gated
The API denies by default. Every request needs a valid bearer token with at least read, except POST /api/v1/auth/login, POST /api/v1/auth/mfa/verify-login, GET /health (liveness only) and the probe and discovery-agent paths that carry their own credentials. After that, any method other than GET, HEAD and OPTIONS needs write or the admin role, with a short list of exceptions that a read token may call because they compute or answer without changing shared state: self-service profile, password and MFA calls, POST /api/v1/analysis/chat and /chat/stream, POST /api/v1/rag/query, POST /api/v1/semantic/find-semantic-match, POST /api/v1/mapping/suggest, and POST /api/v1/orchestration/plan. A small admin-only list sits on top.
In practice this gives agents a simple rule. Treat every GET as safe to call freely, subject to the rate limit and the note below. Treat every POST, PUT, PATCH and DELETE as a write that needs explicit human approval, apart from the exceptions listed above.
GET /api/v1/discovery/schemas can start a scan if nothing has been discovered yet, so agents should avoid it and use the list endpoints. GET /api/v1/discovery/status/{scan_id} returns HTTP 200 with {"error": "Scan ID not found"} for an unknown id, so check the body as well as the status code. GET /api/v1/guardian/status/{job_id} returns 404 for an unknown job id.
Interactive docs
The Swagger UI and openapi.json are served only outside production mode. On a production deployment they are switched off, so an agent cannot fetch the schema from the server. Use the reference pages linked at the top of this page.
Rate limits
All HTTP routes except /health, /docs, /redoc, /openapi.json and OPTIONS preflights count against two sliding windows. The defaults are 6000 requests per 60 seconds across the whole installation, and 18000 requests per 3600 seconds per caller. Operators can change the limits with MMUNE_RATE_LIMIT_GLOBAL_CALLS, MMUNE_RATE_LIMIT_GLOBAL_PERIOD, MMUNE_RATE_LIMIT_PER_KEY_CALLS and MMUNE_RATE_LIMIT_PER_KEY_PERIOD.
A limited request returns 429 with a body like {"error": {"code": 429, "message": "Rate limit exceeded for this user", "retry_after": 3600}} and the headers Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Successful responses carry X-RateLimit-Limit and X-RateLimit-Remaining for the per-caller window. The global window is shared with every console user and background process, so an agent that polls aggressively can cause 429s for people. MCP tool calls do not count against these limits.
Recommended agent behaviour
Start read-only. Open the session with mmune_whoami (MCP) or GET /api/v1/auth/me (REST) to learn which permissions you hold, and plan only what those permissions allow. If the user asks for something that needs write, say so and ask for approval before sending any non-GET request.
Prefer the narrowest tool. Ask get_trust_verdict for one object instead of pulling the whole lineage graph. Pass integration_id to filters whenever you know it, and set a small limit on get_active_findings.
Report what the data says and where it came from. Each result carries a console_location; include it so a person can verify the answer. Do not turn a caution verdict into a claim that something is broken, and do not turn an empty result into a claim that everything is healthy.
Poll jobs by id, not by repeating the action. Long operations return an id immediately: POST /api/v1/discovery/scan returns scan_id, POST /api/v1/integrations/discovered/{id}/introspect returns scan_id, and POST /api/v1/guardian/scan returns job_id. Read progress with GET /api/v1/discovery/status/{scan_id} or GET /api/v1/guardian/status/{job_id}. The status values are PENDING, RUNNING, COMPLETED and FAILED. Pass cursor set to the previous next_cursor to receive only new log lines. Wait a few seconds between polls, give up after a limit you choose, and never start a second job because the first looks slow.
Honour limits and errors. On 429, wait for Retry-After. On 401, sign in once more. On 403, stop and report the required permission. Do not loop on failures.
Respect licensing and cost. Registering an integration can be refused when the licence seat cap is reached, and the platform tracks AI spend. Do not bulk-register or bulk-scan to see what happens.
Keep credentials out of prompts and logs. The token belongs in an environment variable or secret store. Log tool names and ids, not bearer tokens, and not the PII values that findings point at.
A tool-calling loop with the Anthropic SDK
This example connects Claude to the mmune-trust MCP server. It starts the connection command your administrator gave you as a child process, converts the advertised tools into Anthropic tool definitions, and runs a bounded loop. It uses the mcp and anthropic Python packages and the model id claude-sonnet-5-5.
python
import asyncio
import os
import shlex
from anthropic import AsyncAnthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
MODEL = "claude-sonnet-5-5"
MAX_TURNS = 8
SYSTEM = """You answer questions about a data estate monitored by mmune.
Use only the provided tools. They are read-only.
Call get_trust_verdict before saying a table or integration is safe to use.
Always quote the verdict, its reasons, and the console_location route and tab.
If a tool returns ok=false, report the error and stop. Do not guess.
Never claim compliance certification. Posture results are automated monitoring only."""
# The command and token come from your mmune administrator.
command, *args = shlex.split(os.environ["MMUNE_MCP_COMMAND"])
server = StdioServerParameters(
command=command,
args=args,
env={"MMUNE_MCP_BEARER_TOKEN": os.environ["MMUNE_MCP_BEARER_TOKEN"]},
)
async def ask(question: str) -> str:
client = AsyncAnthropic() # reads ANTHROPIC_API_KEY
async with stdio_client(server) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
listed = await session.list_tools()
tools = [
{"name": t.name, "description": t.description or "", "input_schema": t.inputSchema}
for t in listed.tools
]
messages = [{"role": "user", "content": question}]
for _ in range(MAX_TURNS):
response = await client.messages.create(
model=MODEL,
max_tokens=1500,
system=SYSTEM,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if b.type == "text")
results = []
for block in response.content:
if block.type != "tool_use":
continue
result = await session.call_tool(block.name, block.input)
text = "".join(c.text for c in result.content if c.type == "text")
results.append(
{
"type": "tool_result",
"tool_use_id": block.id,
"content": text,
"is_error": bool(result.isError),
}
)
messages.append({"role": "user", "content": results})
raise RuntimeError(f"No final answer after {MAX_TURNS} turns")
if __name__ == "__main__":
print(asyncio.run(ask("Is it safe to build a report on pg-orders-prod orders.customer_email?")))A good run looks like this. The model calls get_trust_verdict with integration_id and object_key. If the verdict is caution with open drift, it calls get_drift_reports for that integration to see the change, then get_impact_for for the table to see what depends on it, and then answers in plain words with the verdict, the reasons, the affected downstream nodes, and the console location to check. It does not call anything else.
Few-shot snippet for the system prompt
If your agent does not use MCP and instead gets mmune facts pasted in by another process, these two worked turns teach the pattern of verifying before answering and handling errors.
text
User: Can I drop the column orders.legacy_flag?
Assistant (tool call): get_impact_for {"node_key": "orders.legacy_flag"}
Tool result: {"ok": true, "summary": "`orders.legacy_flag` is ambiguous - matches: `pg-orders-prod.public.orders.legacy_flag`, `pg-archive.public.orders.legacy_flag`", "data": {"candidates": ["pg-orders-prod.public.orders.legacy_flag", "pg-archive.public.orders.legacy_flag"]}}
Assistant: Two columns match. Which one do you mean, the one in pg-orders-prod or in pg-archive?
User: pg-orders-prod.
Assistant (tool call): get_impact_for {"node_key": "pg-orders-prod.public.orders.legacy_flag"}
Tool result: {"ok": true, "summary": "Nothing downstream of `pg-orders-prod.public.orders.legacy_flag` - a change there has no tracked impact on other systems.", "data": {"origin": "pg-orders-prod.public.orders.legacy_flag", "affected_nodes": [], "edges": [], "truncated": false}}
Assistant: mmune tracks nothing downstream of that column, so no lineage consumer is affected. That only covers what mmune has introspected, so check anything outside its view before dropping it. Console: Data Explorer, Lineage tab.The same pattern over plain HTTP
This version uses only REST and httpx. It signs in, refuses any method other than GET unless the caller opts in, backs off on 429, and polls a job by id.
python
import os
import time
import httpx
BASE = "https://mmune.example.com"
class MmuneReadOnlyClient:
def __init__(self, email: str, password: str, allow_writes: bool = False):
self.http = httpx.Client(base_url=BASE, timeout=30)
self.allow_writes = allow_writes
self._login(email, password)
def _login(self, email: str, password: str) -> None:
r = self.http.post("/api/v1/auth/login", json={"email": email, "password": password})
r.raise_for_status()
body = r.json()
if "access_token" not in body:
raise RuntimeError("MFA is enabled for this account; supply a token instead")
self.http.headers["Authorization"] = f"Bearer {body['access_token']}"
def request(self, method: str, path: str, **kwargs) -> dict:
if method.upper() != "GET" and not self.allow_writes:
raise PermissionError(f"{method} {path} needs explicit approval")
for attempt in range(5):
r = self.http.request(method, path, **kwargs)
if r.status_code == 429:
time.sleep(min(int(r.headers.get("Retry-After", "5")), 60) * (attempt + 1))
continue
r.raise_for_status()
return r.json()
raise RuntimeError(f"Rate limited too many times: {method} {path}")
def poll_job(self, status_path: str, interval: float = 3.0, timeout: float = 300.0) -> dict:
deadline = time.time() + timeout
cursor = 0
while time.time() < deadline:
snap = self.request("GET", status_path, params={"cursor": cursor})
if snap.get("error") == "Scan ID not found":
raise LookupError(status_path)
cursor = snap.get("next_cursor", cursor)
if snap["status"] in ("COMPLETED", "FAILED"):
return snap
time.sleep(interval)
raise TimeoutError(status_path)
client = MmuneReadOnlyClient("you@example.com", os.environ["MMUNE_AGENT_PASSWORD"])
print(client.request("GET", "/api/v1/auth/me")["permissions"])
print(client.request("GET", "/api/v1/integrations/registered")["count"])
print(client.request("GET", "/api/v1/lineage/impact/pg-orders-prod.public.orders")["truncated"])To start a job such as a discovery scan, construct the client with allow_writes=True and an account that holds write, call request("POST", "/api/v1/discovery/scan", json={"target": "10.0.4.0/24", "ports": []}), then pass /api/v1/discovery/status/<scan_id> to poll_job. Only do that after a person has approved the scan target.
To use these REST calls as Claude tools instead, declare each as a tool whose handler calls client.request and returns the JSON. Keep the tool set to the GET rows of the capabilities map below.
Capabilities map
This table lists the most common tasks an agent can perform. Permission means the lowest credential that the API accepts: none for public paths, read for any signed-in account, write for state-changing calls, admin for the role admin. Some endpoints add further checks of their own. Paths use {name} for path parameters. For request and response bodies, follow the reference pages linked at the top.
| Task | Method | Path | Permission |
|---|---|---|---|
| Check liveness | GET | /health | none |
| Sign in | POST | /api/v1/auth/login | none |
| Complete MFA sign-in | POST | /api/v1/auth/mfa/verify-login | none |
| Show current user and permissions | GET | /api/v1/auth/me | read |
| Validate a token | GET | /api/v1/auth/validate-token | read |
| Component health summary | GET | /api/v1/health/ | read |
| Detailed component health | GET | /api/v1/health/detailed | read |
| List supported providers | GET | /api/v1/integrations/supported | read |
| Provider coverage report | GET | /api/v1/integrations/coverage | read |
| List discovered integrations | GET | /api/v1/integrations/discovered | read |
| Get one discovered integration | GET | /api/v1/integrations/discovered/{discovered_id} | read |
| List registered integrations | GET | /api/v1/integrations/registered | read |
| Get one registered integration | GET | /api/v1/integrations/registered/{integration_id} | read |
| List tables of an integration | GET | /api/v1/integrations/{integration_id}/tables | read |
| Read discovery scan status and logs | GET | /api/v1/discovery/status/{scan_id} | read |
| List first findings | GET | /api/v1/discovery/first-findings | read |
| List saved mappings | GET | /api/v1/mapping/saved | read |
| Read one saved mapping | GET | /api/v1/mapping/saved/{filename} | read |
| Suggest mappings (compute only) | POST | /api/v1/mapping/suggest | read |
| Get the lineage graph | GET | /api/v1/lineage/graph | read |
| Downstream impact of a node | GET | /api/v1/lineage/impact/{node_key} | read |
| Semantic statistics | GET | /api/v1/semantic/stats | read |
| Semantic signatures | GET | /api/v1/semantic/signatures | read |
| Mappings by business intent | GET | /api/v1/semantic/mappings-by-intent/{intent} | read |
| Find a semantic match (compute only) | POST | /api/v1/semantic/find-semantic-match | read |
| List drift reports | GET | /api/v1/drift/reports | read |
| Drift statistics | GET | /api/v1/drift/stats | read |
| List value drift | GET | /api/v1/drift/value-drift | read |
| List broken connections | GET | /api/v1/healing/broken-connections | read |
| Connection health | GET | /api/v1/healing/connection-health | read |
| Healing statistics | GET | /api/v1/healing/statistics | read |
| Watchdog status | GET | /api/v1/watchdog/status | read |
| Watchdog sessions | GET | /api/v1/watchdog/sessions | read |
| Monitoring summary | GET | /api/v1/monitoring/summary | read |
| Detection metrics | GET | /api/v1/monitoring/detection-metrics | read |
| Change cursor for polling | GET | /api/v1/events/cursor | read |
| List incidents | GET | /api/v1/incidents | read |
| Get one incident | GET | /api/v1/incidents/{incident_id} | read |
| List recommendations | GET | /api/v1/recommendations | read |
| List duplicate groups | GET | /api/v1/duplicates/groups | read |
| Compliance posture | GET | /api/v1/compliance/posture | read |
| Compliance scan history | GET | /api/v1/compliance/history | read |
| Compliance findings | GET | /api/v1/compliance/findings | read |
| Last security scan summary | GET | /api/v1/guardian/scan/status | read |
| Read security scan job status | GET | /api/v1/guardian/status/{job_id} | read |
| List agent executions | GET | /api/v1/agents/executions | read |
| Agent statistics | GET | /api/v1/agents/statistics | read |
| Draft an orchestration plan (does not execute) | POST | /api/v1/orchestration/plan | read |
| Ask a question about the deployment | POST | /api/v1/analysis/chat | read |
| Licence status | GET | /api/v1/license/status | read |
| Start a discovery scan | POST | /api/v1/discovery/scan | write |
| Re-read a registered integration's schema | POST | /api/v1/discovery/reintrospect/{integration_id} | write |
| Dismiss a first finding | POST | /api/v1/discovery/first-findings/{finding_id}/dismiss | write |
| Discover integrations | POST | /api/v1/integrations/discover | write |
| Configure a discovered integration | POST | /api/v1/integrations/discovered/{discovered_id}/configure | write |
| Test a discovered integration | POST | /api/v1/integrations/discovered/{discovered_id}/test | write |
| Register an integration | POST | /api/v1/integrations/discovered/{discovered_id}/register | write |
| Introspect an integration | POST | /api/v1/integrations/discovered/{discovered_id}/introspect | write |
| Introspect every integration | POST | /api/v1/integrations/introspect-all | write |
| Create a mapping | POST | /api/v1/mapping/map | write |
| Add a lineage edge | POST | /api/v1/lineage/edge | write |
| Apply a remapping suggestion | POST | /api/v1/healing/apply-suggestion | write |
| Start the watchdog | POST | /api/v1/watchdog/start | write |
| Stop the watchdog | POST | /api/v1/watchdog/stop | write |
| Acknowledge an incident | POST | /api/v1/incidents/{incident_id}/acknowledge | write |
| Resolve an incident | POST | /api/v1/incidents/{incident_id}/resolve | write |
| Start a recommendations scan | POST | /api/v1/recommendations/scan | write |
| Run duplicate detection | POST | /api/v1/duplicates/detect | write |
| Run a security scan | POST | /api/v1/guardian/scan | admin |
| Change estate-wide healing mode | PUT | /api/v1/healing/config | admin |
The tasks above are also reachable through the MCP tools where one exists: health and drift through get_health_summary and get_drift_reports, broken connections through get_broken_connections, lineage and impact through get_lineage_for and get_impact_for, compliance through get_compliance_posture and get_compliance_findings, and integrations through list_integrations.
Real-time delivery over WebSocket and the other push channels are described in webhooks and events and in the WebSocket reference. A WebSocket client sends the token as the second subprotocol, after the literal mmune.bearer.
Where to go next
Read the API overview for conventions that apply to every endpoint, then the group page for the area your agent works in: integrations and discovery, mapping, lineage and semantic, health and monitoring, security and platform, or agents and workflows. Operators who need to configure the installation should read configuration.