Skip to content

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.

ToolGroupOne-line purpose
mmune_whoamiserverShow the account the session is bound to.
mmune_server_infoserverShow the server name and read-only posture.
get_trust_verdicttrustGive a safe, caution or unsafe verdict for an integration, object or node.
get_active_findingstrustList findings across drift, watchdog connections, zombie flows, duplicates, PII, stale feeds and containment.
lookup_columndataFind where a column is mapped.
list_integrationsdataList discovered and registered integrations.
get_broken_connectionsdataList unreachable integration connections.
get_drift_reportsdataList schema and value drift findings.
get_health_summarydataSummarize deployment health.
get_lineage_fordataReturn the lineage graph, optionally around one node.
get_impact_fordataCompute the downstream blast radius of changing a node.
get_compliance_posturedataReturn per-framework compliance posture.
get_compliance_findingsdataList 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.

InputTypeRequiredMeaning
integration_idstringNoRegistered integration id.
object_keystringNoTable or column identifier, matched loosely against drift and security findings.
node_keystringNoLineage 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.

InputTypeRequiredMeaning
integration_idstringNoLimit the feed to one integration.
limitintegerNoDefault 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.

InputTypeRequiredMeaning
fieldstringYesColumn name, for example id.
tablestringNoSource table.
vendorstringNoSource 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 ​

InputTypeRequiredMeaning
statestring, one of discovered, configured, registeredNoFilter 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 ​

InputTypeRequiredMeaning
integration_idstringNoLimit 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 ​

InputTypeRequiredMeaning
integration_idstringNoLimit 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 ​

InputTypeRequiredMeaning
node_keystringNoReturn 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.

InputTypeRequiredMeaning
node_keystringYesLineage 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 ​

InputTypeRequiredMeaning
sensitivitystring, one of HIGH, MEDIUM, LOWNoFilter 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 ​

StatusMeaningWhat the agent should do
401No 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.
403Authenticated but lacking a permission. The body is {"detail": {"code": "insufficient_permission", "required": "write", "message": "..."}}.Do not retry. Tell the user which permission is required.
429Rate 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.

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.

TaskMethodPathPermission
Check livenessGET/healthnone
Sign inPOST/api/v1/auth/loginnone
Complete MFA sign-inPOST/api/v1/auth/mfa/verify-loginnone
Show current user and permissionsGET/api/v1/auth/meread
Validate a tokenGET/api/v1/auth/validate-tokenread
Component health summaryGET/api/v1/health/read
Detailed component healthGET/api/v1/health/detailedread
List supported providersGET/api/v1/integrations/supportedread
Provider coverage reportGET/api/v1/integrations/coverageread
List discovered integrationsGET/api/v1/integrations/discoveredread
Get one discovered integrationGET/api/v1/integrations/discovered/{discovered_id}read
List registered integrationsGET/api/v1/integrations/registeredread
Get one registered integrationGET/api/v1/integrations/registered/{integration_id}read
List tables of an integrationGET/api/v1/integrations/{integration_id}/tablesread
Read discovery scan status and logsGET/api/v1/discovery/status/{scan_id}read
List first findingsGET/api/v1/discovery/first-findingsread
List saved mappingsGET/api/v1/mapping/savedread
Read one saved mappingGET/api/v1/mapping/saved/{filename}read
Suggest mappings (compute only)POST/api/v1/mapping/suggestread
Get the lineage graphGET/api/v1/lineage/graphread
Downstream impact of a nodeGET/api/v1/lineage/impact/{node_key}read
Semantic statisticsGET/api/v1/semantic/statsread
Semantic signaturesGET/api/v1/semantic/signaturesread
Mappings by business intentGET/api/v1/semantic/mappings-by-intent/{intent}read
Find a semantic match (compute only)POST/api/v1/semantic/find-semantic-matchread
List drift reportsGET/api/v1/drift/reportsread
Drift statisticsGET/api/v1/drift/statsread
List value driftGET/api/v1/drift/value-driftread
List broken connectionsGET/api/v1/healing/broken-connectionsread
Connection healthGET/api/v1/healing/connection-healthread
Healing statisticsGET/api/v1/healing/statisticsread
Watchdog statusGET/api/v1/watchdog/statusread
Watchdog sessionsGET/api/v1/watchdog/sessionsread
Monitoring summaryGET/api/v1/monitoring/summaryread
Detection metricsGET/api/v1/monitoring/detection-metricsread
Change cursor for pollingGET/api/v1/events/cursorread
List incidentsGET/api/v1/incidentsread
Get one incidentGET/api/v1/incidents/{incident_id}read
List recommendationsGET/api/v1/recommendationsread
List duplicate groupsGET/api/v1/duplicates/groupsread
Compliance postureGET/api/v1/compliance/postureread
Compliance scan historyGET/api/v1/compliance/historyread
Compliance findingsGET/api/v1/compliance/findingsread
Last security scan summaryGET/api/v1/guardian/scan/statusread
Read security scan job statusGET/api/v1/guardian/status/{job_id}read
List agent executionsGET/api/v1/agents/executionsread
Agent statisticsGET/api/v1/agents/statisticsread
Draft an orchestration plan (does not execute)POST/api/v1/orchestration/planread
Ask a question about the deploymentPOST/api/v1/analysis/chatread
Licence statusGET/api/v1/license/statusread
Start a discovery scanPOST/api/v1/discovery/scanwrite
Re-read a registered integration's schemaPOST/api/v1/discovery/reintrospect/{integration_id}write
Dismiss a first findingPOST/api/v1/discovery/first-findings/{finding_id}/dismisswrite
Discover integrationsPOST/api/v1/integrations/discoverwrite
Configure a discovered integrationPOST/api/v1/integrations/discovered/{discovered_id}/configurewrite
Test a discovered integrationPOST/api/v1/integrations/discovered/{discovered_id}/testwrite
Register an integrationPOST/api/v1/integrations/discovered/{discovered_id}/registerwrite
Introspect an integrationPOST/api/v1/integrations/discovered/{discovered_id}/introspectwrite
Introspect every integrationPOST/api/v1/integrations/introspect-allwrite
Create a mappingPOST/api/v1/mapping/mapwrite
Add a lineage edgePOST/api/v1/lineage/edgewrite
Apply a remapping suggestionPOST/api/v1/healing/apply-suggestionwrite
Start the watchdogPOST/api/v1/watchdog/startwrite
Stop the watchdogPOST/api/v1/watchdog/stopwrite
Acknowledge an incidentPOST/api/v1/incidents/{incident_id}/acknowledgewrite
Resolve an incidentPOST/api/v1/incidents/{incident_id}/resolvewrite
Start a recommendations scanPOST/api/v1/recommendations/scanwrite
Run duplicate detectionPOST/api/v1/duplicates/detectwrite
Run a security scanPOST/api/v1/guardian/scanadmin
Change estate-wide healing modePUT/api/v1/healing/configadmin

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.