@questpie/mcp 3.3.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 +69 -0
- package/dist/create-server-DIvtVlma.mjs +1014 -0
- package/dist/index-Cxta5KOn.d.mts +58 -0
- package/dist/index.d.mts +122 -0
- package/dist/index.mjs +12 -0
- package/dist/mcp-DapXh3Mk.mjs +163 -0
- package/dist/modules/mcp.d.mts +2 -0
- package/dist/modules/mcp.mjs +3 -0
- package/dist/plugin-BJ6gELRj.mjs +39 -0
- package/dist/plugin-CMR-wkeP.d.mts +6 -0
- package/dist/plugin.d.mts +2 -0
- package/dist/plugin.mjs +3 -0
- package/dist/stdio-qyQFVEyU.mjs +16 -0
- package/dist/stdio-t_9rKayH.d.mts +154 -0
- package/dist/stdio.d.mts +2 -0
- package/dist/stdio.mjs +3 -0
- package/package.json +83 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as questpie0 from "questpie";
|
|
2
|
+
import { RouteDefinition, RouteParamsFromKey } from "questpie/types";
|
|
3
|
+
|
|
4
|
+
//#region src/server/modules/mcp/routes/mcp.d.ts
|
|
5
|
+
declare const _default$4: questpie0.RawRouteDefinition<questpie0.JsonRouteParams>;
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/server/modules/mcp/routes/mcp.delete.d.ts
|
|
8
|
+
declare const _default$3: questpie0.RawRouteDefinition<questpie0.JsonRouteParams>;
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/server/modules/mcp/routes/mcp.get.d.ts
|
|
11
|
+
declare const _default$2: questpie0.RawRouteDefinition<questpie0.JsonRouteParams>;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/server/modules/mcp/routes/mcp.options.d.ts
|
|
14
|
+
declare const _default$1: questpie0.RawRouteDefinition<questpie0.JsonRouteParams>;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/server/modules/mcp/plugin.d.ts
|
|
17
|
+
declare const _default: questpie0.CodegenPlugin;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/server/modules/mcp/.generated/module.d.ts
|
|
20
|
+
type McpCollections = Record<never, never>;
|
|
21
|
+
type McpGlobals = Record<never, never>;
|
|
22
|
+
type McpJobs = Record<never, never>;
|
|
23
|
+
type McpRoutes = {
|
|
24
|
+
mcp: typeof _default$4 extends {
|
|
25
|
+
__brand: "route";
|
|
26
|
+
} ? RouteDefinition<unknown, unknown, RouteParamsFromKey<"mcp">> : typeof _default$4;
|
|
27
|
+
"mcp:DELETE": typeof _default$3 extends {
|
|
28
|
+
__brand: "route";
|
|
29
|
+
} ? RouteDefinition<unknown, unknown, RouteParamsFromKey<"mcp:DELETE">> : typeof _default$3;
|
|
30
|
+
"mcp:GET": typeof _default$2 extends {
|
|
31
|
+
__brand: "route";
|
|
32
|
+
} ? RouteDefinition<unknown, unknown, RouteParamsFromKey<"mcp:GET">> : typeof _default$2;
|
|
33
|
+
"mcp:OPTIONS": typeof _default$1 extends {
|
|
34
|
+
__brand: "route";
|
|
35
|
+
} ? RouteDefinition<unknown, unknown, RouteParamsFromKey<"mcp:OPTIONS">> : typeof _default$1;
|
|
36
|
+
};
|
|
37
|
+
type McpFieldTypes = Record<never, never>;
|
|
38
|
+
type McpMcpTools = Record<never, never>;
|
|
39
|
+
type McpModule = {
|
|
40
|
+
name: "questpie-mcp";
|
|
41
|
+
routes: McpRoutes;
|
|
42
|
+
collections: McpCollections;
|
|
43
|
+
globals: McpGlobals;
|
|
44
|
+
jobs: McpJobs;
|
|
45
|
+
fieldTypes: McpFieldTypes;
|
|
46
|
+
mcpTools: McpMcpTools;
|
|
47
|
+
messages: Record<never, never>;
|
|
48
|
+
services: Record<never, never>;
|
|
49
|
+
emails: Record<never, never>;
|
|
50
|
+
migrations: readonly unknown[];
|
|
51
|
+
seeds: readonly unknown[];
|
|
52
|
+
plugin: typeof _default;
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/server/modules/mcp/index.d.ts
|
|
56
|
+
declare const mcpModule: McpModule;
|
|
57
|
+
//#endregion
|
|
58
|
+
export { McpModule as n, mcpModule as t };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { t as mcpModule } from "./index-Cxta5KOn.mjs";
|
|
2
|
+
import { _ as McpToolDefinition, a as McpAccessRuleContext, c as McpCrudDefaults, d as McpHttpConfig, f as McpRequiredScopes, g as McpToolConfig, h as McpStdioConfig, i as McpAccessRule, l as McpEntityPolicy, m as McpRoutesConfig, n as QuestpieApp, o as McpConfig, p as McpResourcesConfig, r as McpAccessMode, s as McpCrudConfig, t as startStdioServer, u as McpExecutionOptions, v as McpToolHandlerArgs, y as McpTransportKind } from "./stdio-t_9rKayH.mjs";
|
|
3
|
+
import { t as mcpPlugin } from "./plugin-CMR-wkeP.mjs";
|
|
4
|
+
import { AppContext, RequestContext, ScopeOperationKind, defaultOperationScope } from "questpie";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
//#region src/server/create-server.d.ts
|
|
9
|
+
declare function createMcpServer(app: QuestpieApp, options?: McpExecutionOptions): Promise<McpServer>;
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/server/config.d.ts
|
|
12
|
+
declare function mcpConfig<TConfig extends McpConfig>(config: TConfig): TConfig;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/server/mcp-tool.d.ts
|
|
15
|
+
declare class McpToolBuilder<TInputSchema extends z.ZodTypeAny, TOutputSchema extends z.ZodTypeAny> {
|
|
16
|
+
private readonly name;
|
|
17
|
+
private readonly config;
|
|
18
|
+
constructor(name: string, config: McpToolConfig<TInputSchema, TOutputSchema>);
|
|
19
|
+
handler(handler: (args: McpToolHandlerArgs<z.infer<TInputSchema>>) => ReturnType<McpToolDefinition<TInputSchema, TOutputSchema>["handler"]>): McpToolDefinition<TInputSchema, TOutputSchema>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Define a custom MCP tool, contributed by a module the same way collections and
|
|
23
|
+
* routes are (via `ModuleDefinition.mcpTools`). `mcpTool(name, config).handler(fn)`
|
|
24
|
+
* returns a frozen, branded {@link McpToolDefinition}; the CRUD/route tools the
|
|
25
|
+
* framework auto-generates are the same shape.
|
|
26
|
+
*
|
|
27
|
+
* `config` mirrors the MCP tool contract: `inputSchema`/`outputSchema` (Zod),
|
|
28
|
+
* `title`/`description`/`annotations` for discovery, plus the two QUESTPIE
|
|
29
|
+
* authorization hooks that compose exactly like the built-in tools:
|
|
30
|
+
* - `access` — an {@link McpAccessRule} run against the caller's transport /
|
|
31
|
+
* accessMode / session (and, for an OAuth caller, its scopes). Same RBAC-style
|
|
32
|
+
* gate the CRUD tools use.
|
|
33
|
+
* - `scopes` — the OAuth scopes an `oauth` caller must hold ({@link McpToolConfig.scopes}).
|
|
34
|
+
* Custom tools have no default scope mapping (unlike CRUD, there is no
|
|
35
|
+
* resource/operation to derive one from), so an omitted value requires no
|
|
36
|
+
* scope. The shipped scope gate ({@link scopeGateAllows}) enforces this at both
|
|
37
|
+
* `tools/list` (hidden) and `tools/call` (denied); `system`/`user` callers
|
|
38
|
+
* carry no scopes and skip it, so the effective bound is always `scopes ∩ RBAC`.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* export const ping = mcpTool("ops.ping", {
|
|
43
|
+
* description: "Health probe",
|
|
44
|
+
* inputSchema: z.object({}),
|
|
45
|
+
* scopes: "routes:ops/ping:invoke", // only OAuth callers holding this scope
|
|
46
|
+
* }).handler(async () => ({ content: [{ type: "text", text: "pong" }] }));
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
declare function mcpTool<TInputSchema extends z.ZodTypeAny = z.ZodTypeAny, TOutputSchema extends z.ZodTypeAny = z.ZodTypeAny>(name: string, config?: McpToolConfig<TInputSchema, TOutputSchema>): McpToolBuilder<TInputSchema, TOutputSchema>;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/server/policy.d.ts
|
|
52
|
+
type EntityKind = "collection" | "global" | "route";
|
|
53
|
+
interface ResolvedMcpPolicy {
|
|
54
|
+
expose: boolean;
|
|
55
|
+
read?: boolean | McpAccessRule;
|
|
56
|
+
write?: boolean | McpAccessRule;
|
|
57
|
+
delete?: boolean | McpAccessRule;
|
|
58
|
+
operations: Record<string, boolean | McpAccessRule>;
|
|
59
|
+
requiredScopes?: McpRequiredScopes;
|
|
60
|
+
operationScopes: Record<string, McpRequiredScopes>;
|
|
61
|
+
fields?: {
|
|
62
|
+
include?: string[];
|
|
63
|
+
exclude?: string[];
|
|
64
|
+
};
|
|
65
|
+
description?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Normalize an {@link McpRequiredScopes} declaration to a scope list. */
|
|
68
|
+
declare function normalizeRequiredScopes(required: McpRequiredScopes | undefined): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the scopes an OAuth caller must hold for a given entity operation.
|
|
71
|
+
*
|
|
72
|
+
* Precedence (most specific wins), each level is declarative data:
|
|
73
|
+
* 1. `policy.operationScopes[operationName]` — per-operation override.
|
|
74
|
+
* 2. `policy.requiredScopes` — entity-level requirement (all operations).
|
|
75
|
+
* 3. the default `<resource>:<name>:<verb>` mapping derived from the kind.
|
|
76
|
+
*
|
|
77
|
+
* A level set to `false` explicitly requires no scope (stops the fallback).
|
|
78
|
+
* Returns a (possibly empty) scope list; empty means "no scope required". This
|
|
79
|
+
* resolves the requirement; the deny/register decision that consumes it is
|
|
80
|
+
* {@link scopeGateAllows}.
|
|
81
|
+
*/
|
|
82
|
+
declare function requiredScopesForOperation(policy: ResolvedMcpPolicy, kind: EntityKind, name: string, operationName: string, operationKind: ScopeOperationKind): string[];
|
|
83
|
+
/**
|
|
84
|
+
* The consented scopes carried by the request, or `undefined` when the caller
|
|
85
|
+
* is not OAuth-authenticated. Reads `ctx.principal` (populated by MO6): only the
|
|
86
|
+
* `oauth` principal carries `scopes`; `user` and `system` do not.
|
|
87
|
+
*/
|
|
88
|
+
declare function scopesFromContext(ctx: AppContext & Partial<RequestContext>): string[] | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* The OAuth scope gate (MO8, extended by the umbrella model — LOCKED #2).
|
|
91
|
+
*
|
|
92
|
+
* Given the scopes the caller holds (from {@link scopesFromContext}) and the
|
|
93
|
+
* scopes an operation requires (from {@link requiredScopesForOperation} or a
|
|
94
|
+
* custom tool's `config.scopes`), decide whether the scope gate passes.
|
|
95
|
+
*
|
|
96
|
+
* Semantics:
|
|
97
|
+
* - `heldScopes === undefined` → the caller is not OAuth-authenticated (`user`,
|
|
98
|
+
* `system`, or unauthenticated). The scope gate does not apply and PASSES;
|
|
99
|
+
* authorization is left entirely to RBAC / MCP rules. This is what keeps the
|
|
100
|
+
* first-party admin (`user`) and stdio (`system`) paths unchanged.
|
|
101
|
+
* - Otherwise the caller is an `oauth` principal: the gate PASSES iff EVERY
|
|
102
|
+
* required scope is satisfied — a required scope is satisfied when the caller
|
|
103
|
+
* holds it directly OR holds its applicable coarse umbrella
|
|
104
|
+
* ({@link umbrellaScopeFor}). An empty `required` needs nothing, so it passes
|
|
105
|
+
* for everyone.
|
|
106
|
+
*
|
|
107
|
+
* The umbrella widening is deliberately narrow — it can only satisfy a
|
|
108
|
+
* granular `<resource>:<name>:read|write` with the matching `<resource>:read`
|
|
109
|
+
* or `<resource>:write` umbrella. `read` and `write` are independent (holding
|
|
110
|
+
* `collections:write` never satisfies a `<name>:read` requirement, and vice
|
|
111
|
+
* versa), and there is NO umbrella for `:delete` or `routes:…:invoke`. So the
|
|
112
|
+
* umbrella grants convenience without ever over-granting across verb, resource
|
|
113
|
+
* kind, or into delete/invoke.
|
|
114
|
+
*
|
|
115
|
+
* This gate is **additive** — it can only ever REMOVE access from an `oauth`
|
|
116
|
+
* caller. It never grants access on its own: callers still run the MCP rule and
|
|
117
|
+
* the collection/global `.access()` RBAC independently, and all must pass. The
|
|
118
|
+
* effective `oauth` permission is therefore `scopes ∩ RBAC`.
|
|
119
|
+
*/
|
|
120
|
+
declare function scopeGateAllows(heldScopes: string[] | undefined, required: string[]): boolean;
|
|
121
|
+
//#endregion
|
|
122
|
+
export { type McpAccessMode, type McpAccessRule, type McpAccessRuleContext, type McpConfig, type McpCrudConfig, type McpCrudDefaults, type McpEntityPolicy, type McpExecutionOptions, type McpHttpConfig, type McpRequiredScopes, type McpResourcesConfig, type McpRoutesConfig, type McpStdioConfig, type McpToolConfig, type McpToolDefinition, type McpToolHandlerArgs, type McpTransportKind, type ScopeOperationKind, createMcpServer, mcpModule as default, mcpModule, defaultOperationScope, mcpConfig, mcpPlugin, mcpTool, normalizeRequiredScopes, requiredScopesForOperation, scopeGateAllows, scopesFromContext, startStdioServer };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { a as requiredScopesForOperation, i as normalizeRequiredScopes, n as mcpTool, o as scopeGateAllows, r as defaultOperationScope, s as scopesFromContext, t as createMcpServer } from "./create-server-DIvtVlma.mjs";
|
|
2
|
+
import { n as mcp_default, t as mcpModule } from "./mcp-DapXh3Mk.mjs";
|
|
3
|
+
import { t as mcpPlugin } from "./plugin-BJ6gELRj.mjs";
|
|
4
|
+
import { t as startStdioServer } from "./stdio-qyQFVEyU.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/server/config.ts
|
|
7
|
+
function mcpConfig(config) {
|
|
8
|
+
return config;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { createMcpServer, mcp_default as default, defaultOperationScope, mcpConfig, mcpModule, mcpPlugin, mcpTool, normalizeRequiredScopes, requiredScopesForOperation, scopeGateAllows, scopesFromContext, startStdioServer };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { t as createMcpServer } from "./create-server-DIvtVlma.mjs";
|
|
2
|
+
import { t as mcpPlugin } from "./plugin-BJ6gELRj.mjs";
|
|
3
|
+
import { route } from "questpie";
|
|
4
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
5
|
+
|
|
6
|
+
//#region src/server/mcp-http.ts
|
|
7
|
+
/**
|
|
8
|
+
* Shared MCP Streamable-HTTP handler.
|
|
9
|
+
*
|
|
10
|
+
* `route()` is single-method, so the MCP endpoint's four verbs (POST = JSON-RPC
|
|
11
|
+
* messages, GET = SSE stream, DELETE = session end, OPTIONS = CORS preflight)
|
|
12
|
+
* are registered as separate method-suffixed route files under `routes/`
|
|
13
|
+
* (`mcp.ts` = POST, `mcp.get.ts`, `mcp.delete.ts`, `mcp.options.ts`) per the
|
|
14
|
+
* codegen file convention — NOT method chaining and NOT a hand-written
|
|
15
|
+
* `module.ts`. All four share THIS handler, which dispatches on
|
|
16
|
+
* `request.method` internally because the transport needs one object for every
|
|
17
|
+
* verb on the path.
|
|
18
|
+
*/
|
|
19
|
+
const mcpMeta = {
|
|
20
|
+
title: "MCP endpoint",
|
|
21
|
+
description: "Model Context Protocol endpoint for QUESTPIE."
|
|
22
|
+
};
|
|
23
|
+
function appFromRouteContext(ctx) {
|
|
24
|
+
return ctx.app;
|
|
25
|
+
}
|
|
26
|
+
function executionContextFromRouteContext(ctx) {
|
|
27
|
+
const routeCtx = { ...ctx };
|
|
28
|
+
delete routeCtx.app;
|
|
29
|
+
delete routeCtx.request;
|
|
30
|
+
return routeCtx;
|
|
31
|
+
}
|
|
32
|
+
function withCors(response, request) {
|
|
33
|
+
const headers = new Headers(response.headers);
|
|
34
|
+
const origin = request.headers.get("origin");
|
|
35
|
+
if (origin) {
|
|
36
|
+
headers.set("Access-Control-Allow-Origin", origin);
|
|
37
|
+
headers.set("Vary", "Origin");
|
|
38
|
+
}
|
|
39
|
+
headers.set("Access-Control-Allow-Methods", "POST, GET, DELETE, OPTIONS");
|
|
40
|
+
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, Mcp-Session-Id, Last-Event-ID");
|
|
41
|
+
return new Response(response.body, {
|
|
42
|
+
status: response.status,
|
|
43
|
+
statusText: response.statusText,
|
|
44
|
+
headers
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build the `WWW-Authenticate` challenge value for an unauthenticated HTTP MCP
|
|
49
|
+
* request. `resource_metadata` points at this app's Protected Resource metadata
|
|
50
|
+
* document (RFC 9728), served at the server root by the core `oauth-protected-
|
|
51
|
+
* resource` route (MO4). An MCP client reads it to discover the authorization
|
|
52
|
+
* server and start the OAuth 2.1 flow.
|
|
53
|
+
*
|
|
54
|
+
* The origin is derived from `app.config.app.url` — the same base MO2/MO6 use to
|
|
55
|
+
* bind and verify token audience — so the advertised metadata URL always matches
|
|
56
|
+
* where discovery actually lives. `new URL(...).origin` strips any path/query and
|
|
57
|
+
* yields a bare `scheme://host[:port]`, so an app URL with a subpath still
|
|
58
|
+
* produces a root-mounted metadata URL.
|
|
59
|
+
*/
|
|
60
|
+
function protectedResourceChallenge(appUrl) {
|
|
61
|
+
return `Bearer resource_metadata="${new URL(appUrl).origin}/.well-known/oauth-protected-resource"`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A verified principal is required to reach MCP tool execution over HTTP. The
|
|
65
|
+
* request context resolves one for a cookie/bearer session (`kind: "user"`) or a
|
|
66
|
+
* valid OAuth access token (`kind: "oauth"`); an unauthenticated request has
|
|
67
|
+
* none. Read it structurally — the handler-args type does not surface it.
|
|
68
|
+
*/
|
|
69
|
+
function hasVerifiedPrincipal(ctx) {
|
|
70
|
+
return Boolean(ctx.principal);
|
|
71
|
+
}
|
|
72
|
+
async function mcpHandler(ctx) {
|
|
73
|
+
const { request } = ctx;
|
|
74
|
+
const app = appFromRouteContext(ctx);
|
|
75
|
+
if (request.method === "OPTIONS") return withCors(new Response(null, { status: 204 }), request);
|
|
76
|
+
if (!hasVerifiedPrincipal(ctx)) return withCors(new Response(null, {
|
|
77
|
+
status: 401,
|
|
78
|
+
headers: { "WWW-Authenticate": protectedResourceChallenge(app.config.app.url) }
|
|
79
|
+
}), request);
|
|
80
|
+
const config = app.state?.config?.mcp ?? {};
|
|
81
|
+
const server = await createMcpServer(app, {
|
|
82
|
+
transport: "http",
|
|
83
|
+
accessMode: "user",
|
|
84
|
+
request,
|
|
85
|
+
ctx: executionContextFromRouteContext(ctx)
|
|
86
|
+
});
|
|
87
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
88
|
+
sessionIdGenerator: void 0,
|
|
89
|
+
allowedOrigins: config.http?.allowedOrigins,
|
|
90
|
+
allowedHosts: config.http?.allowedHosts,
|
|
91
|
+
enableJsonResponse: config.http?.enableJsonResponse ?? true
|
|
92
|
+
});
|
|
93
|
+
await server.connect(transport);
|
|
94
|
+
return withCors(await transport.handleRequest(request), request);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/server/modules/mcp/routes/mcp.ts
|
|
99
|
+
var mcp_default$1 = route().post().raw().meta(mcpMeta).handler(mcpHandler);
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/server/modules/mcp/routes/mcp.delete.ts
|
|
103
|
+
var mcp_delete_default = route().delete().raw().meta(mcpMeta).handler(mcpHandler);
|
|
104
|
+
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/server/modules/mcp/routes/mcp.get.ts
|
|
107
|
+
var mcp_get_default = route().get().raw().meta(mcpMeta).handler(mcpHandler);
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/server/modules/mcp/routes/mcp.options.ts
|
|
111
|
+
var mcp_options_default = route().options().raw().meta(mcpMeta).handler(mcpHandler);
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/server/modules/mcp/plugin.ts
|
|
115
|
+
/**
|
|
116
|
+
* MCP module plugin — re-exports the MCP codegen plugin so that module
|
|
117
|
+
* codegen discovers it and embeds it in the generated module definition.
|
|
118
|
+
* This allows `extractPluginsFromModules` to pick it up automatically at
|
|
119
|
+
* root-app codegen time (so consuming apps get the `mcp-tools` category,
|
|
120
|
+
* `config/mcp.ts` discover, and scaffolds).
|
|
121
|
+
*/
|
|
122
|
+
var plugin_default = mcpPlugin();
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/server/modules/mcp/.generated/module.ts
|
|
126
|
+
const _module = {
|
|
127
|
+
name: "questpie-mcp",
|
|
128
|
+
routes: {
|
|
129
|
+
mcp: mcp_default$1,
|
|
130
|
+
"mcp:DELETE": mcp_delete_default,
|
|
131
|
+
"mcp:GET": mcp_get_default,
|
|
132
|
+
"mcp:OPTIONS": mcp_options_default
|
|
133
|
+
},
|
|
134
|
+
collections: {},
|
|
135
|
+
globals: {},
|
|
136
|
+
jobs: {},
|
|
137
|
+
messages: {},
|
|
138
|
+
services: {},
|
|
139
|
+
emails: {},
|
|
140
|
+
migrations: [],
|
|
141
|
+
seeds: [],
|
|
142
|
+
fieldTypes: {},
|
|
143
|
+
mcpTools: {},
|
|
144
|
+
plugin: plugin_default
|
|
145
|
+
};
|
|
146
|
+
var module_default = _module;
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/server/modules/mcp/index.ts
|
|
150
|
+
/**
|
|
151
|
+
* MCP Module
|
|
152
|
+
*
|
|
153
|
+
* Backend module for the QUESTPIE Model Context Protocol endpoint.
|
|
154
|
+
*
|
|
155
|
+
* Exports:
|
|
156
|
+
* - `mcpModule` — static module definition (generated by codegen from file convention)
|
|
157
|
+
* - `McpModule` — type of the static module
|
|
158
|
+
*/
|
|
159
|
+
const mcpModule = module_default;
|
|
160
|
+
var mcp_default = mcpModule;
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
export { mcp_default as n, mcpModule as t };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/server/plugin.ts
|
|
2
|
+
function mcpPlugin() {
|
|
3
|
+
return {
|
|
4
|
+
name: "questpie-mcp",
|
|
5
|
+
targets: { server: {
|
|
6
|
+
root: ".",
|
|
7
|
+
outputFile: "index.ts",
|
|
8
|
+
categories: { mcpTools: {
|
|
9
|
+
dirs: ["mcp-tools"],
|
|
10
|
+
prefix: "mcpTool",
|
|
11
|
+
factoryFunctions: ["mcpTool"],
|
|
12
|
+
registryKey: true,
|
|
13
|
+
includeInAppState: true,
|
|
14
|
+
extractFromModules: true,
|
|
15
|
+
typeEmit: "standard"
|
|
16
|
+
} },
|
|
17
|
+
discover: { mcpConfig: {
|
|
18
|
+
pattern: "config/mcp.ts",
|
|
19
|
+
configKey: "mcp"
|
|
20
|
+
} },
|
|
21
|
+
registries: { singletonFactories: { mcpConfig: {
|
|
22
|
+
configType: "McpConfig",
|
|
23
|
+
imports: [{
|
|
24
|
+
name: "McpConfig",
|
|
25
|
+
from: "@questpie/mcp"
|
|
26
|
+
}],
|
|
27
|
+
isCallback: true
|
|
28
|
+
} } },
|
|
29
|
+
scaffolds: { "mcp-tool": {
|
|
30
|
+
dir: "mcp-tools",
|
|
31
|
+
description: "MCP custom tool definition",
|
|
32
|
+
template: ({ kebab, camel }) => `import { mcpTool } from "@questpie/mcp";\nimport { z } from "zod";\n\nexport default mcpTool("${kebab}", {\n\tdescription: "Run ${kebab}.",\n\tinputSchema: z.object({}),\n}).handler(async ({ ctx }) => {\n\treturn {\n\t\tstructuredContent: { ok: true },\n\t\tcontent: [{ type: "text", text: "${camel} completed" }],\n\t};\n});\n`
|
|
33
|
+
} }
|
|
34
|
+
} }
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { mcpPlugin as t };
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { t as createMcpServer } from "./create-server-DIvtVlma.mjs";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
|
|
4
|
+
//#region src/server/stdio.ts
|
|
5
|
+
async function startStdioServer(app, options = {}) {
|
|
6
|
+
const server = await createMcpServer(app, {
|
|
7
|
+
...options,
|
|
8
|
+
transport: "stdio"
|
|
9
|
+
});
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
return server;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { startStdioServer as t };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { AppContext, RequestContext, createContextFactory } from "questpie";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { CallToolResult, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import * as _modelcontextprotocol_sdk_server_mcp0 from "@modelcontextprotocol/sdk/server/mcp";
|
|
5
|
+
|
|
6
|
+
//#region src/server/types.d.ts
|
|
7
|
+
type McpTransportKind = "http" | "stdio";
|
|
8
|
+
type McpAccessMode = "user" | "system";
|
|
9
|
+
interface McpAccessRuleContext {
|
|
10
|
+
transport: McpTransportKind;
|
|
11
|
+
accessMode: McpAccessMode;
|
|
12
|
+
session?: RequestContext["session"];
|
|
13
|
+
/**
|
|
14
|
+
* The consented OAuth scopes carried by the caller, when the request was
|
|
15
|
+
* authenticated by an OAuth access token (`ctx.principal.kind === "oauth"`).
|
|
16
|
+
* `undefined` for the `user` (first-party) and `system` (stdio/trusted)
|
|
17
|
+
* principals, which carry no scopes. This is the model input the scope gate
|
|
18
|
+
* (`scopeGateAllows`) reads when deciding whether a tool is visible/callable.
|
|
19
|
+
*/
|
|
20
|
+
scopes?: string[];
|
|
21
|
+
ctx: AppContext & Partial<RequestContext>;
|
|
22
|
+
}
|
|
23
|
+
type McpAccessRule = boolean | ((ctx: McpAccessRuleContext) => boolean | Promise<boolean>);
|
|
24
|
+
/**
|
|
25
|
+
* OAuth scope requirement declaration. A single scope string, a list (all
|
|
26
|
+
* required — AND), or `false` to explicitly require no scope (public even to
|
|
27
|
+
* scoped OAuth callers). `undefined` means "fall back to the default mapping".
|
|
28
|
+
*/
|
|
29
|
+
type McpRequiredScopes = string | string[] | false;
|
|
30
|
+
type McpEntityPolicy = boolean | {
|
|
31
|
+
expose?: boolean;
|
|
32
|
+
read?: boolean | McpAccessRule;
|
|
33
|
+
write?: boolean | McpAccessRule;
|
|
34
|
+
delete?: boolean | McpAccessRule;
|
|
35
|
+
operations?: Record<string, boolean | McpAccessRule>;
|
|
36
|
+
/**
|
|
37
|
+
* Scopes an OAuth caller must hold to reach this entity. Declarable at
|
|
38
|
+
* the entity level (applies to every operation) and/or per operation via
|
|
39
|
+
* {@link McpEntityPolicy.operationScopes}. When omitted, the default
|
|
40
|
+
* operation→scope mapping is derived from the operation kind (e.g.
|
|
41
|
+
* `collections:<name>:read`). Resolved by `requiredScopesForOperation`
|
|
42
|
+
* and enforced by the scope gate (`scopeGateAllows`) at both
|
|
43
|
+
* `tools/list` and `tools/call`.
|
|
44
|
+
*/
|
|
45
|
+
requiredScopes?: McpRequiredScopes;
|
|
46
|
+
/**
|
|
47
|
+
* Per-operation scope requirements, keyed by operation name
|
|
48
|
+
* (`list`/`get`/`create`/`update`/`delete`/`invoke`/…). Overrides both
|
|
49
|
+
* {@link McpEntityPolicy.requiredScopes} and the default mapping for the
|
|
50
|
+
* named operation.
|
|
51
|
+
*/
|
|
52
|
+
operationScopes?: Record<string, McpRequiredScopes>;
|
|
53
|
+
fields?: {
|
|
54
|
+
include?: string[];
|
|
55
|
+
exclude?: string[];
|
|
56
|
+
};
|
|
57
|
+
description?: string;
|
|
58
|
+
};
|
|
59
|
+
interface McpCrudDefaults {
|
|
60
|
+
collections?: {
|
|
61
|
+
read?: boolean | McpAccessRule;
|
|
62
|
+
write?: boolean | McpAccessRule;
|
|
63
|
+
delete?: boolean | McpAccessRule;
|
|
64
|
+
};
|
|
65
|
+
globals?: {
|
|
66
|
+
read?: boolean | McpAccessRule;
|
|
67
|
+
write?: boolean | McpAccessRule;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
interface McpCrudConfig {
|
|
71
|
+
defaults?: McpCrudDefaults;
|
|
72
|
+
collections?: Record<string, McpEntityPolicy>;
|
|
73
|
+
globals?: Record<string, McpEntityPolicy>;
|
|
74
|
+
maxLimit?: number;
|
|
75
|
+
}
|
|
76
|
+
interface McpRoutesConfig {
|
|
77
|
+
exposeAnnotated?: boolean;
|
|
78
|
+
routes?: Record<string, McpEntityPolicy>;
|
|
79
|
+
}
|
|
80
|
+
interface McpResourcesConfig {
|
|
81
|
+
schemas?: boolean;
|
|
82
|
+
routes?: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface McpTransportConfig {
|
|
85
|
+
accessMode?: McpAccessMode;
|
|
86
|
+
}
|
|
87
|
+
interface McpHttpConfig extends McpTransportConfig {
|
|
88
|
+
allowedOrigins?: string[];
|
|
89
|
+
allowedHosts?: string[];
|
|
90
|
+
enableJsonResponse?: boolean;
|
|
91
|
+
}
|
|
92
|
+
interface McpStdioConfig extends McpTransportConfig {}
|
|
93
|
+
interface McpConfig {
|
|
94
|
+
name?: string;
|
|
95
|
+
version?: string;
|
|
96
|
+
crud?: McpCrudConfig;
|
|
97
|
+
routes?: McpRoutesConfig;
|
|
98
|
+
resources?: McpResourcesConfig;
|
|
99
|
+
http?: McpHttpConfig;
|
|
100
|
+
stdio?: McpStdioConfig;
|
|
101
|
+
}
|
|
102
|
+
interface McpExecutionOptions {
|
|
103
|
+
transport?: McpTransportKind;
|
|
104
|
+
accessMode?: McpAccessMode;
|
|
105
|
+
ctx?: AppContext & Partial<RequestContext>;
|
|
106
|
+
request?: Request;
|
|
107
|
+
config?: McpConfig;
|
|
108
|
+
}
|
|
109
|
+
interface McpToolHandlerArgs<TInput = unknown> {
|
|
110
|
+
input: TInput;
|
|
111
|
+
ctx: AppContext & Partial<RequestContext>;
|
|
112
|
+
transport: McpTransportKind;
|
|
113
|
+
accessMode: McpAccessMode;
|
|
114
|
+
request?: Request;
|
|
115
|
+
}
|
|
116
|
+
interface McpToolConfig<TInputSchema extends z.ZodTypeAny = z.ZodTypeAny, TOutputSchema extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
117
|
+
title?: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
inputSchema?: TInputSchema;
|
|
120
|
+
outputSchema?: TOutputSchema;
|
|
121
|
+
annotations?: ToolAnnotations;
|
|
122
|
+
access?: McpAccessRule;
|
|
123
|
+
/**
|
|
124
|
+
* Scopes an OAuth caller must hold to reach this custom tool (all required —
|
|
125
|
+
* AND). Custom tools have no default mapping (there is no resource/operation
|
|
126
|
+
* to derive one from), so an omitted value requires no scope. Enforced by the
|
|
127
|
+
* scope gate (`scopeGateAllows`) at both `tools/list` (hidden) and
|
|
128
|
+
* `tools/call` (denied); `system`/`user` callers carry no scopes and skip it.
|
|
129
|
+
*/
|
|
130
|
+
scopes?: McpRequiredScopes;
|
|
131
|
+
_meta?: Record<string, unknown>;
|
|
132
|
+
}
|
|
133
|
+
interface McpToolDefinition<TInputSchema extends z.ZodTypeAny = z.ZodTypeAny, TOutputSchema extends z.ZodTypeAny = z.ZodTypeAny> {
|
|
134
|
+
readonly __brand: "mcpTool";
|
|
135
|
+
readonly name: string;
|
|
136
|
+
readonly config: McpToolConfig<TInputSchema, TOutputSchema>;
|
|
137
|
+
readonly handler: (args: McpToolHandlerArgs<z.infer<TInputSchema>>) => CallToolResult | Promise<CallToolResult>;
|
|
138
|
+
}
|
|
139
|
+
declare module "questpie" {
|
|
140
|
+
interface AppStateConfig {
|
|
141
|
+
mcp?: McpConfig;
|
|
142
|
+
}
|
|
143
|
+
interface ModuleDefinition {
|
|
144
|
+
mcpTools?: Record<string, McpToolDefinition>;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/server/runtime.d.ts
|
|
149
|
+
type QuestpieApp = Parameters<typeof createContextFactory>[0];
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/server/stdio.d.ts
|
|
152
|
+
declare function startStdioServer(app: QuestpieApp, options?: Omit<McpExecutionOptions, "transport">): Promise<_modelcontextprotocol_sdk_server_mcp0.McpServer>;
|
|
153
|
+
//#endregion
|
|
154
|
+
export { McpToolDefinition as _, McpAccessRuleContext as a, McpCrudDefaults as c, McpHttpConfig as d, McpRequiredScopes as f, McpToolConfig as g, McpStdioConfig as h, McpAccessRule as i, McpEntityPolicy as l, McpRoutesConfig as m, QuestpieApp as n, McpConfig as o, McpResourcesConfig as p, McpAccessMode as r, McpCrudConfig as s, startStdioServer as t, McpExecutionOptions as u, McpToolHandlerArgs as v, McpTransportKind as y };
|
package/dist/stdio.d.mts
ADDED
package/dist/stdio.mjs
ADDED