Personal MCP Hub
by Aravind-MJ
README.md
# Personal MCP Hub
A Node.js personal MCP service hosting multiple MCP modules under one domain. It currently provides authenticated HTML artifacts and credential-free questionnaire management with signed public answer links.
## Route layout
| Route | Access | Purpose |
|---|---|---|
| `POST /artifact/mcp` | Shared bearer secret | Stateless Streamable HTTP MCP endpoint |
| `GET /artifact/<24-char-id>` | Signed URL or Basic Auth | Render the latest artifact version |
| `GET /artifact/<id>/versions/<n>` | Signed URL or Basic Auth | Render one immutable version snapshot |
| `GET /artifact` | Public | Non-indexed module landing page |
| `GET /artifacts` | Caddy Basic Auth | Private gallery with confirmed deletion controls |
| `POST /artifacts/<id>/delete` | Caddy Basic Auth + CSRF token | Permanently delete from the gallery |
| `GET /artifact/README.md` | Public | Secret-free cross-harness installation guide |
| `GET /artifact/SKILL.md` | Public | Companion agent skill with collision routing |
| `POST /questionnaire/mcp` | Public (no auth) | Stateless Streamable HTTP questionnaire management endpoint |
| `GET /questionnaire/<id>/r/<revision>?expires=...&signature=...` | Signed URL | Render one exact questionnaire revision |
| `POST /questionnaire/<id>/r/<revision>/responses` | Signed URL | Create one anonymous draft |
| `GET/PATCH /questionnaire/<id>/r/<revision>/responses/<response-id>` | Signed URL + edit token | Resume or autosave a draft |
| `POST /questionnaire/<id>/r/<revision>/responses/<response-id>/submit` | Signed URL + edit token | Validate, identify, and finalize one response |
| `GET /questionnaire` | Public | Non-indexed module landing page |
| `GET /questionnaire/README.md` | Public | Secret-free installation and schema guide |
| `GET /questionnaire/SKILL.md` | Public | Companion agent skill |
| `GET /questionnaires` | Caddy Basic Auth | Private questionnaire index with links, status, and response counts |
| `GET /questionnaires/<id>/responses[/<response-id>]` | Caddy Basic Auth | Inspect collected response metadata and answers |
| `POST /questionnaires/<id>/{sign,status,delete}` | Caddy Basic Auth + action-scoped CSRF token | Mint links, open/close, or permanently delete from the index |
| `GET /logo.svg` | Public | Full MCP brand mark |
| `GET /favicon.svg` | Public | Browser/favicon brand mark |
| `GET /healthz` | Public | Health probe |
Future MCPs should use sibling namespaces such as `/<module>/mcp`; see `docs/ADDING_MODULES.md`.
Public installation URLs:
- `https://mcp.aravindmj.in/artifact/README.md`
- `https://mcp.aravindmj.in/artifact/SKILL.md`
- `https://mcp.aravindmj.in/questionnaire/README.md`
- `https://mcp.aravindmj.in/questionnaire/SKILL.md`
Use the local MCP name `aravind_html_publisher` in other harnesses. The companion skill distinguishes this externally hosted publisher from Claude's built-in Artifacts feature: public/shareable-link requests use this MCP; Claude-native in-chat canvas requests use the built-in feature.
Gallery deletion is intentionally two-step: hover a card and press the top-right trash button, then confirm in the modal. Touch devices show the button persistently. The form carries an HMAC confirmation token and the entire `/artifacts/*` namespace remains behind Caddy Basic Auth.
The private questionnaire index is available at `https://mcp.aravindmj.in/questionnaires` behind the same Caddy Basic Auth. It supports copying fresh one-week links, opening or closing collection, browsing human-readable responses, deleting individual responses, and confirmed questionnaire deletion. Node accepts these routes only when Caddy injects its narrow trusted-header marker; each state-changing form also requires a domain-separated HMAC CSRF token.
Successful deletion uses a signed, short-lived, HTTP-only flash cookie. The gallery consumes and clears it on the first render, so the success message is absent on reload and never appears in the URL.
## Artifact tools
- `publish_html(html, title?)` — publishes HTML and returns one-week signed latest/version URLs plus SHA-256 metadata.
- `update_artifact(artifact_id, html, title?)` — creates a new version while preserving artifact identity and returns fresh signed URLs.
- `get_signed_url(artifact_id, expires_in_seconds?, version?)` — creates a fresh expiring share URL; defaults to one week.
- `list_artifacts(limit?)` — lists recent private metadata; HTML source is omitted.
- `delete_artifact(artifact_id)` — permanently removes one artifact.
## Questionnaire tools
- `create_questionnaire` — creates revision 1 and returns a one-week signed answer URL.
- `update_questionnaire` — creates a new immutable revision while preserving prior links and responses.
- `get_questionnaire` / `list_questionnaires` — retrieves definitions, status, and response counts.
- `get_questionnaire_signed_url` — mints an exact-revision link for 60 seconds through one year.
- `submit_questionnaire_response` — atomically submits complete answers with respondent name and email through MCP.
- `set_questionnaire_status` — opens or closes response collection.
- `delete_questionnaire` — removes every revision and response.
- `list_questionnaire_responses` — lists bounded response metadata; `get_questionnaire_response` retrieves one answer body by ID.
- `delete_questionnaire_response` — permanently removes one response.
Supported types: short/long text, email, URL, phone, number, date, time, date-time, single/multiple choice, dropdown, yes/no, consent, rating, scale, ranking, and matrix. The answering UI is responsive, keyboard accessible, progress-aware, dark-mode aware, and autosaves incomplete anonymous drafts before strict final validation and respondent attribution.
## Security model
- Node binds only to `127.0.0.1:4330`; Caddy is the only public ingress.
- Artifact MCP calls require the common bearer secret. Questionnaire MCP calls intentionally require no authentication.
- The shared secret is stored outside the repository at `/data/mcp-hub/secrets/shared-secret`, mode `0600`; its directory is `0700`.
- The secret is read on every MCP request, allowing atomic rotation without restarting Node.
- Secret comparison uses Node's constant-time `crypto.timingSafeEqual`.
- Artifact IDs use 24 uniformly random base62 characters (about 143 bits) with no punctuation.
- Artifact and version pages require either a valid HMAC-signed URL or the gallery's HTTP Basic Auth. Signed URLs default to seven days and may be requested for 60 seconds through one year.
- Artifact responses include `X-Robots-Tag: noindex, nofollow, noarchive, nosnippet, noimageindex`.
- HTML and metadata use atomic writes and mode `0600`.
- Latest and version-specific HTML are served with CSP sandboxing, `nosniff`, no-referrer, and a restrictive Permissions Policy. Version URLs are immutable; stable artifact URLs revalidate to the latest version.
- Default HTML limit: 2 MiB.
- Questionnaire IDs and response IDs are 24 uniformly random base62 characters. Signed links bind the questionnaire ID, exact revision, and expiry.
- Questionnaire definitions, revisions, answers, and submitted respondent names/emails are stored in `/data/mcp-hub/data/questionnaire/questionnaires.sqlite3` using foreign keys, WAL mode, full synchronization, and private filesystem permissions.
- Storage is bounded by default to 100 immutable revisions and 10,000 response rows per questionnaire, plus 256 KiB of serialized answers per response. Operators may adjust these with `QUESTIONNAIRE_MAX_REVISIONS`, `QUESTIONNAIRE_MAX_RESPONSES`, and `QUESTIONNAIRE_MAX_ANSWER_BYTES`.
- Draft edit tokens are returned only to the answering browser, isolated to the current tab through session storage, and persisted server-side only as SHA-256 hashes. Management tools never return them. Response versions prevent a stale duplicated tab from silently overwriting newer answers.
- All questionnaire pages and response APIs require an unexpired signed URL. Response resume/autosave/submit also requires the draft edit token. Cross-site unsafe requests are rejected.
Anyone who has an artifact URL can view it. Never publish secrets or private data in an artifact.
## Local development
Requires Node.js 22+.
```bash
npm install
npm test
MCP_HUB_RUNTIME_DIR="$PWD/.runtime" npm run rotate-secret
MCP_HUB_RUNTIME_DIR="$PWD/.runtime" MCP_HUB_PUBLIC_BASE_URL=http://127.0.0.1:4330 npm start
```
Do not put secrets in `.env` or shell startup files. For platform-managed environment overrides, use **hPanel → Hermes Agent → Dashboard → Environment**. The deployed service uses the protected secret file by default.
## Operations
```bash
npm test
MCP_HUB_RUNTIME_DIR=/data/mcp-hub node /data/mcp-hub/app/scripts/rotate-secret.js
curl -fsS http://127.0.0.1:4330/healthz
hermes mcp test aravind_html_publisher
hermes mcp test aravind_questionnaires
```
The questionnaire SQLite database is persistent runtime state, not release content. Back up `/data/mcp-hub/data/questionnaire/questionnaires.sqlite3` with SQLite's online backup mechanism while the service is running, or stop the service and copy the database together with any `-wal` and `-shm` sidecars. Never replace `/data/mcp-hub/data` during an application deployment.
### Ask an agent to update installed user-scope skills
The Node updater discovers existing user-scope copies across Hermes, Cursor, OpenCode, Claude Code, and Codex. It never creates a missing harness installation. Its generated prompt pins the downloaded skill by SHA-256 and forbids credential/config changes.
```bash
# Inspect detected installations and the exact agent prompt; no mutation
npm run update-harness-skills -- --dry-run
# Ask Cursor Agent to update every detected user-scope copy
npm run update-harness-skills -- --run --agent cursor
# Alternative agent runners
npm run update-harness-skills -- --run --agent opencode
npm run update-harness-skills -- --run --agent hermes
```
Cursor is the default runner. The script exits without invoking an agent when no existing user-scope copies are found.
The production rotation command atomically updates both the service secret and Hermes' protected `MCP_ARAVIND_HTML_PUBLISHER_API_KEY` credential without printing either value. It also removes the retired `MCP_ARTIFACT_API_KEY` entry. Start a new Hermes session after rotation so long-lived clients cannot retain the prior header.
A no-agent Hermes cron job runs `scripts/watchdog.sh` once per minute. The wrapper executes the Node watchdog; no Python runtime is used.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues