@socialneuron/mcp-server 1.5.0 → 1.5.2
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/CHANGELOG.md +16 -0
- package/dist/http.js +535 -128
- package/dist/index.js +535 -128
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -853,6 +853,41 @@ function checkRateLimit(category, key) {
|
|
|
853
853
|
|
|
854
854
|
// src/tools/ideation.ts
|
|
855
855
|
init_supabase();
|
|
856
|
+
|
|
857
|
+
// src/lib/tool-errors.ts
|
|
858
|
+
function formatToolError(rawMessage) {
|
|
859
|
+
const msg = rawMessage.toLowerCase();
|
|
860
|
+
if (msg.includes("rate limit") || msg.includes("too many requests")) {
|
|
861
|
+
return `${rawMessage} Reduce request frequency or wait before retrying.`;
|
|
862
|
+
}
|
|
863
|
+
if (msg.includes("insufficient credit") || msg.includes("budget") || msg.includes("spending cap")) {
|
|
864
|
+
return `${rawMessage} Call get_credit_balance to check remaining credits. Consider a cheaper model or wait for monthly refresh.`;
|
|
865
|
+
}
|
|
866
|
+
if (msg.includes("oauth") || msg.includes("token expired") || msg.includes("not connected") || msg.includes("reconnect")) {
|
|
867
|
+
return `${rawMessage} Call list_connected_accounts to check status. User may need to reconnect at socialneuron.com/settings/connections.`;
|
|
868
|
+
}
|
|
869
|
+
if (msg.includes("generation failed") || msg.includes("failed to start") || msg.includes("no job id") || msg.includes("could not be parsed")) {
|
|
870
|
+
return `${rawMessage} Try simplifying the prompt, using a different model, or check credits with get_credit_balance.`;
|
|
871
|
+
}
|
|
872
|
+
if (msg.includes("not found") || msg.includes("no ") && msg.includes(" found")) {
|
|
873
|
+
return `${rawMessage} Verify the ID is correct \u2014 use the corresponding list tool to find valid IDs.`;
|
|
874
|
+
}
|
|
875
|
+
if (msg.includes("not accessible") || msg.includes("unauthorized") || msg.includes("permission")) {
|
|
876
|
+
return `${rawMessage} Check API key scopes with get_credit_balance. A higher-tier plan may be required.`;
|
|
877
|
+
}
|
|
878
|
+
if (msg.includes("ssrf") || msg.includes("url blocked")) {
|
|
879
|
+
return `${rawMessage} The URL was blocked for security. Use a publicly accessible HTTPS URL.`;
|
|
880
|
+
}
|
|
881
|
+
if (msg.includes("failed to schedule") || msg.includes("scheduling failed")) {
|
|
882
|
+
return `${rawMessage} Verify platform OAuth is active with list_connected_accounts, then retry.`;
|
|
883
|
+
}
|
|
884
|
+
if (msg.includes("no posts") || msg.includes("plan") && msg.includes("has no")) {
|
|
885
|
+
return `${rawMessage} Generate a plan with plan_content_week first, then save with save_content_plan.`;
|
|
886
|
+
}
|
|
887
|
+
return rawMessage;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// src/tools/ideation.ts
|
|
856
891
|
function registerIdeationTools(server) {
|
|
857
892
|
server.tool(
|
|
858
893
|
"generate_content",
|
|
@@ -886,6 +921,13 @@ function registerIdeationTools(server) {
|
|
|
886
921
|
"Project ID to auto-load brand profile and performance context for prompt enrichment."
|
|
887
922
|
)
|
|
888
923
|
},
|
|
924
|
+
{
|
|
925
|
+
title: "Generate Content",
|
|
926
|
+
readOnlyHint: false,
|
|
927
|
+
destructiveHint: false,
|
|
928
|
+
idempotentHint: false,
|
|
929
|
+
openWorldHint: true
|
|
930
|
+
},
|
|
889
931
|
async ({
|
|
890
932
|
prompt,
|
|
891
933
|
content_type,
|
|
@@ -1019,7 +1061,7 @@ Content Type: ${content_type}`;
|
|
|
1019
1061
|
content: [
|
|
1020
1062
|
{
|
|
1021
1063
|
type: "text",
|
|
1022
|
-
text: `Content generation failed: ${error}`
|
|
1064
|
+
text: formatToolError(`Content generation failed: ${error}`)
|
|
1023
1065
|
}
|
|
1024
1066
|
],
|
|
1025
1067
|
isError: true
|
|
@@ -1049,6 +1091,13 @@ Content Type: ${content_type}`;
|
|
|
1049
1091
|
),
|
|
1050
1092
|
force_refresh: z.boolean().optional().describe("Skip the server-side cache and fetch fresh data.")
|
|
1051
1093
|
},
|
|
1094
|
+
{
|
|
1095
|
+
title: "Fetch Trends",
|
|
1096
|
+
readOnlyHint: true,
|
|
1097
|
+
destructiveHint: false,
|
|
1098
|
+
idempotentHint: false,
|
|
1099
|
+
openWorldHint: true
|
|
1100
|
+
},
|
|
1052
1101
|
async ({ source, category, niche, url, force_refresh }) => {
|
|
1053
1102
|
if ((source === "rss" || source === "url") && !url) {
|
|
1054
1103
|
return {
|
|
@@ -1077,7 +1126,7 @@ Content Type: ${content_type}`;
|
|
|
1077
1126
|
content: [
|
|
1078
1127
|
{
|
|
1079
1128
|
type: "text",
|
|
1080
|
-
text: `Failed to fetch trends: ${error}`
|
|
1129
|
+
text: formatToolError(`Failed to fetch trends: ${error}`)
|
|
1081
1130
|
}
|
|
1082
1131
|
],
|
|
1083
1132
|
isError: true
|
|
@@ -1153,6 +1202,13 @@ Content Type: ${content_type}`;
|
|
|
1153
1202
|
"Optional project ID to load platform voice overrides from brand profile."
|
|
1154
1203
|
)
|
|
1155
1204
|
},
|
|
1205
|
+
{
|
|
1206
|
+
title: "Adapt Content",
|
|
1207
|
+
readOnlyHint: false,
|
|
1208
|
+
destructiveHint: false,
|
|
1209
|
+
idempotentHint: false,
|
|
1210
|
+
openWorldHint: true
|
|
1211
|
+
},
|
|
1156
1212
|
async ({
|
|
1157
1213
|
content,
|
|
1158
1214
|
source_platform,
|
|
@@ -1238,7 +1294,7 @@ ${content}`,
|
|
|
1238
1294
|
content: [
|
|
1239
1295
|
{
|
|
1240
1296
|
type: "text",
|
|
1241
|
-
text: `Content adaptation failed: ${error}`
|
|
1297
|
+
text: formatToolError(`Content adaptation failed: ${error}`)
|
|
1242
1298
|
}
|
|
1243
1299
|
],
|
|
1244
1300
|
isError: true
|
|
@@ -1312,7 +1368,7 @@ function sanitizeDbError(error) {
|
|
|
1312
1368
|
init_request_context();
|
|
1313
1369
|
|
|
1314
1370
|
// src/lib/version.ts
|
|
1315
|
-
var MCP_VERSION = "1.5.
|
|
1371
|
+
var MCP_VERSION = "1.5.2";
|
|
1316
1372
|
|
|
1317
1373
|
// src/tools/content.ts
|
|
1318
1374
|
var MAX_CREDITS_PER_RUN = Math.max(
|
|
@@ -1454,6 +1510,13 @@ function registerContentTools(server) {
|
|
|
1454
1510
|
),
|
|
1455
1511
|
response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
1456
1512
|
},
|
|
1513
|
+
{
|
|
1514
|
+
title: "Generate Video",
|
|
1515
|
+
readOnlyHint: false,
|
|
1516
|
+
destructiveHint: false,
|
|
1517
|
+
idempotentHint: false,
|
|
1518
|
+
openWorldHint: true
|
|
1519
|
+
},
|
|
1457
1520
|
async ({
|
|
1458
1521
|
prompt,
|
|
1459
1522
|
model,
|
|
@@ -1545,7 +1608,7 @@ function registerContentTools(server) {
|
|
|
1545
1608
|
content: [
|
|
1546
1609
|
{
|
|
1547
1610
|
type: "text",
|
|
1548
|
-
text: `Video generation failed to start: ${error}`
|
|
1611
|
+
text: formatToolError(`Video generation failed to start: ${error}`)
|
|
1549
1612
|
}
|
|
1550
1613
|
],
|
|
1551
1614
|
isError: true
|
|
@@ -1562,7 +1625,7 @@ function registerContentTools(server) {
|
|
|
1562
1625
|
content: [
|
|
1563
1626
|
{
|
|
1564
1627
|
type: "text",
|
|
1565
|
-
text: "Video generation failed: no job ID returned."
|
|
1628
|
+
text: formatToolError("Video generation failed: no job ID returned.")
|
|
1566
1629
|
}
|
|
1567
1630
|
],
|
|
1568
1631
|
isError: true
|
|
@@ -1652,6 +1715,13 @@ function registerContentTools(server) {
|
|
|
1652
1715
|
),
|
|
1653
1716
|
response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
1654
1717
|
},
|
|
1718
|
+
{
|
|
1719
|
+
title: "Generate Image",
|
|
1720
|
+
readOnlyHint: false,
|
|
1721
|
+
destructiveHint: false,
|
|
1722
|
+
idempotentHint: false,
|
|
1723
|
+
openWorldHint: true
|
|
1724
|
+
},
|
|
1655
1725
|
async ({ prompt, model, aspect_ratio, image_url, response_format }) => {
|
|
1656
1726
|
const format = response_format ?? "text";
|
|
1657
1727
|
const startedAt = Date.now();
|
|
@@ -1731,7 +1801,7 @@ function registerContentTools(server) {
|
|
|
1731
1801
|
content: [
|
|
1732
1802
|
{
|
|
1733
1803
|
type: "text",
|
|
1734
|
-
text: `Image generation failed to start: ${error}`
|
|
1804
|
+
text: formatToolError(`Image generation failed to start: ${error}`)
|
|
1735
1805
|
}
|
|
1736
1806
|
],
|
|
1737
1807
|
isError: true
|
|
@@ -1748,7 +1818,7 @@ function registerContentTools(server) {
|
|
|
1748
1818
|
content: [
|
|
1749
1819
|
{
|
|
1750
1820
|
type: "text",
|
|
1751
|
-
text: "Image generation failed: no job ID returned."
|
|
1821
|
+
text: formatToolError("Image generation failed: no job ID returned.")
|
|
1752
1822
|
}
|
|
1753
1823
|
],
|
|
1754
1824
|
isError: true
|
|
@@ -1815,6 +1885,13 @@ function registerContentTools(server) {
|
|
|
1815
1885
|
),
|
|
1816
1886
|
response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
1817
1887
|
},
|
|
1888
|
+
{
|
|
1889
|
+
title: "Check Job Status",
|
|
1890
|
+
readOnlyHint: true,
|
|
1891
|
+
destructiveHint: false,
|
|
1892
|
+
idempotentHint: true,
|
|
1893
|
+
openWorldHint: true
|
|
1894
|
+
},
|
|
1818
1895
|
async ({ job_id, response_format }) => {
|
|
1819
1896
|
const format = response_format ?? "text";
|
|
1820
1897
|
const startedAt = Date.now();
|
|
@@ -1865,7 +1942,7 @@ function registerContentTools(server) {
|
|
|
1865
1942
|
content: [
|
|
1866
1943
|
{
|
|
1867
1944
|
type: "text",
|
|
1868
|
-
text: `Failed to look up job: ${sanitizeDbError(jobError)}`
|
|
1945
|
+
text: formatToolError(`Failed to look up job: ${sanitizeDbError(jobError)}`)
|
|
1869
1946
|
}
|
|
1870
1947
|
],
|
|
1871
1948
|
isError: true
|
|
@@ -1882,7 +1959,7 @@ function registerContentTools(server) {
|
|
|
1882
1959
|
content: [
|
|
1883
1960
|
{
|
|
1884
1961
|
type: "text",
|
|
1885
|
-
text: `No job found with ID "${job_id}". The ID may be incorrect or the job has expired.`
|
|
1962
|
+
text: formatToolError(`No job found with ID "${job_id}". The ID may be incorrect or the job has expired.`)
|
|
1886
1963
|
}
|
|
1887
1964
|
],
|
|
1888
1965
|
isError: true
|
|
@@ -2012,6 +2089,13 @@ function registerContentTools(server) {
|
|
|
2012
2089
|
"Response format. Defaults to json for structured storyboard data."
|
|
2013
2090
|
)
|
|
2014
2091
|
},
|
|
2092
|
+
{
|
|
2093
|
+
title: "Create Storyboard",
|
|
2094
|
+
readOnlyHint: false,
|
|
2095
|
+
destructiveHint: false,
|
|
2096
|
+
idempotentHint: false,
|
|
2097
|
+
openWorldHint: true
|
|
2098
|
+
},
|
|
2015
2099
|
async ({
|
|
2016
2100
|
concept,
|
|
2017
2101
|
brand_context,
|
|
@@ -2123,7 +2207,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2123
2207
|
content: [
|
|
2124
2208
|
{
|
|
2125
2209
|
type: "text",
|
|
2126
|
-
text: `Storyboard generation failed: ${error}`
|
|
2210
|
+
text: formatToolError(`Storyboard generation failed: ${error}`)
|
|
2127
2211
|
}
|
|
2128
2212
|
],
|
|
2129
2213
|
isError: true
|
|
@@ -2192,6 +2276,13 @@ Return ONLY valid JSON in this exact format:
|
|
|
2192
2276
|
speed: z2.number().min(0.5).max(2).optional().describe("Speech speed multiplier. 1.0 is normal. Defaults to 1.0."),
|
|
2193
2277
|
response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
2194
2278
|
},
|
|
2279
|
+
{
|
|
2280
|
+
title: "Generate Voiceover",
|
|
2281
|
+
readOnlyHint: false,
|
|
2282
|
+
destructiveHint: false,
|
|
2283
|
+
idempotentHint: false,
|
|
2284
|
+
openWorldHint: true
|
|
2285
|
+
},
|
|
2195
2286
|
async ({ text, voice, speed, response_format }) => {
|
|
2196
2287
|
const format = response_format ?? "text";
|
|
2197
2288
|
const startedAt = Date.now();
|
|
@@ -2251,7 +2342,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2251
2342
|
content: [
|
|
2252
2343
|
{
|
|
2253
2344
|
type: "text",
|
|
2254
|
-
text: `Voiceover generation failed: ${error}`
|
|
2345
|
+
text: formatToolError(`Voiceover generation failed: ${error}`)
|
|
2255
2346
|
}
|
|
2256
2347
|
],
|
|
2257
2348
|
isError: true
|
|
@@ -2268,7 +2359,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2268
2359
|
content: [
|
|
2269
2360
|
{
|
|
2270
2361
|
type: "text",
|
|
2271
|
-
text: "Voiceover generation failed: no audio URL returned."
|
|
2362
|
+
text: formatToolError("Voiceover generation failed: no audio URL returned.")
|
|
2272
2363
|
}
|
|
2273
2364
|
],
|
|
2274
2365
|
isError: true
|
|
@@ -2350,6 +2441,13 @@ Return ONLY valid JSON in this exact format:
|
|
|
2350
2441
|
project_id: z2.string().optional().describe("Project ID to associate the carousel with."),
|
|
2351
2442
|
response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to json.")
|
|
2352
2443
|
},
|
|
2444
|
+
{
|
|
2445
|
+
title: "Generate Carousel",
|
|
2446
|
+
readOnlyHint: false,
|
|
2447
|
+
destructiveHint: false,
|
|
2448
|
+
idempotentHint: false,
|
|
2449
|
+
openWorldHint: true
|
|
2450
|
+
},
|
|
2353
2451
|
async ({
|
|
2354
2452
|
topic,
|
|
2355
2453
|
template_id,
|
|
@@ -2424,7 +2522,7 @@ Return ONLY valid JSON in this exact format:
|
|
|
2424
2522
|
content: [
|
|
2425
2523
|
{
|
|
2426
2524
|
type: "text",
|
|
2427
|
-
text: `Carousel generation failed: ${error}`
|
|
2525
|
+
text: formatToolError(`Carousel generation failed: ${error}`)
|
|
2428
2526
|
}
|
|
2429
2527
|
],
|
|
2430
2528
|
isError: true
|
|
@@ -2684,6 +2782,13 @@ function registerDistributionTools(server) {
|
|
|
2684
2782
|
'If true, appends "Created with Social Neuron" to the caption. Default: false.'
|
|
2685
2783
|
)
|
|
2686
2784
|
},
|
|
2785
|
+
{
|
|
2786
|
+
title: "Schedule Post",
|
|
2787
|
+
readOnlyHint: false,
|
|
2788
|
+
destructiveHint: false,
|
|
2789
|
+
idempotentHint: false,
|
|
2790
|
+
openWorldHint: true
|
|
2791
|
+
},
|
|
2687
2792
|
async ({
|
|
2688
2793
|
media_url,
|
|
2689
2794
|
media_urls,
|
|
@@ -2764,7 +2869,7 @@ Created with Social Neuron`;
|
|
|
2764
2869
|
content: [
|
|
2765
2870
|
{
|
|
2766
2871
|
type: "text",
|
|
2767
|
-
text: `Failed to schedule post: ${error}`
|
|
2872
|
+
text: formatToolError(`Failed to schedule post: ${error}`)
|
|
2768
2873
|
}
|
|
2769
2874
|
],
|
|
2770
2875
|
isError: true
|
|
@@ -2835,6 +2940,13 @@ Created with Social Neuron`;
|
|
|
2835
2940
|
{
|
|
2836
2941
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
2837
2942
|
},
|
|
2943
|
+
{
|
|
2944
|
+
title: "List Connected Accounts",
|
|
2945
|
+
readOnlyHint: true,
|
|
2946
|
+
destructiveHint: false,
|
|
2947
|
+
idempotentHint: true,
|
|
2948
|
+
openWorldHint: false
|
|
2949
|
+
},
|
|
2838
2950
|
async ({ response_format }) => {
|
|
2839
2951
|
const format = response_format ?? "text";
|
|
2840
2952
|
const supabase = getSupabaseClient();
|
|
@@ -2845,7 +2957,7 @@ Created with Social Neuron`;
|
|
|
2845
2957
|
content: [
|
|
2846
2958
|
{
|
|
2847
2959
|
type: "text",
|
|
2848
|
-
text: `Failed to list connected accounts: ${sanitizeDbError(error)}`
|
|
2960
|
+
text: formatToolError(`Failed to list connected accounts: ${sanitizeDbError(error)}`)
|
|
2849
2961
|
}
|
|
2850
2962
|
],
|
|
2851
2963
|
isError: true
|
|
@@ -2913,6 +3025,13 @@ Created with Social Neuron`;
|
|
|
2913
3025
|
limit: z3.number().min(1).max(50).optional().describe("Maximum number of posts to return. Defaults to 20."),
|
|
2914
3026
|
response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
2915
3027
|
},
|
|
3028
|
+
{
|
|
3029
|
+
title: "List Recent Posts",
|
|
3030
|
+
readOnlyHint: true,
|
|
3031
|
+
destructiveHint: false,
|
|
3032
|
+
idempotentHint: true,
|
|
3033
|
+
openWorldHint: false
|
|
3034
|
+
},
|
|
2916
3035
|
async ({ platform: platform2, status, days, limit, response_format }) => {
|
|
2917
3036
|
const format = response_format ?? "text";
|
|
2918
3037
|
const supabase = getSupabaseClient();
|
|
@@ -2937,7 +3056,7 @@ Created with Social Neuron`;
|
|
|
2937
3056
|
content: [
|
|
2938
3057
|
{
|
|
2939
3058
|
type: "text",
|
|
2940
|
-
text: `Failed to list posts: ${sanitizeDbError(error)}`
|
|
3059
|
+
text: formatToolError(`Failed to list posts: ${sanitizeDbError(error)}`)
|
|
2941
3060
|
}
|
|
2942
3061
|
],
|
|
2943
3062
|
isError: true
|
|
@@ -3012,7 +3131,7 @@ Created with Social Neuron`;
|
|
|
3012
3131
|
};
|
|
3013
3132
|
server.tool(
|
|
3014
3133
|
"find_next_slots",
|
|
3015
|
-
"Find
|
|
3134
|
+
"Find the next available posting time slots that avoid conflicts with already-scheduled posts. Uses engagement data from get_best_posting_times to rank slots. Call this before schedule_content_plan to pick optimal, non-overlapping times for each post.",
|
|
3016
3135
|
{
|
|
3017
3136
|
platforms: z3.array(
|
|
3018
3137
|
z3.enum([
|
|
@@ -3025,11 +3144,18 @@ Created with Social Neuron`;
|
|
|
3025
3144
|
"threads",
|
|
3026
3145
|
"bluesky"
|
|
3027
3146
|
])
|
|
3028
|
-
).min(1),
|
|
3147
|
+
).min(1).describe("Platforms to find posting slots for."),
|
|
3029
3148
|
count: z3.number().min(1).max(20).default(7).describe("Number of slots to find"),
|
|
3030
3149
|
start_after: z3.string().optional().describe("ISO datetime, defaults to now"),
|
|
3031
3150
|
min_gap_hours: z3.number().min(1).max(24).default(4).describe("Minimum gap between posts on same platform"),
|
|
3032
|
-
response_format: z3.enum(["text", "json"]).default("text")
|
|
3151
|
+
response_format: z3.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
|
|
3152
|
+
},
|
|
3153
|
+
{
|
|
3154
|
+
title: "Find Next Posting Slots",
|
|
3155
|
+
readOnlyHint: true,
|
|
3156
|
+
destructiveHint: false,
|
|
3157
|
+
idempotentHint: true,
|
|
3158
|
+
openWorldHint: false
|
|
3033
3159
|
},
|
|
3034
3160
|
async ({
|
|
3035
3161
|
platforms,
|
|
@@ -3135,7 +3261,7 @@ Created with Social Neuron`;
|
|
|
3135
3261
|
});
|
|
3136
3262
|
return {
|
|
3137
3263
|
content: [
|
|
3138
|
-
{ type: "text", text: `Failed to find slots: ${message}` }
|
|
3264
|
+
{ type: "text", text: formatToolError(`Failed to find slots: ${message}`) }
|
|
3139
3265
|
],
|
|
3140
3266
|
isError: true
|
|
3141
3267
|
};
|
|
@@ -3149,20 +3275,20 @@ Created with Social Neuron`;
|
|
|
3149
3275
|
plan: z3.object({
|
|
3150
3276
|
posts: z3.array(
|
|
3151
3277
|
z3.object({
|
|
3152
|
-
id: z3.string(),
|
|
3153
|
-
caption: z3.string(),
|
|
3154
|
-
platform: z3.string(),
|
|
3155
|
-
title: z3.string().optional(),
|
|
3156
|
-
media_url: z3.string().optional(),
|
|
3157
|
-
schedule_at: z3.string().optional(),
|
|
3158
|
-
hashtags: z3.array(z3.string()).optional()
|
|
3278
|
+
id: z3.string().describe("Unique post identifier from the content plan."),
|
|
3279
|
+
caption: z3.string().describe("Post caption/body text."),
|
|
3280
|
+
platform: z3.string().describe("Target platform name (e.g. instagram, youtube)."),
|
|
3281
|
+
title: z3.string().optional().describe("Post title, required for YouTube."),
|
|
3282
|
+
media_url: z3.string().optional().describe("Public or R2 signed URL for the post media."),
|
|
3283
|
+
schedule_at: z3.string().optional().describe("ISO 8601 UTC datetime to publish (e.g. 2026-03-20T14:00:00Z)."),
|
|
3284
|
+
hashtags: z3.array(z3.string()).optional().describe("Hashtags to append to the caption.")
|
|
3159
3285
|
})
|
|
3160
3286
|
)
|
|
3161
|
-
}).passthrough().optional(),
|
|
3287
|
+
}).passthrough().optional().describe("Inline content plan object with a posts array. Provide this or plan_id."),
|
|
3162
3288
|
plan_id: z3.string().uuid().optional().describe("Persisted content plan ID from content_plans table"),
|
|
3163
3289
|
auto_slot: z3.boolean().default(true).describe("Auto-assign time slots for posts without schedule_at"),
|
|
3164
3290
|
dry_run: z3.boolean().default(false).describe("Preview without actually scheduling"),
|
|
3165
|
-
response_format: z3.enum(["text", "json"]).default("text"),
|
|
3291
|
+
response_format: z3.enum(["text", "json"]).default("text").describe("Response format. Defaults to text."),
|
|
3166
3292
|
enforce_quality: z3.boolean().default(true).describe(
|
|
3167
3293
|
"When true, block scheduling for posts that fail quality checks."
|
|
3168
3294
|
),
|
|
@@ -3172,6 +3298,13 @@ Created with Social Neuron`;
|
|
|
3172
3298
|
batch_size: z3.number().int().min(1).max(10).default(4).describe("Concurrent schedule calls per platform batch."),
|
|
3173
3299
|
idempotency_seed: z3.string().max(128).optional().describe("Optional stable seed used when building idempotency keys.")
|
|
3174
3300
|
},
|
|
3301
|
+
{
|
|
3302
|
+
title: "Schedule Content Plan",
|
|
3303
|
+
readOnlyHint: false,
|
|
3304
|
+
destructiveHint: false,
|
|
3305
|
+
idempotentHint: false,
|
|
3306
|
+
openWorldHint: true
|
|
3307
|
+
},
|
|
3175
3308
|
async ({
|
|
3176
3309
|
plan,
|
|
3177
3310
|
plan_id,
|
|
@@ -3198,7 +3331,7 @@ Created with Social Neuron`;
|
|
|
3198
3331
|
content: [
|
|
3199
3332
|
{
|
|
3200
3333
|
type: "text",
|
|
3201
|
-
text: `Failed to load content plan: ${sanitizeDbError(storedError)}`
|
|
3334
|
+
text: formatToolError(`Failed to load content plan: ${sanitizeDbError(storedError)}`)
|
|
3202
3335
|
}
|
|
3203
3336
|
],
|
|
3204
3337
|
isError: true
|
|
@@ -3209,7 +3342,7 @@ Created with Social Neuron`;
|
|
|
3209
3342
|
content: [
|
|
3210
3343
|
{
|
|
3211
3344
|
type: "text",
|
|
3212
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
3345
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
3213
3346
|
}
|
|
3214
3347
|
],
|
|
3215
3348
|
isError: true
|
|
@@ -3224,7 +3357,7 @@ Created with Social Neuron`;
|
|
|
3224
3357
|
content: [
|
|
3225
3358
|
{
|
|
3226
3359
|
type: "text",
|
|
3227
|
-
text: `Stored plan ${plan_id} has no posts array.`
|
|
3360
|
+
text: formatToolError(`Stored plan ${plan_id} has no posts array.`)
|
|
3228
3361
|
}
|
|
3229
3362
|
],
|
|
3230
3363
|
isError: true
|
|
@@ -3263,7 +3396,7 @@ Created with Social Neuron`;
|
|
|
3263
3396
|
content: [
|
|
3264
3397
|
{
|
|
3265
3398
|
type: "text",
|
|
3266
|
-
text: `Failed to load plan approvals: ${sanitizeDbError(approvalsError)}`
|
|
3399
|
+
text: formatToolError(`Failed to load plan approvals: ${sanitizeDbError(approvalsError)}`)
|
|
3267
3400
|
}
|
|
3268
3401
|
],
|
|
3269
3402
|
isError: true
|
|
@@ -3698,7 +3831,7 @@ Created with Social Neuron`;
|
|
|
3698
3831
|
content: [
|
|
3699
3832
|
{
|
|
3700
3833
|
type: "text",
|
|
3701
|
-
text: `Batch scheduling failed: ${message}`
|
|
3834
|
+
text: formatToolError(`Batch scheduling failed: ${message}`)
|
|
3702
3835
|
}
|
|
3703
3836
|
],
|
|
3704
3837
|
isError: true
|
|
@@ -3742,6 +3875,7 @@ function registerAnalyticsTools(server) {
|
|
|
3742
3875
|
limit: z4.number().min(1).max(100).optional().describe("Maximum number of posts to return. Defaults to 20."),
|
|
3743
3876
|
response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
3744
3877
|
},
|
|
3878
|
+
{ title: "Fetch Analytics", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
3745
3879
|
async ({ platform: platform2, days, content_id, limit, response_format }) => {
|
|
3746
3880
|
const format = response_format ?? "text";
|
|
3747
3881
|
const supabase = getSupabaseClient();
|
|
@@ -3788,7 +3922,7 @@ function registerAnalyticsTools(server) {
|
|
|
3788
3922
|
content: [
|
|
3789
3923
|
{
|
|
3790
3924
|
type: "text",
|
|
3791
|
-
text: `Failed to fetch user-scoped posts: ${sanitizeDbError(postsError)}`
|
|
3925
|
+
text: formatToolError(`Failed to fetch user-scoped posts: ${sanitizeDbError(postsError)}`)
|
|
3792
3926
|
}
|
|
3793
3927
|
],
|
|
3794
3928
|
isError: true
|
|
@@ -3835,7 +3969,7 @@ function registerAnalyticsTools(server) {
|
|
|
3835
3969
|
content: [
|
|
3836
3970
|
{
|
|
3837
3971
|
type: "text",
|
|
3838
|
-
text: `Failed to fetch analytics: ${sanitizeDbError(simpleError)}`
|
|
3972
|
+
text: formatToolError(`Failed to fetch analytics: ${sanitizeDbError(simpleError)}`)
|
|
3839
3973
|
}
|
|
3840
3974
|
],
|
|
3841
3975
|
isError: true
|
|
@@ -3919,6 +4053,13 @@ function registerAnalyticsTools(server) {
|
|
|
3919
4053
|
{
|
|
3920
4054
|
response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
3921
4055
|
},
|
|
4056
|
+
{
|
|
4057
|
+
title: "Refresh Platform Analytics",
|
|
4058
|
+
readOnlyHint: false,
|
|
4059
|
+
destructiveHint: false,
|
|
4060
|
+
idempotentHint: false,
|
|
4061
|
+
openWorldHint: true
|
|
4062
|
+
},
|
|
3922
4063
|
async ({ response_format }) => {
|
|
3923
4064
|
const format = response_format ?? "text";
|
|
3924
4065
|
const startedAt = Date.now();
|
|
@@ -3958,7 +4099,7 @@ function registerAnalyticsTools(server) {
|
|
|
3958
4099
|
content: [
|
|
3959
4100
|
{
|
|
3960
4101
|
type: "text",
|
|
3961
|
-
text: `Error refreshing analytics: ${error}`
|
|
4102
|
+
text: formatToolError(`Error refreshing analytics: ${error}`)
|
|
3962
4103
|
}
|
|
3963
4104
|
],
|
|
3964
4105
|
isError: true
|
|
@@ -4318,6 +4459,13 @@ function registerBrandTools(server) {
|
|
|
4318
4459
|
),
|
|
4319
4460
|
response_format: z5.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
4320
4461
|
},
|
|
4462
|
+
{
|
|
4463
|
+
title: "Extract Brand",
|
|
4464
|
+
readOnlyHint: true,
|
|
4465
|
+
destructiveHint: false,
|
|
4466
|
+
idempotentHint: true,
|
|
4467
|
+
openWorldHint: true
|
|
4468
|
+
},
|
|
4321
4469
|
async ({ url, response_format }) => {
|
|
4322
4470
|
const ssrfCheck = await validateUrlForSSRF(url);
|
|
4323
4471
|
if (!ssrfCheck.isValid) {
|
|
@@ -4338,7 +4486,7 @@ function registerBrandTools(server) {
|
|
|
4338
4486
|
content: [
|
|
4339
4487
|
{
|
|
4340
4488
|
type: "text",
|
|
4341
|
-
text: `Brand extraction failed: ${error}`
|
|
4489
|
+
text: formatToolError(`Brand extraction failed: ${error}`)
|
|
4342
4490
|
}
|
|
4343
4491
|
],
|
|
4344
4492
|
isError: true
|
|
@@ -4401,6 +4549,13 @@ function registerBrandTools(server) {
|
|
|
4401
4549
|
project_id: z5.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
4402
4550
|
response_format: z5.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
4403
4551
|
},
|
|
4552
|
+
{
|
|
4553
|
+
title: "Get Brand Profile",
|
|
4554
|
+
readOnlyHint: true,
|
|
4555
|
+
destructiveHint: false,
|
|
4556
|
+
idempotentHint: true,
|
|
4557
|
+
openWorldHint: false
|
|
4558
|
+
},
|
|
4404
4559
|
async ({ project_id, response_format }) => {
|
|
4405
4560
|
const supabase = getSupabaseClient();
|
|
4406
4561
|
const userId = await getDefaultUserId();
|
|
@@ -4441,7 +4596,7 @@ function registerBrandTools(server) {
|
|
|
4441
4596
|
content: [
|
|
4442
4597
|
{
|
|
4443
4598
|
type: "text",
|
|
4444
|
-
text: `Failed to load brand profile: ${sanitizeDbError(error)}`
|
|
4599
|
+
text: formatToolError(`Failed to load brand profile: ${sanitizeDbError(error)}`)
|
|
4445
4600
|
}
|
|
4446
4601
|
],
|
|
4447
4602
|
isError: true
|
|
@@ -4498,8 +4653,17 @@ function registerBrandTools(server) {
|
|
|
4498
4653
|
"product_showcase"
|
|
4499
4654
|
]).optional().describe("Extraction method metadata."),
|
|
4500
4655
|
overall_confidence: z5.number().min(0).max(1).optional().describe("Optional overall confidence score in range 0..1."),
|
|
4501
|
-
extraction_metadata: z5.record(z5.string(), z5.unknown()).optional()
|
|
4502
|
-
|
|
4656
|
+
extraction_metadata: z5.record(z5.string(), z5.unknown()).optional().describe(
|
|
4657
|
+
"Arbitrary key-value metadata about the extraction process."
|
|
4658
|
+
),
|
|
4659
|
+
response_format: z5.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
4660
|
+
},
|
|
4661
|
+
{
|
|
4662
|
+
title: "Save Brand Profile",
|
|
4663
|
+
readOnlyHint: false,
|
|
4664
|
+
destructiveHint: false,
|
|
4665
|
+
idempotentHint: false,
|
|
4666
|
+
openWorldHint: false
|
|
4503
4667
|
},
|
|
4504
4668
|
async ({
|
|
4505
4669
|
project_id,
|
|
@@ -4563,7 +4727,7 @@ function registerBrandTools(server) {
|
|
|
4563
4727
|
content: [
|
|
4564
4728
|
{
|
|
4565
4729
|
type: "text",
|
|
4566
|
-
text: `Failed to save brand profile: ${sanitizeDbError(error)}`
|
|
4730
|
+
text: formatToolError(`Failed to save brand profile: ${sanitizeDbError(error)}`)
|
|
4567
4731
|
}
|
|
4568
4732
|
],
|
|
4569
4733
|
isError: true
|
|
@@ -4612,15 +4776,32 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
4612
4776
|
"facebook",
|
|
4613
4777
|
"threads",
|
|
4614
4778
|
"bluesky"
|
|
4615
|
-
]),
|
|
4779
|
+
]).describe("Social platform to set voice overrides for."),
|
|
4616
4780
|
project_id: z5.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
4617
4781
|
samples: z5.string().max(3e3).optional().describe("3-5 real platform post examples for style anchoring."),
|
|
4618
|
-
tone: z5.array(z5.string()).optional()
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4782
|
+
tone: z5.array(z5.string()).optional().describe(
|
|
4783
|
+
'Tone descriptors for this platform (e.g. ["casual", "witty", "informative"]).'
|
|
4784
|
+
),
|
|
4785
|
+
style: z5.array(z5.string()).optional().describe(
|
|
4786
|
+
'Writing style tags (e.g. ["short-form", "emoji-heavy", "storytelling"]).'
|
|
4787
|
+
),
|
|
4788
|
+
avoid_patterns: z5.array(z5.string()).optional().describe(
|
|
4789
|
+
'Phrases or patterns the brand should never use on this platform (e.g. ["click here", "buy now"]).'
|
|
4790
|
+
),
|
|
4791
|
+
hashtag_strategy: z5.string().max(300).optional().describe(
|
|
4792
|
+
'Hashtag usage guidelines for this platform (e.g. "3-5 niche hashtags, no generic tags").'
|
|
4793
|
+
),
|
|
4794
|
+
cta_style: z5.string().max(300).optional().describe(
|
|
4795
|
+
'Preferred call-to-action style (e.g. "soft CTA with question" or "direct link in bio").'
|
|
4796
|
+
),
|
|
4797
|
+
response_format: z5.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
4798
|
+
},
|
|
4799
|
+
{
|
|
4800
|
+
title: "Update Platform Voice",
|
|
4801
|
+
readOnlyHint: false,
|
|
4802
|
+
destructiveHint: false,
|
|
4803
|
+
idempotentHint: false,
|
|
4804
|
+
openWorldHint: false
|
|
4624
4805
|
},
|
|
4625
4806
|
async ({
|
|
4626
4807
|
platform: platform2,
|
|
@@ -4722,7 +4903,7 @@ Version: ${payload.version ?? "N/A"}`
|
|
|
4722
4903
|
content: [
|
|
4723
4904
|
{
|
|
4724
4905
|
type: "text",
|
|
4725
|
-
text: `Failed to update platform voice: ${saveError.message}`
|
|
4906
|
+
text: formatToolError(`Failed to update platform voice: ${saveError.message}`)
|
|
4726
4907
|
}
|
|
4727
4908
|
],
|
|
4728
4909
|
isError: true
|
|
@@ -4869,6 +5050,13 @@ function registerScreenshotTools(server) {
|
|
|
4869
5050
|
"Extra milliseconds to wait after page load before capturing. Useful for animations. Defaults to 2000."
|
|
4870
5051
|
)
|
|
4871
5052
|
},
|
|
5053
|
+
{
|
|
5054
|
+
title: "Capture App Page",
|
|
5055
|
+
readOnlyHint: true,
|
|
5056
|
+
destructiveHint: false,
|
|
5057
|
+
idempotentHint: false,
|
|
5058
|
+
openWorldHint: false
|
|
5059
|
+
},
|
|
4872
5060
|
async ({ page: pageName, viewport, theme, selector, wait_ms }) => {
|
|
4873
5061
|
const startedAt = Date.now();
|
|
4874
5062
|
let rateLimitKey = "anonymous";
|
|
@@ -4989,6 +5177,13 @@ function registerScreenshotTools(server) {
|
|
|
4989
5177
|
),
|
|
4990
5178
|
wait_ms: z6.number().min(0).max(3e4).optional().describe("Extra milliseconds to wait after page load before capturing. Defaults to 1000.")
|
|
4991
5179
|
},
|
|
5180
|
+
{
|
|
5181
|
+
title: "Capture Screenshot",
|
|
5182
|
+
readOnlyHint: true,
|
|
5183
|
+
destructiveHint: false,
|
|
5184
|
+
idempotentHint: false,
|
|
5185
|
+
openWorldHint: true
|
|
5186
|
+
},
|
|
4992
5187
|
async ({ url, viewport, selector, output_path, wait_ms }) => {
|
|
4993
5188
|
const startedAt = Date.now();
|
|
4994
5189
|
let rateLimitKey = "anonymous";
|
|
@@ -5251,6 +5446,13 @@ function registerRemotionTools(server) {
|
|
|
5251
5446
|
"list_compositions",
|
|
5252
5447
|
"List all available Remotion video compositions defined in Social Neuron. Returns composition IDs, dimensions, duration, and descriptions. Use this to discover what videos can be rendered with render_demo_video.",
|
|
5253
5448
|
{},
|
|
5449
|
+
{
|
|
5450
|
+
title: "List Compositions",
|
|
5451
|
+
readOnlyHint: true,
|
|
5452
|
+
destructiveHint: false,
|
|
5453
|
+
idempotentHint: true,
|
|
5454
|
+
openWorldHint: false
|
|
5455
|
+
},
|
|
5254
5456
|
async () => {
|
|
5255
5457
|
const lines = [`${COMPOSITIONS.length} Remotion compositions available:`, ""];
|
|
5256
5458
|
for (const comp of COMPOSITIONS) {
|
|
@@ -5279,6 +5481,13 @@ function registerRemotionTools(server) {
|
|
|
5279
5481
|
"JSON string of input props to pass to the composition. Each composition accepts different props. Omit for defaults."
|
|
5280
5482
|
)
|
|
5281
5483
|
},
|
|
5484
|
+
{
|
|
5485
|
+
title: "Render Demo Video",
|
|
5486
|
+
readOnlyHint: false,
|
|
5487
|
+
destructiveHint: false,
|
|
5488
|
+
idempotentHint: false,
|
|
5489
|
+
openWorldHint: false
|
|
5490
|
+
},
|
|
5282
5491
|
async ({ composition_id, output_format, props }) => {
|
|
5283
5492
|
const startedAt = Date.now();
|
|
5284
5493
|
const userId = await getDefaultUserId();
|
|
@@ -5459,6 +5668,13 @@ function registerInsightsTools(server) {
|
|
|
5459
5668
|
limit: z8.number().min(1).max(50).optional().describe("Maximum number of insights to return. Defaults to 10."),
|
|
5460
5669
|
response_format: z8.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
5461
5670
|
},
|
|
5671
|
+
{
|
|
5672
|
+
title: "Get Performance Insights",
|
|
5673
|
+
readOnlyHint: true,
|
|
5674
|
+
destructiveHint: false,
|
|
5675
|
+
idempotentHint: true,
|
|
5676
|
+
openWorldHint: false
|
|
5677
|
+
},
|
|
5462
5678
|
async ({ insight_type, days, limit, response_format }) => {
|
|
5463
5679
|
const format = response_format ?? "text";
|
|
5464
5680
|
const supabase = getSupabaseClient();
|
|
@@ -5586,6 +5802,13 @@ function registerInsightsTools(server) {
|
|
|
5586
5802
|
days: z8.number().min(1).max(90).optional().describe("Number of days to analyze. Defaults to 30. Max 90."),
|
|
5587
5803
|
response_format: z8.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
5588
5804
|
},
|
|
5805
|
+
{
|
|
5806
|
+
title: "Get Best Posting Times",
|
|
5807
|
+
readOnlyHint: true,
|
|
5808
|
+
destructiveHint: false,
|
|
5809
|
+
idempotentHint: true,
|
|
5810
|
+
openWorldHint: false
|
|
5811
|
+
},
|
|
5589
5812
|
async ({ platform: platform2, days, response_format }) => {
|
|
5590
5813
|
const format = response_format ?? "text";
|
|
5591
5814
|
const supabase = getSupabaseClient();
|
|
@@ -5734,6 +5957,13 @@ function registerYouTubeAnalyticsTools(server) {
|
|
|
5734
5957
|
video_id: z9.string().optional().describe('YouTube video ID. Required when action is "video".'),
|
|
5735
5958
|
max_results: z9.number().min(1).max(50).optional().describe('Max videos to return for "topVideos" action. Defaults to 10.')
|
|
5736
5959
|
},
|
|
5960
|
+
{
|
|
5961
|
+
title: "Fetch YouTube Analytics",
|
|
5962
|
+
readOnlyHint: true,
|
|
5963
|
+
destructiveHint: false,
|
|
5964
|
+
idempotentHint: true,
|
|
5965
|
+
openWorldHint: true
|
|
5966
|
+
},
|
|
5737
5967
|
async ({ action, start_date, end_date, video_id, max_results }) => {
|
|
5738
5968
|
if (action === "video" && !video_id) {
|
|
5739
5969
|
return {
|
|
@@ -5852,6 +6082,13 @@ function registerCommentsTools(server) {
|
|
|
5852
6082
|
page_token: z10.string().optional().describe("Pagination cursor from previous list_comments response nextPageToken field. Omit for first page of results."),
|
|
5853
6083
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
5854
6084
|
},
|
|
6085
|
+
{
|
|
6086
|
+
title: "List Comments",
|
|
6087
|
+
readOnlyHint: true,
|
|
6088
|
+
destructiveHint: false,
|
|
6089
|
+
idempotentHint: true,
|
|
6090
|
+
openWorldHint: true
|
|
6091
|
+
},
|
|
5855
6092
|
async ({ video_id, max_results, page_token, response_format }) => {
|
|
5856
6093
|
const format = response_format ?? "text";
|
|
5857
6094
|
const { data, error } = await callEdgeFunction("youtube-comments", {
|
|
@@ -5863,7 +6100,7 @@ function registerCommentsTools(server) {
|
|
|
5863
6100
|
if (error) {
|
|
5864
6101
|
return {
|
|
5865
6102
|
content: [
|
|
5866
|
-
{ type: "text", text: `Error listing comments: ${error}` }
|
|
6103
|
+
{ type: "text", text: formatToolError(`Error listing comments: ${error}`) }
|
|
5867
6104
|
],
|
|
5868
6105
|
isError: true
|
|
5869
6106
|
};
|
|
@@ -5922,6 +6159,13 @@ function registerCommentsTools(server) {
|
|
|
5922
6159
|
text: z10.string().min(1).describe("The reply text."),
|
|
5923
6160
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
5924
6161
|
},
|
|
6162
|
+
{
|
|
6163
|
+
title: "Reply to Comment",
|
|
6164
|
+
readOnlyHint: false,
|
|
6165
|
+
destructiveHint: false,
|
|
6166
|
+
idempotentHint: false,
|
|
6167
|
+
openWorldHint: true
|
|
6168
|
+
},
|
|
5925
6169
|
async ({ parent_id, text, response_format }) => {
|
|
5926
6170
|
const format = response_format ?? "text";
|
|
5927
6171
|
const startedAt = Date.now();
|
|
@@ -5960,7 +6204,7 @@ function registerCommentsTools(server) {
|
|
|
5960
6204
|
content: [
|
|
5961
6205
|
{
|
|
5962
6206
|
type: "text",
|
|
5963
|
-
text: `Error replying to comment: ${error}`
|
|
6207
|
+
text: formatToolError(`Error replying to comment: ${error}`)
|
|
5964
6208
|
}
|
|
5965
6209
|
],
|
|
5966
6210
|
isError: true
|
|
@@ -6003,6 +6247,13 @@ function registerCommentsTools(server) {
|
|
|
6003
6247
|
text: z10.string().min(1).describe("The comment text."),
|
|
6004
6248
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6005
6249
|
},
|
|
6250
|
+
{
|
|
6251
|
+
title: "Post Comment",
|
|
6252
|
+
readOnlyHint: false,
|
|
6253
|
+
destructiveHint: false,
|
|
6254
|
+
idempotentHint: false,
|
|
6255
|
+
openWorldHint: true
|
|
6256
|
+
},
|
|
6006
6257
|
async ({ video_id, text, response_format }) => {
|
|
6007
6258
|
const format = response_format ?? "text";
|
|
6008
6259
|
const startedAt = Date.now();
|
|
@@ -6039,7 +6290,7 @@ function registerCommentsTools(server) {
|
|
|
6039
6290
|
});
|
|
6040
6291
|
return {
|
|
6041
6292
|
content: [
|
|
6042
|
-
{ type: "text", text: `Error posting comment: ${error}` }
|
|
6293
|
+
{ type: "text", text: formatToolError(`Error posting comment: ${error}`) }
|
|
6043
6294
|
],
|
|
6044
6295
|
isError: true
|
|
6045
6296
|
};
|
|
@@ -6081,6 +6332,13 @@ function registerCommentsTools(server) {
|
|
|
6081
6332
|
moderation_status: z10.enum(["published", "rejected"]).describe('"published" to approve, "rejected" to hide.'),
|
|
6082
6333
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6083
6334
|
},
|
|
6335
|
+
{
|
|
6336
|
+
title: "Moderate Comment",
|
|
6337
|
+
readOnlyHint: false,
|
|
6338
|
+
destructiveHint: false,
|
|
6339
|
+
idempotentHint: true,
|
|
6340
|
+
openWorldHint: true
|
|
6341
|
+
},
|
|
6084
6342
|
async ({ comment_id, moderation_status, response_format }) => {
|
|
6085
6343
|
const format = response_format ?? "text";
|
|
6086
6344
|
const startedAt = Date.now();
|
|
@@ -6119,7 +6377,7 @@ function registerCommentsTools(server) {
|
|
|
6119
6377
|
content: [
|
|
6120
6378
|
{
|
|
6121
6379
|
type: "text",
|
|
6122
|
-
text: `Error moderating comment: ${error}`
|
|
6380
|
+
text: formatToolError(`Error moderating comment: ${error}`)
|
|
6123
6381
|
}
|
|
6124
6382
|
],
|
|
6125
6383
|
isError: true
|
|
@@ -6166,6 +6424,13 @@ function registerCommentsTools(server) {
|
|
|
6166
6424
|
comment_id: z10.string().describe("The comment ID to delete."),
|
|
6167
6425
|
response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6168
6426
|
},
|
|
6427
|
+
{
|
|
6428
|
+
title: "Delete Comment",
|
|
6429
|
+
readOnlyHint: false,
|
|
6430
|
+
destructiveHint: true,
|
|
6431
|
+
idempotentHint: true,
|
|
6432
|
+
openWorldHint: true
|
|
6433
|
+
},
|
|
6169
6434
|
async ({ comment_id, response_format }) => {
|
|
6170
6435
|
const format = response_format ?? "text";
|
|
6171
6436
|
const startedAt = Date.now();
|
|
@@ -6201,7 +6466,7 @@ function registerCommentsTools(server) {
|
|
|
6201
6466
|
});
|
|
6202
6467
|
return {
|
|
6203
6468
|
content: [
|
|
6204
|
-
{ type: "text", text: `Error deleting comment: ${error}` }
|
|
6469
|
+
{ type: "text", text: formatToolError(`Error deleting comment: ${error}`) }
|
|
6205
6470
|
],
|
|
6206
6471
|
isError: true
|
|
6207
6472
|
};
|
|
@@ -6314,12 +6579,19 @@ function asEnvelope7(data) {
|
|
|
6314
6579
|
function registerIdeationContextTools(server) {
|
|
6315
6580
|
server.tool(
|
|
6316
6581
|
"get_ideation_context",
|
|
6317
|
-
"
|
|
6582
|
+
"Load performance-derived context (top hooks, optimal timing, winning patterns) that should inform your next content generation. Call this before generate_content or plan_content_week to ground new content in what has actually performed well. Returns a promptInjection string ready to pass into generation tools.",
|
|
6318
6583
|
{
|
|
6319
6584
|
project_id: z11.string().uuid().optional().describe("Project ID to scope insights."),
|
|
6320
6585
|
days: z11.number().min(1).max(90).optional().describe("Lookback window for insights. Defaults to 30 days."),
|
|
6321
6586
|
response_format: z11.enum(["text", "json"]).optional().describe("Optional output format. Defaults to text.")
|
|
6322
6587
|
},
|
|
6588
|
+
{
|
|
6589
|
+
title: "Get Ideation Context",
|
|
6590
|
+
readOnlyHint: true,
|
|
6591
|
+
destructiveHint: false,
|
|
6592
|
+
idempotentHint: true,
|
|
6593
|
+
openWorldHint: false
|
|
6594
|
+
},
|
|
6323
6595
|
async ({ project_id, days, response_format }) => {
|
|
6324
6596
|
const supabase = getSupabaseClient();
|
|
6325
6597
|
const userId = await getDefaultUserId();
|
|
@@ -6449,6 +6721,13 @@ function registerCreditsTools(server) {
|
|
|
6449
6721
|
{
|
|
6450
6722
|
response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6451
6723
|
},
|
|
6724
|
+
{
|
|
6725
|
+
title: "Get Credit Balance",
|
|
6726
|
+
readOnlyHint: true,
|
|
6727
|
+
destructiveHint: false,
|
|
6728
|
+
idempotentHint: true,
|
|
6729
|
+
openWorldHint: false
|
|
6730
|
+
},
|
|
6452
6731
|
async ({ response_format }) => {
|
|
6453
6732
|
const supabase = getSupabaseClient();
|
|
6454
6733
|
const userId = await getDefaultUserId();
|
|
@@ -6461,7 +6740,7 @@ function registerCreditsTools(server) {
|
|
|
6461
6740
|
content: [
|
|
6462
6741
|
{
|
|
6463
6742
|
type: "text",
|
|
6464
|
-
text: `Failed to fetch credit balance: ${sanitizeDbError(profileResult.error)}`
|
|
6743
|
+
text: formatToolError(`Failed to fetch credit balance: ${sanitizeDbError(profileResult.error)}`)
|
|
6465
6744
|
}
|
|
6466
6745
|
],
|
|
6467
6746
|
isError: true
|
|
@@ -6502,6 +6781,13 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
|
|
|
6502
6781
|
{
|
|
6503
6782
|
response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6504
6783
|
},
|
|
6784
|
+
{
|
|
6785
|
+
title: "Get Budget Status",
|
|
6786
|
+
readOnlyHint: true,
|
|
6787
|
+
destructiveHint: false,
|
|
6788
|
+
idempotentHint: true,
|
|
6789
|
+
openWorldHint: false
|
|
6790
|
+
},
|
|
6505
6791
|
async ({ response_format }) => {
|
|
6506
6792
|
const budget = getCurrentBudgetStatus();
|
|
6507
6793
|
const payload = {
|
|
@@ -6555,11 +6841,18 @@ function asEnvelope9(data) {
|
|
|
6555
6841
|
function registerLoopSummaryTools(server) {
|
|
6556
6842
|
server.tool(
|
|
6557
6843
|
"get_loop_summary",
|
|
6558
|
-
"Get a
|
|
6844
|
+
"Get a single-call health check of the content feedback loop: brand profile status, recent content, and active insights. Call at the start of a session to decide what to do next. The response includes a recommendedNextAction field that tells you which tool to call.",
|
|
6559
6845
|
{
|
|
6560
6846
|
project_id: z13.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
6561
6847
|
response_format: z13.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6562
6848
|
},
|
|
6849
|
+
{
|
|
6850
|
+
title: "Get Loop Summary",
|
|
6851
|
+
readOnlyHint: true,
|
|
6852
|
+
destructiveHint: false,
|
|
6853
|
+
idempotentHint: true,
|
|
6854
|
+
openWorldHint: false
|
|
6855
|
+
},
|
|
6563
6856
|
async ({ project_id, response_format }) => {
|
|
6564
6857
|
const supabase = getSupabaseClient();
|
|
6565
6858
|
const userId = await getDefaultUserId();
|
|
@@ -6657,6 +6950,13 @@ function registerUsageTools(server) {
|
|
|
6657
6950
|
{
|
|
6658
6951
|
response_format: z14.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6659
6952
|
},
|
|
6953
|
+
{
|
|
6954
|
+
title: "Get MCP Usage",
|
|
6955
|
+
readOnlyHint: true,
|
|
6956
|
+
destructiveHint: false,
|
|
6957
|
+
idempotentHint: true,
|
|
6958
|
+
openWorldHint: false
|
|
6959
|
+
},
|
|
6660
6960
|
async ({ response_format }) => {
|
|
6661
6961
|
const format = response_format ?? "text";
|
|
6662
6962
|
const supabase = getSupabaseClient();
|
|
@@ -6746,6 +7046,13 @@ function registerAutopilotTools(server) {
|
|
|
6746
7046
|
active_only: z15.boolean().optional().describe("If true, only return active configs. Defaults to false (show all)."),
|
|
6747
7047
|
response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6748
7048
|
},
|
|
7049
|
+
{
|
|
7050
|
+
title: "List Autopilot Configs",
|
|
7051
|
+
readOnlyHint: true,
|
|
7052
|
+
destructiveHint: false,
|
|
7053
|
+
idempotentHint: true,
|
|
7054
|
+
openWorldHint: false
|
|
7055
|
+
},
|
|
6749
7056
|
async ({ active_only, response_format }) => {
|
|
6750
7057
|
const format = response_format ?? "text";
|
|
6751
7058
|
const supabase = getSupabaseClient();
|
|
@@ -6823,11 +7130,18 @@ ${"=".repeat(40)}
|
|
|
6823
7130
|
{
|
|
6824
7131
|
config_id: z15.string().uuid().describe("The autopilot config ID to update."),
|
|
6825
7132
|
is_active: z15.boolean().optional().describe("Enable or disable this autopilot config."),
|
|
6826
|
-
schedule_days: z15.array(z15.enum(["mon", "tue", "wed", "thu", "fri", "sat", "sun"])).optional().describe('Days of the week to run (e.g
|
|
7133
|
+
schedule_days: z15.array(z15.enum(["mon", "tue", "wed", "thu", "fri", "sat", "sun"]).describe("Three-letter lowercase day abbreviation.")).optional().describe('Days of the week to run (e.g. ["mon", "wed", "fri"]).'),
|
|
6827
7134
|
schedule_time: z15.string().optional().describe('Time to run in HH:MM format (24h, user timezone). E.g., "09:00".'),
|
|
6828
7135
|
max_credits_per_run: z15.number().optional().describe("Maximum credits per execution."),
|
|
6829
7136
|
max_credits_per_week: z15.number().optional().describe("Maximum credits per week.")
|
|
6830
7137
|
},
|
|
7138
|
+
{
|
|
7139
|
+
title: "Update Autopilot Config",
|
|
7140
|
+
readOnlyHint: false,
|
|
7141
|
+
destructiveHint: false,
|
|
7142
|
+
idempotentHint: true,
|
|
7143
|
+
openWorldHint: false
|
|
7144
|
+
},
|
|
6831
7145
|
async ({
|
|
6832
7146
|
config_id,
|
|
6833
7147
|
is_active,
|
|
@@ -6891,6 +7205,13 @@ Schedule: ${JSON.stringify(updated.schedule_config)}`
|
|
|
6891
7205
|
{
|
|
6892
7206
|
response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
|
|
6893
7207
|
},
|
|
7208
|
+
{
|
|
7209
|
+
title: "Get Autopilot Status",
|
|
7210
|
+
readOnlyHint: true,
|
|
7211
|
+
destructiveHint: false,
|
|
7212
|
+
idempotentHint: true,
|
|
7213
|
+
openWorldHint: false
|
|
7214
|
+
},
|
|
6894
7215
|
async ({ response_format }) => {
|
|
6895
7216
|
const format = response_format ?? "text";
|
|
6896
7217
|
const supabase = getSupabaseClient();
|
|
@@ -7010,7 +7331,14 @@ function registerExtractionTools(server) {
|
|
|
7010
7331
|
extract_type: z16.enum(["auto", "transcript", "article", "product"]).default("auto").describe("Type of extraction"),
|
|
7011
7332
|
include_comments: z16.boolean().default(false).describe("Include top comments (YouTube only)"),
|
|
7012
7333
|
max_results: z16.number().min(1).max(100).default(10).describe("Max comments to include"),
|
|
7013
|
-
response_format: z16.enum(["text", "json"]).default("text")
|
|
7334
|
+
response_format: z16.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
|
|
7335
|
+
},
|
|
7336
|
+
{
|
|
7337
|
+
title: "Extract URL Content",
|
|
7338
|
+
readOnlyHint: true,
|
|
7339
|
+
destructiveHint: false,
|
|
7340
|
+
idempotentHint: true,
|
|
7341
|
+
openWorldHint: true
|
|
7014
7342
|
},
|
|
7015
7343
|
async ({
|
|
7016
7344
|
url,
|
|
@@ -7053,7 +7381,7 @@ function registerExtractionTools(server) {
|
|
|
7053
7381
|
content: [
|
|
7054
7382
|
{
|
|
7055
7383
|
type: "text",
|
|
7056
|
-
text: `Failed to extract YouTube video: ${error ?? "No data returned"}`
|
|
7384
|
+
text: formatToolError(`Failed to extract YouTube video: ${error ?? "No data returned"}`)
|
|
7057
7385
|
}
|
|
7058
7386
|
],
|
|
7059
7387
|
isError: true
|
|
@@ -7093,7 +7421,7 @@ function registerExtractionTools(server) {
|
|
|
7093
7421
|
content: [
|
|
7094
7422
|
{
|
|
7095
7423
|
type: "text",
|
|
7096
|
-
text: `Failed to extract YouTube channel: ${error ?? "No data returned"}`
|
|
7424
|
+
text: formatToolError(`Failed to extract YouTube channel: ${error ?? "No data returned"}`)
|
|
7097
7425
|
}
|
|
7098
7426
|
],
|
|
7099
7427
|
isError: true
|
|
@@ -7124,7 +7452,7 @@ function registerExtractionTools(server) {
|
|
|
7124
7452
|
content: [
|
|
7125
7453
|
{
|
|
7126
7454
|
type: "text",
|
|
7127
|
-
text: `Failed to extract URL content: ${error ?? "No data returned"}`
|
|
7455
|
+
text: formatToolError(`Failed to extract URL content: ${error ?? "No data returned"}`)
|
|
7128
7456
|
}
|
|
7129
7457
|
],
|
|
7130
7458
|
isError: true
|
|
@@ -7181,7 +7509,7 @@ function registerExtractionTools(server) {
|
|
|
7181
7509
|
});
|
|
7182
7510
|
return {
|
|
7183
7511
|
content: [
|
|
7184
|
-
{ type: "text", text: `Extraction failed: ${message}` }
|
|
7512
|
+
{ type: "text", text: formatToolError(`Extraction failed: ${message}`) }
|
|
7185
7513
|
],
|
|
7186
7514
|
isError: true
|
|
7187
7515
|
};
|
|
@@ -7220,9 +7548,16 @@ function registerQualityTools(server) {
|
|
|
7220
7548
|
).min(1).describe("Target platforms"),
|
|
7221
7549
|
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."),
|
|
7222
7550
|
brand_keyword: z17.string().optional().describe("Brand keyword for alignment check"),
|
|
7223
|
-
brand_avoid_patterns: z17.array(z17.string()).optional(),
|
|
7224
|
-
custom_banned_terms: z17.array(z17.string()).optional(),
|
|
7225
|
-
response_format: z17.enum(["text", "json"]).default("text")
|
|
7551
|
+
brand_avoid_patterns: z17.array(z17.string()).optional().describe("Phrases the brand should never use (e.g. competitor names, off-brand slang). Matched case-insensitively."),
|
|
7552
|
+
custom_banned_terms: z17.array(z17.string()).optional().describe("Additional banned words beyond the built-in safety list. Useful for industry-specific compliance terms."),
|
|
7553
|
+
response_format: z17.enum(["text", "json"]).default("text").describe("'text' for human-readable report, 'json' for structured scores suitable for pipeline automation.")
|
|
7554
|
+
},
|
|
7555
|
+
{
|
|
7556
|
+
title: "Quality Check",
|
|
7557
|
+
readOnlyHint: true,
|
|
7558
|
+
destructiveHint: false,
|
|
7559
|
+
idempotentHint: true,
|
|
7560
|
+
openWorldHint: false
|
|
7226
7561
|
},
|
|
7227
7562
|
async ({
|
|
7228
7563
|
caption,
|
|
@@ -7294,15 +7629,22 @@ function registerQualityTools(server) {
|
|
|
7294
7629
|
plan: z17.object({
|
|
7295
7630
|
posts: z17.array(
|
|
7296
7631
|
z17.object({
|
|
7297
|
-
id: z17.string(),
|
|
7298
|
-
caption: z17.string(),
|
|
7299
|
-
title: z17.string().optional(),
|
|
7300
|
-
platform: z17.string()
|
|
7632
|
+
id: z17.string().describe("Unique post identifier."),
|
|
7633
|
+
caption: z17.string().describe("Post caption/body text to quality-check."),
|
|
7634
|
+
title: z17.string().optional().describe("Post title (important for YouTube)."),
|
|
7635
|
+
platform: z17.string().describe("Target platform (e.g. instagram, youtube).")
|
|
7301
7636
|
})
|
|
7302
7637
|
)
|
|
7303
|
-
}).passthrough().describe("Content plan with posts array"),
|
|
7638
|
+
}).passthrough().describe("Content plan with posts array."),
|
|
7304
7639
|
threshold: z17.number().min(0).max(35).default(26).describe("Minimum total score to pass (max 35, scored across 7 categories at 0-5 each). Default 26 (~75%). Use 20 for rough drafts, 28+ for final posts going to large audiences."),
|
|
7305
|
-
response_format: z17.enum(["text", "json"]).default("text")
|
|
7640
|
+
response_format: z17.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
|
|
7641
|
+
},
|
|
7642
|
+
{
|
|
7643
|
+
title: "Quality Check Plan",
|
|
7644
|
+
readOnlyHint: true,
|
|
7645
|
+
destructiveHint: false,
|
|
7646
|
+
idempotentHint: true,
|
|
7647
|
+
openWorldHint: false
|
|
7306
7648
|
},
|
|
7307
7649
|
async ({ plan, threshold, response_format }) => {
|
|
7308
7650
|
const startedAt = Date.now();
|
|
@@ -7503,7 +7845,14 @@ function registerPlanningTools(server) {
|
|
|
7503
7845
|
start_date: z18.string().optional().describe("ISO date, defaults to tomorrow"),
|
|
7504
7846
|
brand_voice: z18.string().optional().describe("Override brand voice description"),
|
|
7505
7847
|
project_id: z18.string().optional().describe("Project ID for brand/insights context"),
|
|
7506
|
-
response_format: z18.enum(["text", "json"]).default("json")
|
|
7848
|
+
response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
|
|
7849
|
+
},
|
|
7850
|
+
{
|
|
7851
|
+
title: "Plan Content Week",
|
|
7852
|
+
readOnlyHint: false,
|
|
7853
|
+
destructiveHint: false,
|
|
7854
|
+
idempotentHint: false,
|
|
7855
|
+
openWorldHint: true
|
|
7507
7856
|
},
|
|
7508
7857
|
async ({
|
|
7509
7858
|
topic,
|
|
@@ -7658,7 +8007,7 @@ ${ideationContext.promptInjection.slice(0, 1500)}` : "",
|
|
|
7658
8007
|
content: [
|
|
7659
8008
|
{
|
|
7660
8009
|
type: "text",
|
|
7661
|
-
text: `Plan generation failed: ${aiError ?? "No response from AI"}`
|
|
8010
|
+
text: formatToolError(`Plan generation failed: ${aiError ?? "No response from AI"}`)
|
|
7662
8011
|
}
|
|
7663
8012
|
],
|
|
7664
8013
|
isError: true
|
|
@@ -7678,10 +8027,10 @@ ${ideationContext.promptInjection.slice(0, 1500)}` : "",
|
|
|
7678
8027
|
content: [
|
|
7679
8028
|
{
|
|
7680
8029
|
type: "text",
|
|
7681
|
-
text: `AI response could not be parsed as JSON.
|
|
8030
|
+
text: formatToolError(`AI response could not be parsed as JSON.
|
|
7682
8031
|
|
|
7683
8032
|
Raw output (first 1000 chars):
|
|
7684
|
-
${rawText.slice(0, 1e3)}`
|
|
8033
|
+
${rawText.slice(0, 1e3)}`)
|
|
7685
8034
|
}
|
|
7686
8035
|
],
|
|
7687
8036
|
isError: true
|
|
@@ -7808,7 +8157,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7808
8157
|
content: [
|
|
7809
8158
|
{
|
|
7810
8159
|
type: "text",
|
|
7811
|
-
text: `Plan generation failed: ${message}`
|
|
8160
|
+
text: formatToolError(`Plan generation failed: ${message}`)
|
|
7812
8161
|
}
|
|
7813
8162
|
],
|
|
7814
8163
|
isError: true
|
|
@@ -7821,12 +8170,19 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7821
8170
|
"Save a content plan to the database for team review, approval workflows, and scheduled publishing. Creates a plan_id you can reference in get_content_plan, update_content_plan, and schedule_content_plan.",
|
|
7822
8171
|
{
|
|
7823
8172
|
plan: z18.object({
|
|
7824
|
-
topic: z18.string(),
|
|
7825
|
-
posts: z18.array(z18.record(z18.string(), z18.unknown()))
|
|
7826
|
-
}).passthrough(),
|
|
7827
|
-
project_id: z18.string().uuid().optional(),
|
|
7828
|
-
status: z18.enum(["draft", "in_review", "approved", "scheduled", "completed"]).default("draft"),
|
|
7829
|
-
response_format: z18.enum(["text", "json"]).default("json")
|
|
8173
|
+
topic: z18.string().describe("Content plan topic or theme."),
|
|
8174
|
+
posts: z18.array(z18.record(z18.string(), z18.unknown())).describe("Array of post objects to save.")
|
|
8175
|
+
}).passthrough().describe("Content plan object with topic and posts array."),
|
|
8176
|
+
project_id: z18.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
8177
|
+
status: z18.enum(["draft", "in_review", "approved", "scheduled", "completed"]).default("draft").describe("Initial plan status. Defaults to draft."),
|
|
8178
|
+
response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
|
|
8179
|
+
},
|
|
8180
|
+
{
|
|
8181
|
+
title: "Save Content Plan",
|
|
8182
|
+
readOnlyHint: false,
|
|
8183
|
+
destructiveHint: false,
|
|
8184
|
+
idempotentHint: false,
|
|
8185
|
+
openWorldHint: false
|
|
7830
8186
|
},
|
|
7831
8187
|
async ({ plan, project_id, status, response_format }) => {
|
|
7832
8188
|
const startedAt = Date.now();
|
|
@@ -7914,7 +8270,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7914
8270
|
content: [
|
|
7915
8271
|
{
|
|
7916
8272
|
type: "text",
|
|
7917
|
-
text: `Failed to save content plan: ${message}`
|
|
8273
|
+
text: formatToolError(`Failed to save content plan: ${message}`)
|
|
7918
8274
|
}
|
|
7919
8275
|
],
|
|
7920
8276
|
isError: true
|
|
@@ -7924,10 +8280,17 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7924
8280
|
);
|
|
7925
8281
|
server.tool(
|
|
7926
8282
|
"get_content_plan",
|
|
7927
|
-
"Retrieve a
|
|
8283
|
+
"Retrieve a saved content plan to review its posts, status, and applied insights. Use after plan_content_week or save_content_plan to inspect what was generated. Feed the result into update_content_plan to revise posts or submit_content_plan_for_approval to start the review workflow.",
|
|
7928
8284
|
{
|
|
7929
8285
|
plan_id: z18.string().uuid().describe("Persisted content plan ID"),
|
|
7930
|
-
response_format: z18.enum(["text", "json"]).default("json")
|
|
8286
|
+
response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
|
|
8287
|
+
},
|
|
8288
|
+
{
|
|
8289
|
+
title: "Get Content Plan",
|
|
8290
|
+
readOnlyHint: true,
|
|
8291
|
+
destructiveHint: false,
|
|
8292
|
+
idempotentHint: true,
|
|
8293
|
+
openWorldHint: false
|
|
7931
8294
|
},
|
|
7932
8295
|
async ({ plan_id, response_format }) => {
|
|
7933
8296
|
const supabase = getSupabaseClient();
|
|
@@ -7940,7 +8303,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7940
8303
|
content: [
|
|
7941
8304
|
{
|
|
7942
8305
|
type: "text",
|
|
7943
|
-
text: `Failed to load content plan: ${sanitizeDbError(error)}`
|
|
8306
|
+
text: formatToolError(`Failed to load content plan: ${sanitizeDbError(error)}`)
|
|
7944
8307
|
}
|
|
7945
8308
|
],
|
|
7946
8309
|
isError: true
|
|
@@ -7951,7 +8314,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7951
8314
|
content: [
|
|
7952
8315
|
{
|
|
7953
8316
|
type: "text",
|
|
7954
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
8317
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
7955
8318
|
}
|
|
7956
8319
|
],
|
|
7957
8320
|
isError: true
|
|
@@ -7992,25 +8355,32 @@ ${rawText.slice(0, 1e3)}`
|
|
|
7992
8355
|
);
|
|
7993
8356
|
server.tool(
|
|
7994
8357
|
"update_content_plan",
|
|
7995
|
-
"
|
|
8358
|
+
"Revise specific posts in a saved content plan -- edit captions, hooks, hashtags, schedule times, or mark posts as approved/rejected. Call after reviewing a plan with get_content_plan. When all posts are approved, the plan status auto-advances so it can be scheduled.",
|
|
7996
8359
|
{
|
|
7997
|
-
plan_id: z18.string().uuid(),
|
|
8360
|
+
plan_id: z18.string().uuid().describe("Content plan ID to update."),
|
|
7998
8361
|
post_updates: z18.array(
|
|
7999
8362
|
z18.object({
|
|
8000
|
-
post_id: z18.string(),
|
|
8001
|
-
caption: z18.string().optional(),
|
|
8002
|
-
title: z18.string().optional(),
|
|
8003
|
-
hashtags: z18.array(z18.string()).optional(),
|
|
8004
|
-
hook: z18.string().optional(),
|
|
8005
|
-
angle: z18.string().optional(),
|
|
8006
|
-
visual_direction: z18.string().optional(),
|
|
8007
|
-
media_url: z18.string().optional(),
|
|
8008
|
-
schedule_at: z18.string().optional(),
|
|
8009
|
-
platform: z18.string().optional(),
|
|
8010
|
-
status: z18.enum(["approved", "rejected", "needs_edit"]).optional()
|
|
8363
|
+
post_id: z18.string().describe("ID of the post to update within this plan."),
|
|
8364
|
+
caption: z18.string().optional().describe("Revised caption/body text."),
|
|
8365
|
+
title: z18.string().optional().describe("Revised post title."),
|
|
8366
|
+
hashtags: z18.array(z18.string()).optional().describe("Revised hashtags array."),
|
|
8367
|
+
hook: z18.string().optional().describe("Revised attention-grabbing opening line."),
|
|
8368
|
+
angle: z18.string().optional().describe("Revised content angle or perspective."),
|
|
8369
|
+
visual_direction: z18.string().optional().describe("Revised visual/media direction notes."),
|
|
8370
|
+
media_url: z18.string().optional().describe("Revised media URL (public or R2 signed URL)."),
|
|
8371
|
+
schedule_at: z18.string().optional().describe("Revised ISO 8601 UTC publish datetime."),
|
|
8372
|
+
platform: z18.string().optional().describe("Revised target platform."),
|
|
8373
|
+
status: z18.enum(["approved", "rejected", "needs_edit"]).optional().describe("Review status for this post.")
|
|
8011
8374
|
})
|
|
8012
|
-
).min(1),
|
|
8013
|
-
response_format: z18.enum(["text", "json"]).default("json")
|
|
8375
|
+
).min(1).describe("Array of post-level updates to apply."),
|
|
8376
|
+
response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
|
|
8377
|
+
},
|
|
8378
|
+
{
|
|
8379
|
+
title: "Update Content Plan",
|
|
8380
|
+
readOnlyHint: false,
|
|
8381
|
+
destructiveHint: false,
|
|
8382
|
+
idempotentHint: true,
|
|
8383
|
+
openWorldHint: false
|
|
8014
8384
|
},
|
|
8015
8385
|
async ({ plan_id, post_updates, response_format }) => {
|
|
8016
8386
|
const supabase = getSupabaseClient();
|
|
@@ -8032,7 +8402,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8032
8402
|
content: [
|
|
8033
8403
|
{
|
|
8034
8404
|
type: "text",
|
|
8035
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
8405
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
8036
8406
|
}
|
|
8037
8407
|
],
|
|
8038
8408
|
isError: true
|
|
@@ -8076,7 +8446,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8076
8446
|
content: [
|
|
8077
8447
|
{
|
|
8078
8448
|
type: "text",
|
|
8079
|
-
text: `Failed to update content plan: ${sanitizeDbError(saveError)}`
|
|
8449
|
+
text: formatToolError(`Failed to update content plan: ${sanitizeDbError(saveError)}`)
|
|
8080
8450
|
}
|
|
8081
8451
|
],
|
|
8082
8452
|
isError: true
|
|
@@ -8111,10 +8481,17 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8111
8481
|
);
|
|
8112
8482
|
server.tool(
|
|
8113
8483
|
"submit_content_plan_for_approval",
|
|
8114
|
-
"
|
|
8484
|
+
"Submit an entire saved content plan for team review in one call -- creates approval items for every post and sets the plan to in_review status. Call after plan_content_week and any update_content_plan edits are done. Use list_plan_approvals to track reviewer decisions.",
|
|
8115
8485
|
{
|
|
8116
|
-
plan_id: z18.string().uuid(),
|
|
8117
|
-
response_format: z18.enum(["text", "json"]).default("json")
|
|
8486
|
+
plan_id: z18.string().uuid().describe("Content plan ID to submit for review."),
|
|
8487
|
+
response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
|
|
8488
|
+
},
|
|
8489
|
+
{
|
|
8490
|
+
title: "Submit Plan for Approval",
|
|
8491
|
+
readOnlyHint: false,
|
|
8492
|
+
destructiveHint: false,
|
|
8493
|
+
idempotentHint: true,
|
|
8494
|
+
openWorldHint: false
|
|
8118
8495
|
},
|
|
8119
8496
|
async ({ plan_id, response_format }) => {
|
|
8120
8497
|
const supabase = getSupabaseClient();
|
|
@@ -8136,7 +8513,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8136
8513
|
content: [
|
|
8137
8514
|
{
|
|
8138
8515
|
type: "text",
|
|
8139
|
-
text: `No content plan found for plan_id=${plan_id}`
|
|
8516
|
+
text: formatToolError(`No content plan found for plan_id=${plan_id}`)
|
|
8140
8517
|
}
|
|
8141
8518
|
],
|
|
8142
8519
|
isError: true
|
|
@@ -8149,7 +8526,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8149
8526
|
content: [
|
|
8150
8527
|
{
|
|
8151
8528
|
type: "text",
|
|
8152
|
-
text: `Plan ${plan_id} has no posts to submit.`
|
|
8529
|
+
text: formatToolError(`Plan ${plan_id} has no posts to submit.`)
|
|
8153
8530
|
}
|
|
8154
8531
|
],
|
|
8155
8532
|
isError: true
|
|
@@ -8169,7 +8546,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8169
8546
|
content: [
|
|
8170
8547
|
{
|
|
8171
8548
|
type: "text",
|
|
8172
|
-
text: `Failed to create approvals: ${sanitizeDbError(approvalError)}`
|
|
8549
|
+
text: formatToolError(`Failed to create approvals: ${sanitizeDbError(approvalError)}`)
|
|
8173
8550
|
}
|
|
8174
8551
|
],
|
|
8175
8552
|
isError: true
|
|
@@ -8181,7 +8558,7 @@ ${rawText.slice(0, 1e3)}`
|
|
|
8181
8558
|
content: [
|
|
8182
8559
|
{
|
|
8183
8560
|
type: "text",
|
|
8184
|
-
text: `Failed to update plan status: ${sanitizeDbError(statusError)}`
|
|
8561
|
+
text: formatToolError(`Failed to update plan status: ${sanitizeDbError(statusError)}`)
|
|
8185
8562
|
}
|
|
8186
8563
|
],
|
|
8187
8564
|
isError: true
|
|
@@ -8238,21 +8615,28 @@ async function assertProjectAccess(supabase, userId, projectId) {
|
|
|
8238
8615
|
function registerPlanApprovalTools(server) {
|
|
8239
8616
|
server.tool(
|
|
8240
8617
|
"create_plan_approvals",
|
|
8241
|
-
"Create
|
|
8618
|
+
"Create individual approval items for posts you supply explicitly, useful when building a custom approval queue outside the standard plan workflow. Requires the post array as input. Use list_plan_approvals to check status afterward, and respond_plan_approval to approve or reject each item.",
|
|
8242
8619
|
{
|
|
8243
8620
|
plan_id: z19.string().uuid().describe("Content plan ID"),
|
|
8244
8621
|
posts: z19.array(
|
|
8245
8622
|
z19.object({
|
|
8246
|
-
id: z19.string(),
|
|
8247
|
-
platform: z19.string().optional(),
|
|
8248
|
-
caption: z19.string().optional(),
|
|
8249
|
-
title: z19.string().optional(),
|
|
8250
|
-
media_url: z19.string().optional(),
|
|
8251
|
-
schedule_at: z19.string().optional()
|
|
8623
|
+
id: z19.string().describe("Unique post identifier from the content plan."),
|
|
8624
|
+
platform: z19.string().optional().describe("Target platform (e.g. instagram, youtube)."),
|
|
8625
|
+
caption: z19.string().optional().describe("Post caption/body text."),
|
|
8626
|
+
title: z19.string().optional().describe("Post title, used by YouTube and LinkedIn articles."),
|
|
8627
|
+
media_url: z19.string().optional().describe("Public or R2 signed URL for the post media."),
|
|
8628
|
+
schedule_at: z19.string().optional().describe("ISO 8601 UTC datetime to publish (e.g. 2026-03-20T14:00:00Z).")
|
|
8252
8629
|
}).passthrough()
|
|
8253
8630
|
).min(1).describe("Posts to create approval entries for."),
|
|
8254
8631
|
project_id: z19.string().uuid().optional().describe("Project ID. Defaults to active project context."),
|
|
8255
|
-
response_format: z19.enum(["text", "json"]).optional()
|
|
8632
|
+
response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
8633
|
+
},
|
|
8634
|
+
{
|
|
8635
|
+
title: "Create Plan Approvals",
|
|
8636
|
+
readOnlyHint: false,
|
|
8637
|
+
destructiveHint: false,
|
|
8638
|
+
idempotentHint: false,
|
|
8639
|
+
openWorldHint: false
|
|
8256
8640
|
},
|
|
8257
8641
|
async ({ plan_id, posts, project_id, response_format }) => {
|
|
8258
8642
|
const supabase = getSupabaseClient();
|
|
@@ -8332,8 +8716,15 @@ function registerPlanApprovalTools(server) {
|
|
|
8332
8716
|
"List MCP-native approval items for a specific content plan.",
|
|
8333
8717
|
{
|
|
8334
8718
|
plan_id: z19.string().uuid().describe("Content plan ID"),
|
|
8335
|
-
status: z19.enum(["pending", "approved", "rejected", "edited"]).optional(),
|
|
8336
|
-
response_format: z19.enum(["text", "json"]).optional()
|
|
8719
|
+
status: z19.enum(["pending", "approved", "rejected", "edited"]).optional().describe("Filter approvals by status. Omit to return all statuses."),
|
|
8720
|
+
response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
8721
|
+
},
|
|
8722
|
+
{
|
|
8723
|
+
title: "List Plan Approvals",
|
|
8724
|
+
readOnlyHint: true,
|
|
8725
|
+
destructiveHint: false,
|
|
8726
|
+
idempotentHint: true,
|
|
8727
|
+
openWorldHint: false
|
|
8337
8728
|
},
|
|
8338
8729
|
async ({ plan_id, status, response_format }) => {
|
|
8339
8730
|
const supabase = getSupabaseClient();
|
|
@@ -8400,10 +8791,19 @@ function registerPlanApprovalTools(server) {
|
|
|
8400
8791
|
"Approve, reject, or edit a pending plan approval item.",
|
|
8401
8792
|
{
|
|
8402
8793
|
approval_id: z19.string().uuid().describe("Approval item ID"),
|
|
8403
|
-
decision: z19.enum(["approved", "rejected", "edited"]),
|
|
8404
|
-
edited_post: z19.record(z19.string(), z19.unknown()).optional()
|
|
8405
|
-
|
|
8406
|
-
|
|
8794
|
+
decision: z19.enum(["approved", "rejected", "edited"]).describe("Approval decision for this post."),
|
|
8795
|
+
edited_post: z19.record(z19.string(), z19.unknown()).optional().describe(
|
|
8796
|
+
'Revised post fields when decision is "edited" (e.g. {caption: "...", hashtags: [...]}).'
|
|
8797
|
+
),
|
|
8798
|
+
reason: z19.string().max(1e3).optional().describe("Optional reason for the decision, visible to the plan author."),
|
|
8799
|
+
response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
|
|
8800
|
+
},
|
|
8801
|
+
{
|
|
8802
|
+
title: "Respond to Plan Approval",
|
|
8803
|
+
readOnlyHint: false,
|
|
8804
|
+
destructiveHint: false,
|
|
8805
|
+
idempotentHint: true,
|
|
8806
|
+
openWorldHint: false
|
|
8407
8807
|
},
|
|
8408
8808
|
async ({ approval_id, decision, edited_post, reason, response_format }) => {
|
|
8409
8809
|
const supabase = getSupabaseClient();
|
|
@@ -8840,6 +9240,13 @@ function registerDiscoveryTools(server) {
|
|
|
8840
9240
|
'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
|
|
8841
9241
|
)
|
|
8842
9242
|
},
|
|
9243
|
+
{
|
|
9244
|
+
title: "Search Tools",
|
|
9245
|
+
readOnlyHint: true,
|
|
9246
|
+
destructiveHint: false,
|
|
9247
|
+
idempotentHint: true,
|
|
9248
|
+
openWorldHint: false
|
|
9249
|
+
},
|
|
8843
9250
|
async ({ query, module, scope, detail }) => {
|
|
8844
9251
|
let results = [...TOOL_CATALOG];
|
|
8845
9252
|
if (query) {
|