@questpie/mcp 3.28.11 → 3.29.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 +42 -6
- package/dist/{create-server-5Eo4QP3q.mjs → create-server-DzTo-3Wy.mjs} +215 -55
- package/dist/{index-C3gm6ar5.d.mts → index-CLEEqMqK.d.mts} +2 -0
- package/dist/index.d.mts +43 -3
- package/dist/index.mjs +5 -5
- package/dist/{mcp-i0Kqu_bB.mjs → mcp-C55EQ-yd.mjs} +3 -2
- package/dist/modules/mcp.d.mts +1 -1
- package/dist/modules/mcp.mjs +1 -1
- package/dist/{plugin-BZQe1pji.mjs → plugin-BgwWI9S8.mjs} +20 -9
- package/dist/plugin.mjs +1 -1
- package/dist/{stdio-37bSPka9.mjs → stdio-Bq7nzR79.mjs} +1 -1
- package/dist/{stdio-C3663EKG.d.mts → stdio-C2V13-vP.d.mts} +48 -2
- package/dist/stdio.d.mts +1 -1
- package/dist/stdio.mjs +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -94,6 +94,42 @@ export default mcpTool("publish-summary", {
|
|
|
94
94
|
});
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
## Prompts
|
|
98
|
+
|
|
99
|
+
A prompt provider answers `prompts/list` and `prompts/get` per request, so the
|
|
100
|
+
prompts a caller sees can depend on who they are. Clients such as Claude Code
|
|
101
|
+
show them as slash commands. Prompts do not add to the `tools/list` catalogue.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// mcp-prompts/playbooks.ts
|
|
105
|
+
import { mcpPrompts } from "@questpie/mcp";
|
|
106
|
+
|
|
107
|
+
export default mcpPrompts("playbooks", {
|
|
108
|
+
access: ({ session }) => !!session,
|
|
109
|
+
scopes: "playbooks:read",
|
|
110
|
+
list: async ({ ctx }) =>
|
|
111
|
+
(await ctx.collections.playbooks.find({})).docs.map((doc) => ({
|
|
112
|
+
name: doc.slug,
|
|
113
|
+
description: doc.summary,
|
|
114
|
+
})),
|
|
115
|
+
get: async ({ ctx, name }) => {
|
|
116
|
+
const doc = await ctx.collections.playbooks.findOne({
|
|
117
|
+
where: { slug: name },
|
|
118
|
+
});
|
|
119
|
+
if (!doc) return null;
|
|
120
|
+
return {
|
|
121
|
+
messages: [{ role: "user", content: { type: "text", text: doc.body } }],
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`access` and `scopes` are required and gate the whole provider, exactly like a
|
|
128
|
+
custom tool. `get` returns `null` for a name the caller cannot use; the client
|
|
129
|
+
receives the same not-found error as for an unknown name. The server advertises
|
|
130
|
+
the `prompts` capability only when at least one provider is released. Prompts
|
|
131
|
+
are never served to remote workloads.
|
|
132
|
+
|
|
97
133
|
## Remote Workloads
|
|
98
134
|
|
|
99
135
|
Remote workload execution uses a separate public factory.
|
|
@@ -201,12 +237,12 @@ output, credentials, authorization envelopes, or workload attribution.
|
|
|
201
237
|
|
|
202
238
|
## Exports
|
|
203
239
|
|
|
204
|
-
| Entry Point | Purpose
|
|
205
|
-
| --------------------------- |
|
|
206
|
-
| `@questpie/mcp` | Config, plugin, server, tool
|
|
207
|
-
| `@questpie/mcp/modules/mcp` | `mcpModule` for `modules.ts`
|
|
208
|
-
| `@questpie/mcp/plugin` | Codegen plugin export
|
|
209
|
-
| `@questpie/mcp/stdio` | Stdio server entry
|
|
240
|
+
| Entry Point | Purpose |
|
|
241
|
+
| --------------------------- | ------------------------------------------------- |
|
|
242
|
+
| `@questpie/mcp` | Config, plugin, server, tool and prompt factories |
|
|
243
|
+
| `@questpie/mcp/modules/mcp` | `mcpModule` for `modules.ts` |
|
|
244
|
+
| `@questpie/mcp/plugin` | Codegen plugin export |
|
|
245
|
+
| `@questpie/mcp/stdio` | Stdio server entry |
|
|
210
246
|
|
|
211
247
|
## License
|
|
212
248
|
|
|
@@ -1,63 +1,11 @@
|
|
|
1
1
|
import { createAdapterContext, createContextFactory, defaultOperationScope, evaluateRouteAccess, executeJsonRoute, extractAppServices, introspectCollection, introspectGlobal, introspectRoutes, isJsonRoute, umbrellaScopeFor } from "questpie";
|
|
2
|
+
import { ErrorCode, GetPromptRequestSchema, GetPromptResultSchema, ListPromptsRequestSchema, ListToolsRequestSchema, McpError, PromptSchema, ToolSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { types } from "node:util";
|
|
4
5
|
import { snapshotBoundedBrokerValue } from "questpie/executor";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
import { ListToolsRequestSchema, ToolSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
|
|
9
|
-
//#region src/server/mcp-tool.ts
|
|
10
|
-
var McpToolBuilder = class {
|
|
11
|
-
constructor(name, config) {
|
|
12
|
-
this.name = name;
|
|
13
|
-
this.config = config;
|
|
14
|
-
}
|
|
15
|
-
handler(handler) {
|
|
16
|
-
return Object.freeze({
|
|
17
|
-
__brand: "mcpTool",
|
|
18
|
-
name: this.name,
|
|
19
|
-
config: this.config,
|
|
20
|
-
handler
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
/**
|
|
25
|
-
* Define a custom MCP tool, contributed by a module the same way collections and
|
|
26
|
-
* routes are (via `ModuleDefinition.mcpTools`). `mcpTool(name, config).handler(fn)`
|
|
27
|
-
* returns a frozen, branded {@link McpToolDefinition}; the CRUD/route tools the
|
|
28
|
-
* framework auto-generates are the same shape.
|
|
29
|
-
*
|
|
30
|
-
* `config` mirrors the MCP tool contract: `inputSchema`/`outputSchema` (Zod),
|
|
31
|
-
* `title`/`description`/`annotations` for discovery, plus the two QUESTPIE
|
|
32
|
-
* authorization hooks that compose exactly like the built-in tools:
|
|
33
|
-
* - `access` — required explicit opt-in and an {@link McpAccessRule} run against the caller's transport /
|
|
34
|
-
* accessMode / session (and, for an OAuth caller, its scopes). Same RBAC-style
|
|
35
|
-
* gate the CRUD tools use.
|
|
36
|
-
* - `scopes` — the OAuth scopes an `oauth` caller must hold ({@link McpToolConfig.scopes}).
|
|
37
|
-
* Custom tools have no default scope mapping (unlike CRUD, there is no
|
|
38
|
-
* resource/operation to derive one from), so it is required; use `false` for
|
|
39
|
-
* an explicit no-OAuth-scope policy. The shipped scope gate ({@link scopeGateAllows}) enforces this at both
|
|
40
|
-
* `tools/list` (hidden) and `tools/call` (denied); `system`/`user` callers
|
|
41
|
-
* carry no scopes and skip it, so the effective bound is always `scopes ∩ RBAC`.
|
|
42
|
-
*
|
|
43
|
-
* @example
|
|
44
|
-
* ```ts
|
|
45
|
-
* export const ping = mcpTool("ops.ping", {
|
|
46
|
-
* access: ({ session }) => !!session,
|
|
47
|
-
* description: "Health probe",
|
|
48
|
-
* inputSchema: z.object({}),
|
|
49
|
-
* scopes: "routes:ops/ping:invoke", // only OAuth callers holding this scope
|
|
50
|
-
* }).handler(async () => ({ content: [{ type: "text", text: "pong" }] }));
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
function mcpTool(name, config) {
|
|
54
|
-
return new McpToolBuilder(name, config);
|
|
55
|
-
}
|
|
56
|
-
function isMcpTool(value) {
|
|
57
|
-
return !!value && typeof value === "object" && value["__brand"] === "mcpTool" && typeof value.handler === "function";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
//#endregion
|
|
61
9
|
//#region src/server/principal.ts
|
|
62
10
|
const hasOwnData = (descriptors, key) => {
|
|
63
11
|
const descriptor = descriptors[key];
|
|
@@ -363,6 +311,206 @@ async function evaluateMcpRule(rule, options) {
|
|
|
363
311
|
});
|
|
364
312
|
}
|
|
365
313
|
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/server/mcp-prompts.ts
|
|
316
|
+
/**
|
|
317
|
+
* Define an MCP prompt provider, contributed by a module the same way custom
|
|
318
|
+
* tools are (via `ModuleDefinition.mcpPrompts`, file convention
|
|
319
|
+
* `mcp-prompts/*.ts`). A provider answers `prompts/list` and `prompts/get`
|
|
320
|
+
* per request, so the prompts a caller sees can depend on who the caller is.
|
|
321
|
+
*
|
|
322
|
+
* `access` and `scopes` compose exactly like a custom tool's: a caller the
|
|
323
|
+
* provider denies sees none of its prompts, and `prompts/get` of any of them
|
|
324
|
+
* answers not-found. `list` and `get` then run with that caller's context and
|
|
325
|
+
* must apply their own row-level rules; returning `null` from `get` for a
|
|
326
|
+
* prompt the caller cannot read keeps it indistinguishable from an unknown
|
|
327
|
+
* name. Prompts are served on the `http` and `stdio` transports only, never to
|
|
328
|
+
* remote workloads.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* export default mcpPrompts("playbooks", {
|
|
333
|
+
* access: ({ session }) => !!session,
|
|
334
|
+
* scopes: "playbooks:read",
|
|
335
|
+
* list: async ({ ctx }) =>
|
|
336
|
+
* (await ctx.collections.playbooks.find({})).docs.map((doc) => ({
|
|
337
|
+
* name: doc.slug,
|
|
338
|
+
* description: doc.summary,
|
|
339
|
+
* })),
|
|
340
|
+
* get: async ({ ctx, name }) => {
|
|
341
|
+
* const doc = await ctx.collections.playbooks.findOne({ where: { slug: name } });
|
|
342
|
+
* if (!doc) return null;
|
|
343
|
+
* return { messages: [{ role: "user", content: { type: "text", text: doc.body } }] };
|
|
344
|
+
* },
|
|
345
|
+
* });
|
|
346
|
+
* ```
|
|
347
|
+
*/
|
|
348
|
+
function mcpPrompts(name, config) {
|
|
349
|
+
return Object.freeze({
|
|
350
|
+
__brand: "mcpPrompts",
|
|
351
|
+
name,
|
|
352
|
+
config: Object.freeze({
|
|
353
|
+
...config,
|
|
354
|
+
scopes: Array.isArray(config.scopes) ? [...config.scopes] : config.scopes
|
|
355
|
+
})
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function isMcpPromptProvider(value) {
|
|
359
|
+
if (!value || typeof value !== "object") return false;
|
|
360
|
+
const candidate = value;
|
|
361
|
+
return candidate.__brand === "mcpPrompts" && typeof candidate.config?.list === "function" && typeof candidate.config?.get === "function";
|
|
362
|
+
}
|
|
363
|
+
async function promptProviderAllows(scope, provider, ctx) {
|
|
364
|
+
return await evaluateMcpRule(provider.config.access, {
|
|
365
|
+
transport: scope.transport,
|
|
366
|
+
accessMode: scope.accessMode,
|
|
367
|
+
ctx
|
|
368
|
+
}) && scopeGateAllows(scopesFromContext(ctx), normalizeRequiredScopes(provider.config.scopes));
|
|
369
|
+
}
|
|
370
|
+
const MAX_INVALID_PARAMS_MESSAGE_CHARS = 512;
|
|
371
|
+
/**
|
|
372
|
+
* A provider's own `McpError(ErrorCode.InvalidParams, …)` is the one error
|
|
373
|
+
* that reaches the client as itself: it states what was wrong with the
|
|
374
|
+
* caller's arguments, which the provider authored for the caller. Every other
|
|
375
|
+
* throw stays the boundary's opaque `internal`.
|
|
376
|
+
*/
|
|
377
|
+
function providerInvalidParams(error) {
|
|
378
|
+
if (!(error instanceof McpError) || error.code !== ErrorCode.InvalidParams) return;
|
|
379
|
+
const message = error.message.replace(/^MCP error -?\d+: /, "").slice(0, MAX_INVALID_PARAMS_MESSAGE_CHARS);
|
|
380
|
+
return new McpError(ErrorCode.InvalidParams, message);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Install `prompts/list` and `prompts/get` over the released providers. The
|
|
384
|
+
* caller must declare the `prompts` capability on the server beforehand.
|
|
385
|
+
*/
|
|
386
|
+
function registerPromptProviders(server, scope, catalog) {
|
|
387
|
+
if (scope.workload || catalog.promptProviders.size === 0) return;
|
|
388
|
+
const providers = [...catalog.promptProviders.values()].map((entry) => entry.provider);
|
|
389
|
+
server.server.setRequestHandler(ListPromptsRequestSchema, (request, extra) => {
|
|
390
|
+
if (request.params?.cursor) throw new McpError(ErrorCode.InvalidParams, "Invalid cursor");
|
|
391
|
+
return scope.execution.execute({
|
|
392
|
+
operation: "prompts/list",
|
|
393
|
+
transport: scope.transport,
|
|
394
|
+
accessMode: scope.accessMode,
|
|
395
|
+
input: request.params ?? {},
|
|
396
|
+
extra,
|
|
397
|
+
authorize: () => scope.getContext(),
|
|
398
|
+
invoke: async (control) => {
|
|
399
|
+
const handlerArgs = {
|
|
400
|
+
...control,
|
|
401
|
+
transport: scope.transport,
|
|
402
|
+
accessMode: scope.accessMode,
|
|
403
|
+
request: scope.request
|
|
404
|
+
};
|
|
405
|
+
const prompts = [];
|
|
406
|
+
const names = /* @__PURE__ */ new Set();
|
|
407
|
+
for (const provider of providers) {
|
|
408
|
+
if (!await promptProviderAllows(scope, provider, control.ctx)) continue;
|
|
409
|
+
for (const candidate of await provider.config.list(handlerArgs)) {
|
|
410
|
+
const parsed = PromptSchema.safeParse(candidate);
|
|
411
|
+
if (!parsed.success) throw new Error("MCP prompt provider listed an invalid prompt");
|
|
412
|
+
if (names.has(parsed.data.name)) continue;
|
|
413
|
+
names.add(parsed.data.name);
|
|
414
|
+
prompts.push(parsed.data);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return { prompts };
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
server.server.setRequestHandler(GetPromptRequestSchema, async (request, extra) => {
|
|
422
|
+
let invalidParams;
|
|
423
|
+
const rendered = await scope.execution.execute({
|
|
424
|
+
operation: "prompts/get",
|
|
425
|
+
transport: scope.transport,
|
|
426
|
+
accessMode: scope.accessMode,
|
|
427
|
+
input: request.params,
|
|
428
|
+
extra,
|
|
429
|
+
authorize: () => scope.getContext(),
|
|
430
|
+
invoke: async (control) => {
|
|
431
|
+
for (const provider of providers) {
|
|
432
|
+
if (!await promptProviderAllows(scope, provider, control.ctx)) continue;
|
|
433
|
+
let result;
|
|
434
|
+
try {
|
|
435
|
+
result = await provider.config.get({
|
|
436
|
+
...control,
|
|
437
|
+
transport: scope.transport,
|
|
438
|
+
accessMode: scope.accessMode,
|
|
439
|
+
request: scope.request,
|
|
440
|
+
name: request.params.name,
|
|
441
|
+
arguments: { ...request.params.arguments }
|
|
442
|
+
});
|
|
443
|
+
} catch (error) {
|
|
444
|
+
invalidParams = providerInvalidParams(error);
|
|
445
|
+
if (!invalidParams) throw error;
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
if (result === null || result === void 0) continue;
|
|
449
|
+
const parsed = GetPromptResultSchema.safeParse(result);
|
|
450
|
+
if (!parsed.success) throw new Error("MCP prompt provider rendered an invalid prompt");
|
|
451
|
+
return parsed.data;
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
if (invalidParams) throw invalidParams;
|
|
457
|
+
if (!rendered) throw new McpError(ErrorCode.InvalidParams, "Prompt not found");
|
|
458
|
+
return rendered;
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region src/server/mcp-tool.ts
|
|
464
|
+
var McpToolBuilder = class {
|
|
465
|
+
constructor(name, config) {
|
|
466
|
+
this.name = name;
|
|
467
|
+
this.config = config;
|
|
468
|
+
}
|
|
469
|
+
handler(handler) {
|
|
470
|
+
return Object.freeze({
|
|
471
|
+
__brand: "mcpTool",
|
|
472
|
+
name: this.name,
|
|
473
|
+
config: this.config,
|
|
474
|
+
handler
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* Define a custom MCP tool, contributed by a module the same way collections and
|
|
480
|
+
* routes are (via `ModuleDefinition.mcpTools`). `mcpTool(name, config).handler(fn)`
|
|
481
|
+
* returns a frozen, branded {@link McpToolDefinition}; the CRUD/route tools the
|
|
482
|
+
* framework auto-generates are the same shape.
|
|
483
|
+
*
|
|
484
|
+
* `config` mirrors the MCP tool contract: `inputSchema`/`outputSchema` (Zod),
|
|
485
|
+
* `title`/`description`/`annotations` for discovery, plus the two QUESTPIE
|
|
486
|
+
* authorization hooks that compose exactly like the built-in tools:
|
|
487
|
+
* - `access` — required explicit opt-in and an {@link McpAccessRule} run against the caller's transport /
|
|
488
|
+
* accessMode / session (and, for an OAuth caller, its scopes). Same RBAC-style
|
|
489
|
+
* gate the CRUD tools use.
|
|
490
|
+
* - `scopes` — the OAuth scopes an `oauth` caller must hold ({@link McpToolConfig.scopes}).
|
|
491
|
+
* Custom tools have no default scope mapping (unlike CRUD, there is no
|
|
492
|
+
* resource/operation to derive one from), so it is required; use `false` for
|
|
493
|
+
* an explicit no-OAuth-scope policy. The shipped scope gate ({@link scopeGateAllows}) enforces this at both
|
|
494
|
+
* `tools/list` (hidden) and `tools/call` (denied); `system`/`user` callers
|
|
495
|
+
* carry no scopes and skip it, so the effective bound is always `scopes ∩ RBAC`.
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```ts
|
|
499
|
+
* export const ping = mcpTool("ops.ping", {
|
|
500
|
+
* access: ({ session }) => !!session,
|
|
501
|
+
* description: "Health probe",
|
|
502
|
+
* inputSchema: z.object({}),
|
|
503
|
+
* scopes: "routes:ops/ping:invoke", // only OAuth callers holding this scope
|
|
504
|
+
* }).handler(async () => ({ content: [{ type: "text", text: "pong" }] }));
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
function mcpTool(name, config) {
|
|
508
|
+
return new McpToolBuilder(name, config);
|
|
509
|
+
}
|
|
510
|
+
function isMcpTool(value) {
|
|
511
|
+
return !!value && typeof value === "object" && value["__brand"] === "mcpTool" && typeof value.handler === "function";
|
|
512
|
+
}
|
|
513
|
+
|
|
366
514
|
//#endregion
|
|
367
515
|
//#region src/server/catalog.ts
|
|
368
516
|
const COLLECTION_OPERATION_KINDS = {
|
|
@@ -547,6 +695,15 @@ function resolveMcpCatalog(app, config) {
|
|
|
547
695
|
customTools.set(name, Object.freeze({ tool }));
|
|
548
696
|
addRequiredScopes(oauthScopes, tool.config.scopes);
|
|
549
697
|
}
|
|
698
|
+
const promptProviders = /* @__PURE__ */ new Map();
|
|
699
|
+
const configuredPromptProviders = app.state?.mcpPrompts ?? {};
|
|
700
|
+
for (const [key, candidate] of Object.entries(configuredPromptProviders)) {
|
|
701
|
+
if (!isMcpPromptProvider(candidate) || candidate.config.access === void 0 || candidate.config.access === false || candidate.config.scopes === void 0) continue;
|
|
702
|
+
const name = candidate.name || key;
|
|
703
|
+
if (promptProviders.has(name)) throw new Error(`Duplicate MCP prompt provider name: ${name}`);
|
|
704
|
+
promptProviders.set(name, Object.freeze({ provider: candidate }));
|
|
705
|
+
addRequiredScopes(oauthScopes, candidate.config.scopes);
|
|
706
|
+
}
|
|
550
707
|
const resourceConfig = ownSection(config, "resources");
|
|
551
708
|
const configuredResourceCollections = ownSection(resourceConfig, "collections");
|
|
552
709
|
const configuredResourceGlobals = ownSection(resourceConfig, "globals");
|
|
@@ -563,6 +720,7 @@ function resolveMcpCatalog(app, config) {
|
|
|
563
720
|
globals: readonlyMap(globals),
|
|
564
721
|
routes: readonlyMap(routes),
|
|
565
722
|
customTools: readonlyMap(customTools),
|
|
723
|
+
promptProviders: readonlyMap(promptProviders),
|
|
566
724
|
resources: Object.freeze({
|
|
567
725
|
collections: readonlySet(resourceCollections),
|
|
568
726
|
globals: readonlySet(resourceGlobals),
|
|
@@ -2553,12 +2711,14 @@ async function createServer(app, options, workload) {
|
|
|
2553
2711
|
version: config.version ?? "0.0.0"
|
|
2554
2712
|
}, { capabilities: {
|
|
2555
2713
|
tools: {},
|
|
2556
|
-
...catalog.resources.collections.size > 0 || catalog.resources.globals.size > 0 || catalog.resources.routes.size > 0 ? { resources: {} } : {}
|
|
2714
|
+
...catalog.resources.collections.size > 0 || catalog.resources.globals.size > 0 || catalog.resources.routes.size > 0 ? { resources: {} } : {},
|
|
2715
|
+
...!workload && catalog.promptProviders.size > 0 ? { prompts: {} } : {}
|
|
2557
2716
|
} });
|
|
2558
2717
|
await registerCrudTools(server, scope, config, catalog);
|
|
2559
2718
|
await registerRouteTools(server, scope, catalog);
|
|
2560
2719
|
registerSchemaResources(server, scope, catalog);
|
|
2561
2720
|
await registerCustomTools(server, scope, catalog);
|
|
2721
|
+
registerPromptProviders(server, scope, catalog);
|
|
2562
2722
|
if (workload) server.server.setRequestHandler(ListToolsRequestSchema, (_request, extra) => listWorkloadTools(workload, release.execution, extra));
|
|
2563
2723
|
else if ([...catalog.collections.values()].every((entry) => entry.operations.length === 0) && [...catalog.globals.values()].every((entry) => entry.operations.length === 0) && catalog.routes.size === 0 && catalog.customTools.size === 0) server.server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [] }));
|
|
2564
2724
|
else installSchemaDietListToolsHandler(server);
|
|
@@ -2585,4 +2745,4 @@ async function createWorkloadMcpServer(app, options) {
|
|
|
2585
2745
|
}
|
|
2586
2746
|
|
|
2587
2747
|
//#endregion
|
|
2588
|
-
export {
|
|
2748
|
+
export { scopeGateAllows as C, requiredScopesForOperation as S, validateMcpPrincipal as T, resolveMcpCatalog as _, customToolWorkloadIdentity as a, defaultOperationScope as b, createWorkloadMcpBoundary as c, getAppMcpRelease as d, McpAccessDeniedError as f, toMcpToolError as g, mcpPublicErrorCode as h, customToolDescriptor as i, createRuntimeScope as l, assertBoundedMcpValue as m, createWorkloadMcpServer as n, executeCustomToolCall as o, McpPublicError as p, customToolAllows as r, authorizeWorkload as s, createMcpServer as t, createWorkloadMcpRelease as u, mcpTool as v, scopesFromContext as w, normalizeRequiredScopes as x, mcpPrompts as y };
|
|
@@ -37,6 +37,7 @@ type McpRoutes = {
|
|
|
37
37
|
};
|
|
38
38
|
type McpFieldTypes = Record<never, never>;
|
|
39
39
|
type McpMcpTools = Record<never, never>;
|
|
40
|
+
type McpMcpPrompts = Record<never, never>;
|
|
40
41
|
//#endregion
|
|
41
42
|
//#region src/server/modules/mcp/index.d.ts
|
|
42
43
|
type McpModule = typeof mcpModule;
|
|
@@ -52,6 +53,7 @@ declare const mcpModule: {
|
|
|
52
53
|
readonly jobs: McpJobs;
|
|
53
54
|
readonly fieldTypes: McpFieldTypes;
|
|
54
55
|
readonly mcpTools: McpMcpTools;
|
|
56
|
+
readonly mcpPrompts: McpMcpPrompts;
|
|
55
57
|
readonly messages: Record<never, never>;
|
|
56
58
|
readonly services: Record<never, never>;
|
|
57
59
|
readonly emailTemplates: Record<never, never>;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as mcpModule } from "./index-
|
|
2
|
-
import { A as
|
|
1
|
+
import { n as mcpModule } from "./index-CLEEqMqK.mjs";
|
|
2
|
+
import { A as McpTransportKind, B as McpWorkloadRequirement, C as McpRequiredScopes, D as McpToolConfig, E as McpStdioConfig, F as McpWorkloadContextBinder, H as McpWorkloadToolKind, I as McpWorkloadContextBindingInput, L as McpWorkloadExecutionControl, M as McpWorkloadAuthorization, N as McpWorkloadAuthorizationRequest, O as McpToolDefinition, P as McpWorkloadAuthorizer, R as McpWorkloadHandoff, S as McpPublicErrorCode, T as McpRoutesConfig, U as McpWorkloadToolPort, V as McpWorkloadToolFacts, W as WorkloadMcpServerOptions, _ as McpProgrammaticToolResult, a as McpAccessRule, b as McpPromptProviderConfig, c as McpCrudConfig, d as McpExecutionDiagnosticEvent, f as McpExecutionLimits, g as McpProgrammaticTool, h as McpProgrammaticRequestOptions, i as McpAccessMode, j as McpWorkloadAuditEvent, k as McpToolHandlerArgs, l as McpEntityPolicy, m as McpHttpConfig, n as QuestpieApp, o as McpAccessRuleContext, p as McpExecutionOptions, r as mcpPublicErrorCode, s as McpConfig, t as startStdioServer, u as McpExecutionConfig, v as McpPromptGetArgs, w as McpResourcesConfig, x as McpPromptProviderDefinition, y as McpPromptHandlerArgs, z as McpWorkloadHandoffInput } from "./stdio-C2V13-vP.mjs";
|
|
3
3
|
import { t as mcpPlugin } from "./plugin-CMR-wkeP.mjs";
|
|
4
4
|
import { AppContext, Questpie, RequestContext, ScopeOperationKind, defaultOperationScope, introspectRoutes } from "questpie";
|
|
5
5
|
import { z } from "zod";
|
|
@@ -100,11 +100,15 @@ interface ResolvedMcpRouteCatalogEntry extends ResolvedMcpEntityCatalogEntry {
|
|
|
100
100
|
interface ResolvedMcpCustomToolCatalogEntry {
|
|
101
101
|
tool: McpToolDefinition;
|
|
102
102
|
}
|
|
103
|
+
interface ResolvedMcpPromptProviderCatalogEntry {
|
|
104
|
+
provider: McpPromptProviderDefinition;
|
|
105
|
+
}
|
|
103
106
|
interface ResolvedMcpCatalog {
|
|
104
107
|
collections: ReadonlyMap<string, ResolvedMcpEntityCatalogEntry>;
|
|
105
108
|
globals: ReadonlyMap<string, ResolvedMcpEntityCatalogEntry>;
|
|
106
109
|
routes: ReadonlyMap<string, ResolvedMcpRouteCatalogEntry>;
|
|
107
110
|
customTools: ReadonlyMap<string, ResolvedMcpCustomToolCatalogEntry>;
|
|
111
|
+
promptProviders: ReadonlyMap<string, ResolvedMcpPromptProviderCatalogEntry>;
|
|
108
112
|
resources: {
|
|
109
113
|
collections: ReadonlySet<string>;
|
|
110
114
|
globals: ReadonlySet<string>;
|
|
@@ -118,6 +122,7 @@ interface ResolvedMcpCatalog {
|
|
|
118
122
|
type CatalogApp = Pick<Questpie<any>, "getCollections" | "getGlobals"> & {
|
|
119
123
|
state?: {
|
|
120
124
|
mcpTools?: Record<string, unknown>;
|
|
125
|
+
mcpPrompts?: Record<string, unknown>;
|
|
121
126
|
} & Record<string, unknown>;
|
|
122
127
|
config?: Questpie<any>["config"];
|
|
123
128
|
};
|
|
@@ -164,4 +169,39 @@ declare class McpToolBuilder<TInputSchema extends z.ZodTypeAny, TOutputSchema ex
|
|
|
164
169
|
*/
|
|
165
170
|
declare function mcpTool<TInputSchema extends z.ZodTypeAny = z.ZodTypeAny, TOutputSchema extends z.ZodTypeAny = z.ZodTypeAny>(name: string, config: McpToolConfig<TInputSchema, TOutputSchema>): McpToolBuilder<TInputSchema, TOutputSchema>;
|
|
166
171
|
//#endregion
|
|
167
|
-
|
|
172
|
+
//#region src/server/mcp-prompts.d.ts
|
|
173
|
+
/**
|
|
174
|
+
* Define an MCP prompt provider, contributed by a module the same way custom
|
|
175
|
+
* tools are (via `ModuleDefinition.mcpPrompts`, file convention
|
|
176
|
+
* `mcp-prompts/*.ts`). A provider answers `prompts/list` and `prompts/get`
|
|
177
|
+
* per request, so the prompts a caller sees can depend on who the caller is.
|
|
178
|
+
*
|
|
179
|
+
* `access` and `scopes` compose exactly like a custom tool's: a caller the
|
|
180
|
+
* provider denies sees none of its prompts, and `prompts/get` of any of them
|
|
181
|
+
* answers not-found. `list` and `get` then run with that caller's context and
|
|
182
|
+
* must apply their own row-level rules; returning `null` from `get` for a
|
|
183
|
+
* prompt the caller cannot read keeps it indistinguishable from an unknown
|
|
184
|
+
* name. Prompts are served on the `http` and `stdio` transports only, never to
|
|
185
|
+
* remote workloads.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* export default mcpPrompts("playbooks", {
|
|
190
|
+
* access: ({ session }) => !!session,
|
|
191
|
+
* scopes: "playbooks:read",
|
|
192
|
+
* list: async ({ ctx }) =>
|
|
193
|
+
* (await ctx.collections.playbooks.find({})).docs.map((doc) => ({
|
|
194
|
+
* name: doc.slug,
|
|
195
|
+
* description: doc.summary,
|
|
196
|
+
* })),
|
|
197
|
+
* get: async ({ ctx, name }) => {
|
|
198
|
+
* const doc = await ctx.collections.playbooks.findOne({ where: { slug: name } });
|
|
199
|
+
* if (!doc) return null;
|
|
200
|
+
* return { messages: [{ role: "user", content: { type: "text", text: doc.body } }] };
|
|
201
|
+
* },
|
|
202
|
+
* });
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
declare function mcpPrompts(name: string, config: McpPromptProviderConfig): McpPromptProviderDefinition;
|
|
206
|
+
//#endregion
|
|
207
|
+
export { type McpAccessMode, type McpAccessRule, type McpAccessRuleContext, type McpConfig, type McpCrudConfig, type McpEntityPolicy, type McpExecutionConfig, type McpExecutionDiagnosticEvent, type McpExecutionLimits, type McpExecutionOptions, type McpHttpConfig, type McpProgrammaticRequestOptions, type McpProgrammaticTool, type McpProgrammaticToolResult, type McpPromptGetArgs, type McpPromptHandlerArgs, type McpPromptProviderConfig, type McpPromptProviderDefinition, type McpPublicErrorCode, type McpRequiredScopes, type McpResourcesConfig, type McpRoutesConfig, type McpStdioConfig, type McpToolConfig, type McpToolDefinition, type McpToolHandlerArgs, type McpTransportKind, type McpWorkloadAuditEvent, type McpWorkloadAuthorization, type McpWorkloadAuthorizationRequest, type McpWorkloadAuthorizer, type McpWorkloadContextBinder, type McpWorkloadContextBindingInput, type McpWorkloadExecutionControl, type McpWorkloadHandoff, type McpWorkloadHandoffInput, type McpWorkloadRequirement, type McpWorkloadToolFacts, type McpWorkloadToolKind, type McpWorkloadToolPort, type ResolvedMcpCatalog, type ResolvedMcpCustomToolCatalogEntry, type ResolvedMcpEntityCatalogEntry, type ResolvedMcpPromptProviderCatalogEntry, type ResolvedMcpRouteCatalogEntry, type ScopeOperationKind, type WorkloadMcpServerOptions, createMcpServer, createWorkloadMcpServer, createWorkloadMcpToolPort, mcpModule as default, mcpModule, defaultOperationScope, mcpConfig, mcpPlugin, mcpPrompts, mcpPublicErrorCode, mcpTool, normalizeRequiredScopes, requiredScopesForOperation, resolveMcpCatalog, scopeGateAllows, scopesFromContext, startStdioServer };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as
|
|
2
|
-
import { n as mcp_default, t as mcpModule } from "./mcp-
|
|
3
|
-
import { t as mcpPlugin } from "./plugin-
|
|
4
|
-
import { t as startStdioServer } from "./stdio-
|
|
1
|
+
import { C as scopeGateAllows, S as requiredScopesForOperation, _ as resolveMcpCatalog, a as customToolWorkloadIdentity, b as defaultOperationScope, c as createWorkloadMcpBoundary, f as McpAccessDeniedError, g as toMcpToolError, h as mcpPublicErrorCode, i as customToolDescriptor, l as createRuntimeScope, m as assertBoundedMcpValue, n as createWorkloadMcpServer, o as executeCustomToolCall, p as McpPublicError, r as customToolAllows, s as authorizeWorkload, t as createMcpServer, u as createWorkloadMcpRelease, v as mcpTool, w as scopesFromContext, x as normalizeRequiredScopes, y as mcpPrompts } from "./create-server-DzTo-3Wy.mjs";
|
|
2
|
+
import { n as mcp_default, t as mcpModule } from "./mcp-C55EQ-yd.mjs";
|
|
3
|
+
import { t as mcpPlugin } from "./plugin-BgwWI9S8.mjs";
|
|
4
|
+
import { t as startStdioServer } from "./stdio-Bq7nzR79.mjs";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
|
|
7
7
|
//#region src/server/workload-tool-port.ts
|
|
@@ -96,4 +96,4 @@ function mcpConfig(config) {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
//#endregion
|
|
99
|
-
export { createMcpServer, createWorkloadMcpServer, createWorkloadMcpToolPort, mcp_default as default, defaultOperationScope, mcpConfig, mcpModule, mcpPlugin, mcpPublicErrorCode, mcpTool, normalizeRequiredScopes, requiredScopesForOperation, resolveMcpCatalog, scopeGateAllows, scopesFromContext, startStdioServer };
|
|
99
|
+
export { createMcpServer, createWorkloadMcpServer, createWorkloadMcpToolPort, mcp_default as default, defaultOperationScope, mcpConfig, mcpModule, mcpPlugin, mcpPrompts, mcpPublicErrorCode, mcpTool, normalizeRequiredScopes, requiredScopesForOperation, resolveMcpCatalog, scopeGateAllows, scopesFromContext, startStdioServer };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as mcpPlugin } from "./plugin-
|
|
1
|
+
import { T as validateMcpPrincipal, d as getAppMcpRelease, m as assertBoundedMcpValue, t as createMcpServer } from "./create-server-DzTo-3Wy.mjs";
|
|
2
|
+
import { t as mcpPlugin } from "./plugin-BgwWI9S8.mjs";
|
|
3
3
|
import { route } from "questpie";
|
|
4
4
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
5
5
|
|
|
@@ -325,6 +325,7 @@ const _module = {
|
|
|
325
325
|
seeds: [],
|
|
326
326
|
fieldTypes: {},
|
|
327
327
|
mcpTools: {},
|
|
328
|
+
mcpPrompts: {},
|
|
328
329
|
plugin: plugin_default
|
|
329
330
|
};
|
|
330
331
|
var module_default = _module;
|
package/dist/modules/mcp.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as mcpModule, t as McpModule } from "../index-
|
|
1
|
+
import { n as mcpModule, t as McpModule } from "../index-CLEEqMqK.mjs";
|
|
2
2
|
export { type McpModule, mcpModule };
|
package/dist/modules/mcp.mjs
CHANGED
|
@@ -5,15 +5,26 @@ function mcpPlugin() {
|
|
|
5
5
|
targets: { server: {
|
|
6
6
|
root: ".",
|
|
7
7
|
outputFile: "index.ts",
|
|
8
|
-
categories: {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
categories: {
|
|
9
|
+
mcpTools: {
|
|
10
|
+
dirs: ["mcp-tools"],
|
|
11
|
+
prefix: "mcpTool",
|
|
12
|
+
factoryFunctions: ["mcpTool"],
|
|
13
|
+
registryKey: true,
|
|
14
|
+
includeInAppState: true,
|
|
15
|
+
extractFromModules: true,
|
|
16
|
+
typeEmit: "standard"
|
|
17
|
+
},
|
|
18
|
+
mcpPrompts: {
|
|
19
|
+
dirs: ["mcp-prompts"],
|
|
20
|
+
prefix: "mcpPrompts",
|
|
21
|
+
factoryFunctions: ["mcpPrompts"],
|
|
22
|
+
registryKey: true,
|
|
23
|
+
includeInAppState: true,
|
|
24
|
+
extractFromModules: true,
|
|
25
|
+
typeEmit: "standard"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
17
28
|
discover: { mcpConfig: {
|
|
18
29
|
pattern: "config/mcp.ts",
|
|
19
30
|
configKey: "mcp"
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AppContext, RequestContext, createContextFactory } from "questpie";
|
|
2
|
+
import { CallToolResult, GetPromptResult, Prompt, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
2
3
|
import "questpie/executor";
|
|
3
4
|
import { z } from "zod";
|
|
4
|
-
import { CallToolResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import * as _modelcontextprotocol_sdk_server_mcp0 from "@modelcontextprotocol/sdk/server/mcp";
|
|
6
6
|
|
|
7
7
|
//#region src/server/types.d.ts
|
|
@@ -303,12 +303,58 @@ interface McpToolDefinition<TInputSchema extends z.ZodTypeAny = z.ZodTypeAny, TO
|
|
|
303
303
|
readonly config: McpToolConfig<TInputSchema, TOutputSchema>;
|
|
304
304
|
readonly handler: (args: McpToolHandlerArgs<z.infer<TInputSchema>>) => CallToolResult | Promise<CallToolResult>;
|
|
305
305
|
}
|
|
306
|
+
/** Per-request arguments shared by a prompt provider's `list` and `get`. */
|
|
307
|
+
interface McpPromptHandlerArgs {
|
|
308
|
+
ctx: AppContext & Partial<RequestContext>;
|
|
309
|
+
transport: McpTransportKind;
|
|
310
|
+
accessMode: McpAccessMode;
|
|
311
|
+
request?: Request;
|
|
312
|
+
signal: AbortSignal;
|
|
313
|
+
requestId: string | number;
|
|
314
|
+
correlationId: string;
|
|
315
|
+
}
|
|
316
|
+
interface McpPromptGetArgs extends McpPromptHandlerArgs {
|
|
317
|
+
/** The prompt name the client asked for, exactly as sent. */
|
|
318
|
+
name: string;
|
|
319
|
+
/** The client's string arguments; `{}` when the client sent none. */
|
|
320
|
+
arguments: Record<string, string>;
|
|
321
|
+
}
|
|
322
|
+
interface McpPromptProviderConfig {
|
|
323
|
+
/**
|
|
324
|
+
* Required explicit opt-in, evaluated per request against the same caller
|
|
325
|
+
* context the tools get. A denied caller sees none of this provider's prompts
|
|
326
|
+
* and a `prompts/get` of any of them answers not-found.
|
|
327
|
+
*/
|
|
328
|
+
access: McpAccessRule;
|
|
329
|
+
/**
|
|
330
|
+
* Scopes an OAuth caller must hold (all required — AND), or `false` for an
|
|
331
|
+
* explicit no-OAuth-scope policy. Required, like a custom tool's `scopes`.
|
|
332
|
+
*/
|
|
333
|
+
scopes: McpRequiredScopes;
|
|
334
|
+
/**
|
|
335
|
+
* The prompts this caller can use right now. Runs on every `prompts/list`;
|
|
336
|
+
* the list may differ per caller and per request.
|
|
337
|
+
*/
|
|
338
|
+
list: (args: McpPromptHandlerArgs) => Prompt[] | Promise<Prompt[]>;
|
|
339
|
+
/**
|
|
340
|
+
* Render one prompt, or return `null` when this caller has no prompt of
|
|
341
|
+
* that name. `null` is answered as not-found, the same as an unknown name,
|
|
342
|
+
* so a prompt the caller cannot read is indistinguishable from none.
|
|
343
|
+
*/
|
|
344
|
+
get: (args: McpPromptGetArgs) => GetPromptResult | null | Promise<GetPromptResult | null>;
|
|
345
|
+
}
|
|
346
|
+
interface McpPromptProviderDefinition {
|
|
347
|
+
readonly __brand: "mcpPrompts";
|
|
348
|
+
readonly name: string;
|
|
349
|
+
readonly config: McpPromptProviderConfig;
|
|
350
|
+
}
|
|
306
351
|
declare module "questpie" {
|
|
307
352
|
interface AppStateConfig {
|
|
308
353
|
mcp?: McpConfig;
|
|
309
354
|
}
|
|
310
355
|
interface ModuleDefinition {
|
|
311
356
|
mcpTools?: Record<string, McpToolDefinition>;
|
|
357
|
+
mcpPrompts?: Record<string, McpPromptProviderDefinition>;
|
|
312
358
|
}
|
|
313
359
|
}
|
|
314
360
|
//#endregion
|
|
@@ -322,4 +368,4 @@ type QuestpieApp = Parameters<typeof createContextFactory>[0];
|
|
|
322
368
|
//#region src/server/stdio.d.ts
|
|
323
369
|
declare function startStdioServer(app: QuestpieApp, options?: Omit<McpExecutionOptions, "transport">): Promise<_modelcontextprotocol_sdk_server_mcp0.McpServer>;
|
|
324
370
|
//#endregion
|
|
325
|
-
export {
|
|
371
|
+
export { McpTransportKind as A, McpWorkloadRequirement as B, McpRequiredScopes as C, McpToolConfig as D, McpStdioConfig as E, McpWorkloadContextBinder as F, McpWorkloadToolKind as H, McpWorkloadContextBindingInput as I, McpWorkloadExecutionControl as L, McpWorkloadAuthorization as M, McpWorkloadAuthorizationRequest as N, McpToolDefinition as O, McpWorkloadAuthorizer as P, McpWorkloadHandoff as R, McpPublicErrorCode as S, McpRoutesConfig as T, McpWorkloadToolPort as U, McpWorkloadToolFacts as V, WorkloadMcpServerOptions as W, McpProgrammaticToolResult as _, McpAccessRule as a, McpPromptProviderConfig as b, McpCrudConfig as c, McpExecutionDiagnosticEvent as d, McpExecutionLimits as f, McpProgrammaticTool as g, McpProgrammaticRequestOptions as h, McpAccessMode as i, McpWorkloadAuditEvent as j, McpToolHandlerArgs as k, McpEntityPolicy as l, McpHttpConfig as m, QuestpieApp as n, McpAccessRuleContext as o, McpExecutionOptions as p, mcpPublicErrorCode as r, McpConfig as s, startStdioServer as t, McpExecutionConfig as u, McpPromptGetArgs as v, McpResourcesConfig as w, McpPromptProviderDefinition as x, McpPromptHandlerArgs as y, McpWorkloadHandoffInput as z };
|
package/dist/stdio.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as startStdioServer } from "./stdio-
|
|
1
|
+
import { t as startStdioServer } from "./stdio-C2V13-vP.mjs";
|
|
2
2
|
export { startStdioServer };
|
package/dist/stdio.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@questpie/mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.29.0",
|
|
4
4
|
"description": "Model Context Protocol server for QUESTPIE apps, under the app's own access rules",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
73
|
-
"questpie": "^3.
|
|
73
|
+
"questpie": "^3.29.0",
|
|
74
74
|
"zod": "^4.2.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
@@ -80,6 +80,6 @@
|
|
|
80
80
|
"tsdown": "^0.18.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"questpie": "^3.
|
|
83
|
+
"questpie": "^3.29.0"
|
|
84
84
|
}
|
|
85
85
|
}
|