Repository navigation
First-run flow: demo → namespace → MCP install (with read token) → first agent call #18
Description
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
-
ShareMcpInstallandHostedBuildBannersnippets 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
recallagainst 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 theAuthorizationheader, ~main.tsx:449-456) insideShareMcpInstall(main.tsx:2028); pull the token from/api/hosted/namespaces/:ns/tokens. - Replace static
QuickStartCard(main.tsx:4216) with a progress-tracked stepper; reuseShowcasePage(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
harrymove-ctrl commentedon Jun 19, 2026
Implementation Plan
Approach
The 401 trap is purely client-side: the server already mints correct, header-bearing snippets (
apps/api/src/worker.ts:3244-3293buildsclaudeDesktop/cursor/codex/generic/mcpRemotewith--header "Authorization: Bearer <readToken>"), butShareMcpInstall(main.tsx:2028) re-derives its own snippet without the header andHostedBuildBanner(main.tsx:4267) shows no install snippet at all. The chosen strategy: (1) introduce one shared frontend helperbuildMcpSnippets(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 alocalStorage-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
GET/POST /api/hosted/namespaces/:ns/tokens(workerlistNamespaceTokens/createNamespaceToken, routed via the/api/hosted/*→/api/*rewrite atworker.ts:581-583, regexmatchNamespaceTokenPathatworker.ts:912, gated byrequireImportAuthorizationatworker.ts:4436+assertManagedNamespaceatworker.ts:3629) and the already-publicGET /api/directory(worker.ts:687→listDirectoryNamespaces:1028, frontendShowcasePageatmain.tsx:2057).publicShareLink(worker.ts, the function returning the share JSON used bygetShareLink:1610) does NOT exposevisibility; the share page needs it to choose token vs no-token snippet. Addvisibility(and nothing secret) to that return.NamespacesAppPage" (main.tsx:5943, currently/app/namespacesusesNamespacesSimplePageatmain.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.CONTEXTMEM_NAMESPACE_IMPORT_TOKEN; ownership is only enforced when?ownerId=is passed (listNamespaceTokens:977,createNamespaceToken:994). See Risks — recommend deriving owner fromx-memwal-account-id(the pattern atworker.ts:1120) so a shared import bearer can't enumerate other owners' tokens.Step-by-step
Backend: expose
visibilityon the share payload. Inapps/api/src/worker.ts, addvisibility: row.visibility(or look it up viaCloudflareNamespaceStore.getNamespace(share.namespace)ifShareLinkRowlacks it) to the object returned bypublicShareLink. ConfirmgetShareLink(worker.ts:1610) still returns NO token. Update the frontendShareLinkResponse.sharetype (main.tsx:527) to addvisibility: "private" | "public".Frontend: add a shared snippet builder + reusable CopyButton. In
apps/web/src/main.tsx, addfunction buildMcpSnippets(mcpUrl: string, namespace: string, readToken?: string)that returns the same shape asworker.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, usesnavigator.clipboard.writeText`).Fix
ShareMcpInstall(main.tsx:2028). Change props to{ mcpUrl, namespace, visibility, isOwner, sessionToken }. Logic:public→ render no-token snippets frombuildMcpSnippets(mcpUrl, namespace)+ one-line note "Public namespace — no token needed." UseCopyButtonper snippet.private+isOwner→ show a "Generate install token" button that POSTscreateNamespaceToken(fetch(\${API_BASE}/api/hosted/namespaces/${ns}/tokens`, { method: "POST", headers: authHeaders(sessionToken), body: {label:"install", ownerId:} }), reusingauthHeadersatmain.tsx:7910), then renderbuildMcpSnippets(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) andSharePage(:1742), threadingshare.visibilityand anisOwnercomputed in step 5.Fix
HostedBuildBanner(main.tsx:4267). Extend theresultprop to{ shareId, namespace, shareUrl, mcpUrl, visibility, readToken? }. Render an "Install in your MCP client" block viabuildMcpSnippets(mcpUrl, namespace, visibility === "private" ? readToken : undefined)with the public note when public, plusCopyButtons. Update the producing statehostedBuildResult(declaredmain.tsx:839, set atmain.tsx:1280) to carryvisibility: body.job.visibilityand, for private+owner,readToken: body.job.result?.readToken(the demo/QuickStart path is public, so token is omitted there). Update the type atmain.tsx:2848.Compute
isOwneron the share page. InSharePage(aroundmain.tsx:1742), after loadingshare, attemptGET /api/hosted/namespaces/:ns/tokens?ownerId=<delegate account>withauthHeaders(sessionToken)(delegate account fromreadHostedDelegate()atmain.tsx:7938); HTTP 200 ⇒isOwner=true, else false. This never leaks a token (list returns onlyhashPrefixperworker.ts:980-988) and is the natural ownership probe. Skip the probe entirely when no delegate is present (anonymous viewer ⇒ not owner)."Use this in your agent" first-call card. Add
function FirstCallCard({ namespace, mcpUrl, readToken? })rendering a copyable concrete recall example in two tabs: (a) MCPtools/callJSON forcontextmem_read_artifact/recall (mirror the call inMcpPlaygroundatmain.tsx:4352-4355), and (b) acurltomcpUrlincluding-H "Authorization: Bearer <readToken>"when private. Mount it inHostedBuildBanner(next toMcpPlaygroundatmain.tsx:4301) and in the share-page MCP tab (main.tsx:1800).First-run checklist (persisted). Add
function useOnboardingProgress()backed bylocalStoragekeycontextmem.onboarding.v1using the existingsafeLocalGet/safeLocalSetwrappers (main.tsx:669-688, Safari-private-mode safe), storing{ demo, build, install, recall }booleans + timestamps. Auto-mark steps from real signals:demowhen aDemoPreviewStatereachescompleted(main.tsx:516),buildwhenhostedBuildResultis set (:1280),installwhen any install snippetCopyButtonfires,recallwhenFirstCallCardis copied/run. Replace the static body ofQuickStartCard(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.Explore-public-first entry. In
HostedCredentialGate(main.tsx:4522) and/orLockedPreview(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.Backend hardening (recommended, small). In
listNamespaceTokens/createNamespaceToken/revokeNamespaceToken(worker.ts:976/992/1013), derive the owner fromrequest.headers.get("x-memwal-account-id")when present (same approach asworker.ts:1120) and pass it toassertManagedNamespace, 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),hostedBuildResultstate+type (:839/:1280/:2848),ShareLinkResponsetype (:527),HostedCredentialGate/LockedPreview(:4522/:4576); new:buildMcpSnippets,CopyButton,FirstCallCard,FirstRunChecklist,useOnboardingProgress.apps/web/src/styles.css— styles forCopyButton, checklist stepper, first-call card, "explore public" CTA (reuse existing--cm-*tokens, brand green#a8d946).apps/api/src/worker.ts—publicShareLinkaddsvisibility; (recommended) header-derived ownerId in the three token handlers.Test plan
buildMcpSnippets— assert public output has noAuthorization/--header; private output exactly matches the worker shape (mcp-remote <url> --header "Authorization: Bearer <tok>"); snapshot vsworker.ts:3244-3293.bun test+ miniflare/D1): import aprivatenamespace, mint a token viaPOST /api/hosted/namespaces/:ns/tokens, assertGET /api/share-links/:idreturnsvisibility:"private"and no token field; assert the token list endpoint 200s with the owner'sownerId/account header and 403/404s without it (step 9).mcp-remotesnippet → runnpx -y mcp-remote <mcpUrl> --header "Authorization: Bearer <tok>"against the hosted Worker → expect tools/list + a successfulcontextmem_read_artifact; then drop the header → expect the 401 with hintNAMESPACE_TOKEN(worker.ts:8024). Also POST the MCP endpoint with the header and assert a 200 JSON-RPC result./share/:idin a logged-out browser → no token, no "generate token" button, "private — owner only" message; open as owner → token reveal works./showcasefrom the gate without a delegate.Risks & gotchas
getShareLink/publicShareLinktoken-free; the token is only ever fetched client-side by an authenticated owner (step 5). Addingvisibilityis safe (already shown on the namespace HTML page,worker.ts:864).listNamespaceTokensreturns onlyhashPrefix(worker.ts:980-988); the only source of a usable token iscreateNamespaceToken(:1007) or the build-timeHostedNamespaceImportResponse.readToken. Mint only on explicit click to avoid token sprawl.CONTEXTMEM_NAMESPACE_IMPORT_TOKENplus a guessedownerIdcould enumerate tokens — flag for team; step 9 closes it./api/demo/extractions(main.tsx:1212) and are public, so the banner correctly shows the no-token snippet; don't assumebody.job.result.readTokenexists there.mcpBase/isLocalApiBaselogic atmain.tsx:1278.safeLocalGet/Set(main.tsx:669) — rawlocalStoragethrows in Safari private mode.Acceptance-criteria mapping
--header Authorization: Bearer <read-token>for private, omit + note for public → Steps 2, 3, 4 (buildMcpSnippets,ShareMcpInstall,HostedBuildBanner)./share/*→ Steps 1, 3, 5 (visibility-only public payload, owner probe, mint-on-click).useOnboardingProgress+FirstRunChecklist).FirstCallCardon banner + share page)./showcase).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 smallworker.tstouches.Implementation plan generated for execution (planning phase). Part of the roadmap (#4).