@plurnk/plurnk-mcp 1.23.0 → 1.25.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
@@ -172,11 +172,21 @@ conformance stays a separate named gate, never folded into a matrix row.
172
172
  | 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 |
173
173
  | Streamable HTTP | Send one POST per request or notification; accept JSON or SSE responses; close the response stream to cancel; modern connections never open the removed general GET stream |
174
174
 
175
+ §mcp-endpoint-security **Configured MCP and registry endpoints accept HTTP or HTTPS,
176
+ including private-network hosts.** Transport admission does not relax OAuth:
177
+ the SDK retains token-endpoint TLS enforcement (with its loopback exception),
178
+ issuer/resource binding, PKCE and redirect validation. No transport-policy bypass is supplied.
179
+
175
180
  §mcp-stdio-process-ownership A stdio connection owns the complete process group
176
181
  created for its server. Ordinary closure forwards stdin EOF and permits a
177
182
  bounded graceful exit; an expired shutdown bound or disappearance of the host
178
183
  process forcibly terminates the group, including descendants.
179
184
 
185
+ §mcp-redirect-refused **An HTTP endpoint is never redirected.** Every Streamable HTTP request,
186
+ authorization discovery included, sets `redirect: "manual"`; a 301, 302, 303, 307, or 308 fails the
187
+ connection naming the `Location`. Configured headers therefore never reach another origin
188
+ and the endpoint URL is corrected where it is configured.
189
+
180
190
  At the pinned revision, HTTP requests carry matching `MCP-Protocol-Version` and `Mcp-Method`
181
191
  headers. Named requests also carry `Mcp-Name`; declared primitive tool
182
192
  parameters carry validated `Mcp-Param-*` headers. Header names compare
@@ -204,169 +214,183 @@ originating distinction in its canonical Problem/result path.
204
214
 
205
215
  ## §mcp-configuration Configuration
206
216
 
207
- Service configuration and workspace state produce one available set and one
208
- enabled subset per workspace. Every `PLURNK_MCP_<server>` declares an available
209
- service-owned definition. `PLURNK_MCP_ENABLED` names the exact subset enabled
210
- when a workspace has no override. Workspace state may positively override a
211
- service definition's enabledness or own an added definition and its enabledness.
212
- Disabled definitions remain client-visible but contribute no connection,
213
- Registry, documentation, or resource authority.
217
+ MCP servers are complete connection definitions ({§mcp-server-definition}). Standalone files, plugin components, and environment declarations
218
+ supply the service baseline; live additions belong to the workspace. All use the common resolution
219
+ and lifecycle ({§configuration-definition-resolution}, {§functionality-coordinator}). Disabled definitions remain
220
+ client-visible but contribute no connection, Registry, documentation, or resource authority.
214
221
 
215
- §mcp-activation-isolation **Cold endpoint failure is capability-local.** Invalid
216
- service configuration or durable state fails admission, but an enabled server
222
+ §mcp-file-configuration **Standalone `mcp.json` files are read-only configuration inputs, not plugins.**
223
+ The module reads `mcp.json` in the directories supplied by {§agent-roots}. Definitions resolve
224
+ by alias, highest precedence first:
225
+
226
+ | Source | Location |
227
+ |---|---|
228
+ | Workspace overlay | Ordinary `mcp (add)` state |
229
+ | Environment | `PLURNK_MCP_<alias>` |
230
+ | Project | `<project>/.agents/mcp.json`, then selected project plugins |
231
+ | Plurnk-only | `$XDG_CONFIG_HOME/plurnk/mcp.json`, then selected Plurnk plugins |
232
+ | Shared global | `~/.agents/mcp.json`, then selected global plugins |
233
+ | Installed npm plugins | Selected plugin bundles in the installed graph |
234
+
235
+ - The document is an object with required `mcpServers`, an object keyed by server alias,
236
+ and an optional string `$schema` editor hint. No schema is fetched. No other top-level fields.
237
+ - Each entry is the owning connection definition without `name`; its map key supplies the name.
238
+ An omitted `type` is inferred from `command` (stdio) or `url` (Streamable HTTP).
239
+ Ambiguous, unsupported, or incomplete entries fail the same definition validator as environment/live inputs.
240
+ - Select the whole winning entry before validating it; never merge fields or fall back from an invalid winner.
241
+ Environment enabledness and tool controls apply independently to file-backed aliases.
242
+ - Missing files contribute nothing. Invalid/unreadable files or selected entries produce a named
243
+ configuration diagnostic under {§configuration-repair-path}, not a daemon exit or an empty catalog.
244
+ - Inspection reports `kind: file`, the absolute file `source`, and the entry's JSON Pointer `reference`.
245
+ Normal pre-turn refresh observes file edits and deletion; `list` never starts a server or writes a file.
246
+ Workspace removal restores the current inherited definition and controls.
247
+ - File entries retain {§mcp-launch-directory} and {§mcp-launch-environment}; a file's location is
248
+ not a subprocess working directory. Plugin-root configuration and packaging are separate.
249
+
250
+ `mcpServers` follows the common MCP catalog shape, also used by
251
+ [MCP Inspector](https://github.com/modelcontextprotocol/inspector/blob/main/docs/mcp-server-configuration.md).
252
+ The discovery locations are Plurnk's supported cross-client convention, not an MCP wire requirement.
253
+
254
+ §mcp-plugin-configuration **Plugin MCP components use their format's interpretation, not the native file dialect.**
255
+ The validated components supplied by {§agent-plugins-hosting} become ordinary configured MCP
256
+ definitions. The adapter carries the canonical plugin root and persistent data directory as
257
+ interpretation context under {§functionality-adapter}; inspection preserves symbolic definitions.
258
+
259
+ | Field or boundary | Agent Plugins v1 behavior |
260
+ |---|---|
261
+ | `command` | Bare executable or plugin-root-relative `./` path; no expansion |
262
+ | `args`, `env`, `cwd` | One nonrecursive expansion of `${PLUGIN_ROOT}` and `${PLUGIN_DATA}` only; all other placeholder-like text stays literal |
263
+ | Omitted `cwd` | Plugin root; explicit plugin/data cwd is contained within its corresponding resolved root |
264
+ | Subprocess environment | Ordinary admitted operator/workspace environment, then configured values, then authoritative `PLUGIN_ROOT` and `PLUGIN_DATA` |
265
+ | Data | Created before launch; preserved across disable, removal of an override, and in-place plugin updates |
266
+ | Filesystem containment | Rechecked before each subprocess launch, including existing parents of missing paths |
267
+ | HTTP URL and headers | Literal; client-owned protocol headers win; existing no-redirect rule applies |
268
+ | Unsupported transport or name | Skip the entry with a configuration notice; never rename, reinterpret, or disable independent entries |
269
+
270
+ Supported transports are stdio and Streamable HTTP, not legacy SSE. Server names must meet
271
+ Plurnk's runtime alias grammar `[a-z][a-z0-9-]*`; other standard map keys are explicitly unsupported.
272
+ The standard loader owns component validation and narrow failure boundaries
273
+ ({§agent-plugins-mcp-entries}, {§agent-plugins-components}). Connection failures remain ordinary
274
+ unavailable outcomes. Current adapter notices join client/model configuration diagnostics;
275
+ source repair replaces them, rather than retaining a historical warning.
276
+
277
+ §mcp-activation-isolation **Cold endpoint failure is capability-local.** An enabled server
217
278
  that cannot connect or complete discovery during workspace activation remains
218
279
  enabled and client-visible as `unavailable`. It publishes no runtime, tools,
219
280
  resources, or documentation and cannot prevent other capabilities or the daemon
220
281
  from starting or serving dormant workers. Enabling that already-enabled alias is an explicit reconnect
221
282
  attempt; failure preserves the unavailable snapshot, while success atomically
222
- replaces it. Interactive add and enable mutations continue to reject an
223
- unavailable candidate without changing durable state.
283
+ replaces it. An interactive enable continues to reject an unavailable candidate
284
+ without changing durable state.
224
285
 
225
286
  | Variable | Contract |
226
287
  |---|---|
227
- | `PLURNK_MCP_<server>` | HTTP(S) URL or exact stdio executable; empty masks the definition ({§mcp-configuration-cascade}) |
228
- | `PLURNK_MCP_<server>_ARGS` | JSON string array for stdio |
229
- | `PLURNK_MCP_<server>_CWD` | Working directory for stdio |
230
- | `PLURNK_MCP_<server>_ENV` | JSON string map for stdio |
231
- | `PLURNK_MCP_<server>_BEARER` | HTTP bearer credential; use `${TOKEN}` expansion to retain the authoritative environment value |
232
- | `PLURNK_MCP_<server>_HEADERS` | JSON string map for supplementary HTTP headers |
233
- | `PLURNK_MCP_<server>_TOOLS` | Optional JSON array of exact enabled tool names; absent enables all listed server tools, while `[]` enables none |
234
- | `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 |
235
- | `PLURNK_MCP_<server>_SUMMARY` | Authored one-line server orientation ({§mcp-summary-derivation}) |
236
- | `PLURNK_MCP_<server>_<tool>_SUMMARY` | Authored one-line tool orientation; tool names fold the same way and may contain underscores |
237
- | `PLURNK_MCP_ENABLED` | JSON array of exact configured server aliases enabled by default. `[]` is the one spelling of none: the panel states it, and an absent or empty key is refused by name |
238
- | `PLURNK_MCP_EXPANDED` | JSON array subset of enabled servers whose every tool is surveyed at turn 0 — one FIND row per executable block of the family document, with aside and signature ({§tools-resource-materialization}); never a document delivered unasked; absent or `[]` expands none |
239
- | `PLURNK_MCP_CONNECT_TIMEOUT` | Positive integer milliseconds |
240
- | `PLURNK_MCP_REQUEST_TIMEOUT` | Positive integer milliseconds |
241
-
242
- Configured server names match `[a-z][a-z0-9-]*` after case-folding and share
243
- the executor and URI-authority namespace. Duplicate names, reserved-name
244
- collisions, orphan companions, wrong-transport companions, missing environment
245
- references, and invalid JSON fail startup. A stdio target is one exact
246
- executable string even when its path contains whitespace; arguments never hide
247
- inside it. Bearer authentication and a case-insensitive `Authorization` entry
248
- in `_HEADERS` are mutually exclusive.
249
-
250
- §mcp-summary-derivation **Orientation prefers authored purpose over display
288
+ | `PLURNK_MCP_<alias>` | Complete `McpServerDefinition` JSON; transport and authentication replace together |
289
+ | `PLURNK_MCP_ENABLED`, `PLURNK_MCP_<alias>_ENABLED` | Shared default and per-alias flags ({§resource-environment}); declared servers default enabled |
290
+ | `PLURNK_MCP_<alias>_TOOLS` | Optional JSON array of exact enabled tool names; absent or empty enables every listed tool, and `[]` enables none |
291
+ | `PLURNK_MCP_EXPANDED` | JSON array of aliases whose tool invocations are surveyed at turn 0 ({§tools-resource-materialization}); absent or `[]` expands none |
292
+ | `PLURNK_MCP_CONNECT_TIMEOUT` | Positive integer milliseconds for setup and each complete catalog walk |
293
+ | `PLURNK_MCP_REQUEST_TIMEOUT` | Positive integer milliseconds for the whole operation |
294
+ | `PLURNK_MCP_REGISTRY_URL` | HTTP(S) registry discovery endpoint; empty disables registry search |
295
+ | `PLURNK_MCP_REGISTRY_LIMIT` | Positive integer result bound |
296
+
297
+ Aliases and controls follow {§resource-environment}. Malformed definitions or controls fail
298
+ configuration by variable name, even when disabled or not yet associated with a resource.
299
+ Authentication belongs in the definition, never in separate bearer/OAuth environment companions.
300
+ The package's offline validator composes these same readers
301
+ ({§operator-config-offline-validation}); it performs no connection or secret resolution.
302
+ Runtime configuration errors leave the manager inspectable without publishing MCP
303
+ capabilities or preventing unrelated model work ({§configuration-repair-path}).
304
+
305
+ §mcp-definitions **A server is not a plugin installation.** `add` persists a workspace
306
+ connection definition; `remove` removes that definition through the common coordinator.
307
+ Neither writes nor deletes project, user or global configuration files. MCP management
308
+ and preparation do not enumerate or install Agent Plugins. Plugin integration is a separate
309
+ configuration-source concern, not an MCP definition or lifecycle.
310
+
311
+ | Transport | Launch or connection |
312
+ |---|---|
313
+ | `stdio` | Spawn `command` with `args`, without a shell. Bare executable names use PATH; relative executable paths use the working directory. `args`, `env` and explicit `cwd` expand `${NAME}` references once against the workspace-composed operator environment. |
314
+ | `streamable-http` | Connect to `url` with `headers` and `authorization` from that same definition. Header references expand at connection time. Protocol-generated headers are transport-owned; application authentication headers are retained. Declaring both structured authorization and an Authorization header is invalid. No redirect is followed ({§mcp-redirect-refused}). |
315
+
316
+ §mcp-launch-directory **An implicit working directory is workspace-owned state, not the project.**
317
+ Core's {§module-workspace-directory} supplies a stable directory isolated by workspace and MCP alias.
318
+ An explicit `cwd` must resolve to an absolute path and overrides that directory; it is not confined
319
+ to an installation root. The caller owns provisioning an explicit directory. Runtime paths and
320
+ resolved environment values are never copied back into a stored definition. Disabling or removing
321
+ a server closes its connection, not its retained state directory or saved results.
322
+
323
+ A tool whose `annotations.readOnlyHint` is true takes the `read` effect; every other tool keeps the
324
+ conservative `host` effect ({§mcp-model-projection}).
325
+
326
+ §mcp-summary-derivation **Orientation prefers the server's own purpose over display
251
327
  labels; no capabilities are inferred.** Blank values fall through:
252
328
 
253
329
  | Summary | Precedence, highest first |
254
330
  |---|---|
255
- | Server | `_SUMMARY` → `serverInfo.description` → `instructions` → `serverInfo.title` → effective admitted tool-name list → server alias |
256
- | Tool | `_<server>_<tool>_SUMMARY` → `description` → `title` → `annotations.title` → tool name |
331
+ | Server | `serverInfo.description` → `instructions` → `serverInfo.title` → effective admitted tool-name list → server alias |
332
+ | Tool | `description` → `title` → `annotations.title` → tool name |
257
333
 
258
334
  Derived prose is whitespace-normalized, limited to its first sentence, and
259
- clipped within 80 characters plus an ellipsis, preferring a word boundary. Explicit
260
- one-line overrides remain intact. Full server instructions remain authored
335
+ clipped within 80 characters plus an ellipsis, preferring a word boundary. Full server instructions remain authored
261
336
  Markdown in the family document's runtime `details`, available on demand;
262
337
  turn0 surveys only the compact summary/invocations. Full tool descriptions
263
338
  remain in the linked input-contract documents. With tools, the runtime declares
264
339
  `{ from: "tools", description }`: purpose annotates rather than replaces the
265
340
  complete effective menu in the family Summary and survey row
266
- ({§scheme-catalog-aside}). Without authored purpose the menu stands alone. The tool
341
+ ({§scheme-catalog-aside}). Without a stated purpose the menu stands alone. The tool
267
342
  doc's Summary section IS the invocation form
268
343
  ```` ```server (tool) <!-- one-liner --> ````, so the discovery row teaches the
269
- call ({§tools-resource-materialization}). Summary companions expand `${NAME}`
270
- references like every other companion.
344
+ call ({§tools-resource-materialization}).
271
345
 
272
- §mcp-definition-wire The contracts-owned `McpServerDefinition` JSON Schema is
273
- the normalized durable definition shape. It is a closed discriminated union:
274
-
275
- | Transport / authorization | Required definition | Optional definition |
276
- |---|---|---|
277
- | `stdio` | `name`, `transport`, `command` | `args`, `cwd`, `env`, `tools`, `read` |
278
- | `http` + none | `name`, `transport`, `url` | `headers`, `tools`, `read` |
279
- | `http` + bearer | above plus `authorization: { type: "bearer", token: "${NAME}" }` | — |
280
- | `http` + interactive OAuth / CIMD preferred | above plus `authorization: { type: "oauth", redirectUrl, clientMetadataUrl }` | `scope`; DCR remains the server-advertised fallback when CIMD is unavailable |
281
- | `http` + interactive OAuth / pre-registered | above plus `authorization: { type: "oauth", redirectUrl, clientId, clientSecret: "${NAME}" }` | `scope` |
282
- | `http` + interactive OAuth / DCR fallback only | above plus `authorization: { type: "oauth", redirectUrl }` | `scope` |
283
- | `http` + client credentials | above plus `authorization: { type: "client-credentials", clientId, clientSecret: "${NAME}" }` | `scope`; `issuer` binds the credential to its authorization server ({§oauth-client-credentials}) |
284
-
285
- `tools` absent enables the complete listed set; `[]` enables none. `read` is an
286
- exact subset of the enabled set. A credential field is one complete symbolic
287
- environment reference, not a copied token. Other string-valued `headers`,
288
- `env`, `cwd`, and argument values may contain symbolic references and are
289
- expanded only while preparing a connection. The unexpanded definition is the
290
- only durable form. Interactive OAuth tokens, PKCE verifier, issuer-bound
291
- discovery state, and authorization callback state remain process-memory
292
- credentials; a restart reconstructs the attachment as authorization-required
293
- instead of writing secrets into SQLite.
294
-
295
- The contracts-owned `McpServerDefinition` is the exact definition one `add`
296
- accepts and the coordinator persists; a client composes it from its target
297
- (an absolute HTTP(S) URL selects Streamable HTTP, anything else is one exact
298
- stdio executable) and its options (`args`, `cwd`, `env`, `headers`,
299
- `authorization`, `tools`, `read`). The normalized definition rejects
300
- transport-inapplicable options before any connection work.
301
-
302
- §mcp-configuration-cascade MCP server configuration has one field-wise
303
- precedence order: service environment, then the workspace's durable definition.
304
- Arrays and maps replace their lower value instead of appending or merging.
305
- An explicitly empty environment target omits that service definition, its
306
- companions (including summaries), and its inherited `ENABLED`/`EXPANDED`
307
- selections. Companion values are neither parsed nor expanded. It does not
308
- remove a workspace-owned definition or prohibit adding one. Genuinely undeclared
309
- aliases and case-fold collisions still fail validation.
310
- Client configuration is not a live layer: the contracts-owned
311
- `{§mcp-configuration-overlay}` enters only as the `configuration` of a
312
- `discover` query, is parsed by the same owner and path as service environment
313
- declarations, and yields inert candidates with client-configuration provenance
314
- ({§mcp-discovery}); adding one persists a complete, normalized, unexpanded
315
- workspace definition. Thus later enablement needs neither the originating client
316
- nor its configuration file, and symbolic credentials remain resolvable only by
317
- the service at connection preparation.
346
+ §mcp-server-settings **Independent behavior settings do not change connection definitions.**
347
+ `<alias>_TOOLS` narrows the effective tool catalog. A valid control may precede its definition,
348
+ without creating a server; all control values are validated immediately ({§resource-environment}).
349
+ Bearer and OAuth settings belong to the whole HTTP definition. Secrets in structured authorization
350
+ are `${NAME}` references expanded only while preparing the connection. Interactive tokens,
351
+ PKCE verifiers and callback state remain in memory; restart reconstructs an authorization-required
352
+ attachment, never a stored credential.
318
353
 
319
354
  ### §mcp-module The MCP family beneath the coordinator
320
355
 
321
- §mcp-launch-environment Discovery and stdio connection preparation use the workspace
322
- environment supplied by Core ({§workspace-env}), never the invoking worker's overrides.
323
- The admitted workspace environment reaches the actual subprocess; explicit definition
324
- launch options override it. Symbolic references resolve against the operator environment
325
- with the same workspace entries and masks applied. Resolved ambient values are never
326
- copied into durable definitions.
327
-
328
- An `env` header option on `mcp (add)` becomes that definition's retained stdio launch
329
- override, with the definition's existing symbolic-reference semantics. On `discover`
330
- it applies to the probe and its returned candidate. HTTP servers
331
- have no local process environment and refuse these stdio launch overrides; ordinary
332
- HTTP authorization/header references may use workspace values. A running server keeps
333
- its launch environment. Use ordinary `disable` and `enable` to restart it after an env
334
- change; there is no automatic restart or stale-configuration state.
335
-
336
- §mcp-working-storage **A local server never implicitly inherits the daemon's
337
- working directory.** The common connection boundary requires an explicit CWD
338
- or a host-supplied default. Missing or unusable storage fails connection
339
- preparation; it never falls back to the project.
340
-
341
- | Connection | Working directory and lifetime |
342
- | --- | --- |
343
- | Attached stdio server without `cwd` | `servers/<alias>` under the module directory from {§module-workspace-directory}; created lazily with mode `0700`, retained across disable/enable, removal, cooling, and daemon restart. |
344
- | Direct stdio discovery | A unique `discover-*` directory beneath that same module root; removed only after the probe connection closes, including unsuccessful probes. It is never persisted in the candidate definition. |
345
- | Explicit `cwd` | Honored without creation or cleanup. Relative values resolve against the launcher's CWD, not the default storage directory. |
346
- | HTTP | No local working directory is allocated. |
347
-
348
- Host-owned storage does not replace `HOME`, credentials, XDG environment values,
349
- tool arguments, or the project CWD of ordinary executors. Executables and file
350
- arguments needing a particular project must name it explicitly or configure
351
- `cwd`. Stored streams/resources retain their existing ownership. This is a
352
- default-placement contract, not filesystem confinement or a promise to
353
- redirect a third-party server's absolute writes. No server-specific flags or
354
- deprecated roots capability are introduced.
355
-
356
- §mcp-management-actions MCP is one family of workspace Functionality
357
- ({§functionality-coordinator}): the coordinator publishes `workspace.mcp.list |
358
- discover | add | enable | disable | remove` and the model's `mcp` executable fence
359
- family with the common semantics, durable state, and publication; this module
360
- registers the family adapter and owns protocol truth beneath it. `available`
361
- is the service environment with its `PLURNK_MCP_ENABLED` defaults; `admit`
362
- validates one exact definition and binds the alias to its `name`; `prepare`
363
- connects the enabled set, reusing unchanged live attachments, and returns one
364
- executor family and resource facet per connected server, one outcome per alias
365
- (`active` with catalog detail — negotiated protocol version, server identity,
366
- capabilities, tool names, resource and prompt counts — `unavailable` with its
367
- exact Problem, or `authorization-required` with its URL), and a two-phase
368
- snapshot: `commit` closes connections the new set no longer uses and records
369
- pending authorizations; `abort` closes only what the attempt opened.
356
+ §mcp-launch-environment **A server inherits the operator's environment.** A stdio server
357
+ starts with the operator's environment without plurnk's own secrets ({§exec-env-scoped}), as every MCP
358
+ client launches one: stdio servers read their credentials from the environment. The model's command
359
+ ceiling is not its base. The workspace layer ({§workspace-env}) applies on top, its values and
360
+ withholdings included, never the invoking worker's overrides; the definition's `env` follows. Definition references resolve against the operator environment with the same workspace
361
+ entries and masks applied, and resolved ambient values are never copied into definitions. A running server keeps its launch environment:
362
+ `disable` and `enable` restart it after an environment change, and there is no automatic restart or
363
+ stale-configuration state. HTTP servers have no local process environment.
364
+
365
+ §mcp-management-actions MCP is one family of workspace Functionality ({§functionality-coordinator}). The
366
+ coordinator publishes `workspace.mcp.list | discover | add | enable | disable | remove` and the model's
367
+ `mcp` executable fence family with the common semantics, durable state, and publication; this module
368
+ registers the family adapter and owns protocol truth beneath it. `available` is the configured service baseline; `add` and `remove` change workspace state ({§mcp-definitions}), and
369
+ `discover` searches the MCP Registry ({§mcp-registry-discovery}). `prepare` connects the enabled set, reusing
370
+ unchanged live attachments, and returns one executor family and resource facet per connected server,
371
+ one outcome per alias (`active` with catalog detail — negotiated protocol version, server identity,
372
+ capabilities, tool names, resource and prompt counts — `unavailable` with its exact Problem, or
373
+ `authorization-required` with its URL), and a two-phase snapshot: `commit` closes connections the new
374
+ set no longer uses and records pending authorizations; `abort` closes only what the attempt opened.
375
+
376
+ §mcp-registry-discovery **`discover` searches the MCP Registry.** `{ query }` asks
377
+ `PLURNK_MCP_REGISTRY_URL` (API v0.1) for one page of at most `PLURNK_MCP_REGISTRY_LIMIT` servers, each at
378
+ its latest version, whose names match. Each npm, PyPI, NuGet or OCI package with a stdio transport
379
+ becomes a stdio entry run as the registry's own examples run it (`npx -y`, `uvx`, `dnx`, or
380
+ `docker run -i --rm` passing each declared variable through), and each Streamable HTTP remote becomes a
381
+ URL entry with its non-secret literal headers. An entry that needs a person's input first, such as a
382
+ template variable or a required argument with no value, has none. A candidate is a complete definition;
383
+ its summary names the environment variables and headers the
384
+ server needs, and its provenance names the registry and the server's `name@version`. `source` and
385
+ `configuration` are not registry queries and are refused.
386
+
387
+ | Discovery, admission, or installation condition | Problem | Status |
388
+ |---|---|---|
389
+ | No registry is configured | `registry-not-configured` | 501 |
390
+ | The registry is unreachable, fails, or answers malformed | `discover-failed`, retryable | 502 |
391
+ | `discover` names a `source` or client `configuration` | `source-unsupported`, `configuration-unsupported` | 400 |
392
+ | The complete connection definition is invalid | `definition-invalid` | 400 |
393
+ | The alias differs from the definition's name | `alias-mismatch` | 400 |
370
394
 
371
395
  Two protocol continuations remain MCP-registered workspace actions beneath the
372
396
  common grammar:
@@ -376,15 +400,6 @@ common grammar:
376
400
  | `workspace.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. |
377
401
  | `workspace.mcp.complete` | `server`, `ref`, `argument`; optional `context` | Requests negotiated prompt/resource-template argument completion for a client-owned interaction. |
378
402
 
379
- §mcp-discovery Discovery is inert. `configuration` (a client's own
380
- `PLURNK_MCP_*` overlay) becomes candidates without connecting; `source` — an
381
- absolute HTTP(S) URL or one whitespace-separated command line — is probed at the
382
- negotiated revision for its catalog and released, yielding one candidate with
383
- direct-target provenance (an authorization challenge yields the candidate with
384
- that fact in its summary); a bare `query` requires a configured downstream
385
- registry and is `501 registry-not-configured` until one exists. No candidate
386
- is installed, persisted, enabled, or executed by discovery.
387
-
388
403
  Expected preparation failures cross the boundary as MCP-management Problems
389
404
  rather than generic failures; an explicit client action rejects them and a
390
405
  Worker's own accepted mutation publishes them as unavailable
@@ -392,7 +407,9 @@ Worker's own accepted mutation publishes them as unavailable
392
407
 
393
408
  | Endpoint condition | Problem |
394
409
  |---|---|
395
- | Cannot connect or complete discovery/catalog preparation at the negotiated revision | `502 server-unavailable`, retryable; names the server and transport without exposing credentials |
410
+ | Cannot connect or complete discovery/catalog preparation at the negotiated revision | `502 server-unavailable`, retryable; names the server and its `type` without exposing credentials |
411
+ | The endpoint answers with a redirect | `502 server-redirected`, non-retryable ({§mcp-redirect-refused}) |
412
+ | An operator setting of the alias is invalid | `422 server-settings-invalid`, non-retryable ({§mcp-server-settings}) |
396
413
  | 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}) |
397
414
 
398
415
  Resource and prompt failures retain one caught remote diagnostic only through
@@ -429,32 +446,32 @@ as an accidental failure.
429
446
 
430
447
  | Journey point | Behaviour |
431
448
  |---|---|
432
- | Pending authorization | One pending candidate per `(workspace, alias)`; a new add or customized enable cancels and replaces it. A callback from a superseded attempt fails state validation instead of cross-completing. |
449
+ | Pending authorization | One pending candidate per `(workspace, alias)`; a new challenge or customized enable cancels and replaces it. A callback from a superseded attempt fails state validation instead of cross-completing. |
433
450
  | Client disconnect | Does not touch the pending candidate; it can still be completed, or replaced by a fresh request. |
434
451
  | 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. |
435
452
  | Daemon restart after authorization | The durable definition rehydrates but tokens are gone; the attachment publishes `authorization-required` and enable returns status `202` with `definition.authorization.url` in the common mutation result. The operator reauthorizes. |
436
453
  | 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. |
437
454
  | Refresh | Happens only against the issuer bound during the original authorization; the refreshed token replaces the in-memory token. |
438
455
  | Workspace disable/remove | Closes the attachment and clears its pending candidate; no durable secret deletion is needed because nothing secret is durable. |
439
- | 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. |
456
+ | Server replacement | Publication discards any superseded pending connection and releases its residency, including replacement by a definition without OAuth. Completion of an unpublished configuration drift fails `409 oauth-target-conflict` instead of replaying a stale snapshot. |
440
457
  | Cross-authorization protection | Candidates are keyed by `(workspace, alias)`; callback state, PKCE, and issuer are validated by the SDK against the attempt that created them, so no other workspace, alias, or attempt can complete this authorization. |
441
458
 
442
459
  ## §oauth-client-credentials Client-credentials grant adoption
443
460
 
444
461
  The `client-credentials` arm of `McpServerDefinition.authorization` adopts the
445
462
  official `io.modelcontextprotocol/oauth-client-credentials` extension's
446
- client-secret form faithfully: an MCP connection whose definition holds a
447
- client-credentials grant advertises the extension capability in
463
+ client-secret form faithfully: a connection whose definition holds a
464
+ client-credentials grant ({§mcp-server-settings}) advertises the extension capability in
448
465
  `clientCapabilities.extensions`; connections without one never claim it. The
449
466
  grant uses `client_secret_basic` authentication with `grant_type
450
- client_credentials`. Scope from the definition's optional `scope` is passed to
467
+ client_credentials`. The setting's optional `scope` is passed to
451
468
  the token request. The credential itself is one complete symbolic environment
452
469
  reference (`clientSecret: "${NAME}"`), expanded only while preparing the
453
470
  connection; it is never stored in SQLite, logged, or echoed in Problems.
454
471
 
455
472
  | Aspect | Behaviour |
456
473
  |---|---|
457
- | 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. |
474
+ | Issuer binding | The setting'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. |
458
475
  | 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. |
459
476
  | Rotation | `clientSecret` resolution happens per connection preparation, so rotating the operator environment value takes effect on the next preparation of the server. |
460
477
  | 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. |
@@ -513,6 +530,9 @@ boundaries; neither closes or replaces the committed connection.
513
530
  | Disable/remove, workspace cooling, or shutdown | Retires obsolete refresh timers and invalidations. |
514
531
 
515
532
  MCP participates in core Functionality residency ({§module-workspace-residency}).
533
+ Preparation reports the current server alias through the coordinator's activity
534
+ contract ({§functionality-preparation-visibility}); inspection never starts a
535
+ connection ({§functionality-inspection}).
516
536
  Every tool call and Task retains the workspace from executor entry through its
517
537
  terminal result; an interactive OAuth candidate retains it until completion,
518
538
  replacement, cancellation, or module shutdown. Catalog refresh timers are
@@ -533,8 +553,11 @@ commit leaves the durable definition, connection, Registry, docs, and resource
533
553
  authority unchanged. Materialization and registration inspect the complete
534
554
  owning operation result; a non-success preserves its original Problem.
535
555
 
536
- §mcp-connection-shutdown Shutdown prevents new work and aborts each connection's
537
- active requests, including client-input waits. Their protocol cleanup settles
556
+ §mcp-connection-shutdown Module `stop()` prevents new work and aborts each connection's
557
+ active requests, including client-input waits. Discovery and preparation recheck
558
+ admission after asynchronous environment resolution, before owning a new connection;
559
+ connection startup rechecks after asynchronous directory resolution before opening
560
+ its transport. Their protocol cleanup settles
538
561
  before the extension channel or connected transport closes, so a created Task
539
562
  can receive `tasks/cancel`. Concurrent closers await the same settlement.
540
563
  Candidates still negotiating and standalone OAuth transports close immediately.
@@ -586,6 +609,11 @@ does not weaken this uncertain-outcome boundary.
586
609
  remote diagnostic as structured extensions. Their prose states only the failed
587
610
  boundary fact; it neither repeats those fields nor infers whether the remote
588
611
  effect occurred.
612
+ For `isError: true`, nonblank text content blocks, in order and joined by
613
+ newlines, supply `diagnostic` under the existing executor error-detail bound.
614
+ Nontext parts and structured data are not interpreted as explanations. With no
615
+ text explanation the extension is absent; successful results do not acquire one.
616
+ The complete result and passive output remain unchanged ({§mcp-result-content}).
589
617
 
590
618
  §mcp-trailing-aside A tool call's body is one JSON object. HTML comments after
591
619
  that object are the writer's aside, not arguments: when the body does not parse
@@ -696,7 +724,7 @@ shallow required-field previews and alias-scoped schema links. Each linked child
696
724
  preserves the complete remote description and raw input schema, without
697
725
  reconstructing property tables or expanding nested constraints into the preview.
698
726
  Output schemas do not enter model teaching; the returned value remains ordinary evidence. Disabled names
699
- appear in neither discovery nor admission, and there is no MCP-specific FIND,
727
+ appear in no model teaching, and there is no MCP-specific FIND,
700
728
  READ, authority-root, or other model discovery mechanism for tools.
701
729
 
702
730
  Core validates the exact target and the selected tool's invocation before
@@ -707,10 +735,9 @@ contain resources and resource templates, never tools. Tool results become
707
735
  ordinary Plurnk entries and channels, so slicing, tags, curation, notices, and
708
736
  Problems need no MCP-specific parallel mechanism.
709
737
 
710
- MCP tool annotations remain untrusted metadata, not admission authority. The
711
- operator-owned `_READ` subset classifies enabled observations as the executor
712
- `read` effect; every other enabled tool remains `host` and therefore uses the
713
- ordinary proposal policy. Effect classification receiving an unregistered
738
+ A configured server's `annotations.readOnlyHint` supplies its tool's declared effect:
739
+ a tool marked read-only takes the executor `read` effect; every other enabled
740
+ tool remains `host` and therefore uses the ordinary proposal policy. Effect classification receiving an unregistered
714
741
  target is an internal contract violation rather than a conservative guess.
715
742
 
716
743
  ## §mcp-conformance Conformance authority
@@ -736,12 +763,7 @@ stdio/Streamable HTTP servers are composition evidence only.
736
763
  | `invalid-tool-arguments` | 400 | The tool arguments are not one JSON object. Recovery: One JSON object per MCP tool call; a second call is a second fence. |
737
764
  | `oauth-client-credentials-failed` | 502 | MCP server '*name*' rejected the client-credentials grant; check the configured client credentials and issuer. |
738
765
  | `parameters-invalid` | 400 | Unsupported parameter(s): *names*. |
739
- | `configuration-invalid` | 400 | The supplied MCP configuration is invalid. |
740
- | `discover-failed` | 502 | MCP target '*source*' could not be inspected. |
741
- | `registry-not-configured` | 501 | MCP registry search requires a configured downstream registry; none is configured. Recovery: Discover an explicit source (URL or command) or configure a registry. |
742
- | `env-transport` | 400 | An HTTP MCP server has no local process environment. |
743
- | `definition-invalid` | 400 | The MCP server definition is invalid. |
744
- | `alias-mismatch` | 400 | Alias '*alias*' must equal the definition's name '*name*'. |
766
+ | `server-settings-invalid` | 422 | MCP server '*name*' has invalid operator settings: *cause*. |
745
767
  | `server-busy` | 409 | MCP server '*name*' has *n* active request(s). |
746
768
  | `obsolete-connection-close-failed` | 500 | The MCP capability change committed, but an obsolete connection did not close cleanly. |
747
769
  | `oauth-not-pending` | 404 | MCP server '*alias*' has no pending OAuth authorization. |
@@ -750,3 +772,4 @@ stdio/Streamable HTTP servers are composition evidence only.
750
772
  | `server-not-connected` | 409 | MCP server '*name*' is not connected for this workspace. |
751
773
  | `completion-parameters-invalid` | 400 | MCP completion requires 'ref' and 'argument' objects. |
752
774
  | `server-unavailable` | 502 | Configured MCP server '*name*' is unavailable. |
775
+ | `server-redirected` | 502 | MCP endpoint *url* redirected to *location*; plurnk follows no redirect, so the server's url must be the endpoint itself ({§mcp-redirect-refused}). |
@@ -3,8 +3,8 @@ import type { ChannelDecl, Effect, ExecArgs, ExecResult, RuntimeAvailability, Ru
3
3
  import ServerConnection, { type ServerCatalog } from "./client.ts";
4
4
  import type { ContentBlock } from "@modelcontextprotocol/client";
5
5
  import type { ToolPolicy } from "./config.ts";
6
- export declare const serverSummary: (name: string, catalog: ServerCatalog | undefined, override: string | undefined) => string;
7
- export declare const runtimeServerSummary: (name: string, catalog: ServerCatalog | undefined, override: string | undefined) => RuntimeSummaryDecl;
6
+ export declare const serverSummary: (name: string, catalog: ServerCatalog | undefined) => string;
7
+ export declare const runtimeServerSummary: (name: string, catalog: ServerCatalog | undefined) => RuntimeSummaryDecl;
8
8
  export type ToolResultShape = {
9
9
  readonly content?: readonly ContentBlock[];
10
10
  readonly structuredContent?: unknown;
@@ -20,7 +20,7 @@ export default class McpExecutor extends BaseExecutor {
20
20
  constructor(metadata: {
21
21
  runtime: string;
22
22
  glyph: string;
23
- }, connection: ServerConnection, retainWorkspace: () => () => void, policy?: Partial<ToolPolicy>, toolSummaries?: ReadonlyMap<string, string>);
23
+ }, connection: ServerConnection, retainWorkspace: () => () => void, policy?: Partial<ToolPolicy>);
24
24
  get channels(): Readonly<Record<string, ChannelDecl>>;
25
25
  effect(target: string | null): Effect;
26
26
  get publishedChannel(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"McpExecutor.d.ts","sourceRoot":"","sources":["../src/McpExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EAIf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,UAAU,EAEV,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,gBAAgB,EAAE,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,8BAA8B,CAAC;AAGjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAiB9C,eAAO,MAAM,aAAa,SAChB,MAAM,WACH,aAAa,GAAG,SAAS,YACxB,MAAM,GAAG,SAAS,KAC7B,MAOF,CAAC;AAGF,eAAO,MAAM,oBAAoB,SACvB,MAAM,WACH,aAAa,GAAG,SAAS,YACxB,MAAM,GAAG,SAAS,KAC7B,kBAKF,CAAC;AAMF,MAAM,MAAM,eAAe,GAAG;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,cAAc,WAAkB,eAAe,WAAW,MAAM,UAAU,QAAQ,CAAC,OAAO,CAAC,KAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqBvJ,CAAC;AAwBF,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,kBAAkB,eAAe,OAAO,iBAAiB,MAAM,KAAG,WAenH,CAAC;AAuBH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY;;IAoBjD,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,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAOpD;IAEQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK7C;IAED,IAAa,gBAAgB,IAAI,MAAM,CAEtC;IAgCD,YAAY,IAAI,mBAAmB,CAKlC;IAED,IAAI,OAAO,IAAI,aAAa,CAK3B;IAEc,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CASvE;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,EACR,KAAK,GACR,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAsHhC;CACJ"}
1
+ {"version":3,"file":"McpExecutor.d.ts","sourceRoot":"","sources":["../src/McpExecutor.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EAIf,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EACR,WAAW,EACX,MAAM,EACN,QAAQ,EACR,UAAU,EAEV,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,gBAAgB,EAAE,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,8BAA8B,CAAC;AAGjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAY9C,eAAO,MAAM,aAAa,SAChB,MAAM,WACH,aAAa,GAAG,SAAS,KACnC,MAOF,CAAC;AAGF,eAAO,MAAM,oBAAoB,SACvB,MAAM,WACH,aAAa,GAAG,SAAS,KACnC,kBAKF,CAAC;AAMF,MAAM,MAAM,eAAe,GAAG;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,cAAc,WAAkB,eAAe,WAAW,MAAM,UAAU,QAAQ,CAAC,OAAO,CAAC,KAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqBvJ,CAAC;AAwBF,eAAO,MAAM,WAAW,SAAU,MAAM,WAAW,kBAAkB,eAAe,OAAO,iBAAiB,MAAM,KAAG,WAenH,CAAC;AAuBH,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY;;IAoBjD,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,EAMnC;IAED,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAOpD;IAEQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAK7C;IAED,IAAa,gBAAgB,IAAI,MAAM,CAEtC;IAyBD,YAAY,IAAI,mBAAmB,CAKlC;IAED,IAAI,OAAO,IAAI,aAAa,CAK3B;IAEc,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CASvE;IAEK,gBAAgB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsBzE;IAEK,GAAG,CAAC,EACN,OAAO,EACP,IAAI,EACJ,MAAM,EACN,MAAM,EACN,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,KAAK,GACR,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CA0HhC;CACJ"}