Skip to content

Connecting an agent (MCP)

The hub exposes a Model Context Protocol endpoint at /hub/mcp, so an agent such as Claude can read and write your datasets without a browser.

Getting a token

The hub signs you in through your institution, which needs a browser; an agent is not a browser, so it presents a token instead. On the hosted hub that token comes from SRAM; a local instance issues its own.

SRAM application tokens

On the hosted hub, create an application token for the Metaseed Hub application in SRAM itself, under the collaboration's Application tokens tab. SRAM shows it once. The hub validates every request carrying it by asking SRAM, so the token is exactly as alive as your place in SRAM: it stops the moment SRAM suspends you, your institution withdraws your login, or the token reaches the lifetime SRAM fixes for the application. The hub stores nothing about it and cannot revoke it; you do that in SRAM. The answer SRAM gives also carries your current collaborations, so a request made with this token refreshes the reading the hub keeps, as a sign-in does.

You must have signed in to the hub once before, so an account exists for the token to act as; the hub refuses the token with that message otherwise. A request the hub cannot check because SRAM is unreachable is refused as not checked, with status 503, never as an invalid token: someone else's downtime must not read as a revoked credential. Answers are cached for a few minutes, so a client making many requests does not ask SRAM for each one.

Hub personal access tokens

A local instance has no SRAM, so it issues its own: create one under Access tokens on your profile. The token is shown once and cannot be recovered — only its hash is stored, so a copy of the database is not a set of working credentials. Create a new one if you lose it, and revoke any you no longer use. A token can be given an expiry, after which it stops working on its own; a token without one lasts until revoked, which is what a token pasted into a config file and forgotten then does indefinitely. The hosted hub accepts these too, but a hub token outlives your place in SRAM, which is why the SRAM token is the credential there.

What a token is

Either token acts as you. Every tool call is scoped to your own account: it can see and change your datasets and nothing else. Neither carries the administrator role an OIDC sign-in can.

The endpoint also accepts an OIDC bearer — the access token your signed-in hub session already holds — for clients that can obtain one. It expires with your session.

Configuring Claude Code

claude mcp add --transport http metaseed-hub https://metaseed.ewi.tudelft.nl/hub/mcp \
  --header "Authorization: Bearer your_token_here"

What an agent can do

Tool
whoami Which account the token acts as
list_datasets Your datasets
get_dataset A dataset's stored contents
create_dataset A new, empty dataset; refused with a message when you already have a dataset of that name
save_dataset Replace a dataset's contents
validate_dataset Check a dataset against its profile and list what is missing
delete_dataset Remove a dataset (soft — it is not erased)
list_profiles Built-in standards, plus every published specification
get_profile_schema A profile's entity types and their fields. A field carries the example value its specification gives, so an agent sees the expected form of a value before writing one; a field whose specification gives none carries no example
get_profile_relationships A profile's hierarchy: each entity's identifier, children, and cross-references

Editing entities

Tool
create_entity Add one entity, without rewriting the dataset
batch_create Add several entities in one call, root-first; one version is kept for the whole batch
update_entity Change named fields; unnamed fields keep their values
delete_entity Remove one entity
list_entities The dataset's entities, with ids and values
get_entity One entity's stored values

Each of these reports what is still missing after the change, using the profile's own rules, so an agent can fill a dataset step by step instead of resending all of it. In a batch_create call, parent_index nests an item under an earlier item of the same batch, so a parent and its children can land together.

Building a specification

Tool
spec_create Start a new specification as a private draft
spec_import_yaml Start a new private draft from a YAML specification document
spec_clone Start a new private draft from a built-in profile or a published specification
spec_add_entity Add an entity type
spec_update_entity Change an entity's description or ontology term; unset arguments keep their values
spec_rename_entity Rename an entity, cascading the root and every reference to it
spec_delete_entity Remove an entity type
spec_add_field Add a field to an entity. items links a list or entity field to the child entity it nests; reference and parent_ref name cross-references; pattern, min_length, max_length, minimum, maximum, min_items, max_items, and enum constrain its values; the eighteen field markers declare what the field means
spec_update_field Change a field's attributes in place, items, reference, and parent_ref among them, plus the eight constraints, clear, and the eighteen field markers; unset arguments keep their values
spec_delete_field Remove a field
spec_move_field Move a field one position up or down
spec_add_rule Add a validation rule
spec_update_rule Change a validation rule in place
spec_delete_rule Remove a validation rule
spec_set_root_entity Set the entity a dataset starts from
spec_set_metadata Change the profile-level name, version, display name, description, or ontology
spec_status A summary of the draft: name, version, root, entities, rules
spec_validate What is wrong with the draft, under problems, and what merely looks unintended, under warnings
spec_preview_yaml The draft as YAML
list_spec_drafts Your drafts
spec_delete_draft Remove one of your own drafts

A tool's draft argument is the draft's name. When you hold several versions of that name as drafts, the name alone is ambiguous and the tool says so; name one as name@version, for example cropxr-phenotyping@1.3. A draft's name is its specification's profile name: the name you give spec_create, spec_import_yaml or spec_clone becomes the profile name, and renaming the profile with spec_set_metadata renames the draft, so the response carries the name to address it by from then on. Every draft save, from these tools, from the browser or from a metaseed hub push, is checked against the revision the caller read; a save that would overwrite an edit made since is refused and the tool says so.

A specification is a tree: every entity except the root must be linked under a parent by a field on the parent whose type is list or entity and whose items names the child. An unlinked entity is an orphan a dataset can never reach, and spec_validate does not flag orphans. The endpoint's instructions carry this workflow (shared with the standalone metaseed MCP server), so connected agents link entities as they build.

Editing constraints

spec_update_field accepts the same eight constraints as spec_add_field — pattern, min_length, max_length, minimum, maximum, min_items, max_items, enum — and merges them into the field's existing constraints. A constraint you do not supply keeps the value it had, so tightening maximum on a field that already has a minimum no longer discards the minimum.

Because an omitted argument means "unchanged", it cannot express removal. clear names the constraints to unset: clear=["pattern"] removes the pattern and leaves every other constraint alone. Setting and clearing the same constraint in one call is refused rather than resolved in some arbitrary order — the two requests contradict each other. The field is left untouched when a call is refused, including when a name is not one of the eight.

Field markers

A constraint says what values a field may hold; a marker says what the field is. Both spec_add_field and spec_update_field accept the eighteen markers metaseed's FieldSpec defines: is_identifier and is_label declare which field identifies the entity and which one labels it in listings, overriding the positional convention that would otherwise pick the first field; owns marks a containment relationship; and codename, ontologies, unique_within, dcat, example, options, unit, label, and tier carry the field's metadata. tier is one of required, recommended, or optional. For a profile bound to FAIRDOM-SEEK, isa_tag, within, reference_scope, seek_attribute_type, seek_controlled_vocab and seek_cv_free_text say which SEEK column a field becomes: its ISA tag, its attribute type, the controlled vocabulary it binds, and whether that vocabulary accepts free text.

A marker, unlike a numeric constraint, has a representable empty value, so it needs no clear: false, "", and [] are the removal request. An unset marker is absent from the specification rather than written as owns: false, so a draft that never touched a marker hashes the same as one that toggled it back off. A list marker is replaced whole, not merged. A value the schema refuses — a tier that is not one of the three levels, say — is reported and the field is left untouched, as it is when a constraint name is refused.

Adding a nested field with items also creates the parent's identifier field and a back-reference on the child, so the relationship is complete in both directions without further calls. spec_delete_draft removes a draft the caller owns; a draft a dataset is built on is not deleted, because the dataset would lose its specification.

Problems and warnings

spec_validate reports two lists. problems are defects: the draft does not build, and valid is false. warnings are advisory — an entity with no declared is_identifier whose identifier would be inferred onto an optional free-text field, for instance. A warning does not make a draft invalid, so valid stays true and problems stays empty; it names something worth deciding on before publishing.

Drafts are private to you. Publishing is not available to an agent — it shares a specification with every user of the hub, or with a collaboration, so it stays something you do yourself in the web interface.

Published specifications are included in list_profiles: those published to everyone, and those published to a collaboration you are in. Every tool that takes a profile name resolves it for you, the caller, so list_profiles, get_profile_schema, get_profile_relationships, spec_clone and create_dataset agree on which specifications exist — an agent can build a dataset against one it did not write, and a specification it cannot see does not exist for it in any of them. In the hub's code that is one resolver, _profile_spec, whose contract (mcp/_contracts.py) carries the caller; tests/test_mcp_tools_see_collaboration_specs.py checks the five tools against a specification published to a collaboration.

Ontology lookups

Tool
search_ontology Search EMBL-EBI OLS4 for terms matching a query
get_ontology_term One term's definition and synonyms, by CURIE id
suggest_ontology_term Lighter suggestions for a partly typed term
list_ontologies The ontologies OLS4 offers, with their ids

These are read-only lookups against the Ontology Lookup Service, through the same cached, rate-limited service the web interface uses. They require a valid token — an open endpoint would let anyone drive traffic through the hub — but touch no dataset.

What an agent cannot do to your work

An agent replaces a whole dataset in one call, and can do it in a loop, so the write path is built to leave a way back:

  • Every overwrite keeps the previous contents as a version, restorable from the dataset's history in the web interface. A save that changes nothing adds no version, so repeated writes cannot bury the history.
  • Deleting is soft. The dataset stops being listed but is not erased.
  • A dataset larger than 5 MB is refused rather than stored, so a runaway loop is stopped.
  • An edit that would drop entities the hub could not read is refused. If a stored entity does not load — usually because the specification changed and no longer defines its type — it is missing from the dataset the agent sees, and saving over it would delete it. create_entity, update_entity, delete_entity and batch_create therefore refuse the whole dataset while that is true, naming how many entities are affected and of which types. validate_dataset lists them individually. save_dataset still works, because replacing the whole dataset is a deliberate act; use it only once you are content to lose them.
  • Every write is logged with the account it acted as.

A token reaches only its own user's datasets. Another person's dataset is not readable, writable, or deletable, and a name that exists in someone else's account reads as absent.

Using a token with the REST API

The same token authenticates the REST API at /api, so a script can do anything the web interface can:

curl -H "Authorization: Bearer msh_your_token" \
  https://metaseed.ewi.tudelft.nl/api/datasets

Three calls exist for a metaseed instance that pushes and pulls against the hub (see Pushing and pulling with metaseed-hub in the metaseed documentation): GET /api/me names the account and tenant the token acts in; GET /api/specs lists the published specifications and GET /api/specs/{name}/{version} returns one as a YAML profile document; POST /api/specs with {"yaml": "..."} pushes a profile into your account as a private draft — only you see it; pushing the same name and version again updates that draft in place, and a new version becomes a second draft beside it; {"yaml": "...", "publish": true} publishes it for every hub user instead, under the same version-bump gate the spec builder applies (identical content already published answers 200; a version taken by different content, or below what the change requires, answers 409 with the reason). POST /api/specs/{id}/unpublish withdraws a published specification back to a private draft, with the same permissions as the Unpublish button. Each entry GET /api/specs lists carries visibility (draft or published) and mine. GET, POST and PATCH on /api/datasets carry datasets both ways.

The hub accepts exactly two credentials: a SRAM access token, which a browser obtains through the sign-in flow, and a token from this page. A token acts for your own data only and never carries administrator rights, whatever your account holds.

Revoking access

Revoke a token from your profile. It stops working immediately; the record is kept so an administrator can see that it existed and when it was withdrawn.