@astral-mcp/projection 0.7.0 → 0.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/README.md CHANGED
@@ -1,22 +1,57 @@
1
1
  # @astral-mcp/projection
2
2
 
3
- Turn any MCP server into a **code-mode** server with **human-approved side
4
- effects**, built on MCP MRTR (Multi Round-Trip Requests, revision
5
- 2026-07-28) and XState v5.
3
+ Wrap any MCP server in **code mode** (LLM-authored XState workflows) and a
4
+ **tool catalog** (search, describe, call), with **human-approved side
5
+ effects** over MCP MRTR (Multi Round-Trip Requests).
6
+
7
+ ```bash
8
+ npm install @astral-mcp/projection @modelcontextprotocol/server@2.0.0
9
+ ```
10
+
11
+ Add `@modelcontextprotocol/client@2.0.0` when wrapping a connected `Client`.
12
+ Both SDK packages are pinned at `2.0.0` in Projection's own dependencies, so
13
+ install the same version to match the pin.
6
14
 
7
15
  Projection wraps an existing MCP server and exposes two projections of it —
8
- five tools, never the wrapped server's own:
16
+ five tools in all, never a pass-through to the wrapped server's own:
9
17
 
10
18
  - **Code mode** — `execute_machine` runs an LLM-authored XState v5 state
11
- machine in a QuickJS WebAssembly sandbox (read-only calls run inline; any
12
- write suspends the machine into a sealed, encrypted blob and asks the human
13
- to approve that exact call via MRTR elicitation); `discover_apis` returns
19
+ machine in a QuickJS WebAssembly sandbox (read-only calls, or, under
20
+ `destructive-only`, a non-destructive write, run inline; any other write
21
+ suspends the machine into a sealed, encrypted blob and asks the human to
22
+ approve that exact call via MRTR elicitation); `discover_apis` returns
14
23
  the generated TypeScript definitions and the authoring contract.
15
24
  - **Catalog** — `search_tools` lists the wrapped tools with their approval
16
- posture, `describe_tool` returns one tool's schemas and types, and
17
- `call_tool` runs one tool by name under the same approval gate. The
18
- instructions route the model: one step → `call_tool`; a workflow →
19
- `execute_machine`. Either projection can be disabled.
25
+ posture (a query's top three hits with their arguments), `describe_tool` returns one tool's schemas (and its generated
26
+ types on request, `types: true`), and `call_tool` runs one tool by name
27
+ under the same approval gate. The instructions route the model: one step
28
+ → `call_tool`; a workflow → `execute_machine`. Either projection can be
29
+ disabled (`projections: { codeMode: false }` or `{ catalog: false }`; not
30
+ both).
31
+ - **Direct listing** (default `projections.direct: 'auto'`) — when the
32
+ wrapped tools' estimated definitions fit `directBudgetTokens` (8 000),
33
+ each is also listed by name (`<box>__<tool>` on a multi-server facade) as
34
+ a mediated alias of `call_tool` under the same gate, and `search_tools`
35
+ is dropped. `projections: { direct: false }` keeps the five tools.
36
+
37
+ Every tool surface uses one posture vocabulary: a tool `runs immediately`
38
+ (read-only, `auto` by policy, or, under `destructive-only`, a non-destructive
39
+ write), `asks for approval`, or is `blocked by policy`.
40
+
41
+ `approvals` sets the approvals mode: `'every-write'` (default) asks before
42
+ every tool that is not read-only; `'destructive-only'` also runs, without
43
+ approval, a tool whose annotations declare `destructiveHint: false` exactly
44
+ (a missing or any other hint still asks). A per-tool `policy` override wins
45
+ over the mode. The mode extends the trust Projection places in
46
+ `readOnlyHint` to `destructiveHint: false`, and a hostile server can
47
+ mislabel a damaging tool — enable it only for servers you trust.
48
+
49
+ `upstreamElicitation: 'forward'` lets a trusted wrapped server ask the user
50
+ questions mid-call (a form, or a link to open): each is relayed over MRTR as
51
+ `Question from <box> (not an approval)` with the server's words quoted and
52
+ its form allowlisted, after the call itself passed the approval gate. The
53
+ default, `'refuse'`, fails such a call with a result telling the model the
54
+ tool needs the user's input. Per box on `wrapMcpServers`.
20
55
 
21
56
  All suspension state rides the MRTR `requestState` payload, so Projection is
22
57
  fully stateless: any server instance can resume any workflow.
@@ -30,6 +65,8 @@ import { wrapMcpServer } from '@astral-mcp/projection';
30
65
  const facade = await wrapMcpServer(myMcpServer, {
31
66
  blob: { key: myThirtyTwoByteKey }, // shared across instances
32
67
  policy: { query: 'ask' }, // override annotations per tool
68
+ // approvals: 'destructive-only', // skip approval for destructiveHint: false writes (trusted servers only)
69
+ // upstreamElicitation: 'forward', // pass the server's own questions on to the user (trusted servers only)
33
70
  redact: (tool, args) => ({ ...args, token: '[redacted]' }),
34
71
  });
35
72
 
@@ -42,17 +79,51 @@ still run read-only machines and calls, and get a structured error the
42
79
  moment a call needs approval. You can also wrap a connected SDK `Client` to put
43
80
  Projection in front of a server you don't own.
44
81
 
82
+ ### Several servers (preview)
83
+
84
+ `wrapMcpServers({ fs: filesServer, github: { source: githubClient } }, options)`
85
+ fronts several servers as tool boxes: tools are addressed as
86
+ `<alias>.<name>` (`fs.write_file`), policy keys are qualified names, each
87
+ box has its own `search_<alias>` tool (whose first page shows the server's
88
+ guidance), and `search_tools` searches every box (`catalog.globalSearch:
89
+ false` turns it off). Code mode works across boxes: the generated types have
90
+ one `tools.<alias>` property per box, and machines call
91
+ `toolCall('fs.read_file', args)`. Optional sources are not available yet.
92
+ See `docs/server-authors.md`.
93
+
94
+ ### Remote servers by URL (preview)
95
+
96
+ Any source can be `{ url, oauth?: { clientId, scopes } }`: Projection builds
97
+ the Streamable HTTP connection itself, uses and refreshes tokens from a
98
+ token store (the OS credential store through the optional
99
+ `@napi-rs/keyring` dependency by default, or `WrapOptions.tokenStore`, a
100
+ plain string key-value store), and `facade.signOut()` clears them. Never put
101
+ a token in an MCP configuration file. Just-in-time sign-in (asking the user
102
+ on first use) is not supported in this version: a public server works, and
103
+ so does one whose token is already in your `tokenStore`; otherwise the wrap
104
+ fails with an `auth` error. See "Upstream authentication" in
105
+ `docs/server-authors.md`.
106
+
45
107
  ## Security model
46
108
 
47
- | Invariant | How it holds |
48
- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
49
- | The tools bridge is the only way out of the sandbox | No `fetch`, no filesystem, no host globals; only `xstate` and `@astral-mcp/tools` resolve as imports |
50
- | No gated call runs without a matching approval | The approved name and arguments are sealed in the blob and executed from there |
51
- | Clients are untrusted couriers | Blobs are AES-256-GCM encrypted under a per-blob HKDF-derived key and authenticated |
52
- | A snapshot cannot resume under different code | The blob binds a SHA-256 hash of the original `code` — for `call_tool`, of the canonical `{ kind, name, args }` request |
53
- | A blob answers only the tool that issued it | Blobs carry their kind; a `call_tool` blob cannot resume `execute_machine` or vice versa |
54
- | A wrapped server's annotations are hints, not authority | Unannotated tools default to asking; developer overrides always win |
55
- | Approval prompts cannot be forged | Prompt text comes from the catalog and redacted arguments, rendered inert |
109
+ | Invariant | How it holds |
110
+ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
111
+ | The tools bridge is the only way out of the sandbox | No `fetch`, no filesystem, no host globals; only `xstate` and `@astral-mcp/tools` are meant for authored code; nothing that performs I/O resolves |
112
+ | No gated call runs without a matching approval | The approved name and arguments are sealed in the blob and executed from there |
113
+ | Clients carry state but can neither read nor alter it | Blobs are AES-256-GCM encrypted under a per-blob HKDF-derived key and authenticated |
114
+ | A snapshot cannot resume under different code | The blob binds a SHA-256 hash of the original `code` — for `call_tool`, of the canonical `{ kind, name, args }` request |
115
+ | A blob answers only the tool that issued it | Blobs carry their kind; a `call_tool` blob cannot resume `execute_machine` or vice versa |
116
+ | A wrapped server's annotations are hints, not authority | Unannotated tools default to asking; developer overrides always win |
117
+ | Approval prompts cannot be forged | Prompt text comes from the catalog and redacted arguments, rendered inert |
118
+
119
+ Known limitation: unless you supply a shared `replayCache` (R38), an approved
120
+ retry can be replayed until its blob expires (by default 5 min for
121
+ `call_tool`, 15 for machines; `blob.callTtlMs` and `blob.ttlMs` override).
122
+ A forwarded server question's blob keeps the expiry of the approval it
123
+ follows. With `upstreamElicitation: 'forward'` on, a `replayCache` is
124
+ recommended.
125
+ See
126
+ [Lifecycle](https://github.com/astralMCP/projection/blob/main/docs/server-authors.md#lifecycle-and-operations).
56
127
 
57
128
  ## Documentation
58
129
 
@@ -61,7 +132,7 @@ Projection in front of a server you don't own.
61
132
  - [Product requirements](https://github.com/astralMCP/projection/blob/main/docs/PRD.md)
62
133
  and [engineering decisions](https://github.com/astralMCP/projection/blob/main/docs/DECISIONS.md)
63
134
  - [Repository](https://github.com/astralMCP/projection) — scenario test
64
- harness, self-heal benchmark, and an interactive demo TUI live alongside
65
- the library
135
+ harness, self-heal benchmark, an interactive demo TUI, and an MCP Apps
136
+ demo host live alongside the library
66
137
 
67
138
  Requires Node ≥ 22. MIT licensed.