@clipform/mcp-server 3.0.0 → 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.
@@ -24,4 +24,4 @@ export {
24
24
  getMcpAuth,
25
25
  runWithMcpTool
26
26
  };
27
- //# sourceMappingURL=chunk-HZMMOSEM.js.map
27
+ //# sourceMappingURL=chunk-A5CFGL27.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/auth-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\n\n/**\n * Identity of the end-user a given MCP request is acting on behalf of.\n *\n * Populated by the in-process host (apps/api) after validating an OAuth\n * Bearer on /mcp. The MCP tool layer reads it to decide which workspace\n * forms should land in and which plan tier gates apply.\n *\n * NOT populated when the server runs as a local stdio process (npx) - in\n * that mode tools fall back to edit-token based auth.\n */\nexport interface McpAuthContext {\n user_id: string;\n workspace_id: string;\n scopes: string[];\n tool_name?: string;\n api_key?: string;\n // Set when the human behind this tool call is an anonymous Supabase\n // session (apps/api's app-chat lane, #2983/#2984) - api-client.ts's\n // getAuthHeaders reads it to mark the outgoing bot-key HTTP request so the\n // API can propagate the restriction even though the request itself\n // authenticates with an API key, not a JWT.\n is_anonymous?: boolean;\n // Set on the OAuth branch of apps/api's mcp/handler.ts when this request's\n // bot API key was resolved on behalf of an OAuth-connected user (#3716) -\n // api-client.ts's getAuthHeaders reads it to mark the outgoing request so\n // /v1/me can report auth_mode \"oauth\" instead of the generic \"api_key\" the\n // bot-key lane would otherwise get, even though the request itself\n // authenticates with an API key, not an OAuth token.\n auth_via?: \"oauth\";\n // #3330 review fix (blocker 1): an HMAC proof (apps/api's mcp-mint-proof.ts)\n // that THIS exact request's #1415 anon lane freshly minted a brand-new anon\n // identity for `workspace_id` - set only on the mint branch of\n // resolveAnonymousMcpAuth, never when auth was resolved via an inbound\n // form_id (even the caller's own). Lets create-form.ts safely offer a\n // same-UID claim link without ever deriving \"who to bind it to\" from a\n // body-supplied id, which would let a batch smuggling another tenant's\n // form_id alongside a create_form call mint a claim token for THAT\n // tenant's identity instead of the caller's own.\n mcp_mint_proof?: string;\n // #3822: set ONLY on the OAuth branch of apps/api's mcp/handler.ts. Lets a\n // single tool call target another workspace the connected user belongs to\n // (per-call, never per-request - see the #3330 note in the package\n // CLAUDE.md). The host re-checks membership on every call and returns that\n // workspace's own bot key, or null to mean \"act in the default workspace\n // unchanged\" (target is the default, or a form_id the user can't reach -\n // the API then answers exactly as it did before targeting existed). Anon + stdio contexts never carry it, so they\n // stay pinned to one workspace.\n resolveWorkspace?: (target: {\n workspace_id?: string;\n form_id?: string;\n }) => Promise<{ workspace_id: string; workspace_name: string; api_key: string } | null>;\n // #3822: the workspaces the connected user may target (same set\n // resolveWorkspace accepts). OAuth lane only.\n listWorkspaces?: () => Promise<{ id: string; name: string }[]>;\n // #3822: the workspace picked at authorize time - what a call with no\n // `workspace_id` acts in. OAuth lane only (workspace_id above is swapped\n // per call when a target is resolved, this never is).\n default_workspace_id?: string;\n}\n\n/**\n * Marker header the in-process MCP tool layer sets on its outgoing API\n * requests (api-client.ts) when `is_anonymous` is true, so an anonymous\n * app-chat session can't launder itself into an unrestricted API-key caller\n * by asking the bot to publish/mint credentials on its behalf. apps/api's\n * authMiddleware / workspaceAccessMiddleware / requireWorkspaceWriteAuth\n * read this on the API-key path and set isAnonymous accordingly - trusting\n * it is safe because it can only RESTRICT a caller: a real integration that\n * sets this header on its own API-key requests only denies itself, it can\n * never use it to gain anonymous-JWT privileges it doesn't already have.\n */\nexport const MCP_ANONYMOUS_HEADER = \"x-clipform-anonymous\";\n\n/**\n * Marker header the in-process MCP tool layer sets on its outgoing API\n * requests (api-client.ts) when `auth_via` is \"oauth\", so /v1/me can report\n * `auth_mode: \"oauth\"` for an OAuth-connected user instead of the generic\n * \"api_key\" the bot-key lane would otherwise get (#3716). Label-only: it\n * changes nothing but the reported string. Trusting it from the wire is\n * safe because a direct API-key caller who sets this header on their own\n * request only mislabels their own whoami output - they gain no privilege,\n * exactly the same reasoning as MCP_ANONYMOUS_HEADER above (except that\n * header can only restrict, and this one can only relabel).\n */\nexport const MCP_AUTH_VIA_HEADER = \"x-clipform-auth-via\";\n\n/**\n * Carries `mcp_mint_proof` on the same outgoing bot-key request. Unlike\n * MCP_ANONYMOUS_HEADER (a boolean that can only ever RESTRICT), this header's\n * VALUE matters - it names an identity a claim token gets bound to - so its\n * value is HMAC-signed (mcp-mint-proof.ts) rather than trusted raw: a direct\n * external caller reaching `POST /v1/forms` with any valid workspace API key\n * could otherwise hand-set an unsigned \"mint uid\" header to an arbitrary\n * victim's uid themselves (#3330 review).\n */\nexport const MCP_MINT_PROOF_HEADER = \"x-clipform-mcp-mint-proof\";\n\nconst storage = new AsyncLocalStorage<McpAuthContext>();\n\nexport function runWithMcpAuth<T>(ctx: McpAuthContext, fn: () => Promise<T> | T): Promise<T> | T {\n return storage.run(ctx, fn);\n}\n\nexport function getMcpAuth(): McpAuthContext | undefined {\n return storage.getStore();\n}\n\nexport function runWithMcpTool<T>(toolName: string, fn: () => Promise<T> | T): Promise<T> | T {\n const parent = storage.getStore();\n if (!parent) return fn();\n return storage.run({ ...parent, tool_name: toolName }, fn);\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;AAyE3B,IAAM,uBAAuB;AAa7B,IAAM,sBAAsB;AAW5B,IAAM,wBAAwB;AAErC,IAAM,UAAU,IAAI,kBAAkC;AAE/C,SAAS,eAAkB,KAAqB,IAA0C;AAC/F,SAAO,QAAQ,IAAI,KAAK,EAAE;AAC5B;AAEO,SAAS,aAAyC;AACvD,SAAO,QAAQ,SAAS;AAC1B;AAEO,SAAS,eAAkB,UAAkB,IAA0C;AAC5F,QAAM,SAAS,QAAQ,SAAS;AAChC,MAAI,CAAC,OAAQ,QAAO,GAAG;AACvB,SAAO,QAAQ,IAAI,EAAE,GAAG,QAAQ,WAAW,SAAS,GAAG,EAAE;AAC3D;","names":[]}
@@ -5,7 +5,7 @@ import {
5
5
  FULL_CAPS,
6
6
  callApi,
7
7
  getSessionContext
8
- } from "./chunk-KNOCYZHG.js";
8
+ } from "./chunk-SEZ37YSH.js";
9
9
 
10
10
  // src/lib/guides.ts
11
11
  var guidesPromise = null;
@@ -143,4 +143,4 @@ export {
143
143
  getGuideUri,
144
144
  registerResources
145
145
  };
146
- //# sourceMappingURL=chunk-EXV5M7YV.js.map
146
+ //# sourceMappingURL=chunk-ELK26KWM.js.map
@@ -3,7 +3,7 @@ import {
3
3
  MCP_AUTH_VIA_HEADER,
4
4
  MCP_MINT_PROOF_HEADER,
5
5
  getMcpAuth
6
- } from "./chunk-HZMMOSEM.js";
6
+ } from "./chunk-A5CFGL27.js";
7
7
  import {
8
8
  isAuthFault,
9
9
  isServerFault,
@@ -37,9 +37,10 @@ var FEATURES = {
37
37
  consent_documents: {
38
38
  name: "Agreement documents",
39
39
  description: "Attach a PDF agreement to a consent item, read inline in the viewer with scroll-to-accept",
40
- enabled: false,
41
- status: "beta",
42
- category: "builder"
40
+ enabled: true,
41
+ status: "live",
42
+ category: "builder",
43
+ wentLiveAt: "2026-10-02"
43
44
  },
44
45
  node_logic: {
45
46
  name: "Node routing logic",
@@ -156,8 +157,8 @@ var FEATURES = {
156
157
  // Widened (#4338): the checkbox items are typed 'consent' (required,
157
158
  // blocks submission until checked) or 'opt_in' (optional) - see
158
159
  // CONSENT_TYPES in node-types.js and the viewer's Contact.tsx. Both are
159
- // plain text checkboxes available to every creator with no gate; only
160
- // attaching a PDF agreement document is gated (consent_documents, beta).
160
+ // plain text checkboxes available to every creator with no gate. Attaching
161
+ // a PDF agreement document is the separate consent_documents feature (live).
161
162
  description: "Structured fields (name, email, phone, company) with consent and opt-in checkboxes",
162
163
  enabled: true,
163
164
  status: "live",
@@ -794,6 +795,7 @@ var NODE_TYPES = {
794
795
  // Never appears in a completed submission's answers (system node, no
795
796
  // output_schema) - false is a no-op default, set for guard-test coverage.
796
797
  notify_inline: false,
798
+ owner_scoped_config_keys: [],
797
799
  default_config: null,
798
800
  config_schema: null,
799
801
  response_schema: null,
@@ -820,6 +822,7 @@ var NODE_TYPES = {
820
822
  show_in_results: true,
821
823
  // Short selected-option label - safe to render inline in a notification (#4154).
822
824
  notify_inline: true,
825
+ owner_scoped_config_keys: [],
823
826
  default_config: {
824
827
  selection_mode: "single",
825
828
  choice: { enable_branching: false, record_scores: false },
@@ -931,6 +934,7 @@ var NODE_TYPES = {
931
934
  // still swaps a recorded audio/video answer for a typed placeholder
932
935
  // based on the answer's own media type, not this flag.
933
936
  notify_inline: true,
937
+ owner_scoped_config_keys: [],
934
938
  default_config: {
935
939
  formats: [
936
940
  { order: 0, format: "text" },
@@ -1070,6 +1074,7 @@ var NODE_TYPES = {
1070
1074
  show_in_results: true,
1071
1075
  // A bare numeric value - short enough to render inline (#4154).
1072
1076
  notify_inline: true,
1077
+ owner_scoped_config_keys: [],
1073
1078
  default_config: null,
1074
1079
  config_schema: {
1075
1080
  type: "object",
@@ -1107,6 +1112,13 @@ var NODE_TYPES = {
1107
1112
  // Structured provider data (calendly/google_meet), not a flat value -
1108
1113
  // a notification renders a typed placeholder instead (#4154).
1109
1114
  notify_inline: false,
1115
+ // Object granularity, not a dotted path (#4618): every property under
1116
+ // these provider objects (scheduling_url, event_type_uri, calendar_id) is
1117
+ // the source workspace's own calendar, so save_form strips the whole
1118
+ // object on cross-workspace copy. google_meet is included even though it
1119
+ // is absent from the publish gate below (pre-existing drift, not widened
1120
+ // here) because calendar_id is owner-scoped regardless.
1121
+ owner_scoped_config_keys: ["calendly", "calcom", "google_meet"],
1110
1122
  default_config: null,
1111
1123
  config_schema: {
1112
1124
  type: "object",
@@ -1188,6 +1200,7 @@ var NODE_TYPES = {
1188
1200
  // Key contact fields (first name, email) are the whole point of a
1189
1201
  // notification for this node type - always inline (#4154).
1190
1202
  notify_inline: true,
1203
+ owner_scoped_config_keys: [],
1191
1204
  default_config: {
1192
1205
  fields: [
1193
1206
  { id: "first_name", type: "first_name", label: "First Name", enabled: true, required: true },
@@ -1282,7 +1295,17 @@ var NODE_TYPES = {
1282
1295
  // document version they saw, and when they accepted.
1283
1296
  reached_end: { type: "boolean" },
1284
1297
  document_version: { type: "string" },
1285
- accepted_at: { type: "string" }
1298
+ accepted_at: { type: "string" },
1299
+ // Server-stamped on write (#4552) when the node's current document
1300
+ // matches document_version; any client-supplied value is discarded.
1301
+ document: {
1302
+ type: "object",
1303
+ properties: {
1304
+ storage_path: { type: "string" },
1305
+ url: { type: "string" },
1306
+ filename: { type: "string" }
1307
+ }
1308
+ }
1286
1309
  }
1287
1310
  },
1288
1311
  description: "Consent checkbox entries"
@@ -1321,7 +1344,15 @@ var NODE_TYPES = {
1321
1344
  // Scrollwrap audit trail (#2760), emitted only for document-gated items.
1322
1345
  reached_end: { type: "boolean" },
1323
1346
  document_version: { type: "string" },
1324
- accepted_at: { type: "string" }
1347
+ accepted_at: { type: "string" },
1348
+ // The exact agreement file accepted (#4552). storage_path is not emitted.
1349
+ document: {
1350
+ type: "object",
1351
+ properties: {
1352
+ url: { type: "string" },
1353
+ filename: { type: "string" }
1354
+ }
1355
+ }
1325
1356
  }
1326
1357
  }
1327
1358
  }
@@ -1346,6 +1377,7 @@ var NODE_TYPES = {
1346
1377
  show_in_results: true,
1347
1378
  // Files, not a flat value - a notification renders a typed placeholder (#4154).
1348
1379
  notify_inline: false,
1380
+ owner_scoped_config_keys: [],
1349
1381
  default_config: null,
1350
1382
  config_schema: {
1351
1383
  type: "object",
@@ -1437,6 +1469,7 @@ var NODE_TYPES = {
1437
1469
  show_in_results: true,
1438
1470
  // An image, not a flat value - a notification renders a typed placeholder (#4154).
1439
1471
  notify_inline: false,
1472
+ owner_scoped_config_keys: [],
1440
1473
  default_config: {
1441
1474
  paper_color: "#FAF9F6",
1442
1475
  stroke_colors: ["#1F2937", "#EF4444", "#3B82F6", "#22C55E", "#FACC15"],
@@ -1610,6 +1643,7 @@ var NODE_TYPES = {
1610
1643
  // A captured photo/video, not a flat value - a notification renders a
1611
1644
  // typed placeholder (#4154).
1612
1645
  notify_inline: false,
1646
+ owner_scoped_config_keys: [],
1613
1647
  default_config: null,
1614
1648
  config_schema: {
1615
1649
  type: "object",
@@ -1721,6 +1755,7 @@ var NODE_TYPES = {
1721
1755
  // notification, same as the other control nodes (redirect, link_list,
1722
1756
  // end_screen). Required by the #4154 notify_inline invariant.
1723
1757
  notify_inline: false,
1758
+ owner_scoped_config_keys: [],
1724
1759
  default_config: {
1725
1760
  options: [
1726
1761
  { content: "Branch 1" },
@@ -1747,13 +1782,14 @@ var NODE_TYPES = {
1747
1782
  supports_media: false,
1748
1783
  is_system: false,
1749
1784
  show_in_results: true,
1750
- // Config keys that reference a workspace-scoped connected account (Stripe/etc
1751
- // via workspace_integrations). The SoT for save_form to strip on cross-workspace
1752
- // copy (#2064 part 2 - migration, not yet wired). Standing convention for any
1753
- // future OAuth/connected-account node (booking, shopify), not a payment special
1754
- // case - the publish gate's own showcase waiver (graph-validation.js) still
1755
- // names workspace_integration_id directly since it's the only per-type check today.
1756
- connection_config_keys: ["workspace_integration_id"],
1785
+ // Config keys that are owner-scoped to the authoring workspace - a connected
1786
+ // account (Stripe/etc via workspace_integrations), the owner's own uploaded
1787
+ // files, or a calendar link - and so must NOT survive a cross-workspace copy
1788
+ // (#4619/#4617/#4618/#4620 generalised this from the payment-only
1789
+ // connection_config_keys). The SoT for save_form's strip. The publish gate's
1790
+ // own showcase waiver (graph-validation.js) still names workspace_integration_id
1791
+ // directly since it's the only per-type check today.
1792
+ owner_scoped_config_keys: ["workspace_integration_id"],
1757
1793
  // A payment confirmation line is the whole point of a notification for
1758
1794
  // this node type - always inline (#4154).
1759
1795
  notify_inline: true,
@@ -1903,6 +1939,7 @@ var NODE_TYPES = {
1903
1939
  show_in_results: true,
1904
1940
  // Short selected-option label - safe to render inline (#4154).
1905
1941
  notify_inline: true,
1942
+ owner_scoped_config_keys: [],
1906
1943
  default_config: {
1907
1944
  choice: { enable_branching: false },
1908
1945
  options: [
@@ -1967,6 +2004,7 @@ var NODE_TYPES = {
1967
2004
  show_in_results: false,
1968
2005
  // Short selected-option label - safe to render inline (#4154).
1969
2006
  notify_inline: true,
2007
+ owner_scoped_config_keys: [],
1970
2008
  default_config: { options: [{ content: "Continue" }] },
1971
2009
  config_schema: {
1972
2010
  type: "object",
@@ -2008,6 +2046,7 @@ var NODE_TYPES = {
2008
2046
  // Terminal node - never appears in a completed submission's answers
2009
2047
  // (filtered by NON_COUNTABLE_FILTER); false is a no-op default.
2010
2048
  notify_inline: false,
2049
+ owner_scoped_config_keys: [],
2011
2050
  default_config: { url: "", auto_redirect: true },
2012
2051
  config_schema: {
2013
2052
  type: "object",
@@ -2041,6 +2080,7 @@ var NODE_TYPES = {
2041
2080
  // Terminal node - never appears in a completed submission's answers
2042
2081
  // (filtered by NON_COUNTABLE_FILTER); false is a no-op default.
2043
2082
  notify_inline: false,
2083
+ owner_scoped_config_keys: [],
2044
2084
  default_config: { links: [{ id: "default", url: "", title: "", description: "", order: 0 }], auto_redirect: false },
2045
2085
  config_schema: {
2046
2086
  type: "object",
@@ -2088,6 +2128,10 @@ var NODE_TYPES = {
2088
2128
  show_in_results: true,
2089
2129
  // Files, not a flat value - a notification renders a typed placeholder (#4154).
2090
2130
  notify_inline: false,
2131
+ // The owner's own uploaded files (#4617) - stripped on cross-workspace
2132
+ // copy so a forked node can't keep serving the source workspace's files;
2133
+ // the publish gate's "No files uploaded" check then forces a fresh upload.
2134
+ owner_scoped_config_keys: ["files"],
2091
2135
  default_config: null,
2092
2136
  config_schema: {
2093
2137
  type: "object",
@@ -2170,6 +2214,7 @@ var NODE_TYPES = {
2170
2214
  show_in_results: false,
2171
2215
  // No response_schema - never carries a flat answer value; false is a no-op default.
2172
2216
  notify_inline: false,
2217
+ owner_scoped_config_keys: [],
2173
2218
  default_config: { provider: "shopify", cta_mode: "checkout", source_mode: "manual" },
2174
2219
  config_schema: {
2175
2220
  type: "object",
@@ -2241,6 +2286,7 @@ var NODE_TYPES = {
2241
2286
  // Terminal node - never appears in a completed submission's answers
2242
2287
  // (filtered by NON_COUNTABLE_FILTER); false is a no-op default.
2243
2288
  notify_inline: false,
2289
+ owner_scoped_config_keys: [],
2244
2290
  default_config: {
2245
2291
  title: "Thank you!",
2246
2292
  message: "Your response has been submitted."
@@ -2303,8 +2349,8 @@ for (const [type, def] of Object.entries(NODE_TYPES)) {
2303
2349
  var NODE_TYPES_LIST = Object.entries(NODE_TYPES).map(([type, def]) => ({ type, ...def })).sort((a, b) => a.sort_order - b.sort_order);
2304
2350
  var ACTIVE_NODE_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.is_active && !def.is_system).map(([type]) => type);
2305
2351
  var NODE_TYPE_KEYS = Object.keys(NODE_TYPES);
2306
- var CONNECTION_CONFIG_KEYS = Object.fromEntries(
2307
- Object.entries(NODE_TYPES).filter(([, def]) => Array.isArray(def.connection_config_keys) && def.connection_config_keys.length > 0).map(([type, def]) => [type, def.connection_config_keys])
2352
+ var OWNER_SCOPED_CONFIG_KEYS = Object.fromEntries(
2353
+ Object.entries(NODE_TYPES).filter(([, def]) => Array.isArray(def.owner_scoped_config_keys) && def.owner_scoped_config_keys.length > 0).map(([type, def]) => [type, def.owner_scoped_config_keys])
2308
2354
  );
2309
2355
  var RESPONSE_SCHEMAS = Object.fromEntries(
2310
2356
  Object.entries(NODE_TYPES).map(([k, v]) => [k, v.response_schema])
@@ -2593,7 +2639,6 @@ var HELP_TOPICS = {
2593
2639
  emailShare: { path: "/help/sharing", anchor: "share-by-email" },
2594
2640
  publicResults: { path: "/help/public-results", anchor: null },
2595
2641
  trash: { path: "/help/managing-forms", anchor: "trash" },
2596
- agreementVersions: { path: "/help/responses", anchor: "agreement-versions" },
2597
2642
  customDomains: { path: "/help/domains", anchor: null },
2598
2643
  customPath: { path: "/help/links", anchor: "custom-path-on-your-domain" },
2599
2644
  // Per-node-type deep-links into help/node-types.mdx (#3963). Keyed
@@ -2750,6 +2795,7 @@ var BUSINESS = {
2750
2795
  };
2751
2796
  var MEDIA_DIRECT_MAX_BYTES = 20 * 1024 * 1024;
2752
2797
  var MEDIA_IMAGE_MAX_BYTES = 10 * 1024 * 1024;
2798
+ var MEDIA_DOCUMENT_MAX_BYTES = 20 * 1024 * 1024;
2753
2799
  var SUPABASE_STORAGE_MAX_BYTES = 200 * 1024 * 1024;
2754
2800
  var VIDEO_COMPRESS_FLOOR_BYTES = 1 * 1024 * 1024;
2755
2801
  var HELP_LINKS = buildHelpLinks(BUSINESS.urls.docs);
@@ -3010,6 +3056,19 @@ var MCP_TOOL_TOOLSETS = {
3010
3056
  clipform_list_assets: "media",
3011
3057
  clipform_list_video_templates: "media"
3012
3058
  };
3059
+ var MCP_TOOL_SECURITY_SCHEMES = Object.freeze([
3060
+ Object.freeze({ type: "noauth" }),
3061
+ Object.freeze({ type: "oauth2", scopes: ["mcp"] })
3062
+ ]);
3063
+ var MCP_TOOL_SECURITY = Object.freeze(
3064
+ Object.fromEntries(
3065
+ Object.keys(MCP_TOOL_TOOLSETS).map((name) => [name, MCP_TOOL_SECURITY_SCHEMES])
3066
+ )
3067
+ );
3068
+ var MCP_ANON_PUBLISH_GATE = Object.freeze({
3069
+ tool: "clipform_update_form",
3070
+ argument: "is_live"
3071
+ });
3013
3072
 
3014
3073
  // src/lib/caps.ts
3015
3074
  var FULL_CAPS = Object.freeze({ media: true, research: true });
@@ -3117,10 +3176,14 @@ async function callApi(path, options = {}) {
3117
3176
  api_path: `${prefix}${path}`
3118
3177
  });
3119
3178
  }
3179
+ const details = data.details;
3180
+ const reason = details && typeof details.reason === "string" ? details.reason : void 0;
3120
3181
  return {
3121
3182
  ok: false,
3122
3183
  status: response.status,
3123
- error: errorMsg
3184
+ error: errorMsg,
3185
+ code: errorCode,
3186
+ reason
3124
3187
  };
3125
3188
  }
3126
3189
  return { ok: true, data };
@@ -3139,12 +3202,28 @@ function recoveryHint(status, path) {
3139
3202
  }
3140
3203
  return void 0;
3141
3204
  }
3142
- function errorResult(message) {
3205
+ function errorResult(message, meta) {
3143
3206
  return {
3144
3207
  content: [{ type: "text", text: message }],
3145
- isError: true
3208
+ isError: true,
3209
+ ...meta ? { _meta: meta } : {}
3146
3210
  };
3147
3211
  }
3212
+ var PROTECTED_RESOURCE_METADATA_URL = `${BUSINESS.urls.mcp}/.well-known/oauth-protected-resource`;
3213
+ function escapeChallengeValue(value) {
3214
+ return value.replace(/[\r\n]/g, "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3215
+ }
3216
+ function isAnonymousAccountRequired(result) {
3217
+ return result.status === 403 && result.code === "FORBIDDEN" && result.reason === "anonymous_not_allowed";
3218
+ }
3219
+ function authChallengeMeta(errorCode, description) {
3220
+ const challenge = [
3221
+ `Bearer error="${escapeChallengeValue(errorCode)}"`,
3222
+ `error_description="${escapeChallengeValue(description)}"`,
3223
+ `resource_metadata="${PROTECTED_RESOURCE_METADATA_URL}"`
3224
+ ].join(", ");
3225
+ return { "mcp/www_authenticate": [challenge] };
3226
+ }
3148
3227
  function textResult(text) {
3149
3228
  return {
3150
3229
  content: [{ type: "text", text }]
@@ -3226,13 +3305,17 @@ export {
3226
3305
  exposedCompositionIds,
3227
3306
  MCP_TOOL_TIERS,
3228
3307
  MCP_TOOL_TOOLSETS,
3308
+ MCP_TOOL_SECURITY,
3309
+ MCP_ANON_PUBLISH_GATE,
3229
3310
  FULL_CAPS,
3230
3311
  capsFromToolsets,
3231
3312
  callApi,
3232
3313
  errorResult,
3314
+ isAnonymousAccountRequired,
3315
+ authChallengeMeta,
3233
3316
  textResult,
3234
3317
  structuredResult,
3235
3318
  getSessionContextWithAuth,
3236
3319
  getSessionContext
3237
3320
  };
3238
- //# sourceMappingURL=chunk-KNOCYZHG.js.map
3321
+ //# sourceMappingURL=chunk-SEZ37YSH.js.map