@clipform/mcp-server 2.5.0 → 2.6.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 +1 -0
- package/dist/{chunk-3OX54HSJ.js → chunk-44FHF2MF.js} +133 -6
- package/dist/chunk-44FHF2MF.js.map +1 -0
- package/dist/{chunk-6EHOUAPE.js → chunk-L3DIBX4G.js} +2 -2
- package/dist/{chunk-NYMC63ZK.js → chunk-WMLM53A3.js} +2 -2
- package/dist/{chunk-3BYQLPTT.js → chunk-YAI5LQPF.js} +2 -1
- package/dist/{chunk-3BYQLPTT.js.map → chunk-YAI5LQPF.js.map} +1 -1
- package/dist/index.js +4 -4
- package/dist/prompts.js +2 -2
- package/dist/resources.js +2 -2
- package/dist/server.js +4 -4
- package/package.json +1 -1
- package/dist/chunk-3OX54HSJ.js.map +0 -1
- /package/dist/{chunk-6EHOUAPE.js.map → chunk-L3DIBX4G.js.map} +0 -0
- /package/dist/{chunk-NYMC63ZK.js.map → chunk-WMLM53A3.js.map} +0 -0
package/README.md
CHANGED
|
@@ -64,6 +64,7 @@ Your MCP client lists these automatically on connect (via `tools/list`). Full re
|
|
|
64
64
|
| Tool | Description |
|
|
65
65
|
|------|-------------|
|
|
66
66
|
| `clipform_create_form` | Create a new Clipform (interactive video-style form). |
|
|
67
|
+
| `clipform_import_form` | Convert a public Google Form, Typeform, or Tally form into a new Clipform. |
|
|
67
68
|
| `clipform_list_forms` | List forms in your workspace with optional filtering. |
|
|
68
69
|
| `clipform_whoami` | Show the current identity: auth mode (api key, session, or anonymous), active workspace, and plan limits. |
|
|
69
70
|
| `clipform_get_form` | Retrieve a form's details including all nodes in sequential order and their routing. |
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getWorkflowText,
|
|
7
7
|
objectType,
|
|
8
8
|
registerPrompts
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WMLM53A3.js";
|
|
10
10
|
import {
|
|
11
11
|
GUIDE_TYPES,
|
|
12
12
|
QUIZ_VARIANTS,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getGuideUri,
|
|
15
15
|
guideFallbackText,
|
|
16
16
|
registerResources
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-L3DIBX4G.js";
|
|
18
18
|
import {
|
|
19
19
|
ACTIVE_NODE_TYPES,
|
|
20
20
|
CONTACT_FIELDS,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
resolveFormType,
|
|
36
36
|
structuredResult,
|
|
37
37
|
textResult
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-YAI5LQPF.js";
|
|
39
39
|
import {
|
|
40
40
|
getMcpAuth,
|
|
41
41
|
runWithMcpTool
|
|
@@ -17293,10 +17293,12 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17293
17293
|
isPreview ? `CLAIM URL (one-time - claim this account to publish): ${formUrl}` : `DASHBOARD URL (sign in to edit and manage): ${formUrl}`
|
|
17294
17294
|
);
|
|
17295
17295
|
}
|
|
17296
|
+
const firstUrlLabel = isPreview ? "PREVIEW" : "FORM";
|
|
17297
|
+
const secondUrlLabel = isPreview ? "CLAIM" : "DASHBOARD";
|
|
17296
17298
|
lines.push(
|
|
17297
17299
|
``,
|
|
17298
|
-
isPreview ? `This form is a preview - claim and publish to start collecting responses
|
|
17299
|
-
`The URLs above are exact values; constructed or guessed URLs return 404. Use the ${
|
|
17300
|
+
isPreview ? `This form is a preview - claim and publish to start collecting responses.${formUrl ? " Open the CLAIM URL to sign into the account this form already belongs to, then republish" : " Sign in to the account this form already belongs to and republish"} with clipform_update_form (is_live: true). The PREVIEW URL never becomes public on its own.` : `The form is live and accessible at the FORM URL above. Edits and media attached AFTER this point are NOT visible to respondents until you re-send is_live: true via clipform_update_form - republishing is the expected final step of a build, never skip it after attaching media/narration.`,
|
|
17301
|
+
formUrl ? `The URLs above are exact values; constructed or guessed URLs return 404. Use the ${firstUrlLabel} URL and ${secondUrlLabel} URL as shown.` : `The ${firstUrlLabel} URL above is an exact value; constructed or guessed URLs return 404.`,
|
|
17300
17302
|
`Pass form_id on follow-up tools (get_form, add_node, attach_node_media, etc.).`
|
|
17301
17303
|
);
|
|
17302
17304
|
if (planContext) {
|
|
@@ -17333,6 +17335,130 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17333
17335
|
);
|
|
17334
17336
|
}
|
|
17335
17337
|
|
|
17338
|
+
// src/tools/import-form.ts
|
|
17339
|
+
function detectImportPlatform(url) {
|
|
17340
|
+
let parsed;
|
|
17341
|
+
try {
|
|
17342
|
+
parsed = new URL(url);
|
|
17343
|
+
} catch {
|
|
17344
|
+
return null;
|
|
17345
|
+
}
|
|
17346
|
+
const hostname2 = parsed.hostname.toLowerCase();
|
|
17347
|
+
if (hostname2 === "docs.google.com" || hostname2 === "forms.gle") {
|
|
17348
|
+
return "google";
|
|
17349
|
+
}
|
|
17350
|
+
if (hostname2.includes("typeform.com") && parsed.pathname.includes("/to/")) {
|
|
17351
|
+
return "typeform";
|
|
17352
|
+
}
|
|
17353
|
+
if (hostname2.includes("tally.so")) {
|
|
17354
|
+
return "tally";
|
|
17355
|
+
}
|
|
17356
|
+
return null;
|
|
17357
|
+
}
|
|
17358
|
+
function pluralQuestions(n) {
|
|
17359
|
+
return `${n} question${n === 1 ? "" : "s"}`;
|
|
17360
|
+
}
|
|
17361
|
+
function registerImportFormTool(server) {
|
|
17362
|
+
server.registerTool(
|
|
17363
|
+
"clipform_import_form",
|
|
17364
|
+
{
|
|
17365
|
+
title: "Import Form",
|
|
17366
|
+
description: `Convert a public Google Form, Typeform, or Tally form into a new Clipform. Supported URLs: Google Forms (docs.google.com or forms.gle - must be shared as "Anyone with the link"), Typeform (form.typeform.com/to/...), and Tally (tally.so - must be public). The source form must be publicly accessible; a private form's questions cannot be read. The new Clipform is created as a DRAFT - review it, then republish with clipform_update_form (is_live: true) once it's ready. Some question types don't map cleanly to a Clipform node and are skipped rather than guessed at; the result always reports exactly what was imported vs skipped, and why.`,
|
|
17367
|
+
inputSchema: {
|
|
17368
|
+
url: external_exports.string().url().describe("Public URL of the Google Form, Typeform, or Tally form to import")
|
|
17369
|
+
},
|
|
17370
|
+
outputSchema: {
|
|
17371
|
+
form_id: external_exports.string().nullable().describe("New form's UUID - pass to follow-up tools. Null when no importable questions were found (nothing was created)."),
|
|
17372
|
+
viewer_url: external_exports.string().nullable().describe("The address this form WILL be live at once published (clipform_update_form with is_live: true) - it is a draft and is NOT viewable by anyone yet; opening it now shows a maintenance screen, not the form. Null when no importable questions were found."),
|
|
17373
|
+
import_summary: external_exports.object({
|
|
17374
|
+
title: external_exports.string().describe("Title of the source form"),
|
|
17375
|
+
total_questions: external_exports.number().describe("Total questions found in the source form"),
|
|
17376
|
+
imported: external_exports.number().describe("Questions successfully converted into Clipform nodes"),
|
|
17377
|
+
skipped: external_exports.number().describe("Questions that could not be converted"),
|
|
17378
|
+
skipped_details: external_exports.array(external_exports.record(external_exports.string(), external_exports.string())).describe("Per-skipped-question title, reason, and source question type"),
|
|
17379
|
+
warnings: external_exports.array(external_exports.string()).describe("Non-fatal issues surfaced during the import")
|
|
17380
|
+
})
|
|
17381
|
+
},
|
|
17382
|
+
annotations: {
|
|
17383
|
+
readOnlyHint: false,
|
|
17384
|
+
destructiveHint: false,
|
|
17385
|
+
idempotentHint: false,
|
|
17386
|
+
// Creates a new, publicly-reachable-once-published form (same
|
|
17387
|
+
// reasoning as clipform_create_form's own annotation).
|
|
17388
|
+
openWorldHint: true
|
|
17389
|
+
}
|
|
17390
|
+
},
|
|
17391
|
+
async ({ url }) => {
|
|
17392
|
+
const platform = detectImportPlatform(url);
|
|
17393
|
+
if (!platform) {
|
|
17394
|
+
return errorResult(
|
|
17395
|
+
"Unsupported URL. clipform_import_form only supports Google Forms (docs.google.com or forms.gle), Typeform (form.typeform.com/to/...), and Tally (tally.so) links."
|
|
17396
|
+
);
|
|
17397
|
+
}
|
|
17398
|
+
const meResult = await callApi("/me", { method: "GET" });
|
|
17399
|
+
if (!meResult.ok) {
|
|
17400
|
+
return errorResult(`Unable to determine workspace: ${meResult.error}`);
|
|
17401
|
+
}
|
|
17402
|
+
const me = meResult.data;
|
|
17403
|
+
const workspaceId = me.workspace?.id ?? null;
|
|
17404
|
+
if (!workspaceId) {
|
|
17405
|
+
return errorResult(
|
|
17406
|
+
"No workspace available. This usually means authentication is misconfigured - check your API key or OAuth connection."
|
|
17407
|
+
);
|
|
17408
|
+
}
|
|
17409
|
+
const importResult = platform === "google" ? await callApi("/forms/import/google", { method: "POST", body: { url, workspace_id: workspaceId } }) : platform === "typeform" ? await callApi("/forms/import/typeform", { method: "POST", body: { url, workspace_id: workspaceId } }) : await callApi("/forms/import/tally", { method: "POST", body: { url, workspace_id: workspaceId } });
|
|
17410
|
+
if (!importResult.ok) {
|
|
17411
|
+
return errorResult(importResult.error);
|
|
17412
|
+
}
|
|
17413
|
+
const data = importResult.data;
|
|
17414
|
+
const summary = data.import_summary;
|
|
17415
|
+
const formId = data.form_id;
|
|
17416
|
+
const viewerUrl = data.url;
|
|
17417
|
+
const lines = [];
|
|
17418
|
+
if (!formId) {
|
|
17419
|
+
lines.push(
|
|
17420
|
+
`No importable questions were found in "${summary.title}" (${pluralQuestions(summary.total_questions)} total) - nothing was created.`
|
|
17421
|
+
);
|
|
17422
|
+
} else {
|
|
17423
|
+
lines.push(
|
|
17424
|
+
`Imported "${summary.title}": ${summary.imported} of ${pluralQuestions(summary.total_questions)} converted, ${summary.skipped} skipped.`
|
|
17425
|
+
);
|
|
17426
|
+
}
|
|
17427
|
+
if (summary.skipped_details.length > 0) {
|
|
17428
|
+
lines.push(
|
|
17429
|
+
``,
|
|
17430
|
+
`Skipped:`,
|
|
17431
|
+
...summary.skipped_details.map((s) => `- "${s.title}": ${s.reason}`)
|
|
17432
|
+
);
|
|
17433
|
+
}
|
|
17434
|
+
if (summary.warnings.length > 0) {
|
|
17435
|
+
lines.push(``, `Warnings:`, ...summary.warnings.map((w) => `- ${w}`));
|
|
17436
|
+
}
|
|
17437
|
+
if (formId) {
|
|
17438
|
+
lines.push(
|
|
17439
|
+
``,
|
|
17440
|
+
`Form ID: ${formId}`,
|
|
17441
|
+
`FUTURE LIVE URL (not viewable yet - the form is a draft; opening this now shows a maintenance screen): ${viewerUrl}`,
|
|
17442
|
+
`This form was created as a DRAFT. Review it, then publish with clipform_update_form (is_live: true) - only then does the URL above start serving the form to respondents.`,
|
|
17443
|
+
`Pass form_id (the UUID above, not a share_id) to follow-up tools (get_form, add_node, update_node, etc.).`
|
|
17444
|
+
);
|
|
17445
|
+
}
|
|
17446
|
+
return structuredResult(lines.join("\n"), {
|
|
17447
|
+
form_id: formId,
|
|
17448
|
+
viewer_url: viewerUrl,
|
|
17449
|
+
import_summary: {
|
|
17450
|
+
title: summary.title,
|
|
17451
|
+
total_questions: summary.total_questions,
|
|
17452
|
+
imported: summary.imported,
|
|
17453
|
+
skipped: summary.skipped,
|
|
17454
|
+
skipped_details: summary.skipped_details,
|
|
17455
|
+
warnings: summary.warnings
|
|
17456
|
+
}
|
|
17457
|
+
});
|
|
17458
|
+
}
|
|
17459
|
+
);
|
|
17460
|
+
}
|
|
17461
|
+
|
|
17336
17462
|
// src/tools/list-forms.ts
|
|
17337
17463
|
function registerListFormsTool(server) {
|
|
17338
17464
|
server.registerTool(
|
|
@@ -20279,6 +20405,7 @@ function createServer(options) {
|
|
|
20279
20405
|
);
|
|
20280
20406
|
};
|
|
20281
20407
|
registerCreateFormTool(server);
|
|
20408
|
+
registerImportFormTool(server);
|
|
20282
20409
|
registerListFormsTool(server);
|
|
20283
20410
|
registerWhoamiTool(server);
|
|
20284
20411
|
registerGetFormTool(server);
|
|
@@ -20319,4 +20446,4 @@ export {
|
|
|
20319
20446
|
RENDER_TIMING,
|
|
20320
20447
|
createServer
|
|
20321
20448
|
};
|
|
20322
|
-
//# sourceMappingURL=chunk-
|
|
20449
|
+
//# sourceMappingURL=chunk-44FHF2MF.js.map
|