eLabFTW MCP
Powers the AI tools (review_experiment, suggest_tags, suggest_metadata) by calling an OpenAI-compatible chat-completions endpoint for generating experiment reviews, tag suggestions, and metadata suggestions.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@eLabFTW MCP show my recent experiments and their statuses"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
eLabFTW MCP
Standalone Model Context Protocol server for eLabFTW — 41 tools, pure Python, MCP protocol revision 2026-07-28 (stateless) with older handshakes still served. Single-user (stdio) and multi-user hosted mode (register page → personal URL with scoped token).
No R runtime, no external tool library: everything talks to the eLabFTW REST API v2 directly.
Credits
The tool surface, parameter names, validation rules and the safety rails (append-only body updates, provenance blocks, dry-run link operations) are a faithful port of
which implemented the same 41 tools in R on top of mcptools/`ellmer. That implementation is
the reference this port was written against, and its documented behaviour is what the tests
assert. Thank you — this would have been a much worse server without it.
Also derived from that work: the hosted mode's feature set (register flow, profile presets,
per-token tool scope, audit log), which previously lived in the unified-researchdata-mcp
deployment.
Related MCP server: protocol-mcp
Tools (41)
Group | Tools |
Read (13) |
|
Meta (3) |
|
Create / update (7) |
|
Upload (1) |
|
Steps (5) |
|
Links (8) |
|
AI (5) |
|
Write tools honour the profile of the token (r read-only, h hybrid, f full) and the
features.write_* switches in the config; destructive operations require an explicit
confirmation argument.
Install
pip install . # or: uv pip install .Requires Python ≥ 3.10 and an eLabFTW API key (eLabFTW → Account → API keys).
Local (stdio) usage
{
"mcpServers": {
"elabftw": {
"command": "elabftw-mcp",
"env": {
"ELABFTW_BASE_URL": "https://elntest.ub.tum.de",
"ELABFTW_API_KEY": "your-key"
}
}
}
}Or against any HTTP client: elabftw-mcp --transport streamable-http --port 8081
(stateless by default, --stateful for session-based clients, --json-response if the
client cannot read SSE).
Hosted (multi-user) usage
export MCP_JWT_SECRET="$(python -c 'import base64,os;print(base64.urlsafe_b64encode(os.urandom(32)).decode())')"
elabftw-mcp --hosted --host 0.0.0.0 --port 8081/register— enter instance URL + API key, pick a profile and the tools to expose/mcp?token=…— the MCP endpoint for the issued personal URL/status— service and protocol infoaudit log (JSONL), registration rate limit,
X-Forwarded-Protoaware
Tokens are HMAC-signed and carry the instance URL, the API key, the profile and the tool
allow-list; keys are never stored server-side. Requests without a valid token get 401;
tools outside a token's scope are refused with -32601.
Configuration
config.example.yml → config.yml (path via ELABFTW_MCP_CONFIG). Everything can be
overridden by environment variables: ELABFTW_BASE_URL, ELABFTW_API_KEY,
ELABFTW_MCP_AI_MODEL, ELABFTW_MCP_AI_KEY, ELABFTW_MCP_AI_BASE_URL,
ELABFTW_MCP_RATE_LIMIT, MCP_JWT_SECRET, MCP_TOKEN_EXPIRY_DAYS, ELABFTW_MCP_AUDIT_LOG.
features:
write_enabled: true # master switch for every write tool
write_create: true # create_experiment / create_item / …
write_update: true # body / field / metadata updates
allow_body_overwrite: true # otherwise only append mode is allowed
ai_review: true # review_experiment etc.
cache_ttl_seconds: 300 # cache for team catalogues and /users/meAI tools
review_experiment, suggest_tags and suggest_metadata call an OpenAI-compatible
chat-completions endpoint (ai.base_url, ai.model, ai.api_key). Without a key they
answer with the upstream placeholder wording instead of failing — the response contract
(trace_id, entity_type, entity_id, suggestions, status) stays identical either way.
Tests
python tests/test_api_contract.py # offline: request shapes vs. the OpenAPI spec
python tests/test_tools_offline.py # offline: all 41 tools against a stub eLabFTW
python tests/test_transports.py # offline: stdio + stateless HTTP, real MCP client
python tests/test_ai_tools.py # offline: AI tools vs. a local LLM stub (no quota spent)
python tests/test_ai_tools.py --live # live instance + local LLM stub
python tests/test_live_tools.py # live: all 41 tools against a real instance
python tests/test_proxy_live.py # live: register flow, scope, protocol erasFor a running deployment there are two checks that need nothing but a URL:
python tests/diagnostics/deployed_e2e.py # one endpoint: register, 41 tools, read + write + cleanup
python3 tests/diagnostics/service_sweep.py # the whole host: web hosts, register pages, /el, /dt, /nmLive suites read the key from ../.elab_key (never printed), tag everything they create as
elabmcp-test-<timestamp> and delete it again. tests/diagnostics/ holds the small probes used
to pin down the API behaviour on a new instance (step fields, link direction, metadata payload).
Status of the last full run (eLabFTW 6.0.1, MCP Python SDK 2.2.0):
Suite | Result |
| 46 / 46 checks PASS, test data cleaned up |
| 45 / 45 PASS |
| PASS |
| 15 / 15 PASS |
| 17 / 17 PASS |
| 8 / 8 PASS |
| 13 / 13 PASS |
API notes (measured against a running instance, not just the spec)
Observation | Consequence for the tools |
|
|
|
|
|
|
|
|
Links are stored on one side only: | outgoing via the subresource, incoming via |
Tags are canonicalised on write ( | b |
| the live suites verify removal through the listing instead of a 404 |
Single-entity GETs can be very large (item types with long HTML bodies) | results are always valid JSON: long strings are shortened before anything is truncated |
Response statuses used by the write tools: created, updated, deleted, uploaded,
linked, already_linked, would_link, unlinked, already_absent, would_unlink, ok,
partial — the same vocabulary across all write tools.
Relation to the previous /el deployment
Measured against the running R-based service (researchmcp.duckdns.org/el, reproduced by
tests/diagnostics/compare_with_deployed.py): the tool surface is identical — 41 tools on
both sides, none missing, none extra — and the same entities and ids come back (categories,
statuses and user info 100% identical, get_experiment 81%).
Deliberate differences:
previous deployment | this server | |
answer format | R print output ( | JSON |
entity rows | whole row | whole row (only |
protocol | 2025-06-18 only | 2026-07-28 stateless plus legacy handshakes |
AI tools | bootstrap placeholders | real LLM calls, same response contract |
| forwarded to the worker | accepted, can only narrow the token's profile |
personal URLs | HMAC(instance, key, profile, tools, expiry) | byte-compatible format, verified with the same |
Only /el (proxy + R worker) is replaced. /nm (NOMAD MCP), /dt (DataTagger MCP), the
eLabFTW instance, the databases, Caddy and the two Streamlit apps keep running untouched.
Documented differences from the upstream R implementation
Topic | Behaviour here |
Metadata structure | eLabFTW 6 format ( |
Incoming links | Resolved via the documented |
Linkable types | eLabFTW's link routes exist for experiments and items only; template/item-type link calls fail with a clear message instead of a silent no-op |
AI tools | Real LLM calls (upstream shipped bootstrap placeholders); same response contract |
Provenance | Key |
Team capability flags | A missing |
Low-level/inventory/compound flags | Present in the config for compatibility; the API is called directly |
License
MIT, see LICENSE.
This server is a rewrite of Marvin Luepke's elabMCP R server and its elabR library,
both MIT licensed. The tool surface, the response shapes and the runtime quirks documented
below follow those sources; the credits are in the section above.
Available Tools
41 toolsadd_ai_review_commentA
Attach an AI-generated review comment to an eLabFTW experiment or item. The provenance header ('[AI-GENERATED] | | trace: …') is added automatically. WRITE OPERATION — requires the comment write scope.
Inputs: id (required), comment (required), entity_type (required), trace_id. Returns: result
add_stepA
Add a new step (task) to an existing eLabFTW experiment, item, experiment template or item type. WRITE OPERATION — requires the step write scope. A deadline (date/datetime string) is set with a follow-up update because the create endpoint only stores the body. Returns entity_type, entity_id, step_id, the stored deadline and status='created'.
Inputs: id (required), body (required), entity_type (required), deadline, +1 more. Returns: result
apply_tag_suggestionsB
Persist a list of (suggested) tags to an eLabFTW experiment or item. WRITE OPERATION — requires the tag write scope.
Inputs: id (required), tags (required), entity_type (required). Returns: result
bulk_delete_linksA
Idempotently remove several directed links in one call. 'pairs_json' is a JSON array of {from_type, from_id, to_type, to_id} objects. WRITE OPERATION when dry_run=false — requires the link write scope. Returns a per-pair result list plus a summary.
Inputs: pairs_json (required), dry_run, trace_id, continue_on_error. Returns: result
bulk_ensure_linksA
Idempotently create several directed links in one call. 'pairs_json' is a JSON array of {from_type, from_id, to_type, to_id} objects, at most bulk_links_max_pairs entries. WRITE OPERATION when dry_run=false — requires the link write scope. Returns a per-pair result list plus a summary.
Inputs: pairs_json (required), dry_run, trace_id, continue_on_error. Returns: result
create_experimentA
Create a new eLabFTW experiment. WRITE OPERATION — requires the create write scope. Discover valid category/status IDs first; never embed IDs in the title or body. If the team enforces experiment templates (force_exp_tpl), this tool is refused and create_experiment_from_template must be used. Returns the new id.
Inputs: body, tags, title, status_id, +1 more. Returns: result
create_experiment_from_templateA
Create a new eLabFTW experiment from an experiment template. WRITE OPERATION — requires the create write scope. Use list_experiment_templates to find template IDs. include_steps/include_body decide whether the template's steps and body are copied. Returns the new id.
Inputs: template_id (required), tags, title, trace_id, +4 more. Returns: result
create_itemA
Create a new eLabFTW database item (resource). WRITE OPERATION — requires the create write scope. Items belong to item categories; discover valid IDs first and never embed IDs in the title or body. Returns the new id.
Inputs: body, tags, title, status_id, +1 more. Returns: result
delete_linkA
Idempotently remove a directed link between an experiment and an item. WRITE OPERATION when dry_run=false — requires the link write scope. dry_run=true (default) only reports. Returns result: unlinked | already_absent | would_unlink.
Inputs: to_id (required), from_id (required), to_type (required), from_type (required), +2 more. Returns: result
delete_stepA
Delete a step from an eLabFTW experiment, item, experiment template or item type. WRITE OPERATION — requires the step write scope. Returns status='deleted'.
Inputs: id (required), step_id (required), entity_type (required). Returns: result
ensure_linkA
Idempotently create a directed link between an experiment and an item. WRITE OPERATION when dry_run=false — requires the link write scope. dry_run=true (default) only reports what would happen. Returns result: linked | already_linked | would_link (verified after writing).
Inputs: to_id (required), from_id (required), to_type (required), from_type (required), +2 more. Returns: result
ensure_link_by_queryA
Resolve the target entity by query and then link it idempotently. Query resolution works for experiments and items only. WRITE OPERATION when dry_run=false — requires the link write scope. With several matches the tool returns the candidates instead of guessing (on_ambiguous='error' makes it fail instead).
Inputs: from_id (required), to_type (required), from_type (required), q, +11 more. Returns: result
expand_links_networkA
Build the directed link graph around a root experiment/item via BFS. direction is 'both', 'outgoing' or 'incoming'; max_depth is capped at 3; node/edge/fan-out caps and optional filters (allowed_types, category_ids, status_ids, title_regex as JSON arrays/pattern) keep the graph bounded. Returns root, parameters, nodes, edges and truncation flags. Read-only.
Inputs: root_id (required), root_type (required), direction, max_depth, +9 more. Returns: result
get_connection_infoA
Return connection and session details for the active server: eLabFTW base URL, authenticated user, active team, effective server mode (read-only vs write-enabled), enabled tool groups, and a category catalogue (experiments and items) for the active team. Always safe to call; never writes anything.
Inputs: no named parameters. Returns: result
get_current_user_capabilitiesA
Return a sanitized subset of the current user's capabilities and team roles. Calls /users/me and returns only the fields relevant for capability detection: userid, fullname, current team id, team memberships (id, name, is_admin, is_owner, is_archived), is_sysadmin, can_manage_compounds and can_manage_inventory_locations. Sensitive fields (email, API keys, signatures, preferences) are deliberately omitted.
Inputs: no named parameters. Returns: result
get_entity_linksA
Fetch directed link edges of a single eLabFTW experiment or item: outgoing links (items_links / experiments_links) and incoming ones (related_items_links / related_experiments_links). Each edge carries from {type,id}, to {type,id} and a direction label. Read-only.
Inputs: id (required), entity_type (required), include_raw, include_incoming, +1 more. Returns: result
get_experimentA
Fetch a single elabFTW experiment by its numeric ID. Returns the full experiment record including body, metadata, tags, comments, links and uploads. Always safe to call; never writes anything.
Inputs: id (required). Returns: result
get_experiment_templateA
Fetch a single elabFTW experiment template by its numeric ID. Returns the full template record including body, metadata and steps. Use list_experiment_templates to discover templates and create_experiment_from_template to create an experiment from one. Always safe to call; never writes anything.
Inputs: id (required). Returns: result
get_itemA
Fetch a single elabFTW database item (resource) by its numeric ID. Returns the item with metadata, tags, comments, links and uploads. Always safe to call; never writes anything.
Inputs: id (required). Returns: result
get_item_typeA
Fetch a single elabFTW item type by its numeric ID. Returns the full item type record including body, metadata and steps. Use list_item_types to discover item types. Always safe to call; never writes anything.
Inputs: id (required). Returns: result
list_experiment_categoriesA
List all experiment categories for the active elabFTW team. Specify team_id='current' (default) or a numeric team ID. Returns id, title, color and is_default per category — use those ids when filtering list_experiments by category. Always safe to call; never writes anything.
Inputs: team_id. Returns: result
list_experimentsA
Search or list elabFTW experiments. Returns a structured response with paging metadata, applied filters, and result records. Check paging.returned_count vs paging.limit to detect further pages. Use the dedicated filter arguments for known category/user/status IDs or tags instead of putting them into q. Always safe to call; never writes anything.
Inputs: q, sort, tags, limit, +7 more. Returns: result
list_experiment_statusesA
List all experiment statuses for the active eLabFTW team. Specify team_id='current' (default) or a numeric team ID. Read-only.
Inputs: team_id. Returns: result
list_experiment_templatesA
List elabFTW experiment templates (/experiments_templates). Returns a structured response with paging metadata and result records. Templates are reusable pre-filled starting points for new experiments and are DIFFERENT from item types (use list_item_types for those). Use get_experiment_template for a single template and create_experiment_from_template to use one. Always safe to call; never writes anything.
Inputs: q, sort, tags, limit, +3 more. Returns: result
list_item_categoriesA
List all item categories (resource categories) for the active eLabFTW team. Specify team_id='current' (default) or a numeric team ID. Use the returned ids when filtering list_items by category. Always safe to call; never writes anything.
Inputs: team_id. Returns: result
list_itemsB
Search or list elabFTW database items (resources). Returns a structured response with paging metadata, applied filters, and result records. Check paging.returned_count vs paging.limit to detect further pages. Use the dedicated filter arguments for known IDs or tags instead of q. Always safe to call; never writes anything.
Inputs: q, sort, tags, limit, +7 more. Returns: result
list_item_statusesA
List all item statuses (resource statuses) for the active eLabFTW team. Specify team_id='current' (default) or a numeric team ID. Read-only.
Inputs: team_id. Returns: result
list_item_typesA
List elabFTW item types (/items_types), also called resource categories. They define the category structure and default content for database items and are DIFFERENT from experiment templates (use list_experiment_templates). Use get_item_type for a single one. Always safe to call; never writes anything.
Inputs: q, sort, tags, limit, +3 more. Returns: result
list_stepsA
List all steps (tasks) attached to an eLabFTW experiment, item, experiment template or item type. Returns entity_type, entity_id, total_steps and a steps array (id, body, finished, ordering, deadline). With render_markdown=True a 'markdown' field with a GFM checkbox task list and deep links to each step is added. Read-only.
Inputs: id (required), entity_type (required), render_markdown. Returns: result
refresh_team_capsA
Refresh and return the cached team capability flags for the current session. Fetches the team record, sanitizes the capability fields used for policy checks, updates the cache and returns the sanitized team_caps object. Does not write to eLabFTW resources.
Inputs: no named parameters. Returns: result
resolve_entity_by_queryA
Search experiments or items by query and return compact candidates for disambiguation. Read-only, always available. Returns status, entity_type, resolution (not_found | unique | ambiguous) and the candidate list — use it to find an id before acting on it.
Inputs: entity_type (required), q, tags, scope, +6 more. Returns: result
review_experimentA
Generate an AI review summary for an eLabFTW experiment (documentation quality, risks, suggestions, completeness score). Read-only — writes nothing. Needs an AI endpoint; without one the response carries the upstream placeholder marker.
Inputs: id (required). Returns: result
suggest_metadataA
Suggest structured metadata (custom fields) for an eLabFTW experiment or item. Read-only — writes nothing; feed the result into update_entity_metadata to persist it.
Inputs: id (required), entity_type (required). Returns: result
suggest_tagsA
Suggest tags for an eLabFTW experiment or item based on its content. Read-only — writes nothing; use apply_tag_suggestions to persist them.
Inputs: id (required), entity_type (required). Returns: result
toggle_stepA
Mark a step as complete or incomplete. WRITE OPERATION — requires the step write scope. finished=1 marks it complete, finished=0 incomplete; when omitted the server-side finish action flips the current state.
Inputs: id (required), step_id (required), entity_type (required), finished. Returns: result
update_entity_fieldsA
Update top-level scalar fields (title, status, category) on an existing eLabFTW experiment or item. WRITE OPERATION — requires the update write scope. At least one of title, status_id, category_id must be given. Returns the applied fields and a read-back verification.
Inputs: id (required), entity_type (required), title, trace_id, +2 more. Returns: result
update_entity_metadataA
Update the metadata (custom fields) of an eLabFTW experiment, item, experiment template or item type. WRITE OPERATION — requires the update write scope. 'fields' is a JSON string with an array of field objects ({key, value | values, type, group_id, allow_multi_values, description}); 'groups' optionally defines extra field groups ([{id, name}]). mode='merge' (default) keeps the existing metadata, 'overwrite' rebuilds it from the supplied fields. Returns the resulting field names and a read-back verification.
Inputs: id (required), fields (required), entity_type (required), mode, +2 more. Returns: result
update_experiment_bodyA
Update the body of an existing eLabFTW experiment. WRITE OPERATION — requires the update write scope. The existing content_type (HTML=1 / Markdown=2) is preserved. mode='append' (default) keeps the original body byte-for-byte and appends the new text; mode='overwrite' replaces it and needs both the config flag allow_body_overwrite and confirm_overwrite=true. The result reports a read-back verification of the change.
Inputs: id (required), text (required), mode, trace_id, +1 more. Returns: result
update_item_bodyA
Update the body of an existing eLabFTW database item. WRITE OPERATION — requires the update write scope. mode='append' (default) preserves the original body and appends; mode='overwrite' replaces it and needs allow_body_overwrite plus confirm_overwrite=true.
Inputs: id (required), text (required), mode, trace_id, +1 more. Returns: result
update_stepA
Update an existing step: body and/or deadline. WRITE OPERATION — requires the step write scope. At least one of body, deadline, deadline_notif must be provided; deadline_notif is reported as unsupported when the API rejects it (see notes).
Inputs: id (required), step_id (required), entity_type (required), body, +2 more. Returns: result
upload_image_from_contentA
Upload a single image attachment to an eLabFTW experiment or item from base64-encoded content. WRITE OPERATION — requires the update write scope. Only image/png, image/jpeg, image/gif and image/tiff are accepted. Returns the new upload id and size.
Inputs: id (required), filename (required), entity_type (required), content_base64 (required), +2 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 41 tools
Most tools target a distinct resource+action and descriptions clarify boundaries well. However, the link family (ensure_link, ensure_link_by_query, bulk_ensure_links, delete_link, bulk_delete_links) and the info family (get_connection_info, get_current_user_capabilities, refresh_team_caps) have overlapping purposes that could cause misselection, and resolve_entity_by_query overlaps with the list_* searches.
The dominant pattern is consistent verb_noun snake_case (get_item, list_experiments, create_experiment, update_step, delete_link, bulk_ensure_links). A few names deviate slightly (review_experiment, refresh_team_caps, expand_links_network) but remain readable and predictable.
41 tools is heavy for a single MCP server and pushes well past the comfortable 3-15 range. While the eLabFTW domain is broad, the count is high enough to burden tool selection and discovery.
Strong read/link/step/update coverage, but notable lifecycle gaps: no delete for experiments, items, uploads, templates, or metadata. Read+create+update are covered while destructive/cleanup operations are largely absent, and there is no direct tag-list or item-type management.
Maintenance
Related MCP Connectors
Turn any LLM into your lab assistant: search samples, track experiments, analyze data with AI.
Read and update your Vara lab experiments, runs, daily plans, references and materials.
Read and update your Vara lab experiments, runs, daily plans, references and materials.
AI-powered bioprotocol optimization — generate, search, and manage lab protocols via MCP
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceConnects AI assistants to LabArchives electronic lab notebooks, enabling querying, semantic search, page navigation, and file uploads with provenance tracking.6MIT
- AlicenseAqualityDmaintenanceConnects to wetlab protocol resources such as protocols.io, enabling users to access and manage experimental protocols via natural language.22Apache 2.0

Elnora MCP Serverofficial
AlicenseNot gradedqualityBmaintenanceConnects AI agents to the Elnora bioprotocol optimization platform, enabling generation, management, and optimization of wet-lab protocols through natural language.302 npm3Apache 2.0- AlicenseNot gradedqualityDmaintenanceEnables natural language interaction with the Labguru laboratory management system, providing 63 tools across experiments, protocols, inventory, and more.MIT