For developers and AI agents

Five tools over the Model Context Protocol. Read what sources say. Submit what your agent asserts. The wire returns components, not conclusions — your agent reads the decomposition and decides what each record is worth.

Read

get_entity

Fetch one cluster report by KID or slug. Returns each source record, the per-attribute agreement signal, the score components behind it, and the full decision trace.

Read presentation.mode before treating display_name or any selected value as an entity profile. A cluster is a grouping of source records: entity means a classification signal and a current, in-band grouping-health assessment are recorded, while provisional_grouping means one of those is not shown — there the display name is a working label (its support is in presentation.member_composition.working_label_support) and the attributes are an aggregate across the grouped records rather than facts about one subject. presentation.reason_codes and presentation.checks say which conditions did not hold; presentation.member_composition is the evidence to judge the grouping on. Neither mode is a claim that the grouping is correct.

Example call
{
  "name": "get_entity",
  "arguments": {
    "identifier": "k3m7xv2qp"
  }
}

Response shape (redacted) — a cluster in entity mode:

{
  "status": "ok",
  "entity": {
    "kid": "k3m7xv2qp",
    "display_name": "Nordea Bank Abp",
    "slug": "nordea-bank-abp",
    "presentation": {
      "policy_ref": "entity-report-presentation/v1",
      "mode": "entity",
      "label_role": "entity_display_name",
      "content_scope": "entity_profile",
      "temporal_scope": "current",
      "reason_codes": [],
      "checks": {
        "classification_signal": "present",
        "health_assessment": "current",
        "strong_identifier_conflict": "absent",
        "entity_health_band": "passes"
      },
      "member_composition": {
        "total_member_bundles": 9,
        "working_label_support": {
          "member_bundle_count": 7, "source_class_count": 3
        }
      }
    },
    "epistemic": {
      "synthesis_snapshot_basis": "membership_at_synthesis",
      "source_class_count": 3,
      "formation_margin": 4.72,
      "convergence_trace": {
        "sigma_credibility": 0.89,
        "sigma_independence": 0.94,
        "phi": 6.31
      },
      "contributing_sources": [
        "gleif", "wikidata", "eba_institution"
      ]
    },
    "attributes": [
      {
        "attribute": "legal name",
        "selected_value": "Nordea Bank Abp",
        "epistemic": {
          "agreement_ratio": 1.0,
          "source_class_count": 3,
          "contested": false
        },
        "competing_values": [],
        "entity_links": [],
        "cluster_synonyms": []
      }
    ]
  }
}

The same tool, same shape, on a cluster whose grouping has not been shown to be one thing. Everything below the presentation object is still returned — the decomposition is the deliverable either way; what changes is what it may be read as:

{
  "status": "ok",
  "entity": {
    "kid": "kpd6f5rzn",
    "display_name": "Germany",
    "presentation": {
      "policy_ref": "entity-report-presentation/v1",
      "mode": "provisional_grouping",
      "label_role": "working_group_label",
      "content_scope": "member_aggregate",
      "temporal_scope": "current",
      "reason_codes": [
        "strong_identifier_conflict",
        "health_outside_entity_presentation_band"
      ],
      "checks": {
        "classification_signal": "present",
        "health_assessment": "current",
        "strong_identifier_conflict": "present",
        "entity_health_band": "fails"
      },
      "parameter_refs": [
        {
          "name": "ENTITY_PRESENTATION_ENTITY_HEALTH_TIERS",
          "value": "[\"solid\",\"good\",\"uncertain\"]"
        }
      ],
      "member_composition": {
        "total_member_bundles": 538,
        "latest_membership_change_at": "2026-07-08 11:04:02.113221+00",
        "by_source_class": [
          { "source_class": "wikidata", "member_bundle_count": 400 },
          { "source_class": "opensanctions", "member_bundle_count": 100 },
          { "source_class": "gleif", "member_bundle_count": 38 }
        ],
        "by_role": {
          "value_role_members": 12,
          "relationship_role_members": 26,
          "other_or_unmarked_members": 500
        },
        "working_label_support": {
          "member_bundle_count": 4, "source_class_count": 2
        }
      },
      "contest_display": {
        "descriptive_attribute_dimensions": 0,
        "relationship_role_dimensions": 1,
        "substrate_trace_dimensions": 0,
        "state": "none",
        "withholding_reason": null
      }
    }
  }
}

Here display_name is asserted by 4 of 538 member records, so it names the grouping rather than describing a settled subject; a summary that carries it without that scope has dropped the part a reader needs. contest_display.state reads none because no descriptive attribute contest survived filtering — not because nothing is contested: the sources type one relationship dimension differently, which is what the retained count beside it reports.

And when a slug matches several clusters, status is ambiguous and there is no presentation object — nothing has been evaluated yet. Each candidate’s display_name is a working grouping label, marked as such uniformly; fetch the KID you want to get that cluster’s presentation:

{
  "status": "ambiguous",
  "slug": "nordea",
  "candidates": [
    {
      "kid": "k3m7xv2qp",
      "display_name": "Nordea Bank Abp",
      "slug": "nordea-bank-abp",
      "synthesis_score": 0.91,
      "label_scope": "grouping_label_unevaluated"
    },
    {
      "kid": "k8w2ncr4t",
      "display_name": "Nordea",
      "slug": "nordea",
      "synthesis_score": 0.44,
      "label_scope": "grouping_label_unevaluated"
    }
  ]
}

label_scope is the same value on every candidate, by design. Deciding which of these groupings has been shown to be one thing would mean evaluating each here — a second decision, free to disagree with the report you receive on fetching the KID. One neutral scope asserts nothing mode-specific instead, and names where the answer is.

search_entities

Search by free-text query. Returns lightweight hits — KID, name, slug, ranking score. Follow up with get_entity for the full record.

Write and vote

Your agent is a source. Its assertions enter as first-class bundles in the per-source aggregation — composed with every other source assertion, each source contribution scored by track-record signal and independence-weighting.

submit_assertion

Assert a fact about an entity. Three fields: kid, attribute, value. The auth-resolved voter_id carries provenance.

Example call
{
  "name": "submit_assertion",
  "arguments": {
    "kid": "k3m7xv2qp",
    "attribute": "headquarters",
    "value": "Helsinki, Finland"
  }
}
{ "success": true, "bundle_id": "b-..." }

list_open_questions_for_entity

Pull open questions queued for an entity. Each question carries a claim to assess, evidence references, and an expected-response shape. This is the agent equivalent of the human review carousel on entity pages.

respond_to_question

Respond to a question. For relationship-type questions, the answer declares a relationship type (same, different, contains, part of, …) plus a vote: asserts | denies | skip | uncertain, with optional confidence.

Example round-trip
// 1. Pull questions
{
  "name": "list_open_questions_for_entity",
  "arguments": { "kid": "k3m7xv2qp" }
}

// Response includes question_id + proposition + response_schema

// 2. Respond
{
  "name": "respond_to_question",
  "arguments": {
    "question_id": "q-abc123...",
    "answer": {
      "primitive_name": "same",
      "vote": "asserts",
      "confidence": 0.9
    }
  }
}

// Response
{
  "response_id": "r-def456...",
  "question_id": "q-abc123...",
  "status": "responded",
  "relationship_bundles_emitted": 1
}

Connection

The MCP server runs as a stdio transport. Each client process launches its own server with its own credentials. An issued API key (kmcp_...) plus a DATABASE_URL are required.

Claude Desktop configuration
{
  "mcpServers": {
    "kaitsea": {
      "command": "kaitsea-mcp",
      "env": {
        "KAITSEA_MCP_API_KEY": "kmcp_...",
        "DATABASE_URL": "postgres://..."
      }
    }
  }
}

Access is provided on request while the server is in stdio-only mode. Public HTTP-streaming self-serve is the next step. To connect, email contact@kaitsea.com.

Wire shape

Every entity response follows the EntityEpistemicPrimitives schema — the same shape is available as JSON at /api/entity/{kid} without MCP.