@clipform/mcp-server 4.2.5 → 4.2.7
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 +1 -1
- package/dist/api-client.js +2 -2
- package/dist/auth-context.d.ts +1 -0
- package/dist/auth-context.js +1 -1
- package/dist/{chunk-ECG7HBWZ.js → chunk-75X4S4LA.js} +27 -16
- package/dist/chunk-75X4S4LA.js.map +1 -0
- package/dist/{chunk-VPF6MLKM.js → chunk-BV3KA6HC.js} +1 -1
- package/dist/chunk-BV3KA6HC.js.map +1 -0
- package/dist/{chunk-EN5TEETY.js → chunk-K67KW7GF.js} +16 -15
- package/dist/{chunk-EN5TEETY.js.map → chunk-K67KW7GF.js.map} +1 -1
- package/dist/{chunk-63PX3SVJ.js → chunk-OVLNACRG.js} +4 -4
- package/dist/{chunk-BL7T3UBS.js → chunk-RMFF42S5.js} +3 -3
- package/dist/{chunk-GY5QLQOU.js → chunk-UMO47NOJ.js} +45 -14
- package/dist/chunk-UMO47NOJ.js.map +1 -0
- package/dist/index.js +6 -6
- package/dist/prompts.js +4 -4
- package/dist/resources.js +4 -4
- package/dist/server.js +6 -6
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/chunk-ECG7HBWZ.js.map +0 -1
- package/dist/chunk-GY5QLQOU.js.map +0 -1
- package/dist/chunk-VPF6MLKM.js.map +0 -1
- /package/dist/{chunk-63PX3SVJ.js.map → chunk-OVLNACRG.js.map} +0 -0
- /package/dist/{chunk-BL7T3UBS.js.map → chunk-RMFF42S5.js.map} +0 -0
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Your MCP client lists these automatically on connect (via `tools/list`). Full re
|
|
|
75
75
|
| `clipform_complete_media_upload` | Confirm a signed-PUT still image upload finished, after PUTting the bytes to the upload_url returned by clipform_upload_media_asset. |
|
|
76
76
|
| `clipform_attach_node_media` | Attach an existing workspace media asset (from clipform_upload_media_asset) to one or more nodes (max 10). |
|
|
77
77
|
| `clipform_get_node_media` | Get the media attached to a node, including processing status. |
|
|
78
|
-
| `clipform_delete_node_media` | Remove media from a node. |
|
|
78
|
+
| `clipform_delete_node_media` | Remove media from a node by unlinking it; the media asset and its files stay in the workspace library and keep working on any other node or live form that uses them. |
|
|
79
79
|
| `clipform_set_logic` | Set the next node for one or more nodes, wiring a linear route (A to B to C). |
|
|
80
80
|
| `clipform_log_generation` | Internal audit step run by a form-generation workflow. |
|
|
81
81
|
| `clipform_search_news` | Search recent news articles from NewsAPI and The Guardian and return them as structured results. |
|
package/dist/api-client.js
CHANGED
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
recoveryHint,
|
|
7
7
|
structuredResult,
|
|
8
8
|
textResult
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-UMO47NOJ.js";
|
|
10
|
+
import "./chunk-BV3KA6HC.js";
|
|
11
11
|
import "./chunk-PBQ5BQWD.js";
|
|
12
12
|
import "./chunk-PZ5AY32C.js";
|
|
13
13
|
export {
|
package/dist/auth-context.d.ts
CHANGED
package/dist/auth-context.js
CHANGED
|
@@ -2,21 +2,10 @@ import {
|
|
|
2
2
|
CURRENT_PLAN_VERSIONS,
|
|
3
3
|
PLAN_LIMITS,
|
|
4
4
|
callApi
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UMO47NOJ.js";
|
|
6
6
|
import {
|
|
7
7
|
getMcpAuth
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
|
|
10
|
-
// src/lib/caps.ts
|
|
11
|
-
var FULL_CAPS = Object.freeze({ media: true, research: true, forms: true });
|
|
12
|
-
function capsFromToolsets(toolsets) {
|
|
13
|
-
if (toolsets === void 0) return FULL_CAPS;
|
|
14
|
-
return {
|
|
15
|
-
media: toolsets.includes("media"),
|
|
16
|
-
research: toolsets.includes("research"),
|
|
17
|
-
forms: toolsets.includes("forms")
|
|
18
|
-
};
|
|
19
|
-
}
|
|
8
|
+
} from "./chunk-BV3KA6HC.js";
|
|
20
9
|
|
|
21
10
|
// src/lib/anon-notice.ts
|
|
22
11
|
var ANON_PREVIEW_NOTICE = "Preview accounts are temporary - claim yours to keep your forms.";
|
|
@@ -27,10 +16,31 @@ var ANON_PREVIEW_LINK_LABEL = "Preview (answers not saved)";
|
|
|
27
16
|
var ANON_PREVIEW_ONLY_NOTICE = 'The form is ready as a preview. To go live, say "publish it" - that prompts sign-in and keeps this chat connected to your account. In the preview, answers are not saved: do not share the preview link with respondents.';
|
|
28
17
|
var PREVIEW_URL_DESCRIPTION = "Private preview of the form. Answers are not saved, so do not share it with respondents.";
|
|
29
18
|
var CLAIM_URL_DESCRIPTION = "One-time link to sign up, keep this form and publish it.";
|
|
19
|
+
var ADOPTED_FORM_NOTICE = "Signed in - this form moved from the temporary preview account into your Clipform account, so it is yours now. The earlier claim link is no longer needed.";
|
|
20
|
+
function withAdoptedNotice(result, args) {
|
|
21
|
+
const formId = args?.form_id;
|
|
22
|
+
if (typeof formId !== "string") return result;
|
|
23
|
+
const adopted = getMcpAuth()?.adopted_form_ids;
|
|
24
|
+
if (!adopted || !adopted.map((id) => id.toLowerCase()).includes(formId.toLowerCase())) return result;
|
|
25
|
+
const r = result;
|
|
26
|
+
if (!r || r.isError || !Array.isArray(r.content)) return result;
|
|
27
|
+
return { ...r, content: [{ type: "text", text: ADOPTED_FORM_NOTICE }, ...r.content] };
|
|
28
|
+
}
|
|
30
29
|
function isPreviewViewerUrl(url) {
|
|
31
30
|
return typeof url === "string" && url.includes("mode=preview");
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
// src/lib/caps.ts
|
|
34
|
+
var FULL_CAPS = Object.freeze({ media: true, research: true, forms: true });
|
|
35
|
+
function capsFromToolsets(toolsets) {
|
|
36
|
+
if (toolsets === void 0) return FULL_CAPS;
|
|
37
|
+
return {
|
|
38
|
+
media: toolsets.includes("media"),
|
|
39
|
+
research: toolsets.includes("research"),
|
|
40
|
+
forms: toolsets.includes("forms")
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
34
44
|
// src/lib/session-context.ts
|
|
35
45
|
async function getSessionContextWithAuth() {
|
|
36
46
|
let me;
|
|
@@ -100,16 +110,17 @@ async function getSessionContext() {
|
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
export {
|
|
103
|
-
FULL_CAPS,
|
|
104
|
-
capsFromToolsets,
|
|
105
113
|
ANON_PREVIEW_NOTICE,
|
|
106
114
|
anonPreviewNotice,
|
|
107
115
|
ANON_PREVIEW_LINK_LABEL,
|
|
108
116
|
ANON_PREVIEW_ONLY_NOTICE,
|
|
109
117
|
PREVIEW_URL_DESCRIPTION,
|
|
110
118
|
CLAIM_URL_DESCRIPTION,
|
|
119
|
+
withAdoptedNotice,
|
|
111
120
|
isPreviewViewerUrl,
|
|
121
|
+
FULL_CAPS,
|
|
122
|
+
capsFromToolsets,
|
|
112
123
|
getSessionContextWithAuth,
|
|
113
124
|
getSessionContext
|
|
114
125
|
};
|
|
115
|
-
//# sourceMappingURL=chunk-
|
|
126
|
+
//# sourceMappingURL=chunk-75X4S4LA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/anon-notice.ts","../src/lib/caps.ts","../src/lib/session-context.ts"],"sourcesContent":["import { getMcpAuth } from \"./auth-context.js\";\n\n/**\n * #4650: anonymous (preview) accounts are temporary. Date-free by design -\n * the purge is not enforced yet, so no expiry date or day count is promised\n * (exact dates are follow-up #4662). Defined once; every anonymous-lane\n * surface (create/update_form text, whoami, session resource) reuses it.\n */\nexport const ANON_PREVIEW_NOTICE =\n \"Preview accounts are temporary - claim yours to keep your forms.\";\n\n/** The notice for an anonymous MCP caller, or null for everyone else. */\nexport function anonPreviewNotice(): string | null {\n return getMcpAuth()?.is_anonymous ? ANON_PREVIEW_NOTICE : null;\n}\n\n/**\n * #4845: an anonymous build's link is a private preview, never a respondent\n * link (answers are not saved). Wording defined once; create_form, get_form,\n * the format-form link label and the output-schema descriptions all reuse it.\n */\nexport const ANON_PREVIEW_LINK_LABEL = \"Preview (answers not saved)\";\n\nexport const ANON_PREVIEW_ONLY_NOTICE =\n 'The form is ready as a preview. To go live, say \"publish it\" - that prompts sign-in and keeps this chat connected to your account. In the preview, answers are not saved: do not share the preview link with respondents.';\n\nexport const PREVIEW_URL_DESCRIPTION =\n \"Private preview of the form. Answers are not saved, so do not share it with respondents.\";\n\nexport const CLAIM_URL_DESCRIPTION =\n \"One-time link to sign up, keep this form and publish it.\";\n\n/**\n * #5158: sign-in happens in a popup the model never sees, so after an adopt\n * (apps/api oauth-adopt.ts) the tool result tells it the form is now the\n * user's. Prepended by the server.ts registerTool wrapper.\n */\nexport const ADOPTED_FORM_NOTICE =\n \"Signed in - this form moved from the temporary preview account into your Clipform account, so it is yours now. The earlier claim link is no longer needed.\";\n\n/** Prepend ADOPTED_FORM_NOTICE to a successful result for a just-adopted form. content only; structuredContent untouched. */\nexport function withAdoptedNotice<T>(result: T, args: unknown): T {\n const formId = (args as { form_id?: unknown } | undefined)?.form_id;\n if (typeof formId !== \"string\") return result;\n const adopted = getMcpAuth()?.adopted_form_ids;\n if (!adopted || !adopted.map((id) => id.toLowerCase()).includes(formId.toLowerCase())) return result;\n const r = result as { isError?: boolean; content?: unknown } | undefined;\n if (!r || r.isError || !Array.isArray(r.content)) return result;\n return { ...r, content: [{ type: \"text\", text: ADOPTED_FORM_NOTICE }, ...r.content] } as T;\n}\n\n/** True for a viewer link in preview mode (an anonymous caller's draft). */\nexport function isPreviewViewerUrl(url: unknown): boolean {\n return typeof url === \"string\" && url.includes(\"mode=preview\");\n}\n","import type { McpToolset } from \"@vid-master/config\";\n\n/**\n * What a connected surface can actually do, derived from the connect-time\n * toolset filter (#3309) so the guidance layers (server instructions, prompt\n * recipes, `clipform_get_workflow`, craft guides) never reference a tool the\n * surface doesn't register (#3681).\n *\n * The Claude Connectors Directory surface (`mcp.clipform.io/forms`) is\n * account + forms only: no media generation and no research helpers. A\n * workflow that still says \"call clipform_generate_tts\" there sends the model\n * at a tool that doesn't exist, which shows up in review as failed tool\n * calls. Every guidance producer takes a ServerCaps and gates on it.\n */\nexport interface ServerCaps {\n /** Narration, video, image, music, render, and asset tools are registered. */\n media: boolean;\n /** News lookup and the generation audit log are registered. */\n research: boolean;\n /** Form tools (create, edit, publish) are registered. */\n forms: boolean;\n}\n\n/** Every toolset registered - stdio/npx and the bare remote endpoint. */\nexport const FULL_CAPS: ServerCaps = Object.freeze({ media: true, research: true, forms: true });\n\nexport function capsFromToolsets(toolsets?: readonly McpToolset[]): ServerCaps {\n if (toolsets === undefined) return FULL_CAPS;\n return {\n media: toolsets.includes(\"media\"),\n research: toolsets.includes(\"research\"),\n forms: toolsets.includes(\"forms\"),\n };\n}\n","import { BUSINESS, PLAN_LIMITS, CURRENT_PLAN_VERSIONS } from \"@vid-master/config\";\nimport { callApi } from \"./api-client.js\";\nimport { ANON_PREVIEW_NOTICE } from \"./anon-notice.js\";\nimport { getMcpAuth } from \"./auth-context.js\";\n\nexport type AuthMode = \"authenticated\" | \"anonymous\";\n\nexport interface SessionContextResult {\n text: string;\n authMode: AuthMode;\n}\n\nexport async function getSessionContextWithAuth(): Promise<SessionContextResult> {\n let me: any;\n const authCtx = getMcpAuth();\n if (authCtx?.loadInternalContent && !authCtx.api_key) {\n // #4657: the no-workspace anon lane (get_workflow with no mint) has no\n // workspace for /me to describe, so plan caps default to Free.\n const free = PLAN_LIMITS[CURRENT_PLAN_VERSIONS[0]];\n me = {\n auth_mode: \"anonymous\",\n plan: {\n name: free.name,\n tier: free.tier,\n node_limit: free.node_limit,\n form_limit: free.form_limit ?? null,\n custom_theme: free.custom_theme,\n },\n };\n } else {\n let result;\n try {\n result = await callApi(\"/me\", { method: \"GET\" });\n } catch {\n return { text: \"\", authMode: \"anonymous\" };\n }\n if (!result.ok) return { text: \"\", authMode: \"anonymous\" };\n me = result.data as any;\n }\n // The API's auth_mode is \"api_key\" | \"oauth\" | \"session\" | \"anonymous\".\n // Anything credentialed counts as authenticated - the old `=== \"oauth\"`\n // check matched nothing the API returns, so API-key sessions were labelled\n // anonymous with a \"sign in to unlock\" nudge right next to their real\n // plan (#412 F6).\n // The anon MCP lane runs as a minted bot key (/me says \"api_key\"), so the\n // auth context is the truth for anonymous (#4653).\n const apiAuthMode: string = getMcpAuth()?.is_anonymous\n ? \"anonymous\"\n : ((me.auth_mode as string) ?? \"anonymous\");\n const authMode: AuthMode = apiAuthMode === \"anonymous\" ? \"anonymous\" : \"authenticated\";\n const plan = me.plan;\n const lines: string[] = [];\n\n lines.push(\"## Your Session\");\n if (authMode === \"authenticated\") {\n lines.push(\n apiAuthMode === \"api_key\"\n ? \"Auth: API key (workspace-scoped)\"\n : \"Auth: signed in (connected to a Clipform account)\"\n );\n // Workspace NAME only - the workspace UUID and the raw user_id/company_id\n // are internal database identifiers the model never needs (response\n // hygiene / disclosure guard, #2976).\n lines.push(`Workspace: ${me.workspace?.name ?? \"Unknown\"}`);\n } else {\n lines.push(\"Auth: anonymous (not connected to a Clipform account)\");\n if (me.workspace) {\n lines.push(`Workspace: ${me.workspace.name}`);\n }\n }\n lines.push(`Plan: ${plan.name} (tier ${plan.tier})`);\n if (plan.node_limit !== null) {\n lines.push(`Question limit: ${plan.node_limit} per form`);\n } else {\n lines.push(\"Questions: unlimited\");\n }\n if (plan.form_limit !== null) {\n lines.push(`Form limit: ${plan.form_limit} forms`);\n } else {\n lines.push(\"Forms: unlimited\");\n }\n if (plan.custom_theme === false) {\n lines.push(\"Custom themes: not available on this plan\");\n }\n if (authMode === \"anonymous\") {\n // Neutral statement of fact, no upsell/CTA copy (commerce sweep, #2976):\n // an anonymous session's forms are not tied to an account until one signs in.\n lines.push(`\\nAnonymous session: forms are not tied to a Clipform account until you sign in.`);\n lines.push(ANON_PREVIEW_NOTICE);\n }\n\n return { text: lines.join(\"\\n\"), authMode };\n}\n\nexport async function getSessionContext(): Promise<string> {\n const { text } = await getSessionContextWithAuth();\n return text;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -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 // #4090: sibling of mcp_mint_proof, set when the anon lane REUSED a\n // server-verified session binding (#4652) instead of minting. HMAC-bound\n // to the bound {workspace, uid}; lets create-form.ts re-surface the claim\n // link on later forms in the session.\n mcp_session_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 // #5158: OAuth lane only. The anonymous form ids THIS request just adopted\n // into the signed-in user's workspace (apps/api oauth-adopt.ts, #4638). The\n // sign-in happens in a popup the model never sees, so the tool wrapper uses\n // this to tell it the form is now the user's. INFORMATIONAL ONLY - derived\n // from the server-side adopt outcome of this same request, never from client\n // input, and never read for an access decision.\n adopted_form_ids?: 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 // #4657: set ONLY by apps/api's mcp/handler.ts for an anonymous request made\n // entirely of `anon_no_workspace` tools (get_guide / get_workflow /\n // list_assets). That lane has NO credential of any kind: the static content\n // is served in-process through this loader instead of an HTTP callApi, so no\n // secret or key ever sits in the anonymous context. Tools use it when\n // present and callApi otherwise (stdio / published behaviour unchanged).\n // #4657: decoded PostHog MCP `$session_id` from the inbound Mcp-Session-Id -\n // ANALYTICS ONLY (stamped on funnel events). api-client.ts forwards it as\n // MCP_SESSION_ID_HEADER; nothing about identity or access derives from it.\n mcp_session_id?: string;\n loadInternalContent?: (\n kind: \"guides\" | \"assets\",\n params?: { type?: string }\n ) => Promise<Record<string, unknown>>;\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\n/** #4090: carries `mcp_session_proof` (same trust model as MCP_MINT_PROOF_HEADER: HMAC-verified server-side, never trusted raw). */\nexport const MCP_SESSION_PROOF_HEADER = \"x-clipform-mcp-session-proof\";\n\n/** Carries `mcp_session_id` to the API for funnel analytics only (#4657). */\nexport const MCP_SESSION_ID_HEADER = \"x-clipform-mcp-session-id\";\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;AAmG3B,IAAM,uBAAuB;AAa7B,IAAM,sBAAsB;AAW5B,IAAM,wBAAwB;AAG9B,IAAM,2BAA2B;AAGjC,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":[]}
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
union,
|
|
30
30
|
unknown,
|
|
31
31
|
url
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-RMFF42S5.js";
|
|
33
33
|
import {
|
|
34
34
|
GUIDE_TYPES,
|
|
35
35
|
QUIZ_VARIANTS,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
getGuideUri,
|
|
39
39
|
guideFallbackText,
|
|
40
40
|
registerResources
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-OVLNACRG.js";
|
|
42
42
|
import {
|
|
43
43
|
ANON_PREVIEW_LINK_LABEL,
|
|
44
44
|
ANON_PREVIEW_NOTICE,
|
|
@@ -48,8 +48,9 @@ import {
|
|
|
48
48
|
PREVIEW_URL_DESCRIPTION,
|
|
49
49
|
anonPreviewNotice,
|
|
50
50
|
capsFromToolsets,
|
|
51
|
-
isPreviewViewerUrl
|
|
52
|
-
|
|
51
|
+
isPreviewViewerUrl,
|
|
52
|
+
withAdoptedNotice
|
|
53
|
+
} from "./chunk-75X4S4LA.js";
|
|
53
54
|
import {
|
|
54
55
|
ACTIVE_NODE_TYPES,
|
|
55
56
|
CONTACT_FIELDS,
|
|
@@ -71,12 +72,12 @@ import {
|
|
|
71
72
|
resolveFormType,
|
|
72
73
|
structuredResult,
|
|
73
74
|
textResult
|
|
74
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-UMO47NOJ.js";
|
|
75
76
|
import {
|
|
76
77
|
getMcpAuth,
|
|
77
78
|
runWithMcpAuth,
|
|
78
79
|
runWithMcpTool
|
|
79
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-BV3KA6HC.js";
|
|
80
81
|
|
|
81
82
|
// ../../node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
82
83
|
var __create = Object.create;
|
|
@@ -18348,7 +18349,7 @@ function registerDeleteNodeMediaTool(server) {
|
|
|
18348
18349
|
"clipform_delete_node_media",
|
|
18349
18350
|
{
|
|
18350
18351
|
title: "Delete Node Media",
|
|
18351
|
-
description: `Remove media from a node
|
|
18352
|
+
description: `Remove media from a node by unlinking it; the media asset and its files stay in the workspace library and keep working on any other node or live form that uses them. Requires user confirmation before it runs.`,
|
|
18352
18353
|
inputSchema: external_exports.object({
|
|
18353
18354
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
18354
18355
|
node_id: external_exports.string().describe("The node ID"),
|
|
@@ -18360,16 +18361,15 @@ function registerDeleteNodeMediaTool(server) {
|
|
|
18360
18361
|
// Deleting already-removed media has no further effect - textbook
|
|
18361
18362
|
// idempotent delete (#2976).
|
|
18362
18363
|
idempotentHint: true,
|
|
18363
|
-
//
|
|
18364
|
-
|
|
18365
|
-
openWorldHint: true
|
|
18364
|
+
// Unlink only (#5070): no files or third-party copies are deleted.
|
|
18365
|
+
openWorldHint: false
|
|
18366
18366
|
}
|
|
18367
18367
|
},
|
|
18368
18368
|
async ({ form_id, node_id, confirm }, ctx) => {
|
|
18369
18369
|
const decision = await confirmDestructive(server, ctx, {
|
|
18370
18370
|
alreadyConfirmed: confirm,
|
|
18371
|
-
message: `Remove the media attached to node ${node_id}?
|
|
18372
|
-
relay: `Confirmation required: removing media from node ${node_id}
|
|
18371
|
+
message: `Remove the media attached to node ${node_id}? The media asset stays in the library.`,
|
|
18372
|
+
relay: `Confirmation required: removing media from node ${node_id} unlinks it from the node (the media asset stays in the library). Ask the user to confirm, then call clipform_delete_node_media again with confirm: true.`
|
|
18373
18373
|
});
|
|
18374
18374
|
if (!decision.proceed) {
|
|
18375
18375
|
return decision.result ?? textResult(decision.message);
|
|
@@ -20040,7 +20040,7 @@ function buildInstructions(caps) {
|
|
|
20040
20040
|
"At the start of a build, call clipform_get_workflow to load the recipe for the form type, and clipform_get_guide for craft detail.",
|
|
20041
20041
|
"Every *_id argument is a UUID returned by the tools - not the short share_id from a form URL.",
|
|
20042
20042
|
"For an account-scoped request, call clipform_whoami first to confirm the active workspace and plan.",
|
|
20043
|
-
caps.media ? "Deleting a form moves it to the trash (restorable from the dashboard); deleting a node
|
|
20043
|
+
caps.media ? "Deleting a form moves it to the trash (restorable from the dashboard); deleting a node cannot be undone (a node deleted on a live form stays on the live form until you publish); removing media from a node only unlinks it, and the media asset stays in the library. Those tools ask the user to confirm before running." : "Deleting a form moves it to the trash (restorable from the dashboard); deleting a node cannot be undone (on a live form it stays on the live form until you publish). Those tools ask the user to confirm before running."
|
|
20044
20044
|
].filter((line) => typeof line === "string").join(" ");
|
|
20045
20045
|
}
|
|
20046
20046
|
function pinDraft07(schema) {
|
|
@@ -20102,7 +20102,8 @@ function createServer(options) {
|
|
|
20102
20102
|
config,
|
|
20103
20103
|
(...args) => runWithMcpTool(name, async () => {
|
|
20104
20104
|
try {
|
|
20105
|
-
|
|
20105
|
+
const result = await runWithWorkspaceTarget(args[0], () => handler(...args));
|
|
20106
|
+
return withAdoptedNotice(result, args[0]);
|
|
20106
20107
|
} catch (err) {
|
|
20107
20108
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
20108
20109
|
options?.onToolError?.(name, error);
|
|
@@ -20212,4 +20213,4 @@ export {
|
|
|
20212
20213
|
* MIT Licensed
|
|
20213
20214
|
*)
|
|
20214
20215
|
*/
|
|
20215
|
-
//# sourceMappingURL=chunk-
|
|
20216
|
+
//# sourceMappingURL=chunk-K67KW7GF.js.map
|