Experiment · read-only · hosted · v0.3.0

ObjectCanon

A resolver for claims about individual physical objects. Ask it about a guitar and it tells you what the record says — and who said it, how sure they were, when, and who disagreed.

The idea in one paragraph

Most systems store facts about objects. This guitar was made in 1968. By the time that reaches a form, a spreadsheet or an agent, it is a bare string, and nothing downstream can tell whether it came from a professional who examined the instrument or from the seller who wants to sell it. ObjectCanon stores claims instead. Every statement carries who asserted it, how confident they were, and when. Claims about the same attribute are allowed to disagree, and nothing is overwritten. When you ask for a value, the resolver picks one and hands it over — and tells you it had to pick, who lost, and whether its own answer is weaker than the thing it beat.

That last part is the point. A human reading a form discounts "original, per seller" automatically. An agent filling in an insurance schedule or an estate inventory has nothing to be skeptical with. This is an attempt at a machine-readable way to carry doubt.

Connect it

Hosted MCP server, Streamable HTTP, no authentication, no accounts, no writes:

https://objectcanon.replit.app/mcp

For Claude Desktop, Cursor, or any client that takes a remote MCP URL:

{
  "mcpServers": {
    "objectcanon": {
      "url": "https://objectcanon.replit.app/mcp"
    }
  }
}

Or straight over HTTP, no client required:

curl -s https://objectcanon.replit.app/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Prefer to run it yourself? The source is on GitHub and the same three tools work over stdio with node server.js. Health · Usage · llms.txt

Three tools

types

No arguments. Returns the object types present and, for each, the attribute names actually in use with their value shapes and how many claims are abstentions. Call it first; it is the vocabulary, and attribute names are not guessable.

lookup

Optional type, attribute, value (substring, case-insensitive), limit, cursor. Returns compact summaries — id, type, name, how many attributes are contested, and how many resolved against a more confident competing claim. Narrow here, then resolve.

resolve

Takes id, and view of either resolved (default) or claims. The resolved view gives one value per attribute, graded. The claims view gives every claim, unresolved, with source kind and confidence — use it when the disagreement itself is the subject.

The response contract

An attribute in the resolved view. This one is the interesting case:

"neckOriginal": {
  "value": true,
  "source_kind": "owner",
  "confidence": "low",
  "contested": true,
  "claim_count": 2,
  "evidence": "contested",
  "contested_by": [
    { "value": false, "source_kind": "third_party",
      "confidence": "medium", "asserted_at": "2022-11-19" }
  ],
  "resolved_against_stronger_evidence": true,
  "abstentions": [
    { "source_kind": "ai", "confidence": "low",
      "asserted_at": "2026-09-01", "reason": "insufficient_evidence" }
  ]
}

Read that as: the record says the neck is original, the only source asserting it is the owner at low confidence, an independent observer said otherwise at higher confidence four years ago, an AI looked and declined to call it, and the value you are being handed won on precedence rather than on evidence.

evidence

One field to branch on. It exists because contested: false reads as "trustworthy" when it often just means nobody else bothered to weigh in.

GradeMeans
corroboratedTwo or more independent claims agree.
contestedClaims disagree. See contested_by.
sole_claimExactly one source asserted this. Nothing corroborates it. A low-confidence AI estimate and a high-confidence appraisal both land here — check source_kind and confidence.
not_assertedNobody asserted a value. Absence is explicit, and reason says why.

contested_by

The losing claims, each with its value, source kind, confidence and date. This is here so you do not have to accept the default ranking. If your use case wants appraiser precedence over owner precedence, you have enough in one response to re-rank yourself without a second call.

resolved_against_stronger_evidence

True when the winning claim has lower stated confidence than something it outranked. This is the single most misleading state the resolver can produce, so it is named rather than left to be inferred. Rolled up per object as resolution_warnings, and in lookup as resolution_warning_count.

If you do one thing with this response, do this: before acting on a value for insurance, lending, valuation or inventory, read resolution_warnings. Everything named there is a value the record technically asserts and does not actually support.

abstentions

A claim with a null value is a source that was asked and declined to assert. That is information, not absence, so it does not count as a competing value and does not disappear from the resolved view. An AI abstaining is frequently the most honest thing in the record.

The resolution policy, stated plainly

source_kind (owner > appraiser > third_party > ai), then confidence, then recency

Owner-precedence is defensible for a provenance registry: the owner is the custodian of record and usually the only continuous witness. It is the wrong default for a valuation consumer, where an independent observation should beat a self-report. Rather than ship a second selectable profile, the resolved view exposes enough for you to apply your own ordering — and flags the cases where the default and the evidence point different directions.

The policy string ships in both views, so a consumer never has to guess what produced a value.

Privacy model

Objects not marked public are filtered at load. A request for a private object returns an error identical in shape to a request for an object that does not exist. There is no existence leak and no different status code. Private objects are absent from types counts and lookup totals too, not just from resolve.

On the caller side: the server logs which tool was called, when, a salted hash of the caller address, and the client name from the MCP handshake. No IP addresses, no arguments, no response bodies. The counts are public at /stats.

What this deliberately is not

Status

This is a two-week experiment with a pre-committed kill criterion, not a product. There is no revenue model and no roadmap. The corpus is currently three objects, hand-built to exercise conflict — a guitar with a contested neck and finish, a watch where an AI disagrees with an appraiser, and a table as a control. A larger import exists and is not loaded, because the point of the small corpus is that the response contract can be judged before anything is normalized against it.

The contract above is the thing under test. If it survives contact with someone else's agent, the corpus follows.

What would actually be useful to hear

More useful than "nice idea," in rough order: