@plurnk/plurnk-mcp 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SPEC.md CHANGED
@@ -9,23 +9,34 @@ executor, resource, proposal, entry, Problem, and lifecycle contracts.
9
9
 
10
10
  ## §mcp-authority Protocol authority
11
11
 
12
- The only accepted revision is `2026-07-28`, specification commit
12
+ The host's own wire authority is revision `2026-07-28`, specification commit
13
13
  `5f5440bb26a62e2cf3440b92da5a667efa03b267`. The implementation exact-pins
14
14
  `@modelcontextprotocol/client@2.0.0`. SDK exports are not protocol authority:
15
15
  that package deliberately retains legacy and deprecated API shapes. It owns
16
16
  core negotiation and transport; this package owns only exact-pinned extension
17
17
  wire that the SDK does not yet implement.
18
18
 
19
- The optional Tasks authority is the official `experimental-ext-tasks` contract
20
- at commit `2c1425d9a288b9b1f489430fe1e00bb392b47e48`. Its absence from the current
21
- SDK runtime does not revive that SDK's retained 2025 Tasks vocabulary.
19
+ Connection setup negotiates-and-degrades. A server that negotiates the pinned
20
+ revision and offers `server/discover` is a **modern** peer: it gets the complete
21
+ extension wire (the `_meta` envelope, `resultType`, the Tasks extension) and its
22
+ discover result is the identity and capability source. A server the SDK
23
+ negotiated below the pin is an ordinary MCP peer: it serves the standard
24
+ tool/resource/prompt surface from its `initialize` result at its negotiated
25
+ revision, and the host applies no envelope, discover, or Tasks requirements to
26
+ it. There is no rejection for offering an older supported revision and no
27
+ protocol downgrade of the host's own extension wire: modern peers and legacy
28
+ peers simply carry different surfaces.
22
29
 
23
- Every request carries the modern `_meta` envelope. Connection setup verifies
24
- `server/discover` at the pinned revision before publishing any runtime or
25
- scheme. There is no protocol downgrade or legacy fallback.
30
+ The optional Tasks authority is the official `experimental-ext-tasks` contract
31
+ at commit `2c1425d9a288b9b1f489430fe1e00bb392b47e48`. It is negotiated
32
+ per-connection and absent from a legacy peer.
26
33
 
27
34
  ## §mcp-core-matrix Core capability matrix
28
35
 
36
+ The accountable capability matrix lives in `capabilityMatrix.ts`
37
+ ({§mcp-capability-matrix}); this section states the core surface contract the
38
+ matrix rows cite.
39
+
29
40
  | Surface | Upstream contract | Plurnk host disposition |
30
41
  |---|---|---|
31
42
  | Base | JSON-RPC 2.0; per-request protocol, identity, and capability metadata; every result has `resultType` | Require the modern envelope and preserve protocol results and errors without reconstructing them |
@@ -58,6 +69,26 @@ Polling honors each current `pollIntervalMs` under the one owning operation
58
69
  deadline. Task input keys are fulfilled at most once, in one atomic client
59
70
  interaction per observed input set. A completed Task is validated as the
60
71
  originating tool result; a failed Task preserves its JSON-RPC error.
72
+ Handle ownership and the restart journey are bounded in {§tasks-lifetime}.
73
+
74
+ ## §tasks-lifetime Tasks lifetime and re-run
75
+
76
+ Task handles are owned in-process by the connection and operation that created
77
+ them; Plurnk deliberately declines durable task-handle recovery. A durable
78
+ handle would need a persisted scheduler and a home for a result whose owning
79
+ operation does not outlive the daemon; the ordinary Plurnk journey is that a
80
+ restart re-runs the operation, which drives a fresh task. The server's own
81
+ persistence of task state is respected only within one connection lifetime;
82
+ nothing task-shaped is written to SQLite and no MCP sidecar lifecycle exists.
83
+
84
+ | Boundary | Behaviour |
85
+ |---|---|
86
+ | Client disconnect | The daemon-owned operation and its task keep running; the client reattaches to the operation, not the task. |
87
+ | Daemon restart | The connection and every in-flight task handle die with it; the tool call fails like any interrupted operation, the loop re-runs, and the tool call creates a fresh task. |
88
+ | Worker reactivation | The attachment reconstructs from its durable definition; in-flight tasks on the replaced connection are abandoned, not resumed. |
89
+ | Expiry | The owning operation deadline bounds polling; a non-converging task fails at the standard round bound and is cancelled. |
90
+ | Cancellation | Owner abort cancels the task before settling; the handle is then terminal. |
91
+ | Already terminal | Terminal results and errors are consumed by the drive loop; a completed or failed task is never re-polled or re-resumed. |
61
92
 
62
93
  ## §mcp-exclusions Removed, deprecated, and excluded surfaces
63
94
 
@@ -71,9 +102,29 @@ originating tool result; a failed Task preserves its JSON-RPC error.
71
102
  | Removed | `resources/subscribe`, `resources/unsubscribe`, SSE resumption and `Last-Event-ID` | Use `subscriptions/listen`; reissue a lost request with a new ID |
72
103
  | Removed | Legacy Tasks `tasks/list`, `tasks/result`, and task-augmentation request fields | Use only the negotiated final Tasks extension |
73
104
  | Excluded | Other official, experimental, or private extensions | Require a separately owned contract before negotiation |
74
- | Excluded | Legacy revisions and dual-era operation | Pin `2026-07-28`; never probe-and-fallback |
105
+ | Excluded | Dual-era operation | Modern peers and legacy peers carry different surfaces; the host never mixes the two on one connection |
75
106
  | Excluded | MCP server and authorization-server roles | This package is the host/client only |
76
107
 
108
+ ## §mcp-capability-matrix Accountable capability matrix
109
+
110
+ `capabilityMatrix.ts` is the one accountable support matrix: one row per core
111
+ surface, official extension, or explicitly selected experimental candidate,
112
+ carrying authority, disposition (supported, partial, excluded, deferred),
113
+ advertisement, interactivity, and evidence citations. Rows are "supported"
114
+ only when every layer their owning contract includes has real coverage; a row
115
+ cannot claim support merely because the direct SDK or conformance path passes.
116
+ Every evidence citation must resolve through a named specification tag or a
117
+ named composed test.
118
+
119
+ The static wire advertisement is derived from the matrix by construction
120
+ (`staticClientCapabilities`): an extension reaches the wire only because its
121
+ row says `always`, and a `conditional` extension is added only by its owning
122
+ connection logic ({§oauth-client-credentials}). The matrix unit tests enforce
123
+ unique identities, no excluded row advertising, supported rows citing evidence,
124
+ composed coverage for interactive advertised rows, and exact reconciliation
125
+ between the matrix and the derived advertisement. Official required
126
+ conformance stays a separate named gate, never folded into a matrix row.
127
+
77
128
  ## §mcp-transports Transport bindings
78
129
 
79
130
  | Binding | Contract |
@@ -81,6 +132,11 @@ originating tool result; a failed Task preserves its JSON-RPC error.
81
132
  | stdio | Spawn one exact executable with an explicit argument array and no shell; newline-delimited JSON-RPC is the only stdout/stdin traffic; stderr is diagnostic; shutdown closes stdin, waits, then terminates if necessary |
82
133
  | Streamable HTTP | Send one POST per request or notification; accept JSON or SSE responses; close the response stream to cancel; never open the removed general GET stream |
83
134
 
135
+ §mcp-stdio-process-ownership A stdio connection owns the complete process group
136
+ created for its server. Ordinary closure forwards stdin EOF and permits a
137
+ bounded graceful exit; an expired shutdown bound or disappearance of the host
138
+ process forcibly terminates the group, including descendants.
139
+
84
140
  Every HTTP request carries matching `MCP-Protocol-Version` and `Mcp-Method`
85
141
  headers. Named requests also carry `Mcp-Name`; declared primitive tool
86
142
  parameters carry validated `Mcp-Param-*` headers. Header names compare
@@ -108,11 +164,23 @@ originating distinction in its canonical Problem/result path.
108
164
 
109
165
  ## §mcp-configuration Configuration
110
166
 
111
- Two inputs produce one effective definition per workspace. Service environment
112
- variables are convenience defaults instantiated independently for every
113
- workspace. The workspace's durable attachment map may add a server, replace a
114
- default, or retain a tombstone that suppresses a default after detach. Neither
115
- source expands the model-facing namespace with a second discovery surface.
167
+ Service configuration and worker state produce one available set and one
168
+ enabled subset per worker. Every `PLURNK_MCP_<server>` declares an available
169
+ service-owned definition. `PLURNK_MCP_ENABLED` names the exact subset enabled
170
+ when a worker has no override. Worker state may positively override a
171
+ service definition's enabledness or own an added definition and its enabledness.
172
+ Disabled definitions remain client-visible but contribute no connection,
173
+ Registry, documentation, or resource authority.
174
+
175
+ §mcp-activation-isolation **Cold endpoint failure is capability-local.** Invalid
176
+ service configuration or durable state fails admission, but an enabled server
177
+ that cannot connect or complete discovery during worker activation remains
178
+ enabled and client-visible as `unavailable`. It publishes no runtime, tools,
179
+ resources, or documentation and cannot prevent other capabilities or the daemon
180
+ from starting or serving dormant workers. Enabling that already-enabled alias is an explicit reconnect
181
+ attempt; failure preserves the unavailable snapshot, while success atomically
182
+ replaces it. Interactive add and enable mutations continue to reject an
183
+ unavailable candidate without changing durable state.
116
184
 
117
185
  | Variable | Contract |
118
186
  |---|---|
@@ -124,6 +192,10 @@ source expands the model-facing namespace with a second discovery surface.
124
192
  | `PLURNK_MCP_<server>_HEADERS` | JSON string map for supplementary HTTP headers |
125
193
  | `PLURNK_MCP_<server>_TOOLS` | Optional JSON array of exact enabled tool names; absent enables all listed server tools, while `[]` enables none |
126
194
  | `PLURNK_MCP_<server>_READ` | JSON string array forming an exact subset of enabled tools that the operator classifies as read-only; every other enabled tool retains the conservative `host` effect |
195
+ | `PLURNK_MCP_<server>_SUMMARY` | Authored one-line server orientation ({§mcp-summary-derivation}) |
196
+ | `PLURNK_MCP_<server>_<tool>_SUMMARY` | Authored one-line tool orientation; tool names fold the same way and may contain underscores |
197
+ | `PLURNK_MCP_ENABLED` | JSON array of exact configured server aliases enabled by default; absent or `[]` enables none |
198
+ | `PLURNK_MCP_EXPANDED` | JSON array subset of enabled servers whose complete tool tree also expands into the turn-0 tools survey ({§tools-resource-materialization}); absent or `[]` expands none |
127
199
  | `PLURNK_MCP_CONNECT_TIMEOUT` | Positive integer milliseconds |
128
200
  | `PLURNK_MCP_REQUEST_TIMEOUT` | Positive integer milliseconds |
129
201
 
@@ -135,9 +207,22 @@ executable string even when its path contains whitespace; arguments never hide
135
207
  inside it. Bearer authentication and a case-insensitive `Authorization` entry
136
208
  in `_HEADERS` are mutually exclusive.
137
209
 
210
+ §mcp-summary-derivation **Every orientation line derives from authored
211
+ metadata — never a container template.** The runtime declaration's summary
212
+ resolves in order: the `_SUMMARY` companion, the server's own
213
+ `serverInfo.description`, its display `title` (both spec metadata — a title
214
+ like "Chrome DevTools MCP server" is already a one-liner), the first sentence
215
+ of its `instructions` essay, then a factual tool-name list. Each tool's one-liner
216
+ resolves: its `_<server>_<tool>_SUMMARY` companion, `annotations.title`, the
217
+ first sentence of its `description` (capped), then the tool name. The family
218
+ doc's Summary section and the survey row carry the server one-liner; the tool
219
+ doc's Summary section IS the invocation form
220
+ `EXEC [server] (tool) <!-- one-liner -->`, so the discovery row teaches the
221
+ call ({§tools-resource-materialization}). Summary companions expand `${NAME}`
222
+ references like every other companion.
223
+
138
224
  §mcp-definition-wire The contracts-owned `McpServerDefinition` JSON Schema is
139
- the one workspace action and durable-state shape. It is a closed discriminated
140
- union:
225
+ the normalized durable definition shape. It is a closed discriminated union:
141
226
 
142
227
  | Transport / authorization | Required definition | Optional definition |
143
228
  |---|---|---|
@@ -147,7 +232,7 @@ union:
147
232
  | `http` + interactive OAuth / CIMD preferred | above plus `authorization: { type: "oauth", redirectUrl, clientMetadataUrl }` | `scope`; DCR remains the server-advertised fallback when CIMD is unavailable |
148
233
  | `http` + interactive OAuth / pre-registered | above plus `authorization: { type: "oauth", redirectUrl, clientId, clientSecret: "${NAME}" }` | `scope` |
149
234
  | `http` + interactive OAuth / DCR fallback only | above plus `authorization: { type: "oauth", redirectUrl }` | `scope` |
150
- | `http` + client credentials | above plus `authorization: { type: "client-credentials", clientId, clientSecret: "${NAME}" }` | `scope` |
235
+ | `http` + client credentials | above plus `authorization: { type: "client-credentials", clientId, clientSecret: "${NAME}" }` | `scope`; `issuer` binds the credential to its authorization server ({§oauth-client-credentials}) |
151
236
 
152
237
  `tools` absent enables the complete listed set; `[]` enables none. `read` is an
153
238
  exact subset of the enabled set. A credential field is one complete symbolic
@@ -159,56 +244,182 @@ discovery state, and authorization callback state remain process-memory
159
244
  credentials; a restart reconstructs the attachment as authorization-required
160
245
  instead of writing secrets into SQLite.
161
246
 
162
- ### §mcp-management-actions Workspace management
163
-
164
- Every action below declares `scope: "workspace"` under
165
- {§module-action-registration}. AG-UI binds its workspace; none accepts a
166
- workspace identifier in params.
247
+ The contracts-owned `McpServerDefinition` is the exact definition one `add`
248
+ accepts and the coordinator persists; a client composes it from its target
249
+ (an absolute HTTP(S) URL selects Streamable HTTP, anything else is one exact
250
+ stdio executable) and its options (`args`, `cwd`, `env`, `headers`,
251
+ `authorization`, `tools`, `read`). The normalized definition rejects
252
+ transport-inapplicable options before any connection work.
253
+
254
+ §mcp-configuration-cascade MCP server configuration has one field-wise
255
+ precedence order: service environment, then the Worker's durable definition.
256
+ Arrays and maps replace their lower value instead of appending or merging.
257
+ Client configuration is not a live layer: the contracts-owned
258
+ `{§mcp-configuration-overlay}` enters only as the `configuration` of a
259
+ `discover` query, is parsed by the same owner and path as service environment
260
+ declarations, and yields inert candidates with client-configuration provenance
261
+ ({§mcp-discovery}); adding one persists a complete, normalized, unexpanded
262
+ Worker definition. Thus later enablement needs neither the originating client
263
+ nor its configuration file, and symbolic credentials remain resolvable only by
264
+ the service at connection preparation.
265
+
266
+ ### §mcp-module The MCP family beneath the coordinator
267
+
268
+ §mcp-management-actions MCP is one family of Worker Functionality
269
+ ({§functionality-coordinator}): the coordinator publishes `worker.mcp.list |
270
+ discover | add | enable | disable | remove` and the model's `EXEC [mcp]`
271
+ family with the common semantics, durable state, and publication; this module
272
+ registers the family adapter and owns protocol truth beneath it. `available`
273
+ is the service environment with its `PLURNK_MCP_ENABLED` defaults; `admit`
274
+ validates one exact definition and binds the alias to its `name`; `prepare`
275
+ connects the enabled set, reusing unchanged live attachments, and returns one
276
+ executor family and resource facet per connected server, one outcome per alias
277
+ (`active` with catalog detail — negotiated protocol version, server identity,
278
+ capabilities, tool names, resource and prompt counts — `unavailable` with its
279
+ exact Problem, or `authorization-required` with its URL), and a two-phase
280
+ snapshot: `commit` closes connections the new set no longer uses and records
281
+ pending authorizations; `abort` closes only what the attempt opened.
282
+
283
+ Two protocol continuations remain MCP-registered worker actions beneath the
284
+ common grammar:
167
285
 
168
286
  | Action | Parameters | Result / effect |
169
287
  |---|---|---|
170
- | `workspace.mcp.list` | none | Sorted effective server summaries: name, source, transport, connection/authorization state, negotiated identity/capabilities, enabled tools, and read subset. No credential values. |
171
- | `workspace.mcp.attach` | `server: McpServerDefinition` | Adds a name absent from the effective workspace. Preparation completes before publication. |
172
- | `workspace.mcp.replace` | `server: McpServerDefinition` | Replaces one effective definition under the same name; absence is 404. |
173
- | `workspace.mcp.detach` | `name` | Removes a workspace attachment or writes a tombstone for a service default, then removes its exact Registry, docs, and resource authority. |
174
- | `workspace.mcp.reconnect` | `name` | Builds a fresh connection from the existing unexpanded definition and atomically replaces the old connection after successful preparation. |
175
- | `workspace.mcp.oauth.complete` | `name`, `callbackUrl` | State- and issuer-validates one pending interactive callback through the SDK, completes connection preparation, then performs the originally requested attach, replace, or reconnect. |
176
- | `workspace.mcp.complete` | `server`, `ref`, `argument`; optional `context` | Requests negotiated prompt/resource-template argument completion for a client-owned interaction. |
177
-
178
- Expected preparation failures cross the action boundary as MCP-management
179
- Problems rather than generic AG-UI failures:
288
+ | `worker.mcp.oauth.complete` | `alias`, `callbackUrl` | State- and issuer-validates one pending interactive callback through the SDK, completes connection preparation, and re-enables the alias through the coordinator ({§oauth-continuation}); the result is the common mutation result. |
289
+ | `worker.mcp.complete` | `server`, `ref`, `argument`; optional `context` | Requests negotiated prompt/resource-template argument completion for a client-owned interaction. |
290
+
291
+ §mcp-discovery Discovery is inert. `configuration` (a client's own
292
+ `PLURNK_MCP_*` overlay) becomes candidates without connecting; `source` — an
293
+ absolute HTTP(S) URL or one whitespace-separated command line — is probed at the
294
+ negotiated revision for its catalog and released, yielding one candidate with
295
+ direct-target provenance (an authorization challenge yields the candidate with
296
+ that fact in its summary); a bare `query` requires a configured downstream
297
+ registry and is `501 registry-not-configured` until one exists. No candidate
298
+ is installed, persisted, enabled, or executed by discovery.
299
+
300
+ Expected preparation failures cross the boundary as MCP-management Problems
301
+ rather than generic failures; an explicit client action rejects them and a
302
+ Worker's own accepted mutation publishes them as unavailable
303
+ ({§functionality-model-mutation}):
180
304
 
181
305
  | Endpoint condition | Problem |
182
306
  |---|---|
183
- | Definitively does not offer pinned `2026-07-28` through `server/discover` | `502 protocol-revision-unsupported`, non-retryable; names the server, required revision and method, and directs the operator to upgrade or replace the legacy endpoint |
184
- | Cannot connect or complete current discovery/catalog preparation | `502 server-unavailable`, retryable; names the server and transport without exposing credentials |
185
-
186
- Interactive preparation returns a successful pending result shaped as
187
- `{ status: 202, authorization: { url } }`; it publishes no candidate runtime.
188
- The action owner retains one pending candidate per `(workspace, name)` and a
189
- new request cancels and replaces it. Unrelated workspace changes remain
190
- authoritative while authorization is pending; drift of the same server fails
191
- completion with a conflict instead of replaying a stale workspace snapshot.
192
- `oauth.complete` accepts the complete
193
- callback URL so state, `code`, and `iss` remain one parsing unit. A missing,
194
- expired, mismatched, or replayed callback fails without exposing attacker-owned
195
- OAuth error text. It completes either pending hydration or the originally
196
- requested attach, replace, or reconnect. There is no callback HTTP endpoint,
197
- authority-root resource, or MCP-specific AG-UI route.
307
+ | Cannot connect or complete discovery/catalog preparation at the negotiated revision | `502 server-unavailable`, retryable; names the server and transport without exposing credentials |
308
+ | Client-credentials grant rejected by the authorization server | `502 oauth-client-credentials-failed`, non-retryable; names the server and client id, never the secret ({§oauth-client-credentials}) |
309
+
310
+ §oauth-continuation Interactive preparation publishes the alias as enabled and
311
+ `authorization-required` with its URL; it publishes no runtime. The adapter
312
+ retains one pending candidate per `(worker, alias)` holding the challenged
313
+ connection and one Worker residency lease; a new challenge for the alias
314
+ supersedes and releases the previous one ({§oauth-lifetime}).
315
+ `oauth.complete` accepts the complete callback URL so state, `code`, and
316
+ `iss` remain one parsing unit; it finishes the pending connection's
317
+ authorization, prepares its active attachment, and re-enables the alias through
318
+ the coordinator, whose publication consumes the prepared attachment and
319
+ releases the lease. A callback for a superseded attempt fails as invalid; a
320
+ committed attachment that no longer matches the pending definition fails
321
+ with a conflict instead of replaying a stale snapshot. A missing, expired,
322
+ mismatched, or replayed callback fails without exposing attacker-owned OAuth
323
+ error text.
324
+
325
+ ## §oauth-lifetime Interactive OAuth lifetime and reauthorization
326
+
327
+ Interactive OAuth state is deliberately ephemeral and process-memory: client
328
+ registration data, access and refresh tokens, the PKCE verifier, and pending
329
+ state live only in the owning connection or pending candidate. Nothing
330
+ OAuth-secret is written to SQLite; the durable worker state holds only the
331
+ unexpanded definition ({§mcp-configuration}). There is no callback HTTP
332
+ listener, authority-root resource, or daemon-side browser side channel: the
333
+ client returns the complete callback URL through `worker.mcp.oauth.complete`
334
+ so `state`, `code`, and `iss` remain one parsing unit. Reauthorization after a
335
+ daemon restart is the intended journey, documented here rather than presented
336
+ as an accidental failure.
337
+
338
+ | Journey point | Behaviour |
339
+ |---|---|
340
+ | Pending authorization | One pending candidate per `(worker, alias)`; a new add or customized enable cancels and replaces it. A callback from a superseded attempt fails state validation instead of cross-completing. |
341
+ | Client disconnect | Does not touch the pending candidate; it can still be completed, or replaced by a fresh request. |
342
+ | Daemon restart during pending | The candidate is lost: nothing was durable, no attachment publishes, and `oauth.complete` answers `404 oauth-not-pending`. Start authorization again. |
343
+ | Daemon restart after authorization | The durable definition rehydrates but tokens are gone; the attachment publishes `authorization-required` and enable returns a fresh `{ status: 202, authorization: { url } }`. The operator reauthorizes. |
344
+ | Token expiry | An expired access token surfaces as one unauthorized response; the SDK re-acquires via `refresh_token` when one was issued, otherwise re-enters interactive authorization. |
345
+ | Refresh | Happens only against the issuer bound during the original authorization; the refreshed token replaces the in-memory token. |
346
+ | Worker disable/remove | Closes the attachment and clears its pending candidate; no durable secret deletion is needed because nothing secret is durable. |
347
+ | Server replacement | Completion compares the pending candidate's expected definition with the current one; drift of the same server fails `409 oauth-target-conflict` instead of replaying a stale snapshot. |
348
+ | Cross-authorization protection | Candidates are keyed by `(worker, alias)`; callback state, PKCE, and issuer are validated by the SDK against the attempt that created them, so no other worker, alias, or attempt can complete this authorization. |
349
+
350
+ ## §oauth-client-credentials Client-credentials grant adoption
351
+
352
+ The `client-credentials` arm of `McpServerDefinition.authorization` adopts the
353
+ official `io.modelcontextprotocol/oauth-client-credentials` extension's
354
+ client-secret form faithfully: an MCP connection whose definition holds a
355
+ client-credentials grant advertises the extension capability in
356
+ `clientCapabilities.extensions`; connections without one never claim it. The
357
+ grant uses `client_secret_basic` authentication with `grant_type
358
+ client_credentials`. Scope from the definition's optional `scope` is passed to
359
+ the token request. The credential itself is one complete symbolic environment
360
+ reference (`clientSecret: "${NAME}"`), expanded only while preparing the
361
+ connection; it is never stored in SQLite, logged, or echoed in Problems.
362
+
363
+ | Aspect | Behaviour |
364
+ |---|---|
365
+ | Issuer binding | The definition's optional `issuer` is passed as the SDK provider's `expectedIssuer`, stamping the credential with its authorization server so SEP-2352 issuer checks refuse to send it elsewhere. Absent, the SDK's legacy no-binding behaviour applies. |
366
+ | Token lifetime | Token refresh is 401-triggered by the SDK client: an expired access token surfaces as one unauthorized response, the provider re-fetches with the stored credential, and the request is retried. Proactive expiry scheduling is a client-internal optimization, not a wire requirement; Plurnk does not wrap the SDK with its own scheduler. |
367
+ | Rotation | `clientSecret` resolution happens per connection preparation, so rotating the operator environment value takes effect on the next preparation of the server. |
368
+ | Errors | A rejected grant crosses the action boundary as `502 oauth-client-credentials-failed`, non-retryable, naming the server and client id only; SDK OAuth error text is never echoed. Other connection failures keep the generic `server-unavailable` allocation. |
369
+
370
+ Static credentials authorize application principals (service attachments, CI,
371
+ daemons), not human users. Private-key JWT and static JWT assertions for
372
+ client authentication are a declared non-goal; the specification permits a
373
+ secret-only client and Plurnk declines the assertion arms. The interactive
374
+ OAuth arm is the human-principal path ({§mcp-management-actions}); bearer
375
+ remains the private-service/legacy transport credential.
376
+
377
+ ## §mcp-ema-deferral Enterprise-managed authorization deferral
378
+
379
+ Plurnk does not advertise or implement
380
+ `io.modelcontextprotocol/enterprise-managed-authorization`. The SDK supplies
381
+ the wire steps (ID-JAG acquisition via RFC 8693 and the RFC 7523 JWT bearer
382
+ grant); the extension's remaining responsibilities are enterprise deployment
383
+ policy, not open-source host mechanics:
384
+
385
+ | Responsibility | Ownership |
386
+ |---|---|
387
+ | Capability advertisement, ID-JAG and access-token exchange, scope-error handling | Public protocol responsibilities the SDK host could own |
388
+ | SSO acquisition of the identity assertion (ID token or SAML) | Presumes a user session the headless daemon does not own |
389
+ | Saving the identity assertion for later use | Durable identity material; conflicts with the credential policy — secrets have one owner, the operator environment, and never a durable store |
390
+ | IdP endpoint and client registration configuration | Organization-owned; Plurnk has no org-level configuration seam |
391
+
392
+ The conformance client declines the enterprise scenarios, the capability
393
+ matrix keeps the extension non-advertised ({§mcp-capability-matrix}), and the
394
+ extension stays separate from core OAuth and client-credentials reporting.
395
+ Re-evaluate when an organization-owned configuration owner exists and a
396
+ decision on durable identity material is ratified.
198
397
 
199
398
  ## §mcp-setup Atomic lifecycle
200
399
 
201
- For each workspace, hydration resolves service defaults against the durable
202
- attachment/tombstone map, opens and discovers every effective connection,
400
+ When a cold worker is demanded, activation resolves service defaults and durable positive
401
+ worker state, opens and discovers only enabled connections,
203
402
  lists the negotiated catalogs, applies enabled/effect policy, builds each exact
204
403
  tool Registry and resource facet, and submits one complete owner snapshot to
205
- {§module-workspace-capabilities}. A configured tool absent from the server, a
404
+ {§module-worker-capabilities}. A configured tool absent from the server, a
206
405
  duplicate remote name, an enabled name not representable as a Plurnk target,
207
- or a `read` name outside the enabled set fails that workspace hydration. No
406
+ or a `read` name outside the enabled set fails that worker activation. No
208
407
  partial namespace is published and every acquired candidate closes.
209
408
 
210
- Attach, replace, and reconnect prepare the candidate while the old snapshot
211
- remains authoritative, then commit only at {§module-workspace-quiescence}. The
409
+ MCP participates in core Functionality residency ({§module-worker-residency}).
410
+ Every tool call and Task retains the worker from executor entry through its
411
+ terminal result; an interactive OAuth candidate retains it until completion,
412
+ replacement, cancellation, or module shutdown. Catalog refresh timers are
413
+ infrastructure, not residency owners: cooling serializes behind a refresh
414
+ already running and cancels any timer not yet begun. At a lease-free quiescent
415
+ boundary, deactivation removes the worker snapshot and closes all of its
416
+ connections. Core separately withdraws the executor/scheme publication while
417
+ preserving durable MCP state and generated reference entries for transparent
418
+ reactivation.
419
+
420
+ Add and enable prepare the candidate while the old snapshot remains
421
+ authoritative, then commit only at {§module-worker-quiescence}. Disable and
422
+ remove commit the complete reduced snapshot at the same boundary. The
212
423
  old connection rejects replacement while it owns an active protocol request,
213
424
  MRTR exchange, or Task. Cache/list-change watches are infrastructure and close
214
425
  with the old connection after the new snapshot commits. A failed candidate or
@@ -216,17 +427,19 @@ commit leaves the durable definition, connection, Registry, docs, and resource
216
427
  authority unchanged. Materialization and registration inspect the complete
217
428
  owning operation result; a non-success preserves its original Problem.
218
429
 
219
- Shutdown first prevents new work, cancels pending OAuth candidates and
220
- infrastructure watches, settles every active request and Task, closes every
221
- acquired connection, then reports all close failures. Whole-connection
222
- shutdown retires subscription work before closing its transport; it does not
223
- first issue a redundant per-listen cancellation.
430
+ Shutdown first prevents new serialized work, cancels infrastructure watches,
431
+ and closes every acquired connection—including a candidate still negotiating.
432
+ That cancellation reaches pending OAuth, active requests, and Tasks. It then
433
+ waits active mutations to settle, discards process-local snapshots, and reports
434
+ every close failure. Whole-connection shutdown retires subscription work before
435
+ closing its transport; it does not first issue a redundant per-listen
436
+ cancellation.
224
437
 
225
438
  ## §mcp-host-composition Protocol-to-Plurnk composition
226
439
 
227
440
  One `ServerConnection` owns negotiation, SDK caches, authorization partition,
228
441
  subscriptions, active request controllers, MRTR rounds, and Tasks for one
229
- workspace attachment. The host does not reproduce SDK protocol machinery.
442
+ worker attachment. The host does not reproduce SDK protocol machinery.
230
443
 
231
444
  | Protocol event | Plurnk composition |
232
445
  |---|---|
@@ -246,27 +459,62 @@ pending client-owned interaction exactly as proposal review does. MRTR round
246
459
  limits, request timeout, cancellation, and Task terminal state are one
247
460
  operation lifecycle; none becomes a hidden retry loop.
248
461
 
462
+ ## §mcp-result-content Passive result content
463
+
464
+ Every passive content variant the modern revision defines — text, image,
465
+ audio, resource links, and embedded text/blob resources — is preserved
466
+ losslessly into the EXEC channel as one JSON value
467
+ ({§json-result-rendering}), the durable evidence path. Plurnk does not claim
468
+ first-class client rendering of non-text variants and adds no MCP-only media
469
+ envelopes: presentation is a client concern over ordinary typed
470
+ entries/resources, and a text-only client degrades by rendering the JSON. A
471
+ standalone `blob` content block is not a modern `tools/call` content member
472
+ (blobs ride inside embedded blob resources) and is rejected as
473
+ protocol-invalid. Size limits and MIME trust remain ordinary channel and
474
+ entry policy, not MCP-specific rules.
475
+
476
+ ## §mcp-apps-exclusion MCP Apps exclusion
477
+
478
+ Plurnk does not advertise or implement MCP Apps. An Apps host must sandbox
479
+ render third-party HTML/JavaScript, enforce CSP and `_meta.ui` permissions,
480
+ mediate a `postMessage` JSON-RPC `ui/` dialect, proxy app-initiated tool
481
+ calls with consent, and own teardown. No Plurnk client can enforce that
482
+ sandbox today (terminal and Neovim cannot), the daemon is not a second
483
+ application platform, and AG-UI has no standard Apps projection — inventing
484
+ a private event stream to carry Apps is rejected. Tool descriptions carrying
485
+ `_meta.ui` metadata project into Plurnk without it: model-facing summaries
486
+ derive only from name, description, title, and input schema, and no UI
487
+ resource is fetched or preloaded. The capability matrix keeps the extension
488
+ non-advertised ({§mcp-capability-matrix}). Re-evaluate only when a
489
+ sandbox-capable client exists and a standard AG-UI projection is agreed;
490
+ even then the capability would be per-client-advertised, never daemon-wide.
491
+
249
492
  ## §mcp-model-projection Model-facing projection
250
493
 
251
494
  | MCP surface | Plurnk surface |
252
495
  |---|---|
253
- | Server | One registered executor family and matching resource scheme |
254
- | Enabled tool | One exact Registered Tools row and `## EXEC0 [<server>] (<tool>)` call |
255
- | Enabled tool details | `worker://plurnk/docs/<server>.md` through the standard kernel documentation surface |
496
+ | Server | One registered executor family, `worker://~/_plurnk/tools/<server>.md`, and matching resource scheme |
497
+ | Enabled tool | One annotated call in the compact family document plus one exact `worker://~/_plurnk/tools/<server>/<encoded-tool>.md` input-contract document |
498
+ | Tool survey | Ordinary FIND summary metadata from the standard executable-tool resource tree |
256
499
  | Resource catalog | `<server>:///` and `<server>:///resources` |
257
500
  | Resources | `<server>:///resources` and encoded resource-URI descendants |
258
501
  | Prompts | `<server>:///prompts` and encoded prompt-name descendants |
259
502
 
260
503
  §mcp-tool-presentation One canonical enabled-tool snapshot owns every
261
504
  model-facing and executable consequence. Each enabled remote tool becomes one
262
- exact target in {§executor-tool-registry}. Its Registered Tools row carries the
263
- remote description, requiredness derived from the input schema, and a
264
- deterministic one-line JSON-shaped signature: quoted property names, `?` on
265
- optional properties, primitive type words, and literal unions—never fabricated
266
- argument data. The snapshot's standard kernel document contains each enabled
267
- tool's description and exact input/output JSON Schemas. Disabled names appear
268
- in neither surface, and there is no MCP-specific FIND, READ, authority-root, or
269
- other model discovery mechanism for tools.
505
+ exact target in {§executor-tool-registry}. Its standard
506
+ {§executor-tool-document} carries the normalized remote description as Summary,
507
+ requiredness derived from the input schema, and a deterministic one-line
508
+ JSON-shaped invocation signature: quoted property names, `?` on optional
509
+ properties, primitive type words, and literal unions—never fabricated argument
510
+ data. The compact family document projects those same facts into annotated,
511
+ copyable EXEC headings; each exact child additionally projects property-level
512
+ input descriptions and standard constraints such as defaults, formats, ranges,
513
+ lengths, and patterns. A missing remote description receives a deterministic
514
+ server-and-tool summary rather than an invented capability claim. Output schemas
515
+ do not enter model teaching; the returned value remains ordinary evidence. Disabled names
516
+ appear in neither discovery nor admission, and there is no MCP-specific FIND,
517
+ READ, authority-root, or other model discovery mechanism for tools.
270
518
 
271
519
  Core validates the exact target and the selected tool's invocation before
272
520
  effect admission. `McpExecutor.run()` independently rejects a target outside
@@ -2,28 +2,53 @@ import { BaseExecutor } from "@plurnk/plurnk-execs";
2
2
  import type { ChannelDecl, Effect, ExecArgs, ExecResult, RuntimeAvailability, RuntimeDecl, RuntimeToolRegistry } from "@plurnk/plurnk-execs";
3
3
  import ServerConnection, { type ServerCatalog } from "./client.ts";
4
4
  import type { ToolPolicy } from "./config.ts";
5
- export declare const runtimeDecl: (name: string) => RuntimeDecl;
5
+ export declare const serverSummary: (name: string, catalog: ServerCatalog | undefined, override: string | undefined) => string;
6
+ export declare const runtimeDecl: (name: string, summary: string, expandTools: boolean) => RuntimeDecl;
6
7
  export default class McpExecutor extends BaseExecutor {
7
8
  #private;
8
9
  constructor(metadata: {
9
10
  runtime: string;
10
11
  glyph: string;
11
- }, connection: ServerConnection, policy?: Partial<ToolPolicy>);
12
+ }, connection: ServerConnection, retainWorkspace: () => () => void, policy?: Partial<ToolPolicy>, toolSummaries?: ReadonlyMap<string, string>);
12
13
  get manifest(): {
13
14
  name: string;
15
+ authority?: import("@plurnk/plurnk-schemes").SchemeAuthority;
14
16
  channels: Record<string, string>;
15
17
  defaultChannel: string;
16
- category: "data" | "logging" | "control";
17
18
  writableBy: ReadonlyArray<import("@plurnk/plurnk-schemes").WriterTier>;
18
19
  volatile: boolean;
19
20
  modelVisible: boolean;
20
21
  folderScopes?: boolean;
21
22
  textEditScopes?: boolean;
23
+ lineAnchors?: boolean;
22
24
  foldedByDefault?: boolean;
23
25
  flags?: import("@plurnk/plurnk-schemes").SchemeFlagAffinity;
24
26
  documentation?: string;
25
27
  glyph?: string;
26
28
  storedScheme?: string;
29
+ category: "data";
30
+ entryOwner: import("@plurnk/plurnk-schemes").SchemeEntryOwner;
31
+ inherit: import("@plurnk/plurnk-schemes").SchemeEntryInheritance;
32
+ example: string;
33
+ } | {
34
+ name: string;
35
+ authority?: import("@plurnk/plurnk-schemes").SchemeAuthority;
36
+ channels: Record<string, string>;
37
+ defaultChannel: string;
38
+ writableBy: ReadonlyArray<import("@plurnk/plurnk-schemes").WriterTier>;
39
+ volatile: boolean;
40
+ modelVisible: boolean;
41
+ folderScopes?: boolean;
42
+ textEditScopes?: boolean;
43
+ lineAnchors?: boolean;
44
+ foldedByDefault?: boolean;
45
+ flags?: import("@plurnk/plurnk-schemes").SchemeFlagAffinity;
46
+ documentation?: string;
47
+ glyph?: string;
48
+ storedScheme?: string;
49
+ category: "logging" | "control";
50
+ entryOwner?: never;
51
+ inherit?: never;
27
52
  example: string;
28
53
  };
29
54
  get channels(): Readonly<Record<string, ChannelDecl>>;
@@ -1 +1 @@
1
- {"version":3,"file":"McpExecutor.d.ts","sourceRoot":"","sources":["../src/McpExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EAMf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,UAAU,EAEV,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,gBAAgB,EAAE,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,eAAO,MAAM,WAAW,SAAU,MAAM,KAAG,WASzC,CAAC;AAuBH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY;;IAOjD,YACI,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAC5C,UAAU,EAAE,gBAAgB,EAC5B,MAAM,GAAE,OAAO,CAAC,UAAU,CAAM,EAMnC;IAED,IAAa,QAAQ;;;;;;;;;;;;;;;;MAKpB;IAED,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAMpD;IAEQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK7C;IAgCD,YAAY,IAAI,mBAAmB,CAKlC;IAED,IAAI,OAAO,IAAI,aAAa,CAK3B;IAEc,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgBvE;IAEK,gBAAgB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqBzE;IAEK,GAAG,CAAC,EACN,OAAO,EACP,IAAI,EACJ,MAAM,EACN,MAAM,EACN,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,QAAQ,GACX,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAiHhC;CACJ"}
1
+ {"version":3,"file":"McpExecutor.d.ts","sourceRoot":"","sources":["../src/McpExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EAMf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,UAAU,EAEV,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,gBAAgB,EAAE,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAkB9C,eAAO,MAAM,aAAa,SAChB,MAAM,WACH,aAAa,GAAG,SAAS,YACxB,MAAM,GAAG,SAAS,KAC7B,MAYF,CAAC;AAEF,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,MAAM,eAAe,OAAO,KAAG,WAchF,CAAC;AAuBH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY;;IASjD,YACI,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAC5C,UAAU,EAAE,gBAAgB,EAC5B,eAAe,EAAE,MAAM,MAAM,IAAI,EACjC,MAAM,GAAE,OAAO,CAAC,UAAU,CAAM,EAChC,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAQ9C;IAED,IAAa,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAKpB;IAED,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAMpD;IAEQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK7C;IAgCD,YAAY,IAAI,mBAAmB,CAKlC;IAED,IAAI,OAAO,IAAI,aAAa,CAK3B;IAEc,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAgBvE;IAEK,gBAAgB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqBzE;IAEK,GAAG,CAAC,EACN,OAAO,EACP,IAAI,EACJ,MAAM,EACN,MAAM,EACN,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,QAAQ,GACX,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAoHhC;CACJ"}