Relay Card Display
Complete reference for the display field in every MCP tool response. Every tool. Rich and plain side by side. Ground truth for src/relay/mcp/server.py.
Rich mode
Plain mode
Framing rule
Known gap
Live
Template
§
Display field
The render-verbatim rule
Every MCP tool response includes a display field — a markdown string the agent renders verbatim as a visual card. It is not a summary. It is an identity block: who, what, when, and (for transfers) whether content has been loaded.
The Rule
Agents must render display verbatim as markdown before doing anything with the content. The next_step field enforces this explicitly. Never let the agent skip the card and summarise content in its own voice.
Without --- framing and a signal line, agents see a 2–3 line block with no structural cue, skip rendering the card, and summarise the content in their own voice. This is the root cause of both bugs patched in June 2026.
§
Entity symbols
Defined in server.py
| Symbol | Constant | Entity | Usage |
| ⇄ | RELAY_SYM |
Relay transfer |
Code line in every transfer card |
| ◎ | STREAM_SYM |
Stream context |
Stream name line in stream tool responses |
Do not use these symbols for other purposes. Do not swap them.
§
Framing convention
Rich mode only · transfers + profile_get
Rich-mode relay cards use horizontal-rule framing. The opening --- separates the card from prior conversation. The closing --- + signal line tells the agent the card is complete and content follows.
---← opening rule · separates from prior context
✅ **Transfer title — Jun 3, 2026**← title line
⇄ `K886XH` · 👤 Sender · Jun 3 · 👤 Private← code line
---← closing rule
*content loaded — see below*← signal line · agent cue
| State | Signal line | Notes |
| Confirmed receive | *content loaded — see below* | Content is in the content field below the card |
| Owner / already claimed | *content loaded — card above* | Card appears above the content the agent is contextualising |
| relay_profile_get | (no signal — framing only) | Only tool outside transfers that uses --- framing; see §5 |
| Preview | (no framing — known gap) | Low priority; no content loaded at this step |
| Pending approval | (no framing — known gap) | No content loaded; risk lower |
§
Top pick — collection cards
All read hubs · one weighted candidate, or silence
Every list (collection card) MAY lead with a single promoted candidate — the one the user probably wants — marked ▸ in rich mode, > in plain, with a short italic reason after the row. Promotion fires only when the weighting is unambiguous: a single candidate, or a strictly-newest recent one. A tie, a stale newest, or a missing sort key renders no pick — the flat capped list is byte-identical (the nudge discipline applied to promotion). The pick is a surfaced row, not an extra one: it counts toward the … +N more footer, and the JSON response mirrors it as top_pick {…item, reason}.
📂 **Sessions** · 12← list header, true total
▸ 📂 `JSKEAW` **MCP card schema** · 2026-08-05 · *most recent open session*← the pick + reason
📂 `Q2MMD4` **Creative estate sweep** · 2026-08-01← ordinary rows follow
*… +10 more · app.relayctx.com/sessions*← footer counts the pick as shown
Rich mode
List Sessions›
📂 Sessions · 3
▸ 📂 relay-platform / MCP card schema · 2026-08-05 · most recent open session
📂 Creative estate sweep · 2026-08-01
📂 Billing reconciliation · 2026-07-28 · closed
Plain mode
List Sessions›
▭ Sessions · 3
> relay-platform / MCP card schema · 2026-08-05 · most recent open session
Creative estate sweep · 2026-08-01
Billing reconciliation · 2026-07-28 · closed
| Read hub | Candidates | Recency rule |
| relay_sessions | Open sessions (workspace-scoped when filtered) | created_at / session_date · 7 d |
| relay_sessions (browse) | Open handoff packages | created_at · 7 d |
| Transfers list (pending lens) | Pending, not yet stale | created · 1 d — transfers age in hours; archived/claimed lenses never promote |
| Stream list | All streams | updated_at · 7 d (workspace-bound stream promotes at boot via relay_context) |
| Series list | Open series | last_activity · 7 d |
| Requests list | Pending requests | Single-candidate only — an action queue; a recency pick would invert urgency |
Selection: relay.mcp._promote.top_pick() · rendering: relay_list_card(pick=…, pick_reason=…) · rule: relay-platform docs/DESIGN-NOTE.mcp-surface-depth.md §Promotion.
1
Transfer States
relay_transfer_receive · 6 states
relay_transfer_receive
No Framing
confirm=false · non-owner
Shown before the user confirms the receive. No content is returned. Agent presents the card and asks "Load it?" Preview state does not yet have --- framing — low priority since no content is loaded at this step.
State 1 — Preview
Rich mode
Receive Relay Transfer›
⇄ HAVVPX
👤 Sender Name · Jun 3
This transfer is from Sender: 'Sprint Handoff'. Load it?
Plain mode
Receive Relay Transfer›
[RELAY PREVIEW] HAVVPX
Sprint Handoff
From: Sender Name · Jun 3
Call relay_transfer_receive confirm=true to load.
⚠ Preview state does not yet have --- framing. Low priority — content is not loaded at this step.
relay_transfer_receive
confirm=true · status='claimed'
Shown after a successful receive. Full content is in the content field. Code is spent after this step. The --- framing + signal line cue the agent to render first, then load content.
State 2 — Confirmed receive
Rich mode
Receive Relay Transfer›
---
✅ Sprint Handoff — May 7, 2026
⇄ `HAVVPX` · 👤 Sender Name · Jun 3
---
*content loaded — see below*
Plain mode
Receive Relay Transfer›
[RELAY LOADED] HAVVPX
Sprint Handoff — May 7, 2026
From: Sender Name · Jun 3
Context loaded below.
● --- framing: opening rule → card content → closing rule → signal line «content loaded — see below»
relay_transfer_receive
confirm=false · viewer='owner'
Triggered when the caller is the transfer owner. Content is returned immediately — no confirm step. Signal line reads «content loaded — card above» because the card appears above the content block.
State 3 — Owner
Rich mode
Receive Relay Transfer›
---
👤 Sprint Handoff — May 7, 2026
⇄ `HAVVPX` · Sent by you · Jun 3
---
*content loaded — card above*
Plain mode
Receive Relay Transfer›
[YOUR RELAY] HAVVPX
Sprint Handoff — May 7, 2026
Sent by you · Jun 3
Context loaded below.
● Signal «card above» used here (vs «see below» on confirmed receive) — positional distinction for agent rendering.
relay_transfer_receive
confirm=false · viewer='claimed'
Triggered when the caller has already claimed this code. Content is returned immediately, same as owner path. Same framing, same signal line.
State 4 — Already claimed
Rich mode
Receive Relay Transfer›
---
✅ Sprint Handoff — May 7, 2026
⇄ `HAVVPX` · Already claimed · Jun 3
---
*content loaded — card above*
Plain mode
Receive Relay Transfer›
[RELAY LOADED] HAVVPX
Sprint Handoff — May 7, 2026
Already claimed · Jun 3
Context loaded below.
relay_transfer_receive
No Framing
approval required
Shown when a cross-user receive is blocked pending sender approval. Content is not loaded. No --- framing yet — known gap, lower priority than confirmed-receive framing.
State 5 — Pending approval
Rich mode
Receive Relay Transfer›
⏳ Approval requested
⇄ HAVVPX
Your request has been sent. You'll be notified once the sender approves.
Plain mode
Receive Relay Transfer›
[PENDING APPROVAL] HAVVPX
Request sent. Waiting for sender to approve.
⚠ Pending approval does not yet have --- framing. Risk is lower since no content is loaded, but consistency suggests adding framing in a future pass.
relay_series_receive
2-step confirm
Same two-step confirm pattern as relay_transfer_receive. On confirmed receive, series name is appended to the code line.
Step 1 — Series preview
Receive Series›
📋 Sprint Handoff — May 7, 2026
⇄ series weekly-sync · latest: HAVVPX
👤 Sender Name · Jun 3 · 👤 Private
→ Agent asks: "Load it?" — waits for confirmation
Step 2 — Confirmed
Rich mode
Receive Series›
---
✅ Sprint Handoff — May 7, 2026
⇄ `HAVVPX` loaded · series weekly-sync
👤 Sender Name · Jun 3
---
*content loaded — see below*
Plain mode
Receive Series›
[RELAY LOADED] HAVVPX
Sprint Handoff — May 7, 2026
Series: weekly-sync
From: Sender Name · Jun 3
Context loaded below.
2
Session & Context
3 tools
relay_get_context
Live
Session bootstrap
Called at the start of a session to establish the active user and org. Display confirms the session loaded, who the user is, and — in the variant below — whether there are pending requests.
Base response
Rich mode
Get Context›
⚡ Session loaded · erik
Relay Context Inc. · admin · Pro
Plain mode
Get Context›
[SESSION LOADED] erik
Relay Context Inc. admin Pro
Context ready.
With pending requests
Rich mode
Get Context›
⚡ Session loaded · erik
Relay Context Inc. · admin · Pro
💬 You have 1 pending request(s). Use relay_request_list to review.
Plain mode
Get Context›
[SESSION LOADED] erik
Relay Context Inc. admin Pro
1 pending request(s). Run relay_request_list.
Context ready.
relay_get_recent
Live
Recent relay list
Returns the user's recent relay transfers as a display list. Header shows count; each row shows code, title, visibility, and receive count.
Rich mode
Get Recent Relays›
📋 Recent Relays · 5 relay(s)
⇄ K886XH · Sprint Handoff · Private · 1 received
⇄ M241RQ · Design Brief · Org · 3 received
⇄ T990WW · Auth Spec · Private · 0 received
Plain mode
Get Recent Relays›
[RECENT RELAYS] 5
K886XH Sprint Handoff Private 1 received
M241RQ Design Brief Org 3 received
T990WW Auth Spec Private 0 received
relay_session_log
Template
Session close
Called at the end of a session to log activity to a relay. Shows the relay code the session was logged against, the date, and the title.
Rich mode
Log Session›
📝 Session logged · Jun 5, 2026
⇄ K886XH · Sprint Handoff · Private
Plain mode
Log Session›
[SESSION LOGGED] K886XH
Sprint Handoff
Jun 5, 2026
3
Stream
5 tools · ◎ STREAM_SYM
relay_stream_list
Template
Focus area index
Returns all stream focus areas for the active user. Header shows total count; each row shows the stream name and item count.
Rich mode
List Streams›
📋 Focus Areas · 3
◎ Relay Product Development · 9 Item(s)
◎ Relay Creative · 4 Item(s)
◎ Admin & Ops · 2 Item(s)
Plain mode
List Streams›
[FOCUS AREAS] 3
Relay Product Development 9 items
Relay Creative 4 items
Admin & Ops 2 items
relay_stream_create
Template
New focus area
Confirms the creation of a new stream. Shows the stream name and the opaque context_id. The ⬡ prefix (hexagon) marks the context_id — a machine-facing handle, not a user-facing label.
Rich mode
Create Stream›
✅ Focus area created: Relay Creative
⬡ sc_e80950d4c486 · add Items with relay_stream_update
Plain mode
Create Stream›
[STREAM CREATED] sc_e80950d4c486
Relay Creative
relay_stream_get
Live
Stream detail
Full detail view of a single stream. Shows name, context_id, weight percentage, item count, and a preview of the first few items.
Rich mode
Get Stream›
◎ Relay Creative
⬡ sc_e80950d4c486 · weight 30% · 4 live
· relay-card-display v3.5
· MCP reference refresh
· Creative direction brief
Plain mode
Get Stream›
[STREAM] sc_e80950d4c486
Relay Creative 30% 4 live
- relay-card-display v3.5
- MCP reference refresh
relay_stream_update
Template
Item add / edit / remove
Confirms an item was added, updated, or removed from a stream. Shows the operation prefix (+ / ~ / −) and the item title.
Rich mode
Update Stream›
✅ Stream updated
+ relay-card-display v3.5 added
Plain mode
Update Stream›
[STREAM UPDATED] sc_e80950d4c486
+ relay-card-display v3.5 added
relay_stream_active
Live
Active focus area
Returns the currently active stream for this workspace/session. Shows stream name, context_id, and the workspace tag. Used by the agent at session start to orient to the active focus area.
Rich mode
Get Active Stream›
◎ Relay Product Development
⬡ sc_78b145fbb371 · workspace: github:christensen-digital/relay-platform
Plain mode
Get Active Stream›
[ACTIVE STREAM] sc_78b145fbb371
Relay Product Development
github:christensen-digital/relay-platform
5
Identity & Org
4 tools
relay_profile_get
Live
Identity block
Returns a markdown table of the user's identity and org membership. This is the only non-transfer tool that uses --- framing — because the profile block is a structured identity artifact, not a status message. It does not use a signal line.
Rich mode
Get Profile›
---
Your Relay Profile
| Field | Value |
|---|---|
| User ID | rd521776 |
| Org | Relay Context Inc. |
| Name | Erik Christensen |
| Role | admin |
| Tier | Pro |
---
Plain mode
Get Profile›
[YOUR PROFILE]
User ID: rd521776
Org: Relay Context Inc.
Name: Erik Christensen
Role: admin
Tier: Pro
● relay_profile_get is the only non-transfer tool that uses --- framing. Reason: the profile table is a structured identity artifact with clear boundaries, not a status confirmation. There is no signal line — the closing --- is sufficient because no content follows in the same response.
relay_workspace_switch
Template
Org switch
Confirms a workspace (org) switch. Shows the org name, the internal org ID, the user's role in the new org, and status.
Rich mode
Switch Workspace›
🔄 Switched to Relay Context Inc.
⬡ gwh04nszwz5s76s · admin · active
Plain mode
Switch Workspace›
[WORKSPACE SWITCHED]
Relay Context Inc.
gwh04nszwz5s76s admin active
relay_request_list
Live
Pending requests
Lists incoming receive requests awaiting the user's approval. Each row shows requester name, request ID, and date. Empty state shown when no requests are pending.
With pending requests
Rich mode
List Requests›
⏳ 1 pending request
From Ada · rd521776 · requested May 5
Plain mode
List Requests›
[PENDING REQUESTS] 1
Ada rd521776 May 5
Empty state
Rich mode
List Requests›
No pending requests.
Plain mode
List Requests›
[PENDING REQUESTS] 0
No requests.
relay_search
Live
Transfer search
Searches the user's relay transfers. Header shows result count and query; each row shows code, title, status, and date. Empty state shown when no results.
With results
Rich mode
Search Relays›
🔍 3 results for "sprint"
⇄ K886XH · Sprint Handoff · pending · May 7
⇄ T330AB · Sprint 22 Notes · claimed · Apr 28
⇄ R119XZ · Sprint Planning · claimed · Apr 14
Plain mode
Search Relays›
[SEARCH] 3 results for: sprint
K886XH Sprint Handoff pending May 7
T330AB Sprint 22 Notes claimed Apr 28
R119XZ Sprint Planning claimed Apr 14
No results
Rich mode
Search Relays›
🔍 No results for "quarterly"
Plain mode
Search Relays›
[SEARCH] 0 results for: quarterly
6
Error States
3 categories
relay_transfer_receive
Not found · already received
Code does not exist, or has already been received by another user (codes are single-use on non-org transfers).
Code not found
Rich mode
Receive Relay Transfer›
❌ Code not found
⇄ ZZZZZZ · Check the code and try again.
Plain mode
Receive Relay Transfer›
[ERROR] CODE NOT FOUND
ZZZZZZ
Check the code and try again.
Already received
Rich mode
Receive Relay Transfer›
❌ Already received
⇄ HAVVPX · This code has already been claimed by another user.
Plain mode
Receive Relay Transfer›
[ERROR] ALREADY RECEIVED
HAVVPX
Code already claimed by another user.
any tool
401 / token expired
Returned by any tool when the session JWT has expired or the token is invalid. Agent should prompt the user to re-authenticate.
Rich mode
MCP Tool›
🔒 Session expired
Re-authenticate at app.relayctx.com to continue.
Plain mode
MCP Tool›
[ERROR] SESSION EXPIRED
Re-authenticate at app.relayctx.com.
any tool
Network / 5xx
Returned when the MCP server cannot reach the Relay API. Includes partial load variant when context was partially loaded before the failure.
Full failure
Rich mode
MCP Tool›
⚠️ Connection error
Could not reach relay API. Check your connection and try again.
Plain mode
MCP Tool›
[ERROR] CONNECTION FAILED
Could not reach relay API. Try again.
Partial load
Rich mode
MCP Tool›
⚠️ Partial load
⇄ HAVVPX · Sprint Handoff
Context partially loaded. Some content may be missing.
Plain mode
MCP Tool›
[ERROR] PARTIAL LOAD
HAVVPX Sprint Handoff
Some content may be missing.
§
Plain mode
RELAY_PLAIN=1
_plain_mode() returns True when RELAY_PLAIN=1 or RELAY_PLAIN=true. Produces ASCII-safe, emoji-free output for terminals. Every display string must have both branches.
# Every display field structure
"display": (
(
f"[RELAY LOADED] {code}\n"
f"{title}\n"
f"From: {sender} · {date}\n\n"
"Context loaded below."
) if _plain_mode() else (
f"---\n"
f"✅ **{title}**\n"
f"⇄ `{code}` · {sender} · {date}"
+ "\n---\n*content loaded — see below*"
)
),
Plain mode: always bracketed label, always a text signal line. No emoji, no markdown, no --- framing.
§
Helper functions
server.py
- relay_card() Build a display string per the card grammar (obj_type, name, code, status, meta, body, signal, plain)
- card_result() Assemble tool-response dict with strong verbatim next_step baked in — use for all card tools
- _RENDER_CARD_VERBATIM The strong verbatim constant (source of truth in relay.mcp._display)
- _plain_mode() bool — reads RELAY_PLAIN env var
- _date_label(iso) Short date string e.g. "Jun 3" — never use raw ISO date in display
- _sender_emoji(name) 🤖 for agent senders (name contains /), 👤 for humans
- _vis_icon(v) 👥 org · 📢 broadcast · 👤 private
- _vis_label(v) "Org" · "Broadcast" · "Private"
§
New state checklist
Before merging
- Rich mode:
--- framing, card content, closing ---, signal line (transfer tools)
- Plain mode: bracketed label, title, sender/date or key fields, blank line, text signal
- Assemble response with
card_result() from relay.mcp._display — never hand-write next_step for card tools
- Uses
_plain_mode() branch — both branches present in every tool
- Uses
RELAY_SYM (⇄) for the code line — not a plain string
- Uses
STREAM_SYM (◎) for stream name lines — not a plain string
- Uses
_date_label() not raw ISO date in any display output
- Uses
_sender_emoji() for sender icon (agent vs human)
- Test with
RELAY_PLAIN=1 to verify plain branch renders correctly
- LIVE badge: uses real response values (not hardcoded placeholders)
- TEMPLATE badge: uses hardcoded example values clearly labelled as templates
- List cards: one weighted candidate →
▸ top-pick row via relay_list_card(pick=…) + top_pick JSON field; ambiguous → no pick, byte-identical list (see §Top pick)
- Update
DESIGN-NOTE.relay-card-display.md in relay-platform/docs/