@clipform/mcp-server 2.6.0 → 2.10.3
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/caps-DBnfcEyJ.d.ts +20 -0
- package/dist/{chunk-L3DIBX4G.js → chunk-A5RVUIGS.js} +17 -7
- package/dist/chunk-A5RVUIGS.js.map +1 -0
- package/dist/{chunk-YAI5LQPF.js → chunk-O35IVA4L.js} +142 -9
- package/dist/chunk-O35IVA4L.js.map +1 -0
- package/dist/{chunk-WMLM53A3.js → chunk-QZO6MGNN.js} +310 -162
- package/dist/{chunk-WMLM53A3.js.map → chunk-QZO6MGNN.js.map} +1 -1
- package/dist/{chunk-44FHF2MF.js → chunk-ZOBK6CK7.js} +269 -45
- package/dist/chunk-ZOBK6CK7.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/instrument.d.ts +42 -0
- package/dist/instrument.js +5296 -0
- package/dist/instrument.js.map +1 -0
- package/dist/prompts.d.ts +3 -2
- package/dist/prompts.js +2 -2
- package/dist/resources.d.ts +3 -1
- package/dist/resources.js +2 -2
- package/dist/server.d.ts +12 -1
- package/dist/server.js +6 -4
- package/package.json +6 -1
- package/dist/chunk-44FHF2MF.js.map +0 -1
- package/dist/chunk-L3DIBX4G.js.map +0 -1
- package/dist/chunk-YAI5LQPF.js.map +0 -1
|
@@ -6,19 +6,22 @@ import {
|
|
|
6
6
|
getWorkflowText,
|
|
7
7
|
objectType,
|
|
8
8
|
registerPrompts
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QZO6MGNN.js";
|
|
10
10
|
import {
|
|
11
11
|
GUIDE_TYPES,
|
|
12
12
|
QUIZ_VARIANTS,
|
|
13
|
+
applyCapsToGuide,
|
|
13
14
|
fetchGuideText,
|
|
14
15
|
getGuideUri,
|
|
15
16
|
guideFallbackText,
|
|
16
17
|
registerResources
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-A5RVUIGS.js";
|
|
18
19
|
import {
|
|
19
20
|
ACTIVE_NODE_TYPES,
|
|
20
21
|
CONTACT_FIELDS,
|
|
22
|
+
FEATURE_FLAGS,
|
|
21
23
|
FORM_TYPE_ALIASES,
|
|
24
|
+
FULL_CAPS,
|
|
22
25
|
KEN_BURNS_EFFECTS,
|
|
23
26
|
KEN_BURNS_FIT_MODES,
|
|
24
27
|
KEN_BURNS_PRESETS,
|
|
@@ -29,13 +32,14 @@ import {
|
|
|
29
32
|
SLIDESHOW_TRANSITIONS,
|
|
30
33
|
bfsOrder,
|
|
31
34
|
callApi,
|
|
35
|
+
capsFromToolsets,
|
|
32
36
|
defaultTarget,
|
|
33
37
|
errorResult,
|
|
34
38
|
exposedCompositionIds,
|
|
35
39
|
resolveFormType,
|
|
36
40
|
structuredResult,
|
|
37
41
|
textResult
|
|
38
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-O35IVA4L.js";
|
|
39
43
|
import {
|
|
40
44
|
getMcpAuth,
|
|
41
45
|
runWithMcpTool
|
|
@@ -17002,6 +17006,7 @@ var EXPOSED_COMPOSITIONS = exposedCompositionIds(
|
|
|
17002
17006
|
);
|
|
17003
17007
|
|
|
17004
17008
|
// src/lib/schemas.ts
|
|
17009
|
+
var CUSTOM_SCORES_GATED_KEYS = /* @__PURE__ */ new Set(["show_score", "score_ranges", "scoring_results"]);
|
|
17005
17010
|
var ACTIVE_NODE_TYPES2 = ACTIVE_NODE_TYPES;
|
|
17006
17011
|
var CONTACT_FIELD_IDS = CONTACT_FIELDS.map(
|
|
17007
17012
|
(f) => f.id
|
|
@@ -17011,6 +17016,7 @@ function generateConfigSummary(configSchema, typeKey) {
|
|
|
17011
17016
|
const parts = [];
|
|
17012
17017
|
for (const [key, prop] of Object.entries(configSchema.properties)) {
|
|
17013
17018
|
if (key === "content_media_type") continue;
|
|
17019
|
+
if (!FEATURE_FLAGS.ENABLE_CUSTOM_SCORES && CUSTOM_SCORES_GATED_KEYS.has(key)) continue;
|
|
17014
17020
|
if (prop.enum) {
|
|
17015
17021
|
const vals = prop.enum.map((v) => `"${v}"`).join("|");
|
|
17016
17022
|
let s = `${key} (${vals}`;
|
|
@@ -17094,8 +17100,11 @@ var CaptionSchema = external_exports.object({
|
|
|
17094
17100
|
});
|
|
17095
17101
|
var OptionSchema = external_exports.object({
|
|
17096
17102
|
content: external_exports.string().describe("Option text"),
|
|
17103
|
+
is_correct: external_exports.boolean().optional().describe("Mark this as the correct answer for a quiz (maps to score 1; other options score 0). Use this OR score, not both."),
|
|
17097
17104
|
score: external_exports.number().optional().describe("Score value for this option (for scored quizzes). Use 1 for correct, 0 for wrong. Scored options automatically enable correct-answer marking (record_scores) on the node."),
|
|
17098
|
-
scores
|
|
17105
|
+
// `scores` (category-based) is gated behind custom_scores (planned, #767) -
|
|
17106
|
+
// omitted from the schema while off so the MCP doesn't accept a not-live field.
|
|
17107
|
+
...FEATURE_FLAGS.ENABLE_CUSTOM_SCORES ? { scores: external_exports.record(external_exports.number()).optional().describe("Category-based scores keyed by category name (personality quizzes).") } : {}
|
|
17099
17108
|
});
|
|
17100
17109
|
var OPTIONS_DESCRIPTION = (() => {
|
|
17101
17110
|
const hints = ["Answer options."];
|
|
@@ -17125,6 +17134,14 @@ function applyNodeDefaults(node) {
|
|
|
17125
17134
|
const label = node.config?.button_text || buttonDefault;
|
|
17126
17135
|
node.options = [{ content: label }];
|
|
17127
17136
|
}
|
|
17137
|
+
if (node.options) {
|
|
17138
|
+
for (const o of node.options) {
|
|
17139
|
+
if (o.score == null && typeof o.is_correct === "boolean") {
|
|
17140
|
+
o.score = o.is_correct ? 1 : 0;
|
|
17141
|
+
}
|
|
17142
|
+
delete o.is_correct;
|
|
17143
|
+
}
|
|
17144
|
+
}
|
|
17128
17145
|
const supportsRecordScores = def?.config_schema?.properties?.choice?.properties?.record_scores !== void 0;
|
|
17129
17146
|
const hasScoredOptions = node.options?.some((o) => o.score != null || o.scores != null);
|
|
17130
17147
|
if (supportsRecordScores && hasScoredOptions) {
|
|
@@ -17276,7 +17293,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17276
17293
|
body: { tags }
|
|
17277
17294
|
});
|
|
17278
17295
|
}
|
|
17279
|
-
const
|
|
17296
|
+
const truncate2 = (s, n = 60) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
17280
17297
|
const lines = [
|
|
17281
17298
|
isPreview ? `Form created as a preview draft.` : `Form created and live.`,
|
|
17282
17299
|
``,
|
|
@@ -17284,7 +17301,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17284
17301
|
`Form ID: ${formId}`,
|
|
17285
17302
|
``,
|
|
17286
17303
|
`Node IDs (use directly with attach_node_media / update_node - no get_form round-trip needed):`,
|
|
17287
|
-
...createdNodes.map((n) => `- [${n.type}] "${
|
|
17304
|
+
...createdNodes.map((n) => `- [${n.type}] "${truncate2(n.prompt)}": ${n.id}`),
|
|
17288
17305
|
``,
|
|
17289
17306
|
isPreview ? `PREVIEW URL (draft - only you can open this, not yet visible to respondents): ${data.viewer_url}` : `FORM URL (live - share this with respondents): ${data.viewer_url}`
|
|
17290
17307
|
];
|
|
@@ -17489,7 +17506,9 @@ function registerListFormsTool(server) {
|
|
|
17489
17506
|
share_id: external_exports.string(),
|
|
17490
17507
|
is_live: external_exports.boolean(),
|
|
17491
17508
|
has_unpublished_changes: external_exports.boolean(),
|
|
17492
|
-
tags: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), color: external_exports.string().nullable() }))
|
|
17509
|
+
tags: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), color: external_exports.string().nullable() })),
|
|
17510
|
+
builder_url: external_exports.string().optional().describe("Dashboard Build tab for this form. Not present for an anonymous caller."),
|
|
17511
|
+
viewer_url: external_exports.string().optional().describe("Live respondent link. Only present for a published (is_live) form, and not for an anonymous caller.")
|
|
17493
17512
|
})
|
|
17494
17513
|
).describe("Matching forms (empty if none)"),
|
|
17495
17514
|
next_cursor: external_exports.string().nullable().describe("Pass as cursor to fetch the next page, or null when no more")
|
|
@@ -17522,7 +17541,9 @@ function registerListFormsTool(server) {
|
|
|
17522
17541
|
share_id: f.share_id,
|
|
17523
17542
|
is_live: f.is_live,
|
|
17524
17543
|
has_unpublished_changes: f.has_unpublished_changes,
|
|
17525
|
-
tags: f.tags.map((t) => ({ id: t.id, name: t.name, color: t.color }))
|
|
17544
|
+
tags: f.tags.map((t) => ({ id: t.id, name: t.name, color: t.color })),
|
|
17545
|
+
...f.builder_url ? { builder_url: f.builder_url } : {},
|
|
17546
|
+
...f.viewer_url ? { viewer_url: f.viewer_url } : {}
|
|
17526
17547
|
})),
|
|
17527
17548
|
next_cursor: data.next_cursor
|
|
17528
17549
|
};
|
|
@@ -17534,7 +17555,8 @@ function registerListFormsTool(server) {
|
|
|
17534
17555
|
for (const f of data.forms) {
|
|
17535
17556
|
const status = f.is_live ? f.has_unpublished_changes ? "live, unpublished changes" : "live" : "draft";
|
|
17536
17557
|
const tagStr = f.tags.length > 0 ? ` [${f.tags.map((t) => t.name).join(", ")}]` : "";
|
|
17537
|
-
|
|
17558
|
+
const linkStr = f.builder_url ? ` \u2014 ${f.builder_url}` : "";
|
|
17559
|
+
lines.push(`- ${f.title || "(untitled)"} (${status})${tagStr} \u2014 ${f.id}${linkStr}`);
|
|
17538
17560
|
}
|
|
17539
17561
|
if (data.next_cursor) {
|
|
17540
17562
|
lines.push(
|
|
@@ -17570,6 +17592,26 @@ function projectConfig(nodeType, config2) {
|
|
|
17570
17592
|
}
|
|
17571
17593
|
return projected;
|
|
17572
17594
|
}
|
|
17595
|
+
var FORM_URL_KEYS = [
|
|
17596
|
+
"viewer",
|
|
17597
|
+
"builder",
|
|
17598
|
+
"summary",
|
|
17599
|
+
"responses",
|
|
17600
|
+
"analytics",
|
|
17601
|
+
"share",
|
|
17602
|
+
"embed",
|
|
17603
|
+
"dynamic_link"
|
|
17604
|
+
];
|
|
17605
|
+
function projectUrls(data) {
|
|
17606
|
+
const raw = data.urls;
|
|
17607
|
+
if (!raw) return void 0;
|
|
17608
|
+
const urls = {};
|
|
17609
|
+
for (const key of FORM_URL_KEYS) {
|
|
17610
|
+
const value = raw[key];
|
|
17611
|
+
if (typeof value === "string") urls[key] = value;
|
|
17612
|
+
}
|
|
17613
|
+
return Object.keys(urls).length > 0 ? urls : void 0;
|
|
17614
|
+
}
|
|
17573
17615
|
function getFormNodes(data) {
|
|
17574
17616
|
return data.node ?? data.nodes ?? [];
|
|
17575
17617
|
}
|
|
@@ -17596,11 +17638,13 @@ function projectNext(node, logicByNode) {
|
|
|
17596
17638
|
}
|
|
17597
17639
|
function projectFormState(data) {
|
|
17598
17640
|
const { nodes, logicByNode } = orderNodesWithLogic(data);
|
|
17641
|
+
const urls = projectUrls(data);
|
|
17599
17642
|
return {
|
|
17600
17643
|
form_id: String(data.form_id ?? ""),
|
|
17601
17644
|
title: String(data.title ?? ""),
|
|
17602
17645
|
is_live: Boolean(data.is_live),
|
|
17603
17646
|
has_unpublished_changes: Boolean(data.has_unpublished_changes),
|
|
17647
|
+
...urls ? { urls } : {},
|
|
17604
17648
|
nodes: nodes.map((q) => {
|
|
17605
17649
|
const options = q.node_options ?? q.options;
|
|
17606
17650
|
const config2 = projectConfig(q.type, q.config);
|
|
@@ -17629,6 +17673,18 @@ function formatFormState(data) {
|
|
|
17629
17673
|
`Unpublished changes: yes - respondents still see the last published version. Send is_live:true via clipform_update_form to publish.`
|
|
17630
17674
|
);
|
|
17631
17675
|
}
|
|
17676
|
+
const urls = projectUrls(data);
|
|
17677
|
+
if (urls) {
|
|
17678
|
+
lines.push(``, `Links (exact values - constructed or guessed URLs return 404):`);
|
|
17679
|
+
if (urls.viewer) lines.push(` Viewer (respondent view): ${urls.viewer}`);
|
|
17680
|
+
if (urls.builder) lines.push(` Builder: ${urls.builder}`);
|
|
17681
|
+
if (urls.summary) lines.push(` Summary: ${urls.summary}`);
|
|
17682
|
+
if (urls.responses) lines.push(` Responses: ${urls.responses}`);
|
|
17683
|
+
if (urls.analytics) lines.push(` Analytics: ${urls.analytics}`);
|
|
17684
|
+
if (urls.share) lines.push(` Share: ${urls.share}`);
|
|
17685
|
+
if (urls.embed) lines.push(` Embed: ${urls.embed}`);
|
|
17686
|
+
if (urls.dynamic_link) lines.push(` Dynamic link: ${urls.dynamic_link}`);
|
|
17687
|
+
}
|
|
17632
17688
|
lines.push(``, `Nodes (in order):`);
|
|
17633
17689
|
for (let i = 0; i < nodes.length; i++) {
|
|
17634
17690
|
const q = nodes[i];
|
|
@@ -17668,6 +17724,16 @@ function registerGetFormTool(server) {
|
|
|
17668
17724
|
title: external_exports.string().describe("Form title"),
|
|
17669
17725
|
is_live: external_exports.boolean().describe("Whether the form is published"),
|
|
17670
17726
|
has_unpublished_changes: external_exports.boolean().describe("Live form has edits not yet republished"),
|
|
17727
|
+
urls: external_exports.object({
|
|
17728
|
+
viewer: external_exports.string().optional().describe("Live respondent link, or a scoped preview link for an anonymous caller's draft. Omitted for a non-anonymous caller's unpublished draft."),
|
|
17729
|
+
builder: external_exports.string().optional().describe("Build tab - the node/canvas editor. Not present for an anonymous caller."),
|
|
17730
|
+
summary: external_exports.string().optional().describe("Summary tab - form overview and key stats. Not present for an anonymous caller."),
|
|
17731
|
+
responses: external_exports.string().optional().describe("Responses tab - individual submissions. Not present for an anonymous caller."),
|
|
17732
|
+
analytics: external_exports.string().optional().describe("Analytics tab - views, starts, completion rate, drop-off per node. Not present for an anonymous caller."),
|
|
17733
|
+
share: external_exports.string().optional().describe("Share tab - link, QR, social. Not present for an anonymous caller."),
|
|
17734
|
+
embed: external_exports.string().optional().describe("Embed setup for websites. Not present for an anonymous caller."),
|
|
17735
|
+
dynamic_link: external_exports.string().optional().describe("Dynamic/personalized link setup. Not present for an anonymous caller.")
|
|
17736
|
+
}).optional().describe("Dashboard/viewer deeplinks for this form. An anonymous caller gets only 'viewer'."),
|
|
17671
17737
|
nodes: external_exports.array(
|
|
17672
17738
|
external_exports.object({
|
|
17673
17739
|
id: external_exports.string(),
|
|
@@ -17999,7 +18065,8 @@ function registerGetResultsTool(server) {
|
|
|
17999
18065
|
})
|
|
18000
18066
|
).optional().describe("Most recent open-text answers, capped at 20 - open questions only")
|
|
18001
18067
|
})
|
|
18002
|
-
).describe("Nodes in form order. Contact/file/draw/camera/payment nodes carry only total_responses/total_views - no per-response detail.")
|
|
18068
|
+
).describe("Nodes in form order. Contact/file/draw/camera/payment nodes carry only total_responses/total_views - no per-response detail."),
|
|
18069
|
+
results_url: external_exports.string().optional().describe("Dashboard Responses tab for this form. Not present for an anonymous caller.")
|
|
18003
18070
|
},
|
|
18004
18071
|
annotations: {
|
|
18005
18072
|
readOnlyHint: true,
|
|
@@ -18017,13 +18084,13 @@ function registerGetResultsTool(server) {
|
|
|
18017
18084
|
const nodes = (data.nodes || []).map(projectNode);
|
|
18018
18085
|
const structured = {
|
|
18019
18086
|
total_responses: data.total_responses,
|
|
18020
|
-
nodes
|
|
18087
|
+
nodes,
|
|
18088
|
+
...data.results_url ? { results_url: data.results_url } : {}
|
|
18021
18089
|
};
|
|
18022
18090
|
if (data.total_responses === 0) {
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
);
|
|
18091
|
+
const lines2 = [`This form has no responses yet (${nodes.length} node(s)).`];
|
|
18092
|
+
if (data.results_url) lines2.push(`View in the dashboard: ${data.results_url}`);
|
|
18093
|
+
return structuredResult(lines2.join("\n"), structured);
|
|
18027
18094
|
}
|
|
18028
18095
|
const lines = [`${data.total_responses} total response(s) across ${nodes.length} node(s).`];
|
|
18029
18096
|
const highlights = nodes.filter((n) => "options" in n || "recent_answers" in n).slice(0, 3);
|
|
@@ -18035,11 +18102,157 @@ function registerGetResultsTool(server) {
|
|
|
18035
18102
|
lines.push(`- ${n.prompt}: ${n.recent_answers.length} recent open-text answer(s)`);
|
|
18036
18103
|
}
|
|
18037
18104
|
}
|
|
18105
|
+
if (data.results_url) lines.push(`View in the dashboard: ${data.results_url}`);
|
|
18038
18106
|
return structuredResult(lines.join("\n"), structured);
|
|
18039
18107
|
}
|
|
18040
18108
|
);
|
|
18041
18109
|
}
|
|
18042
18110
|
|
|
18111
|
+
// src/tools/get-responses.ts
|
|
18112
|
+
var MAX_ANSWER_LENGTH = 300;
|
|
18113
|
+
var DEFAULT_LIMIT = 20;
|
|
18114
|
+
var MAX_LIMIT = 50;
|
|
18115
|
+
var REDACTED_TYPES = {
|
|
18116
|
+
details: "[contact details - view in dashboard]",
|
|
18117
|
+
contact: "[contact details - view in dashboard]",
|
|
18118
|
+
file_upload: "[file upload - view in dashboard]",
|
|
18119
|
+
payment: "[payment - view in dashboard]",
|
|
18120
|
+
open: ""
|
|
18121
|
+
// open is redacted ONLY when the answer is a recording (handled below)
|
|
18122
|
+
};
|
|
18123
|
+
function truncate(s, n = MAX_ANSWER_LENGTH) {
|
|
18124
|
+
return s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
18125
|
+
}
|
|
18126
|
+
function looksLikeMediaReference(display) {
|
|
18127
|
+
return /https?:\/\/|storage\/|\.(webm|mp4|mp3|wav|m4a|mov)\b/i.test(display);
|
|
18128
|
+
}
|
|
18129
|
+
function registerGetResponsesTool(server) {
|
|
18130
|
+
server.registerTool(
|
|
18131
|
+
"clipform_get_responses",
|
|
18132
|
+
{
|
|
18133
|
+
title: "List form responses",
|
|
18134
|
+
description: "List a Clipform form's individual responses (submissions) with time filtering - complements clipform_get_results (aggregates). Answers questions like 'what came in during the last 24 hours', 'what's the longest answer', 'what did people say'. Text answers only: contact details, file uploads, payments, and audio/video answers are redacted to placeholders - point the user at the dashboard for those. Responses are sorted newest first unless sort: 'oldest'.",
|
|
18135
|
+
inputSchema: {
|
|
18136
|
+
form_id: external_exports.string().describe("Form UUID (from clipform_list_forms / clipform_create_form)"),
|
|
18137
|
+
since: external_exports.string().datetime({ offset: true }).optional().describe("Only responses submitted at or after this ISO 8601 time (e.g. '2026-08-29T00:00:00Z')"),
|
|
18138
|
+
until: external_exports.string().datetime({ offset: true }).optional().describe("Only responses submitted at or before this ISO 8601 time"),
|
|
18139
|
+
query: external_exports.string().optional().describe("Only responses whose text answers contain this string (case-insensitive)"),
|
|
18140
|
+
sort: external_exports.enum(["newest", "oldest"]).optional().describe("Order by submission time. Default: newest"),
|
|
18141
|
+
limit: external_exports.number().int().min(1).max(MAX_LIMIT).optional().describe(`Max responses to return (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT})`)
|
|
18142
|
+
},
|
|
18143
|
+
outputSchema: {
|
|
18144
|
+
total_matching: external_exports.number().describe("Responses matching the filters (before the limit cap)"),
|
|
18145
|
+
returned: external_exports.number().describe("Responses in this result"),
|
|
18146
|
+
locked_over_plan_limit: external_exports.number().describe("Matching responses hidden by the plan's response limit"),
|
|
18147
|
+
responses: external_exports.array(
|
|
18148
|
+
external_exports.object({
|
|
18149
|
+
submitted_at: external_exports.string().describe("ISO 8601 submission time"),
|
|
18150
|
+
status: external_exports.string(),
|
|
18151
|
+
answers: external_exports.array(
|
|
18152
|
+
external_exports.object({
|
|
18153
|
+
question: external_exports.string(),
|
|
18154
|
+
type: external_exports.string(),
|
|
18155
|
+
answer: external_exports.string().describe("Text answer, or a redaction placeholder")
|
|
18156
|
+
})
|
|
18157
|
+
)
|
|
18158
|
+
})
|
|
18159
|
+
).describe("Matching responses, filtered/sorted/capped per the inputs")
|
|
18160
|
+
},
|
|
18161
|
+
annotations: {
|
|
18162
|
+
readOnlyHint: true,
|
|
18163
|
+
destructiveHint: false,
|
|
18164
|
+
idempotentHint: true,
|
|
18165
|
+
openWorldHint: false
|
|
18166
|
+
}
|
|
18167
|
+
},
|
|
18168
|
+
async ({ form_id, since, until, query, sort, limit }) => {
|
|
18169
|
+
const [subsResult, formResult] = await Promise.all([
|
|
18170
|
+
callApi(`/forms/${form_id}/submissions`),
|
|
18171
|
+
callApi(`/forms/${form_id}`)
|
|
18172
|
+
]);
|
|
18173
|
+
if (!subsResult.ok) {
|
|
18174
|
+
return errorResult(`Could not fetch responses: ${subsResult.error}`);
|
|
18175
|
+
}
|
|
18176
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
18177
|
+
if (formResult.ok) {
|
|
18178
|
+
const nodes = formResult.data?.nodes ?? [];
|
|
18179
|
+
for (const n of nodes) nodeById.set(n.id, n);
|
|
18180
|
+
}
|
|
18181
|
+
const rows = subsResult.data ?? [];
|
|
18182
|
+
const sinceMs = since ? Date.parse(since) : null;
|
|
18183
|
+
const untilMs = until ? Date.parse(until) : null;
|
|
18184
|
+
const needle = query?.toLowerCase() ?? null;
|
|
18185
|
+
let lockedCount = 0;
|
|
18186
|
+
const projected = [];
|
|
18187
|
+
for (const row of rows) {
|
|
18188
|
+
if (row.locked) {
|
|
18189
|
+
lockedCount++;
|
|
18190
|
+
continue;
|
|
18191
|
+
}
|
|
18192
|
+
const submittedAt = row.completed_at ?? row.created_at;
|
|
18193
|
+
if (!submittedAt) continue;
|
|
18194
|
+
const ms = Date.parse(submittedAt);
|
|
18195
|
+
if (sinceMs !== null && ms < sinceMs) continue;
|
|
18196
|
+
if (untilMs !== null && ms > untilMs) continue;
|
|
18197
|
+
const answers = [];
|
|
18198
|
+
for (const [nodeId, resp] of Object.entries(row.node_responses ?? {})) {
|
|
18199
|
+
const node = nodeById.get(nodeId);
|
|
18200
|
+
const type = node?.type ?? (typeof resp?.type === "string" ? resp.type : "unknown");
|
|
18201
|
+
const question = truncate(node?.prompt || type, 120);
|
|
18202
|
+
const placeholder = REDACTED_TYPES[type];
|
|
18203
|
+
if (placeholder) {
|
|
18204
|
+
answers.push({ question, type, answer: placeholder });
|
|
18205
|
+
continue;
|
|
18206
|
+
}
|
|
18207
|
+
const display = typeof resp?.display === "string" ? resp.display : "";
|
|
18208
|
+
if (!display) continue;
|
|
18209
|
+
if (looksLikeMediaReference(display)) {
|
|
18210
|
+
answers.push({ question, type, answer: "[recording - view in dashboard]" });
|
|
18211
|
+
continue;
|
|
18212
|
+
}
|
|
18213
|
+
answers.push({ question, type, answer: truncate(display) });
|
|
18214
|
+
}
|
|
18215
|
+
if (needle !== null) {
|
|
18216
|
+
const haystack = answers.map((a) => a.answer.toLowerCase()).join("\n");
|
|
18217
|
+
if (!haystack.includes(needle)) continue;
|
|
18218
|
+
}
|
|
18219
|
+
projected.push({
|
|
18220
|
+
submitted_at: submittedAt,
|
|
18221
|
+
submitted_ms: ms,
|
|
18222
|
+
status: row.status ?? "unknown",
|
|
18223
|
+
answers
|
|
18224
|
+
});
|
|
18225
|
+
}
|
|
18226
|
+
projected.sort(
|
|
18227
|
+
(a, b) => sort === "oldest" ? a.submitted_ms - b.submitted_ms : b.submitted_ms - a.submitted_ms
|
|
18228
|
+
);
|
|
18229
|
+
const totalMatching = projected.length;
|
|
18230
|
+
const capped = projected.slice(0, limit ?? DEFAULT_LIMIT).map(({ submitted_ms: _ms, ...rest }) => rest);
|
|
18231
|
+
const filterBits = [
|
|
18232
|
+
since ? `since ${since}` : null,
|
|
18233
|
+
until ? `until ${until}` : null,
|
|
18234
|
+
query ? `matching "${query}"` : null
|
|
18235
|
+
].filter(Boolean);
|
|
18236
|
+
const lines = [
|
|
18237
|
+
`${totalMatching} response(s)${filterBits.length ? ` ${filterBits.join(", ")}` : ""}${lockedCount > 0 ? ` (+${lockedCount} over the plan's response limit - not shown; upgrade or view counts in clipform_get_results)` : ""}. Showing ${capped.length}, ${sort === "oldest" ? "oldest" : "newest"} first.`,
|
|
18238
|
+
``,
|
|
18239
|
+
...capped.flatMap((r) => [
|
|
18240
|
+
`\u2014 ${r.submitted_at} [${r.status}]`,
|
|
18241
|
+
...r.answers.map((a) => ` ${a.question}: ${a.answer}`)
|
|
18242
|
+
]),
|
|
18243
|
+
``,
|
|
18244
|
+
`Contact details, files, payments, and recordings are redacted here - the dashboard shows full response detail. For aggregate stats use clipform_get_results.`
|
|
18245
|
+
];
|
|
18246
|
+
return structuredResult(lines.join("\n"), {
|
|
18247
|
+
total_matching: totalMatching,
|
|
18248
|
+
returned: capped.length,
|
|
18249
|
+
locked_over_plan_limit: lockedCount,
|
|
18250
|
+
responses: capped
|
|
18251
|
+
});
|
|
18252
|
+
}
|
|
18253
|
+
);
|
|
18254
|
+
}
|
|
18255
|
+
|
|
18043
18256
|
// src/tools/whoami.ts
|
|
18044
18257
|
function registerWhoamiTool(server) {
|
|
18045
18258
|
server.registerTool(
|
|
@@ -18058,7 +18271,11 @@ function registerWhoamiTool(server) {
|
|
|
18058
18271
|
node_limit: external_exports.number().nullable().describe("Max nodes per form, or null if unlimited"),
|
|
18059
18272
|
form_limit: external_exports.number().nullable().describe("Max forms in the workspace, or null if unlimited")
|
|
18060
18273
|
}).describe("Plan limits for the active workspace"),
|
|
18061
|
-
workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string() })).optional().describe("All workspaces in the caller's company - only present for session (dashboard) auth")
|
|
18274
|
+
workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string() })).optional().describe("All workspaces in the caller's company - only present for session (dashboard) auth"),
|
|
18275
|
+
links: external_exports.object({
|
|
18276
|
+
dashboard: external_exports.string().describe("Dashboard home for the active workspace."),
|
|
18277
|
+
forms: external_exports.string().describe("Forms list for the active workspace.")
|
|
18278
|
+
}).optional().describe("Workspace-level dashboard links - only present for a non-anonymous caller.")
|
|
18062
18279
|
},
|
|
18063
18280
|
annotations: {
|
|
18064
18281
|
readOnlyHint: true,
|
|
@@ -18092,9 +18309,16 @@ function registerWhoamiTool(server) {
|
|
|
18092
18309
|
if (Array.isArray(me.workspaces)) {
|
|
18093
18310
|
structured.workspaces = me.workspaces.map((w) => ({ id: w.id, name: w.name }));
|
|
18094
18311
|
}
|
|
18312
|
+
if (me.links && typeof me.links.dashboard === "string" && typeof me.links.forms === "string") {
|
|
18313
|
+
structured.links = { dashboard: me.links.dashboard, forms: me.links.forms };
|
|
18314
|
+
}
|
|
18095
18315
|
const workspaceLine = workspace ? `workspace '${workspace.name}'` : "no workspace (anonymous)";
|
|
18096
|
-
const
|
|
18097
|
-
|
|
18316
|
+
const lines = [`Auth: ${authMode}. ${workspaceLine}. Plan: ${plan.name}.`];
|
|
18317
|
+
if (structured.links) {
|
|
18318
|
+
const links = structured.links;
|
|
18319
|
+
lines.push(`Dashboard: ${links.dashboard}`, `Forms list: ${links.forms}`);
|
|
18320
|
+
}
|
|
18321
|
+
return structuredResult(lines.join("\n"), structured);
|
|
18098
18322
|
}
|
|
18099
18323
|
);
|
|
18100
18324
|
}
|
|
@@ -20235,7 +20459,7 @@ mainlandOnly excludes small islands and overseas territories (e.g. Corsica for F
|
|
|
20235
20459
|
}
|
|
20236
20460
|
|
|
20237
20461
|
// src/tools/get-guide.ts
|
|
20238
|
-
function registerGetGuideTool(server) {
|
|
20462
|
+
function registerGetGuideTool(server, caps = FULL_CAPS) {
|
|
20239
20463
|
const aliasHint = Object.entries(FORM_TYPE_ALIASES).map(([alias, target]) => `${alias} \u2192 ${target}`).join(", ");
|
|
20240
20464
|
server.registerTool(
|
|
20241
20465
|
"clipform_get_guide",
|
|
@@ -20270,7 +20494,7 @@ Quiz variants (optional): ${QUIZ_VARIANTS.join(", ")} - appends variant-specific
|
|
|
20270
20494
|
const type = resolved;
|
|
20271
20495
|
const validVariant = type === "quiz" ? variant : void 0;
|
|
20272
20496
|
const uri = getGuideUri(type, validVariant);
|
|
20273
|
-
const content = await fetchGuideText(uri) ?? guideFallbackText(uri);
|
|
20497
|
+
const content = applyCapsToGuide(await fetchGuideText(uri) ?? guideFallbackText(uri), caps);
|
|
20274
20498
|
return {
|
|
20275
20499
|
content: [{
|
|
20276
20500
|
type: "resource",
|
|
@@ -20286,7 +20510,7 @@ Quiz variants (optional): ${QUIZ_VARIANTS.join(", ")} - appends variant-specific
|
|
|
20286
20510
|
}
|
|
20287
20511
|
|
|
20288
20512
|
// src/tools/get-workflow.ts
|
|
20289
|
-
function registerGetWorkflowTool(server) {
|
|
20513
|
+
function registerGetWorkflowTool(server, caps = FULL_CAPS) {
|
|
20290
20514
|
const aliasHint = Object.entries(FORM_TYPE_ALIASES).map(([alias, target]) => `${alias} \u2192 ${target}`).join(", ");
|
|
20291
20515
|
server.registerTool(
|
|
20292
20516
|
"clipform_get_workflow",
|
|
@@ -20309,7 +20533,9 @@ Optional args by type:
|
|
|
20309
20533
|
- funnel: outcomes, criteria, needs_contact (true)
|
|
20310
20534
|
- testimonial: use_case
|
|
20311
20535
|
- application: role
|
|
20312
|
-
- booking: event_name, event_type
|
|
20536
|
+
- booking: event_name, event_type
|
|
20537
|
+
- intake: use_case, response_format (all)
|
|
20538
|
+
- lead-capture: use_case`,
|
|
20313
20539
|
inputSchema: {
|
|
20314
20540
|
type: external_exports.string().describe("Form type to get the build workflow for (accepts aliases like 'feedback' \u2192 survey)"),
|
|
20315
20541
|
variant: external_exports.enum(QUIZ_VARIANTS).optional().describe("Quiz sub-variant. Only used when type is 'quiz'. Omit for a standard scored quiz."),
|
|
@@ -20344,7 +20570,7 @@ Optional args by type:
|
|
|
20344
20570
|
}
|
|
20345
20571
|
const type = resolved;
|
|
20346
20572
|
const validVariant = type === "quiz" ? variant : void 0;
|
|
20347
|
-
const text = await getWorkflowText(type, args, validVariant);
|
|
20573
|
+
const text = await getWorkflowText(type, args, validVariant, caps);
|
|
20348
20574
|
return textResult(text);
|
|
20349
20575
|
}
|
|
20350
20576
|
);
|
|
@@ -20354,29 +20580,25 @@ Optional args by type:
|
|
|
20354
20580
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20355
20581
|
var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
20356
20582
|
var MCP_VERSION = pkg.version;
|
|
20583
|
+
function buildInstructions(caps) {
|
|
20584
|
+
return [
|
|
20585
|
+
"Clipform builds interactive video-style forms. When the brief is clear, build the form directly; when it's vague, ask one or two short questions first.",
|
|
20586
|
+
caps.media && "Prefer rich media: video over images over plain text.",
|
|
20587
|
+
caps.media ? "Before building, derive a register from the topic and audience (e.g. hype, playful, warm, considered, elegant, sombre); if the user states a mood explicitly, that wins. State the register once, then apply it to every rendering choice - narration tone, motion preset, music, pacing. Never mix registers in one form." : "Before building, derive a register from the topic and audience (e.g. hype, playful, warm, considered, elegant, sombre); if the user states a mood explicitly, that wins. State the register once, then apply it to every question, option, and end-screen line. Never mix registers in one form.",
|
|
20588
|
+
"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.",
|
|
20589
|
+
"Every *_id argument is a UUID returned by the tools - not the short share_id from a form URL.",
|
|
20590
|
+
"For an account-scoped request, call clipform_whoami first to confirm the active workspace and plan.",
|
|
20591
|
+
caps.media ? "Deleting a form, node, or its media is permanent; those tools ask the user to confirm before running." : "Deleting a form or node is permanent; those tools ask the user to confirm before running."
|
|
20592
|
+
].filter((line) => typeof line === "string").join(" ");
|
|
20593
|
+
}
|
|
20357
20594
|
function createServer(options) {
|
|
20595
|
+
const caps = capsFromToolsets(options?.toolsets);
|
|
20358
20596
|
const server = new McpServer(
|
|
20359
20597
|
{
|
|
20360
20598
|
name: "clipform-mcp-server",
|
|
20361
20599
|
version: MCP_VERSION
|
|
20362
20600
|
},
|
|
20363
|
-
{
|
|
20364
|
-
// Lands directly in the client's system prompt. This is the only
|
|
20365
|
-
// high-level guidance external clients (claude.ai, Desktop, ChatGPT App)
|
|
20366
|
-
// receive - they have no equivalent of the bot's system prompt. It is
|
|
20367
|
-
// also the correct home for cross-tool / sequencing guidance, which must
|
|
20368
|
-
// NOT go in individual tool descriptions (treated as prompt injection at
|
|
20369
|
-
// directory review).
|
|
20370
|
-
instructions: [
|
|
20371
|
-
"Clipform builds interactive video-style forms. When the brief is clear, build the form directly; when it's vague, ask one or two short questions first.",
|
|
20372
|
-
"Prefer rich media: video over images over plain text.",
|
|
20373
|
-
"Before building, derive a register from the topic and audience (e.g. hype, playful, warm, considered, elegant, sombre); if the user states a mood explicitly, that wins. State the register once, then apply it to every rendering choice - narration tone, motion preset, music, pacing. Never mix registers in one form.",
|
|
20374
|
-
"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.",
|
|
20375
|
-
"Every *_id argument is a UUID returned by the tools - not the short share_id from a form URL.",
|
|
20376
|
-
"For an account-scoped request, call clipform_whoami first to confirm the active workspace and plan.",
|
|
20377
|
-
"Deleting a form, node, or its media is permanent; those tools ask the user to confirm before running."
|
|
20378
|
-
].join(" ")
|
|
20379
|
-
}
|
|
20601
|
+
{ instructions: buildInstructions(caps) }
|
|
20380
20602
|
);
|
|
20381
20603
|
const _registerTool = server.registerTool.bind(server);
|
|
20382
20604
|
server.registerTool = (name, config2, handler) => {
|
|
@@ -20412,6 +20634,7 @@ function createServer(options) {
|
|
|
20412
20634
|
registerUpdateFormTool(server);
|
|
20413
20635
|
registerDeleteFormTool(server);
|
|
20414
20636
|
registerGetResultsTool(server);
|
|
20637
|
+
registerGetResponsesTool(server);
|
|
20415
20638
|
registerAddNodeTool(server);
|
|
20416
20639
|
registerUpdateNodeTool(server);
|
|
20417
20640
|
registerDeleteNodeTool(server);
|
|
@@ -20434,16 +20657,17 @@ function createServer(options) {
|
|
|
20434
20657
|
registerListAssetsTool(server);
|
|
20435
20658
|
registerCheckRenderTool(server);
|
|
20436
20659
|
registerFetchBoundaryTool(server);
|
|
20437
|
-
registerGetGuideTool(server);
|
|
20438
|
-
registerGetWorkflowTool(server);
|
|
20439
|
-
registerPrompts(server);
|
|
20440
|
-
registerResources(server);
|
|
20660
|
+
registerGetGuideTool(server, caps);
|
|
20661
|
+
registerGetWorkflowTool(server, caps);
|
|
20662
|
+
registerPrompts(server, caps);
|
|
20663
|
+
registerResources(server, caps);
|
|
20441
20664
|
return server;
|
|
20442
20665
|
}
|
|
20443
20666
|
|
|
20444
20667
|
export {
|
|
20445
20668
|
JSONRPCMessageSchema,
|
|
20446
20669
|
RENDER_TIMING,
|
|
20670
|
+
buildInstructions,
|
|
20447
20671
|
createServer
|
|
20448
20672
|
};
|
|
20449
|
-
//# sourceMappingURL=chunk-
|
|
20673
|
+
//# sourceMappingURL=chunk-ZOBK6CK7.js.map
|