feat(mcp): subscribe, read_acl/write_acl, call_remote_pod (closes #494 #495 #496)#497
Merged
Conversation
…#495 #496) Three follow-ups to the v0.0.200 MCP capstone (#490), bundled. ## subscribe (#494) Streaming tool. Response switches to SSE (text/event-stream) and emits MCP notifications as resources change. WAC-filtered per event so the subscriber only sees resources they have Read access to. Wraps the existing resourceEvents EventEmitter from src/notifications/events.js. Path scope: trailing slash = subtree, exact = single resource, default = whole pod (filtered by access). Cleanup on client disconnect. ## read_acl / write_acl (#496) Structured ACL editing as first-class MCP tools. Eliminates hand-rolled JSON-LD for the most common owner operation (delegating an agent access). read_acl returns parsed authorizations (agents, agentClasses, modes, isDefault). write_acl persists the same shape via serializeAcl() in src/wac/parser.js. Both require acl:Control on the resource. ## call_remote_pod (#495) Federation primitive — invoke MCP tools on another pod by URL. Locked in for v1 with conservative defaults: - Federation gate path is derived from the agent's WebID: <agent-pod>/private/federation/. Caller must have acl:Write there. Foreign or anonymous identities can't federate. - No pod-resident credential storage — every call carries its own auth (bearer / custom header / anonymous). - Depth cap via MCP-Federation-Depth header, hardcoded max 3. Calls remote /mcp endpoint, forwards tools/call, returns the response. 30s timeout. Per-call audit via the existing log infrastructure. ## Tool count 12 → 17. Surface still graspable in one read. CRUD / Skills / Docs / Introspect / ACL / Subscribe / Federation. ## Tests 26 passing (was 18). New coverage: - read_acl returns owner authorizations on a fresh pod ACL - write_acl + read_acl round-trip with 3 agent classes - write_acl denied without Control - call_remote_pod denied without federation gate (anonymous) - call_remote_pod hits its own /mcp through the gate - call_remote_pod depth cap enforcement (header at 3 → next hop fails) - subscribe streams a 'subscribed' init event + resource_changed events - subscribe filters by scope (out-of-scope changes are silent) ## Docs docs/mcp.md expanded with sections for all three: structured ACL ergonomics, subscribe SSE wire format with curl example, federation worked example. 'What's not included' section pruned (these were the items it deferred to).
2 tasks
melvincarvalho
added a commit
that referenced
this pull request
May 18, 2026
Live-fire smoke of #497 surfaced two real gotchas with the new write_acl tool: 1. Relative WebID paths in 'agents' resolve against the .acl URL, not the pod root. ../profile/card.jsonld#me writes to a different absolute URI depending on where the .acl is — easy to lock yourself out. 2. There was no server-side check that the resulting ACL would keep the caller in control. This commit: - Adds a safety check in write_acl that parses the proposed ACL with its real URL (so relative agents resolve correctly), then checks whether any authorization grants Control to the caller. If not, refuses with an explanatory error pointing at the most common cause (relative WebIDs) and the workaround for legitimate ownership transfer. - Adds a 'Footguns' section to docs/mcp.md documenting the relative- vs-absolute WebID rule with concrete right/wrong examples, the new safety check, and the SSE keep-alive caveat. Tests: 27 passing (was 26). New test: write_acl refuses an ACL that grants Control only to a foreign WebID.
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three follow-ups to the v0.0.200 MCP capstone (#490), bundled into one PR as discussed.
Closes #494 (subscribe), #495 (call_remote_pod), #496 (read_acl/write_acl).
What's in
subscribe (#494) — streaming change notifications
Streaming tool. Response switches to SSE; emits MCP notifications as resources change. WAC-filtered per event. Wraps the existing `resourceEvents` from `src/notifications/events.js`. Path scope: trailing slash = subtree, exact = single resource. Cleanup on client disconnect.
read_acl / write_acl (#496) — structured ACL editing
Structured form abstracts JSON-LD shape:
```json
{
"path": "/private/notes/",
"authorizations": [
{ "agents": ["did:nostr:abc..."], "modes": ["Read","Append"], "isDefault": true },
{ "agentClasses": ["acl:AuthenticatedAgent"], "modes": ["Read"] }
]
}
```
Round-trips via `src/wac/parser.js`. Requires `acl:Control` on the resource.
call_remote_pod (#495) — federation primitive
Design calls locked in as discussed:
Tool count
12 → 17. Still graspable in one read.
Tests
26 passing (was 18). New coverage:
```
ℹ pass 26
ℹ fail 0
```
Test plan