@framexyz/frame-mcp
v0.4.1
Published
Frame's unified Model Context Protocol server for Claude Desktop and other MCP-capable clients. Exposes the team's Brain (memory, traversal, observability, server-backed PDF), the Frame virtual team (Chief-of-Staff routing + specialist personas), and bran
Maintainers
Readme
@framexyz/frame-mcp
Frame's unified Model Context Protocol server for Claude Desktop and any MCP-capable client. One install line, one config block, three tool surfaces:
- Brain — the team's shared knowledge store (memory, traversal,
observability) plus server-backed PDF rendering. Replaces the
previous
@iolloyd/brain-mcppackage. - Brand — vocabulary lint, BRAND.md ↔ brand.css drift check,
local Frame-branded HTML preview. Replaces the previous
@framexyz/frame-skills-mcppackage. - Team — Frame's virtual team. Decomposes the single brain agent
into specialist persona lenses (Chief of Staff, Head of Operations,
Head of Engineering, Head of Compliance) and routes a task across
them, then reconciles the answers.
frame_team_route+frame_team_persona+frame_team_synthesize.
Brain tools register only when BRAIN_URL is set; brand and team
tools are local (prompt assembly) and always available, so brand- or
team-only users can install the server with no env vars at all.
Team (virtual team)
The persona prompts are source-of-truth in the brain repo
(framexyz/brain, personas/) and vendored here under personas/,
pinned by content hash in personas/personas.lock.json.
src/personas.mjs fails loud if a vendored copy drifts from its
pinned hash — the placement decision (brain 75af9f8a) put the
orchestration runtime in frame-skills while keeping personas in brain,
and content-hash pinning is how the two stay honest.
Routing flow (the host model is the LLM runtime):
frame_team_route({ task })→ the Chief-of-Staff system prompt, the routable roster, the gate role, and the routing directive. The host adopts the prompt, recalls from brain, and emitsOWNER → rolewith a sub-question per specialist. Compliance is a mandatory gate whenever the task touches the regulated perimeter.frame_team_persona({ role, task })→ one specialist's assembled prompt; the host answers in that lens. Run per named owner.frame_team_synthesize({ task, specialist_outputs })→ the Chief-of-Staff synthesis prompt with every specialist output embedded; the host emits one unified recommendation. The Head of Compliance ruling is a binding veto — if present it constrains the result and is never averaged away; if the task touches the regulated perimeter and no Compliance ruling is supplied, the response flags it as unsafe to close (gate_present: false).
To update the vendored personas after editing them in brain:
scripts/sync-personas.sh # re-vendor + regenerate the lock
scripts/sync-personas.sh --check # CI: exit 1 if vendored copies driftInstall (Claude Desktop)
Two paths. Pick the one that fits.
Option A — Desktop Extension (.mcpb, recommended)
Download frame-mcp-<version>.mcpb from the latest GitHub
release and
double-click it. Claude Desktop walks you through the env-var
prompts (BRAIN_URL, default domain, optional API key, optional PDF
render token) and adds the server to your config. No JSON editing.
Restart Claude Desktop after install. Startup logs land in
~/Library/Logs/Claude/mcp-server-frame.log and include a one-line
diagnostic showing tool count, brain auth mode, and which local
scripts were located.
Option B — npx from npm
Edit ~/Library/Application Support/Claude/claude_desktop_config.json
(macOS) or your platform's equivalent:
{
"mcpServers": {
"frame": {
"command": "npx",
"args": ["-y", "@framexyz/frame-mcp"],
"env": {
"BRAIN_URL": "https://<project>.supabase.co/functions/v1",
"BRAIN_API_KEY": "<optional — OAuth used if absent>",
"FRAME_PDF_RENDER_TOKEN": "<for frame_pdf_preview_html>"
}
}
}
}Restart Claude Desktop. Same log location and diagnostic as Option A.
Migrating from the predecessor packages
If you currently install both @iolloyd/brain-mcp and
@framexyz/frame-skills-mcp, replace both entries with the single
frame block above. The three env vars — BRAIN_URL,
BRAIN_API_KEY, FRAME_PDF_RENDER_TOKEN — keep the same names
and values. No secret rotation needed.
OAuth tokens persist in ~/.brain-mcp/tokens.json (unchanged from
@iolloyd/brain-mcp), so users on OAuth don't re-authenticate.
Tools
Brain (registered when BRAIN_URL is set)
Session / auth (no terminal required):
| Tool | What it does |
|---|---|
| brain_auth | Sign in / re-authenticate from inside Claude Desktop. Opens a Google sign-in window for your @frame.xyz account and stores the session locally. Say "reconnect brain". |
| brain_whoami | Report current sign-in status, account, and session validity. |
| brain_signout | Clear the locally stored session (for switching accounts / forcing a clean re-auth). |
Memory + traversal:
| Tool | What it does |
|---|---|
| brain_ask | Natural-language question with synthesised answer + [N] citations. Use this for any "what do we know about X" / "have we discussed Y" query. |
| brain_recall | Raw semantic similarity hits (no synthesis). Lower-level than brain_ask. |
| brain_remember | Store a fact, decision, or note. Long text is chunked automatically. |
| brain_list | Paginated browse over memories in a domain. |
| brain_forget | Delete a memory by UUID. |
| brain_traverse | Walk cross-reference links from a memory. |
| brain_link | Create an explicit link (related / supersedes / contradicts / extends). |
| brain_pin | Change a memory's tier (0=identity, 1=critical, 2=topic, 3=deep). |
| brain_wake_up | Return the L0+L1 context snapshot for a domain. |
Observability:
| Tool | What it does |
|---|---|
| brain_health | Status + memory count for a domain. |
| brain_stats | Latency / counts / tokens over a time window. |
Knowledge graph (entity triples):
| Tool | What it does |
|---|---|
| brain_kg_query | Current facts about an entity. |
| brain_kg_timeline | Chronological history of triples for an entity. |
| brain_kg_invalidate | Mark a triple as no longer true. |
Hierarchical docs (ltree-pathed canonical state):
| Tool | What it does |
|---|---|
| brain_doc_read | Read a doc by path. |
| brain_doc_write | Create or overwrite a doc. |
| brain_doc_list | List docs under an ltree prefix. |
| brain_doc_search | Hybrid semantic search over docs. |
| brain_doc_move | Move a doc (or subtree) to a new path. |
| brain_doc_delete | Delete a doc (recursive optional). |
| brain_doc_link | Create a typed edge between two docs. |
| brain_doc_links | List incoming + outgoing edges for a doc. |
Server-backed PDF:
| Tool | What it does |
|---|---|
| frame_pdf_generate | Render a Frame-branded PDF (one-pager / brief / addendum / memo) via the backend; returns a 7-day signed download URL. |
Brand (always registered)
| Tool | What it does |
|---|---|
| frame_brand_lint | Vocabulary + tone lint. Default mode catches tone smells, posturing words, negative-zero phrasing, title-shape. strict mode adds the Annex-B/OSFI forbidden-vocabulary check for customer-facing or regulator disclosure docs. |
| frame_brand_tokens_check | BRAND.md § 1 ↔ frame-pdf/assets/brand.css palette drift check. |
| frame_pdf_preview_html | Local-iteration HTML preview via frame-pdf-service.fly.dev. For a shareable signed PDF URL, use frame_pdf_generate. |
CLI subcommands
frame-mcp start MCP server (what Claude Desktop runs)
frame-mcp auth interactive Google sign-in (writes ~/.brain-mcp/tokens.json)
frame-mcp signout clear stored tokens
frame-mcp whoami print currently signed-in identityThese mirror the in-app brain_auth / brain_signout / brain_whoami
tools. Prefer the tools inside Claude Desktop — they need no terminal.
Prerequisites
- Node 18+
- For
frame_brand_tokens_checkagainst the canonical brand authority: a local checkout offramexyz/frame-visualsat~/code/frame-visuals/(or passbrand_md_pathexplicitly). - For brain tools: either
BRAIN_API_KEYset, or sign in via Google OAuth. You can sign in without a terminal — install the server, open Claude Desktop, and say "reconnect brain" (thebrain_authtool opens a Google window).frame-mcp authfrom a shell does the same thing. When a session expires, brain tools return an actionable "sign-in required" message and Claude can re-auth in place viabrain_auth— no reinstall, no terminal. - For
frame_pdf_preview_html:FRAME_PDF_RENDER_TOKENset (same token brain's/frame-pdfslash command uses).
Development
git clone https://github.com/framexyz/frame-skills.git
cd frame-skills/mcp
npm install
node src/index.mjs # speaks MCP over stdioSmoke-test via raw JSON-RPC stdio:
printf '%s\n%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| node src/index.mjs 2>/dev/nullBrain tools are only listed when BRAIN_URL is set; the startup
banner on stderr makes this explicit.
Building the Desktop Extension (.mcpb)
The .mcpb bundle for Option-A installs is built from the same
source as the npm package, so the two install paths are identical at
runtime — only the install gesture differs. The build is driven by
@anthropic-ai/mcpb
and a small wrapper script that mirrors prepublishOnly's asset
staging.
cd mcp
npm run build:mcpb # produces frame-mcp-<version>.mcpb (gitignored)The wrapper at scripts/build-mcpb.sh stages source + brand assets
into a temp dir, installs production-only deps, validates the
manifest, and calls mcpb pack. The artefact stays out of git
(*.mcpb in .gitignore); attach it to the matching GitHub Release
when cutting a new version.
Cutting a release
One command from the repo root builds the .mcpb, publishes to npm,
tags, and creates the GitHub Release with the bundle attached:
task mcp:releaseIt runs from main with a clean tree, verifies personas are in sync
with brain and tests pass, then publishes before tagging so a
failed/unauthenticated npm publish leaves no dangling tag. Bump the
version in both mcp/package.json and mcp/manifest.json first
(task mcp:manifest:check enforces they match). task mcp:publish
(npm only) and task mcp:build (.mcpb only) are available for the
pieces. See Taskfile.yml.
Publishing the .mcpb to Slack
The release ends by posting the bundle to the #all-frame channel so
teammates can install without visiting GitHub Releases. It's a final,
non-fatal step — skipped silently unless SLACK_BOT_TOKEN is set, so
it never undoes a completed npm + GitHub release:
export SLACK_BOT_TOKEN=xoxb-… # Slack app invited to the channel,
# with files:write (+ channels:read
# to resolve a channel name)
export SLACK_MCPB_CHANNEL=all-frame # optional; defaults to all-frame.
# Accepts a name, #name, or an ID.
task mcp:release # …or, standalone, after a build:
task mcp:slackscripts/post-mcpb-to-slack.sh resolves the channel name to an ID
(conversations.list), then uploads via Slack's
files.getUploadURLExternal → upload → files.completeUploadExternal
and attaches a short install note. Pass an ID in SLACK_MCPB_CHANNEL to
skip the lookup and the channels:read scope. bash
scripts/post-mcpb-to-slack.sh --dry-run prints what it would send
without calling Slack.
Token: reuse the brain Slack app's bot token (the same
SLACK_BOT_TOKEN stored as a Supabase secret for brain). One-time
setup, since that app today only posts (chat:write):
- In the brain Slack app config, add the
files:writescope (andchannels:readif you want name resolution; skip it by passingSLACK_MCPB_CHANNEL=<channel-id>), then reinstall the app. - Invite the bot to #all-frame (
/invite @<bot>).
Then export that token before task mcp:release (or task mcp:slack).
License
MIT
