Skip to content

fix(skills): make remember skill's project scoping transport-safe - #75

Open
irresi wants to merge 2 commits into
mainfrom
fix/remember-skill-transport-safe-project-scope
Open

fix(skills): make remember skill's project scoping transport-safe#75
irresi wants to merge 2 commits into
mainfrom
fix/remember-skill-transport-safe-project-scope

Conversation

@irresi

@irresi irresi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What

The membase-remember skill instructed every client to read the
membase://project MCP resource before a project-scoped save. That resource
only exists on the stdio server (the Claude Code plugin), where it resolves
the slug from process.cwd(). Remote-HTTP clients (Cursor, Codex, VS Code)
connect to the hosted server at https://mcp.membase.so/mcp, which has no
client filesystem and does not expose the resource — so the documented happy
path hard-failed with MCP -32602: Resource membase://project not found on
every project-scoped save.

Found while dogfooding the plugin on Codex CLI.

Why it hits real users

npx plugins add aristoapp/membase-plugin-mcp copies the whole repo — including
skills/ — into each client's plugin cache (verified:
~/.codex/plugins/cache/personal/membase/0.3.0/skills/remember/SKILL.md). So
the shared skill's instruction reaches remote-HTTP clients whose server can't
serve the resource. It breaks for 3 of the 5 clients.

Root cause

  • skills/remember/SKILL.md:15-16 — shared skill, no transport caveat, told
    clients to read membase://project.
  • packages/stdio-runtime/src/mcp/server.ts:725 — the resource is registered
    only on the stdio server and resolves via
    resolveProjectSlug(process.cwd(), config) — inherently local-filesystem.
  • The hosted remote server has no client cwd, so it architecturally cannot
    expose the same resource.

Fix

Rewrite the guidance to be transport-safe: derive the project slug from the
repository (git remote or directory name) and pass it to add_memory, or omit
it when unknown; keep membase://project documented as a Claude-plugin-only
optimization. Skill-only change — no bundle rebuild.

-When storing repository-specific context through MCP, read `membase://project`
-and pass the project slug explicitly.
+When storing repository-specific context, scope it to the project by passing a
+`project` slug to `add_memory`. Derive the slug from the repository — its git
+remote name or the working-directory name (e.g. `aristoapp/membase-plugin-mcp`
+→ `membase-plugin-mcp`). If you can't determine a slug, omit `project` rather
+than guessing. On the Claude Code plugin only, the `membase://project` resource
+returns the already-resolved slug; other clients (remote MCP) do not expose it,
+so don't depend on reading it.

Alternatives considered

  • Expose the resource on the remote server — out of scope (separate private
    service; can't know client cwd). Rejected.
  • Add a get_project tool — still stdio-only, same transport coupling,
    extra surface. Rejected.
  • Drop project scoping entirely — regresses a real feature (add_memory has
    a project param; "persistent project context" is the headline use case).
    Rejected.
  • Transport-safe prose (this PR) — works on both transports, ships to all
    clients, minimal blast radius. Chosen.

Verification

  • pnpm check — all 21 guards green (bundle-provenance confirms no bundle drift;
    this is a source-of-truth skill edit, not a generated artifact).
  • pnpm test — full suite green (41 contract + capture-core/stdio/openclaw/
    cursor/hermes).
  • Live repro on Codex (remote MCP): the same store task that previously fired
    list_mcp_resources ("Unexpected response type") and
    read_mcp_resource(membase://project) (-32602) now makes zero resource
    calls
    and stores in a single add_memory call with the correctly-derived
    project: "membase-plugin-mcp" slug.

Before: 3 calls, 2 errors. After: 1 call, 0 errors.

Scope note

Leaves the stdio server's startPromptText (server.ts:165) mention of
membase://project untouched on purpose — that text only ships on the stdio
server, which does expose the resource, so it's correct in context and
editing it would force an unnecessary bundle regen.

irresi added 2 commits July 15, 2026 15:24
The membase-remember skill ships to every client via the one-command
installer (npx plugins add copies skills/ into each client's plugin
cache). It instructed all clients to read the membase://project MCP
resource, but that resource only exists on the stdio server (Claude
plugin), where it resolves the slug from process.cwd(). Remote-HTTP
clients (Cursor, Codex, VS Code) connect to the hosted server, which has
no client filesystem and does not expose the resource — so the documented
happy path hard-failed with MCP -32602 on every project-scoped save.

Rewrite the guidance to be transport-safe: derive the project slug from
the repository (git remote or dir name) and pass it to add_memory, or omit
it when unknown; keep membase://project as a documented Claude-plugin-only
optimization. Skill-only change (no bundle rebuild).

Verified live on Codex (remote MCP): the same store task that previously
fired list_mcp_resources ('Unexpected response type') and
read_mcp_resource(membase://project) (-32602) now makes zero resource
calls and stores in one add_memory call with the correct project slug.
Independent review caught a real correctness bug in the first cut: the
example 'aristoapp/membase-plugin-mcp -> membase-plugin-mcp' dropped the org
and kept only the basename, but the plugin's resolveProjectSlug normalizes
the FULL git-remote URL path (owner/repo, '/' -> '-'), yielding
'aristoapp-membase-plugin-mcp'. A remote client following the old example
would scope to a different slug than the Claude plugin on the same repo and
silently split memories across transports — defeating the cross-tool
continuity the fix is meant to preserve.

Describe the exact normalization (remote URL path, lowercased, non-alphanumeric
runs -> single '-') with a correct worked example, and clarify it's the remote
URL, not a remote name.

Verified live on Codex: a fresh session now runs 'git remote get-url origin'
and produces project 'aristoapp-membase-plugin-mcp', matching the plugin's
own auto-resolution.
@irresi

irresi commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Independent review round → fixed a real correctness bug

An independent reviewer flagged that the first cut's slug example was wrong in a way that would silently split memories across transports:

  • My example: aristoapp/membase-plugin-mcp → membase-plugin-mcp (basename only).
  • The plugin's resolveProjectSlugremoteSlug normalizes the full remote URL path (owner/repo, /-), producing aristoapp-membase-plugin-mcp.

So a remote-MCP client following the old example would scope to membase-plugin-mcp while the Claude plugin auto-resolves to aristoapp-membase-plugin-mcp on the same repo — two different slugs, memories split. That directly undercuts the cross-tool continuity this fix exists to protect.

Fixed in 6ab5090: the skill now describes the exact normalization (remote URL path, lowercased, non-alphanumeric runs → single -) with a correct worked example, and clarifies it's the remote URL not a remote name.

Verified live on Codex: a fresh session now runs git remote get-url origin and produces project: "aristoapp-membase-plugin-mcp" — matching the plugin's own auto-resolution. Both transports converge on the same slug. pnpm check still green.

Follow-up (separate issue, not blocking): add a cheap lint guard asserting no skills/**/SKILL.md instructs reading a stdio-only membase:// resource, to prevent silent reintroduction. Fits the existing check-docs-markers / parity-guard culture.

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