facts-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@facts-mcpMeasure http://localhost:3000/api/orders for project checkout and report HTTP status"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
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 returnsremeasure_with— the exact tool and arguments to take that measurement again yourself.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.
No output field is a judgement. No
health,score,severity,passed,recommendation.okmeans 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 buildIt 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 |
| Status, headers, body, timings — and which IP it actually connected to |
| The messages that arrived in N seconds, each with absolute time and offset |
| The claim as written, with its evidence embedded |
| The claim marked retracted, with a reason. Never deleted |
| Claims with their age, their |
| 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 citedclaims.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
Hostand SNI, which closes the window between checking and connecting.Every redirect hop repeats the whole check.
169.254.169.254and 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 |
| Nothing |
| Services on the same machine |
| The same machine and private networks (RFC 1918, |
| Exactly the |
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 |
|
| Where the registry lives. Give each client its own: one server per directory |
|
|
|
| — | Comma-separated |
|
| Hops a call may follow when it asks to ( |
|
|
|
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-18and2025-11-25, opened withinitialize— what Claude Code and most clients use today.2026-07-28, the stateless revision:server/discover, a_metaenvelope on every request,resultTypeon 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.0Use 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 buildCI 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 toolsfacts.measure.httpMeasure HTTPARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Absolute URL. Subject to the network policy. | |
| body | No | Request body. An object or array is serialized to JSON, with content-type application/json if absent; a string is sent as is. | |
| method | No | HTTP method. | GET |
| headers | No | Request headers. Sent intact; redacted in the output and on disk. | |
| project | Yes | Project this observation belongs to. | |
| timeout_ms | No | Total timeout. | |
| max_body_bytes | No | Body cap. On exceeding it the body is truncated and a warning is emitted. | |
| follow_redirects | No | When false, a 302 is returned as a 302. Each hop is revalidated by the policy. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | Whether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true. |
| data | Yes | The raw observation, or null when error is set. |
| tool | Yes | Full tool name. |
| error | Yes | What prevented observing, if anything. |
| target | Yes | Effective arguments, redacted. |
| project | Yes | The normalized project. |
| warnings | Yes | Non-fatal notices. |
| age_basis | Yes | Which clock the age came from. |
| request_id | Yes | The request that produced it. |
| age_seconds | Yes | Seconds since observed_at. |
| duration_ms | Yes | How long the observation took. |
| observed_at | Yes | ISO 8601 with offset. |
| observation_id | Yes | Citable id of this observation. |
TDQS
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.
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.
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.
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.
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.
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 WebSocketARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | ws:// or wss:// URL. Subject to the network policy. | |
| send | No | Messages to send once the socket is open. | |
| headers | No | Handshake headers, e.g. auth. | |
| project | Yes | Project this observation belongs to. | |
| max_messages | No | Stop recording received messages at this many; listening continues for the full time. Does not count your own send messages. | |
| subprotocols | No | Requested subprotocols. | |
| listen_seconds | Yes | How long to listen. The full time is always served unless the peer closes. | |
| max_total_bytes | No | Aggregate cap across received messages. | |
| max_message_bytes | No | Per-message cap; larger payloads are truncated. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | Whether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true. |
| data | Yes | The raw observation, or null when error is set. |
| tool | Yes | Full tool name. |
| error | Yes | What prevented observing, if anything. |
| target | Yes | Effective arguments, redacted. |
| project | Yes | The normalized project. |
| warnings | Yes | Non-fatal notices. |
| age_basis | Yes | Which clock the age came from. |
| request_id | Yes | The request that produced it. |
| age_seconds | Yes | Seconds since observed_at. |
| duration_ms | Yes | How long the observation took. |
| observed_at | Yes | ISO 8601 with offset. |
| observation_id | Yes | Citable id of this observation. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| tags | No | Tags, normalized like a project. | |
| project | Yes | Project whose registry to write to. | |
| evidence | Yes | At least one piece of evidence. A claim without evidence does not go in. | |
| statement | Yes | The claim. Free text: it is never parsed, validated or interpreted. | |
| observed_at | No | When it was observed. Required with external evidence; forbidden when all evidence is this server’s own, where it is derived. | |
| observed_by | Yes | Who observed it. Mandatory, no default: a claim without an author does not go in. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | Whether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true. |
| data | Yes | The raw observation, or null when error is set. |
| tool | Yes | Full tool name. |
| error | Yes | What prevented observing, if anything. |
| target | Yes | Effective arguments, redacted. |
| project | Yes | The normalized project. |
| warnings | Yes | Non-fatal notices. |
| age_basis | Yes | Which clock the age came from. |
| request_id | Yes | The request that produced it. |
| age_seconds | Yes | Seconds since observed_at. |
| duration_ms | Yes | How long the observation took. |
| observed_at | Yes | ISO 8601 with offset. |
| observation_id | Yes | Citable id of this observation. |
TDQS
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.
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.
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.
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.
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.
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 historyARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| project | Yes | Project whose registry to read. | |
| claim_id | Yes | The claim whose events to return. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | Whether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true. |
| data | Yes | The raw observation, or null when error is set. |
| tool | Yes | Full tool name. |
| error | Yes | What prevented observing, if anything. |
| target | Yes | Effective arguments, redacted. |
| project | Yes | The normalized project. |
| warnings | Yes | Non-fatal notices. |
| age_basis | Yes | Which clock the age came from. |
| request_id | Yes | The request that produced it. |
| age_seconds | Yes | Seconds since observed_at. |
| duration_ms | Yes | How long the observation took. |
| observed_at | Yes | ISO 8601 with offset. |
| observation_id | Yes | Citable id of this observation. |
TDQS
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.
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.
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.
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.
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.
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 registryARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| tags | No | Conjunction: all of them must be present. Normalized as on assert. | |
| text | No | Case-insensitive substring over statement. No ranking, no relevance. | |
| limit | No | Page size. | |
| order | No | Sort order. | observed_desc |
| cursor | No | Opaque 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. | |
| project | Yes | Project whose registry to read. | |
| evidence | No | How much evidence to include. `ref` keeps responses small. | ref |
| observed_by | No | Exact match on the author. | |
| stale_seconds | No | Threshold for the stale mark. Your threshold, not a judgement of ours. Defaults to the server's configured one (an hour unless set). | |
| observed_after | No | ISO 8601 lower bound on observed_at. | |
| only_retracted | No | Only retracted claims. Takes precedence over include_retracted. | |
| observed_before | No | ISO 8601 upper bound on observed_at. | |
| include_retracted | No | Bring retracted claims too. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | Whether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true. |
| data | Yes | The raw observation, or null when error is set. |
| tool | Yes | Full tool name. |
| error | Yes | What prevented observing, if anything. |
| target | Yes | Effective arguments, redacted. |
| project | Yes | The normalized project. |
| warnings | Yes | Non-fatal notices. |
| age_basis | Yes | Which clock the age came from. |
| request_id | Yes | The request that produced it. |
| age_seconds | Yes | Seconds since observed_at. |
| duration_ms | Yes | How long the observation took. |
| observed_at | Yes | ISO 8601 with offset. |
| observation_id | Yes | Citable id of this observation. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| by | Yes | Who is retracting it. | |
| reason | Yes | Why it is being retracted. Without a reason there is no retraction. | |
| project | Yes | Project whose registry to write to. | |
| claim_id | Yes | The claim to mark as retracted. | |
| superseded_by | No | Claim id that replaces it. Must exist in the same project. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | Yes | Whether the observation could be taken. NOT a judgement about what was observed: a cleanly received HTTP 500 is ok:true. |
| data | Yes | The raw observation, or null when error is set. |
| tool | Yes | Full tool name. |
| error | Yes | What prevented observing, if anything. |
| target | Yes | Effective arguments, redacted. |
| project | Yes | The normalized project. |
| warnings | Yes | Non-fatal notices. |
| age_basis | Yes | Which clock the age came from. |
| request_id | Yes | The request that produced it. |
| age_seconds | Yes | Seconds since observed_at. |
| duration_ms | Yes | How long the observation took. |
| observed_at | Yes | ISO 8601 with offset. |
| observation_id | Yes | Citable id of this observation. |
TDQS
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.
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.
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.
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.
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.
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.
6 tool updates
v0.3.0- First observed
facts.measure.http - First observed
facts.measure.ws - First observed
facts.recall.assert - First observed
facts.recall.history - First observed
facts.recall.query - First observed
facts.recall.retract
TDQS
Scored across 6 tools
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.
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.
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.
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
Related MCP Connectors
Live uptime and source-URL verification for AI agents, with per-call x402/USDC paid endpoints.
Live uptime and source-URL verification for AI agents, with per-call x402/USDC paid endpoints.
Live uptime and source-URL verification for AI agents, with per-call x402/USDC paid endpoints.
Live uptime and source-URL verification for AI agents, with per-call x402/USDC paid endpoints.
Related MCP Servers
- AlicenseAqualityAmaintenanceEnables 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.556 npm1MIT
- AlicenseNot gradedqualityAmaintenanceProvides evidence-oriented MCP service for cryptographically identified agents, bounded public contracts, privacy-preserving records, and append-only audit.Apache 2.0
- FlicenseAqualityAmaintenanceEnables agents to verify claims against a real falsification ledger, querying pre-registered refutation thresholds, recorded verdicts, and verifier drift metrics instead of guessing.524 PyPI-
- AlicenseNot gradedqualityCmaintenanceEnables 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