@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/index.js CHANGED
@@ -14,7 +14,7 @@ var MCP_VERSION;
14
14
  var init_version = __esm({
15
15
  "src/lib/version.ts"() {
16
16
  "use strict";
17
- MCP_VERSION = "1.5.0";
17
+ MCP_VERSION = "1.5.1";
18
18
  }
19
19
  });
20
20
 
@@ -3928,6 +3928,13 @@ function registerIdeationTools(server2) {
3928
3928
  "Project ID to auto-load brand profile and performance context for prompt enrichment."
3929
3929
  )
3930
3930
  },
3931
+ {
3932
+ title: "Generate Content",
3933
+ readOnlyHint: false,
3934
+ destructiveHint: false,
3935
+ idempotentHint: false,
3936
+ openWorldHint: true
3937
+ },
3931
3938
  async ({
3932
3939
  prompt: prompt2,
3933
3940
  content_type,
@@ -4091,6 +4098,13 @@ Content Type: ${content_type}`;
4091
4098
  ),
4092
4099
  force_refresh: z.boolean().optional().describe("Skip the server-side cache and fetch fresh data.")
4093
4100
  },
4101
+ {
4102
+ title: "Fetch Trends",
4103
+ readOnlyHint: true,
4104
+ destructiveHint: false,
4105
+ idempotentHint: false,
4106
+ openWorldHint: true
4107
+ },
4094
4108
  async ({ source, category, niche, url, force_refresh }) => {
4095
4109
  if ((source === "rss" || source === "url") && !url) {
4096
4110
  return {
@@ -4195,6 +4209,13 @@ Content Type: ${content_type}`;
4195
4209
  "Optional project ID to load platform voice overrides from brand profile."
4196
4210
  )
4197
4211
  },
4212
+ {
4213
+ title: "Adapt Content",
4214
+ readOnlyHint: false,
4215
+ destructiveHint: false,
4216
+ idempotentHint: false,
4217
+ openWorldHint: true
4218
+ },
4198
4219
  async ({
4199
4220
  content,
4200
4221
  source_platform,
@@ -4493,6 +4514,13 @@ function registerContentTools(server2) {
4493
4514
  ),
4494
4515
  response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
4495
4516
  },
4517
+ {
4518
+ title: "Generate Video",
4519
+ readOnlyHint: false,
4520
+ destructiveHint: false,
4521
+ idempotentHint: false,
4522
+ openWorldHint: true
4523
+ },
4496
4524
  async ({
4497
4525
  prompt: prompt2,
4498
4526
  model,
@@ -4691,6 +4719,13 @@ function registerContentTools(server2) {
4691
4719
  ),
4692
4720
  response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
4693
4721
  },
4722
+ {
4723
+ title: "Generate Image",
4724
+ readOnlyHint: false,
4725
+ destructiveHint: false,
4726
+ idempotentHint: false,
4727
+ openWorldHint: true
4728
+ },
4694
4729
  async ({ prompt: prompt2, model, aspect_ratio, image_url, response_format }) => {
4695
4730
  const format = response_format ?? "text";
4696
4731
  const startedAt = Date.now();
@@ -4854,6 +4889,13 @@ function registerContentTools(server2) {
4854
4889
  ),
4855
4890
  response_format: z2.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
4856
4891
  },
4892
+ {
4893
+ title: "Check Job Status",
4894
+ readOnlyHint: true,
4895
+ destructiveHint: false,
4896
+ idempotentHint: true,
4897
+ openWorldHint: true
4898
+ },
4857
4899
  async ({ job_id, response_format }) => {
4858
4900
  const format = response_format ?? "text";
4859
4901
  const startedAt = Date.now();
@@ -5051,6 +5093,13 @@ function registerContentTools(server2) {
5051
5093
  "Response format. Defaults to json for structured storyboard data."
5052
5094
  )
5053
5095
  },
5096
+ {
5097
+ title: "Create Storyboard",
5098
+ readOnlyHint: false,
5099
+ destructiveHint: false,
5100
+ idempotentHint: false,
5101
+ openWorldHint: true
5102
+ },
5054
5103
  async ({
5055
5104
  concept,
5056
5105
  brand_context,
@@ -5231,6 +5280,13 @@ Return ONLY valid JSON in this exact format:
5231
5280
  speed: z2.number().min(0.5).max(2).optional().describe("Speech speed multiplier. 1.0 is normal. Defaults to 1.0."),
5232
5281
  response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
5233
5282
  },
5283
+ {
5284
+ title: "Generate Voiceover",
5285
+ readOnlyHint: false,
5286
+ destructiveHint: false,
5287
+ idempotentHint: false,
5288
+ openWorldHint: true
5289
+ },
5234
5290
  async ({ text, voice, speed, response_format }) => {
5235
5291
  const format = response_format ?? "text";
5236
5292
  const startedAt = Date.now();
@@ -5389,6 +5445,13 @@ Return ONLY valid JSON in this exact format:
5389
5445
  project_id: z2.string().optional().describe("Project ID to associate the carousel with."),
5390
5446
  response_format: z2.enum(["text", "json"]).optional().describe("Response format. Defaults to json.")
5391
5447
  },
5448
+ {
5449
+ title: "Generate Carousel",
5450
+ readOnlyHint: false,
5451
+ destructiveHint: false,
5452
+ idempotentHint: false,
5453
+ openWorldHint: true
5454
+ },
5392
5455
  async ({
5393
5456
  topic,
5394
5457
  template_id,
@@ -5602,6 +5665,13 @@ function registerDistributionTools(server2) {
5602
5665
  'If true, appends "Created with Social Neuron" to the caption. Default: false.'
5603
5666
  )
5604
5667
  },
5668
+ {
5669
+ title: "Schedule Post",
5670
+ readOnlyHint: false,
5671
+ destructiveHint: false,
5672
+ idempotentHint: false,
5673
+ openWorldHint: true
5674
+ },
5605
5675
  async ({
5606
5676
  media_url,
5607
5677
  media_urls,
@@ -5753,6 +5823,13 @@ Created with Social Neuron`;
5753
5823
  {
5754
5824
  response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
5755
5825
  },
5826
+ {
5827
+ title: "List Connected Accounts",
5828
+ readOnlyHint: true,
5829
+ destructiveHint: false,
5830
+ idempotentHint: true,
5831
+ openWorldHint: false
5832
+ },
5756
5833
  async ({ response_format }) => {
5757
5834
  const format = response_format ?? "text";
5758
5835
  const supabase = getSupabaseClient();
@@ -5831,6 +5908,13 @@ Created with Social Neuron`;
5831
5908
  limit: z3.number().min(1).max(50).optional().describe("Maximum number of posts to return. Defaults to 20."),
5832
5909
  response_format: z3.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
5833
5910
  },
5911
+ {
5912
+ title: "List Recent Posts",
5913
+ readOnlyHint: true,
5914
+ destructiveHint: false,
5915
+ idempotentHint: true,
5916
+ openWorldHint: false
5917
+ },
5834
5918
  async ({ platform: platform3, status, days, limit, response_format }) => {
5835
5919
  const format = response_format ?? "text";
5836
5920
  const supabase = getSupabaseClient();
@@ -5930,7 +6014,7 @@ Created with Social Neuron`;
5930
6014
  };
5931
6015
  server2.tool(
5932
6016
  "find_next_slots",
5933
- "Find optimal posting time slots based on best posting times and existing schedule. Returns non-conflicting slots sorted by engagement score.",
6017
+ "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.",
5934
6018
  {
5935
6019
  platforms: z3.array(
5936
6020
  z3.enum([
@@ -5943,11 +6027,18 @@ Created with Social Neuron`;
5943
6027
  "threads",
5944
6028
  "bluesky"
5945
6029
  ])
5946
- ).min(1),
6030
+ ).min(1).describe("Platforms to find posting slots for."),
5947
6031
  count: z3.number().min(1).max(20).default(7).describe("Number of slots to find"),
5948
6032
  start_after: z3.string().optional().describe("ISO datetime, defaults to now"),
5949
6033
  min_gap_hours: z3.number().min(1).max(24).default(4).describe("Minimum gap between posts on same platform"),
5950
- response_format: z3.enum(["text", "json"]).default("text")
6034
+ response_format: z3.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
6035
+ },
6036
+ {
6037
+ title: "Find Next Posting Slots",
6038
+ readOnlyHint: true,
6039
+ destructiveHint: false,
6040
+ idempotentHint: true,
6041
+ openWorldHint: false
5951
6042
  },
5952
6043
  async ({
5953
6044
  platforms,
@@ -6067,20 +6158,20 @@ Created with Social Neuron`;
6067
6158
  plan: z3.object({
6068
6159
  posts: z3.array(
6069
6160
  z3.object({
6070
- id: z3.string(),
6071
- caption: z3.string(),
6072
- platform: z3.string(),
6073
- title: z3.string().optional(),
6074
- media_url: z3.string().optional(),
6075
- schedule_at: z3.string().optional(),
6076
- hashtags: z3.array(z3.string()).optional()
6161
+ id: z3.string().describe("Unique post identifier from the content plan."),
6162
+ caption: z3.string().describe("Post caption/body text."),
6163
+ platform: z3.string().describe("Target platform name (e.g. instagram, youtube)."),
6164
+ title: z3.string().optional().describe("Post title, required for YouTube."),
6165
+ media_url: z3.string().optional().describe("Public or R2 signed URL for the post media."),
6166
+ schedule_at: z3.string().optional().describe("ISO 8601 UTC datetime to publish (e.g. 2026-03-20T14:00:00Z)."),
6167
+ hashtags: z3.array(z3.string()).optional().describe("Hashtags to append to the caption.")
6077
6168
  })
6078
6169
  )
6079
- }).passthrough().optional(),
6170
+ }).passthrough().optional().describe("Inline content plan object with a posts array. Provide this or plan_id."),
6080
6171
  plan_id: z3.string().uuid().optional().describe("Persisted content plan ID from content_plans table"),
6081
6172
  auto_slot: z3.boolean().default(true).describe("Auto-assign time slots for posts without schedule_at"),
6082
6173
  dry_run: z3.boolean().default(false).describe("Preview without actually scheduling"),
6083
- response_format: z3.enum(["text", "json"]).default("text"),
6174
+ response_format: z3.enum(["text", "json"]).default("text").describe("Response format. Defaults to text."),
6084
6175
  enforce_quality: z3.boolean().default(true).describe(
6085
6176
  "When true, block scheduling for posts that fail quality checks."
6086
6177
  ),
@@ -6090,6 +6181,13 @@ Created with Social Neuron`;
6090
6181
  batch_size: z3.number().int().min(1).max(10).default(4).describe("Concurrent schedule calls per platform batch."),
6091
6182
  idempotency_seed: z3.string().max(128).optional().describe("Optional stable seed used when building idempotency keys.")
6092
6183
  },
6184
+ {
6185
+ title: "Schedule Content Plan",
6186
+ readOnlyHint: false,
6187
+ destructiveHint: false,
6188
+ idempotentHint: false,
6189
+ openWorldHint: true
6190
+ },
6093
6191
  async ({
6094
6192
  plan,
6095
6193
  plan_id,
@@ -6662,6 +6760,7 @@ function registerAnalyticsTools(server2) {
6662
6760
  limit: z4.number().min(1).max(100).optional().describe("Maximum number of posts to return. Defaults to 20."),
6663
6761
  response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6664
6762
  },
6763
+ { title: "Fetch Analytics", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
6665
6764
  async ({ platform: platform3, days, content_id, limit, response_format }) => {
6666
6765
  const format = response_format ?? "text";
6667
6766
  const supabase = getSupabaseClient();
@@ -6839,6 +6938,13 @@ function registerAnalyticsTools(server2) {
6839
6938
  {
6840
6939
  response_format: z4.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
6841
6940
  },
6941
+ {
6942
+ title: "Refresh Platform Analytics",
6943
+ readOnlyHint: false,
6944
+ destructiveHint: false,
6945
+ idempotentHint: false,
6946
+ openWorldHint: true
6947
+ },
6842
6948
  async ({ response_format }) => {
6843
6949
  const format = response_format ?? "text";
6844
6950
  const startedAt = Date.now();
@@ -7240,6 +7346,13 @@ function registerBrandTools(server2) {
7240
7346
  ),
7241
7347
  response_format: z5.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
7242
7348
  },
7349
+ {
7350
+ title: "Extract Brand",
7351
+ readOnlyHint: true,
7352
+ destructiveHint: false,
7353
+ idempotentHint: true,
7354
+ openWorldHint: true
7355
+ },
7243
7356
  async ({ url, response_format }) => {
7244
7357
  const ssrfCheck = await validateUrlForSSRF(url);
7245
7358
  if (!ssrfCheck.isValid) {
@@ -7323,6 +7436,13 @@ function registerBrandTools(server2) {
7323
7436
  project_id: z5.string().uuid().optional().describe("Project ID. Defaults to active project context."),
7324
7437
  response_format: z5.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
7325
7438
  },
7439
+ {
7440
+ title: "Get Brand Profile",
7441
+ readOnlyHint: true,
7442
+ destructiveHint: false,
7443
+ idempotentHint: true,
7444
+ openWorldHint: false
7445
+ },
7326
7446
  async ({ project_id, response_format }) => {
7327
7447
  const supabase = getSupabaseClient();
7328
7448
  const userId = await getDefaultUserId();
@@ -7420,8 +7540,17 @@ function registerBrandTools(server2) {
7420
7540
  "product_showcase"
7421
7541
  ]).optional().describe("Extraction method metadata."),
7422
7542
  overall_confidence: z5.number().min(0).max(1).optional().describe("Optional overall confidence score in range 0..1."),
7423
- extraction_metadata: z5.record(z5.string(), z5.unknown()).optional(),
7424
- response_format: z5.enum(["text", "json"]).optional()
7543
+ extraction_metadata: z5.record(z5.string(), z5.unknown()).optional().describe(
7544
+ "Arbitrary key-value metadata about the extraction process."
7545
+ ),
7546
+ response_format: z5.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
7547
+ },
7548
+ {
7549
+ title: "Save Brand Profile",
7550
+ readOnlyHint: false,
7551
+ destructiveHint: false,
7552
+ idempotentHint: false,
7553
+ openWorldHint: false
7425
7554
  },
7426
7555
  async ({
7427
7556
  project_id,
@@ -7534,15 +7663,32 @@ Version: ${payload.version ?? "N/A"}`
7534
7663
  "facebook",
7535
7664
  "threads",
7536
7665
  "bluesky"
7537
- ]),
7666
+ ]).describe("Social platform to set voice overrides for."),
7538
7667
  project_id: z5.string().uuid().optional().describe("Project ID. Defaults to active project context."),
7539
7668
  samples: z5.string().max(3e3).optional().describe("3-5 real platform post examples for style anchoring."),
7540
- tone: z5.array(z5.string()).optional(),
7541
- style: z5.array(z5.string()).optional(),
7542
- avoid_patterns: z5.array(z5.string()).optional(),
7543
- hashtag_strategy: z5.string().max(300).optional(),
7544
- cta_style: z5.string().max(300).optional(),
7545
- response_format: z5.enum(["text", "json"]).optional()
7669
+ tone: z5.array(z5.string()).optional().describe(
7670
+ 'Tone descriptors for this platform (e.g. ["casual", "witty", "informative"]).'
7671
+ ),
7672
+ style: z5.array(z5.string()).optional().describe(
7673
+ 'Writing style tags (e.g. ["short-form", "emoji-heavy", "storytelling"]).'
7674
+ ),
7675
+ avoid_patterns: z5.array(z5.string()).optional().describe(
7676
+ 'Phrases or patterns the brand should never use on this platform (e.g. ["click here", "buy now"]).'
7677
+ ),
7678
+ hashtag_strategy: z5.string().max(300).optional().describe(
7679
+ 'Hashtag usage guidelines for this platform (e.g. "3-5 niche hashtags, no generic tags").'
7680
+ ),
7681
+ cta_style: z5.string().max(300).optional().describe(
7682
+ 'Preferred call-to-action style (e.g. "soft CTA with question" or "direct link in bio").'
7683
+ ),
7684
+ response_format: z5.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
7685
+ },
7686
+ {
7687
+ title: "Update Platform Voice",
7688
+ readOnlyHint: false,
7689
+ destructiveHint: false,
7690
+ idempotentHint: false,
7691
+ openWorldHint: false
7546
7692
  },
7547
7693
  async ({
7548
7694
  platform: platform3,
@@ -7791,6 +7937,13 @@ function registerScreenshotTools(server2) {
7791
7937
  "Extra milliseconds to wait after page load before capturing. Useful for animations. Defaults to 2000."
7792
7938
  )
7793
7939
  },
7940
+ {
7941
+ title: "Capture App Page",
7942
+ readOnlyHint: true,
7943
+ destructiveHint: false,
7944
+ idempotentHint: false,
7945
+ openWorldHint: false
7946
+ },
7794
7947
  async ({ page: pageName, viewport, theme, selector, wait_ms }) => {
7795
7948
  const startedAt = Date.now();
7796
7949
  let rateLimitKey = "anonymous";
@@ -7911,6 +8064,13 @@ function registerScreenshotTools(server2) {
7911
8064
  ),
7912
8065
  wait_ms: z6.number().min(0).max(3e4).optional().describe("Extra milliseconds to wait after page load before capturing. Defaults to 1000.")
7913
8066
  },
8067
+ {
8068
+ title: "Capture Screenshot",
8069
+ readOnlyHint: true,
8070
+ destructiveHint: false,
8071
+ idempotentHint: false,
8072
+ openWorldHint: true
8073
+ },
7914
8074
  async ({ url, viewport, selector, output_path, wait_ms }) => {
7915
8075
  const startedAt = Date.now();
7916
8076
  let rateLimitKey = "anonymous";
@@ -8173,6 +8333,13 @@ function registerRemotionTools(server2) {
8173
8333
  "list_compositions",
8174
8334
  "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.",
8175
8335
  {},
8336
+ {
8337
+ title: "List Compositions",
8338
+ readOnlyHint: true,
8339
+ destructiveHint: false,
8340
+ idempotentHint: true,
8341
+ openWorldHint: false
8342
+ },
8176
8343
  async () => {
8177
8344
  const lines = [`${COMPOSITIONS.length} Remotion compositions available:`, ""];
8178
8345
  for (const comp of COMPOSITIONS) {
@@ -8201,6 +8368,13 @@ function registerRemotionTools(server2) {
8201
8368
  "JSON string of input props to pass to the composition. Each composition accepts different props. Omit for defaults."
8202
8369
  )
8203
8370
  },
8371
+ {
8372
+ title: "Render Demo Video",
8373
+ readOnlyHint: false,
8374
+ destructiveHint: false,
8375
+ idempotentHint: false,
8376
+ openWorldHint: false
8377
+ },
8204
8378
  async ({ composition_id, output_format, props }) => {
8205
8379
  const startedAt = Date.now();
8206
8380
  const userId = await getDefaultUserId();
@@ -8382,6 +8556,13 @@ function registerInsightsTools(server2) {
8382
8556
  limit: z8.number().min(1).max(50).optional().describe("Maximum number of insights to return. Defaults to 10."),
8383
8557
  response_format: z8.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
8384
8558
  },
8559
+ {
8560
+ title: "Get Performance Insights",
8561
+ readOnlyHint: true,
8562
+ destructiveHint: false,
8563
+ idempotentHint: true,
8564
+ openWorldHint: false
8565
+ },
8385
8566
  async ({ insight_type, days, limit, response_format }) => {
8386
8567
  const format = response_format ?? "text";
8387
8568
  const supabase = getSupabaseClient();
@@ -8509,6 +8690,13 @@ function registerInsightsTools(server2) {
8509
8690
  days: z8.number().min(1).max(90).optional().describe("Number of days to analyze. Defaults to 30. Max 90."),
8510
8691
  response_format: z8.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
8511
8692
  },
8693
+ {
8694
+ title: "Get Best Posting Times",
8695
+ readOnlyHint: true,
8696
+ destructiveHint: false,
8697
+ idempotentHint: true,
8698
+ openWorldHint: false
8699
+ },
8512
8700
  async ({ platform: platform3, days, response_format }) => {
8513
8701
  const format = response_format ?? "text";
8514
8702
  const supabase = getSupabaseClient();
@@ -8658,6 +8846,13 @@ function registerYouTubeAnalyticsTools(server2) {
8658
8846
  video_id: z9.string().optional().describe('YouTube video ID. Required when action is "video".'),
8659
8847
  max_results: z9.number().min(1).max(50).optional().describe('Max videos to return for "topVideos" action. Defaults to 10.')
8660
8848
  },
8849
+ {
8850
+ title: "Fetch YouTube Analytics",
8851
+ readOnlyHint: true,
8852
+ destructiveHint: false,
8853
+ idempotentHint: true,
8854
+ openWorldHint: true
8855
+ },
8661
8856
  async ({ action, start_date, end_date, video_id, max_results }) => {
8662
8857
  if (action === "video" && !video_id) {
8663
8858
  return {
@@ -8778,6 +8973,13 @@ function registerCommentsTools(server2) {
8778
8973
  page_token: z10.string().optional().describe("Pagination cursor from previous list_comments response nextPageToken field. Omit for first page of results."),
8779
8974
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
8780
8975
  },
8976
+ {
8977
+ title: "List Comments",
8978
+ readOnlyHint: true,
8979
+ destructiveHint: false,
8980
+ idempotentHint: true,
8981
+ openWorldHint: true
8982
+ },
8781
8983
  async ({ video_id, max_results, page_token, response_format }) => {
8782
8984
  const format = response_format ?? "text";
8783
8985
  const { data, error } = await callEdgeFunction("youtube-comments", {
@@ -8848,6 +9050,13 @@ function registerCommentsTools(server2) {
8848
9050
  text: z10.string().min(1).describe("The reply text."),
8849
9051
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
8850
9052
  },
9053
+ {
9054
+ title: "Reply to Comment",
9055
+ readOnlyHint: false,
9056
+ destructiveHint: false,
9057
+ idempotentHint: false,
9058
+ openWorldHint: true
9059
+ },
8851
9060
  async ({ parent_id, text, response_format }) => {
8852
9061
  const format = response_format ?? "text";
8853
9062
  const startedAt = Date.now();
@@ -8929,6 +9138,13 @@ function registerCommentsTools(server2) {
8929
9138
  text: z10.string().min(1).describe("The comment text."),
8930
9139
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
8931
9140
  },
9141
+ {
9142
+ title: "Post Comment",
9143
+ readOnlyHint: false,
9144
+ destructiveHint: false,
9145
+ idempotentHint: false,
9146
+ openWorldHint: true
9147
+ },
8932
9148
  async ({ video_id, text, response_format }) => {
8933
9149
  const format = response_format ?? "text";
8934
9150
  const startedAt = Date.now();
@@ -9007,6 +9223,13 @@ function registerCommentsTools(server2) {
9007
9223
  moderation_status: z10.enum(["published", "rejected"]).describe('"published" to approve, "rejected" to hide.'),
9008
9224
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9009
9225
  },
9226
+ {
9227
+ title: "Moderate Comment",
9228
+ readOnlyHint: false,
9229
+ destructiveHint: false,
9230
+ idempotentHint: true,
9231
+ openWorldHint: true
9232
+ },
9010
9233
  async ({ comment_id, moderation_status, response_format }) => {
9011
9234
  const format = response_format ?? "text";
9012
9235
  const startedAt = Date.now();
@@ -9092,6 +9315,13 @@ function registerCommentsTools(server2) {
9092
9315
  comment_id: z10.string().describe("The comment ID to delete."),
9093
9316
  response_format: z10.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9094
9317
  },
9318
+ {
9319
+ title: "Delete Comment",
9320
+ readOnlyHint: false,
9321
+ destructiveHint: true,
9322
+ idempotentHint: true,
9323
+ openWorldHint: true
9324
+ },
9095
9325
  async ({ comment_id, response_format }) => {
9096
9326
  const format = response_format ?? "text";
9097
9327
  const startedAt = Date.now();
@@ -9241,12 +9471,19 @@ function asEnvelope7(data) {
9241
9471
  function registerIdeationContextTools(server2) {
9242
9472
  server2.tool(
9243
9473
  "get_ideation_context",
9244
- "Get synthesized ideation context from performance insights. Returns the same prompt-injection context used by ideation generation.",
9474
+ "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.",
9245
9475
  {
9246
9476
  project_id: z11.string().uuid().optional().describe("Project ID to scope insights."),
9247
9477
  days: z11.number().min(1).max(90).optional().describe("Lookback window for insights. Defaults to 30 days."),
9248
9478
  response_format: z11.enum(["text", "json"]).optional().describe("Optional output format. Defaults to text.")
9249
9479
  },
9480
+ {
9481
+ title: "Get Ideation Context",
9482
+ readOnlyHint: true,
9483
+ destructiveHint: false,
9484
+ idempotentHint: true,
9485
+ openWorldHint: false
9486
+ },
9250
9487
  async ({ project_id, days, response_format }) => {
9251
9488
  const supabase = getSupabaseClient();
9252
9489
  const userId = await getDefaultUserId();
@@ -9377,6 +9614,13 @@ function registerCreditsTools(server2) {
9377
9614
  {
9378
9615
  response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9379
9616
  },
9617
+ {
9618
+ title: "Get Credit Balance",
9619
+ readOnlyHint: true,
9620
+ destructiveHint: false,
9621
+ idempotentHint: true,
9622
+ openWorldHint: false
9623
+ },
9380
9624
  async ({ response_format }) => {
9381
9625
  const supabase = getSupabaseClient();
9382
9626
  const userId = await getDefaultUserId();
@@ -9430,6 +9674,13 @@ Monthly used: ${payload.monthlyUsed}` + (payload.monthlyLimit ? ` / ${payload.mo
9430
9674
  {
9431
9675
  response_format: z12.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9432
9676
  },
9677
+ {
9678
+ title: "Get Budget Status",
9679
+ readOnlyHint: true,
9680
+ destructiveHint: false,
9681
+ idempotentHint: true,
9682
+ openWorldHint: false
9683
+ },
9433
9684
  async ({ response_format }) => {
9434
9685
  const budget = getCurrentBudgetStatus();
9435
9686
  const payload = {
@@ -9484,11 +9735,18 @@ function asEnvelope9(data) {
9484
9735
  function registerLoopSummaryTools(server2) {
9485
9736
  server2.tool(
9486
9737
  "get_loop_summary",
9487
- "Get a one-call dashboard summary of the feedback loop state (brand profile, recent content, and current insights).",
9738
+ "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.",
9488
9739
  {
9489
9740
  project_id: z13.string().uuid().optional().describe("Project ID. Defaults to active project context."),
9490
9741
  response_format: z13.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9491
9742
  },
9743
+ {
9744
+ title: "Get Loop Summary",
9745
+ readOnlyHint: true,
9746
+ destructiveHint: false,
9747
+ idempotentHint: true,
9748
+ openWorldHint: false
9749
+ },
9492
9750
  async ({ project_id, response_format }) => {
9493
9751
  const supabase = getSupabaseClient();
9494
9752
  const userId = await getDefaultUserId();
@@ -9587,6 +9845,13 @@ function registerUsageTools(server2) {
9587
9845
  {
9588
9846
  response_format: z14.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9589
9847
  },
9848
+ {
9849
+ title: "Get MCP Usage",
9850
+ readOnlyHint: true,
9851
+ destructiveHint: false,
9852
+ idempotentHint: true,
9853
+ openWorldHint: false
9854
+ },
9590
9855
  async ({ response_format }) => {
9591
9856
  const format = response_format ?? "text";
9592
9857
  const supabase = getSupabaseClient();
@@ -9677,6 +9942,13 @@ function registerAutopilotTools(server2) {
9677
9942
  active_only: z15.boolean().optional().describe("If true, only return active configs. Defaults to false (show all)."),
9678
9943
  response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9679
9944
  },
9945
+ {
9946
+ title: "List Autopilot Configs",
9947
+ readOnlyHint: true,
9948
+ destructiveHint: false,
9949
+ idempotentHint: true,
9950
+ openWorldHint: false
9951
+ },
9680
9952
  async ({ active_only, response_format }) => {
9681
9953
  const format = response_format ?? "text";
9682
9954
  const supabase = getSupabaseClient();
@@ -9754,11 +10026,18 @@ ${"=".repeat(40)}
9754
10026
  {
9755
10027
  config_id: z15.string().uuid().describe("The autopilot config ID to update."),
9756
10028
  is_active: z15.boolean().optional().describe("Enable or disable this autopilot config."),
9757
- 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"]).'),
10029
+ 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"]).'),
9758
10030
  schedule_time: z15.string().optional().describe('Time to run in HH:MM format (24h, user timezone). E.g., "09:00".'),
9759
10031
  max_credits_per_run: z15.number().optional().describe("Maximum credits per execution."),
9760
10032
  max_credits_per_week: z15.number().optional().describe("Maximum credits per week.")
9761
10033
  },
10034
+ {
10035
+ title: "Update Autopilot Config",
10036
+ readOnlyHint: false,
10037
+ destructiveHint: false,
10038
+ idempotentHint: true,
10039
+ openWorldHint: false
10040
+ },
9762
10041
  async ({
9763
10042
  config_id,
9764
10043
  is_active,
@@ -9822,6 +10101,13 @@ Schedule: ${JSON.stringify(updated.schedule_config)}`
9822
10101
  {
9823
10102
  response_format: z15.enum(["text", "json"]).optional().describe("Optional response format. Defaults to text.")
9824
10103
  },
10104
+ {
10105
+ title: "Get Autopilot Status",
10106
+ readOnlyHint: true,
10107
+ destructiveHint: false,
10108
+ idempotentHint: true,
10109
+ openWorldHint: false
10110
+ },
9825
10111
  async ({ response_format }) => {
9826
10112
  const format = response_format ?? "text";
9827
10113
  const supabase = getSupabaseClient();
@@ -9943,7 +10229,14 @@ function registerExtractionTools(server2) {
9943
10229
  extract_type: z16.enum(["auto", "transcript", "article", "product"]).default("auto").describe("Type of extraction"),
9944
10230
  include_comments: z16.boolean().default(false).describe("Include top comments (YouTube only)"),
9945
10231
  max_results: z16.number().min(1).max(100).default(10).describe("Max comments to include"),
9946
- response_format: z16.enum(["text", "json"]).default("text")
10232
+ response_format: z16.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
10233
+ },
10234
+ {
10235
+ title: "Extract URL Content",
10236
+ readOnlyHint: true,
10237
+ destructiveHint: false,
10238
+ idempotentHint: true,
10239
+ openWorldHint: true
9947
10240
  },
9948
10241
  async ({
9949
10242
  url,
@@ -10155,9 +10448,16 @@ function registerQualityTools(server2) {
10155
10448
  ).min(1).describe("Target platforms"),
10156
10449
  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."),
10157
10450
  brand_keyword: z17.string().optional().describe("Brand keyword for alignment check"),
10158
- brand_avoid_patterns: z17.array(z17.string()).optional(),
10159
- custom_banned_terms: z17.array(z17.string()).optional(),
10160
- response_format: z17.enum(["text", "json"]).default("text")
10451
+ 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."),
10452
+ custom_banned_terms: z17.array(z17.string()).optional().describe("Additional banned words beyond the built-in safety list. Useful for industry-specific compliance terms."),
10453
+ response_format: z17.enum(["text", "json"]).default("text").describe("'text' for human-readable report, 'json' for structured scores suitable for pipeline automation.")
10454
+ },
10455
+ {
10456
+ title: "Quality Check",
10457
+ readOnlyHint: true,
10458
+ destructiveHint: false,
10459
+ idempotentHint: true,
10460
+ openWorldHint: false
10161
10461
  },
10162
10462
  async ({
10163
10463
  caption,
@@ -10229,15 +10529,22 @@ function registerQualityTools(server2) {
10229
10529
  plan: z17.object({
10230
10530
  posts: z17.array(
10231
10531
  z17.object({
10232
- id: z17.string(),
10233
- caption: z17.string(),
10234
- title: z17.string().optional(),
10235
- platform: z17.string()
10532
+ id: z17.string().describe("Unique post identifier."),
10533
+ caption: z17.string().describe("Post caption/body text to quality-check."),
10534
+ title: z17.string().optional().describe("Post title (important for YouTube)."),
10535
+ platform: z17.string().describe("Target platform (e.g. instagram, youtube).")
10236
10536
  })
10237
10537
  )
10238
- }).passthrough().describe("Content plan with posts array"),
10538
+ }).passthrough().describe("Content plan with posts array."),
10239
10539
  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."),
10240
- response_format: z17.enum(["text", "json"]).default("text")
10540
+ response_format: z17.enum(["text", "json"]).default("text").describe("Response format. Defaults to text.")
10541
+ },
10542
+ {
10543
+ title: "Quality Check Plan",
10544
+ readOnlyHint: true,
10545
+ destructiveHint: false,
10546
+ idempotentHint: true,
10547
+ openWorldHint: false
10241
10548
  },
10242
10549
  async ({ plan, threshold, response_format }) => {
10243
10550
  const startedAt = Date.now();
@@ -10440,7 +10747,14 @@ function registerPlanningTools(server2) {
10440
10747
  start_date: z18.string().optional().describe("ISO date, defaults to tomorrow"),
10441
10748
  brand_voice: z18.string().optional().describe("Override brand voice description"),
10442
10749
  project_id: z18.string().optional().describe("Project ID for brand/insights context"),
10443
- response_format: z18.enum(["text", "json"]).default("json")
10750
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
10751
+ },
10752
+ {
10753
+ title: "Plan Content Week",
10754
+ readOnlyHint: false,
10755
+ destructiveHint: false,
10756
+ idempotentHint: false,
10757
+ openWorldHint: true
10444
10758
  },
10445
10759
  async ({
10446
10760
  topic,
@@ -10758,12 +11072,19 @@ ${rawText.slice(0, 1e3)}`
10758
11072
  "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.",
10759
11073
  {
10760
11074
  plan: z18.object({
10761
- topic: z18.string(),
10762
- posts: z18.array(z18.record(z18.string(), z18.unknown()))
10763
- }).passthrough(),
10764
- project_id: z18.string().uuid().optional(),
10765
- status: z18.enum(["draft", "in_review", "approved", "scheduled", "completed"]).default("draft"),
10766
- response_format: z18.enum(["text", "json"]).default("json")
11075
+ topic: z18.string().describe("Content plan topic or theme."),
11076
+ posts: z18.array(z18.record(z18.string(), z18.unknown())).describe("Array of post objects to save.")
11077
+ }).passthrough().describe("Content plan object with topic and posts array."),
11078
+ project_id: z18.string().uuid().optional().describe("Project ID. Defaults to active project context."),
11079
+ status: z18.enum(["draft", "in_review", "approved", "scheduled", "completed"]).default("draft").describe("Initial plan status. Defaults to draft."),
11080
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
11081
+ },
11082
+ {
11083
+ title: "Save Content Plan",
11084
+ readOnlyHint: false,
11085
+ destructiveHint: false,
11086
+ idempotentHint: false,
11087
+ openWorldHint: false
10767
11088
  },
10768
11089
  async ({ plan, project_id, status, response_format }) => {
10769
11090
  const startedAt = Date.now();
@@ -10861,10 +11182,17 @@ ${rawText.slice(0, 1e3)}`
10861
11182
  );
10862
11183
  server2.tool(
10863
11184
  "get_content_plan",
10864
- "Retrieve a persisted content plan by ID.",
11185
+ "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.",
10865
11186
  {
10866
11187
  plan_id: z18.string().uuid().describe("Persisted content plan ID"),
10867
- response_format: z18.enum(["text", "json"]).default("json")
11188
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
11189
+ },
11190
+ {
11191
+ title: "Get Content Plan",
11192
+ readOnlyHint: true,
11193
+ destructiveHint: false,
11194
+ idempotentHint: true,
11195
+ openWorldHint: false
10868
11196
  },
10869
11197
  async ({ plan_id, response_format }) => {
10870
11198
  const supabase = getSupabaseClient();
@@ -10929,25 +11257,32 @@ ${rawText.slice(0, 1e3)}`
10929
11257
  );
10930
11258
  server2.tool(
10931
11259
  "update_content_plan",
10932
- "Update individual posts in a persisted content plan.",
11260
+ "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.",
10933
11261
  {
10934
- plan_id: z18.string().uuid(),
11262
+ plan_id: z18.string().uuid().describe("Content plan ID to update."),
10935
11263
  post_updates: z18.array(
10936
11264
  z18.object({
10937
- post_id: z18.string(),
10938
- caption: z18.string().optional(),
10939
- title: z18.string().optional(),
10940
- hashtags: z18.array(z18.string()).optional(),
10941
- hook: z18.string().optional(),
10942
- angle: z18.string().optional(),
10943
- visual_direction: z18.string().optional(),
10944
- media_url: z18.string().optional(),
10945
- schedule_at: z18.string().optional(),
10946
- platform: z18.string().optional(),
10947
- status: z18.enum(["approved", "rejected", "needs_edit"]).optional()
11265
+ post_id: z18.string().describe("ID of the post to update within this plan."),
11266
+ caption: z18.string().optional().describe("Revised caption/body text."),
11267
+ title: z18.string().optional().describe("Revised post title."),
11268
+ hashtags: z18.array(z18.string()).optional().describe("Revised hashtags array."),
11269
+ hook: z18.string().optional().describe("Revised attention-grabbing opening line."),
11270
+ angle: z18.string().optional().describe("Revised content angle or perspective."),
11271
+ visual_direction: z18.string().optional().describe("Revised visual/media direction notes."),
11272
+ media_url: z18.string().optional().describe("Revised media URL (public or R2 signed URL)."),
11273
+ schedule_at: z18.string().optional().describe("Revised ISO 8601 UTC publish datetime."),
11274
+ platform: z18.string().optional().describe("Revised target platform."),
11275
+ status: z18.enum(["approved", "rejected", "needs_edit"]).optional().describe("Review status for this post.")
10948
11276
  })
10949
- ).min(1),
10950
- response_format: z18.enum(["text", "json"]).default("json")
11277
+ ).min(1).describe("Array of post-level updates to apply."),
11278
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
11279
+ },
11280
+ {
11281
+ title: "Update Content Plan",
11282
+ readOnlyHint: false,
11283
+ destructiveHint: false,
11284
+ idempotentHint: true,
11285
+ openWorldHint: false
10951
11286
  },
10952
11287
  async ({ plan_id, post_updates, response_format }) => {
10953
11288
  const supabase = getSupabaseClient();
@@ -11048,10 +11383,17 @@ ${rawText.slice(0, 1e3)}`
11048
11383
  );
11049
11384
  server2.tool(
11050
11385
  "submit_content_plan_for_approval",
11051
- "Create pending approval items for each post in a plan and mark plan status as in_review.",
11386
+ "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.",
11387
+ {
11388
+ plan_id: z18.string().uuid().describe("Content plan ID to submit for review."),
11389
+ response_format: z18.enum(["text", "json"]).default("json").describe("Response format. Defaults to json.")
11390
+ },
11052
11391
  {
11053
- plan_id: z18.string().uuid(),
11054
- response_format: z18.enum(["text", "json"]).default("json")
11392
+ title: "Submit Plan for Approval",
11393
+ readOnlyHint: false,
11394
+ destructiveHint: false,
11395
+ idempotentHint: true,
11396
+ openWorldHint: false
11055
11397
  },
11056
11398
  async ({ plan_id, response_format }) => {
11057
11399
  const supabase = getSupabaseClient();
@@ -11176,21 +11518,28 @@ async function assertProjectAccess(supabase, userId, projectId) {
11176
11518
  function registerPlanApprovalTools(server2) {
11177
11519
  server2.tool(
11178
11520
  "create_plan_approvals",
11179
- "Create pending approval rows for each post in a content plan.",
11521
+ "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.",
11180
11522
  {
11181
11523
  plan_id: z19.string().uuid().describe("Content plan ID"),
11182
11524
  posts: z19.array(
11183
11525
  z19.object({
11184
- id: z19.string(),
11185
- platform: z19.string().optional(),
11186
- caption: z19.string().optional(),
11187
- title: z19.string().optional(),
11188
- media_url: z19.string().optional(),
11189
- schedule_at: z19.string().optional()
11526
+ id: z19.string().describe("Unique post identifier from the content plan."),
11527
+ platform: z19.string().optional().describe("Target platform (e.g. instagram, youtube)."),
11528
+ caption: z19.string().optional().describe("Post caption/body text."),
11529
+ title: z19.string().optional().describe("Post title, used by YouTube and LinkedIn articles."),
11530
+ media_url: z19.string().optional().describe("Public or R2 signed URL for the post media."),
11531
+ schedule_at: z19.string().optional().describe("ISO 8601 UTC datetime to publish (e.g. 2026-03-20T14:00:00Z).")
11190
11532
  }).passthrough()
11191
11533
  ).min(1).describe("Posts to create approval entries for."),
11192
11534
  project_id: z19.string().uuid().optional().describe("Project ID. Defaults to active project context."),
11193
- response_format: z19.enum(["text", "json"]).optional()
11535
+ response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
11536
+ },
11537
+ {
11538
+ title: "Create Plan Approvals",
11539
+ readOnlyHint: false,
11540
+ destructiveHint: false,
11541
+ idempotentHint: false,
11542
+ openWorldHint: false
11194
11543
  },
11195
11544
  async ({ plan_id, posts, project_id, response_format }) => {
11196
11545
  const supabase = getSupabaseClient();
@@ -11270,8 +11619,15 @@ function registerPlanApprovalTools(server2) {
11270
11619
  "List MCP-native approval items for a specific content plan.",
11271
11620
  {
11272
11621
  plan_id: z19.string().uuid().describe("Content plan ID"),
11273
- status: z19.enum(["pending", "approved", "rejected", "edited"]).optional(),
11274
- response_format: z19.enum(["text", "json"]).optional()
11622
+ status: z19.enum(["pending", "approved", "rejected", "edited"]).optional().describe("Filter approvals by status. Omit to return all statuses."),
11623
+ response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
11624
+ },
11625
+ {
11626
+ title: "List Plan Approvals",
11627
+ readOnlyHint: true,
11628
+ destructiveHint: false,
11629
+ idempotentHint: true,
11630
+ openWorldHint: false
11275
11631
  },
11276
11632
  async ({ plan_id, status, response_format }) => {
11277
11633
  const supabase = getSupabaseClient();
@@ -11338,10 +11694,19 @@ function registerPlanApprovalTools(server2) {
11338
11694
  "Approve, reject, or edit a pending plan approval item.",
11339
11695
  {
11340
11696
  approval_id: z19.string().uuid().describe("Approval item ID"),
11341
- decision: z19.enum(["approved", "rejected", "edited"]),
11342
- edited_post: z19.record(z19.string(), z19.unknown()).optional(),
11343
- reason: z19.string().max(1e3).optional(),
11344
- response_format: z19.enum(["text", "json"]).optional()
11697
+ decision: z19.enum(["approved", "rejected", "edited"]).describe("Approval decision for this post."),
11698
+ edited_post: z19.record(z19.string(), z19.unknown()).optional().describe(
11699
+ 'Revised post fields when decision is "edited" (e.g. {caption: "...", hashtags: [...]}).'
11700
+ ),
11701
+ reason: z19.string().max(1e3).optional().describe("Optional reason for the decision, visible to the plan author."),
11702
+ response_format: z19.enum(["text", "json"]).optional().describe("Response format. Defaults to text.")
11703
+ },
11704
+ {
11705
+ title: "Respond to Plan Approval",
11706
+ readOnlyHint: false,
11707
+ destructiveHint: false,
11708
+ idempotentHint: true,
11709
+ openWorldHint: false
11345
11710
  },
11346
11711
  async ({ approval_id, decision, edited_post, reason, response_format }) => {
11347
11712
  const supabase = getSupabaseClient();
@@ -11429,6 +11794,13 @@ function registerDiscoveryTools(server2) {
11429
11794
  'Detail level: "name" for just tool names, "summary" for names + descriptions, "full" for complete info including scope and module'
11430
11795
  )
11431
11796
  },
11797
+ {
11798
+ title: "Search Tools",
11799
+ readOnlyHint: true,
11800
+ destructiveHint: false,
11801
+ idempotentHint: true,
11802
+ openWorldHint: false
11803
+ },
11432
11804
  async ({ query, module, scope, detail }) => {
11433
11805
  let results = [...TOOL_CATALOG];
11434
11806
  if (query) {