Skip to main content
Glama
johnnyclem

hypervault-mcp

by johnnyclem

hypervault-mcp

MCP server for HyperVault — lets any MCP-capable agent save artifacts to a user's vault and claim vanity subdomains.

Built with FastMCP.

Hosted endpoint: https://mcp.vault.cool/mcp (Streamable HTTP) — no install needed, just point your MCP client at it with your own API key. See Auth & rate limits for the header format.

Install & run

pip install -e .          # from this directory (or: uv pip install -e .)

export HYPERVAULT_API_KEY=hv_...            # create one in the web dashboard (/vault)
export HYPERVAULT_API_URL=https://hypervault.store   # optional; defaults to hypervault.store

hypervault-mcp                                # STDIO (local agents)
hypervault-mcp --transport http --port 8787   # HTTP (web agents)

Authentication differs by transport — see Auth & rate limits below.

Related MCP server: hashnet-mcp

Tools

Tool

What it does

save_to_hypervault(content, title, type, tags, connect_to, make_pwa, source_prompt, visibility, mutable)

Saves HTML or React/JSX and returns a permanent, installable URL. JSX is auto-detected and wrapped server-side. connect_to links the new artifact to existing ones (graph view edges); similar items are auto-connected too. Pass source_prompt to bake the originating prompt into the page as <meta name="hypervault-source-prompt"> so agents can iterate later. Re-saving identical content returns the existing URL (duplicate: true) instead of creating a copy. Pass mutable=True for a living document you can rewrite in place (see below).

claim_vanity_subdomain(desired_name, base_domain="vault.cool")

Claims name.vault.cool for the user, effective immediately. Pro accounts can hold up to 10 subdomains; the full vault lives on every one.

connect_vault_items(source, target)

Connects two existing artifacts (bidirectional, drawn in graph view).

list_my_vault_items()

Lists everything already in the vault.

read_artifact(ref, version=None)

Reads an artifact's current editable source (raw JSX for JSX artifacts, HTML otherwise) by slug or URL. Pass a version id to read a past iteration. Pair with write_artifact to iterate.

write_artifact(ref, content, title, message, force_html)

Writes a new iteration of a mutable artifact — a git commit on the living document. The page updates in place (URL unchanged) and the write is kept as a version. Immutable artifacts are refused.

artifact_history(ref, full, limit)

Lists a mutable artifact's version history (git commits), newest first, with authorship. Revert by reading an old version and writing it back.

extract_source_prompt(url)

Fetches any artifact URL (vanity domains included) and returns the source prompt from its hidden <meta name="hypervault-source-prompt"> tag, so you can iterate on the original idea.

delete_vault_item(slug_or_id)

Permanently deletes an artifact (and its graph connections). Irreversible — the share URL stops working immediately.

create_artifact_group(files, title, tags, connect_to, visibility, source_prompt)

Saves a multi-file artifact group — several .html/.css/.js/.jsx files that run together — behind a required root index.html. Returns a JSFiddle-style run/preview URL (see Artifact groups below).

read_artifact_group(ref)

Reads a group's full file set and metadata by slug or URL.

list_artifact_groups()

Lists everything already saved as a group.

add_artifact_group_item(ref, path, content)

Adds a new file to an existing group (fails if that path already exists).

edit_artifact_group_item(ref, path, content)

Replaces an existing file's content, including index.html itself (fails if that path doesn't exist yet).

remove_artifact_group_item(ref, path)

Removes a file from a group. The root index.html can't be removed this way.

delete_artifact_group(slug_or_id)

Permanently deletes a whole group. Irreversible.

memorize(content, title, tags, source)

Stores a chunk in the user's private memory wiki (Imaging V2). Auto-titled, auto-tagged, summarized, and linked to related memories in their knowledge graph.

recall(query)

Natural-language search over the wiki ("what did I say about the Rust borrow checker?"). Top matches return the exact stored content; every match lists its linked memories.

list_memories()

Browses everything memorized, newest first (summaries + tags).

forget_memory(memory_id)

Permanently deletes one memory — only on the user's explicit request.

create_task_board(title, project, tasks, stages, visibility)

Creates a universal task board — a shared, versioned task list — plus the interactive board page the user watches it on. Returns project (what every other task tool takes) and board.url (the human deliverable). See Universal task boards below.

list_task_boards()

Lists the user's existing boards, so you can join one instead of creating a duplicate.

tasklist_get(project, since_version=None)

Reads the full list. With since_version you get {unchanged: true, version} when nothing moved — the cheap poll.

tasklist_summary(project)

Rollup only: counts, progress, epics, who holds what. Prefer it over the full list for status reporting on a large board.

task_create(project, title, type, parent, description, priority, active_form, depends_on, metadata, expected_version, agent_name, agent_type)

Adds a task to an existing board.

task_update(project, task_id, status, progress, note, title, description, priority, active_form, parent, metadata, expected_version, agent_name, agent_type)

Patches one task; only the arguments you pass change. note appends to the task's thread, metadata merges key-wise.

task_claim(project, task_id, agent_name, agent_type, force, release, lock_minutes, expected_version)

Claims a task — lock + assign + in_progress — so several agents can share a board without colliding. release=True hands it back.

task_complete(project, task_id, note, expected_version, agent_name, agent_type)

Done, progress 100, lock released, in one call. The response includes the list summary.

Plus the hypervault://help resource with agent-facing usage notes.

Memories are owner-only: they power the Memory Control Panel at /vault/memory and are never rendered on public pages.

Mutable artifacts (a living document)

Artifacts are immutable by default: a save is permanent, and re-saving the same content just returns the existing link. Save with mutable=True to get a document you can iterate on in place — its URL never changes, and every write is kept as a git commit you can list and revert to:

saved = save_to_hypervault(content="<h1>v1</h1>", title="Notes", mutable=True)
read_artifact(saved["slug"])                       # -> current source + head version
write_artifact(saved["slug"], "<h1>v2</h1>", message="expand intro")
artifact_history(saved["slug"])                    # -> the commit chain, newest first

read_artifact → edit → write_artifact is the iteration loop; to revert, read an old version's content (read_artifact(ref, version=...)) and write it back. The write tools are owner-scoped (the API key resolves to its owner), so a mutable artifact is read and written privately even when the page is public.

Artifact groups (multi-file projects)

Use an artifact group instead of save_to_hypervault when a project needs more than one file — separate markup, styles, and script(s) that reference each other normally, like a tiny JSFiddle. A group always runs/previews as a container at https://hypervault.store/g/{slug} — a minimal editor/preview UI similar to JSFiddle — routed through a required root index.html.

group = create_artifact_group(
    files=[
        {"path": "index.html", "content": "<link rel='stylesheet' href='style.css'><script src='app.js'></script>"},
        {"path": "style.css", "content": "body { font-family: sans-serif; }"},
        {"path": "app.js", "content": "console.log('hello from the group')"},
    ],
    title="My Widget",
)
read_artifact_group(group["slug"])                                   # -> current files + metadata
add_artifact_group_item(group["slug"], "extra.js", "// more code")   # add a new file
edit_artifact_group_item(group["slug"], "style.css", "body { color: red; }")  # replace a file's content
remove_artifact_group_item(group["slug"], "extra.js")                # remove a file (not index.html)
list_artifact_groups()                                               # browse everything saved
delete_artifact_group(group["slug"])                                 # permanently delete the group

Validation runs locally before any network call, so bad input never reaches the backend:

  • Exactly one root file at path index.html — the entry point the run/preview container routes through. A nested one like public/index.html does not count.

  • Paths must be relative, use / as the separator, contain no .. segments, and only [A-Za-z0-9._/-] characters.

  • Extensions are limited to .html, .css, .js, .jsx.

  • At most 50 files; 256 KB per file; 1 MB total.

  • Paths must be unique (case-insensitively).

  • The root index.html can't be removed with remove_artifact_group_item — edit its content instead, or delete the whole group.

Universal task boards (shared work lists)

A task board is a shared, versioned task list that an agent and the user work from together. One call creates both halves: a JSON data artifact (tasks-{project}) the agent syncs through, and an interactive board page (taskboard-{project}) the user opens to watch and steer the work live. Every write is an artifact version (audit trail + rollback), writes are optimistic-concurrency-safe, and claims are locks, so several agents can share one board without collisions.

board = create_task_board(
    title="Eurorack choir firmware",
    tasks=[
        {"id": "epic-1", "title": "Firmware", "type": "epic"},
        {"title": "Bring up I2S clocking", "parent": "epic-1", "priority": "high"},
    ],
)
project = board["project"]
board["board"]["url"]        # <- hand this to the user; it's the living UI

tasks = tasklist_get(project)["tasklist"]["tasks"]
task_claim(project, tasks[1]["id"], agent_name="claude-code:session-abc")
task_update(project, tasks[1]["id"], progress=50, note="I2S clock locked at 48 kHz")
task_complete(project, tasks[1]["id"], note="landed in PR #12")

tasklist_get(project, since_version=12)   # -> {"unchanged": true, ...} when nothing moved
tasklist_summary(project)                 # -> counts, progress, epics, claims

The protocol agents should follow (it's also spelled out in hypervault://help, so a connected agent reads it without being told):

  • Read at session start, and re-poll with since_version at tool boundaries — that's how the user's steering from the board page reaches you mid-task.

  • Claim deliberately. Prefer tasks assigned to you or unassigned. A live foreign lock fails with a 409 naming the holder; force is only for a holder who is clearly gone (expired locks need no force). Locks last 60 minutes by default, 24 h max, and re-claiming your own task renews it.

  • Push every meaningful change immediately — the user's board polls the same list, and a stale board means they're steering blind.

  • Send expected_version on writes. A version conflict comes back as {conflict: true, latest, error} — and latest is the whole fresh list, so the tools return that payload rather than collapsing it into an error message. Re-apply your change on top of latest; don't overwrite.

  • Map both ways via metadata.externalId to keep a native todo list and the board in sync.

Statuses are todo | in_progress | blocked | review | done | cancelled; priorities are low | medium | high | critical. Marking a task done (either tool) releases the lock, and a done task can't be re-claimed. Task writes return the whole list for convenience — on a large board that's token-heavy, so reach for tasklist_summary and since_version polling instead.

Claude Desktop / Claude Code config

{
  "mcpServers": {
    "hypervault": {
      "command": "hypervault-mcp",
      "env": {
        "HYPERVAULT_API_KEY": "hv_your_key_here"
      }
    }
  }
}

Running under greywall (sandboxed agents)

The server is single-host on purpose: every tool call — including extract_source_prompt, which resolves artifact URLs through the backend's /api/extract — goes to the API origin only. That means it works inside deny-by-default sandboxes like greywall with exactly one domain allowed:

export HYPERVAULT_API_KEY=hv_...
greywall --profile claude,python --settings ./greywall.json -- claude

Then allow the API host (hypervault.store, or your HYPERVAULT_API_URL) in the greyproxy dashboard. The greywall.json template also marks HYPERVAULT_API_KEY as a secret, so the sandboxed agent only ever sees a placeholder — greyproxy substitutes the real key into the X-HyperVault-Key header outside the sandbox. Full guide: docs/greywall.md.

Auth & rate limits

Keys are minted (and revoked) in the web dashboard's Vault → Agent API keys panel. This MCP server never stores or looks up keys itself — it forwards whatever key you give it straight to the real HyperVault backend (hypervault.store), which is the only place that ever validates one (it stores just a salted SHA-256 hash and enforces 60 requests/minute per key).

How the key gets there depends on the transport:

  • STDIO (hypervault-mcp, no --transport http) — a single trusted local process. The key comes from the HYPERVAULT_API_KEY environment variable, set once when you start the server (as in Install & run above).

  • HTTP (hypervault-mcp --transport http, and the hosted Vercel deployment) — a single server can be shared by many callers, so every request must carry its own key, sent per-call as either:

    • Authorization: Bearer hv_... (standard, recommended for MCP clients), or

    • X-HyperVault-Key: hv_...

    There is no shared fallback key for HTTP: a request with neither header is rejected with an "Authentication required" tool error before any call reaches the backend, even if the server process happens to have HYPERVAULT_API_KEY set in its own environment. Listing the available tools (tools/list) doesn't require a key — no user data is involved — but every tool call does. Configure your MCP client to send your key as a header on the hosted endpoint, e.g. for a mcp.json-style config:

    {
      "mcpServers": {
        "hypervault": {
          "url": "https://mcp.vault.cool/mcp",
          "headers": { "Authorization": "Bearer hv_your_key_here" }
        }
      }
    }

    https://mcp.vault.cool/mcp is a custom-domain alias for the same deployment as https://hypervault-mcp.vercel.app/mcp — the two are interchangeable and always serve identical code.

Tests

pip install -e ".[test]"
pytest

The suite (tests/) covers the request-shaping logic of every tool, the _client/_request HTTP layer (mocked with respx — no real network calls), the extract_source_prompt preferred/legacy fallback chain, the task-board tools (body shaping, the empty-patch and blank-agent_name guards, and the 409 conflict payload coming back intact instead of as an exception), and — most importantly — the per-request auth model: header parsing, the STDIO-vs-HTTP key resolution split, and full end-to-end requests against the real ASGI app proving an unauthenticated tools/call is rejected even when an operator HYPERVAULT_API_KEY is set in the environment.

Smoke test

With the web app running locally (npm run dev in the repo root) and a key exported:

python - <<'PY'
from fastmcp import Client
from hypervault_mcp.server import mcp
import asyncio

async def go():
    async with Client(mcp) as client:
        tools = await client.list_tools()
        print("tools:", [t.name for t in tools])
        result = await client.call_tool("save_to_hypervault", {
            "content": "<h1>Hello from an agent</h1>",
            "title": "MCP smoke test",
        })
        print(result)

asyncio.run(go())
PY

Task boards need a backend running hypervault ≥ PR #128:

python - <<'PY'
from fastmcp import Client
from hypervault_mcp.server import mcp
import asyncio

async def go():
    async with Client(mcp) as c:
        board = (await c.call_tool("create_task_board", {
            "title": "MCP smoke", "tasks": [
                {"id": "epic-1", "title": "Epic", "type": "epic"},
                {"title": "Child task", "parent": "epic-1"},
            ]})).data
        p = board["project"]                          # board["board"]["url"] is the human page
        lst = (await c.call_tool("tasklist_get", {"project": p})).data["tasklist"]
        tid = next(t["id"] for t in lst["tasks"] if t["parent"] == "epic-1")
        await c.call_tool("task_claim", {"project": p, "task_id": tid, "agent_name": "smoke-test"})
        await c.call_tool("task_update", {"project": p, "task_id": tid, "progress": 50, "note": "halfway"})
        done = (await c.call_tool("task_complete", {"project": p, "task_id": tid, "note": "done"})).data
        assert done["summary"]["byStatus"]["done"] == 1
        assert (await c.call_tool("tasklist_get", {"project": p,
            "since_version": done["summary"]["version"]})).data["unchanged"] is True
        print("task board:", board["board"]["url"])

asyncio.run(go())
PY
Install Server
A
license - permissive license
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    A
    maintenance
    MCP server for BrewPage, a free no-signup hosting service. Lets AI agents publish HTML, Markdown, JSON, files, or a full multi-file static site and get a public URL instantly via a REST API.
    2
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    MCP server for Wundervault zero-knowledge secret management. Exposes vault secrets to AI agents via the Model Context Protocol — secrets are decrypted server-side and never returned to the agent in plaintext.
    6
    108
    2
    AGPL 3.0

View all related MCP servers

Related MCP Connectors

  • Hosted AgentLux MCP server for marketplace, identity, creator, services, and social flows.

  • Artifact store for AI agents. Hosted OAuth at mcp.artifacta.io/mcp; local stdio via npm/PyPI.

  • MCP server for AgentDocs (agentdocs.eu): read, search, write, comment on & share Markdown docs.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/johnnyclem/hypervault-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server