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.