You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Status (2026-04-19): prerequisites resolved, design confirmed, ticket clear to implement
Both open prerequisites from the upstream investigation are now
closed empirically. Summary of the research verdict so this
ticket's readers don't have to chase the separate research repo:
Mechanism Update debug-agent-tests skill: log review as verification on every run #3 (asymmetric built-in-tool grants via agent
frontmatter) — closed as architecturally not viable. Claude
Code subagents inherit parent permissions with additional
tool-type restrictions only; there is no documented form of
per-agent path-scoped tool grants. Confirmed both by
documentation and empirical test.
Mechanism Agent interface contract: parent-facing skill, schema enforcement, and discovery #4 (key embedded in subagent .md body) —
confirmed confidential in the plugin-distributed case. Plugin
install paths live outside the user's project cwd, and the
parent's default Read is cwd-bounded, so the bundled
subagent's .md isn't reachable to the parent without an
explicit grant that the user would have to consciously set.
No additional project-level path-deny hardening required for
the shippable plugin-distributed form. (Path-deny hardening
is only relevant for the degraded project-scope packaging
variant.)
Mechanism Schema redesign: SARIF-aligned file findings, ruleId foreign keys, local-only scope #5 (plugin-bundled capability-broker MCP with
key in subagent body) — empirically validated end-to-end.
Plugin loads, bundled MCP server spawns, bundled subagent
invokes load_context(key), broker returns the mapped
content, parent sees it via the subagent's reply. Parent
without a valid key cannot extract content. Chained-attack
integration test (parent tries to Read the subagent .md to
extract the key) was blocked by the cwd boundary without any
extra hardening.
Mechanism CI/CD integration: privacy scanning in GitHub Actions for PRs #6 (hook-based caller-identity enforcement via parent_tool_use_id) — closed as not implementable with
current Claude Code primitives. PreToolUse hook stdin payload
carries session_id, transcript_path, cwd, permission_mode, hook_event_name, tool_name, tool_input, tool_use_id — NOT parent_tool_use_id.
The hook layer has no way to distinguish parent-scope from
subagent-scope invocations using the standard stream-json
scope signal. Mechanism Schema redesign: SARIF-aligned file findings, ruleId foreign keys, local-only scope #5 stands without it.
Shippable recipe for this ticket (minimal, no degraded
variants):
Plugin bundles: .claude-plugin/plugin.json, .mcp.json
wiring ${CLAUDE_PLUGIN_ROOT}/server.py, server.py
exposing load_context(key) -> str reading a user-XDG
key→.md-file map, hooks/hooks.json for the SessionStart pip install -t ${CLAUDE_PLUGIN_DATA}/site-packages
pattern (canonical, from claude-plugin-creator), and agents/<subagent>.md with its specific key embedded in
the body.
Settings (user-scope, project-scope, or plugin-shipped —
verify which carries by running the installed plugin)
include permissions.allow: ["mcp__plugin_<plugin>_<server>__*"]
so the subagent's tool invocation isn't gated on an
interactive approval that doesn't arrive in -p mode.
No additional path-deny hardening required for the
plugin-distributed form. The cwd boundary is doing the work.
The remaining implementation work is writing the plugin's server.py, the key-map schema, the subagent .md, and
verifying the settings grant lands at an appropriate scope for
users installing the plugin.
Problem
Some agents bundled with this plugin need access to configuration
or reference content that the plugin's orchestrating parent (and
any other agent in the session) must not see. The canonical
example is a scanning agent that needs a user-maintained catalog
of identifiers or patterns to look for in other artifacts; the
orchestrating parent never needs that catalog and must not end
up with it in its context window (where it would bleed into test
fixtures, code comments, commit messages, or other emissions).
The implementation mechanisms Claude Code documents for this
privilege-separation shape and their current status:
The most obvious mechanism — inline mcpServers: in a
subagent's frontmatter — is documented but has open upstream
bugs preventing reliable invocation
(anthropics/claude-code#13898, #25200, #33689),
and a companion limitation closed as not planned
(#29655)
— subagents do not receive MCP server instructions.
The workarounds suggested in those issue threads either
defeat context isolation (move the MCP server to global,
have the parent orchestrate the call) or require a whole
different architecture (external MCP server that mediates
agent + tool relationships).
So a plugin that wants to keep sensitive context out of the
parent's context window — but also wants to keep shipping as a
normal plugin that any user can install — has no clean
first-party mechanism today.
Proposed solution
Ship a small MCP server inside this plugin that exposes a
single tool for loading content by opaque key. Embed the key
that identifies a given agent's required context inside that
agent's .md body (not its frontmatter description, not
project settings, not anywhere the parent would naturally
read). The parent can technically call the tool, but without
the key it cannot pivot the tool into a useful lookup.
This turns a filesystem-permission question ("is the parent
blocked from reading this path?") into a capability-token
question ("does the caller know the right key?"), which is
strictly weaker but much more portable across environments
and does not depend on the broken-upstream features above.
Interface
Single tool, plain stdio MCP, bundled with this plugin:
load_context(key: str) -> str
key is a short opaque string. The server does not enumerate
valid keys over the wire; there is no list_keys() tool.
Valid keys are learned only by being embedded in agent
definitions that are authorized to call this tool.
Returns the raw content of a Markdown file mapped to that
key, or a structured error if the key is unknown, the
mapped path is missing, or the mapped file is not a .md
file.
Key → path mapping
The server reads a user-maintained config file at a
conventional location under the user's XDG config base (exact
path TBD at implementation; pick a location namespaced to this
plugin, e.g. $XDG_CONFIG_HOME/<namespace>/context-map.toml,
with ~/.config/ fallback when XDG_CONFIG_HOME is not set).
Config shape (sketch — finalize during implementation):
# Keys are opaque identifiers agents embed in their .md body.# Paths may be absolute, home-relative (~/...), or relative to# a conventional base directory.
[keys]
some-key-name = "~/some-relative/path.md"another-key = "/absolute/path/to/file.md"
Format restriction
The server loads ONLY files with a .md extension. This is a
defense-in-depth layer: even if the parent somehow learned a
valid key, it cannot pivot the tool into a general-purpose
filesystem read by pointing a key at /etc/passwd or similar.
Unknown extensions return a structured error.
Install / bundling
Bundled in this plugin using the direct-source + explicit-deps
pattern documented canonically in echomodel/claude-plugin-creator/docs/plugin-patterns.md: ${CLAUDE_PLUGIN_ROOT}/server.py, deps installed via the
SessionStart hook into ${CLAUDE_PLUGIN_DATA}/site-packages by
diffing requirements.txt. No separate pipx install step for
users. Do not use uvx-based invocation — uv is not yet
ubiquitous on user machines and would be a portability regression.
Do not use an in-package version-string compare as the install
trigger (see the antipattern section
of the patterns doc for the failure-mode analysis).
./agent script reconsideration (forced by this change)
This repo currently ships a ./agent script that supports
standalone installation of the privacy agents outside the plugin.
Introducing a bundled MCP server means that path — which today
covers agent .md files only — no longer gives standalone users
the full privacy tooling. Maintaining the ./agent path in
parallel with the bundled MCP server risks divergence and
maintenance cost that may not be worth it.
Decide at implementation time among:
Deprecate ./agent. Standalone users install via
documented pipx install git+<url>@<tag> (or similar) once the
MCP server ships with a pyproject.toml entry point. Simplest
maintenance; drops a distribution path we built ad-hoc.
Extend ./agent to also register the MCP server
alongside the agents. Preserves the existing UX; adds complexity
and couples the script to the MCP install path.
Leave ./agent alone and accept that standalone users get
the agents but not the MCP-backed features. Cheapest but
silently degrades functionality for existing standalone users.
Record the decision and rationale in CONTRIBUTING.md and README.md at the moment of implementation. Do not preserve the
path out of inertia; the default expectation is deprecation
unless a concrete user-facing reason to keep it is identified.
Who calls it
Only the plugin's own subagents that are specifically
authorized for privileged context lookups. Each such agent's .md body contains the exact key it should call with. Other
agents in the plugin do not receive keys and therefore cannot
usefully invoke the tool even though they can see it in their
MCP catalog.
Why this is YAGNI on the broader concern
It is tempting to frame this as "a general-purpose capability
broker" and design the full Vault-style abstraction up front:
multiple namespaces, role-based access, key rotation, audit
logging, a bulk-import skill, a web-facing admin, etc. That
design space is real and interesting, but:
We have exactly one concrete use case today — the
scanning-agent-needs-PII-catalog pattern. Building for
imagined future use cases of "shared config", "secrets
broker", or "per-agent capability scoping" produces a
larger and more abstract surface without serving any
consumer we actually have.
The interface as specified here is forward-compatible.
A future evolution to richer semantics can layer on top:
add namespace prefixes to keys, add new tools alongside load_context, move the key→path resolver into a shared
library. Nothing in this design traps us.
We are partially using this as a workaround to Claude
Code product limitations, not as a considered
architectural layer. Acknowledging that: we should not
over-invest in an ecosystem-flavored design for what is, in
part, a patch around upstream bugs. If those upstream issues
close with fixes that enable the cleaner "inline-MCP-in-
subagent-frontmatter" path, we may want to fold this MCP
server back down or remove it entirely. Keeping the surface
small now keeps that option open.
We acknowledge the broader general-purpose-context-broker
framing and may revisit it later. This ticket explicitly does
NOT attempt it.
Upstream issues driving this design — document and revisit
Because part of our rationale for building this MCP server is
that upstream Claude Code limitations block cleaner
alternatives, the upstream tickets that define those
limitations are load-bearing context. If any of them close
with a fix that enables the cleaner path, we should
reconsider whether to keep this MCP server at all, or fold it
down to a minimal bridge, or rip it out entirely.
CONTRIBUTING.md should carry a section that:
Lists each upstream issue with title, link, current status
as of the section's last update, and one-line summary of
what it blocks for us.
Explicitly notes this is a revisit list — when any of
these tickets changes state (closes with a fix, gets a
concrete design proposal, or gets declined as
"not planned"), we should re-evaluate the design captured
in this repo.
Recommends a periodic cadence (e.g., quarterly, or any
time a major Claude Code release lands) for a maintainer
or agent to walk the list, update the statuses, and open
a follow-up ticket here if any design decision becomes
worth revisiting.
Records the date and claude-code version of the most
recent check so readers can tell how stale the listed
statuses are.
The upstream issues to seed the list:
anthropics/claude-code#13898
— "Subagents hallucinate instead of calling MCP tools from
project-scoped servers." Blocks the inline-MCP-in-subagent
path; if fixed, reconsider whether a plugin MCP server for
context lookup is still needed.
anthropics/claude-code#25200
— "Custom agents cannot use deferred MCP tools." Blocks
per-agent tools: whitelisting of MCP tool names as a
privilege-separation mechanism.
anthropics/claude-code#29655
— "Subagents do not receive MCP server instructions."
Closed "not planned"; an architectural limitation that
shapes the workaround. If reopened and fixed, tool
descriptions could carry their own invocation context
without embedding keys in agent bodies.
anthropics/claude-code#33689
— "Sub-agent-scoped MCP configs silently ignored in
plugin-distributed agents." Blocks scoping an MCP server
to a specific plugin agent via agent-file frontmatter.
If any of these ship fixes that enable the cleaner first-party
alternative, the follow-up work is to evaluate reverting this
ticket's code and simplifying to the now-working first-party
mechanism.
Prerequisite verdict (2026-04-19): asymmetric-tool-grant mechanism is architecturally dead — ticket proceeds as specified
The prerequisite below has been resolved. The
asymmetric-built-in-tool-grant mechanism is not viable —
empirically and per upstream documentation — so this ticket
proceeds as originally designed. Evidence:
Empirical result: an independent experimental workstream
tested a subagent whose frontmatter declared bare tools: [Read] attempting to Read an outside-cwd file that
the parent could not Read. The subagent was denied with the
same cwd-boundary permission check the parent hit. The
subagent's tools: frontmatter field did NOT expand
filesystem reach — it is a capability allowlist (which tool
TYPES the subagent can use), not a path grant.
Documentation confirmation:
https://code.claude.com/docs/en/sub-agents — "Each [subagent] inherits the parent conversation's
permissions with additional tool restrictions."
Subagents can only have LESS access than the parent via
frontmatter, never more.
https://code.claude.com/docs/en/permissions — path-scoped Read(<path>) patterns are documented only for settings.json (session scope). No documented form of
per-agent path-scoped tool permissions exists.
Untested variants (speculative tools: [Read(<path>)]
syntax, per-agent permissions.deny) are undocumented, and
relying on undocumented behavior for a security primitive is
inappropriate regardless of whether empirical probes might
succeed.
Decision: proceed with this ticket's original design —
the capability-broker MCP server with keys embedded in the
subagent's .md body.
The two remaining empirical prerequisites for THIS ticket's
design to work (replacing the old single prerequisite):
Does plugin-bundled MCP invocation work reliably from
a subagent? — the upstream bugs listed above affect
inline MCP in subagent frontmatter; the community
workaround is "move the MCP server to plugin or global
scope." Empirical confirmation still pending.
Both are being investigated in the same experimental
workstream that produced the mechanism-#3 verdict. This
ticket remains BLOCKED on those two sub-questions, but no
longer blocked on the (now-closed) primary prerequisite.
Prerequisite: verify asymmetric built-in-tool grants first
Before proceeding with the workaround-flavored MCP server in
this ticket, one of the alternative mechanisms — asymmetric
grants on built-in tools (Read / Bash), where the subagent
has access to sensitive content that the parent does not —
should be empirically verified. That mechanism, if it works,
would make the MCP server in this ticket unnecessary, because
the standard built-in-tool permission surface plus subagent
context isolation would already solve the problem without any
plugin-level machinery.
That verification is being conducted separately by repo
contributors in an experimental research workstream. Any
contributor who wants visibility into that work can reach out
to a maintainer; the decision criteria and the verdict will be
documented back into this ticket (and a successor ticket if
the verdict points at a different design) before
implementation begins.
Exit criteria for the prerequisite
The separate investigation is complete for the purposes of
this ticket when all of the following are recorded here or in
a linked doc:
Paired positive/negative test evidence showing, for built-in
Read (and ideally Bash) tools, whether the subagent can
access a path the parent cannot, under configuration applied
via frontmatter or project settings — the key question being
whether the asymmetry is structurally enforceable.
A clear verdict: the asymmetric-grant mechanism works
reliably / works with caveats / does not work.
A stated next action for this ticket based on that verdict:
close as not-needed, proceed as specified, or revise the
design.
Even if the prerequisite confirms #3 works, it isn't an automatic choice
Mechanism #3 — asymmetric grants on a built-in tool like Read — has an inherent cost that the prerequisite test
will not by itself resolve: it requires embedding a
concrete filesystem path (or glob) in the agent definition
or in project/plugin settings. The subagent's tools:
frontmatter entry, or the settings' permissions.allow
pattern, has to name where the sensitive content lives.
That has real downsides independent of whether the
asymmetric-grant mechanism works:
Portability. Users on different OSes, different XDG
overrides, or different personal conventions have content
in different places. A hard-coded path means either
shipping multiple variants, accepting fragility, or forcing
every user into one specific location.
Permission-surface shape. A path-specific Read(...)
grant exposes exactly one specific filesystem pattern to a
generic Read tool. Security review has to reason about
both what Read can do and what paths it is allowed on. A
custom tool with a tight interface (takes an opaque key,
only loads .md files, only from a map the user controls)
is easier to reason about: one MCP-tool-specific permission
that names a single well-scoped tool.
Install UX. A custom MCP tool gets one permission
approval per install (or zero if the plugin marketplace
handles it). Per-path Read grants either require manual
edits to settings or a plugin-supplied settings delta that
has to be correct for every user's file layout.
Evolution. If the set of loadable artifacts grows or
changes, the custom-tool side only requires editing a
user-land config file. The path-grant side requires
editing agent definitions or settings — published and
versioned artifacts.
So even in the branch where the prerequisite verifies #3
works structurally, the verdict for THIS ticket should be a
two-part question, not a one-part question:
Does the asymmetric-grant mechanism work reliably?
Is its path-hardcoding shape acceptable for the use
case at hand, given the portability and permission-surface
tradeoffs above, OR is the custom-tool surface proposed in
this ticket preferable on those grounds independent of the
upstream-bug-workaround motivation?
That second question deserves a deliberate answer documented
alongside the first. It is entirely possible that #3 works
and we still build this MCP server because the permission
surface and portability are strictly better.
Update debug-agent-tests skill: log review as verification on every run #3 works BUT the custom-tool shape is preferred anyway.
This ticket proceeds, with the
security/portability/permission-surface argument
explicitly captured in CONTRIBUTING.md alongside the
upstream-bug-workaround framing so the rationale is not
solely "we had to."
Work on this ticket should NOT begin until the prerequisite
is resolved AND the two-part question above has a
deliberate, documented answer.
"How else might we do this" — preserved-alternatives capture
To avoid re-deriving the design space from scratch in a future
session, CONTRIBUTING.md (or a linked sibling .md, e.g. docs/context-mcp-design.md) should capture the alternative
mechanisms that WOULD become viable or attractive if one or
more of the upstream issues above close, along with what
enables each and what the shape of the alternative would be.
These are NOT decided preferences. They are notes-to-self:
research was done once; capturing it at head means a future
session reconsidering the design can start from the saved
sketch rather than re-evaluating from zero.
Each captured alternative should include:
Short name for the mechanism.
Which upstream issue(s) closing with a fix would unlock or
make it attractive (cross-reference the issue list above).
One-paragraph sketch of the shape — what the configuration
would look like, where the sensitive content lives, how the
parent is structurally excluded.
One-paragraph honest account of why it is NOT the chosen
path today — upstream blocker(s), tradeoff against the
current design, or both.
Whether adopting it would deprecate this MCP server
entirely, replace part of it, or complement it.
Suggested mechanisms to capture at minimum (expand as
appropriate during implementation):
Inline MCP server declared in the subagent's
frontmatter. Sensitive content accessed via a tool whose
MCP server is declared mcpServers: on the subagent's .md. Parent's tool catalog structurally excludes it.
Unlocked by: #13898 and/or #25200 closing with a fix.
Project-scope MCP + per-agent tools: whitelist. MCP
server declared once at project or plugin scope; each
agent's frontmatter whitelists which of its tools to
surface. Parent's whitelist excludes the sensitive tool;
subagent's includes it. Unlocked by: #25200 and/or #33689
closing with a fix.
Asymmetric built-in-tool grants (Read/Bash). No MCP at
all. Subagent granted Read on the sensitive file via its
frontmatter or project settings; parent explicitly denied
via permissions.deny or by tool-restriction frontmatter.
Subagent context isolation prevents raw content from
bleeding back. Unlocked by: empirical verification of
parent/subagent asymmetry for built-in-tool grants (does
not depend on any of the upstream MCP bugs being fixed —
currently blocked only by untested-ness).
Hook-based caller-identity enforcement. A PreToolUse hook rejects tool invocations whose parent_tool_use_id indicates parent scope, allowing only
subagent-originated calls. Layered on top of any of the
mechanisms above as defense-in-depth. Unlocked by:
empirical confirmation that hook payloads expose parent_tool_use_id and that hooks can structurally abort
tool calls based on it.
The captured section should make clear that any future move
off the current design is driven by evidence (upstream fix
landed, empirical verification completed, etc.) and that the
current design stands until that evidence arrives.
Deliverables
New MCP server source under this plugin (e.g. server.py at plugin root, if claude-coding adopts the same
layout as the scaffold plugin).
.mcp.json entry wiring the server via ${CLAUDE_PLUGIN_ROOT} + ${CLAUDE_PLUGIN_DATA}
PYTHONPATH pattern.
requirements.txt listing the mcp package (and
anything else the server needs).
hooks/hooks.json SessionStart hook using the requirements.txt-diff install-trigger pattern per plugin-patterns.md.
Do not use an in-package version-string compare.
Config loader in the server that reads the user's context-map.toml (or chosen format/name) from the chosen
XDG location.
.md-only format gate in the load_context tool
implementation.
Update the relevant subagent's .md body to embed the
key it should call with (no key in frontmatter; no key in
project settings).
Resolve the ./agent script question (deprecate / extend
/ leave) per the section above. Record decision and rationale
in CONTRIBUTING.md and update README.md install
instructions to match.
Update CONTRIBUTING.md with a section explaining the
design and the YAGNI + workaround framing above, so the head
revision documents why this shape was chosen over the more
obvious alternatives. Include a short pointer (not a
restatement) to plugin-patterns.md for the install-trigger
pattern and antipattern rationale — one canonical source,
one pointer.
Out of scope
A general-purpose capability-broker design with namespaces,
audit logs, multi-tenancy, etc.
An admin / editor CLI for maintaining the config map. Users
edit the TOML by hand for now.
Integration with any secrets-management product.
Revisiting the design if the upstream subagent-inline-MCP
bugs close with fixes that eliminate the motivation — that's
a future ticket, not this one.
Verification
After the work lands, a reader of this plugin's head revision
should be able to answer, from CONTRIBUTING.md and the
server source alone:
What does the load_context tool do and what keys are
valid?
Where does the key→path mapping live?
Why does this plugin carry a load_context tool at all,
and why not something more general?
What upstream Claude Code limitations is this partly
working around, and under what conditions would we
reconsider keeping it?
Status (2026-04-19): prerequisites resolved, design confirmed, ticket clear to implement
Both open prerequisites from the upstream investigation are now
closed empirically. Summary of the research verdict so this
ticket's readers don't have to chase the separate research repo:
frontmatter) — closed as architecturally not viable. Claude
Code subagents inherit parent permissions with additional
tool-type restrictions only; there is no documented form of
per-agent path-scoped tool grants. Confirmed both by
documentation and empirical test.
.mdbody) —confirmed confidential in the plugin-distributed case. Plugin
install paths live outside the user's project cwd, and the
parent's default
Readis cwd-bounded, so the bundledsubagent's
.mdisn't reachable to the parent without anexplicit grant that the user would have to consciously set.
No additional project-level path-deny hardening required for
the shippable plugin-distributed form. (Path-deny hardening
is only relevant for the degraded project-scope packaging
variant.)
key in subagent body) — empirically validated end-to-end.
Plugin loads, bundled MCP server spawns, bundled subagent
invokes
load_context(key), broker returns the mappedcontent, parent sees it via the subagent's reply. Parent
without a valid key cannot extract content. Chained-attack
integration test (parent tries to Read the subagent
.mdtoextract the key) was blocked by the cwd boundary without any
extra hardening.
parent_tool_use_id) — closed as not implementable withcurrent Claude Code primitives. PreToolUse hook stdin payload
carries
session_id,transcript_path,cwd,permission_mode,hook_event_name,tool_name,tool_input,tool_use_id— NOTparent_tool_use_id.The hook layer has no way to distinguish parent-scope from
subagent-scope invocations using the standard stream-json
scope signal. Mechanism Schema redesign: SARIF-aligned file findings, ruleId foreign keys, local-only scope #5 stands without it.
Shippable recipe for this ticket (minimal, no degraded
variants):
.claude-plugin/plugin.json,.mcp.jsonwiring
${CLAUDE_PLUGIN_ROOT}/server.py,server.pyexposing
load_context(key) -> strreading a user-XDGkey→
.md-file map,hooks/hooks.jsonfor the SessionStartpip install -t ${CLAUDE_PLUGIN_DATA}/site-packagespattern (canonical, from claude-plugin-creator), and
agents/<subagent>.mdwith its specific key embedded inthe body.
verify which carries by running the installed plugin)
include
permissions.allow: ["mcp__plugin_<plugin>_<server>__*"]so the subagent's tool invocation isn't gated on an
interactive approval that doesn't arrive in -p mode.
plugin-distributed form. The cwd boundary is doing the work.
The remaining implementation work is writing the plugin's
server.py, the key-map schema, the subagent.md, andverifying the settings grant lands at an appropriate scope for
users installing the plugin.
Problem
Some agents bundled with this plugin need access to configuration
or reference content that the plugin's orchestrating parent (and
any other agent in the session) must not see. The canonical
example is a scanning agent that needs a user-maintained catalog
of identifiers or patterns to look for in other artifacts; the
orchestrating parent never needs that catalog and must not end
up with it in its context window (where it would bleed into test
fixtures, code comments, commit messages, or other emissions).
The implementation mechanisms Claude Code documents for this
privilege-separation shape and their current status:
mcpServers:in asubagent's frontmatter — is documented but has open upstream
bugs preventing reliable invocation
(anthropics/claude-code#13898,
#25200,
#33689),
and a companion limitation closed as not planned
(#29655)
— subagents do not receive MCP server
instructions.defeat context isolation (move the MCP server to global,
have the parent orchestrate the call) or require a whole
different architecture (external MCP server that mediates
agent + tool relationships).
So a plugin that wants to keep sensitive context out of the
parent's context window — but also wants to keep shipping as a
normal plugin that any user can install — has no clean
first-party mechanism today.
Proposed solution
Ship a small MCP server inside this plugin that exposes a
single tool for loading content by opaque key. Embed the key
that identifies a given agent's required context inside that
agent's
.mdbody (not its frontmatterdescription, notproject settings, not anywhere the parent would naturally
read). The parent can technically call the tool, but without
the key it cannot pivot the tool into a useful lookup.
This turns a filesystem-permission question ("is the parent
blocked from reading this path?") into a capability-token
question ("does the caller know the right key?"), which is
strictly weaker but much more portable across environments
and does not depend on the broken-upstream features above.
Interface
Single tool, plain stdio MCP, bundled with this plugin:
keyis a short opaque string. The server does not enumeratevalid keys over the wire; there is no
list_keys()tool.Valid keys are learned only by being embedded in agent
definitions that are authorized to call this tool.
key, or a structured error if the key is unknown, the
mapped path is missing, or the mapped file is not a
.mdfile.
Key → path mapping
The server reads a user-maintained config file at a
conventional location under the user's XDG config base (exact
path TBD at implementation; pick a location namespaced to this
plugin, e.g.
$XDG_CONFIG_HOME/<namespace>/context-map.toml,with
~/.config/fallback whenXDG_CONFIG_HOMEis not set).Config shape (sketch — finalize during implementation):
Format restriction
The server loads ONLY files with a
.mdextension. This is adefense-in-depth layer: even if the parent somehow learned a
valid key, it cannot pivot the tool into a general-purpose
filesystem read by pointing a key at
/etc/passwdor similar.Unknown extensions return a structured error.
Install / bundling
Bundled in this plugin using the
direct-source + explicit-depspattern documented canonically in
echomodel/claude-plugin-creator/docs/plugin-patterns.md:${CLAUDE_PLUGIN_ROOT}/server.py, deps installed via theSessionStart hook into
${CLAUDE_PLUGIN_DATA}/site-packagesbydiffing
requirements.txt. No separatepipx installstep forusers. Do not use
uvx-based invocation —uvis not yetubiquitous on user machines and would be a portability regression.
Do not use an in-package version-string compare as the install
trigger (see the
antipattern section
of the patterns doc for the failure-mode analysis).
./agentscript reconsideration (forced by this change)This repo currently ships a
./agentscript that supportsstandalone installation of the privacy agents outside the plugin.
Introducing a bundled MCP server means that path — which today
covers agent
.mdfiles only — no longer gives standalone usersthe full privacy tooling. Maintaining the
./agentpath inparallel with the bundled MCP server risks divergence and
maintenance cost that may not be worth it.
Decide at implementation time among:
./agent. Standalone users install viadocumented
pipx install git+<url>@<tag>(or similar) once theMCP server ships with a
pyproject.tomlentry point. Simplestmaintenance; drops a distribution path we built ad-hoc.
./agentto also register the MCP serveralongside the agents. Preserves the existing UX; adds complexity
and couples the script to the MCP install path.
./agentalone and accept that standalone users getthe agents but not the MCP-backed features. Cheapest but
silently degrades functionality for existing standalone users.
Record the decision and rationale in
CONTRIBUTING.mdandREADME.mdat the moment of implementation. Do not preserve thepath out of inertia; the default expectation is deprecation
unless a concrete user-facing reason to keep it is identified.
Who calls it
Only the plugin's own subagents that are specifically
authorized for privileged context lookups. Each such agent's
.mdbody contains the exact key it should call with. Otheragents in the plugin do not receive keys and therefore cannot
usefully invoke the tool even though they can see it in their
MCP catalog.
Why this is YAGNI on the broader concern
It is tempting to frame this as "a general-purpose capability
broker" and design the full Vault-style abstraction up front:
multiple namespaces, role-based access, key rotation, audit
logging, a bulk-import skill, a web-facing admin, etc. That
design space is real and interesting, but:
scanning-agent-needs-PII-catalog pattern. Building for
imagined future use cases of "shared config", "secrets
broker", or "per-agent capability scoping" produces a
larger and more abstract surface without serving any
consumer we actually have.
A future evolution to richer semantics can layer on top:
add namespace prefixes to keys, add new tools alongside
load_context, move the key→path resolver into a sharedlibrary. Nothing in this design traps us.
Code product limitations, not as a considered
architectural layer. Acknowledging that: we should not
over-invest in an ecosystem-flavored design for what is, in
part, a patch around upstream bugs. If those upstream issues
close with fixes that enable the cleaner "inline-MCP-in-
subagent-frontmatter" path, we may want to fold this MCP
server back down or remove it entirely. Keeping the surface
small now keeps that option open.
We acknowledge the broader general-purpose-context-broker
framing and may revisit it later. This ticket explicitly does
NOT attempt it.
Upstream issues driving this design — document and revisit
Because part of our rationale for building this MCP server is
that upstream Claude Code limitations block cleaner
alternatives, the upstream tickets that define those
limitations are load-bearing context. If any of them close
with a fix that enables the cleaner path, we should
reconsider whether to keep this MCP server at all, or fold it
down to a minimal bridge, or rip it out entirely.
CONTRIBUTING.mdshould carry a section that:as of the section's last update, and one-line summary of
what it blocks for us.
these tickets changes state (closes with a fix, gets a
concrete design proposal, or gets declined as
"not planned"), we should re-evaluate the design captured
in this repo.
time a major Claude Code release lands) for a maintainer
or agent to walk the list, update the statuses, and open
a follow-up ticket here if any design decision becomes
worth revisiting.
recent check so readers can tell how stale the listed
statuses are.
The upstream issues to seed the list:
— "Subagents hallucinate instead of calling MCP tools from
project-scoped servers." Blocks the inline-MCP-in-subagent
path; if fixed, reconsider whether a plugin MCP server for
context lookup is still needed.
— "Custom agents cannot use deferred MCP tools." Blocks
per-agent
tools:whitelisting of MCP tool names as aprivilege-separation mechanism.
— "Subagents do not receive MCP server instructions."
Closed "not planned"; an architectural limitation that
shapes the workaround. If reopened and fixed, tool
descriptions could carry their own invocation context
without embedding keys in agent bodies.
— "Sub-agent-scoped MCP configs silently ignored in
plugin-distributed agents." Blocks scoping an MCP server
to a specific plugin agent via agent-file frontmatter.
If any of these ship fixes that enable the cleaner first-party
alternative, the follow-up work is to evaluate reverting this
ticket's code and simplifying to the now-working first-party
mechanism.
Prerequisite verdict (2026-04-19): asymmetric-tool-grant mechanism is architecturally dead — ticket proceeds as specified
The prerequisite below has been resolved. The
asymmetric-built-in-tool-grant mechanism is not viable —
empirically and per upstream documentation — so this ticket
proceeds as originally designed. Evidence:
Empirical result: an independent experimental workstream
tested a subagent whose frontmatter declared bare
tools: [Read]attempting to Read an outside-cwd file thatthe parent could not Read. The subagent was denied with the
same cwd-boundary permission check the parent hit. The
subagent's
tools:frontmatter field did NOT expandfilesystem reach — it is a capability allowlist (which tool
TYPES the subagent can use), not a path grant.
Documentation confirmation:
"Each [subagent] inherits the parent conversation's
permissions with additional tool restrictions."
Subagents can only have LESS access than the parent via
frontmatter, never more.
Read(<path>)patterns are documented only forsettings.json(session scope). No documented form ofper-agent path-scoped tool permissions exists.
Untested variants (speculative
tools: [Read(<path>)]syntax, per-agent
permissions.deny) are undocumented, andrelying on undocumented behavior for a security primitive is
inappropriate regardless of whether empirical probes might
succeed.
Decision: proceed with this ticket's original design —
the capability-broker MCP server with keys embedded in the
subagent's
.mdbody.The two remaining empirical prerequisites for THIS ticket's
design to work (replacing the old single prerequisite):
the subagent's own
.mdbody stay opaque to the parent?If not, the broker's key is leakable and the design
collapses.
a subagent? — the upstream bugs listed above affect
inline MCP in subagent frontmatter; the community
workaround is "move the MCP server to plugin or global
scope." Empirical confirmation still pending.
Both are being investigated in the same experimental
workstream that produced the mechanism-#3 verdict. This
ticket remains BLOCKED on those two sub-questions, but no
longer blocked on the (now-closed) primary prerequisite.
Prerequisite: verify asymmetric built-in-tool grants first
Before proceeding with the workaround-flavored MCP server in
this ticket, one of the alternative mechanisms — asymmetric
grants on built-in tools (Read / Bash), where the subagent
has access to sensitive content that the parent does not —
should be empirically verified. That mechanism, if it works,
would make the MCP server in this ticket unnecessary, because
the standard built-in-tool permission surface plus subagent
context isolation would already solve the problem without any
plugin-level machinery.
That verification is being conducted separately by repo
contributors in an experimental research workstream. Any
contributor who wants visibility into that work can reach out
to a maintainer; the decision criteria and the verdict will be
documented back into this ticket (and a successor ticket if
the verdict points at a different design) before
implementation begins.
Exit criteria for the prerequisite
The separate investigation is complete for the purposes of
this ticket when all of the following are recorded here or in
a linked doc:
Read (and ideally Bash) tools, whether the subagent can
access a path the parent cannot, under configuration applied
via frontmatter or project settings — the key question being
whether the asymmetry is structurally enforceable.
reliably / works with caveats / does not work.
close as not-needed, proceed as specified, or revise the
design.
Even if the prerequisite confirms #3 works, it isn't an automatic choice
Mechanism #3 — asymmetric grants on a built-in tool like
Read— has an inherent cost that the prerequisite testwill not by itself resolve: it requires embedding a
concrete filesystem path (or glob) in the agent definition
or in project/plugin settings. The subagent's
tools:frontmatter entry, or the settings'
permissions.allowpattern, has to name where the sensitive content lives.
That has real downsides independent of whether the
asymmetric-grant mechanism works:
overrides, or different personal conventions have content
in different places. A hard-coded path means either
shipping multiple variants, accepting fragility, or forcing
every user into one specific location.
Read(...)grant exposes exactly one specific filesystem pattern to a
generic
Readtool. Security review has to reason aboutboth what
Readcan do and what paths it is allowed on. Acustom tool with a tight interface (takes an opaque key,
only loads
.mdfiles, only from a map the user controls)is easier to reason about: one MCP-tool-specific permission
that names a single well-scoped tool.
approval per install (or zero if the plugin marketplace
handles it). Per-path
Readgrants either require manualedits to settings or a plugin-supplied settings delta that
has to be correct for every user's file layout.
changes, the custom-tool side only requires editing a
user-land config file. The path-grant side requires
editing agent definitions or settings — published and
versioned artifacts.
So even in the branch where the prerequisite verifies #3
works structurally, the verdict for THIS ticket should be a
two-part question, not a one-part question:
case at hand, given the portability and permission-surface
tradeoffs above, OR is the custom-tool surface proposed in
this ticket preferable on those grounds independent of the
upstream-bug-workaround motivation?
That second question deserves a deliberate answer documented
alongside the first. It is entirely possible that #3 works
and we still build this MCP server because the permission
surface and portability are strictly better.
Possible outcomes
the built-in permission surface plus context isolation is
sufficient. Document the verdict and criteria so future
sessions can re-evaluate if the use case changes.
This ticket proceeds, with the
security/portability/permission-surface argument
explicitly captured in
CONTRIBUTING.mdalongside theupstream-bug-workaround framing so the rationale is not
solely "we had to."
themselves push us toward the custom-tool shape; decide
per case.
driven by the workaround framing alone.
Work on this ticket should NOT begin until the prerequisite
is resolved AND the two-part question above has a
deliberate, documented answer.
"How else might we do this" — preserved-alternatives capture
To avoid re-deriving the design space from scratch in a future
session,
CONTRIBUTING.md(or a linked sibling.md, e.g.docs/context-mcp-design.md) should capture the alternativemechanisms that WOULD become viable or attractive if one or
more of the upstream issues above close, along with what
enables each and what the shape of the alternative would be.
These are NOT decided preferences. They are notes-to-self:
research was done once; capturing it at head means a future
session reconsidering the design can start from the saved
sketch rather than re-evaluating from zero.
Each captured alternative should include:
make it attractive (cross-reference the issue list above).
would look like, where the sensitive content lives, how the
parent is structurally excluded.
path today — upstream blocker(s), tradeoff against the
current design, or both.
entirely, replace part of it, or complement it.
Suggested mechanisms to capture at minimum (expand as
appropriate during implementation):
frontmatter. Sensitive content accessed via a tool whose
MCP server is declared
mcpServers:on the subagent's.md. Parent's tool catalog structurally excludes it.Unlocked by: #13898 and/or #25200 closing with a fix.
tools:whitelist. MCPserver declared once at project or plugin scope; each
agent's frontmatter whitelists which of its tools to
surface. Parent's whitelist excludes the sensitive tool;
subagent's includes it. Unlocked by: #25200 and/or #33689
closing with a fix.
all. Subagent granted Read on the sensitive file via its
frontmatter or project settings; parent explicitly denied
via
permissions.denyor by tool-restriction frontmatter.Subagent context isolation prevents raw content from
bleeding back. Unlocked by: empirical verification of
parent/subagent asymmetry for built-in-tool grants (does
not depend on any of the upstream MCP bugs being fixed —
currently blocked only by untested-ness).
PreToolUsehook rejects tool invocations whoseparent_tool_use_idindicates parent scope, allowing onlysubagent-originated calls. Layered on top of any of the
mechanisms above as defense-in-depth. Unlocked by:
empirical confirmation that hook payloads expose
parent_tool_use_idand that hooks can structurally aborttool calls based on it.
The captured section should make clear that any future move
off the current design is driven by evidence (upstream fix
landed, empirical verification completed, etc.) and that the
current design stands until that evidence arrives.
Deliverables
server.pyat plugin root, if claude-coding adopts the samelayout as the scaffold plugin).
.mcp.jsonentry wiring the server via${CLAUDE_PLUGIN_ROOT}+${CLAUDE_PLUGIN_DATA}PYTHONPATH pattern.
requirements.txtlisting themcppackage (andanything else the server needs).
hooks/hooks.jsonSessionStart hook using therequirements.txt-diff install-trigger pattern perplugin-patterns.md.Do not use an in-package version-string compare.
context-map.toml(or chosen format/name) from the chosenXDG location.
.md-only format gate in theload_contexttoolimplementation.
path → returns content; missing file → structured error;
unknown key → structured error; non-
.mdtarget →structured error; missing config → structured error.
.mdbody to embed thekey it should call with (no key in frontmatter; no key in
project settings).
./agentscript question (deprecate / extend/ leave) per the section above. Record decision and rationale
in
CONTRIBUTING.mdand updateREADME.mdinstallinstructions to match.
CONTRIBUTING.mdwith a section explaining thedesign and the YAGNI + workaround framing above, so the head
revision documents why this shape was chosen over the more
obvious alternatives. Include a short pointer (not a
restatement) to
plugin-patterns.mdfor the install-triggerpattern and antipattern rationale — one canonical source,
one pointer.
Out of scope
audit logs, multi-tenancy, etc.
edit the TOML by hand for now.
bugs close with fixes that eliminate the motivation — that's
a future ticket, not this one.
Verification
After the work lands, a reader of this plugin's head revision
should be able to answer, from
CONTRIBUTING.mdand theserver source alone:
load_contexttool do and what keys arevalid?
load_contexttool at all,and why not something more general?
working around, and under what conditions would we
reconsider keeping it?