Skip to main content
Glama
dVeracity Semantic MCP server

dVeracity Semantic MCP server

Official
by d-veracity
README.md
# dVeracity Semantic MCP server

[![smithery badge](https://smithery.ai/badge/ajvdvoort/dveracity)](https://smithery.ai/servers/ajvdvoort/dveracity)

Gives any MCP-capable AI agent (Claude Code, Cursor, custom agents) metered access
to the **dVeracity Semantic API** — natural-language queries over the
verified-emissions knowledge graph (Open Footprint / PPDM / OGMP-methane) — and
**VaaS** standards validation.

## Prerequisites

1. A free **Explorer** account (OAuth) or a paid plan: https://dveracity.com/pricing
   — Explorer includes API-key management and a monthly metered-call allowance.
2. An **API key** (`dvrc_…`): `POST /api/v1/api-keys` (or the dashboard)
3. API **credits** for calls beyond the free allowance: `POST /api/v1/vaas/credits/purchase`
   (or ask the agent to use `billing_purchase`)

The machine-readable service contract lives at `GET /api/v1/semantic/manifest`
(public, no auth).

## Install

From this directory: `npm install`

### Claude Code

```bash
claude mcp add dveracity \
  -e DVERACITY_API_KEY=dvrc_yourkey \
  -- node /path/to/dVE/mcp/semantic-mcp/index.js
```

### Generic MCP JSON config (Cursor, etc.)

```json
{
  "mcpServers": {
    "dveracity": {
      "command": "node",
      "args": ["/path/to/dVE/mcp/semantic-mcp/index.js"],
      "env": { "DVERACITY_API_KEY": "dvrc_yourkey" }
    }
  }
}
```

Optional: `DVERACITY_API_URL` overrides the API base URL (defaults to prod).

### KERI mode — verified agent identity (optional)

If the agent holds a **dVeracity Agent Authorization credential** (an ACDC issued
by its Legal Entity, chained to the Legal Entity's vLEI — see
`elm/docs/VLEI_AGENT_TOKENS_DESIGN.md`), set:

```
DVERACITY_KERI_AID=<the agent's AID (credential issuee)>
DVERACITY_KERI_PRESENTATION=/path/to/agent-credential.cesr   # self-contained CESR
```

The server then authenticates the agent by verifiable presentation
(challenge → exchange → 1-hour session, refreshed transparently) and attaches
`X-Keri-Session` to every call: the API key keeps carrying **billing**, the KERI
session adds **verified identity** — every metered call is attributed to the
agent AID and Legal Entity LEI in dVeracity's audit trail. The `keri_identity`
tool (free) shows the active identity. Scope denials (a credential that doesn't
carry e.g. `semantic:query`) surface as actionable errors naming the carried
scopes. Signify-based nonce signing is a planned enhancement.

## Tools

| Tool | Cost | What it does |
|---|---|---|
| `semantic_query` | credits | Natural-language question over the verified-emissions knowledge graph |
| `semantic_templates` | free | Catalog of supported query templates |
| `credits_balance` | free | Remaining credit balance |
| `list_standards` | free | Standards VaaS can validate against |
| `validate_data` | credits | Validate a payload against a supported standard |
| `keri_identity` | free | This agent's verified vLEI identity, when KERI mode is configured |

Every tool definition carries `_meta.pricing`:
`{ "unit": "credit", "amount": <int>, "usd": <decimal> }`, with `amount` 0 for a
free tool. A plan-gated tool also carries `"plan_required": "<tier>"` — 0
credits on `compliance_policies` or `compliance_evaluate` means "included in
the VaaS plan", not "free for any account".

### Billing

| Tool | Cost | What it does |
|---|---|---|
| `billing_plans` | free | Plans, credit packs, per-credit price and the free allowance (anonymous on the hosted endpoint) |
| `billing_status` | free | This account's plan, balance and remaining free allowance |
| `billing_purchase` | free | One-time credit checkout: returns a `checkoutUrl` for a human, plus a `paymentLink` when the deployment configures one |
| `billing_subscribe` | free | Membership checkout: returns `checkoutUrl` only; recurring charges need human approval |

### Open Footprint canonical model

Design-time tools for building an application on the Open Footprint standard.
Reading the model is free; only the check at the end is metered.

| Tool | Cost | What it does |
|---|---|---|
| `ofp_models` | free | The model domains (nine as of openfootprint b60a24e), and which database dialects have published DDL |
| `ofp_search_entities` | free | Search the canonical entities (262 entries as of b60a24e) by name, description or field |
| `ofp_entity` | free | One entity in full: fields, types, keys, relationships, physical table |
| `ofp_sectors` | free | Industry sectors, each with a status |
| `ofp_sector` | free | One sector, with its reference artifacts |
| `ofp_policies` | free | A sector's Rego guardrails, or an explicit "none published" |
| `compliance_policies` | paid plan | Approved regulatory compliance policies (rules extracted from a regulation, admin-approved); never drafts |
| `compliance_policy` | paid plan | One approved policy: Rego, extracted rules, inputs, provenance |
| `compliance_policy_register` | paid plan | The published release register: every provision, its legal reference, review status and why it is or is not enforced |
| `compliance_evaluate` | paid plan | Execute one named approved policy against a record: four-valued decision, per-check results, missing inputs, remediation, coverage. Stores an audit record |
| `compliance_evaluation` | paid plan | Read back one stored evaluation — the caller's own only |
| `ofp_validate` | credits | Check a payload against the model and, optionally, sector guardrails |
| `ofp_semantics` | free | O-DEF semantic codes, for aligning another system's fields onto the model |
| `ofp_semantic_code` | free | Which canonical fields carry one code — the reverse lookup a connector needs |
| `ofp_model_provenance` | free | Which snapshot of the standard this deployment serves |

Two behaviours are deliberate and worth knowing before you build against them.

**Ambiguous entity names fail rather than resolve.** dozens of entity names (the `ofp_models` summary reports the count as `ambiguousNameCount`)
are defined in more than one domain — `Country` is in four. `ofp_entity` without
a `domain` returns an error listing the candidates instead of picking one. Pass
`domain` whenever you know it.

**Semantic codes vary in usefulness.** Most canonical fields carry an O-DEF code,
but a code shared by many fields is a generic classification, and aligning to one
asserts almost nothing. The `ofp_models` summary reports the real shape under
`precision` — `preciseFieldThreshold` (the `fieldCount` at or below which a code
is precise rather than generic), `fieldsOnPreciseCodes` and
`preciseShareOfAnnotated` — and those follow the vendored snapshot rather than
this document. Every code is returned with its `fieldCount`: check it before
aligning to one, and pass `maxFieldCount` at that threshold to `ofp_semantics` to
see only the precise ones.

**"Nothing published" is an answer, not an error.** Most sectors are named in the
taxonomy but have no reference implementation, and only seven publish policy
guardrails. `ofp_policies` on such a sector returns `published: false` with a
reason, and `ofp_validate` reports `policy.ran: false`. Both mean *no rules are
published*, never *there are no constraints* — a payload checked for structure
alone is not a compliant one, and should not be described as one.

A fourth outcome, `unevaluable`, means the sector's rules ran but every rule that came back false reads an input the payload does not carry (`policy.missingInputs`, e.g. `co2e_kg`, `direction`, `counterparty_industry`). Those are e-ledger record fields, not canonical Open Footprint field names — `ofp_policies` lists them per policy under `inputs`. Unevaluable is neither a pass nor a breach, and `valid` is `null`.

### What `ofp_validate` checks, and what it does not

The response is a contract, not a verdict. Every call reports the check
catalogue in two lists: `checked` (what ran) and `checks_not_run` (what did not,
each with a `reason` and usually a `detail`). Read both before describing a
payload as anything.

| Check | Status | Reason reported when it does not run |
|---|---|---|
| `schema` — presence, primary key, types, declared constraints | runs | `entity_has_no_fields` |
| `value_range` | **runs** where the model declares a bound: a quantity outside the declared minimum or maximum is rejected. Which fields declare a bound moves with the vendored snapshot, so it fires on some entities and not others — read `checked` and `schema.coverage.constraintsDeclared` per call | `no_range_declared`, `no_numeric_fields` |
| `unit_coherence` | not run | `not_implemented` |
| `calculation_reproducibility` | **runs** where the record carries all three of a quantity, an emission factor value and a stated CO2e result: `quantity × factor = result` is reproduced within 1%, and a record that contradicts itself is rejected | `no_calculation_inputs`, `calculation_inputs_incomplete`, `ambiguous_calculation_inputs`, `calculation_inputs_unparseable` |
| `temporal_consistency` | **runs**: a validity period whose end precedes its start is rejected | `no_validity_pair`, `validity_pair_incomplete`, `validity_values_unparseable` |
| `enum_membership` | **runs** where the vocabulary publishes its members: a well-formed key for a referent that does not exist is rejected | `no_members_published`, `no_reference_field_supplied`, `reference_ids_unparseable` |
| `referential_integrity` | not run: keys are pattern- and member-checked, never resolved against live records | `no_data_plane` |
| `factor_provenance` | not run | `not_implemented` |
| `materiality` | not run | `not_implemented` |
| `sector_policy` | runs when a sector with published guardrails covers the record type | `no_sector_supplied`, `no_policy_published`, `policy_source_missing`, `policy_package_unparseable`, `not_applicable`, `unevaluable` |

Any check can also report `schema_not_run`, which means the structural check it
builds on could not run at all.

`calculation_reproducibility` is self-referential and nothing more. It resolves
its three inputs from the model — the field carrying the emission-measure role, a
`… Factor Value` (never a `… Factor ID`, which is a reference to a factor rather
than a number to multiply by) and a `… CO2e …` result — or, for a record in the
vocabulary the guardrails read, from `quantity_value` / `emission_factor_value` /
`co2e_kg`. A slot the record fills more than once is `ambiguous_calculation_inputs`
and the check declines rather than picks. The 1% tolerance is the one
`CalculationAccuracy` in the verification engine and `calc_tolerance` in the
ISO 14064 quantification policy use, so the identity has one tolerance on every
surface. What it catches is a record whose own numbers do not multiply out. It
does **not** say the factor is the right one for this source and period
(`factor_provenance`, still not run) or that the quantity came from the meter it
claims (the data plane, still not run) — a reproduced calculation is a
self-consistent record, not a verified one.

- `schemaValid` is the structural verdict (`null` if the structural check could not run).
- `assuranceLevel` names the depth earned: `schema-only`, `schema-and-value`,
  `schema-value-and-policy` or `full`. Each level needs every check beneath it,
  so guardrails without a value check is still `schema-only`. The ladder is gated
  on `value_range` — a bound the model itself declares — so a record that
  reproduced its own arithmetic without one still reads `schema-only`;
  `checked` is where that shows up, and it is the more precise of the two.
- Every violation and warning carries `severity` (`error` | `warning`). Rule ids
  are stable: `required_field_missing`, `pattern_mismatch`, `format_mismatch`,
  `type_mismatch`, `primary_key_missing`, `enum_violation`, `value_range` (with
  `bound`: `minimum` | `maximum`), `temporal_consistency`, `enum_membership`,
  `calculation_reproducibility` (with `expected`, `received`, `tolerance` and the
  three `inputs` it read), …
  `unknown_field` is a per-field warning and stays one.
- `unknown_field` warnings carry `didYouMean`: up to three canonical candidates,
  each with a confidence and the O-DEF code that field carries. The list is empty
  when nothing in the model is a plausible match — a key that belongs to another
  system stays a key that belongs to another system. `schema.normalisations`
  separately discloses keys that resolved through case and separator folding.
- `advisories` carries findings that are true of the payload but do not bear on
  its validity. Today that is `deprecated_field`: a field the model has retired,
  severity `warning`, carrying `modelDescription` verbatim and a `successor` when
  the model names one. Where the model names no replacement the key is absent
  rather than guessed. An advisory never changes `schemaValid`, and the same
  entries also appear in `schema.warnings`.
- `valid` is **deprecated** (see `deprecations` in the response). It keeps its
  0.5.0 meaning through the 0.5.x line and is removed no earlier than 0.6.0.
  Read `schemaValid` instead.

## Billing behavior (for agents)

Metered calls return an **HTTP 402** when the account is out of credits. The
server surfaces this as a tool error that tells the agent to ask its **human
operator** to purchase credits or upgrade — agents should relay that message and
stop, not retry.

## Retrying metered calls safely

Metered tools advertise `_meta.idempotency` (`{ header, parameter,
maxLength }`). Send the key either as the `Idempotency-Key` header on the `/mcp`
request or as `params._meta.idempotencyKey` on the `tools/call` itself. The
hosted endpoint forwards it to the metered REST call: the same key is charged
once and replays the original result, and a different body under the same key is
refused. If both the header and the parameter are present they must match; a
mismatch is refused with a JSON-RPC `-32602` error before the tool runs. Tools
without `_meta.idempotency` are not metered, so a key has no effect on them.

## Test

`npm test` (no network; the HTTP layer is stubbed).

## Where it is listed

- [Smithery](https://smithery.ai/servers/ajvdvoort/dveracity) — one-click add for Smithery toolbox users
- [Official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=dveracity) — `com.dveracity/semantic-mcp`
- [npm](https://www.npmjs.com/package/@dveracity/semantic-mcp) — `@dveracity/semantic-mcp`

TDQS

A4.1/5.0

Scored across 16 tools

Disambiguation4/5

Most tools are clearly separated by resource and action: list/get pairs (ofp_sectors/ofp_sector), search/fetch pairs (ofp_search_entities/ofp_entity), and query/template pairs (semantic_query/semantic_templates). The main confusion risk is validate_data vs ofp_validate, which both validate payloads for credits but target different rule sets, though the descriptions do clarify that boundary.

Naming Consistency3/5

The ofp_ prefix on ten tools creates a strong family signal, and plural/singular pairs (ofp_sectors/ofp_sector, ofp_semantics/ofp_semantic_code) encode list-vs-get nicely. However, the convention isn't uniform: the entity list is ofp_search_entities rather than ofp_entities, and non-OFP tools mix verb_noun (validate_data, list_standards), adjective_noun (semantic_query), and noun_noun (credits_balance) patterns. Overall readable but stylistically mixed.

Tool Count4/5

At 16 tools the server sits just above the ideal range, but the count is justified by the breadth of the Open Footprint domain it exposes: model exploration, sector/policy lookups, semantic codes, validation, and account utilities. The OFP family alone needs list/get pairs for entities, sectors, and codes, plus validation and provenance tools. No tool feels redundant, though a few are niche.

Completeness4/5

The surface covers the read-and-validate lifecycle for the Open Footprint domain: discover the model, find entities, list sectors and policies, validate payloads, and query the knowledge graph, with free list_standards and semantic_templates acting as pre-paid discovery steps. The main gaps are that entities can only be found via search (no full enumeration of the 239) and there is no validation-history or standard-detail tool. These are workaround-able rather than dead ends.

Maintenance

ActivityActive
ResponsivenessMixed handling