Project layout, the invoke file, and how your server actually runs
Contents
Repository layout
middleware/invokestart_mcpbehaviorPublishing and revisions
Repository layout of a nanoHUB MCP tool
A nanoHUB MCP tool is a regular nanoHUB tool. Real examples: padremcp
(minimal) and rappturemcp (large).
yourtool/
├── bin/
│ ├── yourtool.py # THE server — defines module-level `server`
│ └── test_offline.py # tests that need no solver/session (run anywhere)
├── middleware/
│ └── invoke # how the hub launches the tool (see below)
├── scripts/
│ ├── validate_server.py # copied by the scaffold
│ └── check_ci.py # fail CI on skipped/empty suites
├── .github/workflows/ci.yml # offline tests + validator on push/PR
├── doc/
│ └── description.html # the tool's page on nanohub.org
├── examples/ # example inputs / notebooks
├── data/ # static data the tools read
├── src/ # your scientific library code, or a Makefile
└── README.md
Rules of thumb:
Everything the server imports at runtime must be importable inside the tool session — either in
bin/next to the server file (start_mcpadds that directory tosys.path), installed in the conda env named by the invoke file, or an installed hub app.Keep the server in ONE file if you can. Deployment, debugging, and the
@tool/bin/...invoke reference all get simpler.Offline tests belong in the repo and must not require the solver binary or a hub session (test deck generation, schemas, tool registration — see
scripts/in this skill).Keep CI offline. Do not run
smoke_live.shautomatically: DCR and deployed solver calls may mutate production state and require explicit credentials.
The middleware/invoke file, flag by flag
Minimal (padremcp):
#!/bin/sh
/usr/bin/invoke_app "$@" -t padremcp \
-C "start_mcp --app @tool/bin/padremcp.py" \
-u anaconda-6 \
-u padre-2.4E-r15 \
-r none \
-w headless
Flag |
Meaning |
|---|---|
|
tool name — must match the published tool (and the gateway URL |
|
the command to run; |
|
environment module(s) to load — repeatable; load your conda distribution AND your solver (e.g. |
|
no Rappture wrapper |
|
no VNC desktop — an MCP server has no GUI; sessions are cheaper and start faster |
|
extra environment variables (e.g. |
The env-pinning gotcha (will bite you): a bare start_mcp resolves
against whatever the loaded anaconda base provides. If your dependencies
live in a specific conda env, pin the absolute path:
-C "/apps/share64/debian7/anaconda/anaconda-6/envs/yourenv/bin/start_mcp --app @tool/bin/yourtool.py"
Symptom of getting this wrong: the server starts and lists tools fine, but
calls that import your library fail with No module named '...' — often
surfacing only inside resources/read.
What start_mcp does (so you can debug it)
start_mcp (entry point of the nanohub-mcp package, nanohubmcp/cli.py):
Imports your
--appfile and takes its module-levelservervariable (must be anMCPServerinstance — this name is the contract).On nanoHUB (detected via
SESSION/SESSIONDIRenv): reads the sessionresourcesfile, computes the proxy path/weber/{session}/{cookie}/{port%1000}/, runs the MCP server on port 8001 and thewrwroxyreverse proxy on port 8000. The gateway reaches the server through that weber path — this is why resource URIs sometimes arrive with a proxied prefix (the framework strips it).Locally (no session env): serves directly on
--port(default 8000).--python-env NAMEre-executes under a named conda env’s python — an alternative to hardcoding the env path in the invoke line.
Local development loop:
start_mcp --app bin/yourtool.py --port 8000
curl -s localhost:8000 -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | python3 -m json.tool
This is byte-for-byte the server that runs on the hub; anything you can’t make work locally will not work deployed.
Publishing on the hub
Create the tool at
nanohub.org/tools/create; pick a short lowercase name (it becomes the URL and the-tflag).Commit the layout above to the tool repo; drive the tool status pipeline (Uploaded → Installed → Approved → Published). Headless tools skip the screenshot/GUI review steps’ complications.
Test the installed revision in a workspace/session before publishing:
invokeshould leave a running server you can curl through the session.Ask the hub team to register the tool with com_mcp (the MCP gateway component). After that,
https://{hub}/api/mcp/{yourtool}/mcpis live and gated by com_mcp’sallowed_groupsconfiguration.Re-verify through the gateway with the smoke tests (verification.md /
scripts/smoke_live.sh) — the session-side server working does not guarantee the gateway path does.
Updating: publish a new tool revision; the gateway picks a fresh session. Old sessions may keep running the previous revision — kill stale sessions when testing an update, or you will debug the wrong code.