Skip to content

Commit 41c301b

Browse files
docs: MCP follow-ups (subscribe, read_acl/write_acl, call_remote_pod, footguns) (#22)
Mirrors the in-repo docs/mcp.md update from JavaScriptSolidServer#497 and #498. Adds sections for the three new tools shipped in JSS 0.0.201 plus the Footguns section covering relative-vs-absolute WebID resolution in write_acl agents.
1 parent a89efd8 commit 41c301b

1 file changed

Lines changed: 109 additions & 3 deletions

File tree

docs/features/mcp.md

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,83 @@ Both `SKILL.md` (Anthropic markdown format) and `SKILL.jsonld` (typed JSON-LD de
104104

105105
Pod-resident docs (`/docs/`, `/public/apps/<name>/docs/`) are reachable via the regular CRUD tools — no separate surface.
106106

107+
### ACL editing
108+
109+
The most common owner operation is delegating an agent access to a resource. The MCP server exposes ACL editing as first-class tools so bots don't need to hand-roll JSON-LD.
110+
111+
| Tool | Effect | WAC check |
112+
|---|---|---|
113+
| `read_acl` | Return the ACL for a resource as a structured list (agents, agentClasses, modes, isDefault) | Control on resource |
114+
| `write_acl` | Persist a structured ACL to the resource's `.acl` file | Control on resource |
115+
116+
```json
117+
// write_acl arguments
118+
{
119+
"path": "/private/notes/",
120+
"authorizations": [
121+
{
122+
"agents": ["did:nostr:abc...", "https://alice.example.com/profile#me"],
123+
"modes": ["Read", "Append"],
124+
"isDefault": true
125+
},
126+
{
127+
"agentClasses": ["acl:AuthenticatedAgent"],
128+
"modes": ["Read"]
129+
}
130+
]
131+
}
132+
```
133+
134+
The structured form abstracts JSON-LD shape (`acl:agent` vs `acl:agentClass`, mode URI prefixes, `acl:default` propagation). New WAC vocabulary additions extend the structure without breaking existing bots.
135+
136+
**Safety**: `write_acl` refuses ACLs that would lock the caller out (no Control for the calling identity). This is the most common write_acl failure mode — typically caused by relative WebID paths in `agents` resolving against the .acl URL to a different absolute URI than the caller's actual WebID.
137+
138+
### Subscribe — live change notifications
139+
140+
`subscribe` is a streaming tool. The response switches to SSE (`text/event-stream`) and emits MCP notifications as resources change. WAC-filtered per event so subscribers only see resources they have Read access to.
141+
142+
| Tool | Effect |
143+
|---|---|
144+
| `subscribe` | Stream `resource_changed` events for a container subtree or specific path |
145+
146+
```bash
147+
curl -N http://localhost:4443/mcp \
148+
-H "Content-Type: application/json" \
149+
-H "Authorization: Bearer $TOKEN" \
150+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"subscribe","arguments":{"path":"/forum/channels/general/"}}}'
151+
```
152+
153+
Events arrive as:
154+
```
155+
event: notification
156+
data: {"jsonrpc":"2.0","method":"notifications/tool_event","params":{"tool":"subscribe","event":{"type":"resource_changed","path":"/forum/channels/general/abc.jsonld"}}}
157+
```
158+
159+
For chat-style bots, replace polling with `subscribe` and react to events as they land. Path scope: trailing slash watches a subtree, exact path watches a single resource, default watches the whole pod (filtered by Read access).
160+
161+
### Federation — bot-to-bot
162+
163+
`call_remote_pod` lets a bot on this pod invoke MCP tools on another pod. WAC-gated on both ends; depth-capped at 3 hops.
164+
165+
| Tool | Effect | Gating |
166+
|---|---|---|
167+
| `call_remote_pod` | Forward an MCP `tools/call` to another pod | Caller needs `acl:Write` on `<their-pod>/private/federation/` on this pod |
168+
169+
```json
170+
{
171+
"pod_url": "https://alice.example.com",
172+
"tool": "read_resource",
173+
"arguments": { "path": "/public/notes/shared.md" },
174+
"auth": { "type": "bearer", "token": "..." }
175+
}
176+
```
177+
178+
To delegate outbound federation to a specific agent, grant them `acl:Write` on your `/private/federation/` container. Owners control which agents can initiate calls; remote pods control what they expose.
179+
180+
In single-user mode, the pod owner has implicit gate access via `/private/` inheritance. In multi-user mode, each pod's owner gates their own federation.
181+
182+
Foreign WebIDs (identities hosted on other pods) cannot initiate federation from this pod — there's no local path for the gate to live at. Multi-pod federation chains compose by hopping between pods, each gated locally.
183+
107184
### Introspection
108185

109186
| Tool | Returns |
@@ -138,13 +215,42 @@ The owner opens `/public/apps/charlie/`, logs in via [xlogin](https://npm.im/xlo
138215

139216
The bot's *behavior* lives in `SKILL.md`. Edit the file → next session picks up the change. No re-deploy, no API call sequence — the bot's brain is a pod resource.
140217

218+
## Footguns
219+
220+
A short list of real gotchas, learned from live-fire use:
221+
222+
### Use absolute WebIDs in `write_acl` agents
223+
224+
The `agents` array is interpreted as a list of URIs. Relative paths (e.g. `../profile/card.jsonld#me`) resolve against the **.acl file's URL**, not the pod root — and the .acl URL changes depending on which resource the ACL applies to. Two pitfalls:
225+
226+
```json
227+
// Pod owner WebID: http://example.com/profile/card.jsonld#me
228+
// Writing this ACL to /public/forum/.acl:
229+
"agents": ["../profile/card.jsonld#me"] // wrong — resolves to /public/profile/card.jsonld#me
230+
"agents": ["./profile/card.jsonld#me"] // wrong — resolves to /public/forum/profile/card.jsonld#me
231+
"agents": ["/profile/card.jsonld#me"] // right — absolute path
232+
"agents": ["http://example.com/profile/card.jsonld#me"] // right — absolute URL, portable
233+
```
234+
235+
**Always use absolute WebID URLs unless you know exactly what relative-URL resolution will give you.**
236+
237+
### `write_acl` will refuse if you'd lock yourself out
238+
239+
If the proposed ACL doesn't grant `Control` to the caller (typically a relative-URL mistake), `write_acl` refuses with an explanatory error. This is a safety, not a permission check — it's stopping you from breaking your own access.
240+
241+
If you really want to transfer ownership: do it in two steps. First `write_acl` granting Control to the new owner *in addition to* yourself. Then the new owner calls `write_acl` removing you.
242+
243+
### Subscribe needs an SSE-capable client
244+
245+
`subscribe` keeps an HTTP+SSE connection open indefinitely. Some proxies and load balancers will time out idle streams. Use a client that handles SSE reconnect (most browsers do; raw `curl` does not).
246+
141247
## What's not yet included
142248

143-
The first cut ships CRUD, skills, docs, and introspection. Deferred (tracked on [JSS#490](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/490)):
249+
The current cut ships CRUD, structured ACL editing, subscribe, federation, skills, docs, and introspection. Deferred (tracked on [JSS#490](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/490)):
144250

145251
- **`update_resource` (PATCH)** — SPARQL Update / N3 patches. Read-modify-write through the CRUD tools is the workaround.
146-
- **`subscribe`**wrap JSS's WebSocket notifications as MCP events over SSE. Today, agents can poll via `read_resource`.
147-
- **`call_remote_pod`**federation primitive for bot-to-bot. Today, an agent can talk to two pods by registering both as MCP servers in its client.
252+
- **Discovery layer**no DNS SRV / Solid Type Index entry for "this pod offers MCP". Owners share URLs explicitly today.
253+
- **Pod-resident federation credentials**every `call_remote_pod` carries its own auth. A vault for storing remote-pod credentials is a separate security surface worth its own design pass.
148254
- **Hosted Charlie** (`/agent/` endpoint) — JSS-internal LLM proxy with token metering. Tracked on [JSS#205](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/205).
149255

150256
## References

0 commit comments

Comments
 (0)