Skip to content

First-run flow: demo → namespace → MCP install (with read token) → first agent call #18

Open

Description

@harrymove-ctrl

Context

After a hosted build, HostedBuildBanner (apps/web/src/main.tsx:4267) shows the share page, MCP endpoint, and an McpPlayground. But the install snippet generator ShareMcpInstall (main.tsx:2028) emits npx -y mcp-remote <mcpUrl> with no Authorization header / read token — so installing a private namespace silently 401s. QuickStartCard (main.tsx:4216) is static text with no progress, and unauthenticated users hit the hard /app gate before they can explore anything.

Goal / user story

As a first-timer, I go demo → see my new namespace → copy an MCP install command that actually works (token included for private) → make a first recall call, guided step by step, without getting silently 401'd.

Acceptance criteria

  • ShareMcpInstall and HostedBuildBanner snippets include --header "Authorization: Bearer <read-token>" for private namespaces, and omit it (with a one-line "public needs no token" note) for public ones.
  • The read token is fetched/shown only to a viewer who owns the namespace (never leaked on public /share/* pages).
  • A guided checklist (Demo → Build → Install → First recall) tracks progress and persists across reloads (localStorage).
  • A "Use this in your agent" card shows a copyable first-call example (a concrete recall against the namespace) on the build banner / share page.
  • Unauthenticated users can browse seeded public namespaces (showcase/directory) before encountering the delegate/wallet gate.
  • Copy buttons give visible "Copied" confirmation.

Implementation notes

  • Reuse PublishPanel's snippet shape (it already includes the Authorization header, ~main.tsx:449-456) inside ShareMcpInstall (main.tsx:2028); pull the token from /api/hosted/namespaces/:ns/tokens.
  • Replace static QuickStartCard (main.tsx:4216) with a progress-tracked stepper; reuse ShowcasePage (main.tsx:2057, backed by /api/directory) as the "explore public first" entry so the hard gate isn't the first thing new users hit.
  • Gotcha: the read-token snippet must be gated on ownership — share-page viewers must not see another user's private token. The read-token fix alone is small and shippable independently of the checklist.

Sui Overflow angle

This is the judge's first two minutes. A 401-free, guided demo→install→first-call path is what turns a Walrus/Sui demo into a "wow" instead of a debugging session. It's also where the extract/recall/remember capabilities are introduced as the contract that later becomes Talus/Nexus tools with on-chain attribution receipts.

Dependencies

Read-token fix is independent (ship now). Checklist/explore-first benefits from the routed namespace console (token management) and the Sui wallet identity issue.

Part of the ContextMEM roadmap (#4) • Sui Overflow build.

Activity

  1. added this to the Sui Overflow milestone on Jun 16, 2026
  2. added
    P0Demo-blocking: required for a working Sui Overflow demo
    mcpModel Context Protocol servers and tools
    ui-uxWeb app UI/UX, accessibility, and responsive design
    onboardingUser and developer onboarding / first-run experience
    on Jun 16, 2026
  3. harrymove-ctrl commented on Jun 19, 2026

    @harrymove-ctrl
    OwnerAuthor

    Implementation Plan

    Approach
    The 401 trap is purely client-side: the server already mints correct, header-bearing snippets (apps/api/src/worker.ts:3244-3293 builds claudeDesktop/cursor/codex/generic/mcpRemote with --header "Authorization: Bearer <readToken>"), but ShareMcpInstall (main.tsx:2028) re-derives its own snippet without the header and HostedBuildBanner (main.tsx:4267) shows no install snippet at all. The chosen strategy: (1) introduce one shared frontend helper buildMcpSnippets(mcpUrl, namespace, readToken?) that mirrors the worker's snippet shape and emits the header only when a token is present, (2) keep the read token strictly client-fetched by an authenticated owner (never embedded in the public /share/* payload), and (3) layer a localStorage-persisted first-run checklist + a copyable "first recall" card + an explore-public-first entry on top. Ship the read-token fix first (small, independent), then the guided-onboarding layer.

    Prerequisites & dependencies

    • No new external creds/bindings. Uses existing endpoints: GET/POST /api/hosted/namespaces/:ns/tokens (worker listNamespaceTokens/createNamespaceToken, routed via the /api/hosted/*→/api/* rewrite at worker.ts:581-583, regex matchNamespaceTokenPath at worker.ts:912, gated by requireImportAuthorization at worker.ts:4436 + assertManagedNamespace at worker.ts:3629) and the already-public GET /api/directory (worker.ts:687 → listDirectoryNamespaces:1028, frontend ShowcasePage at main.tsx:2057).
    • One small backend change required: publicShareLink (worker.ts, the function returning the share JSON used by getShareLink:1610) does NOT expose visibility; the share page needs it to choose token vs no-token snippet. Add visibility (and nothing secret) to that return.
    • Soft dependency (do NOT block on these): roadmap §4 P0 "wire the orphaned NamespacesAppPage" (main.tsx:5943, currently /app/namespaces uses NamespacesSimplePage at main.tsx:1575) is where owners manage tokens long-term; and roadmap §5 "kill the delegate signup wall." The read-token fix and checklist work with what exists today.
    • Decision to confirm with team: the token endpoints are gated by a deployment-wide CONTEXTMEM_NAMESPACE_IMPORT_TOKEN; ownership is only enforced when ?ownerId= is passed (listNamespaceTokens:977, createNamespaceToken:994). See Risks — recommend deriving owner from x-memwal-account-id (the pattern at worker.ts:1120) so a shared import bearer can't enumerate other owners' tokens.

    Step-by-step

    1. Backend: expose visibility on the share payload. In apps/api/src/worker.ts, add visibility: row.visibility (or look it up via CloudflareNamespaceStore.getNamespace(share.namespace) if ShareLinkRow lacks it) to the object returned by publicShareLink. Confirm getShareLink (worker.ts:1610) still returns NO token. Update the frontend ShareLinkResponse.share type (main.tsx:527) to add visibility: "private" | "public".

    2. Frontend: add a shared snippet builder + reusable CopyButton. In apps/web/src/main.tsx, add function buildMcpSnippets(mcpUrl: string, namespace: string, readToken?: string) that returns the same shape as worker.ts:3244-3293 (claudeDesktop, cursor, codex, mcpRemote), conditionally appending "--header", \Authorization: Bearer ${readToken}`/headers.Authorizationonly whenreadTokenis set. Addfunction CopyButton({ text, label }: ...)factoring the "Copied" pattern already inlined inHostedBuildBanner (main.tsx:4268-4277) into one component (1.8s reset, uses navigator.clipboard.writeText`).

    3. Fix ShareMcpInstall (main.tsx:2028). Change props to { mcpUrl, namespace, visibility, isOwner, sessionToken }. Logic:

      • public → render no-token snippets from buildMcpSnippets(mcpUrl, namespace) + one-line note "Public namespace — no token needed." Use CopyButton per snippet.
      • private + isOwner → show a "Generate install token" button that POSTs createNamespaceToken (fetch(\${API_BASE}/api/hosted/namespaces/${ns}/tokens`, { method: "POST", headers: authHeaders(sessionToken), body: {label:"install", ownerId:} }), reusing authHeadersatmain.tsx:7910), then render buildMcpSnippets(mcpUrl, namespace, readToken)with the header. (Raw token is only returned on create —worker.ts:1007` — so minting on explicit click is correct; do not auto-mint on view.)
      • private + not owner → render "This namespace is private — only its owner can install it" (no snippet, no token).
        Pass the new props from ShareContentTabs (main.tsx:1749/call at :1800) and SharePage (:1742), threading share.visibility and an isOwner computed in step 5.
    4. Fix HostedBuildBanner (main.tsx:4267). Extend the result prop to { shareId, namespace, shareUrl, mcpUrl, visibility, readToken? }. Render an "Install in your MCP client" block via buildMcpSnippets(mcpUrl, namespace, visibility === "private" ? readToken : undefined) with the public note when public, plus CopyButtons. Update the producing state hostedBuildResult (declared main.tsx:839, set at main.tsx:1280) to carry visibility: body.job.visibility and, for private+owner, readToken: body.job.result?.readToken (the demo/QuickStart path is public, so token is omitted there). Update the type at main.tsx:2848.

    5. Compute isOwner on the share page. In SharePage (around main.tsx:1742), after loading share, attempt GET /api/hosted/namespaces/:ns/tokens?ownerId=<delegate account> with authHeaders(sessionToken) (delegate account from readHostedDelegate() at main.tsx:7938); HTTP 200 ⇒ isOwner=true, else false. This never leaks a token (list returns only hashPrefix per worker.ts:980-988) and is the natural ownership probe. Skip the probe entirely when no delegate is present (anonymous viewer ⇒ not owner).

    6. "Use this in your agent" first-call card. Add function FirstCallCard({ namespace, mcpUrl, readToken? }) rendering a copyable concrete recall example in two tabs: (a) MCP tools/call JSON for contextmem_read_artifact/recall (mirror the call in McpPlayground at main.tsx:4352-4355), and (b) a curl to mcpUrl including -H "Authorization: Bearer <readToken>" when private. Mount it in HostedBuildBanner (next to McpPlayground at main.tsx:4301) and in the share-page MCP tab (main.tsx:1800).

    7. First-run checklist (persisted). Add function useOnboardingProgress() backed by localStorage key contextmem.onboarding.v1 using the existing safeLocalGet/safeLocalSet wrappers (main.tsx:669-688, Safari-private-mode safe), storing { demo, build, install, recall } booleans + timestamps. Auto-mark steps from real signals: demo when a DemoPreviewState reaches completed (main.tsx:516), build when hostedBuildResult is set (:1280), install when any install snippet CopyButton fires, recall when FirstCallCard is copied/run. Replace the static body of QuickStartCard (main.tsx:4216, rendered at :3857) with a <FirstRunChecklist> stepper (Demo → Build → Install → First recall) while keeping the URL input + QUICK_START_SAMPLES + Build button.

    8. Explore-public-first entry. In HostedCredentialGate (main.tsx:4522) and/or LockedPreview (main.tsx:4576), add a prominent "Explore public namespaces" link to /showcase (already public via /api/directory) so an unauthenticated user can browse seeded public namespaces before the delegate gate. Optionally add the same CTA to the landing route block (main.tsx:1518). No change to write-gating — only adds a read-only path ahead of the gate.

    9. Backend hardening (recommended, small). In listNamespaceTokens/createNamespaceToken/revokeNamespaceToken (worker.ts:976/992/1013), derive the owner from request.headers.get("x-memwal-account-id") when present (same approach as worker.ts:1120) and pass it to assertManagedNamespace, so the deployment-wide import bearer alone can't list/mint another owner's tokens. Keeps the share-page ownership probe (step 5) honest.

    New / changed files

    • apps/web/src/main.tsx — edits: ShareMcpInstall (:2028), HostedBuildBanner (:4267), QuickStartCard (:4216), ShareContentTabs/SharePage (:1749/:1742/:1800), hostedBuildResult state+type (:839/:1280/:2848), ShareLinkResponse type (:527), HostedCredentialGate/LockedPreview (:4522/:4576); new: buildMcpSnippets, CopyButton, FirstCallCard, FirstRunChecklist, useOnboardingProgress.
    • apps/web/src/styles.css — styles for CopyButton, checklist stepper, first-call card, "explore public" CTA (reuse existing --cm-* tokens, brand green #a8d946).
    • apps/api/src/worker.ts — publicShareLink adds visibility; (recommended) header-derived ownerId in the three token handlers.

    Test plan

    • Unit: buildMcpSnippets — assert public output has no Authorization/--header; private output exactly matches the worker shape (mcp-remote <url> --header "Authorization: Bearer <tok>"); snapshot vs worker.ts:3244-3293.
    • Integration (Worker, bun test + miniflare/D1): import a private namespace, mint a token via POST /api/hosted/namespaces/:ns/tokens, assert GET /api/share-links/:id returns visibility:"private" and no token field; assert the token list endpoint 200s with the owner's ownerId/account header and 403/404s without it (step 9).
    • Manual round-trip (proves no silent 401): build a private namespace → copy the banner's mcp-remote snippet → run npx -y mcp-remote <mcpUrl> --header "Authorization: Bearer <tok>" against the hosted Worker → expect tools/list + a successful contextmem_read_artifact; then drop the header → expect the 401 with hint NAMESPACE_TOKEN (worker.ts:8024). Also POST the MCP endpoint with the header and assert a 200 JSON-RPC result.
    • Manual ownership/leak check: open the private namespace's /share/:id in a logged-out browser → no token, no "generate token" button, "private — owner only" message; open as owner → token reveal works.
    • Manual onboarding: complete demo→build→copy→first-call, reload page, assert checklist state persists; verify "Copied" appears on every copy button; verify an anon user can reach /showcase from the gate without a delegate.

    Risks & gotchas

    • Token leakage: keep getShareLink/publicShareLink token-free; the token is only ever fetched client-side by an authenticated owner (step 5). Adding visibility is safe (already shown on the namespace HTML page, worker.ts:864).
    • Raw token not retrievable after creation: listNamespaceTokens returns only hashPrefix (worker.ts:980-988); the only source of a usable token is createNamespaceToken (:1007) or the build-time HostedNamespaceImportResponse.readToken. Mint only on explicit click to avoid token sprawl.
    • Deployment-wide import bearer: without step 9, any holder of CONTEXTMEM_NAMESPACE_IMPORT_TOKEN plus a guessed ownerId could enumerate tokens — flag for team; step 9 closes it.
    • Demo path is public: QuickStart builds go through /api/demo/extractions (main.tsx:1212) and are public, so the banner correctly shows the no-token snippet; don't assume body.job.result.readToken exists there.
    • MCP URL origin: snippets must use the Worker origin, not the Pages origin (the same-origin proxy isn't active) — reuse the mcpBase/isLocalApiBase logic at main.tsx:1278.
    • localStorage: use safeLocalGet/Set (main.tsx:669) — raw localStorage throws in Safari private mode.
    • Not Walrus/Sui/Harbor-touching: no testnet tx, no SEAL, no migration — out of scope for this issue.

    Acceptance-criteria mapping

    • Snippets include --header Authorization: Bearer <read-token> for private, omit + note for public → Steps 2, 3, 4 (buildMcpSnippets, ShareMcpInstall, HostedBuildBanner).
    • Read token shown only to owner; never on public /share/* → Steps 1, 3, 5 (visibility-only public payload, owner probe, mint-on-click).
    • Guided checklist Demo→Build→Install→First recall, persisted across reloads → Step 7 (useOnboardingProgress + FirstRunChecklist).
    • "Use this in your agent" card with a copyable first recall → Step 6 (FirstCallCard on banner + share page).
    • Unauthenticated users browse seeded public namespaces before the gate → Step 8 (explore-public CTA → existing /showcase).
    • Copy buttons give visible "Copied" confirmation → Step 2 (CopyButton) applied across Steps 3, 4, 6.

    Effort
    M — ~3-4 person-days (Step 1 + Steps 2-4 = the independently shippable ~1 day read-token fix; Steps 5-9 = the guided-onboarding layer). Almost entirely apps/web/src/main.tsx + styles.css, with two small worker.ts touches.


    Implementation plan generated for execution (planning phase). Part of the roadmap (#4).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Demo-blocking: required for a working Sui Overflow demomcpModel Context Protocol servers and toolsonboardingUser and developer onboarding / first-run experienceui-uxWeb app UI/UX, accessibility, and responsive design

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions