@questpie/mcp 3.28.10 → 3.28.11

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.
@@ -128,7 +128,7 @@ const DEFAULT_MCP_CONFIG = {
128
128
  stdio: { trustedMaintenance: false },
129
129
  execution: {}
130
130
  };
131
- function isPlainObject(value) {
131
+ function isPlainObject$1(value) {
132
132
  return !!value && typeof value === "object" && !Array.isArray(value);
133
133
  }
134
134
  const UNSAFE_CONFIG_KEYS = new Set([
@@ -137,7 +137,7 @@ const UNSAFE_CONFIG_KEYS = new Set([
137
137
  "constructor"
138
138
  ]);
139
139
  function ownDataEntries(value) {
140
- if (!isPlainObject(value)) return [];
140
+ if (!isPlainObject$1(value)) return [];
141
141
  try {
142
142
  return Object.entries(Object.getOwnPropertyDescriptors(value)).filter(([key, descriptor]) => !UNSAFE_CONFIG_KEYS.has(key) && "value" in descriptor).map(([key, descriptor]) => [key, descriptor.value]);
143
143
  } catch {
@@ -146,14 +146,14 @@ function ownDataEntries(value) {
146
146
  }
147
147
  function safeCloneConfigValue(value) {
148
148
  if (Array.isArray(value)) return value.map((entry) => safeCloneConfigValue(entry));
149
- if (!isPlainObject(value)) return value;
149
+ if (!isPlainObject$1(value)) return value;
150
150
  const clone = Object.create(null);
151
151
  for (const [key, entry] of ownDataEntries(value)) clone[key] = safeCloneConfigValue(entry);
152
152
  return clone;
153
153
  }
154
154
  function mergeDeep(base, override) {
155
155
  const out = safeCloneConfigValue(base);
156
- for (const [key, value] of ownDataEntries(override)) if (isPlainObject(value) && isPlainObject(out[key])) out[key] = mergeDeep(out[key], value);
156
+ for (const [key, value] of ownDataEntries(override)) if (isPlainObject$1(value) && isPlainObject$1(out[key])) out[key] = mergeDeep(out[key], value);
157
157
  else if (value !== void 0) out[key] = safeCloneConfigValue(value);
158
158
  return out;
159
159
  }
@@ -1073,11 +1073,123 @@ function toJsonSchema(schema) {
1073
1073
  const compatible = jsonSchemaCompatibleSchema(schema);
1074
1074
  if (!compatible) return void 0;
1075
1075
  try {
1076
- return z.toJSONSchema(compatible, { reused: "ref" });
1076
+ return applyMcpSchemaDiet(z.toJSONSchema(compatible, { reused: "ref" }));
1077
1077
  } catch {
1078
1078
  return;
1079
1079
  }
1080
1080
  }
1081
+ function isPlainObject(value) {
1082
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1083
+ }
1084
+ const SCHEMA_MAP_KEYS = [
1085
+ "properties",
1086
+ "patternProperties",
1087
+ "dependentSchemas",
1088
+ "$defs",
1089
+ "definitions"
1090
+ ];
1091
+ const NEUTRAL_SCHEMA_LIST_KEYS = [
1092
+ "anyOf",
1093
+ "allOf",
1094
+ "prefixItems"
1095
+ ];
1096
+ const NEUTRAL_SCHEMA_KEYS = [
1097
+ "items",
1098
+ "then",
1099
+ "else",
1100
+ "propertyNames",
1101
+ "contains"
1102
+ ];
1103
+ const NEUTRAL_MAYBE_BOOLEAN_SCHEMA_KEYS = [
1104
+ "additionalProperties",
1105
+ "unevaluatedProperties",
1106
+ "unevaluatedItems",
1107
+ "additionalItems"
1108
+ ];
1109
+ /**
1110
+ * Rewrites a draft-07-style tuple (`items: [...]` + `additionalItems`) into
1111
+ * its 2020-12 equivalent (`prefixItems: [...]` + trailing `items`) in place.
1112
+ *
1113
+ * The MCP SDK's own `tools/list` projection (`zod-json-schema-compat.js`,
1114
+ * called with no `target`) emits JSON Schema **draft-07**, not 2020-12 — the
1115
+ * only place that difference is load-bearing for this codebase's schemas is
1116
+ * tuple typing, where draft-07's array-form `items` means something entirely
1117
+ * different in 2020-12 (a single "all items must match" schema). Our own
1118
+ * `toJsonSchema()` below (full zod's `z.toJSONSchema`) already emits native
1119
+ * 2020-12 `prefixItems`, so this is a no-op there. Converting first — for
1120
+ * every tuple anywhere in the tree, not just the root — makes the whole
1121
+ * document genuinely dialect-correct 2020-12 before `applyMcpSchemaDiet`
1122
+ * drops the top-level `$schema` pointer.
1123
+ */
1124
+ function normalizeTupleDialect(node) {
1125
+ if (!Array.isArray(node.items) || node.prefixItems !== void 0) return;
1126
+ const tuple = node.items;
1127
+ const additionalItems = node.additionalItems;
1128
+ delete node.items;
1129
+ delete node.additionalItems;
1130
+ node.prefixItems = tuple;
1131
+ if (additionalItems === false) node.items = false;
1132
+ else if (isPlainObject(additionalItems)) node.items = additionalItems;
1133
+ }
1134
+ /**
1135
+ * Strips wire noise the zod v4 / MCP SDK JSON Schema codecs emit on every
1136
+ * tool schema, without touching example/default/const/enum payloads (those
1137
+ * carry caller data, not schema structure, and must never be walked).
1138
+ *
1139
+ * - Removes the document-level `$schema` dialect pointer (informational only;
1140
+ * `normalizeTupleDialect` above makes the document genuinely 2020-12-shaped
1141
+ * first, so an absent `$schema` is universally, correctly, read as 2020-12).
1142
+ * - Drops `additionalProperties: false`, *except inside a `oneOf`/`not`/`if`
1143
+ * subtree*. Ordinarily this is safe to drop everywhere: the MCP SDK
1144
+ * validates tool arguments against the *original* zod schema at call time
1145
+ * (`McpServer#validateToolInput` in `mcp.js`), never against this projected
1146
+ * JSON Schema, so removing the annotation does not change what a call gets
1147
+ * rejected for. But inside `oneOf`, `additionalProperties: false` can be
1148
+ * the only thing making two branches mutually exclusive (reproduced:
1149
+ * `z.xor` of two `.strict()` objects with disjoint keys — stripping it from
1150
+ * both branches makes a value that should match exactly one branch match
1151
+ * both, which every `oneOf`-aware validator then rejects as ambiguous, even
1152
+ * though the MCP server itself would still accept it). Inside `not`, it can
1153
+ * be the very thing being negated — stripping it *widens* what `not`
1154
+ * excludes, which can turn a previously-schema-valid value into
1155
+ * schema-invalid for any client-side validator. `if` shares the same
1156
+ * "widening changes routing" hazard. `anyOf`/`allOf` don't have this
1157
+ * problem (a widened disjunct can never invalidate an already-valid input),
1158
+ * so only `oneOf`/`not`/`if` propagate the protection flag.
1159
+ * - Drops the `pattern` on a `format: "uuid"` node, keeping `format`. The
1160
+ * pattern is the zod-generated uuid regex that always accompanies
1161
+ * `format: "uuid"` in this codebase's schemas (there is no path that sets
1162
+ * `format: "uuid"` other than zod's own `.uuid()`), and — same reasoning as
1163
+ * `additionalProperties` — the MCP SDK never validates against this
1164
+ * projected schema, so the regex is redundant here. `pattern` on any other
1165
+ * node — a node without `format: "uuid"` — is a user-authored constraint
1166
+ * and is left untouched.
1167
+ */
1168
+ function walkSchemaNode(node, protectAdditionalProperties = false) {
1169
+ if (!isPlainObject(node)) return;
1170
+ normalizeTupleDialect(node);
1171
+ if (node.format === "uuid" && typeof node.pattern === "string") delete node.pattern;
1172
+ if (!protectAdditionalProperties && node.additionalProperties === false) delete node.additionalProperties;
1173
+ for (const key of SCHEMA_MAP_KEYS) {
1174
+ const map = node[key];
1175
+ if (isPlainObject(map)) for (const value of Object.values(map)) walkSchemaNode(value, protectAdditionalProperties);
1176
+ }
1177
+ for (const key of NEUTRAL_SCHEMA_LIST_KEYS) {
1178
+ const list = node[key];
1179
+ if (Array.isArray(list)) for (const item of list) walkSchemaNode(item, protectAdditionalProperties);
1180
+ }
1181
+ for (const key of NEUTRAL_SCHEMA_KEYS) walkSchemaNode(node[key], protectAdditionalProperties);
1182
+ for (const key of NEUTRAL_MAYBE_BOOLEAN_SCHEMA_KEYS) walkSchemaNode(node[key], protectAdditionalProperties);
1183
+ if (Array.isArray(node.oneOf)) for (const item of node.oneOf) walkSchemaNode(item, true);
1184
+ walkSchemaNode(node.not, true);
1185
+ walkSchemaNode(node.if, true);
1186
+ }
1187
+ function applyMcpSchemaDiet(schema) {
1188
+ if (!isPlainObject(schema)) return schema;
1189
+ delete schema.$schema;
1190
+ walkSchemaNode(schema);
1191
+ return schema;
1192
+ }
1081
1193
  function toToolInputJsonSchema(schema) {
1082
1194
  const parsed = ToolSchema.shape.inputSchema.safeParse(toJsonSchema(schema));
1083
1195
  return parsed.success ? parsed.data : {
@@ -2385,6 +2497,46 @@ async function registerRouteTools(server, scope, catalog) {
2385
2497
 
2386
2498
  //#endregion
2387
2499
  //#region src/server/create-server.ts
2500
+ function isProductionEnvironment() {
2501
+ return typeof process !== "undefined" && process.env?.NODE_ENV === "production";
2502
+ }
2503
+ /**
2504
+ * Reports a schema-diet install failure loudly instead of either crashing
2505
+ * every session (a renamed/removed SDK internal would otherwise throw deep
2506
+ * inside request handling on the very first `tools/list` call) or failing
2507
+ * silently (shipping an undieted catalogue with nobody told why). Always
2508
+ * logs; only throws outside production, so a CI/dev run surfaces the break
2509
+ * immediately while a live server degrades to the SDK's own undieted
2510
+ * `tools/list` response.
2511
+ */
2512
+ function reportSchemaDietInstallFailure(message) {
2513
+ const prefixed = `@questpie/mcp: ${message}`;
2514
+ console.error(prefixed);
2515
+ if (!isProductionEnvironment()) throw new Error(prefixed);
2516
+ }
2517
+ function installSchemaDietListToolsHandler(server) {
2518
+ const handlers = Reflect.get(server.server, "_requestHandlers");
2519
+ if (!(handlers instanceof Map)) {
2520
+ reportSchemaDietInstallFailure("could not install the tools/list schema diet — McpServer#server's internal request-handler map is missing or not a Map (the MCP SDK's internals likely changed shape). Tools will be listed without the diet applied.");
2521
+ return;
2522
+ }
2523
+ const defaultHandler = handlers.get("tools/list");
2524
+ if (!defaultHandler) {
2525
+ reportSchemaDietInstallFailure("could not install the tools/list schema diet — no default tools/list handler was registered even though at least one tool exists. Tools will be listed without the diet applied.");
2526
+ return;
2527
+ }
2528
+ server.server.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
2529
+ const result = await defaultHandler(request, extra);
2530
+ return {
2531
+ ...result,
2532
+ tools: result.tools.map((tool) => ({
2533
+ ...tool,
2534
+ inputSchema: applyMcpSchemaDiet(tool.inputSchema),
2535
+ ...tool.outputSchema ? { outputSchema: applyMcpSchemaDiet(tool.outputSchema) } : {}
2536
+ }))
2537
+ };
2538
+ });
2539
+ }
2388
2540
  async function createServer(app, options, workload) {
2389
2541
  const release = workload ? createWorkloadMcpRelease(app, options.config) : options.config ? createIsolatedMcpRelease(app, options.config) : getAppMcpRelease(app);
2390
2542
  const { config, catalog } = release;
@@ -2409,6 +2561,7 @@ async function createServer(app, options, workload) {
2409
2561
  await registerCustomTools(server, scope, catalog);
2410
2562
  if (workload) server.server.setRequestHandler(ListToolsRequestSchema, (_request, extra) => listWorkloadTools(workload, release.execution, extra));
2411
2563
  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
+ else installSchemaDietListToolsHandler(server);
2412
2565
  return server;
2413
2566
  }
2414
2567
  function resolveStdioAccessMode(options, trustedMaintenance) {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { S as scopesFromContext, _ as resolveMcpCatalog, a as customToolWorkloadIdentity, b as requiredScopesForOperation, 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 defaultOperationScope, w as mcpTool, x as scopeGateAllows, y as normalizeRequiredScopes } from "./create-server-DvumhdI_.mjs";
2
- import { n as mcp_default, t as mcpModule } from "./mcp-uf7du4OS.mjs";
1
+ import { S as scopesFromContext, _ as resolveMcpCatalog, a as customToolWorkloadIdentity, b as requiredScopesForOperation, 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 defaultOperationScope, w as mcpTool, x as scopeGateAllows, y as normalizeRequiredScopes } from "./create-server-5Eo4QP3q.mjs";
2
+ import { n as mcp_default, t as mcpModule } from "./mcp-i0Kqu_bB.mjs";
3
3
  import { t as mcpPlugin } from "./plugin-BZQe1pji.mjs";
4
- import { t as startStdioServer } from "./stdio-HmbmENQN.mjs";
4
+ import { t as startStdioServer } from "./stdio-37bSPka9.mjs";
5
5
  import { randomUUID } from "node:crypto";
6
6
 
7
7
  //#region src/server/workload-tool-port.ts
@@ -1,4 +1,4 @@
1
- import { C as validateMcpPrincipal, d as getAppMcpRelease, m as assertBoundedMcpValue, t as createMcpServer } from "./create-server-DvumhdI_.mjs";
1
+ import { C as validateMcpPrincipal, d as getAppMcpRelease, m as assertBoundedMcpValue, t as createMcpServer } from "./create-server-5Eo4QP3q.mjs";
2
2
  import { t as mcpPlugin } from "./plugin-BZQe1pji.mjs";
3
3
  import { route } from "questpie";
4
4
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
@@ -1,3 +1,3 @@
1
- import { t as mcpModule } from "../mcp-uf7du4OS.mjs";
1
+ import { t as mcpModule } from "../mcp-i0Kqu_bB.mjs";
2
2
 
3
3
  export { mcpModule };
@@ -1,4 +1,4 @@
1
- import { t as createMcpServer } from "./create-server-DvumhdI_.mjs";
1
+ import { t as createMcpServer } from "./create-server-5Eo4QP3q.mjs";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
 
4
4
  //#region src/server/stdio.ts
package/dist/stdio.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as startStdioServer } from "./stdio-HmbmENQN.mjs";
1
+ import { t as startStdioServer } from "./stdio-37bSPka9.mjs";
2
2
 
3
3
  export { startStdioServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questpie/mcp",
3
- "version": "3.28.10",
3
+ "version": "3.28.11",
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.28.10",
73
+ "questpie": "^3.28.11",
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.28.10"
83
+ "questpie": "^3.28.11"
84
84
  }
85
85
  }