• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
c15r

c15r

sync-mcp

MCP server for agent-sync coordination
Unlisted
Like
sync-mcp
Home
Code
6
docs
3
README.md
auth.ts
db.ts
H
main.ts
tools.ts
Connections
Environment variables
2
Branches
3
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
docs
/
room-token-defaults.md
Code
/
docs
/
room-token-defaults.md
Search
3/5/2026
room-token-defaults.md

Room & Token Resolution in sync-mcp

How MCP clients discover which room to act in and which token to authenticate with — and what users and agents can do beyond the default.

The problem

sync is multi-room. A single user might have tokens for dozens of rooms. But MCP clients (Claude, ChatGPT, Claude Code) send tool calls with no ambient context — each tools/call is a stateless JSON-RPC request. The client doesn't inherently know which room you mean when you say "read the context" or "send a message."

sync-mcp solves this with a three-tier resolution chain: explicit parameters beat vault lookup, which beats the default.

Resolution chain

When any tool is called, sync-mcp resolves the room and token in this order:

1. Explicit params   →  tool call includes { room: "...", token: "..." }
2. Vault lookup      →  OAuth user has a token stored for the named room
3. Default room      →  OAuth user has a vault entry marked is_default=true

Each tier shadows the ones below it. If you pass room and token explicitly, the vault is never consulted. If you pass just room (no token), the vault is searched for the best token for that room. If you pass neither, the default room is used.

Tier 1: Explicit parameters

Every tool accepts optional room and token parameters. When both are provided, they're used directly — no vault, no OAuth, no default. This is the escape hatch that makes sync-mcp backward-compatible with unauthenticated usage: anyone with a room token can use any tool without signing in.

When this matters:

  • Scripted usage where you have tokens in env vars
  • Accessing a room you haven't vaulted
  • Overriding the default for a one-off operation
  • Non-OAuth clients that can't do the passkey flow

Tier 2: Vault lookup by room name

If you pass room but not token, sync-mcp searches your vault for tokens belonging to that room. It picks the highest-authority token available:

room token  >  agent token  >  view token

This means you can say sync_read_context({ room: "workspace" }) without remembering or pasting a token. The vault resolves it.

When this matters:

  • Switching between rooms in conversation: "now read the workspace room"
  • Agents that know room names but shouldn't handle raw tokens
  • Human users who want to name rooms, not paste token strings

Tier 3: Default room

If you pass neither room nor token, sync-mcp falls back to whatever vault entry is marked is_default=true. This is the zero-parameter path — you just call sync_read_context() and get your default room.

Only one vault entry can be the default at a time. Setting a new default clears the old one. The default is set in three places:

  1. Room creation — sync_create_room({ set_default: true }) auto-vaults and marks as default
  2. Management UI — the ☆/★ toggle on room tokens at /manage
  3. OAuth consent — the room picker dropdown during the OAuth flow

The OAuth consent room picker

When an MCP client triggers the OAuth flow (by hitting the 401 on POST /mcp), the user is taken to the authorization page. After passkey authentication, the consent screen now shows:

┌─────────────────────────────────────┐
│  Grant "Claude" access?             │
│                                     │
│  Scope: sync:rooms                  │
│  • Read room context and state      │
│  • Invoke actions and send messages  │
│  • Manage your token vault          │
│                                     │
│  Active room for this client:       │
│  ┌─────────────────────────────┐    │
│  │ ▼ workspace (current default)│    │
│  │   oauth-live-test            │    │
│  │   All rooms (vault default)  │    │
│  └─────────────────────────────┘    │
│                                     │
│  Sets your default room. The client │
│  will use this room when no room is │
│  specified.                         │
│                                     │
│           [Deny]  [Allow]           │
└─────────────────────────────────────┘

Selecting a room sets it as the vault default before issuing the auth code. The MCP client then gets a token that, when used with parameterless tool calls, resolves to that room.

What "All rooms (vault default)" means: don't change the default. Whatever was previously set stays. The client can still access any room by name — the picker only controls which room is used when no room is specified.

What this does NOT do: it does not restrict the client to that room. The OAuth token grants access to the full vault. The client can always pass room: "other-room" explicitly or the LLM can be prompted to use a different room. The picker is purely about convenience — which room the client lands in by default.

What can clients access beyond the chosen default?

Everything in the vault. The OAuth access token maps to a user identity, and that identity owns the entire vault. The default room is a UX convenience, not a security boundary. An authenticated client can:

  • Call sync_read_context({ room: "any-room-in-vault" }) — vault resolves the token
  • Call sync_vault_list() to see all rooms and tokens
  • Call sync_create_room() to make new rooms (which auto-vault)
  • Call any tool with explicit room + token for rooms not even in the vault

The only thing the default controls is what happens when the client (or the LLM) calls a tool with no parameters.

Token types and authority levels

The vault stores three types of tokens, each with different authority:

TypePrefixAuthorityTypical use
Roomroom_Full admin — read/write state, register actions, manage agentsRoom owner, admin tools
Agentas_Agent-scoped — read context, invoke actions, send messages as that agent identityParticipating as a named agent
Viewview_Read-only — read context, state, messagesObservers, dashboards, monitoring

When vault-resolving, sync-mcp picks the highest authority available: room > agent > view. This means if you have both a room token and an agent token for the same room, parameterless calls use the room token (full admin). If you want to act as your agent specifically, pass the room name — or better, the vault could be extended to let you pick preferred authority level.

Current limitation: there's no way to say "default to my agent token, not my room token." The vault always escalates to maximum authority. This is fine for single-user scenarios but matters for multi-agent setups where you want Claude to act as agent:claude not as the room admin.

Ergonomics for different actors

Human users in Claude.ai / ChatGPT

The zero-parameter path is the primary UX. Humans don't want to paste tokens or remember room IDs. They want to say:

"What's the current state?"

And have it just work. The default room makes this possible. When they want to switch:

"Read the context of the workspace room"

The LLM extracts room: "workspace" and the vault resolves the token. No token management needed in conversation.

Room picker at OAuth time is the key ergonomic: it lets the human choose their working context before the conversation even starts. Re-authenticating (disconnect + reconnect the MCP integration) lets them change the default at any time.

AI agents in Claude Code / automated pipelines

Agents benefit from explicit parameters. A well-prompted agent might:

  1. Call sync_vault_list() at the start to discover available rooms
  2. Use sync_read_context({ room: "specific-room" }) for targeted reads
  3. Fall back to the default for general operations

For Claude Code with --scope project, the OAuth consent room picker lets you set a per-project default when you authenticate. Different projects can connect to different rooms.

Multi-agent coordination

When multiple agents share a room, the from field on messages and actions identifies who did what. Currently, messages sent via the room admin token show from: null because the room token has no agent identity. For proper agent attribution:

  1. Join the room as an agent: sync_join_room({ name: "Claude", role: "assistant" })
  2. The agent token gets auto-vaulted
  3. But vault resolution picks the room token (higher authority) over the agent token

This is the main ergonomic gap right now. Solutions under consideration:

  • Preferred token type per room — let users mark which token type to prefer for vault resolution
  • Agent-aware defaulting — if an agent token exists, prefer it over room token for action invocation (but use room token for admin operations like registering actions)
  • Scoped OAuth tokens — issue OAuth tokens that are bound to a specific room + token type, rather than granting vault-wide access

The management UI

The /manage page (passkey-authenticated, no OAuth needed) provides direct control:

  • ★ default badge on the current default room token
  • ☆ default button on other room tokens to switch the default
  • dash ↗ link on every token — opens the sync dashboard with that room + token pre-filled
  • copy — copies the raw token to clipboard
  • revoke — deletes the vault entry (doesn't invalidate the sync token itself, just removes it from your vault)

The dashboard URL format is https://sync.parc.land/?room={id}#token={token} — the token is in the fragment (never sent to the server in the URL), which is marginally better for security than putting it in a query parameter.

Summary of resolution behavior

Tool callResolutionNotes
sync_read_context()Default room, best tokenZero-parameter path
sync_read_context({ room: "X" })Room "X", vault lookupVault picks best token for X
sync_read_context({ room: "X", token: "tok" })Room "X", explicit tokenVault bypassed entirely
sync_send_message({ body: "hi" })Default room, best tokenfrom depends on token type
sync_create_room({ id: "new" })N/A (creates new)Auto-vaults room + view tokens
sync_vault_list()N/A (reads vault)Shows all rooms and tokens

The design principle: make the common case effortless, keep the explicit case possible. Most interactions should need zero parameters. Power users and automated agents can always override.

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.