Skip to content

fix(mcp): let ChatGPT save to the default space when the account has no spaces - #1889

Merged
capy-ai[bot] merged 8 commits into
mainfrom
capy/let-chatgpt-save-to
Nov 11, 2026
Merged

capy-ai[bot] merged 8 commits into
mainfrom
capy/let-chatgpt-save-to

Conversation

@MaheshtheDev

@MaheshtheDev MaheshtheDev commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Problem

ChatGPT users with Read and write access and zero spaces could not save: the Add Memory and Upload widgets had no space to offer, so Save stayed disabled (support ticket T-2159). Instead of silently falling back to sm_project_default, the widgets now offer console's "Search or create tags…" combobox, so the user can type a name and create the space in place.

flowchart LR
  S[guided-save / upload-file] --> G{canCreateNamespace?<br/>not read-only, not scoped,<br/>not restricted}
  G -- yes --> P[Search or create tags combobox<br/>0 spaces: empty, Save disabled until a tag is created<br/>has spaces: list + Create &lt;tag&gt;]
  G -- no --> L[Granted spaces only<br/>or blocked reason]
  P --> W[save-memory / upload with new tag<br/>space created on first write]
  A[add_memory from chat, 0 spaces] --> D[sm_project_default fallback, unchanged]
Loading

Changes

  • canCreateNamespace mirrors the API write guard: a new tag is writable only without a read-only grant, a scoped-key tag list, or a restricted-member allowlist.
  • spaceWriteTargets feeds both widget tools: creators get their spaces plus the active one, but with no spaces at all they get an empty list (no default fallback, no stale active space) and canCreateNamespace: true; everyone else keeps the granted list, active space included, and writeBlockedReason.
  • New SpaceField (shared by Save and Upload): one "Search or create tags…" combobox for zero and existing spaces. With zero spaces nothing is preselected, Save/Upload stays disabled and the hint reads "Type a name to create your first space". A "Saved as " hint appears only when sanitizing changed the typed name (e.g. "Reading list" → Reading_list).
  • SpaceSelect gains onCreate: "Search or create tags…" placeholder, "Create " row, Enter to create, hidden when the tag already exists (case-insensitive).
  • sanitizeNamespace/isValidNamespace copy console-v2's sanitizeContainerTag/isValidContainerTag, so the widget saves the same key the console would.
  • Buttons read "Create space and save" / "Create space and upload" when the target is new.
  • add_memory, list_spaces and select-space keep the sm_project_default fallback and copy from the earlier commits.
  • Studio gains "Save · no spaces", "Save · scoped, no create" and "Upload · no spaces" frames.

Verification

Local mono API (wrangler + Postgres) with this worker at c7eb3ee on wrangler dev. The harness makes the widget's own calls: guided-save/upload-file for the payload, the widget's sanitizeNamespace and create-row rule for the typed name, then save-memory or prepare-file-upload plus a multipart POST. Synthetic sm_ keys only; no staging or production data.

Case Combobox Write result
Full read/write, 0 spaces, types "Reading list" writableNamespaces: [], nothing preselected, "+ Create Reading_list", "Saved as Reading_list" doc lands in Reading_list; list_spaces and the next guided-save list it
Same org (now has spaces), types "Recipes" "+ Create Recipes", no hint; retyping "recipes" shows no create row doc lands in Recipes
Upload, types "Meeting notes" "+ Create Meeting_notes" file lands in Meeting_notes
Scoped key (work) canCreateNamespace: false, no create row (Save and Upload) forced Recipes save and upload refused: "Access restricted to work"
Restricted member (team=write, notes=read) canCreateNamespace: false, no create row (Save and Upload) forced Recipes save and upload refused: "Access restricted to notes, team"
add_memory from chat, 0 spaces n/a sm_project_default fallback (server unchanged since 8734c7e run)

tsc (server + widget), biome check and vitest run src pass.

LOCAL SYNTHETIC widget studio preview of the create-a-space flow

LOCAL SYNTHETIC: widget studio preview taken at c7eb3ee, rendering the real Save/Upload components with mock data. Still matches 9a96d2c, which only deletes a test file.

MaheshtheDev and others added 2 commits November 10, 2026 18:08
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ble active space

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
supermemory-app 9a96d2c Commit Preview URL

Branch Preview URL
Nov 11 2026, 06:31 AM

@capy-ai
capy-ai Bot marked this pull request as ready for review November 10, 2026 18:19
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
supermemory-mcp 9a96d2c Nov 11 2026, 06:31 AM

@polylane polylane Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observability suggestions for this change.

Turn off suggestions

textContent(
await deps
.getSession()
.then(emptySpacesMessage, () => "No spaces found."),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A failed session lookup is swallowed, so an operator cannot tell an auth outage from an empty account.

Suggested change
.then(emptySpacesMessage, () => "No spaces found."),
.then(emptySpacesMessage, (error) => {
console.error("list_spaces session lookup failed:", error)
return "No spaces found."
}),

@polylane

polylane Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Note

This change is unlikely to harm production.

I checked supermemory-mcp in production: 26k–47k requests/hour over 72h, 0 invocation errors, p99 CPU flat at 71 ms. The runtime diff adds optional view fields and a widget-side create-space picker only. No server-side space check, binding, or env value changes.

View the full analysis →

Also considered · 4 refuted
  • Refuted · Create-space picker lets a caller write into a space outside its grant · spaceWriteTargets and canCreateNamespace only shape the view message; the write still goes through the API with the caller's own key, and the branch's own verification records refusals for scoped and restricted keys.
  • Refuted · A cached widget from the previous worker version breaks on the new view payload · The added fields are optional and default to the previous behaviour, and a Worker deploy switches the whole script version atomically, so no old widget meets a payload it cannot parse.
  • Refuted · The rewritten default-space fallback moves reads to another space for restricted members · The changed case needs a read-only default grant plus another writable space; the new target is a space the same session can already read, requests do not fail, and an explicit or active space still wins.
  • Refuted · list_spaces gains a session lookup that becomes a new failure path · The rejection handler keeps the old message, so the new call cannot turn a working call into an error result.

supermemory-mcp · requests per hour

supermemory-mcp · p99 CPU time, no-change forecast

Analysed against 1 Cloudflare Worker and 1 repository

View in Polylane Pause assessments

Polylane analysed 9a96d2c for production impact. You can ask follow-ups by mentioning @polylane in a comment.

Previous verdicts (5)
Head Verdict Analysis
c7eb3ee This change is unlikely to harm production analysis
33881e7 This change is unlikely to harm production analysis
8734c7e This change is unlikely to harm production analysis
bcb3a3c This change is unlikely to harm production analysis
1a73f0f This change is unlikely to harm production analysis

MaheshtheDev and others added 5 commits November 10, 2026 18:57
Callers who can write new tags get console's type-to-create in the space
picker and a first-space prompt when they have no spaces, instead of the
sm_project_default fallback. Read-only, scoped and restricted callers keep
their granted spaces only. Direct add_memory still falls back to the default.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…e spaces

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…paces

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…ing spaces

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@polylane polylane Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observability suggestions for this change.

Turn off suggestions

Comment on lines +35 to +38
await deps
.getSession()
.then(emptySpacesMessage, () => "No spaces found."),
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Records the failed session read, so a backend failure is no longer indistinguishable from a genuine empty space list.

Suggested change
await deps
.getSession()
.then(emptySpacesMessage, () => "No spaces found."),
),
await deps.getSession().then(emptySpacesMessage, (error) => {
console.error("list_spaces session lookup failed:", error)
return "No spaces found."
}),

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capy found no issues.

Open in Capy

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

@polylane polylane Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observability suggestions for this change.

Turn off suggestions

Comment on lines +35 to +37
await deps
.getSession()
.then(emptySpacesMessage, () => "No spaces found."),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rejection handler hides why the empty-spaces message fell back, so an operator cannot count or diagnose failed session lookups.

Suggested change
await deps
.getSession()
.then(emptySpacesMessage, () => "No spaces found."),
await deps
.getSession()
.then(emptySpacesMessage, (error) => {
console.warn(
"No spaces found: session lookup failed for the empty-spaces message.",
error instanceof Error ? error.message : String(error),
)
return "No spaces found."
}),

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 9a96d2c. Capy's review at c7eb3ee found no issues. 9a96d2c deletes only apps/mcp/src/server/space.test.ts, which Mahesh confirmed ("keep the deletion and merge now"). CI is green.

@capy-ai
capy-ai Bot merged commit 8af3b91 into main Nov 11, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant