@questpie/mcp 3.16.0 → 3.18.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
@@ -10,6 +10,7 @@ Model Context Protocol integration for QUESTPIE apps. The package exposes select
10
10
  - **Route tools** - opt annotated JSON routes into MCP with path-param support.
11
11
  - **Schema resources** - let agents inspect allowed collections, globals, routes, and fields.
12
12
  - **Request context preservation** - HTTP tools run under the connecting request/session.
13
+ - **Workload boundary** - remote workloads use a consumer-supplied, fail-closed authorizer instead of OAuth, cookies, requester identity, or stdio system authority.
13
14
 
14
15
  ## Installation
15
16
 
@@ -34,12 +35,48 @@ import { mcpConfig } from "@questpie/mcp";
34
35
  export default mcpConfig({
35
36
  crud: {
36
37
  collections: {
37
- posts: { expose: true, read: true, write: true },
38
+ posts: {
39
+ operations: {
40
+ list: true,
41
+ get: true,
42
+ create: true,
43
+ },
44
+ },
38
45
  },
39
46
  },
47
+ routes: {
48
+ routes: {
49
+ "reports/generate": { operations: { execute: true } },
50
+ },
51
+ },
52
+ resources: {
53
+ collections: { posts: true },
54
+ routes: { "reports/generate": true },
55
+ },
56
+ });
57
+ ```
58
+
59
+ Omission exposes nothing. Entity names, operations, annotated routes, and
60
+ schema resources must each be present in this catalog. Unknown names are
61
+ ignored. The resolved catalog also supplies the OAuth scope catalog, so OAuth
62
+ never advertises an app entity or operation that MCP does not release.
63
+
64
+ ## Stdio Authority
65
+
66
+ Stdio has no ambient system authority. Bind it to an exact user-mode `ctx`, or
67
+ explicitly opt a local maintenance process into the system bypass:
68
+
69
+ ```ts
70
+ await startStdioServer(app, {
71
+ config: {
72
+ stdio: { trustedMaintenance: true },
73
+ },
40
74
  });
41
75
  ```
42
76
 
77
+ Do not enable `trustedMaintenance` for remote or requester-controlled
78
+ processes. It intentionally bypasses ordinary user RBAC and OAuth scope checks.
79
+
43
80
  ## Custom Tools
44
81
 
45
82
  ```ts
@@ -48,6 +85,8 @@ import { mcpTool } from "@questpie/mcp";
48
85
  import { z } from "zod";
49
86
 
50
87
  export default mcpTool("publish-summary", {
88
+ access: ({ session }) => !!session,
89
+ scopes: false,
51
90
  inputSchema: z.object({ postId: z.string() }),
52
91
  }).handler(async ({ input, ctx }) => {
53
92
  const post = await ctx.collections.posts.findById(input.postId);
@@ -55,14 +94,119 @@ export default mcpTool("publish-summary", {
55
94
  });
56
95
  ```
57
96
 
97
+ ## Remote Workloads
98
+
99
+ Remote workload execution uses a separate public factory.
100
+ `createWorkloadMcpServer` accepts only an opaque envelope, a consumer-supplied
101
+ authorizer, a required consumer-supplied context binder, an optional audit sink,
102
+ and an optional opaque execution handoff. The binder turns the opaque authorized
103
+ context into the exact QUESTPIE request context used for discovery access checks,
104
+ call-time access checks, and handler execution. The bound context must use
105
+ `accessMode: "user"`; remote workloads cannot acquire the system bypass. The
106
+ factory deliberately has no request, cookie, requester context, OAuth, or
107
+ access-mode option.
108
+
109
+ Set `concurrencyKey` to a stable, non-secret consumer or tenant identifier when
110
+ multiple workload servers or ports should share one per-principal concurrency
111
+ bucket. Different keys are isolated; omitting the key creates an instance-local
112
+ bucket.
113
+
114
+ Every workload-visible tool opts into named capability facts:
115
+
116
+ ```ts
117
+ import { mcpTool } from "@questpie/mcp";
118
+ import { z } from "zod";
119
+
120
+ export default mcpTool("messages.reply", {
121
+ access: true,
122
+ scopes: false,
123
+ inputSchema: z.object({ body: z.string() }),
124
+ workload: {
125
+ capabilities: ["messages.write"],
126
+ handoff: "messages.commit",
127
+ },
128
+ }).handler(async ({ input }) => ({
129
+ content: [{ type: "text", text: input.body }],
130
+ }));
131
+ ```
132
+
133
+ MCP passes only the opaque envelope, phase, and bounded tool facts (`kind`,
134
+ `name`, `operation`, `intent`, `transport`, `capabilities`, and optional
135
+ `handoff`) to the authorizer. Discovery and every call are authorized and bound
136
+ independently. MCP does not interpret the returned opaque context or attribution.
137
+ Calls with a `handoff` capability execute through the consumer's handoff; MCP
138
+ does not add a durable effect or idempotency store. Missing or malformed
139
+ authorization or context binding fails closed, and tools without an explicit
140
+ workload requirement stay hidden.
141
+
142
+ When a trusted in-process subsystem needs only workload-enabled custom tools,
143
+ use the programmatic port instead of creating an MCP client/server loop:
144
+
145
+ ```ts
146
+ import { createWorkloadMcpToolPort, mcpPublicErrorCode } from "@questpie/mcp";
147
+
148
+ const tools = createWorkloadMcpToolPort(app, workloadOptions);
149
+ const released = await tools.listCustomTools({ signal });
150
+ const result = await tools.callCustomTool({
151
+ name: "messages.reply",
152
+ input: { body: "Hello" },
153
+ signal,
154
+ requestId: runId,
155
+ });
156
+ ```
157
+
158
+ The port excludes generated CRUD tools, routes, and resources by construction.
159
+ It uses the same immutable release catalog, per-call workload authorization,
160
+ context binding, MCP access rule, Zod input/output validation, execution
161
+ budgets, cancellation, and public error contract as transport calls. A failed
162
+ call returns `isError: true` with a stable code and correlation ID under
163
+ `_meta["questpie/error"]`. `mcpPublicErrorCode(error)` safely reads the code
164
+ from errors thrown by list/cancellation paths.
165
+
166
+ ## Execution Budgets And Errors
167
+
168
+ All tool calls and schema resource reads share bounded execution state for an
169
+ app, including across HTTP requests and independently created workload ports.
170
+ Defaults can be narrowed with `execution`:
171
+
172
+ ```ts
173
+ export default mcpConfig({
174
+ execution: {
175
+ maxInputBytes: 64 * 1024,
176
+ maxInputDepth: 16,
177
+ maxOutputDepth: 64,
178
+ maxValueNodes: 10_000,
179
+ maxOutputBytes: 1024 * 1024,
180
+ timeoutMs: 30_000,
181
+ maxConcurrency: 64,
182
+ maxConcurrencyPerPrincipal: 8,
183
+ maxTools: 512,
184
+ maxResources: 512,
185
+ onDiagnostic(event) {
186
+ // Trusted, bounded server-side telemetry only.
187
+ },
188
+ },
189
+ });
190
+ ```
191
+
192
+ Saturation rejects immediately; it does not create an unbounded queue. Timed
193
+ out or cancelled work keeps its concurrency permit until the underlying
194
+ operation actually settles, so code that ignores `signal` cannot bypass the
195
+ limit. Custom tool handlers and workload handoffs receive `signal`,
196
+ `requestId`, and `correlationId`. Public errors use stable codes:
197
+ `access_denied`, `invalid_input`, `input_too_large`, `output_too_large`,
198
+ `timeout`, `cancelled`, `busy`, and `internal`. Protocol responses and
199
+ diagnostics never include raw handler/database error messages, request input,
200
+ output, credentials, authorization envelopes, or workload attribution.
201
+
58
202
  ## Exports
59
203
 
60
- | Entry Point | Purpose |
61
- | --------------------------- | ----------------------------------- |
204
+ | Entry Point | Purpose |
205
+ | --------------------------- | ------------------------------------ |
62
206
  | `@questpie/mcp` | Config, plugin, server, tool factory |
63
- | `@questpie/mcp/modules/mcp` | `mcpModule` for `modules.ts` |
64
- | `@questpie/mcp/plugin` | Codegen plugin export |
65
- | `@questpie/mcp/stdio` | Stdio server entry |
207
+ | `@questpie/mcp/modules/mcp` | `mcpModule` for `modules.ts` |
208
+ | `@questpie/mcp/plugin` | Codegen plugin export |
209
+ | `@questpie/mcp/stdio` | Stdio server entry |
66
210
 
67
211
  ## License
68
212