Skip to main content
Glama

facts-mcp

An MCP server that only holds facts. It measures and it remembers; it never judges.

Point it at a service — a local app, a device on your network, a public API or website — and it returns the raw measurement with the time it was taken. Write down what you concluded, with the evidence, and it keeps that claim — for good. Nothing in it is a verdict, so an agent reading it does not have to verify the answer.

It knows nothing about any particular application. The project is a parameter of every call, and what the server may reach is set by whoever runs it, not by the code.

The three rules

Without these, this is just another agent you would have to check.

  1. If something can be measured now, it is not answered from memory. recall.* never measures. When it returns a claim backed by a measurement it also returns remeasure_with — the exact tool and arguments to take that measurement again yourself.

  2. Every response carries the time it was observed, so staleness is visible. Each item in a collection carries its own age, not the age of the query.

  3. No output field is a judgement. No health, score, severity, passed, recommendation. ok means only "the observation could be taken".

And the distinction that holds it together: an error of observation is not the observation of an error. A cleanly received HTTP 500 is a success of the tool — ok: true, http_status: 500. Only what prevented measuring is an error.

Related MCP server: Witness

Install and run

Requires Node 22.11 or newer. The package is not on npm yet; build it from a clone:

git clone https://github.com/Yesid8/mcp-measure-and-recall.git
cd mcp-measure-and-recall
npm ci
npm run build

It speaks MCP over stdio. Point any MCP client at the built entry point, with a data directory of its own:

{
  "mcpServers": {
    "facts": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-measure-and-recall/dist/main.js"],
      "env": { "PLINTH_DATA_DIR": "/absolute/path/to/a/data/dir" }
    }
  }
}

On Windows, write the paths with forward slashes or doubled backslashes: a single backslash in JSON is an escape. If the client does not connect, read its stderr — every refusal to start begins with facts-mcp: and says why. The guide for consumers is docs/USO.md.

The six tools

Every call takes project, a free string that isolates one consumer's data from another's. It is created on first use and returned normalized, so a typo is visible immediately.

Tool

What it returns

facts.measure.http

Status, headers, body, timings — and which IP it actually connected to

facts.measure.ws

The messages that arrived in N seconds, each with absolute time and offset

facts.recall.assert

The claim as written, with its evidence embedded

facts.recall.retract

The claim marked retracted, with a reason. Never deleted

facts.recall.query

Claims with their age, their stale mark, and how to measure them again

facts.recall.history

The raw events of one claim, in order

A worked example

Measure an endpoint:

// facts.measure.http
{ "project": "checkout", "url": "http://localhost:3000/api/orders" }
{
  "observation_id": "o_01K5Z9K2QX7T4BVWRM3P8QJHYD",
  "observed_at": "2026-09-22T10:14:03.482+02:00",
  "age_seconds": 0.14,
  "age_basis": "monotonic",
  "ok": true,
  "error": null,
  "data": {
    "kind": "http",
    "http_status": 200,
    "body": { "orders": [] },
    "peer": { "address": "127.0.0.1", "port": 3000, "family": 4 }
  }
}

Write down what you concluded, citing that measurement:

// facts.recall.assert
{
  "project": "checkout",
  "statement": "GET /api/orders responds 200 with an empty array",
  "evidence": [{ "type": "observation", "observation_id": "o_01K5Z9K2QX7T4BVWRM3P8QJHYD" }],
  "observed_by": "claude-opus-5 (session 3f9a1c7e)",
  "tags": ["api", "orders"]
}

Three days later, someone reads the registry:

// facts.recall.query → { "project": "checkout", "text": "orders" }
{
  "claim_id": "c_01K5Z9M5P0RA2CF7XN4T6WQJBE",
  "statement": "GET /api/orders responds 200 with an empty array",
  "age_seconds": 259200.0,
  "stale": true,
  "remeasure_with": {
    "tool": "facts.measure.http",
    "arguments": { "project": "checkout", "url": "http://localhost:3000/api/orders" },
    "omitted": []
  }
}

The server does not decide whether to re-measure. It gives you the age and the how; the decision is yours. If the answer has changed, retract with a reason — the original claim stays in the log, byte for byte, and facts.recall.history shows both events.

Where the data lives

$PLINTH_DATA_DIR/                 # defaults to ~/.plinth
└── projects/<project>/logs/
    ├── claims.jsonl              # append-only. Readable with tail, copyable with rsync
    └── observations.jsonl        # measurements, failed ones included, so they can be cited

claims.jsonl is append-only in the strict sense: a retraction is a new line, never an edit. The current state is a projection over the events. There is no update and no delete — not discouraged, absent from the interface.

Network safety

Measurements come from the server's network position, and the destination is validated by resolved IP, not by name. A name is not a destination: evil.localhost can resolve to a public address, and can resolve to loopback once and to something else a moment later.

  • Every resolved address is classified; if any falls outside the profile, the call is refused without opening a socket.

  • The connection is made to the validated address, with the original Host and SNI, which closes the window between checking and connecting.

  • Every redirect hop repeats the whole check.

  • 169.254.169.254 and all link-local addresses are denied in every profile, allowlist included. That is the first stop of any exfiltration in a container.

  • Environment proxies are ignored: a proxy would bypass the IP validation entirely.

The operator picks one profile when starting the server; no tool argument can widen it:

Profile

Reaches

deny-all

Nothing

loopback (default)

Services on the same machine

private

The same machine and private networks (RFC 1918, fc00::/7) — devices on your LAN

allowlist

Exactly the host:port pairs listed, public or not — the way to measure a website

An allowlist entry is an exact host:port. There are no domain wildcards, because a wildcard turns an MCP server into an outbound proxy with an extra step; a malformed entry stops the server at startup rather than being ignored. The active profile is stated in server/discover, so the model knows where it can measure from.

Secrets (authorization, cookie, set-cookie, API keys, JWTs, PEM blocks, URL userinfo, query parameters such as ?token=) are sent intact to the destination and redacted before anything is written to disk, returned, or logged — claims and retraction reasons included. There is no window in which a credential sits in a file. remeasure_with leaves out whatever was redacted rather than replaying a placeholder as a broken request, and names it in omitted so you know what to supply.

Configuration

Read once at startup. A value that does not parse stops the server with a message naming the variable.

Variable

Default

Effect

PLINTH_DATA_DIR

~/.plinth

Where the registry lives. Give each client its own: one server per directory

PLINTH_FACTS_NET_PROFILE

loopback

deny-all, loopback, private or allowlist

PLINTH_FACTS_ALLOW_HOSTS

—

Comma-separated host:port pairs. Required by allowlist, refused by the others

PLINTH_FACTS_MAX_REDIRECTS

5

Hops a call may follow when it asks to (0–20)

PLINTH_FACTS_DEFAULT_STALE_SECONDS

3600

stale threshold when a query gives none

To measure a public site, for example:

"env": {
  "PLINTH_DATA_DIR": "/absolute/path/to/a/data/dir",
  "PLINTH_FACTS_NET_PROFILE": "allowlist",
  "PLINTH_FACTS_ALLOW_HOSTS": "api.example.com:443,www.example.com:443"
}

No variable introduces knowledge of any particular project: no base URLs, no routes, no credentials. That is the point — the same server serves every app you point it at.

What it will never do

measure.assert_status · measure.wait_until · recall.summarize · recall.rank · recall.delete · project.health

Each one breaks rule 1 or rule 3. The moment this server starts giving verdicts it stops being a source and becomes another agent you have to verify — which is exactly the work it exists to save.

Protocol

Speaks both eras of MCP, chosen per connection by the client's first message:

  • 2025-06-18 and 2025-11-25, opened with initialize — what Claude Code and most clients use today.

  • 2026-07-28, the stateless revision: server/discover, a _meta envelope on every request, resultType on every result.

Nothing to configure. See ADR-0004.

The wire format is implemented directly against the normative schema, which is vendored in vendor/ and checked against the implementation at compile time. The official SDK for this revision exists as @modelcontextprotocol/server 2.0; whether to adopt it is a measured decision still to be taken, and every protocol detail lives behind src/protocol/ so that swapping it touches nothing else. See ADR-0001 and ADR-0002.

Building your own server on the core

The package exports the core facts-mcp is built from — ProtocolServer, serveStdio, defineTool, Result, the envelope, redaction — for an MCP server with tools of its own. Install it from a release tag; it builds itself on install:

npm install github:Yesid8/mcp-measure-and-recall#v0.3.0

Use ProtocolServer directly, not build(): build() is facts-mcp, including a store that writes every observation to disk. ProtocolServer writes nothing unless given a recordObservation. Its toolPrefix option replaces the facts. prefix ('' for bare names) and requireProject: false drops the project parameter. Example and details in docs/USO.md.

Development

npm install
npm run typecheck      # strict, with noUncheckedIndexedAccess and exactOptionalPropertyTypes
npm test               # unit, contract and conformance tests
npm run test:coverage  # the same, with a coverage floor that fails the run
npm run lint
npm run format:check
npm run build

CI runs all of the above on Linux and Windows, on Node 22 and 24. Windows is in the matrix on purpose: file truncation and stdin chunking behave differently there, and two of the bugs the tests now guard against only ever showed up on it.

The TLS tests generate a throwaway certificate with openssl and skip when it is not installed; no key material is committed.

The tests are the contract, not examples. They fail if the server starts judging: a 500 must stay ok: true, silence on a socket must stay a valid observation, a retraction must leave the previous bytes untouched, and a clock jumping backwards must never produce a negative age. The mechanical checks of the specification run as ordinary tests too: every schema field described, no judgement key in any output, no secret in any file on disk, no socket opened under deny-all, no host:port or API route anywhere in src/.

Specification in docs/. Prose is in Spanish; code, field names and error codes are in English. Contributions: CONTRIBUTING.md. Security reports: SECURITY.md.

License

0BSD: use, copy, modify and distribute it for any purpose, with or without fee, and with no attribution required.

Available Tools

6 tools
facts.measure.httpMeasure HTTPA
Read-only

Make one HTTP request and return the raw status, headers and body, with the time it was observed and the IP actually connected to. Measures from the server's network position. It does NOT judge the result: a 500 received cleanly is a successful observation (ok:true, http_status 500). It does not check the status against an expectation, does not validate the body, does not retry, and does not follow redirects unless asked.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesAbsolute URL. Subject to the network policy.
bodyNoRequest body. An object or array is serialized to JSON, with content-type application/json if absent; a string is sent as is.
methodNoHTTP method.GET
headersNoRequest headers. Sent intact; redacted in the output and on disk.
projectYesProject this observation belongs to.
timeout_msNoTotal timeout.
max_body_bytesNoBody cap. On exceeding it the body is truncated and a warning is emitted.
follow_redirectsNoWhen false, a 302 is returned as a 302. Each hop is revalidated by the policy.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYesWhether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true.
dataYesThe raw observation, or null when error is set.
toolYesFull tool name.
errorYesWhat prevented observing, if anything.
targetYesEffective arguments, redacted.
projectYesThe normalized project.
warningsYesNon-fatal notices.
age_basisYesWhich clock the age came from.
request_idYesThe request that produced it.
age_secondsYesSeconds since observed_at.
duration_msYesHow long the observation took.
observed_atYesISO 8601 with offset.
observation_idYesCitable id of this observation.

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With readOnlyHint/openWorldHint already covering safety, the description still adds substantial behavioral context: no retry, no body validation, no status-checking, no redirect following unless asked, and the counterintuitive ok:true-with-500 semantics. This is exactly the kind of non-obvious contract an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the core action and return payload, then the non-judgment semantics. Slight redundancy in the negative list ('does not check the status', 'does not validate', 'does not retry') but every clause carries distinct meaning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description needn't explain return values, and it still describes the observation envelope (ok flag, http_status, observed time, IP). Combined with a fully documented 8-parameter schema, nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents every parameter including body serialization, redaction, truncation, and redirect revalidation. The description only echoes follow-redirect behavior and adds no syntax or format detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a concrete verb and resource ('Make one HTTP request') and enumerates exactly what is returned (status, headers, body, observation time, connected IP). The phrase 'Measures from the server's network position' cleanly separates it from the sibling facts.measure.ws and from any client-side fetch.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'does NOT judge the result' paragraph effectively defines the boundary against an assertion tool (facts.recall.assert): this tool observes, it does not evaluate an expectation. No sibling is named explicitly and no exclusion is given for facts.measure.ws, so it falls short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

facts.measure.wsMeasure WebSocketA
Read-only

Connect to a WebSocket, listen for a given number of seconds, and return the messages that arrived, each with its absolute time and its offset from the open. Measures from the server's network position. Zero messages is a valid observation, not an error: it does NOT wait for a particular message, does not interpret payloads, does not reconnect, and does not say whether silence is a problem.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesws:// or wss:// URL. Subject to the network policy.
sendNoMessages to send once the socket is open.
headersNoHandshake headers, e.g. auth.
projectYesProject this observation belongs to.
max_messagesNoStop recording received messages at this many; listening continues for the full time. Does not count your own send messages.
subprotocolsNoRequested subprotocols.
listen_secondsYesHow long to listen. The full time is always served unless the peer closes.
max_total_bytesNoAggregate cap across received messages.
max_message_bytesNoPer-message cap; larger payloads are truncated.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYesWhether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true.
dataYesThe raw observation, or null when error is set.
toolYesFull tool name.
errorYesWhat prevented observing, if anything.
targetYesEffective arguments, redacted.
projectYesThe normalized project.
warningsYesNon-fatal notices.
age_basisYesWhich clock the age came from.
request_idYesThe request that produced it.
age_secondsYesSeconds since observed_at.
duration_msYesHow long the observation took.
observed_atYesISO 8601 with offset.
observation_idYesCitable id of this observation.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only cover readOnlyHint/openWorldHint; the description adds substantive behavior beyond that: measurement origin (server's network position), the fact that zero messages is a valid observation rather than a failure, and that silence is not diagnosed. It does not cover auth/network-policy expectations or the truncation semantics handled elsewhere, and the readOnlyHint sits slightly uneasily with the optional `send` capability.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose is front-loaded in the first sentence, followed by a one-line measurement-scope note and a compact negative list. The trailing clause 'does not say whether silence is a problem' is mildly redundant with the preceding zero-messages statement, but otherwise every clause carries information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With nine parameters, nested `send` objects, and an output schema, the description supplies the behavioral framing the schema cannot (timing origin, silence semantics, no reconnection). It is nearly complete, lacking only mention of the network policy / authorization context that the schema references for `url` and `headers`.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so every parameter (listen_seconds, max_messages, max_message_bytes, headers, send, etc.) is already documented in the schema, including caps and defaults. The description adds only timing semantics ('offset from the open'), so the baseline 3 for schema-driven coverage is correct.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a precise verb chain (connect, listen for N seconds, return arrived messages with absolute time and offset from open) and names the resource (WebSocket), which cleanly separates it from the sibling facts.measure.http. An agent knows exactly what this tool produces without reading the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit negative scope -- does not wait for a particular message, does not interpret payloads, does not reconnect -- which tells the agent when this observation tool is the wrong choice for waiting/asserting on a specific message. However, it never names the alternative tool (e.g. facts.recall.assert) that would be correct for that job, so routing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

facts.recall.assertAssert a claimA

Write a claim into the project registry: statement, evidence, who observed it and when. It does NOT measure anything and it does NOT check that the evidence supports the statement — that would be interpreting. It checks only that the evidence exists and belongs to this project, and embeds it so the claim stays auditable.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagsNoTags, normalized like a project.
projectYesProject whose registry to write to.
evidenceYesAt least one piece of evidence. A claim without evidence does not go in.
statementYesThe claim. Free text: it is never parsed, validated or interpreted.
observed_atNoWhen it was observed. Required with external evidence; forbidden when all evidence is this server’s own, where it is derived.
observed_byYesWho observed it. Mandatory, no default: a claim without an author does not go in.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYesWhether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true.
dataYesThe raw observation, or null when error is set.
toolYesFull tool name.
errorYesWhat prevented observing, if anything.
targetYesEffective arguments, redacted.
projectYesThe normalized project.
warningsYesNon-fatal notices.
age_basisYesWhich clock the age came from.
request_idYesThe request that produced it.
age_secondsYesSeconds since observed_at.
duration_msYesHow long the observation took.
observed_atYesISO 8601 with offset.
observation_idYesCitable id of this observation.

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare a non-read-only, non-destructive, closed-world write, and the description goes beyond them: it discloses that only evidence existence and project ownership are validated, that semantic support is deliberately NOT checked, and that evidence is embedded for auditability. It omits idempotency/duplicate handling and error behavior for a mutation tool, which keeps it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the action and payload, then the scoping negations and the auditability rationale. Every sentence earns its place, though the 'that would be interpreting' aside and the final clause slightly pad an otherwise tight definition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema available, return values need no explanation, and the description covers the write semantics and validation boundary well for a 6-parameter mutation. Remaining gaps are minor: no statement on duplicate/conflicting claims or on what happens if the referenced evidence is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents every parameter including the observed_at derivation rule and the evidence union. The description restates the field set without adding format, syntax, or constraints beyond what the schema provides, which is the expected baseline here.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Write a claim into the project registry') and enumerates the payload fields. The explicit negations ('does NOT measure anything') separate it from the facts.measure.* siblings, so an agent can distinguish it without opening other schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description draws a clear boundary: use this to record a claim, not to measure and not to interpret evidence. It does not name any alternative tool (e.g. facts.recall.query for reading) or state prerequisites, but the when-not conditions are explicit enough to route correctly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

facts.recall.historyClaim historyA
Read-onlyIdempotent

Return the raw events of one claim, in order, exactly as they sit in the log. This is the auditable proof that nothing was rewritten: an assert, then a retract if there was one.

ParametersJSON Schema
NameRequiredDescriptionDefault
projectYesProject whose registry to read.
claim_idYesThe claim whose events to return.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYesWhether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true.
dataYesThe raw observation, or null when error is set.
toolYesFull tool name.
errorYesWhat prevented observing, if anything.
targetYesEffective arguments, redacted.
projectYesThe normalized project.
warningsYesNon-fatal notices.
age_basisYesWhich clock the age came from.
request_idYesThe request that produced it.
age_secondsYesSeconds since observed_at.
duration_msYesHow long the observation took.
observed_atYesISO 8601 with offset.
observation_idYesCitable id of this observation.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnlyHint=true, idempotentHint=true, openWorldHint=false), so the bar is lower. The description still adds real behavioral context beyond them: the events are returned raw, ordered, unrewritten, and shaped as 'an assert, then a retract if there was one.' That is genuinely useful for interpreting the log.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences, front-loaded with the core action and scope. Every clause earns its place and none of it repeats structured fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need not be described, and annotations carry the safety profile. The description adds the audit framing and event shape, leaving only the relationship to sibling recall tools unspecified — a minor gap for a simple two-parameter read.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters ('project whose registry to read', 'claim whose events to return'). The description adds no further syntax or semantics beyond what the schema provides, which is the correct baseline when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return the raw events of one claim, in order') with the scoping qualifier 'exactly as they sit in the log.' This implicitly separates it from facts.recall.query (current state) and facts.recall.assert/retract (mutations), but it never names a sibling, so the differentiation is inferential rather than explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied via the audit framing ('auditable proof that nothing was rewritten'), which suggests a forensic/verification context. It never states when to prefer this over facts.recall.query or when it is unnecessary, so the agent must infer the boundary itself.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

facts.recall.queryQuery the registryA
Read-onlyIdempotent

Read claims from the project registry. It NEVER measures and opens no sockets: it returns what was recorded, each claim with its own age and stale mark, plus remeasure_with — the tool and arguments to measure it again yourself. Retracted claims are hidden unless asked for; they are never gone.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagsNoConjunction: all of them must be present. Normalized as on assert.
textNoCase-insensitive substring over statement. No ranking, no relevance.
limitNoPage size.
orderNoSort order.observed_desc
cursorNoOpaque cursor from a previous page. Every page is cut from the registry as it stood at the first one, so claims written meanwhile never shift the window. Valid only with the filters and order it was issued for.
projectYesProject whose registry to read.
evidenceNoHow much evidence to include. `ref` keeps responses small.ref
observed_byNoExact match on the author.
stale_secondsNoThreshold for the stale mark. Your threshold, not a judgement of ours. Defaults to the server's configured one (an hour unless set).
observed_afterNoISO 8601 lower bound on observed_at.
only_retractedNoOnly retracted claims. Takes precedence over include_retracted.
observed_beforeNoISO 8601 upper bound on observed_at.
include_retractedNoBring retracted claims too.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYesWhether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true.
dataYesThe raw observation, or null when error is set.
toolYesFull tool name.
errorYesWhat prevented observing, if anything.
targetYesEffective arguments, redacted.
projectYesThe normalized project.
warningsYesNon-fatal notices.
age_basisYesWhich clock the age came from.
request_idYesThe request that produced it.
age_secondsYesSeconds since observed_at.
duration_msYesHow long the observation took.
observed_atYesISO 8601 with offset.
observation_idYesCitable id of this observation.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, so the safety profile is covered; the description nonetheless adds real value by confirming no socket activity and disclosing output traits — per-claim age and stale mark, a remeasure_with field, and retraction visibility semantics. It does not discuss permissions, limits beyond paging, or failure behavior, keeping it short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, then the non-measurement guarantee, then return and retraction behavior — a sensible order. Three sentences with little waste, though the read-only point is restated in two guises ("NEVER measures" / "returns what was recorded"), which slightly dilutes it.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 13 fully documented parameters and an output schema present, the description only needs to supply the behavioral frame, and it does: read-only, closed-world, snapshot-consistent paging, and non-destructive retraction. Nothing an agent needs to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and each of the 13 parameters has a detailed inline description, so the schema does the heavy lifting and a 3 is the correct baseline. The prose only adds conceptual color about retracted claims, not syntax or semantics beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Read claims from the project registry") and immediately delimits scope against the measure siblings: "It NEVER measures and opens no sockets: it returns what was recorded." An agent can distinguish this from facts.measure.http/ws and the other recall tools without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear context for use — read recorded claims rather than produce fresh measurements — and covers a key behavioral branch ("Retracted claims are hidden unless asked for"). It never explicitly names an alternative tool or the condition that selects facts.recall.history/assert/retract, so routing is implied rather than spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

facts.recall.retractRetract a claimA

Mark a claim as retracted, with a reason and an author. The claim is NEVER deleted: the original line stays byte for byte and the retraction is a new event. There is no delete tool and no parameter that removes anything.

ParametersJSON Schema
NameRequiredDescriptionDefault
byYesWho is retracting it.
reasonYesWhy it is being retracted. Without a reason there is no retraction.
projectYesProject whose registry to write to.
claim_idYesThe claim to mark as retracted.
superseded_byNoClaim id that replaces it. Must exist in the same project.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYesWhether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true.
dataYesThe raw observation, or null when error is set.
toolYesFull tool name.
errorYesWhat prevented observing, if anything.
targetYesEffective arguments, redacted.
projectYesThe normalized project.
warningsYesNon-fatal notices.
age_basisYesWhich clock the age came from.
request_idYesThe request that produced it.
age_secondsYesSeconds since observed_at.
duration_msYesHow long the observation took.
observed_atYesISO 8601 with offset.
observation_idYesCitable id of this observation.

TDQS

A4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructiveHint=false, and the description goes further with concrete append-only semantics: the original line stays byte for byte, the retraction is a new event, and no parameter removes anything. That is exactly the kind of behavioral context annotations cannot express on their own.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, front-loaded with the action, then the immutability guarantee. No filler and every sentence carries distinct information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A mutation tool with annotations and an output schema needs little more, and the immutability disclosure closes the biggest risk of misinterpretation. The remaining gap is the superseded_by parameter's role in distinguishing supersession from retraction, which is left entirely to the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all five parameters including the required reason and by. The description restates that a reason and author are attached but adds no syntax or semantics for superseded_by (e.g. that it must exist in the same project), so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource ('Mark a claim as retracted') with the required evidence named (reason, author). It distinguishes itself from a hypothetical delete tool, but never contrasts with actual siblings like facts.recall.assert or facts.recall.history, which the agent must choose between.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied by 'Mark a claim as retracted' and the note that there is no delete tool, so the agent knows this is the only removal-adjacent path. However, it never says when to retract versus supersede, nor when to use the optional superseded_by versus a plain retraction.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv0.3.0
    • First observedfacts.measure.http
    • First observedfacts.measure.ws
    • First observedfacts.recall.assert
    • First observedfacts.recall.history
    • First observedfacts.recall.query
    • First observedfacts.recall.retract

TDQS

A4.2/5.0

Scored across 6 tools

Disambiguation5/5

The two namespaces (measure vs recall) cleanly separate observing the network from recording claims, and within each namespace the actions are distinct. measure.http vs measure.ws differ by protocol, while recall.assert/query/retract/history map to write, read, soft-delete, and raw audit respectively, with descriptions reinforcing the boundaries. No two tools can be plausibly confused.

Naming Consistency5/5

Every tool follows the same dotted namespace pattern facts.<domain>.<action> with a single lowercase verb, producing two tidy groups (measure.* and recall.*). The convention is applied uniformly with no camelCase/snake_case mixing or stray verb styles.

Tool Count5/5

Six tools is well scoped for the domain: four cover the fact-log lifecycle (assert, query, retract, history) and two cover measurement (http, ws). Each tool has a clear, non-redundant role and nothing feels padded or missing at the count level.

Completeness4/5

The immutable-log lifecycle is complete: assert, query, retract (with history as the audit trail), and deliberately no update/delete, which is defensible by design. Measurement covers http and ws, but other network probes (DNS, ping, TCP) are absent, a minor gap if broader measurement was intended.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Enables submitting claims and receiving Ed25519-signed, hash-chained verdicts resolved against real external ground truth, supporting resolvers like GitHub PRs, on-chain transactions, URL JSON, HTTP status, and Kalshi markets.
    5
    56 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides evidence-oriented MCP service for cryptographically identified agents, bounded public contracts, privacy-preserving records, and append-only audit.
    Apache 2.0
  • F
    license
    A
    quality
    A
    maintenance
    Enables agents to verify claims against a real falsification ledger, querying pre-registered refutation thresholds, recorded verdicts, and verifier drift metrics instead of guessing.
    5
    24 PyPI
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to obtain independent, Ed25519-signed receipts confirming real-world facts: whether a page is online, a domain is legitimate, an email address is deliverable, a heartbeat was declared, or a scheduled task actually ran. Each response includes a verifiable cryptographic receipt, so agents can prove to their users that an asserted outcome was observed by a neutral third party rather than self-attested.
    MIT