@socialneuron/mcp-server 1.5.0 → 1.5.1

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/dist/http.js CHANGED
@@ -886,6 +886,13 @@ function registerIdeationTools(server) {
886
886
  "Project ID to auto-load brand profile and performance context for prompt enrichment."
887
887
  )
888
888
  },
889
+ {
890
+ title: "Generate Content",
891
+ readOnlyHint: false,
892
+ destructiveHint: false,
893
+ idempotentHint: false,
894
+ openWorldHint: true
895
+ },
889
896
  async ({
890
897
  prompt,
891
898
  content_type,
@@ -1049,6 +1056,13 @@ Content Type: ${content_type}`;
1049
1056
  ),
1050
1057
  force_refresh: z.boolean().optional().describe("Skip the server-side cache and fetch fresh data.")
1051
1058
  },
1059
+ {
1060
+ title: "Fetch Trends",
1061
+ readOnlyHint: true,
1062
+ destructiveHint: false,
1063
+ idempotentHint: false,
1064
+ openWorldHint: true
1065
+ },
1052
1066
  async ({ source, category, niche, url, force_refresh }) => {
1053
1067
  if ((source === "rss" || source === "url") && !url) {
1054
1068
  return {
@@ -1153,6 +1167,13 @@ Content Type: ${content_type}`;
1153
1167
  "Optional project ID to load platform voice overrides from brand profile."
1154
1168
  )
1155
1169
  },
1170
+ {
1171
+ title: "Adapt Content",
1172
+ readOnlyHint: false,
1173
+ destructiveHint: false,
1174
+ idempotentHint: false,
1175
+ openWorldHint: true
1176
+ },
1156
1177
  async ({
1157
1178
  content,
1158
1179
  source_platform,
@@ -1312,7 +1333,7 @@ function sanitizeDbError(error) {
1312
1333
  init_request_context();
1313
1334
 
1314
1335
  // src/lib/version.ts
1315
- var MCP_VERSION = "1.5.0";
1336
+ var MCP_VERSION = "1.5.1";
1316
1337
 
1317
1338
  // src/tools/content.ts
1318
1339
  var MAX_CREDITS_PER_RUN = Math.max(
@@ -1454,6 +1475,13 @@ function registerContentTools(server) {
1454
1475
  ),
1455
1476
  response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
1456
1477
  },
1478
+ {
1479
+ title: "Generate Video",
1480
+ readOnlyHint: false,
1481
+ destructiveHint: false,
1482
+ idempotentHint: false,
1483
+ openWorldHint: true
1484
+ },
1457
1485
  async ({
1458
1486
  prompt,
1459
1487
  model,
@@ -1652,6 +1680,13 @@ function registerContentTools(server) {
1652
1680
  ),
1653
1681
  response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
1654
1682
  },
1683
+ {
1684
+ title: "Generate Image",
1685
+ readOnlyHint: false,
1686
+ destructiveHint: false,
1687
+ idempotentHint: false,
1688
+ openWorldHint: true
1689
+ },
1655
1690
  async ({ prompt, model, aspect_ratio, image_url, response_format }) => {
1656
1691
  const format = response_format ?? "text";
1657
1692
  const startedAt = Date.now();
@@ -1815,6 +1850,13 @@ function registerContentTools(server) {
1815
1850
  ),
1816
1851
  response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
1817
1852
  },
1853
+ {
1854
+ title: "Check Job Status",
1855
+ readOnlyHint: true,
1856
+ destructiveHint: false,
1857
+ idempotentHint: true,
1858
+ openWorldHint: true
1859
+ },
1818
1860
  async ({ job_id, response_format }) => {
1819
1861
  const format = response_format ?? "text";
1820
1862
  const startedAt = Date.now();
@@ -2012,6 +2054,13 @@ function registerContentTools(server) {
2012
2054
  "Response format. Defaults to json for structured storyboard data."
2013
2055
  )
2014
2056
  },
2057
+ {
2058
+ title: "Create Storyboard",
2059
+ readOnlyHint: false,
2060
+ destructiveHint: false,
2061
+ idempotentHint: false,
2062
+ openWorldHint: true
2063
+ },
2015
2064
  async ({
2016
2065
  concept,
2017
2066
  brand_context,
@@ -2192,6 +2241,13 @@ Return ONLY valid JSON in this exact format:
2192
2241
  speed: z2.number().min(0.5).max(2).optional().describe("Speech speed multiplier. 1.0 is normal. Defaults to 1.0."),
2193
2242
  response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
2194
2243
  },
2244
+ {
2245
+ title: "Generate Voiceover",
2246
+ readOnlyHint: false,
2247
+ destructiveHint: false,
2248
+ idempotentHint: false,
2249
+ openWorldHint: true
2250
+ },
2195
2251
  async ({ text, voice, speed, response_format }) => {
2196
2252
  const format = response_format ?? "text";
2197
2253
  const startedAt = Date.now();
@@ -2350,6 +2406,13 @@ Return ONLY valid JSON in this exact format:
2350
2406
  project_id: z2.string().optional().describe("Project ID to associate the carousel with."),
2351
2407
  response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to json.")
2352
2408
  },
2409
+ {
2410
+ title: "Generate Carousel",
2411
+ readOnlyHint: false,
2412
+ destructiveHint: false,
2413
+ idempotentHint: false,
2414
+ openWorldHint: true
2415
+ },
2353
2416
  async ({
2354
2417
  topic,
2355
2418
  template_id,
@@ -2684,6 +2747,13 @@ function registerDistributionTools(server) {
2684
2747
  'If true, appends "Created with Social Neuron" to the caption. Default: false.'
2685
2748
  )
2686
2749
  },
2750
+ {
2751
+ title: "Schedule Post",
2752
+ readOnlyHint: false,
2753
+ destructiveHint: false,
2754
+ idempotentHint: false,
2755
+ openWorldHint: true
2756
+ },
2687
2757
  async ({
2688
2758
  media_url,
2689
2759
  media_urls,
@@ -2835,6 +2905,13 @@ Created with Social Neuron`;
2835
2905
  {
2836
2906
  response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
2837
2907
  },
2908
+ {
2909
+ title: "List Connected Accounts",
2910
+ readOnlyHint: true,
2911
+ destructiveHint: false,
2912
+ idempotentHint: true,
2913
+ openWorldHint: false
2914
+ },
2838
2915
  async ({ response_format }) => {
2839
2916
  const format = response_format ?? "text";
2840
2917
  const supabase = getSupabaseClient();
@@ -2913,6 +2990,13 @@ Created with Social Neuron`;
2913
2990
  limit: z3.number().min(1).max(50).optional().describe("Maximum number of posts to return. Defaults to 20."),
2914
2991
  response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
2915
2992
  },
2993
+ {
2994
+ title: "List Recent Posts",
2995
+ readOnlyHint: true,
2996
+ destructiveHint: false,
2997
+ idempotentHint: true,
2998
+ openWorldHint: false
2999
+ },
2916
3000
  async ({ platform: platform2, status, days, limit, response_format }) => {
2917
3001
  const format = response_format ?? "text";
2918
3002
  const supabase = getSupabaseClient();
@@ -3012,7 +3096,7 @@ Created with Social Neuron`;
3012
3096
  };
3013
3097
  server.tool(
3014
3098
  "find_next_slots",
3015
- "Find optimal posting time slots based on best posting times and existing schedule. Returns non-conflicting slots sorted by engagement score.",
3099
+ "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
3100
  {
3017
3101
  platforms: z3.array(
3018
3102
  z3.enum([
@@ -3025,11 +3109,18 @@ Created with Social Neuron`;
3025
3109
  "threads",
3026
3110
  "bluesky"
3027
3111
  ])
3028
- ).min(1),
3112
+ ).min(1).describe("Platforms to find posting slots for."),
3029
3113
  count: z3.number().min(1).max(20).default(7).describe("Number of slots to find"),
3030
3114
  start_after: z3.string().optional().describe("ISO datetime, defaults to now"),
3031
3115
  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")
3116
+ response_format: z3.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
3117
+ },
3118
+ {
3119
+ title: "Find Next Posting Slots",
3120
+ readOnlyHint: true,
3121
+ destructiveHint: false,
3122
+ idempotentHint: true,
3123
+ openWorldHint: false
3033
3124
  },
3034
3125
  async ({
3035
3126
  platforms,
@@ -3149,20 +3240,20 @@ Created with Social Neuron`;
3149
3240
  plan: z3.object({
3150
3241
  posts: z3.array(
3151
3242
  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()
3243
+ id: z3.string().describe("Unique post identifier from the content plan."),
3244
+ caption: z3.string().describe("Post caption/body text."),
3245
+ platform: z3.string().describe("Target platform name (e.g. instagram, youtube)."),
3246
+ title: z3.string().optional().describe("Post title, required for YouTube."),
3247
+ media_url: z3.string().optional().describe("Public or R2 signed URL for the post media."),
3248
+ schedule_at: z3.string().optional().describe("ISO 8601 UTC datetime to publish (e.g. 2026-03-20T14:00:00Z)."),
3249
+ hashtags: z3.array(z3.string()).optional().describe("Hashtags to append to the caption.")
3159
3250
  })
3160
3251
  )
3161
- }).passthrough().optional(),
3252
+ }).passthrough().optional().describe("Inline content plan object with a posts array. Provide this or plan_id."),
3162
3253
  plan_id: z3.string().uuid().optional().describe("Persisted content plan ID from content_plans table"),
3163
3254
  auto_slot: z3.boolean().default(true).describe("Auto-assign time slots for posts without schedule_at"),
3164
3255
  dry_run: z3.boolean().default(false).describe("Preview without actually scheduling"),
3165
- response_format: z3.enum(["text", "json"]).default("text"),
3256
+ response_format: z3.enum(["text", "json"]).default("text").describe("Response format. Defaults to text."),
3166
3257
  enforce_quality: z3.boolean().default(true).describe(
3167
3258
  "When true, block scheduling for posts that fail quality checks."
3168
3259
  ),
@@ -3172,6 +3263,13 @@ Created with Social Neuron`;
3172
3263
  batch_size: z3.number().int().min(1).max(10).default(4).describe("Concurrent schedule calls per platform batch."),
3173
3264
  idempotency_seed: z3.string().max(128).optional().describe("Optional stable seed used when building idempotency keys.")
3174
3265
  },
3266
+ {
3267
+ title: "Schedule Content Plan",
3268
+ readOnlyHint: false,
3269
+ destructiveHint: false,
3270
+ idempotentHint: false,
3271
+ openWorldHint: true
3272
+ },
3175
3273
  async ({
3176
3274
  plan,
3177
3275
  plan_id,
@@ -3742,6 +3840,7 @@ function registerAnalyticsTools(server) {
3742
3840
  limit: z4.number().min(1).max(100).optional().describe("Maximum number of posts to return. Defaults to 20."),
3743
3841
  response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
3744
3842
  },
3843
+ { title: "Fetch Analytics", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
3745
3844
  async ({ platform: platform2, days, content_id, limit, response_format }) => {
3746
3845
  const format = response_format ?? "text";
3747
3846
  const supabase = getSupabaseClient();
@@ -3919,6 +4018,13 @@ function registerAnalyticsTools(server) {
3919
4018
  {
3920
4019
  response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
3921
4020
  },
4021
+ {
4022
+ title: "Refresh Platform Analytics",
4023
+ readOnlyHint: false,
4024
+ destructiveHint: false,
4025
+ idempotentHint: false,
4026
+ openWorldHint: true
4027
+ },
3922
4028
  async ({ response_format }) => {
3923
4029
  const format = response_format ?? "text";
3924
4030
  const startedAt = Date.now();
@@ -4318,6 +4424,13 @@ function registerBrandTools(server) {
4318
4424
  ),
4319
4425
  response_format: z5.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
4320
4426
  },
4427
+ {
4428
+ title: "Extract Brand",
4429
+ readOnlyHint: true,
4430
+ destructiveHint: false,
4431
+ idempotentHint: true,
4432
+ openWorldHint: true
4433
+ },
4321
4434
  async ({ url, response_format }) => {
4322
4435
  const ssrfCheck = await validateUrlForSSRF(url);
4323
4436
  if (!ssrfCheck.isValid) {
@@ -4401,6 +4514,13 @@ function registerBrandTools(server) {
4401
4514
  project_id: z5.string().uuid().optional().describe("Project ID. Defaults to active project context."),
4402
4515
  response_format: z5.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
4403
4516
  },
4517
+ {
4518
+ title: "Get Brand Profile",
4519
+ readOnlyHint: true,
4520
+ destructiveHint: false,
4521
+ idempotentHint: true,
4522
+ openWorldHint: false
4523
+ },
4404
4524
  async ({ project_id, response_format }) => {
4405
4525
  const supabase = getSupabaseClient();
4406
4526
  const userId = await getDefaultUserId();
@@ -4498,8 +4618,17 @@ function registerBrandTools(server) {
4498
4618
  "product_showcase"
4499
4619
  ]).optional().describe("Extraction method metadata."),
4500
4620
  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
- response_format: z5.enum(["text", "json"]).optional()
4621
+ extraction_metadata: z5.record(z5.string(), z5.unknown()).optional().describe(
4622
+ "Arbitrary key-value metadata about the extraction process."
4623
+ ),
4624
+ response_format: z5.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
4625
+ },
4626
+ {
4627
+ title: "Save Brand Profile",
4628
+ readOnlyHint: false,
4629
+ destructiveHint: false,
4630
+ idempotentHint: false,
4631
+ openWorldHint: false
4503
4632
  },
4504
4633
  async ({
4505
4634
  project_id,
@@ -4612,15 +4741,32 @@ Version: ${payload.version ?? "N/A"}`
4612
4741
  "facebook",
4613
4742
  "threads",
4614
4743
  "bluesky"
4615
- ]),
4744
+ ]).describe("Social platform to set voice overrides for."),
4616
4745
  project_id: z5.string().uuid().optional().describe("Project ID. Defaults to active project context."),
4617
4746
  samples: z5.string().max(3e3).optional().describe("3-5 real platform post examples for style anchoring."),
4618
- tone: z5.array(z5.string()).optional(),
4619
- style: z5.array(z5.string()).optional(),
4620
- avoid_patterns: z5.array(z5.string()).optional(),
4621
- hashtag_strategy: z5.string().max(300).optional(),
4622
- cta_style: z5.string().max(300).optional(),
4623
- response_format: z5.enum(["text", "json"]).optional()
4747
+ tone: z5.array(z5.string()).optional().describe(
4748
+ 'Tone descriptors for this platform (e.g. ["casual", "witty", "informative"]).'
4749
+ ),
4750
+ style: z5.array(z5.string()).optional().describe(
4751
+ 'Writing style tags (e.g. ["short-form", "emoji-heavy", "storytelling"]).'
4752
+ ),
4753
+ avoid_patterns: z5.array(z5.string()).optional().describe(
4754
+ 'Phrases or patterns the brand should never use on this platform (e.g. ["click here", "buy now"]).'
4755
+ ),
4756
+ hashtag_strategy: z5.string().max(300).optional().describe(
4757
+ 'Hashtag usage guidelines for this platform (e.g. "3-5 niche hashtags, no generic tags").'
4758
+ ),
4759
+ cta_style: z5.string().max(300).optional().describe(
4760
+ 'Preferred call-to-action style (e.g. "soft CTA with question" or "direct link in bio").'
4761
+ ),
4762
+ response_format: z5.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
4763
+ },
4764
+ {
4765
+ title: "Update Platform Voice",
4766
+ readOnlyHint: false,
4767
+ destructiveHint: false,
4768
+ idempotentHint: false,
4769
+ openWorldHint: false
4624
4770
  },
4625
4771
  async ({
4626
4772
  platform: platform2,
@@ -4869,6 +5015,13 @@ function registerScreenshotTools(server) {
4869
5015
  "Extra milliseconds to wait after page load before capturing. Useful for animations. Defaults to 2000."
4870
5016
  )
4871
5017
  },
5018
+ {
5019
+ title: "Capture App Page",
5020
+ readOnlyHint: true,
5021
+ destructiveHint: false,
5022
+ idempotentHint: false,
5023
+ openWorldHint: false
5024
+ },
4872
5025
  async ({ page: pageName, viewport, theme, selector, wait_ms }) => {
4873
5026
  const startedAt = Date.now();
4874
5027
  let rateLimitKey = "anonymous";
@@ -4989,6 +5142,13 @@ function registerScreenshotTools(server) {
4989
5142
  ),
4990
5143
  wait_ms: z6.number().min(0).max(3e4).optional().describe("Extra milliseconds to wait after page load before capturing. Defaults to 1000.")
4991
5144
  },
5145
+ {
5146
+ title: "Capture Screenshot",
5147
+ readOnlyHint: true,
5148
+ destructiveHint: false,
5149
+ idempotentHint: false,
5150
+ openWorldHint: true
5151
+ },
4992
5152
  async ({ url, viewport, selector, output_path, wait_ms }) => {
4993
5153
  const startedAt = Date.now();
4994
5154
  let rateLimitKey = "anonymous";
@@ -5251,6 +5411,13 @@ function registerRemotionTools(server) {
5251
5411
  "list_compositions",
5252
5412
  "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
5413
  {},
5414
+ {
5415
+ title: "List Compositions",
5416
+ readOnlyHint: true,
5417
+ destructiveHint: false,
5418
+ idempotentHint: true,
5419
+ openWorldHint: false
5420
+ },
5254
5421
  async () => {
5255
5422
  const lines = [`${COMPOSITIONS.length} Remotion compositions available:`, ""];
5256
5423
  for (const comp of COMPOSITIONS) {
@@ -5279,6 +5446,13 @@ function registerRemotionTools(server) {
5279
5446
  "JSON string of input props to pass to the composition. Each composition accepts different props. Omit for defaults."
5280
5447
  )
5281
5448
  },
5449
+ {
5450
+ title: "Render Demo Video",
5451
+ readOnlyHint: false,
5452
+ destructiveHint: false,
5453
+ idempotentHint: false,
5454
+ openWorldHint: false
5455
+ },
5282
5456
  async ({ composition_id, output_format, props }) => {
5283
5457
  const startedAt = Date.now();
5284
5458
  const userId = await getDefaultUserId();
@@ -5459,6 +5633,13 @@ function registerInsightsTools(server) {
5459
5633
  limit: z8.number().min(1).max(50).optional().describe("Maximum number of insights to return. Defaults to 10."),
5460
5634
  response_format: z8.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
5461
5635
  },
5636
+ {
5637
+ title: "Get Performance Insights",
5638
+ readOnlyHint: true,
5639
+ destructiveHint: false,
5640
+ idempotentHint: true,
5641
+ openWorldHint: false
5642
+ },
5462
5643
  async ({ insight_type, days, limit, response_format }) => {
5463
5644
  const format = response_format ?? "text";
5464
5645
  const supabase = getSupabaseClient();
@@ -5586,6 +5767,13 @@ function registerInsightsTools(server) {
5586
5767
  days: z8.number().min(1).max(90).optional().describe("Number of days to analyze. Defaults to 30. Max 90."),
5587
5768
  response_format: z8.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
5588
5769
  },
5770
+ {
5771
+ title: "Get Best Posting Times",
5772
+ readOnlyHint: true,
5773
+ destructiveHint: false,
5774
+ idempotentHint: true,
5775
+ openWorldHint: false
5776
+ },
5589
5777
  async ({ platform: platform2, days, response_format }) => {
5590
5778
  const format = response_format ?? "text";
5591
5779
  const supabase = getSupabaseClient();
@@ -5734,6 +5922,13 @@ function registerYouTubeAnalyticsTools(server) {
5734
5922
  video_id: z9.string().optional().describe('YouTube video ID. Required when action is "video".'),
5735
5923
  max_results: z9.number().min(1).max(50).optional().describe('Max videos to return for "topVideos" action. Defaults to 10.')
5736
5924
  },
5925
+ {
5926
+ title: "Fetch YouTube Analytics",
5927
+ readOnlyHint: true,
5928
+ destructiveHint: false,
5929
+ idempotentHint: true,
5930
+ openWorldHint: true
5931
+ },
5737
5932
  async ({ action, start_date, end_date, video_id, max_results }) => {
5738
5933
  if (action === "video" && !video_id) {
5739
5934
  return {
@@ -5852,6 +6047,13 @@ function registerCommentsTools(server) {
5852
6047
  page_token: z10.string().optional().describe("Pagination cursor from previous list_comments response nextPageToken field. Omit for first page of results."),
5853
6048
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
5854
6049
  },
6050
+ {
6051
+ title: "List Comments",
6052
+ readOnlyHint: true,
6053
+ destructiveHint: false,
6054
+ idempotentHint: true,
6055
+ openWorldHint: true
6056
+ },
5855
6057
  async ({ video_id, max_results, page_token, response_format }) => {
5856
6058
  const format = response_format ?? "text";
5857
6059
  const { data, error } = await callEdgeFunction("youtube-comments", {
@@ -5922,6 +6124,13 @@ function registerCommentsTools(server) {
5922
6124
  text: z10.string().min(1).describe("The reply text."),
5923
6125
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
5924
6126
  },
6127
+ {
6128
+ title: "Reply to Comment",
6129
+ readOnlyHint: false,
6130
+ destructiveHint: false,
6131
+ idempotentHint: false,
6132
+ openWorldHint: true
6133
+ },
5925
6134
  async ({ parent_id, text, response_format }) => {
5926
6135
  const format = response_format ?? "text";
5927
6136
  const startedAt = Date.now();
@@ -6003,6 +6212,13 @@ function registerCommentsTools(server) {
6003
6212
  text: z10.string().min(1).describe("The comment text."),
6004
6213
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6005
6214
  },
6215
+ {
6216
+ title: "Post Comment",
6217
+ readOnlyHint: false,
6218
+ destructiveHint: false,
6219
+ idempotentHint: false,
6220
+ openWorldHint: true
6221
+ },
6006
6222
  async ({ video_id, text, response_format }) => {
6007
6223
  const format = response_format ?? "text";
6008
6224
  const startedAt = Date.now();
@@ -6081,6 +6297,13 @@ function registerCommentsTools(server) {
6081
6297
  moderation_status: z10.enum(["published", "rejected"]).describe('"published" to approve, "rejected" to hide.'),
6082
6298
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6083
6299
  },
6300
+ {
6301
+ title: "Moderate Comment",
6302
+ readOnlyHint: false,
6303
+ destructiveHint: false,
6304
+ idempotentHint: true,
6305
+ openWorldHint: true
6306
+ },
6084
6307
  async ({ comment_id, moderation_status, response_format }) => {
6085
6308
  const format = response_format ?? "text";
6086
6309
  const startedAt = Date.now();
@@ -6166,6 +6389,13 @@ function registerCommentsTools(server) {
6166
6389
  comment_id: z10.string().describe("The comment ID to delete."),
6167
6390
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6168
6391
  },
6392
+ {
6393
+ title: "Delete Comment",
6394
+ readOnlyHint: false,
6395
+ destructiveHint: true,
6396
+ idempotentHint: true,
6397
+ openWorldHint: true
6398
+ },
6169
6399
  async ({ comment_id, response_format }) => {
6170
6400
  const format = response_format ?? "text";
6171
6401
  const startedAt = Date.now();
@@ -6314,12 +6544,19 @@ function asEnvelope7(data) {
6314
6544
  function registerIdeationContextTools(server) {
6315
6545
  server.tool(
6316
6546
  "get_ideation_context",
6317
- "Get synthesized ideation context from performance insights. Returns the same prompt-injection context used by ideation generation.",
6547
+ "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
6548
  {
6319
6549
  project_id: z11.string().uuid().optional().describe("Project ID to scope insights."),
6320
6550
  days: z11.number().min(1).max(90).optional().describe("Lookback window for insights. Defaults to 30 days."),
6321
6551
  response_format: z11.enum(["text", "json"]).optional().describe("Optional output format. Defaults to text.")
6322
6552
  },
6553
+ {
6554
+ title: "Get Ideation Context",
6555
+ readOnlyHint: true,
6556
+ destructiveHint: false,
6557
+ idempotentHint: true,
6558
+ openWorldHint: false
6559
+ },
6323
6560
  async ({ project_id, days, response_format }) => {
6324
6561
  const supabase = getSupabaseClient();
6325
6562
  const userId = await getDefaultUserId();
@@ -6449,6 +6686,13 @@ function registerCreditsTools(server) {
6449
6686
  {
6450
6687
  response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6451
6688
  },
6689
+ {
6690
+ title: "Get Credit Balance",
6691
+ readOnlyHint: true,
6692
+ destructiveHint: false,
6693
+ idempotentHint: true,
6694
+ openWorldHint: false
6695
+ },
6452
6696
  async ({ response_format }) => {
6453
6697
  const supabase = getSupabaseClient();
6454
6698
  const userId = await getDefaultUserId();
@@ -6502,6 +6746,13 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
6502
6746
  {
6503
6747
  response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6504
6748
  },
6749
+ {
6750
+ title: "Get Budget Status",
6751
+ readOnlyHint: true,
6752
+ destructiveHint: false,
6753
+ idempotentHint: true,
6754
+ openWorldHint: false
6755
+ },
6505
6756
  async ({ response_format }) => {
6506
6757
  const budget = getCurrentBudgetStatus();
6507
6758
  const payload = {
@@ -6555,11 +6806,18 @@ function asEnvelope9(data) {
6555
6806
  function registerLoopSummaryTools(server) {
6556
6807
  server.tool(
6557
6808
  "get_loop_summary",
6558
- "Get a one-call dashboard summary of the feedback loop state (brand profile, recent content, and current insights).",
6809
+ "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
6810
  {
6560
6811
  project_id: z13.string().uuid().optional().describe("Project ID. Defaults to active project context."),
6561
6812
  response_format: z13.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6562
6813
  },
6814
+ {
6815
+ title: "Get Loop Summary",
6816
+ readOnlyHint: true,
6817
+ destructiveHint: false,
6818
+ idempotentHint: true,
6819
+ openWorldHint: false
6820
+ },
6563
6821
  async ({ project_id, response_format }) => {
6564
6822
  const supabase = getSupabaseClient();
6565
6823
  const userId = await getDefaultUserId();
@@ -6657,6 +6915,13 @@ function registerUsageTools(server) {
6657
6915
  {
6658
6916
  response_format: z14.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6659
6917
  },
6918
+ {
6919
+ title: "Get MCP Usage",
6920
+ readOnlyHint: true,
6921
+ destructiveHint: false,
6922
+ idempotentHint: true,
6923
+ openWorldHint: false
6924
+ },
6660
6925
  async ({ response_format }) => {
6661
6926
  const format = response_format ?? "text";
6662
6927
  const supabase = getSupabaseClient();
@@ -6746,6 +7011,13 @@ function registerAutopilotTools(server) {
6746
7011
  active_only: z15.boolean().optional().describe("If true, only return active configs. Defaults to false (show all)."),
6747
7012
  response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6748
7013
  },
7014
+ {
7015
+ title: "List Autopilot Configs",
7016
+ readOnlyHint: true,
7017
+ destructiveHint: false,
7018
+ idempotentHint: true,
7019
+ openWorldHint: false
7020
+ },
6749
7021
  async ({ active_only, response_format }) => {
6750
7022
  const format = response_format ?? "text";
6751
7023
  const supabase = getSupabaseClient();
@@ -6823,11 +7095,18 @@ ${"=".repeat(40)}
6823
7095
  {
6824
7096
  config_id: z15.string().uuid().describe("The autopilot config ID to update."),
6825
7097
  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., ["mon", "wed", "fri"]).'),
7098
+ 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
7099
  schedule_time: z15.string().optional().describe('Time to run in HH:MM format (24h, user timezone). E.g., "09:00".'),
6828
7100
  max_credits_per_run: z15.number().optional().describe("Maximum credits per execution."),
6829
7101
  max_credits_per_week: z15.number().optional().describe("Maximum credits per week.")
6830
7102
  },
7103
+ {
7104
+ title: "Update Autopilot Config",
7105
+ readOnlyHint: false,
7106
+ destructiveHint: false,
7107
+ idempotentHint: true,
7108
+ openWorldHint: false
7109
+ },
6831
7110
  async ({
6832
7111
  config_id,
6833
7112
  is_active,
@@ -6891,6 +7170,13 @@ Schedule: ${JSON.stringify(updated.schedule_config)}`
6891
7170
  {
6892
7171
  response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6893
7172
  },
7173
+ {
7174
+ title: "Get Autopilot Status",
7175
+ readOnlyHint: true,
7176
+ destructiveHint: false,
7177
+ idempotentHint: true,
7178
+ openWorldHint: false
7179
+ },
6894
7180
  async ({ response_format }) => {
6895
7181
  const format = response_format ?? "text";
6896
7182
  const supabase = getSupabaseClient();
@@ -7010,7 +7296,14 @@ function registerExtractionTools(server) {
7010
7296
  extract_type: z16.enum(["auto", "transcript", "article", "product"]).default("auto").describe("Type of extraction"),
7011
7297
  include_comments: z16.boolean().default(false).describe("Include top comments (YouTube only)"),
7012
7298
  max_results: z16.number().min(1).max(100).default(10).describe("Max comments to include"),
7013
- response_format: z16.enum(["text", "json"]).default("text")
7299
+ response_format: z16.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
7300
+ },
7301
+ {
7302
+ title: "Extract URL Content",
7303
+ readOnlyHint: true,
7304
+ destructiveHint: false,
7305
+ idempotentHint: true,
7306
+ openWorldHint: true
7014
7307
  },
7015
7308
  async ({
7016
7309
  url,
@@ -7220,9 +7513,16 @@ function registerQualityTools(server) {
7220
7513
  ).min(1).describe("Target platforms"),
7221
7514
  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
7515
  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")
7516
+ 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."),
7517
+ custom_banned_terms: z17.array(z17.string()).optional().describe("Additional banned words beyond the built-in safety list. Useful for industry-specific compliance terms."),
7518
+ response_format: z17.enum(["text", "json"]).default("text").describe("'text' for human-readable report, 'json' for structured scores suitable for pipeline automation.")
7519
+ },
7520
+ {
7521
+ title: "Quality Check",
7522
+ readOnlyHint: true,
7523
+ destructiveHint: false,
7524
+ idempotentHint: true,
7525
+ openWorldHint: false
7226
7526
  },
7227
7527
  async ({
7228
7528
  caption,
@@ -7294,15 +7594,22 @@ function registerQualityTools(server) {
7294
7594
  plan: z17.object({
7295
7595
  posts: z17.array(
7296
7596
  z17.object({
7297
- id: z17.string(),
7298
- caption: z17.string(),
7299
- title: z17.string().optional(),
7300
- platform: z17.string()
7597
+ id: z17.string().describe("Unique post identifier."),
7598
+ caption: z17.string().describe("Post caption/body text to quality-check."),
7599
+ title: z17.string().optional().describe("Post title (important for YouTube)."),
7600
+ platform: z17.string().describe("Target platform (e.g. instagram, youtube).")
7301
7601
  })
7302
7602
  )
7303
- }).passthrough().describe("Content plan with posts array"),
7603
+ }).passthrough().describe("Content plan with posts array."),
7304
7604
  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")
7605
+ response_format: z17.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
7606
+ },
7607
+ {
7608
+ title: "Quality Check Plan",
7609
+ readOnlyHint: true,
7610
+ destructiveHint: false,
7611
+ idempotentHint: true,
7612
+ openWorldHint: false
7306
7613
  },
7307
7614
  async ({ plan, threshold, response_format }) => {
7308
7615
  const startedAt = Date.now();
@@ -7503,7 +7810,14 @@ function registerPlanningTools(server) {
7503
7810
  start_date: z18.string().optional().describe("ISO date, defaults to tomorrow"),
7504
7811
  brand_voice: z18.string().optional().describe("Override brand voice description"),
7505
7812
  project_id: z18.string().optional().describe("Project ID for brand/insights context"),
7506
- response_format: z18.enum(["text", "json"]).default("json")
7813
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
7814
+ },
7815
+ {
7816
+ title: "Plan Content Week",
7817
+ readOnlyHint: false,
7818
+ destructiveHint: false,
7819
+ idempotentHint: false,
7820
+ openWorldHint: true
7507
7821
  },
7508
7822
  async ({
7509
7823
  topic,
@@ -7821,12 +8135,19 @@ ${rawText.slice(0, 1e3)}`
7821
8135
  "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
8136
  {
7823
8137
  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")
8138
+ topic: z18.string().describe("Content plan topic or theme."),
8139
+ posts: z18.array(z18.record(z18.string(), z18.unknown())).describe("Array of post objects to save.")
8140
+ }).passthrough().describe("Content plan object with topic and posts array."),
8141
+ project_id: z18.string().uuid().optional().describe("Project ID. Defaults to active project context."),
8142
+ status: z18.enum(["draft", "in_review", "approved", "scheduled", "completed"]).default("draft").describe("Initial plan status. Defaults to draft."),
8143
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
8144
+ },
8145
+ {
8146
+ title: "Save Content Plan",
8147
+ readOnlyHint: false,
8148
+ destructiveHint: false,
8149
+ idempotentHint: false,
8150
+ openWorldHint: false
7830
8151
  },
7831
8152
  async ({ plan, project_id, status, response_format }) => {
7832
8153
  const startedAt = Date.now();
@@ -7924,10 +8245,17 @@ ${rawText.slice(0, 1e3)}`
7924
8245
  );
7925
8246
  server.tool(
7926
8247
  "get_content_plan",
7927
- "Retrieve a persisted content plan by ID.",
8248
+ "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
8249
  {
7929
8250
  plan_id: z18.string().uuid().describe("Persisted content plan ID"),
7930
- response_format: z18.enum(["text", "json"]).default("json")
8251
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
8252
+ },
8253
+ {
8254
+ title: "Get Content Plan",
8255
+ readOnlyHint: true,
8256
+ destructiveHint: false,
8257
+ idempotentHint: true,
8258
+ openWorldHint: false
7931
8259
  },
7932
8260
  async ({ plan_id, response_format }) => {
7933
8261
  const supabase = getSupabaseClient();
@@ -7992,25 +8320,32 @@ ${rawText.slice(0, 1e3)}`
7992
8320
  );
7993
8321
  server.tool(
7994
8322
  "update_content_plan",
7995
- "Update individual posts in a persisted content plan.",
8323
+ "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
8324
  {
7997
- plan_id: z18.string().uuid(),
8325
+ plan_id: z18.string().uuid().describe("Content plan ID to update."),
7998
8326
  post_updates: z18.array(
7999
8327
  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()
8328
+ post_id: z18.string().describe("ID of the post to update within this plan."),
8329
+ caption: z18.string().optional().describe("Revised caption/body text."),
8330
+ title: z18.string().optional().describe("Revised post title."),
8331
+ hashtags: z18.array(z18.string()).optional().describe("Revised hashtags array."),
8332
+ hook: z18.string().optional().describe("Revised attention-grabbing opening line."),
8333
+ angle: z18.string().optional().describe("Revised content angle or perspective."),
8334
+ visual_direction: z18.string().optional().describe("Revised visual/media direction notes."),
8335
+ media_url: z18.string().optional().describe("Revised media URL (public or R2 signed URL)."),
8336
+ schedule_at: z18.string().optional().describe("Revised ISO 8601 UTC publish datetime."),
8337
+ platform: z18.string().optional().describe("Revised target platform."),
8338
+ status: z18.enum(["approved", "rejected", "needs_edit"]).optional().describe("Review status for this post.")
8011
8339
  })
8012
- ).min(1),
8013
- response_format: z18.enum(["text", "json"]).default("json")
8340
+ ).min(1).describe("Array of post-level updates to apply."),
8341
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
8342
+ },
8343
+ {
8344
+ title: "Update Content Plan",
8345
+ readOnlyHint: false,
8346
+ destructiveHint: false,
8347
+ idempotentHint: true,
8348
+ openWorldHint: false
8014
8349
  },
8015
8350
  async ({ plan_id, post_updates, response_format }) => {
8016
8351
  const supabase = getSupabaseClient();
@@ -8111,10 +8446,17 @@ ${rawText.slice(0, 1e3)}`
8111
8446
  );
8112
8447
  server.tool(
8113
8448
  "submit_content_plan_for_approval",
8114
- "Create pending approval items for each post in a plan and mark plan status as in_review.",
8449
+ "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.",
8450
+ {
8451
+ plan_id: z18.string().uuid().describe("Content plan ID to submit for review."),
8452
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
8453
+ },
8115
8454
  {
8116
- plan_id: z18.string().uuid(),
8117
- response_format: z18.enum(["text", "json"]).default("json")
8455
+ title: "Submit Plan for Approval",
8456
+ readOnlyHint: false,
8457
+ destructiveHint: false,
8458
+ idempotentHint: true,
8459
+ openWorldHint: false
8118
8460
  },
8119
8461
  async ({ plan_id, response_format }) => {
8120
8462
  const supabase = getSupabaseClient();
@@ -8238,21 +8580,28 @@ async function assertProjectAccess(supabase, userId, projectId) {
8238
8580
  function registerPlanApprovalTools(server) {
8239
8581
  server.tool(
8240
8582
  "create_plan_approvals",
8241
- "Create pending approval rows for each post in a content plan.",
8583
+ "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
8584
  {
8243
8585
  plan_id: z19.string().uuid().describe("Content plan ID"),
8244
8586
  posts: z19.array(
8245
8587
  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()
8588
+ id: z19.string().describe("Unique post identifier from the content plan."),
8589
+ platform: z19.string().optional().describe("Target platform (e.g. instagram, youtube)."),
8590
+ caption: z19.string().optional().describe("Post caption/body text."),
8591
+ title: z19.string().optional().describe("Post title, used by YouTube and LinkedIn articles."),
8592
+ media_url: z19.string().optional().describe("Public or R2 signed URL for the post media."),
8593
+ schedule_at: z19.string().optional().describe("ISO 8601 UTC datetime to publish (e.g. 2026-03-20T14:00:00Z).")
8252
8594
  }).passthrough()
8253
8595
  ).min(1).describe("Posts to create approval entries for."),
8254
8596
  project_id: z19.string().uuid().optional().describe("Project ID. Defaults to active project context."),
8255
- response_format: z19.enum(["text", "json"]).optional()
8597
+ response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
8598
+ },
8599
+ {
8600
+ title: "Create Plan Approvals",
8601
+ readOnlyHint: false,
8602
+ destructiveHint: false,
8603
+ idempotentHint: false,
8604
+ openWorldHint: false
8256
8605
  },
8257
8606
  async ({ plan_id, posts, project_id, response_format }) => {
8258
8607
  const supabase = getSupabaseClient();
@@ -8332,8 +8681,15 @@ function registerPlanApprovalTools(server) {
8332
8681
  "List MCP-native approval items for a specific content plan.",
8333
8682
  {
8334
8683
  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()
8684
+ status: z19.enum(["pending", "approved", "rejected", "edited"]).optional().describe("Filter approvals by status. Omit to return all statuses."),
8685
+ response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
8686
+ },
8687
+ {
8688
+ title: "List Plan Approvals",
8689
+ readOnlyHint: true,
8690
+ destructiveHint: false,
8691
+ idempotentHint: true,
8692
+ openWorldHint: false
8337
8693
  },
8338
8694
  async ({ plan_id, status, response_format }) => {
8339
8695
  const supabase = getSupabaseClient();
@@ -8400,10 +8756,19 @@ function registerPlanApprovalTools(server) {
8400
8756
  "Approve, reject, or edit a pending plan approval item.",
8401
8757
  {
8402
8758
  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
- reason: z19.string().max(1e3).optional(),
8406
- response_format: z19.enum(["text", "json"]).optional()
8759
+ decision: z19.enum(["approved", "rejected", "edited"]).describe("Approval decision for this post."),
8760
+ edited_post: z19.record(z19.string(), z19.unknown()).optional().describe(
8761
+ 'Revised post fields when decision is "edited" (e.g. {caption: "...", hashtags: [...]}).'
8762
+ ),
8763
+ reason: z19.string().max(1e3).optional().describe("Optional reason for the decision, visible to the plan author."),
8764
+ response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
8765
+ },
8766
+ {
8767
+ title: "Respond to Plan Approval",
8768
+ readOnlyHint: false,
8769
+ destructiveHint: false,
8770
+ idempotentHint: true,
8771
+ openWorldHint: false
8407
8772
  },
8408
8773
  async ({ approval_id, decision, edited_post, reason, response_format }) => {
8409
8774
  const supabase = getSupabaseClient();
@@ -8840,6 +9205,13 @@ function registerDiscoveryTools(server) {
8840
9205
  'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
8841
9206
  )
8842
9207
  },
9208
+ {
9209
+ title: "Search Tools",
9210
+ readOnlyHint: true,
9211
+ destructiveHint: false,
9212
+ idempotentHint: true,
9213
+ openWorldHint: false
9214
+ },
8843
9215
  async ({ query, module, scope, detail }) => {
8844
9216
  let results = [...TOOL_CATALOG];
8845
9217
  if (query) {