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_entityandbatch_createtherefore refuse the whole dataset while that is true, naming how many entities are affected and of which types.validate_datasetlists them individually.save_datasetstill 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.