Manage products, EU Digital Product Passports, operator parties, and GS1 EPCIS supply-chain events.
eu.tracepass/tracepass MCP Server
The eu.tracepass/tracepass MCP server is a Model Context Protocol (MCP) server for TracePass, the EU Digital Product Passport platform. It supports managing products, EU Digital Product Passports, operator parties, and GS1 EPCIS supply-chain events, enabling AI assistants and IDE agents to work with those records.
Assist AI agents in EU Digital Product Passport workflows
Support supply-chain data use via GS1 EPCIS events
Coordinate information about operator parties and associated products
β‘ Developer Benefits
Built as an MCP server for model context integration
Aligns with topics including digital-product-passport (DPP) and EU regulation
Uses TypeScript per repository topic tags
β οΈ Limitations
Server capabilities described here are limited to the management areas listed in its description (products, passports, operator parties, and GS1 EPCIS events).
Manage the TracePass product catalogue. A product is the catalogue layer β one product can have many passports (one per serialised unit). Products are not billable on their own.
Actions (pass via `action`, with `args`):
- list β args: { page?, limit? (β€100), category?, status?, search? }. Read-only.
- get β args: { id }. Read-only.
- create β args: { name, model, category, description? }. `category` is one of: battery, textile, electronics, construction, steel, detergents, paints-coatings, packaging, furniture, tyres, jewelry, toys, fmcg.
- update β args: { id, name?, model?, description? }; pass at least one field to change.
- create_batch β args: { products: [ { name, model, category, description? }, β¦ ] }, up to 100. Partial-success: the response carries a per-item status, so some items can be created while others error. The whole batch consumes N writes upfront; if that would exceed the daily cap NOTHING is created (429).
- archive β args: { id }. Soft-archive a product. Blocked with 409 while any non-archived passport still references it β archive those passports first. This is reversible and is NOT deletion.
Parameters2
action
string
required
Which product operation to run: list | get | create | create_batch | update | archive.
args
object
optional
Arguments for the chosen action; required fields depend on `action` (see each action above).
Raw schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"list",
"get",
"create",
"create_batch",
"update",
"archive"
],
"description": "Which product operation to run: list | get | create | create_batch | update | archive."
},
"args": {
"description": "Arguments for the chosen action; required fields depend on `action` (see each action above).",
"type": "object",
"properties": {
"id": {
"description": "Product id. Required for get and update.",
"type": "string"
},
"name": {
"description": "Product name. Required for create; optional on update.",
"type": "string"
},
"model": {
"description": "Manufacturer model / SKU. Required for create; optional on update.",
"type": "string"
},
"category": {
"description": "DPP category for create: battery | textile | electronics | construction | steel | detergents | paints-coatings | packaging | furniture | tyres | jewelry | toys | fmcg.",
"type": "string"
},
"description": {
"description": "Free-text product description (create/update).",
"type": "string"
},
"page": {
"description": "Page number for list (1-based).",
"type": "number"
},
"limit": {
"description": "Page size for list, max 100.",
"type": "number"
},
"status": {
"description": "Filter list by product status.",
"type": "string"
},
"search": {
"description": "Filter list by a search term.",
"type": "string"
},
"products": {
"description": "Products to create for create_batch: [{ name, model, category, description? }], max 100.",
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
}
}
}
},
"required": [
"action"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
tracepass_passports
Manage Digital Product Passports β create, read, and run lifecycle actions.
IMPORTANT: `create` consumes DPP slots and IS BILLABLE. Over-quota creation incurs a per-passport charge; the tool surfaces a 402-style message β only re-run with args.confirmOverage=true after the user explicitly agrees. `archive` is IRREVERSIBLE (the public QR permanently 404s); prefer `suspend` when a change might be undone.
IDENTIFIER SCHEMES (EN 18219): passports are identified by one of five schemes. Battery passports (Battery Regulation Art. 77(3)) accept ONLY gs1 and iso15459.
β’ gs1 β { scheme:"gs1", gtin, serialNumber } β GS1 GTIN + serial; gtin is 8/12/13/14 digits, stored as GTIN-14.
β’ iso15459 β { scheme:"iso15459", issuingAgencyCode, primaryId, serial? } β ISO/IEC 15459; the server derives raw (IAC + primaryId + serial).
β’ iec61406 β { scheme:"iec61406", uri } β IEC 61406 Identification Link (https URI). Not valid for batteries.
β’ did β { scheme:"did", did, method } β W3C DID Core. Not valid for batteries.
β’ doi β { scheme:"doi", doi, granularity:"model"|"batch"|"item" } β ISO 26324 DOI, stored as bare 10.<registrant>/<suffix> (any https://doi.org/ or doi: prefix stripped on input; resolves as https://doi.org/<doi>); granularity REQUIRED per EN 18219 Β§5.6.2(b). Not valid for batteries.
The legacy top-level gtin + serialNumber pair is still accepted as a deprecated alias for scheme:"gs1".
Actions (pass via `action`, with `args`):
- list β args: { page?, limit? (β€100), productId?, status?, search? }. status β draft|in_review|approved|published|suspended|expired|archived. Read-only.
- get β args: { id, format? (summary|full), lang? }. Read-only. Response includes `identifier`, `identifierKey`, and (for GS1 passports) `gs1`.
- get_by_serial β args: { serial, format?, lang?, gtin? }. Read-only. Addresses the passport by your own serial. A serial is unique only WITHIN a GTIN β if the same serial exists under two GTINs in your account the call returns 409 ambiguous_serial; pass `gtin` (or use the by-id action) to resolve exactly.
- compliance β args: { id }. Read-only. Returns a three-tier compliance verdict (compliant | compliant_with_warnings | incomplete) with regulation-cited findings β use to gap-check a passport against the rules for its category, fix the cited fields/parties, then re-check. Also returns byRegulation[]: the same findings grouped per regulation, worst first, so you can tell WHICH regime is failing instead of reading one `incomplete` as everything being wrong. A regulation absent from that array raised no finding β that is not the same as it having passed.
- registry_readiness β args: { id }. Read-only. Returns { ready, findings[] } β whether the passport would pass the EU DPP Registry's FORMAL submission gate (mandatory fields present, correct formatting, a resolvable public link, item-level granularity via a serial number, and a well-formed commodity code where the category carries one). This is the registry's mechanical pre-submission check, NOT the substantive compliance verdict; a passport can be registry-ready yet not substantively compliant. Battery passports only.
- create β args: { productId, identifier?, gtin?, serialNumber?, confirmOverage?, lineage? }. BILLABLE. Provide identifier (preferred) or legacy gtin + serialNumber. Battery passports accept only gs1 and iso15459 schemes β other schemes return 400. A duplicate identifier returns 409.
lineage (battery only) β a repurposed, remanufactured or reused battery needs a NEW passport linked to the original(s) (Battery Regulation Art. 77(7)): { predecessors: [ { internalPassportId? | identifier?, trigger: preparation_for_reuse|preparation_for_repurposing|repurposing|remanufacturing } ] (β€10), noPredecessorReason? (only with an empty list, e.g. placed on the market before 18 Feb 2027) }. The server derives batteryStatus from the triggers and links your own predecessor passports back. Immutable after create. Rule violations return 422 with the rule code (duplicate_predecessor, predecessor_not_found, status_trigger_mismatch, β¦).
- suspend β args: { i
Parameters2
action
string
required
Which passport operation to run. Reads: list | get | get_by_serial | compliance | registry_readiness | get_condition_flags | get_condition_flags_by_serial | get_qr | get_qr_by_serial | list_snapshots | get_snapshot | list_measurements(_by_serial) | latest_measurements(_by_serial). Writes: set_condition_flags | set_condition_flags_by_serial | capture_measurements(_by_serial) | create (BILLABLE). Lifecycle: suspend (reversible) | archive (IRREVERSIBLE), each with a _by_serial variant.
args
object
optional
Arguments for the chosen action; required fields depend on `action` (see each action above).
Raw schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"list",
"get",
"get_by_serial",
"compliance",
"registry_readiness",
"get_condition_flags",
"get_condition_flags_by_serial",
"set_condition_flags",
"set_condition_flags_by_serial",
"capture_measurements",
"capture_measurements_by_serial",
"list_measurements",
"list_measurements_by_serial",
"latest_measurements",
"latest_measurements_by_serial",
"create",
"suspend",
"suspend_by_serial",
"archive",
"archive_by_serial",
"get_qr",
"get_qr_by_serial",
"list_snapshots",
"get_snapshot"
],
"description": "Which passport operation to run. Reads: list | get | get_by_serial | compliance | registry_readiness | get_condition_flags | get_condition_flags_by_serial | get_qr | get_qr_by_serial | list_snapshots | get_snapshot | list_measurements(_by_serial) | latest_measurements(_by_serial). Writes: set_condition_flags | set_condition_flags_by_serial | capture_measurements(_by_serial) | create (BILLABLE). Lifecycle: suspend (reversible) | archive (IRREVERSIBLE), each with a _by_serial variant."
},
"args": {
"description": "Arguments for the chosen action; required fields depend on `action` (see each action above).",
"type": "object",
"properties": {
"id": {
"description": "Passport id. Required for get/compliance/suspend/archive/get_qr (the by-id actions).",
"type": "string"
},
"serial": {
"description": "Your own serial number. Required for the *_by_serial actions.",
"type": "string"
},
"gtin": {
"description": "For create (legacy): GS1 GTIN. Also used as a disambiguator for *_by_serial actions when a serial isn't unique (else 409 ambiguous_serial).",
"type": "string"
},
"productId": {
"description": "Parent product id. Required for create.",
"type": "string"
},
"identifier": {
"description": "EN 18219 scheme-tagged identifier for create. Must have `scheme` plus scheme-specific fields. Schemes: gs1 {gtin, serialNumber} | iso15459 {issuingAgencyCode, primaryId, serial?} | iec61406 {uri} | did {did, method} | doi {doi, granularity} (granularity: \"model\"|\"batch\"|\"item\" REQUIRED per EN 18219 Β§5.6.2(b)). Battery passports: gs1 and iso15459 only.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"serialNumber": {
"description": "Serial for the new passport (create, legacy gs1 path).",
"type": "string"
},
"confirmOverage": {
"description": "Set true to accept per-passport overage charges when over the plan quota (402). Applies to create.",
"type": "boolean"
},
"lineage": {
"description": "create, battery only: link a second-life battery's new passport to the original passport(s) (Art. 77(7)). See the create action.",
"type": "object",
"properties": {
"predecessors": {
"maxItems": 10,
"type": "array",
"items": {
"type": "object",
"properties": {
"identifier": {
"type": "string",
"maxLength": 2000
},
"internalPassportId": {
"type": "string"
},
"trigger": {
"type": "string",
"enum": [
"preparation_for_reuse",
"preparation_for_repurposing",
"repurposing",
"remanufacturing"
]
}
},
"required": [
"trigger"
]
}
},
"noPredecessorReason": {
"type": "string",
"maxLength": 500
}
},
"required": [
"predecessors"
]
},
"format": {
"description": "get/get_by_serial: summary|full. get_qr/get_qr_by_serial: svg|png.",
"type": "string"
},
"symbology": {
"description": "get_qr/get_qr_by_serial: qr (default) | datamatrix.",
"type": "string"
},
"at": {
"description": "list_snapshots: ISO 8601 instant β return the snapshot valid then instead of the list.",
"type": "string"
},
"lang": {
"description": "Resolve field values to one of the 24 EU locales server-side (get/get_by_serial).",
"type": "string"
},
"page": {
"description": "Page number for list (1-based).",
"type": "number"
},
"limit": {
"description": "Page size for list, max 100.",
"type": "number"
},
"status": {
"description": "Filter list by status: draft|in_review|approved|published|suspended|expired|archived.",
"type": "string"
},
"search": {
"description": "Filter list by a search term.",
"type": "string"
},
"snapshotId": {
"description": "Snapshot id. Required for get_snapshot.",
"type": "string"
},
"measurements": {
"description": "capture_measurements(_by_serial): [{ fieldKey, value, measuredAt, externalId?, unit? }], max 500.",
"type": "array",
"items": {
"type": "object",
"properties": {
"fieldKey": {
"type": "string",
"minLength": 1
},
"value": {},
"measuredAt": {
"type": "string",
"minLength": 1
},
"externalId": {
"type": "string"
},
"unit": {
"type": "string"
}
},
"required": [
"fieldKey",
"value",
"measuredAt"
]
}
},
"fieldKey": {
"description": "list_measurements(_by_serial): only this field key.",
"type": "string"
},
"from": {
"description": "list_measurements(_by_serial): measuredAt from (ISO 8601).",
"type": "string"
},
"to": {
"description": "list_measurements(_by_serial): measuredAt to (ISO 8601).",
"type": "string"
},
"cursor": {
"description": "list_measurements(_by_serial): nextCursor from the previous page.",
"type": "string"
},
"flags": {
"description": "For set_condition_flags / set_condition_flags_by_serial: Record<flagKey, boolean|null>. null clears the flag.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
]
}
}
}
}
},
"required": [
"action"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
tracepass_passport_fields
Update field values on a Digital Product Passport. Every change is recorded in the passport's audit trail, tagged as an API-key update.
Actions (pass via `action`, with `args`):
- update β args: { id, fieldKey, value }. `value` type matches the field's dataType (string, number, boolean, array, object).
- update_by_serial β args: { serial, fieldKey, value, gtin? }. Same as update, addressed by your own serial. A serial is unique only WITHIN a GTIN β if it isn't unique in your account the call returns 409 ambiguous_serial; pass `gtin` (or use update by id) to resolve exactly.
Parameters2
action
string
required
Update one passport field, addressed by passport id (update) or by your serial (update_by_serial).
args
object
optional
Arguments for the chosen action; required fields depend on `action`.
Raw schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"update",
"update_by_serial"
],
"description": "Update one passport field, addressed by passport id (update) or by your serial (update_by_serial)."
},
"args": {
"description": "Arguments for the chosen action; required fields depend on `action`.",
"type": "object",
"properties": {
"id": {
"description": "Passport id. Required for update.",
"type": "string"
},
"serial": {
"description": "Your serial. Required for update_by_serial.",
"type": "string"
},
"gtin": {
"description": "GTIN disambiguator for update_by_serial when the serial isn't unique (else 409).",
"type": "string"
},
"fieldKey": {
"description": "The field key to set (required).",
"type": "string"
},
"value": {
"description": "The new value for the field (required). Type depends on the field's dataType."
}
}
}
},
"required": [
"action"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
tracepass_passport_parties
Manage the economic-operator parties on a passport β manufacturer, importer, authorisedRepresentative, distributor, recycler, producerResponsibilityOrg. Each party carries a legal name and at least one identifier.
Identifier rules (EN 18219 Β§6.2β6.5): a party needs at least one of `gln`, `legacyOperatorId`, or `operatorIdentifier`. If both `gln` and `operatorIdentifier` (scheme gln) are provided they must agree β a mismatch returns 400. An operatorIdentifier of scheme gln also fills the top-level `gln` field; facilityIdentifier never does. Typed identifiers (operatorIdentifier / facilityIdentifier) are not set by AI extraction or CSV import.
operatorIdentifier schemes:
β’ iso6523 β { scheme:"iso6523", icd:"<4 digits>", value } β ICD 0199 = LEI (ISO 17442), 0088 = GLN, 0060 = DUNS.
β’ gln β { scheme:"gln", gln:"<13 digits>" } β also fills top-level `gln`.
β’ did β { scheme:"did", did:"did:<method>:<id>" } β syntax-checked only (EN 18219 Β§6.4.2(b)).
β’ doi β { scheme:"doi", doi:"10.<registrant>/<suffix>" } β doi:/https://doi.org/ prefix accepted and stripped.
facilityIdentifier schemes: same four; the gln variant also accepts optional `extension` (GS1 SGLN sub-location).
Actions (pass via `action`, with `args`):
- set β args: { id, role, legalName, gln?, country?, legacyOperatorId?, operatorIdentifier?, facilityIdentifier? }. Sets or updates one role.
- remove β args: { id, role }. Clears one role.
Parameters2
action
string
required
Set (add/replace) or remove an economic-operator party on a passport by its role.
args
object
optional
Arguments for the chosen action; required fields depend on `action`.
Raw schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"set",
"remove"
],
"description": "Set (add/replace) or remove an economic-operator party on a passport by its role."
},
"args": {
"description": "Arguments for the chosen action; required fields depend on `action`.",
"type": "object",
"properties": {
"id": {
"description": "Passport id (required).",
"type": "string"
},
"role": {
"description": "Economic-operator role: manufacturer | importer | authorisedRepresentative | distributor | recycler | producerResponsibilityOrg (required).",
"type": "string"
},
"legalName": {
"description": "Party legal name. Required for set.",
"type": "string"
},
"gln": {
"description": "GS1 Global Location Number (13 digits). Strongly recommended for multi-role disambiguation.",
"type": "string"
},
"country": {
"description": "ISO 3166-1 alpha-2 country code (set, optional).",
"type": "string"
},
"legacyOperatorId": {
"description": "Free-text fallback identifier (VAT, EORI, supplier code). Required when gln and operatorIdentifier are both absent.",
"type": "string"
},
"operatorIdentifier": {
"description": "Structured operator identifier per EN 18219 Β§6.2β6.5. Must have `scheme` plus scheme fields. Schemes: iso6523 {icd:\"<4 digits>\", value} | gln {gln:\"<13 digits>\"} | did {did:\"did:<method>:<id>\"} | doi {doi:\"10.<registrant>/<suffix>\"}. scheme gln also fills the top-level gln field; they must match if both are set (400 otherwise). Not set by AI extraction.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"facilityIdentifier": {
"description": "Structured facility identifier per EN 18219 Β§6.2β6.5. Same four schemes as operatorIdentifier; the gln variant also accepts optional `extension` (GS1 SGLN sub-location). Never fills the top-level gln field. Not set by AI extraction.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
}
}
},
"required": [
"action"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
tracepass_epcis
GS1 EPCIS 2.0 supply-chain events. `export` is included on Starter plans and up; `capture`, `capture_job`, and `query` require the paid EPCIS add-on (those actions return a 403-style message without it).
Actions (pass via `action`, with `args`):
- export β args: { id }. Export a passport's events as an EPCIS 2.0 JSON-LD document. Read-only.
- export_by_serial β args: { serial, gtin? }. Same as export, addressed by your own serial. A serial is unique only WITHIN a GTIN β if it isn't unique in your account the call returns 409 ambiguous_serial; pass `gtin` (or use export by id). Read-only.
- capture β args: { events }. `events` is an EPCISDocument, a single event, or an array of events (JSON-LD). Returns a 202 with a captureJobId.
- capture_job β args: { jobId }. Poll an async capture job. Read-only.
- query β args: { params? }. `params` is a key/value map of standard EPCIS query parameters (EQ_bizStep, GE_eventTime, MATCH_epc, β¦). Read-only.
Parameters2
action
string
required
EPCIS 2.0: export a passport's events (export | export_by_serial), capture new events, poll a capture job, or query events.
args
object
optional
Arguments for the chosen action; required fields depend on `action`.
Raw schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"export",
"export_by_serial",
"capture",
"capture_job",
"query"
],
"description": "EPCIS 2.0: export a passport's events (export | export_by_serial), capture new events, poll a capture job, or query events."
},
"args": {
"description": "Arguments for the chosen action; required fields depend on `action`.",
"type": "object",
"properties": {
"id": {
"description": "Passport id. Required for export.",
"type": "string"
},
"serial": {
"description": "Your serial. Required for export_by_serial.",
"type": "string"
},
"gtin": {
"description": "GTIN disambiguator for export_by_serial when the serial isn't unique (else 409).",
"type": "string"
},
"events": {
"description": "EPCIS 2.0 event payload (an EPCISDocument or event list). Required for capture."
},
"jobId": {
"description": "Capture job id to poll. Required for capture_job.",
"type": "string"
},
"params": {
"description": "EPCIS query parameters as keyβvalue strings (query, optional).",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
}
}
}
},
"required": [
"action"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
tracepass_templates
Discover the regulatory field schema for each DPP category β what a COMPLIANT passport must contain, per the governing EU regulation. Read-only reference data. Use this to advise on requirements before creating products/passports, and to gap-check a draft against the rules.
Actions (pass via `action`, with `args`):
- list β args: {}. Lists all 13 categories with their field count, required-field count, and governing regulation (name + number + effective/mandatory dates).
- get β args: { category }. Full field schema for one category: every field's key, label, dataType, whether it is REQUIRED, its access level (public/restricted/authority), enum options, validation bounds, and β where known β the regulation article/annex that mandates it. `category` is one of: battery, textile, electronics, construction, steel, detergents, paints-coatings, packaging, furniture, tyres, jewelry, toys, fmcg.
BATTERY β required-ness is per-category, so `required` alone is the wrong answer. Resolve it in this order:
1. SCOPE FIRST. Only EV, LMT and industrial_gt_2kwh batteries owe a passport at all (Art. 77(1), Reg (EU) 2023/1542). For portable, SLI or industrial_lte_2kwh, NO field is required β do not list mandatory fields for them; say the battery is out of scope.
2. Then `requiredBy[batteryCategory]` where the field carries that map (required | conditional | notApplicable).
3. Then fall back to `required`.
The map is keyed ONLY by the three in-scope categories, so skipping step 1 falls through to `required` and invents an obligation the Regulation does not impose. Note also that EV and LMT report state-of-health through MUTUALLY EXCLUSIVE field sets β an EV battery must leave the remaining-capacity cluster empty and an LMT battery must leave stateOfCertifiedEnergy empty, so no single battery ever fills every field.
Parameters2
action
string
required
List all DPP category templates, or get one template by category.
args
object
optional
Arguments for the chosen action; `category` is required for get, ignored for list.
Raw schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"list",
"get"
],
"description": "List all DPP category templates, or get one template by category."
},
"args": {
"description": "Arguments for the chosen action; `category` is required for get, ignored for list.",
"type": "object",
"properties": {
"category": {
"description": "DPP category to fetch (required for get): battery | textile | electronics | construction | steel | detergents | paints-coatings | packaging | furniture | tyres | jewelry | toys | fmcg.",
"type": "string"
}
}
}
},
"required": [
"action"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
A Model Context Protocol server for
TracePass β the EU Digital Product
Passport platform. It lets AI assistants (Claude, Cursor, IDE agents)
manage products, Digital Product Passports, economic-operator parties,
and GS1 EPCIS 2.0 supply-chain events.
It speaks the full MCP protocol β tools, resources, resource
templates, and prompts.
Two ways to use it
The same server core ships two ways:
Hosted β point your MCP client at https://ai.tracepass.eu/mcp.
Nothing to install; always current.
Local (npm) β run tracepass-mcp-server via npx. The MCP
client launches it as a subprocess and speaks MCP over stdio.
Authentication
The server accepts either of TracePass's two v1 auth methods on the
same Authorization: Bearer β¦ header β it forwards whatever you send to
the API, which decides. Pick the one that fits how you're connecting:
API key
OAuth 2.0
Best for
A single user, scripts, server-to-server
AI assistants / apps acting on a user's behalf
What you send
A static tp_β¦ key as a Bearer token
A scoped access token obtained via the OAuth flow
Setup
Mint at Developer β API Keys
The user clicks Connect and approves scopes
Scope
All-or-nothing (the whole workspace)
Exactly the scopes the user granted; revocable
Works with
Hosted and local (npx)
Hosted endpoint only (needs a browser consent step)
Which should an AI assistant use? If your MCP client supports OAuth
(Claude.ai, ChatGPT, and others), prefer OAuth β the user authorizes
the connection once on a TracePass consent screen, you never handle a
secret, and access is least-privilege and revocable. If your client only
takes a header/token, use an API key.
OAuth 2.0 (recommended for hosted clients)
No config beyond pointing your client at the hosted endpoint β discovery
is automatic. On the first unauthenticated request the server returns a
401 whose WWW-Authenticate header carries a resource_metadata URL
(RFC 9728) pointing at /.well-known/oauth-protected-resource, which
names the TracePass authorization server. The client runs the standard
authorization-code flow with PKCE (/api/oauth/authorize β
/api/oauth/token), the user approves scopes, and the client stores +
refreshes the token. If you distribute your own client, register an app
under Developer β OAuth Apps to get a client_id; many hosted
clients self-register via Dynamic Client Registration automatically.
Request only the scopes you need, e.g. passports:read passports:write offline_access. Users manage connected apps (and revoke) under
Developer β OAuth Apps β Connected Apps.
API key
Mint a tp_β¦ key under Developer β API Keys and send it as a Bearer
token.
tracepass_passports - manage Digital Product Passports (list, get, compliance check, registry-readiness check, get/set condition flags, create, suspend, archive, get QR, list snapshots, get snapshot), by id or by serial. Passports are identified via GS1 or four additional EN 18219 schemes (iso15459, iec61406, did, doi); battery passports accept only gs1 and iso15459 (Art. 77(3)). Condition flags are approved yes/no facts (e.g. battery: hasBMS, rechargeable, externalStorageOnly, isStationaryBess) that gate conditional legal duties β setting an approved flag may make additional fields required and block publishing if those fields are empty.
tracepass_passport_fields - update a passport's category-specific data fields, by id or by serial.
tracepass_passport_parties - set or remove a passport's economic-operator parties (manufacturer, importer, etc.).
tracepass_epcis - export, capture, and query a passport's GS1 EPCIS 2.0 supply-chain events.
tracepass_templates - list and get the DPP category field schemas, each field traced to the EU instrument that mandates it.
The *_by_serial actions address a passport by the customer's own serial
number instead of its TracePass id. A serial is unique only within a GTIN, so
if the same serial exists under two GTINs in your account a serial-only call
returns 409 ambiguous_serial β pass the optional gtin arg to disambiguate
(or use the by-id action). The same gtin disambiguator applies to every
*_by_serial action.
The tracepass_passportscompliance action returns a three-tier
compliance verdict (compliant / compliant_with_warnings /
incomplete) with regulation-cited findings β missing required fields,
missing economic-operator parties, format issues, and per-category
conditional rules. Read-only; use it to gap-check a passport, fix the
cited gaps, then re-check.
A compliant verdict means this passport satisfies the rules encoded here,
not this product may be placed on the market. The field specifications are
hand-authored from the regulations, not an official EU artefact, and delegated
acts are still landing. It is not legal advice.
Second-life batteries
A repurposed, remanufactured or reused battery needs a new passport
linked to the original one(s) (Battery Regulation Art. 77(7)). Pass a
lineage block to tracepass_passportscreate:
json
{"predecessors":[{"internalPassportId":"<your original passport id>","trigger":"repurposing"}]}
A predecessor is named by internalPassportId (one of your own passports)
or by its resolvable identifier. trigger is one of
preparation_for_reuse, preparation_for_repurposing, repurposing or
remanufacturing. The platform derives batteryStatus from the
triggers, stores the block immutably, and links your own originals back
to the new passport (successors). A battery placed on the market before
18 Feb 2027 has no original passport: send an empty list with
noPredecessorReason. Rule violations return 422 with the rule code.
Battery measurements (living record)
capture_measurements pushes over-life data from your own equipment into a
published battery passport: state of health, fades, cycle counts, dynamic
values, state of charge, negative events, temperature history (Battery
Regulation Annex XIII point 4). Every measurement is kept; the newest per
field becomes the passport's current value and sets dynamicDataAsOf.
list_measurements and latest_measurements read them back.
They are metered against the plan's monthly measurement allowance, not the
daily write budget. Paid plans keep counting past the allowance at no
charge; the Free plan stops at its allowance. Reading a passport is never
metered.
A note on writes
Some actions cost money or are irreversible β the server's tool
descriptions tell the model so:
tracepass_passportscreate consumes billable DPP slots.
Over-quota creation incurs a per-passport overage charge; the tool
surfaces a 402-style message and only proceeds with
args.confirmOverage: true after the user agrees.
tracepass_passportsarchive is irreversible β the public QR
permanently 404s. Use suspend (reversible) when a change might be
undone.
tracepass_epciscapture / query require the paid EPCIS
add-on; export is included on Starter plans and up.
Resources
Read-only entity data you can attach as conversation context:
tracepass://products β the product catalogue
tracepass://product/{id} β one product
tracepass://passport/{id} β one passport, full field detail
tracepass://passport/{id}/epcis β a passport's EPCIS 2.0 events
tracepass://passport/{id}/compliance β a passport's compliance verdict
tracepass://passport/{id}/registry-readiness β a mechanical pre-submission check modelled on the EU DPP Registry's formal gate: mandatory-field presence, formatting, a resolvable public link, item-level granularity (no commodity-code check: a battery passport's registration identifier is not entered in the customs declaration). Not the substantive compliance verdict, and not a prediction of the real registry's response β its registration API has no published spec. Battery only.
tracepass://passport/{id}/snapshots β the snapshot history of a passport (newest first): a snapshot on publish and after every change to a non-draft passport; each entry carries version, reason (e.g. published, field_edit, status_change, baseline), actor, snapshotAt, contentHash, hashValid (re-verified on read), restorable flag, and field count.
tracepass://templates β all 13 DPP category field schemas
tracepass://template/{category} β one category's full field schema
Prompts
Reusable DPP workflows the client surfaces as slash-commands:
audit_passport β review a passport for completeness and
compliance readiness
onboard_product β create a product and its first passport
explain_dpp_requirements β explain what a category's compliant DPP
must contain, and the regulation behind each field
compliance_gap_check β produce a prioritised, regulation-cited list
of what's blocking a passport's compliant publication
review_epcis_events β summarise a passport's supply-chain trail
For suppliers: answering a data request
A second, separate endpoint serves suppliers who receive a TracePass data
request. It is not part of the tools above. The request email carries a
personal address:
code
https://ai.tracepass.eu/supplier/mcp/<token>
Add it to an AI assistant as a custom connector. For clients that can set
headers, https://ai.tracepass.eu/supplier/mcp with
Authorization: Bearer <token> works too. The token authorises that one
request only; there is no account and no OAuth. The assistant can then:
Tool
What it does
get_request
Start here: who is asking, for which product, and every field asked for, each with its meaning, unit, format and legal source; plus answers already sent and the review outcome
validate_answers
A dry run: what would be stored, which keys were not requested, and what does not fit. Writes nothing
get_upload_command
The best way to attach a datasheet or certificate when the assistant can run shell commands: returns a curl command that uploads the file from disk and prints a documentId to cite (PDF, Office, CSV, PNG/JPEG/WebP)
upload_evidence
Attach a very small file (a few kilobytes) inline as base64; prefer get_upload_command, or cite a URL or note
submit_answers
Send answers with evidence per value to the requester's human review; can be repeated (answers merge) until reviewed
get_review_status
Whether the requester has reviewed them, the outcome, accepted fields, and when the link expires
Answers go to the requester's human review; they never publish anything on
their own.
Development
bash
npm install
npm run build # tsc -> dist/
npm run typecheck
npm test# vitest
npm run lint
npm start # run the hosted HTTP service locally (:8080)
npm run start:stdio # run the stdio server locally
The hosted service is a plain Node HTTP server (dist/http.js),
stateless β each request carries its own API key and builds a fresh
MCP session. It is containerised via the Dockerfile and deployed to
Hetzner; see tracepass-environment/docker-mcp.yml.