Skip to content

feat(mcp): server-level wing access control via --allowed-wings / --blocked-wings - #1366

Open
brodheadw wants to merge 3 commits into
MemPalace:developfrom
brodhead-unlimited:feat/mcp-wing-policy-rebased
Open

feat(mcp): server-level wing access control via --allowed-wings / --blocked-wings#1366
brodheadw wants to merge 3 commits into
MemPalace:developfrom
brodhead-unlimited:feat/mcp-wing-policy-rebased

Conversation

@brodheadw

@brodheadw brodheadw commented May 5, 2026

Copy link
Copy Markdown

Summary

  • New WingPolicy class enforces wing-level access control on every MCP tool that touches palace data.
  • Two CLI flags on mempalace-mcp: --allowed-wings (allowlist) and --blocked-wings (denylist; takes precedence).
  • Default is no-op — when neither flag is set, all wings pass.
  • 18 wing-addressable tools now policy-gated via four enforcement patterns (hard block, soft filter, post-filter, ID lookup).
  • 18 new tests, all passing.

Problem

A user running multiple agents shouldn't have to trust every agent with every wing. Today, any agent with the mempalace MCP server configured can read or write any wing the server has access to — there's no way to scope an agent to its own project's wing, hide a financial wing from a general assistant, or give a "master" agent broader access than its sub-agents. WingPolicy adds that scoping at the server-process level.

Change

mempalace-mcp now accepts:

  • --allowed-wings WINGS — comma-separated allowlist; all unlisted wings denied
  • --blocked-wings WINGS — comma-separated denylist; takes precedence over allowlist

Each agent's MCP server invocation carries its own policy:

// master agent — no flags, full access
"mempalace": { "command": "mempalace-mcp" }

// project-scoped agent
"mempalace": {
  "command": "mempalace-mcp",
  "args": ["--allowed-wings", "wing_user,wing_myproject"]
}

// general assistant, financial wings off-limits
"mempalace": {
  "command": "mempalace-mcp",
  "args": ["--blocked-wings", "wing_financial,wing_surveillance"]
}

All instances share ~/.mempalace/ data on disk; isolation is enforced server-side per process.

Enforcement matrix

Pattern Tools
Hard block (access_denied on explicit-wing requests) list_rooms, search, find_tunnels, add_drawer, diary_read, diary_write, list_drawers, create_tunnel, list_tunnels, follow_tunnels
Soft filter (silently omit blocked wings from aggregates) status, list_wings, get_taxonomy, list_rooms (unfiltered), list_drawers (unfiltered)
Post-filter (drop blocked-wing hits from result arrays) search, check_duplicate, diary_read (spans-all-wings), list_tunnels (either endpoint), follow_tunnels (destination)
ID lookup → policy check get_drawer, update_drawer (old AND new wing), delete_drawer, delete_tunnel (both endpoints)

Also bundled: a one-line .gitignore fix for per-process .coverage.* artifacts that slipped past the existing literal .coverage rule.

Tests

  • WingPolicy allowlist + blocklist + precedence logic
  • Soft filter omits blocked wings from status, list_wings, get_taxonomy
  • list_rooms unfiltered drops rooms unique to blocked wings
  • Hard block on explicit wing requests across read + write tools
  • search post-filter drops blocked hits when no wing arg given
  • check_duplicate suppresses blocked-wing matches (no existence-leak)
  • delete_drawer / update_drawer / get_drawer enforce via ID lookup
  • update_drawer also blocks moves into a blocked wing
  • create_tunnel blocks when either endpoint is blocked
  • list_tunnels drops tunnels where either endpoint is in a blocked wing
  • delete_tunnel enforces on either endpoint via tunnel-id lookup
  • follow_tunnels hard-blocks origin and post-filters destinations

ruff format + ruff check clean. All 18 new TestWingPolicy tests pass; rebased onto current main.

What's NOT in this PR

  • traverse_graph / graph_stats — full enforcement requires policy-aware traversal inside palace_graph.py. Inline notes added to both tools' docstrings flagging the gap. Happy to do as a follow-up.
  • KG tools (kg_query, kg_add, kg_invalidate, kg_timeline, kg_stats) — the knowledge graph is entity-keyed, not wing-keyed. Different scoping model.
  • Hook / settings / reconnect tools — not data-bearing.
  • Per-sub-agent scopingWingPolicy operates at the MCP server process level. Hosts that spawn sub-agents which share the parent's MCP connection will see those sub-agents inherit the parent's policy. True per-sub-agent scoping requires the host to launch each sub-agent with its own mempalace-mcp invocation — a host-side concern, not a server-side one. Worth a note in any user docs that mention per-agent policies.

@brodheadw
brodheadw force-pushed the feat/mcp-wing-policy-rebased branch from f4363a2 to 2af5575 Compare May 5, 2026 19:39
@brodheadw

brodheadw commented May 5, 2026

Copy link
Copy Markdown
Author

Open to discussing further with maintainers. I know this might read as a significant change, but it's totally opt-in, no flags, every agent still has full access. Felt necessary for cleanly managing multi-agent access to memory, especially with overlapping projects. My email's in my profile.

@igorls igorls added enhancement New feature or request area/mcp MCP server and tools labels May 6, 2026
brodheadw added 3 commits May 11, 2026 12:02
Add --allowed-wings / --blocked-wings flags to mempalace-mcp. Each
agent's MCP server invocation carries its own policy; all instances
share the same ~/.mempalace data on disk but enforce isolation
server-side per process. Blocklist takes precedence over allowlist.
Default is no-op when neither flag is set.

Enforcement patterns across 18 wing-addressable tools:
- Hard block (access_denied) on explicit-wing requests: search,
  list_rooms, find_tunnels, follow_tunnels, add/get/list/update/
  delete_drawer, create/list/delete_tunnel, diary_read/write.
- Soft filter on aggregates: status, list_wings, get_taxonomy,
  list_rooms (unfiltered), list_drawers (unfiltered) silently
  omit blocked wings from results.
- Post-filter on result arrays: search, check_duplicate, diary_read
  (spans-all-wings), list_tunnels (either endpoint), follow_tunnels
  (destination wing) strip blocked-wing hits before returning.
- ID lookup -> policy: get/update/delete_drawer fetch metadata first;
  delete_tunnel scans tunnels.json to enforce on both endpoints;
  update_drawer also blocks moves *into* a blocked wing.

Not covered (inline TODOs): traverse_graph, graph_stats - need
policy-aware traversal in palace_graph.py.

18 new tests covering policy logic, soft filter, hard block,
post-filter paths, ID-based lookups across drawer + tunnel families.
The existing rule matches only the literal '.coverage' file. pytest-cov
writes additional per-process data files named '.coverage.<host>.<pid>.<rand>'
which slipped past the rule.
@brodheadw
brodheadw force-pushed the feat/mcp-wing-policy-rebased branch from 414f593 to 430c9b7 Compare May 11, 2026 16:04
@igorls
igorls changed the base branch from main to develop May 17, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/mcp MCP server and tools enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants