loading…
Search for a command to run...
loading…
Connects AI assistants to LabArchives electronic lab notebooks, enabling querying, semantic search, page navigation, and file uploads with provenance tracking.
Connects AI assistants to LabArchives electronic lab notebooks, enabling querying, semantic search, page navigation, and file uploads with provenance tracking.
Tests License: MIT Python 3.11+ DOI codecov
A Model Context Protocol (MCP) server that connects AI assistants to LabArchives electronic lab notebooks (ELN). MCP is an open protocol that lets AI clients call external tools and data sources through structured requests. This server lets researchers query notebook records, search notebooks semantically, and integrate lab work into AI-assisted workflows.
Key Features:
onboard() returns a structured lab overview, sticky context, and usage guidancedocs/QUICKSTART.mddocs/agent_configuration.mddocs/onboard_example.jsondocs/QUICKSTART.md and docs/agent_configuration.mddocs/upload_api.mddocs/graph_concepts.mddocs/linked_data.mddocs/semantic_governance.mdImplemented
🚧 Experimental
🔮 Future

Figure: An MCP client calls the FastMCP server tools, which mediate LabArchives API access, local project-state updates, embedding requests, and vector-index reads and writes. Mermaid source and SVG are committed alongside the PNG under docs/figures/.
Language: Python.
Modules:
auth.py – credential loading, HMAC signing, UID resolution.eln_client.py – notebook navigation, page entry retrieval, upload orchestration.models/upload.py – Pydantic contracts for uploads and provenance metadata.
state/ – persistent project contexts, graph construction, related-page heuristics, and next-step suggestions.
linked_data/ – JSON-LD export for legacy and enriched provenance graphs.
transform.py – XML→JSON transforms and API fault translation.
mcp_server.py – MCP server startup and FastMCP bootstrap.
tools/ – MCP tool handlers, shared decorators, and registry wiring.
vector_backend/ – semantic search indexing with Pinecone and local Parquet persistence; Qdrant is a placeholder extension point.
Project contexts can now be exported as JSON-LD using PROV-O for provenance semantics and schema.org for descriptive metadata.
labarchives-mcp export-provenance --project <project-id> --output graph.jsonld
python -m labarchives_mcp export-provenance --project <project-id> --output graph.jsonld
labarchives-mcp export-provenance --project <project-id> --output graph.ttl --format turtle
Install pip install -e ".[linked-data]" to enable Turtle and N-Quads CLI output. The MCP tool remains export_provenance_jsonld(project_id). See docs/linked_data.md for the vocabulary mapping and supported graph shapes.
git clone https://github.com/SamuelBrudner/lab_archives_mcp.git
cd lab_archives_mcp
Create the pinned Conda environment (local prefix):
conda-lock install --prefix ./conda_envs/labarchives-mcp-pol conda-lock.yml
Activate it:
conda activate ./conda_envs/labarchives-mcp-pol
Install git hooks and tooling:
pre-commit install
pre-commit install --hook-type commit-msg
Copy the secrets template:
cp conf/secrets.example.yml conf/secrets.yml
Contact LabArchives support to request API access credentials. You'll need:
akid)https://api.labarchives.com)Edit conf/secrets.yml and add your credentials:
LABARCHIVES_AKID: 'your_access_key_id'
LABARCHIVES_PASSWORD: 'your_api_password'
LABARCHIVES_REGION: 'https://api.labarchives.com'
OPENAI_API_KEY: 'sk-...'
PINECONE_API_KEY: 'your_pinecone_key'
PINECONE_ENVIRONMENT: 'us-east-1'
The LabArchives API requires a user-specific ID (uid) for all operations. You have two options:
Log into your LabArchives notebook in the web browser
Navigate to Account Settings → Password Token for External Applications
Click to generate a temporary token (valid for 1 hour)
Copy the email and password token displayed
Run the helper script:
conda run -p ./conda_envs/labarchives-mcp-pol python scripts/resolve_uid.py redeem \
--email [email protected] \
--auth-code <paste_token_here>
The script will print your UID. Copy it into conf/secrets.yml:
LABARCHIVES_UID: 'your_uid_value'
If you prefer the browser flow (or temporary tokens aren't working):
Generate a login URL:
conda run -p ./conda_envs/labarchives-mcp-pol python scripts/resolve_uid.py login-url
Open the URL in your browser and complete the LabArchives sign-in
After redirect, extract the auth_code from the URL
Run the redeem command (same as Option A step 5)
Note: The UID is permanent for your account—once obtained, store it in conf/secrets.yml and you won't need to retrieve it again unless your LabArchives password changes.
Test that everything works:
conda run -p ./conda_envs/labarchives-mcp-pol python -c "
from labarchives_mcp.auth import Credentials
from labarchives_mcp.eln_client import LabArchivesClient, AuthenticationManager
import httpx, asyncio
async def test():
creds = Credentials.from_file()
async with httpx.AsyncClient() as client:
auth = AuthenticationManager(client, creds)
uid = await auth.ensure_uid()
print(f'✓ UID verified: {uid}')
notebooks = await LabArchivesClient(client, auth).list_notebooks(uid)
print(f'✓ Retrieved {len(notebooks)} notebooks')
asyncio.run(test())
"
LabArchives account and API access
Optional external vector stores
On-premise and open backends
NotImplementedError for QdrantIndex and LocalEmbedding; deployments that need those paths must implement and review the corresponding backend before use.Intended usage
lab_archives_mcp is designed as an integration layer between ELNs and AI assistants, not as a general-purpose ELN or standalone LIMS.Reproducible environments
Provenance-aware uploads
git_commit_sha, git_branch, git_repo_url, git_is_dirtycode_version tagexecuted_at timestamppython_version and key dependenciesStructured schemas
Data and code versioning
pytest --cov=src --cov-report=xml --cov-report=term, writes the measured line coverage from coverage.xml to the job summary, and attempts a non-blocking Codecov upload on the Ubuntu/Python 3.11 job.rdflib-gated linked-data round-trip case), six deselected integration tests, and 81% total line coverage. Use the Codecov badge for the current main-branch value.~/.labarchives_state/session_state.json by default so assistants can resume work across sessions and repositories. Projects are required—create one before logging visits or findings.get_related_pages) and provenance tracing (trace_provenance).create_project, list_projects, switch_project, delete_project, inspect them with get_current_context, and log observations via log_finding. State is only persisted when a project is active.suggest_next_steps provides cold start detection and activity stats (not prescriptive workflow phases).get_onboard_payload (or labarchives-mcp --print-onboard) returns usage guidance and sticky context.labarchives-mcp --print-onboard json (or markdown) to capture:search_labarchives, list_notebook_pages, read_notebook_page)sticky_context in your agent memory before invoking other tools.This transcript was captured by running the registered write_notebook_entry MCP tool in a local smoke harness with LabArchives network calls replaced by test doubles:
{
"call": {
"tool": "write_notebook_entry",
"arguments": {
"notebook_id": "NBID-demo",
"page_title": "QC note",
"content": "# QC note\n\nCaptured from local revision test.",
"content_format": "markdown"
}
},
"response": {
"page_tree_id": "page-42",
"entry_id": "entry-7",
"page_url": "https://mynotebook.labarchives.com/share/NBID-demo/page-42",
"created_at": "2026-05-24T21:45:00+00:00",
"part_type": "text entry"
}
}
Live LabArchives runs use the same tool call and return platform-generated page and entry identifiers.
The server can be started in several ways:
# Method 1: As a Python module
conda run -p ./conda_envs/labarchives-mcp-pol python -m labarchives_mcp
# Method 2: Using the console script (after pip install -e .[dev])
conda run -p ./conda_envs/labarchives-mcp-pol labarchives-mcp
# Method 3: Direct Python (if environment is activated)
conda activate ./conda_envs/labarchives-mcp-pol
labarchives-mcp
The server runs in stdio mode and waits for MCP protocol messages. Press Ctrl+C to stop.
The MCP server exposes LabArchives notebooks to AI agents via the MCP protocol.
For the most up-to-date configuration examples (Claude Desktop, Windsurf, generic MCP clients), see docs/agent_configuration.md. The snippets below are included for convenience.
For configuration examples for Windsurf and Claude Desktop (including environment variables and restart steps), see docs/agent_configuration.md.
Discovery:
list_labarchives_notebooks() - List all your notebookslist_notebook_pages(notebook_id) - Show table of contents for a notebookReading:
read_notebook_page(notebook_id, page_id, track_visit=True, dry_run=False) - Read content from a specific page and optionally record the visit in the active projectSearch & index:
search_labarchives(query, limit=5) - Search indexed LabArchives notebooks semanticallysync_vector_index(force=False, dry_run=False, max_age_hours=None, notebook_id=None) - Plan or run a vector-index syncProject state & graph:
create_project(name, description, linked_notebook_ids=None, dry_run=False) - Start and activate a project workspacelist_projects(), switch_project(project_id, dry_run=False), delete_project(project_id, dry_run=False) - Manage project contextslog_finding(content, source_url=None, page_id=None, dry_run=False) - Append a finding to the active project; page_id links the finding back to evidenceget_current_context() - Return full project state (pages, findings, graph)get_related_pages(notebook_id, page_id, limit=20, offset=0) - Find sibling/linked pages via the project graph and detected LabArchives links; returns items plus pagination metatrace_provenance(notebook_id, page_id, entry_id) - Trace sources and metadata for a specific entrysuggest_next_steps() - Get lightweight guidance based on your current project state (cold start vs active)export_provenance_jsonld(project_id) - Export a project graph as JSON-LDWrite & provenance:
write_notebook_entry(...) - Write a rich text entry directly to a LabArchives page, appending to an existing page or creating a page when page_id is omittedupload_to_labarchives(...) - Upload a file to LabArchives with code provenance metadata, creating a page and storing the file as page text or an attachmentThe support tool get_onboard_payload(format="json") returns usage guidance and sticky context for MCP clients.
sync_vector_index(force=False, dry_run=False, max_age_hours=None, notebook_id=None)conf/vector_search/default.yamlincremental_updates.last_indexed_fileskip when config + embedding version match and the build is recentincremental when the build is older than max_age_hours (only changed entries)rebuild when embedding version or config fingerprint changed, or force=Truedry_run=True to return the plan without any side effectsdocs/vector_backend.md under “Build Records” and “MCP Sync”.list_labarchives_notebooks()
# Returns list of notebooks:
[{
"nbid": "MTU2MTI4NS43...", # Notebook ID
"name": "Mosquito Navigation",
"owner": "[email protected]",
"owner_name": "Example Owner",
"created_at": "1970-01-01T00:00:00Z",
"modified_at": "1970-01-01T00:00:00Z"
}]
list_notebook_pages(notebook_id, folder_id=None)
# Returns pages and folders:
[{
"tree_id": "12345",
"title": "Introduction",
"is_page": true, # Can contain entries
"is_folder": false
}, {
"tree_id": "67890",
"title": "Methods",
"is_page": false,
"is_folder": true # Contains sub-pages - use tree_id as folder_id to navigate
}]
# Navigate into a folder by passing its tree_id as folder_id:
list_notebook_pages(notebook_id, folder_id="67890")
read_notebook_page(notebook_id, page_id)
# Returns page content:
{
"notebook_id": "MTU2MTI4NS43...",
"page_id": "12345",
"entries": [{
"eid": "e789",
"part_type": "text_entry", # or "heading", "plain_text", "attachment"
"content": "<p>Entry text content...</p>",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-02T08:30:00Z"
}],
"tracked_in_project": "Mosquito Navigation Review",
"tracked": true,
"dry_run": false
}
write_notebook_entry(...)
# Write Markdown directly to a page (creates a new page if page_id is omitted)
write_notebook_entry(
notebook_id="MTU2MTI4NS43...",
page_title="Protocol - 2025-10-02",
content="# Protocol\n\n- Step 1\n- Step 2",
content_format="markdown",
parent_folder_id="67890" # optional folder tree_id
)
# Append HTML to an existing page
write_notebook_entry(
notebook_id="MTU2MTI4NS43...",
page_id="12345",
content="<h2>Summary</h2><p>Results look promising.</p>",
content_format="html"
)
upload_to_labarchives(...) ⭐ NEW
See docs/upload_api.md for the complete API documentation and usage notes.
# Upload a file with code provenance metadata
# MANDATORY parameters:
upload_to_labarchives(
notebook_id="MTU2MTI4NS43...",
page_title="Analysis - 2025-09-30",
file_path="/path/to/analysis.ipynb",
git_commit_sha="a1b2c3d4...", # Full 40-char SHA
git_branch="main",
git_repo_url="https://github.com/user/repo",
python_version="3.11.8",
executed_at="2025-09-30T12:00:00Z",
dependencies={"numpy": "1.26.0", "pandas": "2.1.0"},
as_page_text=True # default: store contents as page text (set False for attachments)
)
# Returns:
{
"page_tree_id": "NEW_PAGE_ID",
"entry_id": "ENTRY_ID", # text entry or attachment EID
"page_url": "https://mynotebook.labarchives.com/...",
"created_at": "2025-09-30T12:00:00Z",
"file_size_bytes": 12345,
"filename": "analysis.ipynb"
}
```json
### Library Usage (Python)
```python
import asyncio
from datetime import datetime, UTC
from pathlib import Path
import httpx
from labarchives_mcp.auth import Credentials, AuthenticationManager
from labarchives_mcp.eln_client import LabArchivesClient
from labarchives_mcp.models.upload import UploadRequest, ProvenanceMetadata
async def main():
creds = Credentials.from_file() # reads conf/secrets.yml
async with httpx.AsyncClient(base_url=str(creds.region)) as http_client:
auth = AuthenticationManager(http_client, creds)
client = LabArchivesClient(http_client, auth)
uid = await auth.ensure_uid()
# A) Render Markdown → HTML as page text (recommended for .md)
md_req = UploadRequest(
notebook_id="NBID...",
page_title="Protocol - 2025-10-02",
file_path=Path("protocol.md"),
metadata=ProvenanceMetadata(
git_commit_sha="a" * 40,
git_branch="main",
git_repo_url="https://github.com/user/repo",
git_is_dirty=False,
executed_at=datetime.now(UTC),
python_version="3.11.8",
dependencies={"numpy": "1.26.0"},
os_name="Darwin",
hostname=None,
),
create_as_text=True,
)
result = await client.upload_to_labarchives(uid, md_req)
print("Page:", result.page_url)
# B) Keep file as attachment (e.g., .ipynb)
nb_req = UploadRequest(
notebook_id="NBID...",
page_title="Analysis - 2025-10-02",
file_path=Path("analysis.ipynb"),
metadata=md_req.metadata,
create_as_text=False,
)
result = await client.upload_to_labarchives(uid, nb_req)
print("Page:", result.page_url)
asyncio.run(main())
```python
> **🔒 Security Note**: The upload tool is enabled by default. For production deployments or shared environments, **disable write capabilities** by setting `LABARCHIVES_ENABLE_UPLOAD=false` in the environment configuration above. This prevents AI assistants from unintentionally modifying your research records.
#### Example Scope
The captured transcript above is the repository-maintained tool example. Notebook names, page IDs, and entry IDs are institution-specific, so conversational examples with invented notebook contents are intentionally omitted.
---
## Troubleshooting
### "The supplied signature parameter was invalid" (Error 4520)
This means the API signature computation failed. Common causes:
- Wrong `LABARCHIVES_PASSWORD` in secrets file
- Incorrect method name (should omit class prefix, e.g., `user_access_info` not `users:user_access_info`)
- Clock skew between your machine and LabArchives servers
### "404 Not Found" on API endpoints
- Verify `LABARCHIVES_REGION` is correct for your institution
- Confirm with LabArchives support that your access key has the required API methods enabled
- Check that you're using `/api/` paths (not `/apiv1/` or `/api/v1/`)
### Cannot generate or redeem UID
- Ensure you have API access credentials from LabArchives support
- Try the temporary password token method (Option A) first
- If browser-based flow fails with 404, contact LabArchives to enable callback URLs for your key
### MCP Server Won't Start
- Verify `LABARCHIVES_UID` is set in `conf/secrets.yml`
- Run the verification script (step 5 of setup) to test credentials
- Inspect stderr output for Loguru messages, or configure a Loguru file sink if you need persistent logs
---
## Usage (Example)
### List notebooks
```json
{
"resource": "labarchives:notebooks",
"list": [
{
"nbid": "12345",
"name": "Fly Behavior Study",
"owner": "[email protected]",
"owner_email": "[email protected]",
"owner_name": "Example Owner",
"created_at": "2025-01-01T12:00:00Z",
"modified_at": "2025-01-02T08:30:00Z"
}
]
}
## API Schema
The API contract is defined by **Pydantic models** (single source of truth):
- **Configuration**: `Credentials` in `src/labarchives_mcp/auth.py`
- **Resources**: `NotebookRecord` in `src/labarchives_mcp/eln_client.py`
Generate JSON Schema for API documentation:
```bash
# Notebook resource schema
conda run -p ./conda_envs/labarchives-mcp-pol python -c "
from labarchives_mcp.eln_client import NotebookRecord
import json
print(json.dumps(NotebookRecord.model_json_schema(), indent=2))
"
# Configuration schema
conda run -p ./conda_envs/labarchives-mcp-pol python -c "
from labarchives_mcp.auth import Credentials
import json
print(json.dumps(Credentials.model_json_schema(), indent=2))
"
All field descriptions, examples, and validation rules are in the Pydantic models. No separate YAML/JSON schema files—code is the source of truth.
httpx.AsyncClient; add external throttling if your deployment demands rate limits beyond the platform defaults.epoch_time or api_base_urls; extend AuthenticationManager if you need those optimisations.See CONTRIBUTING.md for versioning, Conventional Commits, and release steps.
Use this citation policy consistently:
v0.5.1, version DOI https://doi.org/10.5281/zenodo.20421457, Git tag https://github.com/SamuelBrudner/lab_archives_mcp/releases/tag/v0.5.1v0.3.2, version DOI https://doi.org/10.5281/zenodo.17772522The DOI badge, CITATION.cff primary identifier, and codemeta.json
identifier use the concept DOI. The README BibTeX and CITATION.cff
preferred citation point to the v0.5.1 resubmission archive; the BibTeX
doi field will be updated to the version-specific DOI after Zenodo
deposit. The v0.5.0 and v0.3.2 DOIs are retained for historical traceability only.
We welcome contributions! See CONTRIBUTING.md for:
If you use this software in your research, please cite:
@software{brudner2026labarchives,
author = {Brudner, Samuel N.},
title = {LabArchives MCP Server: AI Integration for Electronic Lab Notebooks},
year = {2026},
doi = {10.5281/zenodo.20421457},
url = {https://doi.org/10.5281/zenodo.20421457},
version = {0.5.1}
}
This project is licensed under the MIT License - see the LICENSE file for details.
Run in your terminal:
claude mcp add labarchives-mcp-server -- npx Security
Low riskAutomated heuristic from public metadata — not a security guarantee.