# Connect the MCP server

By the end of this page, you will know what the SealTrust MCP server allows an assistant to do, how to connect it to your client, which identifier to pass it, and what each of its seven tools returns.

Source: https://docs.sealtrust.io/en/mcp/

---

The MCP server is a small program that you run on your own machine. It gives a
conversational assistant the right to query the SealTrust public API, read only.
MCP, for Model Context Protocol, is the standardized protocol through which an
assistant calls external tools. By the end of this page, you will know how to
install the server, declare it in your client, choose the identifier to pass it
and call its seven tools.

The path has four steps: check that the scope matches your need, connect the
server to your client, learn which identifier you have at hand, call the tools.

## 1. The scope, before anything else

The server issues only `GET` requests, to public endpoints of
`https://api.sealtrust.io`. It sends no API key and accepts none. None of its
tools creates, modifies or deletes anything.

Direct consequence: the server sees exactly what an anonymous visitor sees when
opening a passport in their browser. The ESPR regulation distinguishes several
audiences: the public, consumers, value chain professionals and market
surveillance authorities. SealTrust applies that distinction with six access
levels: `public`, `end_user`, `repairer`, `recycler`, `upstream` and
`authority`. These values are SealTrust's own. The regulation, for its part,
names audiences. The MCP server reads the **public** level and that level only.
Fields reserved for professionals or authorities cannot be reached through this
path.

> [!INFO] This server has nothing to do with your partner API key
> Your brand's API key is used to create products, declare sales and manage your
> notification subscriptions. The MCP server does not touch it. You can connect
> it without a SealTrust account.

What the server announces to your client when the connection opens: its name,
`sealtrust`, its version, and a sentence of instructions reminding the assistant
that all data is public and that no tool modifies anything.

## 2. Connect the server

You need Node.js version 18 or later. The package is called
`@sealtrust-io/mcp-server` and the latest published version is `0.4.0`.

You can launch it directly, which serves to check that it starts.

```bash
npx @sealtrust-io/mcp-server
```

The server communicates through its standard input and output. A startup line
goes to the error output and indicates the version as well as the API address
in use. Standard output is reserved for the dialogue with the client, so nothing
else is written there.

### Declare the server in a client

An MCP client is configured with a command and its arguments. The block below is
the form expected by desktop clients, to be placed in their configuration file.

```json
{
  "mcpServers": {
    "sealtrust": {
      "command": "npx",
      "args": ["-y", "@sealtrust-io/mcp-server"]
    }
  }
}
```

On the command line with Claude Code, the same declaration is written as
follows.

```bash
claude mcp add sealtrust -- npx -y @sealtrust-io/mcp-server
```

Restart the client after modifying its configuration. The seven tools then
appear in its list of available tools.

### The two available settings

Two environment variables, and only two, change the behavior of the server.

| Variable | Default value | Effect |
| --- | --- | --- |
| `SEALTRUST_API_URL` | `https://api.sealtrust.io` | The address of the API being queried. Trailing slashes are stripped. |
| `SEALTRUST_TIMEOUT_MS` | `15000` | The maximum duration of a call, in milliseconds. A value that is not a strictly positive number is ignored, and the default value applies. |

The timeout covers the full round trip, headers and response body included. Going
over it returns an explicit network error, never a silent hang.

These two variables go in your client's configuration, under the `env` key. The
block below is complete and can be copied as is. It carries a 30 second timeout.

```json
{
  "mcpServers": {
    "sealtrust": {
      "command": "npx",
      "args": ["-y", "@sealtrust-io/mcp-server"],
      "env": {
        "SEALTRUST_TIMEOUT_MS": "30000"
      }
    }
  }
}
```

> [!ATTENTION] `SEALTRUST_API_URL` takes a host, without a path
> The server itself adds the `/v1` prefix in front of every path it calls. That
> prefix is fixed in the program and cannot be turned off. Write
> `https://api.sealtrust.io` and nothing more.

## 3. Know which identifier you have

Six tools out of seven take a product identifier. Four forms exist, and they are
not accepted everywhere.

| Form | What it looks like | Where you find it |
| --- | --- | --- |
| Serial number | 12 characters, digits and uppercase letters, for example `EXEMP1E00000` | This is the only identifier readable on a product. The printed QR carries it. Someone who has scanned or copied a label has that one. |
| Identifier hash (`uid_hash`) | `0x` followed by 64 hexadecimal characters | Returned by our own responses. This is the product's internal identifier. It comes from the NFC chip when the product carries one. SealTrust draws it at random when the product is identified by QR alone. |
| Token identifier | A string of digits | The identifier of the on-chain token, returned under the name `token_id`. |
| Certificate number | Starts with `ST-CERT-` | On the certificate of authenticity. Accepted by `verify_product` and `get_certificate` only. |

The serial number alphabet excludes the letters I, L, O and U, so that a person
copying a label does not mistake a letter for a digit. The MCP server passes the
number along exactly as it is given, without rewriting a single character. It is
the API that puts the number back in uppercase and that brings ambiguous
characters back to their canonical form: `I` and `L` are worth `1`, `O` is worth
`0`.

Two tools also take a GTIN, the article code with 8, 12, 13 or 14 digits. A GTIN
designates a model. It designates no particular item.

> [!INFO] An unknown identifier is still an answer
> A well formed but untraceable identifier returns a normal result, carrying
> `found: false` or `status: "unknown"`. The assistant can reason on it. A tool
> error occurs in three cases: a malformed argument, refused before any network
> call, an API error response other than a 404, and a network failure. A 404
> returns a normal result carrying `found: false`.

## 4. The seven tools

### `verify_product`

Answers the question "is this product genuine". The tool chains two calls:
resolving the identifier, then reading the certificate.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | Serial number, identifier hash, token identifier or certificate number. |

The response carries a `status` field that takes five values.

| Value | What it means |
| --- | --- |
| `authentic` | An active certificate of authenticity is registered. |
| `revoked` | The certificate has been revoked. The product is to be treated as suspect. |
| `expired` | The certificate has passed its end date. |
| `found_no_certificate` | The product exists in the registry and no active certificate is attached to it. |
| `unknown` | No trace of this identifier. |

Coming with them: a plain text `message` field, a `product` block carrying the
product name, the brand name, the category, the token identifier, the identifier
hash, the contract address, the mint transaction and the creation date, a
`certificate` block or the null value, a `has_published_passport` boolean, and an
`anchored_on_base` boolean.

`anchored_on_base` is true when an anchor exists, whichever one it is: the one
for the product batch, or the one for the passport content. The two are
independent. A passport can be anchored while its batch has not been, and the
other way around. To find out which of the two, and with which transaction, call
`get_passport_proof`.

Expect this boolean to be false most of the time. Anchoring is an operation that
SealTrust triggers by hand. An anchoring request coming from a brand account is
refused. Minting a product does not anchor it, and publishing a passport does not
anchor it either. Most products are never anchored. So an `anchored_on_base` set
to false says nothing against the product, and your assistant must never present
it as a suspicion.

### `get_passport`

Returns the published digital passport, at the public access level.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | Serial number, identifier hash or token identifier. A certificate number is not accepted here. |
| `format` | `string` | no | `json` by default, or `jsonld` for a representation in Schema.org and GS1 vocabulary. Any other value is refused before the network call. |

The response in the default format carries the passport version, the published
content under the `data` field, the SHA-256 hash of that content under
`data_hash`, the name of the product and of the brand, the GTIN and the product's
GS1 Digital Link.

The two fields that point to the IPFS copy always hold the null value on this
path. They are reserved for authenticated access levels. The link to the
immutable copy, when it is published, appears in the response of
`get_passport_proof`.

### `get_passport_proof`

Returns the public proofs that make it possible to verify a passport without
trusting us.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | no | The item you want the proof for. |
| `gtin` | `string` | no | The model you want the proof for, at reference level. |

You must supply exactly one of the two. Neither of them, or both at once, gives
an immediate refusal with a message that explains the difference. The two
parameters do not designate the same thing: the proof of an item dates the
object, the proof of a reference dates the document.

The response contains the content hash, the link to the IPFS copy when it is
published, a `seal` block that says whether the version is sealed and whether its
content still matches what was sealed, the issuance state of the verifiable
credential, and the number of successful physical verifications. A missing proof
is absent from the response, with no empty field.

> [!ATTENTION] Base your reading on `anchored`
> The `anchor` block comes in two forms. When the product's batch has been
> anchored, it carries the root of the Merkle tree, `anchored` is true and
> `proves` is `batch_inclusion`. When the batch has never been anchored, that
> same block carries the product's mint transaction, `anchored` is false and
> `proves` is `token_minted`. The second form proves that the token exists and
> says nothing about the passport content. An interface that announces "anchored"
> on the mere presence of the `anchor` key claims more than the proof carries.

A reference level passport, the one obtained with `gtin`, legitimately carries
neither an `anchor` block nor a verification count. It is tied to no physical
item. Its proof is the anchoring of its own content, which dates the document.
The response then carries `level: "model"` and the normalized GTIN.

### `get_product_history`

Returns the history of a product: ownership movements and authentication scans,
gathered into a single timeline, from the most recent to the oldest. The first
element of `timeline` is the most recent event.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | Serial number, identifier hash or token identifier. |

Each entry carries a `type` field that is `verify` for a scan or `transfer` for
an ownership movement, a timestamp, the token identifier and the identifier hash.
The rest depends on the type. A `verify` entry carries a validity indicator and
no party label. A `transfer` entry carries two display ready labels, for the
party giving up and the party receiving, and no validity indicator.

For an anonymous caller, and the MCP server is one, identities are masked: email
addresses are anonymized and raw wallet addresses are not returned.

> [!ATTENTION] No entry carries its own on-chain transaction
> Do not offer a link to the block explorer for each movement, there is nothing
> to link to. The mint transaction is found in `verify_product`, and the
> anchoring in `get_passport_proof`.

### `get_certificate`

Returns the public certificate of authenticity of a product.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | Serial number, identifier hash, token identifier or certificate number. |

The response carries the certificate number, its state, its issuance date, its
end date when there is one, the name of the issuer, the name of the product and
that of the brand, as well as the custom fields recorded at issuance, returned as
is. Everything written there is public.

### `resolve_gs1`

Starts from a GS1 Digital Link and returns the passport it designates.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `gtin` | `string` | yes | The article code of the model. |
| `serial` | `string` | no | The serial number printed on the item. Omit it to target the model. |

The presence of the serial number changes what the link designates. With it, the
link `/01/{gtin}/21/{serial}` designates a precise item and the response carries
`level: "item"`. Without it, the link `/01/{gtin}` designates the article class
and returns the reference level passport, the one shared by every item of the
model, with `level: "model"`. It is that second level that is used by goods sold
by the meter, by the box or by the pallet. An empty string in `serial` is read as
an absence.

The response also carries the reconstructed link, the address the resolution
redirects to, the identifier retained, and the public passport when there is one.

Without a published reference passport, `/01/{gtin}` answers 404 and the tool
returns `found: false` with the message "Unknown GS1 Digital Link". The result
`resolved: true` accompanied by a null passport only happens when you supply a
`serial` and that item has no published passport.

The serial number expected here is indeed the one printed on the product. The
on-chain token identifier is not suitable in this place.

### `verify_credential`

Verifies the signature of the passport's verifiable credential, in SD-JWT-VC
format, against the key published by the issuing brand in the form `did:web`.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `identifier` | `string` | yes | Serial number, identifier hash or token identifier. |

The response carries `verified`, the issuer's decentralized identifier, the type
of the credential, the version of the signing key, the access level applied, an
`error` field holding `verification_failed` in case of failure, and the content
of the credential when the signature is valid.

> [!INFO] A truly independent verification happens on your side
> This tool asks SealTrust to verify a signature produced by SealTrust. For an
> independent check, fetch the brand's decentralized identity document yourself
> and verify the signature against the key published there.

## 5. Reading responses and errors

The server separates three situations, and that separation matters for an
assistant reasoning on the result.

| Situation | What the server returns | What to conclude from it |
| --- | --- | --- |
| Product, passport or credential untraceable | A normal result carrying `found: false` and the API message, or `status: "unknown"` for `verify_product` | The question got an answer. Nothing broke. |
| API error response other than a 404 | A tool error whose message reads `SealTrust API error (`, the HTTP code, `): `, then the detail returned by the API. There is no space before the colon. | The API answered and refused. The code says why. |
| API unreachable, timeout exceeded, DNS, connection refused | A tool error starting with `Network error` | No information about the product. Conclude nothing about its authenticity. |

> [!DANGER] Never confuse the last two lines
> "The product does not exist" and "the API did not answer" lead to opposite
> decisions. The server distinguishes them by construction, your handling must
> distinguish them too.

A malformed argument is refused before any network call: an empty identifier, a
`format` that is neither `json` nor `jsonld`, or the absence of a choice between
`identifier` and `gtin` on the proof.

## 6. The call limits

Public endpoints are capped per calling IP address, over a 60 second window. The
path families that have a limit of their own each keep their own counter. The
others fall under a general counter, shared by all routes without a dedicated
limit.

| Tool | Path called | Limit |
| --- | --- | --- |
| `verify_product` | resolution, then certificate | 60 per minute on each of the two |
| `get_passport`, `get_passport_proof`, `verify_credential` | passport | 60 per minute |
| `get_certificate` | certificate | 60 per minute |
| `get_product_history` | timeline | 30 per minute |
| `resolve_gs1` | GS1 resolution, then passport | general counter on the GS1 resolution, shared with the other paths without a limit of their own, then 60 per minute on the passport |

Two tools consume two calls instead of one. `verify_product` queries the
resolution then the certificate. `resolve_gs1` reads the resolver's redirect
without following it, then requests the passport directly. Count them as two.

Going over the limit returns a 429 response, which the server surfaces as a tool
error. One case is an exception. On `verify_product`, only the first call
surfaces a 429. Going over the limit on the certificate read is logged to the
error output and the tool answers anyway, with the summarized certificate that
the resolution already carries.

## What can stop you

| Symptom | Cause | What to do |
| --- | --- | --- |
| The client shows no tool | The server did not start, or the client was not restarted after its configuration was modified | Run `npx @sealtrust-io/mcp-server` by hand. The startup line must appear. Then restart the client. |
| `Network error` on every tool | The API is not reachable from the machine, or `SEALTRUST_API_URL` points elsewhere | Check the variable, then the machine's network egress. |
| A call systematically exceeds the timeout | The 15 second timeout is too short for your link | Raise `SEALTRUST_TIMEOUT_MS` under the `env` key of your client's configuration, as in point 2, then restart the client. |
| `found: false` on an identifier you know to be right | The form used is not accepted by this tool | Check the form in the identifier table. A certificate number only goes through on `verify_product` and `get_certificate`. |
| The tools contradict each other on an item withdrawn from the market | Each tool handles end of life in its own way | `verify_product` returns `status: "unknown"`. `get_passport_proof` and `get_certificate` return `found: false`. `get_passport` returns the passport, together with a `lifecycle` block indicating that the item is no longer current. |
| The passport comes back without an IPFS link | The public access level does not carry it | Use `get_passport_proof`, which returns the link to the immutable copy when it is published. |
| A 429 error | A call limit has been reached | Space out the calls. The counter restarts after 60 seconds. |
