@commandable/mcp 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/{Sdkz9rYy.js → BD6mASiY.js} +1 -1
  3. package/.output/public/_nuxt/{Ctwv5nxD.js → BUmYUDQu.js} +3 -3
  4. package/.output/public/_nuxt/CjAs3eBq.js +1 -0
  5. package/.output/public/_nuxt/{CBR-0oRi.js → D9wFDhac.js} +1 -1
  6. package/.output/public/_nuxt/{KqToXREt.js → DRfk9W3W.js} +1 -1
  7. package/.output/public/_nuxt/DSWYWRXT.js +59 -0
  8. package/.output/public/_nuxt/{DOIzs5t4.js → VvnbcAzZ.js} +1 -1
  9. package/.output/public/_nuxt/builds/latest.json +1 -1
  10. package/.output/public/_nuxt/builds/meta/9441a86b-16e9-4000-bffc-3b2e78e57710.json +1 -0
  11. package/.output/server/chunks/build/{_id_-Co8jGxsD.mjs → _id_-DA3Q8jun.mjs} +603 -24
  12. package/.output/server/chunks/build/_id_-DA3Q8jun.mjs.map +1 -0
  13. package/.output/server/chunks/build/client.precomputed.mjs +1 -1
  14. package/.output/server/chunks/build/error-404-D1k2kWid.mjs +3 -5
  15. package/.output/server/chunks/build/error-500-D2K2rAfl.mjs +3 -5
  16. package/.output/server/chunks/build/fetch-aDh21opM.mjs +1 -1
  17. package/.output/server/chunks/build/{index-BxsJPthj.mjs → index-F5lAFSQk.mjs} +7 -7
  18. package/.output/server/chunks/build/index-F5lAFSQk.mjs.map +1 -0
  19. package/.output/server/chunks/build/index-ycGPozML.mjs +3 -5
  20. package/.output/server/chunks/build/server.mjs +6 -8
  21. package/.output/server/chunks/nitro/nitro.mjs +2456 -625
  22. package/.output/server/chunks/nitro/nitro.mjs.map +1 -1
  23. package/.output/server/chunks/routes/api/_commandable/status.get.mjs +3 -5
  24. package/.output/server/chunks/routes/api/_commandable/status.get.mjs.map +1 -1
  25. package/.output/server/chunks/routes/api/catalog/_type/tools.get.mjs +3 -5
  26. package/.output/server/chunks/routes/api/catalog/_type/tools.get.mjs.map +1 -1
  27. package/.output/server/chunks/routes/api/catalog/_type/toolsets.get.mjs +3 -5
  28. package/.output/server/chunks/routes/api/catalog/_type/toolsets.get.mjs.map +1 -1
  29. package/.output/server/chunks/routes/api/catalog.get.mjs +3 -5
  30. package/.output/server/chunks/routes/api/catalog.get.mjs.map +1 -1
  31. package/.output/server/chunks/routes/api/index.get.mjs +3 -5
  32. package/.output/server/chunks/routes/api/index.get.mjs.map +1 -1
  33. package/.output/server/chunks/routes/api/index.post.mjs +5 -5
  34. package/.output/server/chunks/routes/api/index.post.mjs.map +1 -1
  35. package/.output/server/chunks/routes/api/integrations/_id/credentials-config.get.mjs +3 -5
  36. package/.output/server/chunks/routes/api/integrations/_id/credentials-config.get.mjs.map +1 -1
  37. package/.output/server/chunks/routes/api/integrations/_id/credentials-status.get.mjs +3 -5
  38. package/.output/server/chunks/routes/api/integrations/_id/credentials-status.get.mjs.map +1 -1
  39. package/.output/server/chunks/routes/api/integrations/_id/credentials.delete.mjs +3 -5
  40. package/.output/server/chunks/routes/api/integrations/_id/credentials.delete.mjs.map +1 -1
  41. package/.output/server/chunks/routes/api/integrations/_id/credentials.post.mjs +3 -5
  42. package/.output/server/chunks/routes/api/integrations/_id/credentials.post.mjs.map +1 -1
  43. package/.output/server/chunks/routes/api/integrations/_id/permissions.post.mjs +3 -5
  44. package/.output/server/chunks/routes/api/integrations/_id/permissions.post.mjs.map +1 -1
  45. package/.output/server/chunks/routes/api/integrations/_id/tools.delete.mjs +3 -5
  46. package/.output/server/chunks/routes/api/integrations/_id/tools.delete.mjs.map +1 -1
  47. package/.output/server/chunks/routes/api/integrations/_id/tools.get.mjs +3 -5
  48. package/.output/server/chunks/routes/api/integrations/_id/tools.get.mjs.map +1 -1
  49. package/.output/server/chunks/routes/api/integrations/_id/toolsets.get.mjs +3 -5
  50. package/.output/server/chunks/routes/api/integrations/_id/toolsets.get.mjs.map +1 -1
  51. package/.output/server/chunks/routes/api/integrations/_id/toolsets.post.mjs +3 -5
  52. package/.output/server/chunks/routes/api/integrations/_id/toolsets.post.mjs.map +1 -1
  53. package/.output/server/chunks/routes/api/integrations/_id/variant-options.post.mjs +72 -0
  54. package/.output/server/chunks/routes/api/integrations/_id/variant-options.post.mjs.map +1 -0
  55. package/.output/server/chunks/routes/api/integrations/_id_.delete.mjs +3 -5
  56. package/.output/server/chunks/routes/api/integrations/_id_.delete.mjs.map +1 -1
  57. package/.output/server/chunks/routes/health.get.mjs +3 -5
  58. package/.output/server/chunks/routes/health.get.mjs.map +1 -1
  59. package/.output/server/chunks/routes/mcp/create.mjs +4 -6
  60. package/.output/server/chunks/routes/mcp/create.mjs.map +1 -1
  61. package/.output/server/chunks/routes/mcp/static.mjs +4 -6
  62. package/.output/server/chunks/routes/mcp/static.mjs.map +1 -1
  63. package/.output/server/chunks/routes/mcp.mjs +4 -6
  64. package/.output/server/chunks/routes/mcp.mjs.map +1 -1
  65. package/.output/server/chunks/routes/renderer.mjs +1 -1
  66. package/.output/server/index.mjs +4 -6
  67. package/.output/server/index.mjs.map +1 -1
  68. package/.output/server/package.json +1 -1
  69. package/package.json +2 -2
  70. package/.output/public/_nuxt/CYsCQznM.js +0 -59
  71. package/.output/public/_nuxt/DKO0MviJ.js +0 -1
  72. package/.output/public/_nuxt/builds/meta/3380bacd-d6f5-40cf-8376-35445d2f246f.json +0 -1
  73. package/.output/server/chunks/build/_id_-Co8jGxsD.mjs.map +0 -1
  74. package/.output/server/chunks/build/index-BxsJPthj.mjs.map +0 -1
@@ -3,8 +3,6 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
4
  import { ListToolsRequestSchema, CallToolRequestSchema, isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
5
5
  import { distance } from 'fastest-levenshtein';
6
- import { URLSearchParams as URLSearchParams$1, URL as URL$1, fileURLToPath } from 'node:url';
7
- import vm from 'node:vm';
8
6
  import TurndownService from 'turndown';
9
7
  import { marked } from 'marked';
10
8
  import { promisify } from 'node:util';
@@ -12,19 +10,19 @@ import { execFile as execFile$2 } from 'node:child_process';
12
10
  import { mkdtemp, writeFile as writeFile$1, readFile as readFile$1, rm } from 'node:fs/promises';
13
11
  import { resolve as resolve$1, dirname as dirname$1, join, basename as basename$1 } from 'node:path';
14
12
  import { tmpdir, homedir } from 'node:os';
15
- import { promises, existsSync, mkdirSync, readFileSync, statSync, writeFileSync, chmodSync } from 'node:fs';
16
13
  import { and, eq } from 'drizzle-orm';
14
+ import { URLSearchParams as URLSearchParams$1, URL as URL$1, fileURLToPath } from 'node:url';
15
+ import vm from 'node:vm';
17
16
  import { Buffer as Buffer$1 } from 'node:buffer';
18
17
  import { JWT } from 'google-auth-library';
19
18
  import http, { Server as Server$2 } from 'node:http';
20
19
  import https, { Server as Server$1 } from 'node:https';
21
20
  import { EventEmitter } from 'node:events';
21
+ import { promises, existsSync, writeFileSync, mkdirSync, statSync, readFileSync, chmodSync } from 'node:fs';
22
22
  import Database from 'better-sqlite3';
23
23
  import { drizzle as drizzle$1 } from 'drizzle-orm/better-sqlite3';
24
24
  import { drizzle } from 'drizzle-orm/node-postgres';
25
25
  import { Pool } from 'pg';
26
- import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
27
- import { migrate as migrate$1 } from 'drizzle-orm/node-postgres/migrator';
28
26
  import { sqliteTable, integer, text, primaryKey } from 'drizzle-orm/sqlite-core';
29
27
  import { pgTable, timestamp, jsonb, text as text$1, primaryKey as primaryKey$1, integer as integer$1 } from 'drizzle-orm/pg-core';
30
28
  import { cwd as cwd$1 } from 'node:process';
@@ -4433,7 +4431,7 @@ function _expandFromEnv(value) {
4433
4431
  const _inlineRuntimeConfig = {
4434
4432
  "app": {
4435
4433
  "baseURL": "/",
4436
- "buildId": "3380bacd-d6f5-40cf-8376-35445d2f246f",
4434
+ "buildId": "9441a86b-16e9-4000-bffc-3b2e78e57710",
4437
4435
  "buildAssetsDir": "/_nuxt/",
4438
4436
  "cdnURL": ""
4439
4437
  },
@@ -5392,7 +5390,8 @@ const GENERATED_INTEGRATIONS = {
5392
5390
  "handlerCode": "async (input) => {\n const path = `/${input.baseId}/${input.tableId}`\n const params = new URLSearchParams()\n params.set('records[]', input.recordId)\n const res = await integration.fetch(`${path}?${params.toString()}`, { method: 'DELETE' })\n return await res.json()\n}",
5393
5391
  "scope": "write"
5394
5392
  }
5395
- ]
5393
+ ],
5394
+ "variantOwnerType": null
5396
5395
  },
5397
5396
  "confluence": {
5398
5397
  "manifest": {
@@ -5963,7 +5962,8 @@ const GENERATED_INTEGRATIONS = {
5963
5962
  ],
5964
5963
  "scope": "write"
5965
5964
  }
5966
- ]
5965
+ ],
5966
+ "variantOwnerType": null
5967
5967
  },
5968
5968
  "github": {
5969
5969
  "manifest": {
@@ -8279,7 +8279,8 @@ const GENERATED_INTEGRATIONS = {
8279
8279
  "scope": "write",
8280
8280
  "toolset": "releases"
8281
8281
  }
8282
- ]
8282
+ ],
8283
+ "variantOwnerType": null
8283
8284
  },
8284
8285
  "google-calendar": {
8285
8286
  "manifest": {
@@ -9020,7 +9021,8 @@ const GENERATED_INTEGRATIONS = {
9020
9021
  "scope": "admin",
9021
9022
  "toolset": "sharing"
9022
9023
  }
9023
- ]
9024
+ ],
9025
+ "variantOwnerType": null
9024
9026
  },
9025
9027
  "google-gmail": {
9026
9028
  "manifest": {
@@ -10225,7 +10227,8 @@ const GENERATED_INTEGRATIONS = {
10225
10227
  "scope": "admin",
10226
10228
  "toolset": "organize"
10227
10229
  }
10228
- ]
10230
+ ],
10231
+ "variantOwnerType": null
10229
10232
  },
10230
10233
  "google-workspace": {
10231
10234
  "manifest": {
@@ -12292,7 +12295,8 @@ const GENERATED_INTEGRATIONS = {
12292
12295
  "scope": "write",
12293
12296
  "toolset": "slides"
12294
12297
  }
12295
- ]
12298
+ ],
12299
+ "variantOwnerType": null
12296
12300
  },
12297
12301
  "hubspot": {
12298
12302
  "manifest": {
@@ -13889,7 +13893,8 @@ const GENERATED_INTEGRATIONS = {
13889
13893
  "handlerCode": "async (input) => {\n const props = { ...(input.properties || {}) }\n\n if (input.subject !== undefined) props.hs_task_subject = input.subject\n if (input.body !== undefined) props.hs_task_body = input.body\n if (input.status !== undefined) props.hs_task_status = input.status\n if (input.priority !== undefined) props.hs_task_priority = input.priority\n if (input.dueTimestamp !== undefined) props.hs_timestamp = String(input.dueTimestamp)\n if (input.hubspot_owner_id !== undefined) props.hubspot_owner_id = String(input.hubspot_owner_id)\n\n const res = await integration.fetch(`/crm/v3/objects/tasks/${encodeURIComponent(input.id)}`, {\n method: 'PATCH',\n body: { properties: props },\n })\n return await res.json()\n}",
13890
13894
  "scope": "write"
13891
13895
  }
13892
- ]
13896
+ ],
13897
+ "variantOwnerType": null
13893
13898
  },
13894
13899
  "jira": {
13895
13900
  "manifest": {
@@ -15010,7 +15015,8 @@ const GENERATED_INTEGRATIONS = {
15010
15015
  "scope": "write",
15011
15016
  "toolset": "boards"
15012
15017
  }
15013
- ]
15018
+ ],
15019
+ "variantOwnerType": null
15014
15020
  },
15015
15021
  "notion": {
15016
15022
  "manifest": {
@@ -15835,331 +15841,1741 @@ const GENERATED_INTEGRATIONS = {
15835
15841
  "scope": "write",
15836
15842
  "toolset": "databases"
15837
15843
  }
15838
- ]
15844
+ ],
15845
+ "variantOwnerType": null
15839
15846
  },
15840
- "trello": {
15847
+ "sharepoint": {
15841
15848
  "manifest": {
15842
- "name": "trello",
15849
+ "name": "sharepoint",
15843
15850
  "version": "0.1.0",
15844
- "baseUrl": "https://api.trello.com/1",
15851
+ "baseUrl": "https://graph.microsoft.com/v1.0",
15845
15852
  "tools": [
15846
15853
  {
15847
- "name": "get_member",
15848
- "description": "Fetch the current member profile.",
15849
- "inputSchema": "schemas/get_member.json",
15850
- "handler": "handlers/get_member.js",
15851
- "scope": "read"
15852
- },
15853
- {
15854
- "name": "get_member_boards",
15855
- "description": "List boards for the current member.",
15856
- "inputSchema": "schemas/empty.json",
15857
- "handler": "handlers/get_member_boards.js",
15858
- "scope": "read"
15859
- },
15860
- {
15861
- "name": "get_member_organizations",
15862
- "description": "List organizations (workspaces) for the current member.",
15863
- "inputSchema": "schemas/empty.json",
15864
- "handler": "handlers/get_member_organizations.js",
15865
- "scope": "read"
15866
- },
15867
- {
15868
- "name": "get_board",
15869
- "description": "Fetch a board by id.",
15870
- "inputSchema": "schemas/id_board.json",
15871
- "handler": "handlers/get_board.js",
15872
- "scope": "read"
15873
- },
15874
- {
15875
- "name": "get_board_lists",
15876
- "description": "List lists on a board.",
15877
- "inputSchema": "schemas/id_board.json",
15878
- "handler": "handlers/get_board_lists.js",
15879
- "scope": "read"
15880
- },
15881
- {
15882
- "name": "get_board_cards",
15883
- "description": "List cards on a board.",
15884
- "inputSchema": "schemas/id_board.json",
15885
- "handler": "handlers/get_board_cards.js",
15886
- "scope": "read"
15887
- },
15888
- {
15889
- "name": "get_board_members",
15890
- "description": "List members on a board.",
15891
- "inputSchema": "schemas/id_board.json",
15892
- "handler": "handlers/get_board_members.js",
15893
- "scope": "read"
15894
- },
15895
- {
15896
- "name": "get_board_labels",
15897
- "description": "List labels on a board.",
15898
- "inputSchema": "schemas/id_board.json",
15899
- "handler": "handlers/get_board_labels.js",
15900
- "scope": "read"
15901
- },
15902
- {
15903
- "name": "get_board_custom_fields",
15904
- "description": "List custom fields on a board.",
15905
- "inputSchema": "schemas/id_board.json",
15906
- "handler": "handlers/get_board_custom_fields.js",
15907
- "scope": "read"
15908
- },
15909
- {
15910
- "name": "get_board_memberships",
15911
- "description": "List memberships for a board.",
15912
- "inputSchema": "schemas/id_board.json",
15913
- "handler": "handlers/get_board_memberships.js",
15914
- "scope": "read"
15915
- },
15916
- {
15917
- "name": "get_list",
15918
- "description": "Fetch a list by id.",
15919
- "inputSchema": "schemas/id_list.json",
15920
- "handler": "handlers/get_list.js",
15921
- "scope": "read"
15922
- },
15923
- {
15924
- "name": "get_list_cards",
15925
- "description": "List cards in a list.",
15926
- "inputSchema": "schemas/id_list.json",
15927
- "handler": "handlers/get_list_cards.js",
15928
- "scope": "read"
15929
- },
15930
- {
15931
- "name": "get_card",
15932
- "description": "Fetch a card by id.",
15933
- "inputSchema": "schemas/id_card.json",
15934
- "handler": "handlers/get_card.js",
15935
- "scope": "read"
15936
- },
15937
- {
15938
- "name": "get_card_members",
15939
- "description": "List members assigned to a card.",
15940
- "inputSchema": "schemas/id_card.json",
15941
- "handler": "handlers/get_card_members.js",
15854
+ "name": "search_sites",
15855
+ "description": "Search SharePoint sites by keyword across the tenant. Returns compact site summaries with IDs and web URLs. Use this when you know the site name or topic but not the site ID. If you already know the hostname and path, use get_site_by_path instead.",
15856
+ "inputSchema": "schemas/search_sites.json",
15857
+ "handler": "handlers/search_sites.js",
15942
15858
  "scope": "read"
15943
15859
  },
15944
15860
  {
15945
- "name": "get_card_attachments",
15946
- "description": "List attachments on a card.",
15947
- "inputSchema": "schemas/id_card.json",
15948
- "handler": "handlers/get_card_attachments.js",
15861
+ "name": "get_site_by_path",
15862
+ "description": "Resolve a SharePoint site from its hostname and server-relative path, such as hostname='contoso.sharepoint.com' and relativePath='//Marketing'. Use this when you know the SharePoint URL structure and need the stable site ID for later calls.",
15863
+ "inputSchema": "schemas/get_site_by_path.json",
15864
+ "handler": "handlers/get_site_by_path.js",
15949
15865
  "scope": "read"
15950
15866
  },
15951
15867
  {
15952
- "name": "get_card_actions",
15953
- "description": "List actions (activity) on a card.",
15954
- "inputSchema": "schemas/id_card.json",
15955
- "handler": "handlers/get_card_actions.js",
15868
+ "name": "get_site",
15869
+ "description": "Get metadata for a SharePoint site by site ID. Returns the site name, description, web URL, and timestamps. Use search_sites or get_site_by_path first if you do not already know the site ID.",
15870
+ "inputSchema": "schemas/get_site.json",
15871
+ "handler": "handlers/get_site.js",
15956
15872
  "scope": "read"
15957
15873
  },
15958
15874
  {
15959
- "name": "get_card_checklists",
15960
- "description": "List checklists on a card.",
15961
- "inputSchema": "schemas/id_card.json",
15962
- "handler": "handlers/get_card_checklists.js",
15875
+ "name": "list_site_drives",
15876
+ "description": "List document libraries (drives) for a SharePoint site. Returns compact drive summaries including IDs, names, web URLs, and drive type. Use this after resolving a site to discover the right document library before browsing folders or reading files.",
15877
+ "inputSchema": "schemas/list_site_drives.json",
15878
+ "handler": "handlers/list_site_drives.js",
15963
15879
  "scope": "read"
15964
15880
  },
15965
15881
  {
15966
- "name": "get_card_custom_field_items",
15967
- "description": "Get custom field items on a card.",
15968
- "inputSchema": "schemas/id_card.json",
15969
- "handler": "handlers/get_card_custom_field_items.js",
15882
+ "name": "list_drive_children",
15883
+ "description": "List the files and folders inside a SharePoint document library folder. By default this lists the root of the drive. Provide itemId to browse a specific folder. Returns compact entries with file-or-folder flags, MIME type when available, and parent references. Use get_drive_item_meta when you need one specific item.",
15884
+ "inputSchema": "schemas/list_drive_children.json",
15885
+ "handler": "handlers/list_drive_children.js",
15970
15886
  "scope": "read"
15971
15887
  },
15972
15888
  {
15973
- "name": "get_organization",
15974
- "description": "Fetch an organization (workspace) by id.",
15975
- "inputSchema": "schemas/id_org.json",
15976
- "handler": "handlers/get_organization.js",
15889
+ "name": "get_drive_item_meta",
15890
+ "description": "Get metadata for a single SharePoint file or folder by drive ID and item ID. Returns a compact item summary including IDs, name, type flags, web URL, size, timestamps, and parent reference. Use read_file_content to read the actual file contents.",
15891
+ "inputSchema": "schemas/get_drive_item.json",
15892
+ "handler": "handlers/get_drive_item.js",
15977
15893
  "scope": "read"
15978
15894
  },
15979
15895
  {
15980
- "name": "get_organization_boards",
15981
- "description": "List boards in an organization (workspace).",
15982
- "inputSchema": "schemas/id_org.json",
15983
- "handler": "handlers/get_organization_boards.js",
15896
+ "name": "search_files",
15897
+ "description": "Search SharePoint and OneDrive content through Microsoft Graph search and return flattened file hits. Provide a query string; Graph KQL syntax is supported. Optional siteId and driveId filters narrow the flattened results after search. Use this for broad file discovery when folder-by-folder browsing is too narrow.",
15898
+ "inputSchema": "schemas/search_files.json",
15899
+ "handler": "handlers/search_files.js",
15984
15900
  "scope": "read"
15985
15901
  },
15986
15902
  {
15987
- "name": "search",
15988
- "description": "Search across boards, cards, and members.",
15989
- "inputSchema": "schemas/search.json",
15990
- "handler": "handlers/search.js",
15903
+ "name": "read_file_content",
15904
+ "description": "Read a SharePoint file into agent-friendly text using the shared file extraction pipeline. This is the standard way to consume document contents such as PDF, DOCX, XLSX, PPTX, and text-like files stored in SharePoint document libraries. Provide driveId and itemId. Folders are rejected; use list_drive_children to browse them.",
15905
+ "inputSchema": "schemas/read_file_content.json",
15906
+ "handler": "handlers/read_file_content.js",
15991
15907
  "scope": "read"
15992
15908
  },
15993
15909
  {
15994
- "name": "create_board",
15995
- "description": "Create a new board.",
15996
- "inputSchema": "schemas/create_board.json",
15997
- "handler": "handlers/create_board.js",
15998
- "scope": "write"
15999
- },
16000
- {
16001
- "name": "close_board",
16002
- "description": "Close a board (set closed=true).",
16003
- "inputSchema": "schemas/close_board.json",
16004
- "handler": "handlers/close_board.js",
16005
- "scope": "write"
16006
- },
16007
- {
16008
- "name": "delete_board",
16009
- "description": "Permanently delete a closed board.",
16010
- "inputSchema": "schemas/delete_board.json",
16011
- "handler": "handlers/delete_board.js",
16012
- "scope": "write"
16013
- },
16014
- {
16015
- "name": "create_card",
16016
- "description": "Create a new card in a list.",
16017
- "inputSchema": "schemas/create_card.json",
16018
- "handler": "handlers/create_card.js",
16019
- "scope": "write"
16020
- },
16021
- {
16022
- "name": "update_card",
16023
- "description": "Update a card's fields (name, desc, due, list, etc).",
16024
- "inputSchema": "schemas/update_card.json",
16025
- "handler": "handlers/update_card.js",
16026
- "scope": "write"
16027
- },
16028
- {
16029
- "name": "delete_card",
16030
- "description": "Delete a card.",
16031
- "inputSchema": "schemas/delete_card.json",
16032
- "handler": "handlers/delete_card.js",
16033
- "scope": "write"
16034
- },
16035
- {
16036
- "name": "move_card_to_list",
16037
- "description": "Move a card to another list.",
16038
- "inputSchema": "schemas/move_card_to_list.json",
16039
- "handler": "handlers/move_card_to_list.js",
16040
- "scope": "write"
16041
- },
16042
- {
16043
- "name": "add_member_to_card",
16044
- "description": "Add a member to a card.",
16045
- "inputSchema": "schemas/add_member_to_card.json",
16046
- "handler": "handlers/add_member_to_card.js",
16047
- "scope": "write"
16048
- },
16049
- {
16050
- "name": "remove_member_from_card",
16051
- "description": "Remove a member from a card.",
16052
- "inputSchema": "schemas/remove_member_from_card.json",
16053
- "handler": "handlers/remove_member_from_card.js",
16054
- "scope": "write"
16055
- },
16056
- {
16057
- "name": "add_checklist_to_card",
16058
- "description": "Create a checklist on a card.",
16059
- "inputSchema": "schemas/add_checklist_to_card.json",
16060
- "handler": "handlers/add_checklist_to_card.js",
16061
- "scope": "write"
16062
- },
16063
- {
16064
- "name": "create_list",
16065
- "description": "Create a new list on a board.",
16066
- "inputSchema": "schemas/create_list.json",
16067
- "handler": "handlers/create_list.js",
15910
+ "name": "create_folder",
15911
+ "description": "Create a new folder in a SharePoint document library. By default the folder is created in the drive root. Provide parentItemId to create it inside an existing folder. Returns the created folder metadata including its item ID for later browsing or moves.",
15912
+ "inputSchema": "schemas/create_folder.json",
15913
+ "handler": "handlers/create_folder.js",
16068
15914
  "scope": "write"
16069
15915
  },
16070
15916
  {
16071
- "name": "update_list",
16072
- "description": "Update a list (name, pos, closed).",
16073
- "inputSchema": "schemas/update_list.json",
16074
- "handler": "handlers/update_list.js",
15917
+ "name": "move_drive_item",
15918
+ "description": "Move a SharePoint file or folder to a different parent folder in the same drive. Provide destinationParentId and optionally a newName to rename during the move. Use get_drive_item_meta or list_drive_children first to discover the current item and destination IDs.",
15919
+ "inputSchema": "schemas/move_drive_item.json",
15920
+ "handler": "handlers/move_drive_item.js",
16075
15921
  "scope": "write"
16076
15922
  },
16077
15923
  {
16078
- "name": "archive_list",
16079
- "description": "Archive a list (set closed=true).",
16080
- "inputSchema": "schemas/archive_list.json",
16081
- "handler": "handlers/archive_list.js",
15924
+ "name": "delete_drive_item",
15925
+ "description": "Delete a SharePoint file or folder by drive ID and item ID. This is a destructive operation. Use get_drive_item_meta or list_drive_children first to confirm you have the correct item before deleting it.",
15926
+ "inputSchema": "schemas/delete_drive_item.json",
15927
+ "handler": "handlers/delete_drive_item.js",
16082
15928
  "scope": "write"
16083
15929
  }
16084
15930
  ]
16085
15931
  },
16086
- "prompt": null,
15932
+ "prompt": "Use this integration for SharePoint document libraries and files.\n\nRecommended workflow:\n\n1. If you know the SharePoint hostname and path, start with `get_site_by_path`.\n2. Otherwise use `search_sites` to discover the correct site.\n3. Use `list_site_drives` to find the relevant document library for that site.\n4. Use `list_drive_children` for deterministic folder browsing or `search_files` for broader file discovery.\n5. Use `get_drive_item_meta` when you need compact metadata for a specific file or folder.\n6. Use `read_file_content` to consume the actual contents of a file in agent-friendly text.\n\nNotes:\n\n- `search_files` uses Microsoft Graph search. The `query` field accepts normal keywords and Graph KQL syntax.\n- `siteId` and `driveId` filters on `search_files` are applied to the flattened search results after Graph returns them.\n- `read_file_content` is for files only. Folders do not have readable file content.\n- This v1 integration is intentionally focused on SharePoint sites, document libraries, folders, and files. It does not include classic SharePoint list/list-item tools or file upload.\n",
16087
15933
  "variants": {
16088
15934
  "variants": {
16089
- "api_key_token": {
16090
- "label": "API Key + Token",
15935
+ "app_credentials": {
15936
+ "label": "Microsoft Graph App Credentials",
16091
15937
  "schema": {
16092
15938
  "type": "object",
16093
15939
  "properties": {
16094
- "apiKey": {
15940
+ "tenantId": {
16095
15941
  "type": "string",
16096
- "title": "API Key",
16097
- "description": "Your Trello API key from https://trello.com/power-ups/admin"
15942
+ "title": "Tenant ID",
15943
+ "description": "Microsoft Entra tenant ID (GUID) that owns the SharePoint tenant."
16098
15944
  },
16099
- "apiToken": {
15945
+ "clientId": {
16100
15946
  "type": "string",
16101
- "title": "API Token",
16102
- "description": `Your Trello API token ("token" param). Generate one via Trello's authorize flow.`
15947
+ "title": "Client ID",
15948
+ "description": "Application (client) ID of the Microsoft Entra app registration."
15949
+ },
15950
+ "clientSecret": {
15951
+ "type": "string",
15952
+ "title": "Client Secret",
15953
+ "description": "Client secret value for the Microsoft Entra app registration.",
15954
+ "format": "password"
16103
15955
  }
16104
15956
  },
16105
15957
  "required": [
16106
- "apiKey",
16107
- "apiToken"
15958
+ "tenantId",
15959
+ "clientId",
15960
+ "clientSecret"
16108
15961
  ],
16109
15962
  "additionalProperties": false
16110
15963
  },
15964
+ "preprocess": {
15965
+ "type": "handler",
15966
+ "handlerCode": "async (creds, utils) => {\n const tenantId = String(creds?.tenantId || '').trim()\n const clientId = String(creds?.clientId || '').trim()\n const clientSecret = String(creds?.clientSecret || '').trim()\n\n if (!tenantId)\n throw new Error('Missing tenantId')\n if (!clientId)\n throw new Error('Missing clientId')\n if (!clientSecret)\n throw new Error('Missing clientSecret')\n\n const response = await utils.tokenFetch(\n `https://login.microsoftonline.com/${encodeURIComponent(tenantId)}/oauth2/v2.0/token`,\n {\n method: 'POST',\n body: new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: clientId,\n client_secret: clientSecret,\n scope: 'https://graph.microsoft.com/.default',\n }),\n },\n )\n\n const data = await response.json()\n if (!response.ok) {\n const message = typeof data?.error_description === 'string'\n ? data.error_description\n : (typeof data?.error === 'string' ? data.error : `Token request failed with status ${response.status}`)\n throw new Error(message)\n }\n\n const token = typeof data?.access_token === 'string' ? data.access_token : ''\n if (!token)\n throw new Error('Microsoft token response did not include access_token')\n\n return {\n token,\n expiresIn: data?.expires_in,\n }\n}",
15967
+ "allowedOrigins": [
15968
+ "https://login.microsoftonline.com"
15969
+ ]
15970
+ },
16111
15971
  "injection": {
16112
- "query": {
16113
- "key": "{{apiKey}}",
16114
- "token": "{{apiToken}}"
15972
+ "headers": {
15973
+ "Authorization": "Bearer {{token}}"
16115
15974
  }
16116
15975
  },
16117
15976
  "healthCheck": {
16118
- "path": "/members/me"
15977
+ "notViable": true
16119
15978
  }
16120
15979
  }
16121
15980
  },
16122
- "default": "api_key_token"
15981
+ "default": "app_credentials"
16123
15982
  },
16124
- "hint": "1. Go to `https://trello.com/power-ups/admin`\n2. Create a new app\n3. Navigate to **API Key** and copy your API key\n4. Click **Generate a Token** and copy the token value",
15983
+ "hint": "1. Create or use a Microsoft Entra app registration for Microsoft Graph at https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade\n2. Create a client secret for that app and copy the **tenant ID**, **client ID**, and **client secret value**.\n3. In Microsoft Graph **Application permissions**, grant at least `Sites.Read.All` and `Files.Read.All`.\n4. If you intend to use write actions such as folder creation, moves, and deletes, also grant `Sites.ReadWrite.All` and `Files.ReadWrite.All`.\n5. Grant admin consent for those application permissions in the tenant.\n6. Paste the tenant ID, client ID, and client secret into this integration. The integration exchanges them for short-lived Microsoft Graph access tokens automatically.",
16125
15984
  "hintsByVariant": {},
16126
15985
  "tools": [
16127
15986
  {
16128
- "name": "get_member",
16129
- "description": "Fetch the current member profile.",
15987
+ "name": "search_sites",
15988
+ "description": "Search SharePoint sites by keyword across the tenant. Returns compact site summaries with IDs and web URLs. Use this when you know the site name or topic but not the site ID. If you already know the hostname and path, use get_site_by_path instead.",
16130
15989
  "inputSchema": {
15990
+ "$schema": "http://json-schema.org/draft-07/schema#",
16131
15991
  "type": "object",
16132
- "properties": {},
15992
+ "required": [
15993
+ "query"
15994
+ ],
15995
+ "properties": {
15996
+ "query": {
15997
+ "type": "string",
15998
+ "description": "Keyword search for SharePoint sites, such as a team name or department."
15999
+ }
16000
+ },
16001
+ "additionalProperties": false
16002
+ },
16003
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set('search', input.query)\n const res = await integration.fetch(`/sites?${params.toString()}`)\n const data = await res.json()\n const sites = Array.isArray(data?.value)\n ? data.value.map(site => ({\n id: site.id,\n name: site.displayName || site.name || null,\n displayName: site.displayName || site.name || null,\n description: site.description || '',\n webUrl: site.webUrl || null,\n createdDateTime: site.createdDateTime || null,\n lastModifiedDateTime: site.lastModifiedDateTime || null,\n }))\n : []\n return { query: input.query, sites }\n}",
16004
+ "scope": "read"
16005
+ },
16006
+ {
16007
+ "name": "get_site_by_path",
16008
+ "description": "Resolve a SharePoint site from its hostname and server-relative path, such as hostname='contoso.sharepoint.com' and relativePath='//Marketing'. Use this when you know the SharePoint URL structure and need the stable site ID for later calls.",
16009
+ "inputSchema": {
16010
+ "$schema": "http://json-schema.org/draft-07/schema#",
16011
+ "type": "object",
16012
+ "required": [
16013
+ "hostname",
16014
+ "relativePath"
16015
+ ],
16016
+ "properties": {
16017
+ "hostname": {
16018
+ "type": "string",
16019
+ "description": "SharePoint hostname, such as contoso.sharepoint.com."
16020
+ },
16021
+ "relativePath": {
16022
+ "type": "string",
16023
+ "description": "Server-relative site path, such as /sites/Marketing."
16024
+ }
16025
+ },
16026
+ "additionalProperties": false
16027
+ },
16028
+ "handlerCode": "async (input) => {\n const hostname = String(input.hostname || '').trim()\n const rawPath = String(input.relativePath || '').trim()\n const normalizedPath = `/${rawPath.replace(/^\\/+/, '')}`\n const encodedPath = normalizedPath\n .split('/')\n .map((segment, index) => index === 0 ? '' : encodeURIComponent(segment))\n .join('/')\n const res = await integration.fetch(`/sites/${hostname}:${encodedPath}`)\n const site = await res.json()\n return {\n id: site.id,\n name: site.displayName || site.name || null,\n displayName: site.displayName || site.name || null,\n description: site.description || '',\n webUrl: site.webUrl || null,\n createdDateTime: site.createdDateTime || null,\n lastModifiedDateTime: site.lastModifiedDateTime || null,\n }\n}",
16029
+ "scope": "read"
16030
+ },
16031
+ {
16032
+ "name": "get_site",
16033
+ "description": "Get metadata for a SharePoint site by site ID. Returns the site name, description, web URL, and timestamps. Use search_sites or get_site_by_path first if you do not already know the site ID.",
16034
+ "inputSchema": {
16035
+ "$schema": "http://json-schema.org/draft-07/schema#",
16036
+ "type": "object",
16037
+ "required": [
16038
+ "siteId"
16039
+ ],
16040
+ "properties": {
16041
+ "siteId": {
16042
+ "type": "string",
16043
+ "description": "Microsoft Graph SharePoint site ID."
16044
+ }
16045
+ },
16046
+ "additionalProperties": false
16047
+ },
16048
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/sites/${encodeURIComponent(input.siteId)}`)\n const site = await res.json()\n return {\n id: site.id,\n name: site.displayName || site.name || null,\n displayName: site.displayName || site.name || null,\n description: site.description || '',\n webUrl: site.webUrl || null,\n createdDateTime: site.createdDateTime || null,\n lastModifiedDateTime: site.lastModifiedDateTime || null,\n }\n}",
16049
+ "scope": "read"
16050
+ },
16051
+ {
16052
+ "name": "list_site_drives",
16053
+ "description": "List document libraries (drives) for a SharePoint site. Returns compact drive summaries including IDs, names, web URLs, and drive type. Use this after resolving a site to discover the right document library before browsing folders or reading files.",
16054
+ "inputSchema": {
16055
+ "$schema": "http://json-schema.org/draft-07/schema#",
16056
+ "type": "object",
16057
+ "required": [
16058
+ "siteId"
16059
+ ],
16060
+ "properties": {
16061
+ "siteId": {
16062
+ "type": "string",
16063
+ "description": "Microsoft Graph SharePoint site ID."
16064
+ },
16065
+ "top": {
16066
+ "type": "integer",
16067
+ "minimum": 1,
16068
+ "maximum": 200,
16069
+ "description": "Maximum number of drives to return."
16070
+ },
16071
+ "includeSystem": {
16072
+ "type": "boolean",
16073
+ "description": "Set true to include hidden/system drives."
16074
+ }
16075
+ },
16076
+ "additionalProperties": false
16077
+ },
16078
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set('$select', input.includeSystem\n ? 'id,name,webUrl,driveType,createdDateTime,lastModifiedDateTime,system'\n : 'id,name,webUrl,driveType,createdDateTime,lastModifiedDateTime')\n if (input.top)\n params.set('$top', String(input.top))\n\n const res = await integration.fetch(`/sites/${encodeURIComponent(input.siteId)}/drives?${params.toString()}`)\n const data = await res.json()\n const drives = Array.isArray(data?.value)\n ? data.value.map(drive => ({\n id: drive.id,\n name: drive.name || null,\n webUrl: drive.webUrl || null,\n driveType: drive.driveType || null,\n createdDateTime: drive.createdDateTime || null,\n lastModifiedDateTime: drive.lastModifiedDateTime || null,\n isSystem: Boolean(drive.system),\n }))\n : []\n\n return {\n siteId: input.siteId,\n drives,\n nextLink: data?.['@odata.nextLink'] || null,\n }\n}",
16079
+ "scope": "read"
16080
+ },
16081
+ {
16082
+ "name": "list_drive_children",
16083
+ "description": "List the files and folders inside a SharePoint document library folder. By default this lists the root of the drive. Provide itemId to browse a specific folder. Returns compact entries with file-or-folder flags, MIME type when available, and parent references. Use get_drive_item_meta when you need one specific item.",
16084
+ "inputSchema": {
16085
+ "$schema": "http://json-schema.org/draft-07/schema#",
16086
+ "type": "object",
16087
+ "required": [
16088
+ "driveId"
16089
+ ],
16090
+ "properties": {
16091
+ "driveId": {
16092
+ "type": "string",
16093
+ "description": "Document library drive ID."
16094
+ },
16095
+ "itemId": {
16096
+ "type": "string",
16097
+ "description": "Optional folder item ID. Omit to list the drive root."
16098
+ },
16099
+ "top": {
16100
+ "type": "integer",
16101
+ "minimum": 1,
16102
+ "maximum": 200,
16103
+ "description": "Maximum number of children to return."
16104
+ },
16105
+ "orderBy": {
16106
+ "type": "string",
16107
+ "description": "Optional Microsoft Graph orderBy expression, such as name or lastModifiedDateTime desc."
16108
+ }
16109
+ },
16110
+ "additionalProperties": false
16111
+ },
16112
+ "handlerCode": "async (input) => {\n const flattenItem = item => ({\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n eTag: item.eTag || null,\n cTag: item.cTag || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n })\n\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,eTag,cTag,parentReference,file,folder,package',\n )\n if (input.top)\n params.set('$top', String(input.top))\n if (input.orderBy)\n params.set('$orderby', input.orderBy)\n\n const basePath = input.itemId\n ? `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/children`\n : `/drives/${encodeURIComponent(input.driveId)}/root/children`\n\n const res = await integration.fetch(`${basePath}?${params.toString()}`)\n const data = await res.json()\n\n return {\n driveId: input.driveId,\n itemId: input.itemId || null,\n children: Array.isArray(data?.value) ? data.value.map(flattenItem) : [],\n nextLink: data?.['@odata.nextLink'] || null,\n }\n}",
16113
+ "scope": "read"
16114
+ },
16115
+ {
16116
+ "name": "get_drive_item_meta",
16117
+ "description": "Get metadata for a single SharePoint file or folder by drive ID and item ID. Returns a compact item summary including IDs, name, type flags, web URL, size, timestamps, and parent reference. Use read_file_content to read the actual file contents.",
16118
+ "inputSchema": {
16119
+ "$schema": "http://json-schema.org/draft-07/schema#",
16120
+ "type": "object",
16121
+ "required": [
16122
+ "driveId",
16123
+ "itemId"
16124
+ ],
16125
+ "properties": {
16126
+ "driveId": {
16127
+ "type": "string",
16128
+ "description": "Document library drive ID."
16129
+ },
16130
+ "itemId": {
16131
+ "type": "string",
16132
+ "description": "Drive item ID for the file or folder."
16133
+ }
16134
+ },
16135
+ "additionalProperties": false
16136
+ },
16137
+ "handlerCode": "async (input) => {\n const flattenItem = item => ({\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n eTag: item.eTag || null,\n cTag: item.cTag || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n })\n\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,eTag,cTag,parentReference,file,folder,package',\n )\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}?${params.toString()}`)\n return flattenItem(await res.json())\n}",
16138
+ "scope": "read"
16139
+ },
16140
+ {
16141
+ "name": "search_files",
16142
+ "description": "Search SharePoint and OneDrive content through Microsoft Graph search and return flattened file hits. Provide a query string; Graph KQL syntax is supported. Optional siteId and driveId filters narrow the flattened results after search. Use this for broad file discovery when folder-by-folder browsing is too narrow.",
16143
+ "inputSchema": {
16144
+ "$schema": "http://json-schema.org/draft-07/schema#",
16145
+ "type": "object",
16146
+ "required": [
16147
+ "query"
16148
+ ],
16149
+ "properties": {
16150
+ "query": {
16151
+ "type": "string",
16152
+ "description": "Search query string. Microsoft Graph KQL syntax is supported."
16153
+ },
16154
+ "siteId": {
16155
+ "type": "string",
16156
+ "description": "Optional site ID to keep only hits from a specific SharePoint site."
16157
+ },
16158
+ "driveId": {
16159
+ "type": "string",
16160
+ "description": "Optional drive ID to keep only hits from a specific document library."
16161
+ },
16162
+ "from": {
16163
+ "type": "integer",
16164
+ "minimum": 0,
16165
+ "description": "Offset into the Graph search results."
16166
+ },
16167
+ "size": {
16168
+ "type": "integer",
16169
+ "minimum": 1,
16170
+ "maximum": 50,
16171
+ "description": "Maximum number of hits to request from Graph."
16172
+ }
16173
+ },
16174
+ "additionalProperties": false
16175
+ },
16176
+ "handlerCode": "async (input) => {\n const extractFallbackRegion = (error) => {\n const texts = [error?.data?.body, error?.message].filter(s => typeof s === 'string')\n for (const text of texts) {\n const match = text.match(/Only valid regions are ([A-Z,\\s]+)/i)\n const region = match?.[1]?.split(',').map(r => r.trim().toUpperCase()).filter(Boolean)[0]\n if (region)\n return region\n }\n return null\n }\n\n const runSearch = async (region) => {\n const res = await integration.fetch('/search/query', {\n method: 'POST',\n body: {\n requests: [{\n entityTypes: ['driveItem'],\n query: { queryString: input.query },\n from: typeof input.from === 'number' ? input.from : 0,\n size: typeof input.size === 'number' ? input.size : 25,\n region,\n }],\n },\n })\n return res.json()\n }\n\n const flattenHit = (hit) => {\n const resource = hit?.resource || {}\n const parentReference = resource.parentReference || {}\n return {\n id: resource.id || hit?.hitId || null,\n name: resource.name || null,\n webUrl: resource.webUrl || null,\n summary: hit?.summary || '',\n rank: hit?.rank ?? null,\n createdDateTime: resource.createdDateTime || null,\n lastModifiedDateTime: resource.lastModifiedDateTime || null,\n mimeType: resource.file?.mimeType || null,\n size: resource.size ?? null,\n isFolder: Boolean(resource.folder || resource.package),\n isFile: Boolean(resource.file),\n driveId: parentReference.driveId || null,\n siteId: parentReference.siteId || null,\n parentReference,\n }\n }\n\n let data\n try {\n data = await runSearch('NAM')\n }\n catch (error) {\n const fallback = extractFallbackRegion(error)\n if (!fallback)\n throw error\n data = await runSearch(fallback)\n }\n\n const container = data?.value?.[0]?.hitsContainers?.[0]\n const allHits = Array.isArray(container?.hits) ? container.hits.map(flattenHit) : []\n const hits = allHits.filter((hit) => {\n if (input.siteId && hit.siteId !== input.siteId)\n return false\n if (input.driveId && hit.driveId !== input.driveId)\n return false\n return true\n })\n\n return {\n query: input.query,\n hits,\n total: container?.total ?? hits.length,\n moreResultsAvailable: Boolean(container?.moreResultsAvailable),\n }\n}",
16177
+ "scope": "read"
16178
+ },
16179
+ {
16180
+ "name": "read_file_content",
16181
+ "description": "Read a SharePoint file into agent-friendly text using the shared file extraction pipeline. This is the standard way to consume document contents such as PDF, DOCX, XLSX, PPTX, and text-like files stored in SharePoint document libraries. Provide driveId and itemId. Folders are rejected; use list_drive_children to browse them.",
16182
+ "inputSchema": {
16183
+ "$schema": "http://json-schema.org/draft-07/schema#",
16184
+ "type": "object",
16185
+ "required": [
16186
+ "driveId",
16187
+ "itemId"
16188
+ ],
16189
+ "properties": {
16190
+ "driveId": {
16191
+ "type": "string",
16192
+ "description": "Document library drive ID."
16193
+ },
16194
+ "itemId": {
16195
+ "type": "string",
16196
+ "description": "Drive item ID for the file."
16197
+ },
16198
+ "mimeType": {
16199
+ "type": "string",
16200
+ "description": "Optional MIME type from get_drive_item_meta or search_files."
16201
+ }
16202
+ },
16203
+ "additionalProperties": false
16204
+ },
16205
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set(\n '$select',\n 'id,name,webUrl,size,createdDateTime,lastModifiedDateTime,parentReference,file,folder,package',\n )\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}?${params.toString()}`)\n const item = await res.json()\n const mimeType = input.mimeType || item?.file?.mimeType || null\n\n if (item?.folder || item?.package) {\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source: `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}/content`,\n })\n\n return {\n driveId: input.driveId,\n itemId: input.itemId,\n name: item?.name || null,\n webUrl: item?.webUrl || null,\n mimeType,\n ...extracted,\n }\n}",
16206
+ "scope": "read"
16207
+ },
16208
+ {
16209
+ "name": "create_folder",
16210
+ "description": "Create a new folder in a SharePoint document library. By default the folder is created in the drive root. Provide parentItemId to create it inside an existing folder. Returns the created folder metadata including its item ID for later browsing or moves.",
16211
+ "inputSchema": {
16212
+ "$schema": "http://json-schema.org/draft-07/schema#",
16213
+ "type": "object",
16214
+ "required": [
16215
+ "driveId",
16216
+ "name"
16217
+ ],
16218
+ "properties": {
16219
+ "driveId": {
16220
+ "type": "string",
16221
+ "description": "Document library drive ID."
16222
+ },
16223
+ "name": {
16224
+ "type": "string",
16225
+ "description": "Folder name to create."
16226
+ },
16227
+ "parentItemId": {
16228
+ "type": "string",
16229
+ "description": "Optional parent folder item ID. Omit to create in the drive root."
16230
+ },
16231
+ "conflictBehavior": {
16232
+ "type": "string",
16233
+ "enum": [
16234
+ "rename",
16235
+ "replace",
16236
+ "fail"
16237
+ ],
16238
+ "description": "How Graph should handle name conflicts. Defaults to rename."
16239
+ }
16240
+ },
16241
+ "additionalProperties": false
16242
+ },
16243
+ "handlerCode": "async (input) => {\n const path = input.parentItemId\n ? `/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.parentItemId)}/children`\n : `/drives/${encodeURIComponent(input.driveId)}/root/children`\n\n const res = await integration.fetch(path, {\n method: 'POST',\n body: {\n name: input.name,\n folder: {},\n '@microsoft.graph.conflictBehavior': input.conflictBehavior || 'rename',\n },\n })\n const item = await res.json()\n return {\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n }\n}",
16244
+ "scope": "write"
16245
+ },
16246
+ {
16247
+ "name": "move_drive_item",
16248
+ "description": "Move a SharePoint file or folder to a different parent folder in the same drive. Provide destinationParentId and optionally a newName to rename during the move. Use get_drive_item_meta or list_drive_children first to discover the current item and destination IDs.",
16249
+ "inputSchema": {
16250
+ "$schema": "http://json-schema.org/draft-07/schema#",
16251
+ "type": "object",
16252
+ "required": [
16253
+ "driveId",
16254
+ "itemId",
16255
+ "destinationParentId"
16256
+ ],
16257
+ "properties": {
16258
+ "driveId": {
16259
+ "type": "string",
16260
+ "description": "Document library drive ID."
16261
+ },
16262
+ "itemId": {
16263
+ "type": "string",
16264
+ "description": "Drive item ID for the file or folder to move."
16265
+ },
16266
+ "destinationParentId": {
16267
+ "type": "string",
16268
+ "description": "Destination folder item ID."
16269
+ },
16270
+ "newName": {
16271
+ "type": "string",
16272
+ "description": "Optional new item name to apply during the move."
16273
+ }
16274
+ },
16275
+ "additionalProperties": false
16276
+ },
16277
+ "handlerCode": "async (input) => {\n const body = {\n parentReference: {\n id: input.destinationParentId,\n },\n }\n if (input.newName)\n body.name = input.newName\n\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}`, {\n method: 'PATCH',\n body,\n })\n const item = await res.json()\n return {\n id: item.id,\n name: item.name || null,\n webUrl: item.webUrl || null,\n size: item.size ?? null,\n createdDateTime: item.createdDateTime || null,\n lastModifiedDateTime: item.lastModifiedDateTime || null,\n mimeType: item.file?.mimeType || null,\n isFolder: Boolean(item.folder || item.package),\n isFile: Boolean(item.file),\n childCount: item.folder?.childCount ?? null,\n parentReference: item.parentReference || null,\n }\n}",
16278
+ "scope": "write"
16279
+ },
16280
+ {
16281
+ "name": "delete_drive_item",
16282
+ "description": "Delete a SharePoint file or folder by drive ID and item ID. This is a destructive operation. Use get_drive_item_meta or list_drive_children first to confirm you have the correct item before deleting it.",
16283
+ "inputSchema": {
16284
+ "$schema": "http://json-schema.org/draft-07/schema#",
16285
+ "type": "object",
16286
+ "required": [
16287
+ "driveId",
16288
+ "itemId"
16289
+ ],
16290
+ "properties": {
16291
+ "driveId": {
16292
+ "type": "string",
16293
+ "description": "Document library drive ID."
16294
+ },
16295
+ "itemId": {
16296
+ "type": "string",
16297
+ "description": "Drive item ID for the file or folder to delete."
16298
+ }
16299
+ },
16133
16300
  "additionalProperties": false
16134
16301
  },
16135
- "handlerCode": "async (input) => {\n const res = await integration.fetch(`/members/me`)\n return await res.json()\n}",
16136
- "scope": "read"
16137
- },
16138
- {
16139
- "name": "get_member_boards",
16140
- "description": "List boards for the current member.",
16141
- "inputSchema": {
16142
- "type": "object",
16143
- "properties": {},
16144
- "additionalProperties": false
16302
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/drives/${encodeURIComponent(input.driveId)}/items/${encodeURIComponent(input.itemId)}`, {\n method: 'DELETE',\n })\n if (res.status === 204)\n return { success: true, status: 204 }\n try {\n return await res.json()\n }\n catch {\n return { success: res.ok, status: res.status }\n }\n}",
16303
+ "scope": "write"
16304
+ }
16305
+ ],
16306
+ "variantOwnerType": null
16307
+ },
16308
+ "trello": {
16309
+ "manifest": {
16310
+ "name": "Trello",
16311
+ "version": "0.1.0",
16312
+ "baseUrl": "https://api.trello.com/1",
16313
+ "tools": [
16314
+ {
16315
+ "name": "get_member",
16316
+ "description": "Fetch the current member profile.",
16317
+ "inputSchema": "schemas/get_member.json",
16318
+ "handler": "handlers/get_member.js",
16319
+ "scope": "read"
16320
+ },
16321
+ {
16322
+ "name": "get_member_boards",
16323
+ "description": "List boards for the current member.",
16324
+ "inputSchema": "schemas/empty.json",
16325
+ "handler": "handlers/get_member_boards.js",
16326
+ "scope": "read"
16327
+ },
16328
+ {
16329
+ "name": "get_member_organizations",
16330
+ "description": "List organizations (workspaces) for the current member.",
16331
+ "inputSchema": "schemas/empty.json",
16332
+ "handler": "handlers/get_member_organizations.js",
16333
+ "scope": "read"
16334
+ },
16335
+ {
16336
+ "name": "get_board",
16337
+ "description": "Fetch a board by id.",
16338
+ "inputSchema": "schemas/id_board.json",
16339
+ "handler": "handlers/get_board.js",
16340
+ "scope": "read"
16341
+ },
16342
+ {
16343
+ "name": "get_board_lists",
16344
+ "description": "List lists on a board.",
16345
+ "inputSchema": "schemas/id_board.json",
16346
+ "handler": "handlers/get_board_lists.js",
16347
+ "scope": "read"
16348
+ },
16349
+ {
16350
+ "name": "get_board_cards",
16351
+ "description": "List cards on a board.",
16352
+ "inputSchema": "schemas/id_board.json",
16353
+ "handler": "handlers/get_board_cards.js",
16354
+ "scope": "read"
16355
+ },
16356
+ {
16357
+ "name": "get_board_members",
16358
+ "description": "List members on a board.",
16359
+ "inputSchema": "schemas/id_board.json",
16360
+ "handler": "handlers/get_board_members.js",
16361
+ "scope": "read"
16362
+ },
16363
+ {
16364
+ "name": "get_board_labels",
16365
+ "description": "List labels on a board.",
16366
+ "inputSchema": "schemas/id_board.json",
16367
+ "handler": "handlers/get_board_labels.js",
16368
+ "scope": "read"
16369
+ },
16370
+ {
16371
+ "name": "get_board_custom_fields",
16372
+ "description": "List custom fields on a board.",
16373
+ "inputSchema": "schemas/id_board.json",
16374
+ "handler": "handlers/get_board_custom_fields.js",
16375
+ "scope": "read"
16376
+ },
16377
+ {
16378
+ "name": "get_board_memberships",
16379
+ "description": "List memberships for a board.",
16380
+ "inputSchema": "schemas/id_board.json",
16381
+ "handler": "handlers/get_board_memberships.js",
16382
+ "scope": "read"
16383
+ },
16384
+ {
16385
+ "name": "get_list",
16386
+ "description": "Fetch a list by id.",
16387
+ "inputSchema": "schemas/id_list.json",
16388
+ "handler": "handlers/get_list.js",
16389
+ "scope": "read"
16390
+ },
16391
+ {
16392
+ "name": "get_list_cards",
16393
+ "description": "List cards in a list.",
16394
+ "inputSchema": "schemas/id_list.json",
16395
+ "handler": "handlers/get_list_cards.js",
16396
+ "scope": "read"
16397
+ },
16398
+ {
16399
+ "name": "get_card",
16400
+ "description": "Fetch a card by id.",
16401
+ "inputSchema": "schemas/id_card.json",
16402
+ "handler": "handlers/get_card.js",
16403
+ "scope": "read"
16404
+ },
16405
+ {
16406
+ "name": "get_card_members",
16407
+ "description": "List members assigned to a card.",
16408
+ "inputSchema": "schemas/id_card.json",
16409
+ "handler": "handlers/get_card_members.js",
16410
+ "scope": "read"
16411
+ },
16412
+ {
16413
+ "name": "get_card_attachments",
16414
+ "description": "List attachments on a card.",
16415
+ "inputSchema": "schemas/id_card.json",
16416
+ "handler": "handlers/get_card_attachments.js",
16417
+ "scope": "read"
16418
+ },
16419
+ {
16420
+ "name": "get_card_actions",
16421
+ "description": "List actions (activity) on a card.",
16422
+ "inputSchema": "schemas/id_card.json",
16423
+ "handler": "handlers/get_card_actions.js",
16424
+ "scope": "read"
16425
+ },
16426
+ {
16427
+ "name": "get_card_checklists",
16428
+ "description": "List checklists on a card.",
16429
+ "inputSchema": "schemas/id_card.json",
16430
+ "handler": "handlers/get_card_checklists.js",
16431
+ "scope": "read"
16432
+ },
16433
+ {
16434
+ "name": "get_card_custom_field_items",
16435
+ "description": "Get custom field items on a card.",
16436
+ "inputSchema": "schemas/id_card.json",
16437
+ "handler": "handlers/get_card_custom_field_items.js",
16438
+ "scope": "read"
16439
+ },
16440
+ {
16441
+ "name": "get_organization",
16442
+ "description": "Fetch an organization (workspace) by id.",
16443
+ "inputSchema": "schemas/id_org.json",
16444
+ "handler": "handlers/get_organization.js",
16445
+ "scope": "read"
16446
+ },
16447
+ {
16448
+ "name": "get_organization_boards",
16449
+ "description": "List boards in an organization (workspace).",
16450
+ "inputSchema": "schemas/id_org.json",
16451
+ "handler": "handlers/get_organization_boards.js",
16452
+ "scope": "read"
16453
+ },
16454
+ {
16455
+ "name": "search",
16456
+ "description": "Search across boards, cards, and members.",
16457
+ "inputSchema": "schemas/search.json",
16458
+ "handler": "handlers/search.js",
16459
+ "scope": "read"
16460
+ },
16461
+ {
16462
+ "name": "create_board",
16463
+ "description": "Create a new board.",
16464
+ "inputSchema": "schemas/create_board.json",
16465
+ "handler": "handlers/create_board.js",
16466
+ "scope": "write"
16467
+ },
16468
+ {
16469
+ "name": "close_board",
16470
+ "description": "Close a board (set closed=true).",
16471
+ "inputSchema": "schemas/close_board.json",
16472
+ "handler": "handlers/close_board.js",
16473
+ "scope": "write"
16474
+ },
16475
+ {
16476
+ "name": "delete_board",
16477
+ "description": "Permanently delete a closed board.",
16478
+ "inputSchema": "schemas/delete_board.json",
16479
+ "handler": "handlers/delete_board.js",
16480
+ "scope": "write"
16481
+ },
16482
+ {
16483
+ "name": "create_card",
16484
+ "description": "Create a new card in a list.",
16485
+ "inputSchema": "schemas/create_card.json",
16486
+ "handler": "handlers/create_card.js",
16487
+ "scope": "write"
16488
+ },
16489
+ {
16490
+ "name": "update_card",
16491
+ "description": "Update a card's fields (name, desc, due, list, etc).",
16492
+ "inputSchema": "schemas/update_card.json",
16493
+ "handler": "handlers/update_card.js",
16494
+ "scope": "write"
16495
+ },
16496
+ {
16497
+ "name": "delete_card",
16498
+ "description": "Delete a card.",
16499
+ "inputSchema": "schemas/delete_card.json",
16500
+ "handler": "handlers/delete_card.js",
16501
+ "scope": "write"
16502
+ },
16503
+ {
16504
+ "name": "move_card_to_list",
16505
+ "description": "Move a card to another list.",
16506
+ "inputSchema": "schemas/move_card_to_list.json",
16507
+ "handler": "handlers/move_card_to_list.js",
16508
+ "scope": "write"
16509
+ },
16510
+ {
16511
+ "name": "add_member_to_card",
16512
+ "description": "Add a member to a card.",
16513
+ "inputSchema": "schemas/add_member_to_card.json",
16514
+ "handler": "handlers/add_member_to_card.js",
16515
+ "scope": "write"
16516
+ },
16517
+ {
16518
+ "name": "remove_member_from_card",
16519
+ "description": "Remove a member from a card.",
16520
+ "inputSchema": "schemas/remove_member_from_card.json",
16521
+ "handler": "handlers/remove_member_from_card.js",
16522
+ "scope": "write"
16523
+ },
16524
+ {
16525
+ "name": "add_checklist_to_card",
16526
+ "description": "Create a checklist on a card.",
16527
+ "inputSchema": "schemas/add_checklist_to_card.json",
16528
+ "handler": "handlers/add_checklist_to_card.js",
16529
+ "scope": "write"
16530
+ },
16531
+ {
16532
+ "name": "create_list",
16533
+ "description": "Create a new list on a board.",
16534
+ "inputSchema": "schemas/create_list.json",
16535
+ "handler": "handlers/create_list.js",
16536
+ "scope": "write"
16537
+ },
16538
+ {
16539
+ "name": "update_list",
16540
+ "description": "Update a list (name, pos, closed).",
16541
+ "inputSchema": "schemas/update_list.json",
16542
+ "handler": "handlers/update_list.js",
16543
+ "scope": "write"
16544
+ },
16545
+ {
16546
+ "name": "archive_list",
16547
+ "description": "Archive a list (set closed=true).",
16548
+ "inputSchema": "schemas/archive_list.json",
16549
+ "handler": "handlers/archive_list.js",
16550
+ "scope": "write"
16551
+ }
16552
+ ]
16553
+ },
16554
+ "prompt": null,
16555
+ "variants": {
16556
+ "variants": {
16557
+ "api_key_token": {
16558
+ "label": "API Key + Token",
16559
+ "schema": {
16560
+ "type": "object",
16561
+ "properties": {
16562
+ "apiKey": {
16563
+ "type": "string",
16564
+ "title": "API Key",
16565
+ "description": "Your Trello API key from https://trello.com/power-ups/admin"
16566
+ },
16567
+ "apiToken": {
16568
+ "type": "string",
16569
+ "title": "API Token",
16570
+ "description": `Your Trello API token ("token" param). Generate one via Trello's authorize flow.`
16571
+ }
16572
+ },
16573
+ "required": [
16574
+ "apiKey",
16575
+ "apiToken"
16576
+ ],
16577
+ "additionalProperties": false
16578
+ },
16579
+ "injection": {
16580
+ "query": {
16581
+ "key": "{{apiKey}}",
16582
+ "token": "{{apiToken}}"
16583
+ }
16584
+ },
16585
+ "healthCheck": {
16586
+ "path": "/members/me"
16587
+ }
16588
+ }
16589
+ },
16590
+ "default": "api_key_token"
16591
+ },
16592
+ "hint": "1. Go to `https://trello.com/power-ups/admin`\n2. Create a new app\n3. Navigate to **API Key** and copy your API key\n4. Click **Generate a Token** and copy the token value",
16593
+ "hintsByVariant": {},
16594
+ "tools": [
16595
+ {
16596
+ "name": "get_member",
16597
+ "description": "Fetch the current member profile.",
16598
+ "inputSchema": {
16599
+ "type": "object",
16600
+ "properties": {},
16601
+ "additionalProperties": false
16602
+ },
16603
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/members/me`)\n return await res.json()\n}",
16604
+ "scope": "read"
16605
+ },
16606
+ {
16607
+ "name": "get_member_boards",
16608
+ "description": "List boards for the current member.",
16609
+ "inputSchema": {
16610
+ "type": "object",
16611
+ "properties": {},
16612
+ "additionalProperties": false
16613
+ },
16614
+ "handlerCode": "async (input) => {\n const truncateDesc = (desc) => {\n if (typeof desc !== 'string' || !desc.trim())\n return null\n const oneLine = desc.replace(/\\s+/g, ' ').trim()\n const max = 200\n return oneLine.length <= max ? oneLine : `${oneLine.slice(0, max - 1)}\u2026`\n }\n\n const fields = [\n 'id',\n 'name',\n 'desc',\n 'url',\n 'shortUrl',\n 'shortLink',\n 'dateLastActivity',\n 'idOrganization',\n 'closed',\n 'starred',\n ].join(',')\n const res = await integration.fetch(`/members/me/boards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n if (!Array.isArray(raw))\n return { count: 0, boards: [], note: 'Unexpected response from Trello; expected a list of boards.' }\n\n const boards = raw.map((b) => ({\n id: b.id,\n name: b.name,\n url: b.url || b.shortUrl || (b.shortLink ? `https://trello.com/b/${b.shortLink}` : undefined),\n shortLink: b.shortLink,\n closed: !!b.closed,\n starred: !!b.starred,\n workspaceId: b.idOrganization || null,\n lastActivity: b.dateLastActivity || null,\n descriptionPreview: truncateDesc(b.desc),\n }))\n\n boards.sort((a, b) => {\n if (a.closed !== b.closed)\n return a.closed ? 1 : -1\n if (a.starred !== b.starred)\n return a.starred ? -1 : 1\n return String(a.name || '').localeCompare(String(b.name || ''), undefined, { sensitivity: 'base' })\n })\n\n const open = boards.filter((x) => !x.closed).length\n return {\n count: boards.length,\n openCount: open,\n closedCount: boards.length - open,\n note:\n 'Use `id` as `boardId` in other Trello tools (lists, cards, labels). `url` is the human-facing board link. Closed boards are archived.',\n boards,\n }\n}",
16615
+ "scope": "read"
16616
+ },
16617
+ {
16618
+ "name": "get_member_organizations",
16619
+ "description": "List organizations (workspaces) for the current member.",
16620
+ "inputSchema": {
16621
+ "type": "object",
16622
+ "properties": {},
16623
+ "additionalProperties": false
16624
+ },
16625
+ "handlerCode": "async (input) => {\n const fields = ['id', 'name', 'displayName', 'desc', 'url'].join(',')\n const res = await integration.fetch(`/members/me/organizations?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const organizations = Array.isArray(raw)\n ? raw.map(org => ({\n id: org.id,\n name: org.name || null,\n displayName: org.displayName || null,\n url: org.url || null,\n descriptionPreview: typeof org.desc === 'string' && org.desc.trim()\n ? (org.desc.trim().length <= 200 ? org.desc.trim() : `${org.desc.trim().slice(0, 199)}...`)\n : null,\n }))\n : []\n return {\n count: organizations.length,\n note: 'Use org id with get_organization for full organization details.',\n organizations,\n }\n}",
16626
+ "scope": "read"
16627
+ },
16628
+ {
16629
+ "name": "get_board",
16630
+ "description": "Fetch a board by id.",
16631
+ "inputSchema": {
16632
+ "type": "object",
16633
+ "properties": {
16634
+ "boardId": {
16635
+ "type": "string"
16636
+ }
16637
+ },
16638
+ "required": [
16639
+ "boardId"
16640
+ ],
16641
+ "additionalProperties": false
16642
+ },
16643
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}`)\n return await res.json()\n}",
16644
+ "scope": "read"
16645
+ },
16646
+ {
16647
+ "name": "get_board_lists",
16648
+ "description": "List lists on a board.",
16649
+ "inputSchema": {
16650
+ "type": "object",
16651
+ "properties": {
16652
+ "boardId": {
16653
+ "type": "string"
16654
+ }
16655
+ },
16656
+ "required": [
16657
+ "boardId"
16658
+ ],
16659
+ "additionalProperties": false
16660
+ },
16661
+ "handlerCode": "async (input) => {\n const fields = ['id', 'name', 'idBoard', 'closed', 'pos', 'softLimit'].join(',')\n const res = await integration.fetch(`/boards/${input.boardId}/lists?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const lists = Array.isArray(raw)\n ? raw.map(list => ({\n id: list.id,\n name: list.name,\n idBoard: list.idBoard || null,\n closed: !!list.closed,\n position: list.pos ?? null,\n softLimit: typeof list.softLimit === 'number' ? list.softLimit : null,\n }))\n : []\n return {\n boardId: input.boardId,\n count: lists.length,\n note: 'Use list id with get_list for full list details.',\n lists,\n }\n}",
16662
+ "scope": "read"
16663
+ },
16664
+ {
16665
+ "name": "get_board_cards",
16666
+ "description": "List cards on a board.",
16667
+ "inputSchema": {
16668
+ "type": "object",
16669
+ "properties": {
16670
+ "boardId": {
16671
+ "type": "string"
16672
+ }
16673
+ },
16674
+ "required": [
16675
+ "boardId"
16676
+ ],
16677
+ "additionalProperties": false
16678
+ },
16679
+ "handlerCode": "async (input) => {\n const fields = [\n 'id',\n 'name',\n 'desc',\n 'idBoard',\n 'idList',\n 'shortLink',\n 'shortUrl',\n 'url',\n 'closed',\n 'due',\n 'dateLastActivity',\n 'labels',\n 'pos',\n ].join(',')\n const res = await integration.fetch(`/boards/${input.boardId}/cards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const cards = Array.isArray(raw)\n ? raw.map(card => ({\n id: card.id,\n name: card.name,\n idBoard: card.idBoard || null,\n idList: card.idList || null,\n url: card.url || card.shortUrl || (card.shortLink ? `https://trello.com/c/${card.shortLink}` : null),\n shortLink: card.shortLink || null,\n closed: !!card.closed,\n due: card.due || null,\n lastActivity: card.dateLastActivity || null,\n position: card.pos ?? null,\n labels: Array.isArray(card.labels)\n ? card.labels.map(label => ({ id: label.id, name: label.name || null, color: label.color || null }))\n : [],\n descriptionPreview: typeof card.desc === 'string' && card.desc.trim()\n ? (card.desc.trim().length <= 200 ? card.desc.trim() : `${card.desc.trim().slice(0, 199)}...`)\n : null,\n }))\n : []\n return {\n boardId: input.boardId,\n count: cards.length,\n note: 'Use card id with get_card for full card details.',\n cards,\n }\n}",
16680
+ "scope": "read"
16681
+ },
16682
+ {
16683
+ "name": "get_board_members",
16684
+ "description": "List members on a board.",
16685
+ "inputSchema": {
16686
+ "type": "object",
16687
+ "properties": {
16688
+ "boardId": {
16689
+ "type": "string"
16690
+ }
16691
+ },
16692
+ "required": [
16693
+ "boardId"
16694
+ ],
16695
+ "additionalProperties": false
16696
+ },
16697
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/members`)\n return await res.json()\n}",
16698
+ "scope": "read"
16699
+ },
16700
+ {
16701
+ "name": "get_board_labels",
16702
+ "description": "List labels on a board.",
16703
+ "inputSchema": {
16704
+ "type": "object",
16705
+ "properties": {
16706
+ "boardId": {
16707
+ "type": "string"
16708
+ }
16709
+ },
16710
+ "required": [
16711
+ "boardId"
16712
+ ],
16713
+ "additionalProperties": false
16714
+ },
16715
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/labels`)\n return await res.json()\n}",
16716
+ "scope": "read"
16717
+ },
16718
+ {
16719
+ "name": "get_board_custom_fields",
16720
+ "description": "List custom fields on a board.",
16721
+ "inputSchema": {
16722
+ "type": "object",
16723
+ "properties": {
16724
+ "boardId": {
16725
+ "type": "string"
16726
+ }
16727
+ },
16728
+ "required": [
16729
+ "boardId"
16730
+ ],
16731
+ "additionalProperties": false
16732
+ },
16733
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/customFields`)\n return await res.json()\n}",
16734
+ "scope": "read"
16735
+ },
16736
+ {
16737
+ "name": "get_board_memberships",
16738
+ "description": "List memberships for a board.",
16739
+ "inputSchema": {
16740
+ "type": "object",
16741
+ "properties": {
16742
+ "boardId": {
16743
+ "type": "string"
16744
+ }
16745
+ },
16746
+ "required": [
16747
+ "boardId"
16748
+ ],
16749
+ "additionalProperties": false
16750
+ },
16751
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/memberships`)\n return await res.json()\n}",
16752
+ "scope": "read"
16753
+ },
16754
+ {
16755
+ "name": "get_list",
16756
+ "description": "Fetch a list by id.",
16757
+ "inputSchema": {
16758
+ "type": "object",
16759
+ "properties": {
16760
+ "listId": {
16761
+ "type": "string"
16762
+ }
16763
+ },
16764
+ "required": [
16765
+ "listId"
16766
+ ],
16767
+ "additionalProperties": false
16768
+ },
16769
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/lists/${input.listId}`)\n return await res.json()\n}",
16770
+ "scope": "read"
16771
+ },
16772
+ {
16773
+ "name": "get_list_cards",
16774
+ "description": "List cards in a list.",
16775
+ "inputSchema": {
16776
+ "type": "object",
16777
+ "properties": {
16778
+ "listId": {
16779
+ "type": "string"
16780
+ }
16781
+ },
16782
+ "required": [
16783
+ "listId"
16784
+ ],
16785
+ "additionalProperties": false
16786
+ },
16787
+ "handlerCode": "async (input) => {\n const fields = [\n 'id',\n 'name',\n 'desc',\n 'idBoard',\n 'idList',\n 'shortLink',\n 'shortUrl',\n 'url',\n 'closed',\n 'due',\n 'dateLastActivity',\n 'labels',\n 'pos',\n ].join(',')\n const res = await integration.fetch(`/lists/${input.listId}/cards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const cards = Array.isArray(raw)\n ? raw.map(card => ({\n id: card.id,\n name: card.name,\n idBoard: card.idBoard || null,\n idList: card.idList || null,\n url: card.url || card.shortUrl || (card.shortLink ? `https://trello.com/c/${card.shortLink}` : null),\n shortLink: card.shortLink || null,\n closed: !!card.closed,\n due: card.due || null,\n lastActivity: card.dateLastActivity || null,\n position: card.pos ?? null,\n labels: Array.isArray(card.labels)\n ? card.labels.map(label => ({ id: label.id, name: label.name || null, color: label.color || null }))\n : [],\n descriptionPreview: typeof card.desc === 'string' && card.desc.trim()\n ? (card.desc.trim().length <= 200 ? card.desc.trim() : `${card.desc.trim().slice(0, 199)}...`)\n : null,\n }))\n : []\n return {\n listId: input.listId,\n count: cards.length,\n note: 'Use card id with get_card for full card details.',\n cards,\n }\n}",
16788
+ "scope": "read"
16789
+ },
16790
+ {
16791
+ "name": "get_card",
16792
+ "description": "Fetch a card by id.",
16793
+ "inputSchema": {
16794
+ "type": "object",
16795
+ "properties": {
16796
+ "cardId": {
16797
+ "type": "string"
16798
+ }
16799
+ },
16800
+ "required": [
16801
+ "cardId"
16802
+ ],
16803
+ "additionalProperties": false
16804
+ },
16805
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}`)\n return await res.json()\n}",
16806
+ "scope": "read"
16807
+ },
16808
+ {
16809
+ "name": "get_card_members",
16810
+ "description": "List members assigned to a card.",
16811
+ "inputSchema": {
16812
+ "type": "object",
16813
+ "properties": {
16814
+ "cardId": {
16815
+ "type": "string"
16816
+ }
16817
+ },
16818
+ "required": [
16819
+ "cardId"
16820
+ ],
16821
+ "additionalProperties": false
16822
+ },
16823
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}/members`)\n return await res.json()\n}",
16824
+ "scope": "read"
16825
+ },
16826
+ {
16827
+ "name": "get_card_attachments",
16828
+ "description": "List attachments on a card.",
16829
+ "inputSchema": {
16830
+ "type": "object",
16831
+ "properties": {
16832
+ "cardId": {
16833
+ "type": "string"
16834
+ }
16835
+ },
16836
+ "required": [
16837
+ "cardId"
16838
+ ],
16839
+ "additionalProperties": false
16840
+ },
16841
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}/attachments`)\n return await res.json()\n}",
16842
+ "scope": "read"
16843
+ },
16844
+ {
16845
+ "name": "get_card_actions",
16846
+ "description": "List actions (activity) on a card.",
16847
+ "inputSchema": {
16848
+ "type": "object",
16849
+ "properties": {
16850
+ "cardId": {
16851
+ "type": "string"
16852
+ }
16853
+ },
16854
+ "required": [
16855
+ "cardId"
16856
+ ],
16857
+ "additionalProperties": false
16858
+ },
16859
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}/actions`)\n return await res.json()\n}",
16860
+ "scope": "read"
16861
+ },
16862
+ {
16863
+ "name": "get_card_checklists",
16864
+ "description": "List checklists on a card.",
16865
+ "inputSchema": {
16866
+ "type": "object",
16867
+ "properties": {
16868
+ "cardId": {
16869
+ "type": "string"
16870
+ }
16871
+ },
16872
+ "required": [
16873
+ "cardId"
16874
+ ],
16875
+ "additionalProperties": false
16876
+ },
16877
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}/checklists`)\n return await res.json()\n}",
16878
+ "scope": "read"
16879
+ },
16880
+ {
16881
+ "name": "get_card_custom_field_items",
16882
+ "description": "Get custom field items on a card.",
16883
+ "inputSchema": {
16884
+ "type": "object",
16885
+ "properties": {
16886
+ "cardId": {
16887
+ "type": "string"
16888
+ }
16889
+ },
16890
+ "required": [
16891
+ "cardId"
16892
+ ],
16893
+ "additionalProperties": false
16894
+ },
16895
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}/customFieldItems`)\n return await res.json()\n}",
16896
+ "scope": "read"
16897
+ },
16898
+ {
16899
+ "name": "get_organization",
16900
+ "description": "Fetch an organization (workspace) by id.",
16901
+ "inputSchema": {
16902
+ "type": "object",
16903
+ "properties": {
16904
+ "orgId": {
16905
+ "type": "string"
16906
+ }
16907
+ },
16908
+ "required": [
16909
+ "orgId"
16910
+ ],
16911
+ "additionalProperties": false
16912
+ },
16913
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/organizations/${input.orgId}`)\n return await res.json()\n}",
16914
+ "scope": "read"
16915
+ },
16916
+ {
16917
+ "name": "get_organization_boards",
16918
+ "description": "List boards in an organization (workspace).",
16919
+ "inputSchema": {
16920
+ "type": "object",
16921
+ "properties": {
16922
+ "orgId": {
16923
+ "type": "string"
16924
+ }
16925
+ },
16926
+ "required": [
16927
+ "orgId"
16928
+ ],
16929
+ "additionalProperties": false
16930
+ },
16931
+ "handlerCode": "async (input) => {\n const fields = [\n 'id',\n 'name',\n 'desc',\n 'url',\n 'shortUrl',\n 'shortLink',\n 'dateLastActivity',\n 'idOrganization',\n 'closed',\n 'starred',\n ].join(',')\n const res = await integration.fetch(`/organizations/${input.orgId}/boards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const boards = Array.isArray(raw)\n ? raw.map(b => ({\n id: b.id,\n name: b.name,\n url: b.url || b.shortUrl || (b.shortLink ? `https://trello.com/b/${b.shortLink}` : null),\n shortLink: b.shortLink || null,\n closed: !!b.closed,\n starred: !!b.starred,\n workspaceId: b.idOrganization || null,\n lastActivity: b.dateLastActivity || null,\n descriptionPreview: typeof b.desc === 'string' && b.desc.trim()\n ? (b.desc.trim().length <= 200 ? b.desc.trim() : `${b.desc.trim().slice(0, 199)}...`)\n : null,\n }))\n : []\n return {\n orgId: input.orgId,\n count: boards.length,\n note: 'Use board id with get_board for full board details.',\n boards,\n }\n}",
16932
+ "scope": "read"
16933
+ },
16934
+ {
16935
+ "name": "search",
16936
+ "description": "Search across boards, cards, and members.",
16937
+ "inputSchema": {
16938
+ "type": "object",
16939
+ "properties": {
16940
+ "query": {
16941
+ "type": "string"
16942
+ }
16943
+ },
16944
+ "required": [
16945
+ "query"
16946
+ ],
16947
+ "additionalProperties": false
16948
+ },
16949
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams({ query: input.query })\n const res = await integration.fetch(`/search?${params.toString()}`)\n const data = await res.json()\n\n const boards = Array.isArray(data?.boards)\n ? data.boards.map(board => ({\n id: board.id,\n name: board.name,\n shortLink: board.shortLink || null,\n url: board.url || (board.shortLink ? `https://trello.com/b/${board.shortLink}` : null),\n closed: !!board.closed,\n }))\n : []\n\n const cards = Array.isArray(data?.cards)\n ? data.cards.map(card => ({\n id: card.id,\n name: card.name,\n idBoard: card.idBoard || null,\n idList: card.idList || null,\n shortLink: card.shortLink || null,\n url: card.url || card.shortUrl || (card.shortLink ? `https://trello.com/c/${card.shortLink}` : null),\n closed: !!card.closed,\n }))\n : []\n\n return {\n query: input.query,\n count: boards.length + cards.length,\n boardCount: boards.length,\n cardCount: cards.length,\n note: 'Use get_board with board id or get_card with card id for full details.',\n boards,\n cards,\n }\n}",
16950
+ "scope": "read"
16951
+ },
16952
+ {
16953
+ "name": "create_board",
16954
+ "description": "Create a new board.",
16955
+ "inputSchema": {
16956
+ "$schema": "http://json-schema.org/draft-07/schema#",
16957
+ "type": "object",
16958
+ "required": [
16959
+ "name"
16960
+ ],
16961
+ "additionalProperties": false,
16962
+ "properties": {
16963
+ "name": {
16964
+ "type": "string"
16965
+ },
16966
+ "defaultLists": {
16967
+ "type": "boolean",
16968
+ "description": "Create the default lists on the board (default: true)"
16969
+ },
16970
+ "desc": {
16971
+ "type": "string"
16972
+ }
16973
+ }
16974
+ },
16975
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set('name', input.name)\n if (input.defaultLists !== undefined && input.defaultLists !== null)\n params.set('defaultLists', String(Boolean(input.defaultLists)))\n if (input.desc !== undefined && input.desc !== null)\n params.set('desc', String(input.desc))\n const res = await integration.fetch(`/boards?${params.toString()}`, { method: 'POST' })\n return await res.json()\n}",
16976
+ "scope": "write"
16977
+ },
16978
+ {
16979
+ "name": "close_board",
16980
+ "description": "Close a board (set closed=true).",
16981
+ "inputSchema": {
16982
+ "$schema": "http://json-schema.org/draft-07/schema#",
16983
+ "type": "object",
16984
+ "required": [
16985
+ "boardId"
16986
+ ],
16987
+ "additionalProperties": false,
16988
+ "properties": {
16989
+ "boardId": {
16990
+ "type": "string"
16991
+ }
16992
+ }
16993
+ },
16994
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams({ closed: 'true' })\n const res = await integration.fetch(`/boards/${encodeURIComponent(input.boardId)}?${params.toString()}`, { method: 'PUT' })\n return await res.json()\n}",
16995
+ "scope": "write"
16996
+ },
16997
+ {
16998
+ "name": "delete_board",
16999
+ "description": "Permanently delete a closed board.",
17000
+ "inputSchema": {
17001
+ "$schema": "http://json-schema.org/draft-07/schema#",
17002
+ "type": "object",
17003
+ "required": [
17004
+ "boardId"
17005
+ ],
17006
+ "additionalProperties": false,
17007
+ "properties": {
17008
+ "boardId": {
17009
+ "type": "string"
17010
+ }
17011
+ }
17012
+ },
17013
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${encodeURIComponent(input.boardId)}`, { method: 'DELETE' })\n if (res.status === 204)\n return { success: true, status: 204 }\n // Trello sometimes returns JSON on delete failures.\n try {\n return await res.json()\n }\n catch {\n return { success: res.ok, status: res.status }\n }\n}",
17014
+ "scope": "write"
17015
+ },
17016
+ {
17017
+ "name": "create_card",
17018
+ "description": "Create a new card in a list.",
17019
+ "inputSchema": {
17020
+ "$schema": "http://json-schema.org/draft-07/schema#",
17021
+ "type": "object",
17022
+ "required": [
17023
+ "idList",
17024
+ "name"
17025
+ ],
17026
+ "additionalProperties": false,
17027
+ "properties": {
17028
+ "idList": {
17029
+ "type": "string"
17030
+ },
17031
+ "name": {
17032
+ "type": "string"
17033
+ },
17034
+ "desc": {
17035
+ "type": "string"
17036
+ },
17037
+ "due": {
17038
+ "type": [
17039
+ "string",
17040
+ "null"
17041
+ ],
17042
+ "description": "ISO 8601 due date"
17043
+ },
17044
+ "pos": {
17045
+ "type": [
17046
+ "string",
17047
+ "number"
17048
+ ],
17049
+ "description": "Position (top,bottom or float)"
17050
+ }
17051
+ }
17052
+ },
17053
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set('idList', input.idList)\n params.set('name', input.name)\n if (input.desc !== undefined)\n params.set('desc', input.desc)\n if (input.due !== undefined && input.due !== null)\n params.set('due', input.due)\n if (input.pos !== undefined && input.pos !== null)\n params.set('pos', String(input.pos))\n const res = await integration.fetch(`/cards?${params.toString()}`, { method: 'POST' })\n return await res.json()\n}",
17054
+ "scope": "write"
17055
+ },
17056
+ {
17057
+ "name": "update_card",
17058
+ "description": "Update a card's fields (name, desc, due, list, etc).",
17059
+ "inputSchema": {
17060
+ "$schema": "http://json-schema.org/draft-07/schema#",
17061
+ "type": "object",
17062
+ "required": [
17063
+ "cardId"
17064
+ ],
17065
+ "additionalProperties": false,
17066
+ "properties": {
17067
+ "cardId": {
17068
+ "type": "string"
17069
+ },
17070
+ "name": {
17071
+ "type": [
17072
+ "string",
17073
+ "null"
17074
+ ]
17075
+ },
17076
+ "desc": {
17077
+ "type": [
17078
+ "string",
17079
+ "null"
17080
+ ]
17081
+ },
17082
+ "due": {
17083
+ "type": [
17084
+ "string",
17085
+ "null"
17086
+ ]
17087
+ },
17088
+ "dueComplete": {
17089
+ "type": [
17090
+ "boolean",
17091
+ "null"
17092
+ ]
17093
+ },
17094
+ "closed": {
17095
+ "type": [
17096
+ "boolean",
17097
+ "null"
17098
+ ]
17099
+ },
17100
+ "idList": {
17101
+ "type": [
17102
+ "string",
17103
+ "null"
17104
+ ]
17105
+ },
17106
+ "pos": {
17107
+ "type": [
17108
+ "string",
17109
+ "number",
17110
+ "null"
17111
+ ]
17112
+ }
17113
+ }
17114
+ },
17115
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n if (input.name !== undefined && input.name !== null)\n params.set('name', input.name)\n if (input.desc !== undefined && input.desc !== null)\n params.set('desc', input.desc)\n if (input.due !== undefined)\n params.set('due', input.due === null ? '' : input.due)\n if (input.dueComplete !== undefined && input.dueComplete !== null)\n params.set('dueComplete', String(input.dueComplete))\n if (input.closed !== undefined && input.closed !== null)\n params.set('closed', String(input.closed))\n if (input.idList !== undefined && input.idList !== null)\n params.set('idList', input.idList)\n if (input.pos !== undefined && input.pos !== null)\n params.set('pos', String(input.pos))\n const res = await integration.fetch(`/cards/${encodeURIComponent(input.cardId)}?${params.toString()}`, { method: 'PUT' })\n return await res.json()\n}",
17116
+ "scope": "write"
17117
+ },
17118
+ {
17119
+ "name": "delete_card",
17120
+ "description": "Delete a card.",
17121
+ "inputSchema": {
17122
+ "$schema": "http://json-schema.org/draft-07/schema#",
17123
+ "type": "object",
17124
+ "required": [
17125
+ "cardId"
17126
+ ],
17127
+ "additionalProperties": false,
17128
+ "properties": {
17129
+ "cardId": {
17130
+ "type": "string"
17131
+ }
17132
+ }
17133
+ },
17134
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${encodeURIComponent(input.cardId)}`, { method: 'DELETE' })\n try {\n return await res.json()\n }\n catch {\n return ''\n }\n}",
17135
+ "scope": "write"
17136
+ },
17137
+ {
17138
+ "name": "move_card_to_list",
17139
+ "description": "Move a card to another list.",
17140
+ "inputSchema": {
17141
+ "$schema": "http://json-schema.org/draft-07/schema#",
17142
+ "type": "object",
17143
+ "required": [
17144
+ "cardId",
17145
+ "listId"
17146
+ ],
17147
+ "additionalProperties": false,
17148
+ "properties": {
17149
+ "cardId": {
17150
+ "type": "string"
17151
+ },
17152
+ "listId": {
17153
+ "type": "string"
17154
+ }
17155
+ }
17156
+ },
17157
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams({ value: input.listId })\n const res = await integration.fetch(`/cards/${encodeURIComponent(input.cardId)}/idList?${params.toString()}`, { method: 'PUT' })\n return await res.json()\n}",
17158
+ "scope": "write"
17159
+ },
17160
+ {
17161
+ "name": "add_member_to_card",
17162
+ "description": "Add a member to a card.",
17163
+ "inputSchema": {
17164
+ "$schema": "http://json-schema.org/draft-07/schema#",
17165
+ "type": "object",
17166
+ "required": [
17167
+ "cardId",
17168
+ "memberId"
17169
+ ],
17170
+ "additionalProperties": false,
17171
+ "properties": {
17172
+ "cardId": {
17173
+ "type": "string"
17174
+ },
17175
+ "memberId": {
17176
+ "type": "string"
17177
+ }
17178
+ }
17179
+ },
17180
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams({ value: input.memberId })\n const res = await integration.fetch(`/cards/${encodeURIComponent(input.cardId)}/idMembers?${params.toString()}`, { method: 'POST' })\n return await res.json()\n}",
17181
+ "scope": "write"
17182
+ },
17183
+ {
17184
+ "name": "remove_member_from_card",
17185
+ "description": "Remove a member from a card.",
17186
+ "inputSchema": {
17187
+ "$schema": "http://json-schema.org/draft-07/schema#",
17188
+ "type": "object",
17189
+ "required": [
17190
+ "cardId",
17191
+ "memberId"
17192
+ ],
17193
+ "additionalProperties": false,
17194
+ "properties": {
17195
+ "cardId": {
17196
+ "type": "string"
17197
+ },
17198
+ "memberId": {
17199
+ "type": "string"
17200
+ }
17201
+ }
17202
+ },
17203
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${encodeURIComponent(input.cardId)}/idMembers/${encodeURIComponent(input.memberId)}`, { method: 'DELETE' })\n try {\n return await res.json()\n }\n catch {\n return ''\n }\n}",
17204
+ "scope": "write"
17205
+ },
17206
+ {
17207
+ "name": "add_checklist_to_card",
17208
+ "description": "Create a checklist on a card.",
17209
+ "inputSchema": {
17210
+ "$schema": "http://json-schema.org/draft-07/schema#",
17211
+ "type": "object",
17212
+ "required": [
17213
+ "cardId",
17214
+ "name"
17215
+ ],
17216
+ "additionalProperties": false,
17217
+ "properties": {
17218
+ "cardId": {
17219
+ "type": "string"
17220
+ },
17221
+ "name": {
17222
+ "type": "string"
17223
+ }
17224
+ }
17225
+ },
17226
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams({ idCard: input.cardId, name: input.name })\n const res = await integration.fetch(`/checklists?${params.toString()}`, { method: 'POST' })\n return await res.json()\n}",
17227
+ "scope": "write"
17228
+ },
17229
+ {
17230
+ "name": "create_list",
17231
+ "description": "Create a new list on a board.",
17232
+ "inputSchema": {
17233
+ "$schema": "http://json-schema.org/draft-07/schema#",
17234
+ "type": "object",
17235
+ "required": [
17236
+ "idBoard",
17237
+ "name"
17238
+ ],
17239
+ "additionalProperties": false,
17240
+ "properties": {
17241
+ "idBoard": {
17242
+ "type": "string"
17243
+ },
17244
+ "name": {
17245
+ "type": "string"
17246
+ },
17247
+ "pos": {
17248
+ "type": [
17249
+ "string",
17250
+ "number"
17251
+ ],
17252
+ "description": "Position (top,bottom or float)"
17253
+ }
17254
+ }
17255
+ },
17256
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams({ idBoard: input.idBoard, name: input.name })\n if (input.pos !== undefined && input.pos !== null)\n params.set('pos', String(input.pos))\n const res = await integration.fetch(`/lists?${params.toString()}`, { method: 'POST' })\n return await res.json()\n}",
17257
+ "scope": "write"
17258
+ },
17259
+ {
17260
+ "name": "update_list",
17261
+ "description": "Update a list (name, pos, closed).",
17262
+ "inputSchema": {
17263
+ "$schema": "http://json-schema.org/draft-07/schema#",
17264
+ "type": "object",
17265
+ "required": [
17266
+ "listId"
17267
+ ],
17268
+ "additionalProperties": false,
17269
+ "properties": {
17270
+ "listId": {
17271
+ "type": "string"
17272
+ },
17273
+ "name": {
17274
+ "type": [
17275
+ "string",
17276
+ "null"
17277
+ ]
17278
+ },
17279
+ "closed": {
17280
+ "type": [
17281
+ "boolean",
17282
+ "null"
17283
+ ]
17284
+ },
17285
+ "pos": {
17286
+ "type": [
17287
+ "string",
17288
+ "number",
17289
+ "null"
17290
+ ]
17291
+ }
17292
+ }
17293
+ },
17294
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n if (input.name !== undefined && input.name !== null)\n params.set('name', input.name)\n if (input.closed !== undefined && input.closed !== null)\n params.set('closed', String(input.closed))\n if (input.pos !== undefined && input.pos !== null)\n params.set('pos', String(input.pos))\n const res = await integration.fetch(`/lists/${encodeURIComponent(input.listId)}?${params.toString()}`, { method: 'PUT' })\n return await res.json()\n}",
17295
+ "scope": "write"
17296
+ },
17297
+ {
17298
+ "name": "archive_list",
17299
+ "description": "Archive a list (set closed=true).",
17300
+ "inputSchema": {
17301
+ "$schema": "http://json-schema.org/draft-07/schema#",
17302
+ "type": "object",
17303
+ "required": [
17304
+ "listId"
17305
+ ],
17306
+ "additionalProperties": false,
17307
+ "properties": {
17308
+ "listId": {
17309
+ "type": "string"
17310
+ }
17311
+ }
17312
+ },
17313
+ "handlerCode": "async (input) => {\n const params = new URLSearchParams({ value: 'true' })\n const res = await integration.fetch(`/lists/${encodeURIComponent(input.listId)}/closed?${params.toString()}`, { method: 'PUT' })\n return await res.json()\n}",
17314
+ "scope": "write"
17315
+ }
17316
+ ],
17317
+ "variantOwnerType": null
17318
+ },
17319
+ "trello-board": {
17320
+ "manifest": {
17321
+ "name": "Trello",
17322
+ "version": "0.1.0",
17323
+ "baseUrl": "https://api.trello.com/1",
17324
+ "variantLabel": "Single board",
17325
+ "variantConfig": [
17326
+ {
17327
+ "key": "board",
17328
+ "label": "Board",
17329
+ "selectionMode": "single",
17330
+ "listHandler": "async (config) => {\n const fields = ['id', 'name', 'closed', 'starred'].join(',')\n const res = await integration.fetch(`/members/me/boards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n if (!Array.isArray(raw))\n return []\n\n return raw\n .map(board => ({\n id: String(board.id ?? ''),\n name: String(board.name ?? ''),\n closed: !!board.closed,\n starred: !!board.starred,\n }))\n .filter(board => board.id && board.name)\n .sort((a, b) => {\n if (a.closed !== b.closed)\n return a.closed ? 1 : -1\n if (a.starred !== b.starred)\n return a.starred ? -1 : 1\n return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })\n })\n .map(({ id, name }) => ({ id, name }))\n}"
17331
+ }
17332
+ ],
17333
+ "tools": [
17334
+ {
17335
+ "name": "get_board",
17336
+ "description": "Fetch the connected board.",
17337
+ "inputSchema": "../../schemas/id_board.json",
17338
+ "handler": "../../handlers/get_board.js",
17339
+ "scope": "read",
17340
+ "injectFromConfig": {
17341
+ "boardId": "boardId"
17342
+ }
17343
+ },
17344
+ {
17345
+ "name": "get_lists",
17346
+ "description": "List lists on the connected board.",
17347
+ "inputSchema": "../../schemas/id_board.json",
17348
+ "handler": "../../handlers/get_board_lists.js",
17349
+ "scope": "read",
17350
+ "injectFromConfig": {
17351
+ "boardId": "boardId"
17352
+ }
17353
+ },
17354
+ {
17355
+ "name": "get_cards",
17356
+ "description": "List cards on the connected board.",
17357
+ "inputSchema": "../../schemas/id_board.json",
17358
+ "handler": "../../handlers/get_board_cards.js",
17359
+ "scope": "read",
17360
+ "injectFromConfig": {
17361
+ "boardId": "boardId"
17362
+ }
17363
+ },
17364
+ {
17365
+ "name": "get_members",
17366
+ "description": "List members on the connected board.",
17367
+ "inputSchema": "../../schemas/id_board.json",
17368
+ "handler": "../../handlers/get_board_members.js",
17369
+ "scope": "read",
17370
+ "injectFromConfig": {
17371
+ "boardId": "boardId"
17372
+ }
17373
+ },
17374
+ {
17375
+ "name": "get_labels",
17376
+ "description": "List labels on the connected board.",
17377
+ "inputSchema": "../../schemas/id_board.json",
17378
+ "handler": "../../handlers/get_board_labels.js",
17379
+ "scope": "read",
17380
+ "injectFromConfig": {
17381
+ "boardId": "boardId"
17382
+ }
17383
+ },
17384
+ {
17385
+ "name": "get_custom_fields",
17386
+ "description": "List custom fields on the connected board.",
17387
+ "inputSchema": "../../schemas/id_board.json",
17388
+ "handler": "../../handlers/get_board_custom_fields.js",
17389
+ "scope": "read",
17390
+ "injectFromConfig": {
17391
+ "boardId": "boardId"
17392
+ }
17393
+ },
17394
+ {
17395
+ "name": "get_memberships",
17396
+ "description": "List memberships for the connected board.",
17397
+ "inputSchema": "../../schemas/id_board.json",
17398
+ "handler": "../../handlers/get_board_memberships.js",
17399
+ "scope": "read",
17400
+ "injectFromConfig": {
17401
+ "boardId": "boardId"
17402
+ }
17403
+ },
17404
+ {
17405
+ "name": "get_list",
17406
+ "description": "Fetch a list by id.",
17407
+ "inputSchema": "../../schemas/id_list.json",
17408
+ "handler": "../../handlers/get_list.js",
17409
+ "scope": "read"
17410
+ },
17411
+ {
17412
+ "name": "get_list_cards",
17413
+ "description": "List cards in a list.",
17414
+ "inputSchema": "../../schemas/id_list.json",
17415
+ "handler": "../../handlers/get_list_cards.js",
17416
+ "scope": "read"
17417
+ },
17418
+ {
17419
+ "name": "get_card",
17420
+ "description": "Fetch a card by id.",
17421
+ "inputSchema": "../../schemas/id_card.json",
17422
+ "handler": "../../handlers/get_card.js",
17423
+ "scope": "read"
17424
+ },
17425
+ {
17426
+ "name": "get_card_members",
17427
+ "description": "List members assigned to a card.",
17428
+ "inputSchema": "../../schemas/id_card.json",
17429
+ "handler": "../../handlers/get_card_members.js",
17430
+ "scope": "read"
17431
+ },
17432
+ {
17433
+ "name": "get_card_attachments",
17434
+ "description": "List attachments on a card.",
17435
+ "inputSchema": "../../schemas/id_card.json",
17436
+ "handler": "../../handlers/get_card_attachments.js",
17437
+ "scope": "read"
17438
+ },
17439
+ {
17440
+ "name": "get_card_actions",
17441
+ "description": "List actions (activity) on a card.",
17442
+ "inputSchema": "../../schemas/id_card.json",
17443
+ "handler": "../../handlers/get_card_actions.js",
17444
+ "scope": "read"
17445
+ },
17446
+ {
17447
+ "name": "get_card_checklists",
17448
+ "description": "List checklists on a card.",
17449
+ "inputSchema": "../../schemas/id_card.json",
17450
+ "handler": "../../handlers/get_card_checklists.js",
17451
+ "scope": "read"
17452
+ },
17453
+ {
17454
+ "name": "get_card_custom_field_items",
17455
+ "description": "Get custom field items on a card.",
17456
+ "inputSchema": "../../schemas/id_card.json",
17457
+ "handler": "../../handlers/get_card_custom_field_items.js",
17458
+ "scope": "read"
17459
+ },
17460
+ {
17461
+ "name": "create_card",
17462
+ "description": "Create a new card in a list.",
17463
+ "inputSchema": "../../schemas/create_card.json",
17464
+ "handler": "../../handlers/create_card.js",
17465
+ "scope": "write"
17466
+ },
17467
+ {
17468
+ "name": "update_card",
17469
+ "description": "Update a card's fields (name, desc, due, list, etc).",
17470
+ "inputSchema": "../../schemas/update_card.json",
17471
+ "handler": "../../handlers/update_card.js",
17472
+ "scope": "write"
17473
+ },
17474
+ {
17475
+ "name": "delete_card",
17476
+ "description": "Delete a card.",
17477
+ "inputSchema": "../../schemas/delete_card.json",
17478
+ "handler": "../../handlers/delete_card.js",
17479
+ "scope": "write"
17480
+ },
17481
+ {
17482
+ "name": "move_card_to_list",
17483
+ "description": "Move a card to another list.",
17484
+ "inputSchema": "../../schemas/move_card_to_list.json",
17485
+ "handler": "../../handlers/move_card_to_list.js",
17486
+ "scope": "write"
16145
17487
  },
16146
- "handlerCode": "async (input) => {\n const truncateDesc = (desc) => {\n if (typeof desc !== 'string' || !desc.trim())\n return null\n const oneLine = desc.replace(/\\s+/g, ' ').trim()\n const max = 200\n return oneLine.length <= max ? oneLine : `${oneLine.slice(0, max - 1)}\u2026`\n }\n\n const fields = [\n 'id',\n 'name',\n 'desc',\n 'url',\n 'shortUrl',\n 'shortLink',\n 'dateLastActivity',\n 'idOrganization',\n 'closed',\n 'starred',\n ].join(',')\n const res = await integration.fetch(`/members/me/boards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n if (!Array.isArray(raw))\n return { count: 0, boards: [], note: 'Unexpected response from Trello; expected a list of boards.' }\n\n const boards = raw.map((b) => ({\n id: b.id,\n name: b.name,\n url: b.url || b.shortUrl || (b.shortLink ? `https://trello.com/b/${b.shortLink}` : undefined),\n shortLink: b.shortLink,\n closed: !!b.closed,\n starred: !!b.starred,\n workspaceId: b.idOrganization || null,\n lastActivity: b.dateLastActivity || null,\n descriptionPreview: truncateDesc(b.desc),\n }))\n\n boards.sort((a, b) => {\n if (a.closed !== b.closed)\n return a.closed ? 1 : -1\n if (a.starred !== b.starred)\n return a.starred ? -1 : 1\n return String(a.name || '').localeCompare(String(b.name || ''), undefined, { sensitivity: 'base' })\n })\n\n const open = boards.filter((x) => !x.closed).length\n return {\n count: boards.length,\n openCount: open,\n closedCount: boards.length - open,\n note:\n 'Use `id` as `boardId` in other Trello tools (lists, cards, labels). `url` is the human-facing board link. Closed boards are archived.',\n boards,\n }\n}",
16147
- "scope": "read"
16148
- },
16149
- {
16150
- "name": "get_member_organizations",
16151
- "description": "List organizations (workspaces) for the current member.",
16152
- "inputSchema": {
16153
- "type": "object",
16154
- "properties": {},
16155
- "additionalProperties": false
17488
+ {
17489
+ "name": "add_member_to_card",
17490
+ "description": "Add a member to a card.",
17491
+ "inputSchema": "../../schemas/add_member_to_card.json",
17492
+ "handler": "../../handlers/add_member_to_card.js",
17493
+ "scope": "write"
16156
17494
  },
16157
- "handlerCode": "async (input) => {\n const fields = ['id', 'name', 'displayName', 'desc', 'url'].join(',')\n const res = await integration.fetch(`/members/me/organizations?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const organizations = Array.isArray(raw)\n ? raw.map(org => ({\n id: org.id,\n name: org.name || null,\n displayName: org.displayName || null,\n url: org.url || null,\n descriptionPreview: typeof org.desc === 'string' && org.desc.trim()\n ? (org.desc.trim().length <= 200 ? org.desc.trim() : `${org.desc.trim().slice(0, 199)}...`)\n : null,\n }))\n : []\n return {\n count: organizations.length,\n note: 'Use org id with get_organization for full organization details.',\n organizations,\n }\n}",
16158
- "scope": "read"
17495
+ {
17496
+ "name": "remove_member_from_card",
17497
+ "description": "Remove a member from a card.",
17498
+ "inputSchema": "../../schemas/remove_member_from_card.json",
17499
+ "handler": "../../handlers/remove_member_from_card.js",
17500
+ "scope": "write"
17501
+ },
17502
+ {
17503
+ "name": "add_checklist_to_card",
17504
+ "description": "Create a checklist on a card.",
17505
+ "inputSchema": "../../schemas/add_checklist_to_card.json",
17506
+ "handler": "../../handlers/add_checklist_to_card.js",
17507
+ "scope": "write"
17508
+ },
17509
+ {
17510
+ "name": "create_list",
17511
+ "description": "Create a new list on the connected board.",
17512
+ "inputSchema": "../../schemas/create_list.json",
17513
+ "handler": "../../handlers/create_list.js",
17514
+ "scope": "write",
17515
+ "injectFromConfig": {
17516
+ "idBoard": "boardId"
17517
+ }
17518
+ },
17519
+ {
17520
+ "name": "update_list",
17521
+ "description": "Update a list (name, pos, closed).",
17522
+ "inputSchema": "../../schemas/update_list.json",
17523
+ "handler": "../../handlers/update_list.js",
17524
+ "scope": "write"
17525
+ },
17526
+ {
17527
+ "name": "archive_list",
17528
+ "description": "Archive a list (set closed=true).",
17529
+ "inputSchema": "../../schemas/archive_list.json",
17530
+ "handler": "../../handlers/archive_list.js",
17531
+ "scope": "write"
17532
+ }
17533
+ ]
17534
+ },
17535
+ "prompt": null,
17536
+ "variants": {
17537
+ "variants": {
17538
+ "api_key_token": {
17539
+ "label": "API Key + Token",
17540
+ "schema": {
17541
+ "type": "object",
17542
+ "properties": {
17543
+ "apiKey": {
17544
+ "type": "string",
17545
+ "title": "API Key",
17546
+ "description": "Your Trello API key from https://trello.com/power-ups/admin"
17547
+ },
17548
+ "apiToken": {
17549
+ "type": "string",
17550
+ "title": "API Token",
17551
+ "description": `Your Trello API token ("token" param). Generate one via Trello's authorize flow.`
17552
+ }
17553
+ },
17554
+ "required": [
17555
+ "apiKey",
17556
+ "apiToken"
17557
+ ],
17558
+ "additionalProperties": false
17559
+ },
17560
+ "injection": {
17561
+ "query": {
17562
+ "key": "{{apiKey}}",
17563
+ "token": "{{apiToken}}"
17564
+ }
17565
+ },
17566
+ "healthCheck": {
17567
+ "path": "/members/me"
17568
+ }
17569
+ }
16159
17570
  },
17571
+ "default": "api_key_token"
17572
+ },
17573
+ "hint": "1. Go to `https://trello.com/power-ups/admin`\n2. Create a new app\n3. Navigate to **API Key** and copy your API key\n4. Click **Generate a Token** and copy the token value",
17574
+ "hintsByVariant": {},
17575
+ "tools": [
16160
17576
  {
16161
17577
  "name": "get_board",
16162
- "description": "Fetch a board by id.",
17578
+ "description": "Fetch the connected board.",
16163
17579
  "inputSchema": {
16164
17580
  "type": "object",
16165
17581
  "properties": {
@@ -16173,11 +17589,14 @@ const GENERATED_INTEGRATIONS = {
16173
17589
  "additionalProperties": false
16174
17590
  },
16175
17591
  "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}`)\n return await res.json()\n}",
16176
- "scope": "read"
17592
+ "scope": "read",
17593
+ "injectFromConfig": {
17594
+ "boardId": "boardId"
17595
+ }
16177
17596
  },
16178
17597
  {
16179
- "name": "get_board_lists",
16180
- "description": "List lists on a board.",
17598
+ "name": "get_lists",
17599
+ "description": "List lists on the connected board.",
16181
17600
  "inputSchema": {
16182
17601
  "type": "object",
16183
17602
  "properties": {
@@ -16191,11 +17610,14 @@ const GENERATED_INTEGRATIONS = {
16191
17610
  "additionalProperties": false
16192
17611
  },
16193
17612
  "handlerCode": "async (input) => {\n const fields = ['id', 'name', 'idBoard', 'closed', 'pos', 'softLimit'].join(',')\n const res = await integration.fetch(`/boards/${input.boardId}/lists?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const lists = Array.isArray(raw)\n ? raw.map(list => ({\n id: list.id,\n name: list.name,\n idBoard: list.idBoard || null,\n closed: !!list.closed,\n position: list.pos ?? null,\n softLimit: typeof list.softLimit === 'number' ? list.softLimit : null,\n }))\n : []\n return {\n boardId: input.boardId,\n count: lists.length,\n note: 'Use list id with get_list for full list details.',\n lists,\n }\n}",
16194
- "scope": "read"
17613
+ "scope": "read",
17614
+ "injectFromConfig": {
17615
+ "boardId": "boardId"
17616
+ }
16195
17617
  },
16196
17618
  {
16197
- "name": "get_board_cards",
16198
- "description": "List cards on a board.",
17619
+ "name": "get_cards",
17620
+ "description": "List cards on the connected board.",
16199
17621
  "inputSchema": {
16200
17622
  "type": "object",
16201
17623
  "properties": {
@@ -16209,11 +17631,14 @@ const GENERATED_INTEGRATIONS = {
16209
17631
  "additionalProperties": false
16210
17632
  },
16211
17633
  "handlerCode": "async (input) => {\n const fields = [\n 'id',\n 'name',\n 'desc',\n 'idBoard',\n 'idList',\n 'shortLink',\n 'shortUrl',\n 'url',\n 'closed',\n 'due',\n 'dateLastActivity',\n 'labels',\n 'pos',\n ].join(',')\n const res = await integration.fetch(`/boards/${input.boardId}/cards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const cards = Array.isArray(raw)\n ? raw.map(card => ({\n id: card.id,\n name: card.name,\n idBoard: card.idBoard || null,\n idList: card.idList || null,\n url: card.url || card.shortUrl || (card.shortLink ? `https://trello.com/c/${card.shortLink}` : null),\n shortLink: card.shortLink || null,\n closed: !!card.closed,\n due: card.due || null,\n lastActivity: card.dateLastActivity || null,\n position: card.pos ?? null,\n labels: Array.isArray(card.labels)\n ? card.labels.map(label => ({ id: label.id, name: label.name || null, color: label.color || null }))\n : [],\n descriptionPreview: typeof card.desc === 'string' && card.desc.trim()\n ? (card.desc.trim().length <= 200 ? card.desc.trim() : `${card.desc.trim().slice(0, 199)}...`)\n : null,\n }))\n : []\n return {\n boardId: input.boardId,\n count: cards.length,\n note: 'Use card id with get_card for full card details.',\n cards,\n }\n}",
16212
- "scope": "read"
17634
+ "scope": "read",
17635
+ "injectFromConfig": {
17636
+ "boardId": "boardId"
17637
+ }
16213
17638
  },
16214
17639
  {
16215
- "name": "get_board_members",
16216
- "description": "List members on a board.",
17640
+ "name": "get_members",
17641
+ "description": "List members on the connected board.",
16217
17642
  "inputSchema": {
16218
17643
  "type": "object",
16219
17644
  "properties": {
@@ -16227,11 +17652,14 @@ const GENERATED_INTEGRATIONS = {
16227
17652
  "additionalProperties": false
16228
17653
  },
16229
17654
  "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/members`)\n return await res.json()\n}",
16230
- "scope": "read"
17655
+ "scope": "read",
17656
+ "injectFromConfig": {
17657
+ "boardId": "boardId"
17658
+ }
16231
17659
  },
16232
17660
  {
16233
- "name": "get_board_labels",
16234
- "description": "List labels on a board.",
17661
+ "name": "get_labels",
17662
+ "description": "List labels on the connected board.",
16235
17663
  "inputSchema": {
16236
17664
  "type": "object",
16237
17665
  "properties": {
@@ -16245,11 +17673,14 @@ const GENERATED_INTEGRATIONS = {
16245
17673
  "additionalProperties": false
16246
17674
  },
16247
17675
  "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/labels`)\n return await res.json()\n}",
16248
- "scope": "read"
17676
+ "scope": "read",
17677
+ "injectFromConfig": {
17678
+ "boardId": "boardId"
17679
+ }
16249
17680
  },
16250
17681
  {
16251
- "name": "get_board_custom_fields",
16252
- "description": "List custom fields on a board.",
17682
+ "name": "get_custom_fields",
17683
+ "description": "List custom fields on the connected board.",
16253
17684
  "inputSchema": {
16254
17685
  "type": "object",
16255
17686
  "properties": {
@@ -16263,11 +17694,14 @@ const GENERATED_INTEGRATIONS = {
16263
17694
  "additionalProperties": false
16264
17695
  },
16265
17696
  "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/customFields`)\n return await res.json()\n}",
16266
- "scope": "read"
17697
+ "scope": "read",
17698
+ "injectFromConfig": {
17699
+ "boardId": "boardId"
17700
+ }
16267
17701
  },
16268
17702
  {
16269
- "name": "get_board_memberships",
16270
- "description": "List memberships for a board.",
17703
+ "name": "get_memberships",
17704
+ "description": "List memberships for the connected board.",
16271
17705
  "inputSchema": {
16272
17706
  "type": "object",
16273
17707
  "properties": {
@@ -16281,7 +17715,10 @@ const GENERATED_INTEGRATIONS = {
16281
17715
  "additionalProperties": false
16282
17716
  },
16283
17717
  "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${input.boardId}/memberships`)\n return await res.json()\n}",
16284
- "scope": "read"
17718
+ "scope": "read",
17719
+ "injectFromConfig": {
17720
+ "boardId": "boardId"
17721
+ }
16285
17722
  },
16286
17723
  {
16287
17724
  "name": "get_list",
@@ -16415,135 +17852,17 @@ const GENERATED_INTEGRATIONS = {
16415
17852
  "inputSchema": {
16416
17853
  "type": "object",
16417
17854
  "properties": {
16418
- "cardId": {
16419
- "type": "string"
16420
- }
16421
- },
16422
- "required": [
16423
- "cardId"
16424
- ],
16425
- "additionalProperties": false
16426
- },
16427
- "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}/customFieldItems`)\n return await res.json()\n}",
16428
- "scope": "read"
16429
- },
16430
- {
16431
- "name": "get_organization",
16432
- "description": "Fetch an organization (workspace) by id.",
16433
- "inputSchema": {
16434
- "type": "object",
16435
- "properties": {
16436
- "orgId": {
16437
- "type": "string"
16438
- }
16439
- },
16440
- "required": [
16441
- "orgId"
16442
- ],
16443
- "additionalProperties": false
16444
- },
16445
- "handlerCode": "async (input) => {\n const res = await integration.fetch(`/organizations/${input.orgId}`)\n return await res.json()\n}",
16446
- "scope": "read"
16447
- },
16448
- {
16449
- "name": "get_organization_boards",
16450
- "description": "List boards in an organization (workspace).",
16451
- "inputSchema": {
16452
- "type": "object",
16453
- "properties": {
16454
- "orgId": {
16455
- "type": "string"
16456
- }
16457
- },
16458
- "required": [
16459
- "orgId"
16460
- ],
16461
- "additionalProperties": false
16462
- },
16463
- "handlerCode": "async (input) => {\n const fields = [\n 'id',\n 'name',\n 'desc',\n 'url',\n 'shortUrl',\n 'shortLink',\n 'dateLastActivity',\n 'idOrganization',\n 'closed',\n 'starred',\n ].join(',')\n const res = await integration.fetch(`/organizations/${input.orgId}/boards?fields=${encodeURIComponent(fields)}`)\n const raw = await res.json()\n const boards = Array.isArray(raw)\n ? raw.map(b => ({\n id: b.id,\n name: b.name,\n url: b.url || b.shortUrl || (b.shortLink ? `https://trello.com/b/${b.shortLink}` : null),\n shortLink: b.shortLink || null,\n closed: !!b.closed,\n starred: !!b.starred,\n workspaceId: b.idOrganization || null,\n lastActivity: b.dateLastActivity || null,\n descriptionPreview: typeof b.desc === 'string' && b.desc.trim()\n ? (b.desc.trim().length <= 200 ? b.desc.trim() : `${b.desc.trim().slice(0, 199)}...`)\n : null,\n }))\n : []\n return {\n orgId: input.orgId,\n count: boards.length,\n note: 'Use board id with get_board for full board details.',\n boards,\n }\n}",
16464
- "scope": "read"
16465
- },
16466
- {
16467
- "name": "search",
16468
- "description": "Search across boards, cards, and members.",
16469
- "inputSchema": {
16470
- "type": "object",
16471
- "properties": {
16472
- "query": {
16473
- "type": "string"
16474
- }
16475
- },
16476
- "required": [
16477
- "query"
16478
- ],
16479
- "additionalProperties": false
16480
- },
16481
- "handlerCode": "async (input) => {\n const params = new URLSearchParams({ query: input.query })\n const res = await integration.fetch(`/search?${params.toString()}`)\n const data = await res.json()\n\n const boards = Array.isArray(data?.boards)\n ? data.boards.map(board => ({\n id: board.id,\n name: board.name,\n shortLink: board.shortLink || null,\n url: board.url || (board.shortLink ? `https://trello.com/b/${board.shortLink}` : null),\n closed: !!board.closed,\n }))\n : []\n\n const cards = Array.isArray(data?.cards)\n ? data.cards.map(card => ({\n id: card.id,\n name: card.name,\n idBoard: card.idBoard || null,\n idList: card.idList || null,\n shortLink: card.shortLink || null,\n url: card.url || card.shortUrl || (card.shortLink ? `https://trello.com/c/${card.shortLink}` : null),\n closed: !!card.closed,\n }))\n : []\n\n return {\n query: input.query,\n count: boards.length + cards.length,\n boardCount: boards.length,\n cardCount: cards.length,\n note: 'Use get_board with board id or get_card with card id for full details.',\n boards,\n cards,\n }\n}",
16482
- "scope": "read"
16483
- },
16484
- {
16485
- "name": "create_board",
16486
- "description": "Create a new board.",
16487
- "inputSchema": {
16488
- "$schema": "http://json-schema.org/draft-07/schema#",
16489
- "type": "object",
16490
- "required": [
16491
- "name"
16492
- ],
16493
- "additionalProperties": false,
16494
- "properties": {
16495
- "name": {
16496
- "type": "string"
16497
- },
16498
- "defaultLists": {
16499
- "type": "boolean",
16500
- "description": "Create the default lists on the board (default: true)"
16501
- },
16502
- "desc": {
16503
- "type": "string"
16504
- }
16505
- }
16506
- },
16507
- "handlerCode": "async (input) => {\n const params = new URLSearchParams()\n params.set('name', input.name)\n if (input.defaultLists !== undefined && input.defaultLists !== null)\n params.set('defaultLists', String(Boolean(input.defaultLists)))\n if (input.desc !== undefined && input.desc !== null)\n params.set('desc', String(input.desc))\n const res = await integration.fetch(`/boards?${params.toString()}`, { method: 'POST' })\n return await res.json()\n}",
16508
- "scope": "write"
16509
- },
16510
- {
16511
- "name": "close_board",
16512
- "description": "Close a board (set closed=true).",
16513
- "inputSchema": {
16514
- "$schema": "http://json-schema.org/draft-07/schema#",
16515
- "type": "object",
16516
- "required": [
16517
- "boardId"
16518
- ],
16519
- "additionalProperties": false,
16520
- "properties": {
16521
- "boardId": {
16522
- "type": "string"
16523
- }
16524
- }
16525
- },
16526
- "handlerCode": "async (input) => {\n const params = new URLSearchParams({ closed: 'true' })\n const res = await integration.fetch(`/boards/${encodeURIComponent(input.boardId)}?${params.toString()}`, { method: 'PUT' })\n return await res.json()\n}",
16527
- "scope": "write"
16528
- },
16529
- {
16530
- "name": "delete_board",
16531
- "description": "Permanently delete a closed board.",
16532
- "inputSchema": {
16533
- "$schema": "http://json-schema.org/draft-07/schema#",
16534
- "type": "object",
16535
- "required": [
16536
- "boardId"
16537
- ],
16538
- "additionalProperties": false,
16539
- "properties": {
16540
- "boardId": {
17855
+ "cardId": {
16541
17856
  "type": "string"
16542
17857
  }
16543
- }
17858
+ },
17859
+ "required": [
17860
+ "cardId"
17861
+ ],
17862
+ "additionalProperties": false
16544
17863
  },
16545
- "handlerCode": "async (input) => {\n const res = await integration.fetch(`/boards/${encodeURIComponent(input.boardId)}`, { method: 'DELETE' })\n if (res.status === 204)\n return { success: true, status: 204 }\n // Trello sometimes returns JSON on delete failures.\n try {\n return await res.json()\n }\n catch {\n return { success: res.ok, status: res.status }\n }\n}",
16546
- "scope": "write"
17864
+ "handlerCode": "async (input) => {\n const res = await integration.fetch(`/cards/${input.cardId}/customFieldItems`)\n return await res.json()\n}",
17865
+ "scope": "read"
16547
17866
  },
16548
17867
  {
16549
17868
  "name": "create_card",
@@ -16760,7 +18079,7 @@ const GENERATED_INTEGRATIONS = {
16760
18079
  },
16761
18080
  {
16762
18081
  "name": "create_list",
16763
- "description": "Create a new list on a board.",
18082
+ "description": "Create a new list on the connected board.",
16764
18083
  "inputSchema": {
16765
18084
  "$schema": "http://json-schema.org/draft-07/schema#",
16766
18085
  "type": "object",
@@ -16786,7 +18105,10 @@ const GENERATED_INTEGRATIONS = {
16786
18105
  }
16787
18106
  },
16788
18107
  "handlerCode": "async (input) => {\n const params = new URLSearchParams({ idBoard: input.idBoard, name: input.name })\n if (input.pos !== undefined && input.pos !== null)\n params.set('pos', String(input.pos))\n const res = await integration.fetch(`/lists?${params.toString()}`, { method: 'POST' })\n return await res.json()\n}",
16789
- "scope": "write"
18108
+ "scope": "write",
18109
+ "injectFromConfig": {
18110
+ "idBoard": "boardId"
18111
+ }
16790
18112
  },
16791
18113
  {
16792
18114
  "name": "update_list",
@@ -16845,7 +18167,8 @@ const GENERATED_INTEGRATIONS = {
16845
18167
  "handlerCode": "async (input) => {\n const params = new URLSearchParams({ value: 'true' })\n const res = await integration.fetch(`/lists/${encodeURIComponent(input.listId)}/closed?${params.toString()}`, { method: 'PUT' })\n return await res.json()\n}",
16846
18168
  "scope": "write"
16847
18169
  }
16848
- ]
18170
+ ],
18171
+ "variantOwnerType": "trello"
16849
18172
  }
16850
18173
  };
16851
18174
 
@@ -16859,8 +18182,12 @@ function getIntegration(type) {
16859
18182
  function cloneManifest(manifest) {
16860
18183
  return {
16861
18184
  ...manifest,
18185
+ variantConfig: manifest.variantConfig ? JSON.parse(JSON.stringify(manifest.variantConfig)) : void 0,
16862
18186
  toolsets: manifest.toolsets ? { ...manifest.toolsets } : void 0,
16863
- tools: manifest.tools.map((tool) => ({ ...tool }))
18187
+ tools: manifest.tools.map((tool) => ({
18188
+ ...tool,
18189
+ injectFromConfig: tool.injectFromConfig ? { ...tool.injectFromConfig } : void 0
18190
+ }))
16864
18191
  };
16865
18192
  }
16866
18193
  function cloneCredentialVariant(variant) {
@@ -16871,6 +18198,10 @@ function cloneCredentialVariant(variant) {
16871
18198
  headers: ((_a = variant.injection) == null ? void 0 : _a.headers) ? { ...variant.injection.headers } : void 0,
16872
18199
  query: ((_b = variant.injection) == null ? void 0 : _b.query) ? { ...variant.injection.query } : void 0
16873
18200
  },
18201
+ preprocess: typeof variant.preprocess === "object" && variant.preprocess !== null ? {
18202
+ ...variant.preprocess,
18203
+ allowedOrigins: Array.isArray(variant.preprocess.allowedOrigins) ? [...variant.preprocess.allowedOrigins] : void 0
18204
+ } : variant.preprocess,
16874
18205
  healthCheck: "path" in variant.healthCheck ? { ...variant.healthCheck } : { notViable: true }
16875
18206
  };
16876
18207
  }
@@ -16896,6 +18227,25 @@ function cloneCredentialVariantsFile(type, variants) {
16896
18227
  variants: Object.fromEntries(Object.entries(validated.variants).map(([key, value]) => [key, cloneCredentialVariant(value)]))
16897
18228
  };
16898
18229
  }
18230
+ function stripInjectedFieldsFromSchema(inputSchema, injectFromConfig) {
18231
+ const injectedKeys = Object.keys(injectFromConfig != null ? injectFromConfig : {});
18232
+ if (!injectedKeys.length || !inputSchema || typeof inputSchema !== "object" || Array.isArray(inputSchema))
18233
+ return inputSchema;
18234
+ const schema = JSON.parse(JSON.stringify(inputSchema));
18235
+ const properties = schema.properties;
18236
+ if (properties && typeof properties === "object" && !Array.isArray(properties)) {
18237
+ for (const key of injectedKeys)
18238
+ delete properties[key];
18239
+ }
18240
+ if (Array.isArray(schema.required)) {
18241
+ const required = schema.required.filter((key) => !injectedKeys.includes(String(key)));
18242
+ if (required.length)
18243
+ schema.required = required;
18244
+ else
18245
+ delete schema.required;
18246
+ }
18247
+ return schema;
18248
+ }
16899
18249
  function loadIntegrationManifest(type) {
16900
18250
  const entry = getIntegration(type);
16901
18251
  return entry ? cloneManifest(entry.manifest) : null;
@@ -16936,9 +18286,10 @@ function loadIntegrationTools(type, opts) {
16936
18286
  name: tool.name,
16937
18287
  displayName: tool.displayName,
16938
18288
  description: tool.description,
16939
- inputSchema: tool.inputSchema,
18289
+ inputSchema: stripInjectedFieldsFromSchema(tool.inputSchema, tool.injectFromConfig),
16940
18290
  handlerCode: tool.handlerCode,
16941
- utils: tool.utils
18291
+ utils: tool.utils,
18292
+ injectFromConfig: tool.injectFromConfig ? { ...tool.injectFromConfig } : void 0
16942
18293
  };
16943
18294
  if (scope === "write")
16944
18295
  write.push(nextTool);
@@ -16984,8 +18335,9 @@ function loadIntegrationVariants(type) {
16984
18335
  return variants ? cloneCredentialVariantsFile(type, variants) : null;
16985
18336
  }
16986
18337
  function loadIntegrationCredentialConfig(type, variantKey) {
16987
- var _a, _b;
16988
- const file = loadIntegrationVariants(type);
18338
+ var _a, _b, _c, _d;
18339
+ const ownerType = (_b = (_a = getIntegration(type)) == null ? void 0 : _a.variantOwnerType) != null ? _b : type;
18340
+ const file = loadIntegrationVariants(ownerType);
16989
18341
  if (!file)
16990
18342
  return null;
16991
18343
  const key = file.default;
@@ -16998,29 +18350,43 @@ function loadIntegrationCredentialConfig(type, variantKey) {
16998
18350
  schema: variant.schema,
16999
18351
  baseUrlTemplate: typeof variant.baseUrlTemplate === "string" ? variant.baseUrlTemplate : void 0,
17000
18352
  injection: {
17001
- headers: ((_a = variant.injection) == null ? void 0 : _a.headers) || void 0,
17002
- query: ((_b = variant.injection) == null ? void 0 : _b.query) || void 0
18353
+ headers: ((_c = variant.injection) == null ? void 0 : _c.headers) || void 0,
18354
+ query: ((_d = variant.injection) == null ? void 0 : _d.query) || void 0
17003
18355
  },
17004
18356
  preprocess: variant.preprocess,
17005
18357
  healthCheck: cloneCredentialVariant(variant).healthCheck
17006
18358
  };
17007
18359
  }
17008
18360
  function loadIntegrationHint(type, variantKey) {
17009
- var _a;
17010
- const entry = getIntegration(type);
18361
+ var _a, _b, _c;
18362
+ const ownerType = (_b = (_a = getIntegration(type)) == null ? void 0 : _a.variantOwnerType) != null ? _b : type;
18363
+ const entry = getIntegration(ownerType);
17011
18364
  if (!entry)
17012
18365
  return null;
17013
18366
  if (variantKey && entry.hintsByVariant[variantKey])
17014
18367
  return entry.hintsByVariant[variantKey];
17015
- return (_a = entry.hint) != null ? _a : null;
18368
+ return (_c = entry.hint) != null ? _c : null;
17016
18369
  }
17017
18370
  function listIntegrationTypes() {
17018
18371
  return Object.keys(GENERATED_INTEGRATIONS);
17019
18372
  }
17020
18373
  function listIntegrationCatalog() {
17021
- return listIntegrationTypes().map((type) => ({
18374
+ return listIntegrationTypes().filter((type) => !GENERATED_INTEGRATIONS[type].variantOwnerType).map((type) => ({
17022
18375
  type,
17023
- name: GENERATED_INTEGRATIONS[type].manifest.name || type
18376
+ name: GENERATED_INTEGRATIONS[type].manifest.name || type,
18377
+ variants: listIntegrationTypes().filter((candidate) => GENERATED_INTEGRATIONS[candidate].variantOwnerType === type).map((candidate) => {
18378
+ var _a, _b;
18379
+ return {
18380
+ type: candidate,
18381
+ label: GENERATED_INTEGRATIONS[candidate].manifest.variantLabel || candidate,
18382
+ variantConfig: (_b = (_a = GENERATED_INTEGRATIONS[candidate].manifest.variantConfig) == null ? void 0 : _a.map((item) => ({
18383
+ key: item.key,
18384
+ label: item.label,
18385
+ selectionMode: item.selectionMode,
18386
+ hasListHandler: Boolean(item.listHandler)
18387
+ }))) != null ? _b : null
18388
+ };
18389
+ })
17024
18390
  }));
17025
18391
  }
17026
18392
 
@@ -17706,29 +19072,228 @@ function createGetIntegration(integrations, proxy) {
17706
19072
  };
17707
19073
  }
17708
19074
 
19075
+ const EXTRACT_FILE_PY = `#!/usr/bin/env python3
19076
+ import argparse
19077
+ import csv
19078
+ import html
19079
+ import json
19080
+ import mimetypes
19081
+ import re
19082
+ import zipfile
19083
+ from pathlib import Path
19084
+
19085
+ from markitdown import MarkItDown
19086
+
19087
+
19088
+ def collapse_whitespace(value: str) -> str:
19089
+ return re.sub(r"\\s+", " ", value or "").strip()
19090
+
19091
+
19092
+ def join_blocks(blocks):
19093
+ cleaned = [str(block).strip() for block in blocks if str(block or "").strip()]
19094
+ return "\\n\\n".join(cleaned).strip()
19095
+
19096
+
19097
+ MARKITDOWN_EXTENSIONS = {
19098
+ ".pdf",
19099
+ ".doc",
19100
+ ".docx",
19101
+ ".ppt",
19102
+ ".pptx",
19103
+ ".xls",
19104
+ ".xlsx",
19105
+ }
19106
+
19107
+
19108
+ DIRECT_TEXT_EXTENSIONS = {
19109
+ ".txt",
19110
+ ".md",
19111
+ ".mdx",
19112
+ ".rtf",
19113
+ ".log",
19114
+ ".json",
19115
+ ".xml",
19116
+ ".csv",
19117
+ ".yaml",
19118
+ ".yml",
19119
+ ".ini",
19120
+ ".cfg",
19121
+ ".properties",
19122
+ ".html",
19123
+ ".htm",
19124
+ }
19125
+
19126
+
19127
+ def sniff_kind(path: Path) -> str:
19128
+ suffix = path.suffix.lower()
19129
+ if suffix in DIRECT_TEXT_EXTENSIONS | MARKITDOWN_EXTENSIONS:
19130
+ return suffix.lstrip(".")
19131
+
19132
+ mime_guess, _ = mimetypes.guess_type(path.name)
19133
+ if mime_guess == "application/pdf":
19134
+ return "pdf"
19135
+
19136
+ with path.open("rb") as handle:
19137
+ prefix = handle.read(16)
19138
+ if prefix.startswith(b"%PDF"):
19139
+ return "pdf"
19140
+
19141
+ if zipfile.is_zipfile(path):
19142
+ with zipfile.ZipFile(path, "r") as archive:
19143
+ names = set(archive.namelist())
19144
+ if "word/document.xml" in names:
19145
+ return "docx"
19146
+ if "xl/workbook.xml" in names:
19147
+ return "xlsx"
19148
+ if "ppt/presentation.xml" in names:
19149
+ return "pptx"
19150
+
19151
+ return "unknown"
19152
+
19153
+
19154
+ def read_text(path: Path) -> dict:
19155
+ content = path.read_text(encoding="utf-8", errors="replace").strip()
19156
+ return {"kind": "text", "content": content, "metadata": {"filename": path.name}}
19157
+
19158
+
19159
+ def read_markdown(path: Path) -> dict:
19160
+ content = path.read_text(encoding="utf-8", errors="replace").strip()
19161
+ return {"kind": "markdown", "content": content, "metadata": {"filename": path.name}}
19162
+
19163
+
19164
+ def read_json(path: Path) -> dict:
19165
+ raw = path.read_text(encoding="utf-8", errors="replace")
19166
+ try:
19167
+ data = json.loads(raw)
19168
+ content = json.dumps(data, indent=2, ensure_ascii=False)
19169
+ except Exception:
19170
+ content = raw
19171
+ return {"kind": "json", "content": content.strip(), "metadata": {"filename": path.name}}
19172
+
19173
+
19174
+ def read_html(path: Path) -> dict:
19175
+ raw = path.read_text(encoding="utf-8", errors="replace")
19176
+ text = re.sub(r"<script[\\s\\S]*?<\/script>", " ", raw, flags=re.IGNORECASE)
19177
+ text = re.sub(r"<style[\\s\\S]*?</style>", " ", text, flags=re.IGNORECASE)
19178
+ text = re.sub(r"<br\\s*/?>", "\\n", text, flags=re.IGNORECASE)
19179
+ text = re.sub(r"</(p|div|section|article|li|tr|h[1-6])>", "\\n", text, flags=re.IGNORECASE)
19180
+ text = re.sub(r"<[^>]+>", " ", text)
19181
+ text = html.unescape(text)
19182
+ text = re.sub(r"[ \\t]+\\n", "\\n", text)
19183
+ text = re.sub(r"\\n{3,}", "\\n\\n", text).strip()
19184
+ return {"kind": "html", "content": text, "metadata": {"filename": path.name}}
19185
+
19186
+
19187
+ def read_csv_file(path: Path) -> dict:
19188
+ with path.open("r", encoding="utf-8", errors="replace", newline="") as handle:
19189
+ reader = csv.reader(handle)
19190
+ rows = list(reader)
19191
+
19192
+ if not rows:
19193
+ return {"kind": "csv", "content": "", "metadata": {"filename": path.name, "rowCount": 0}}
19194
+
19195
+ header_width = max(len(row) for row in rows)
19196
+ normalized = [row + [""] * (header_width - len(row)) for row in rows]
19197
+ headers = normalized[0]
19198
+ body = normalized[1:]
19199
+
19200
+ md_rows = [
19201
+ f"| {' | '.join(headers)} |",
19202
+ f"| {' | '.join(['---'] * header_width)} |",
19203
+ ]
19204
+ md_rows.extend(f"| {' | '.join(row)} |" for row in body[:200])
19205
+ warnings = []
19206
+ if len(body) > 200:
19207
+ warnings.append("CSV output truncated to first 200 data rows.")
19208
+
19209
+ return {
19210
+ "kind": "csv",
19211
+ "content": "\\n".join(md_rows).strip(),
19212
+ "warnings": warnings or None,
19213
+ "metadata": {"filename": path.name, "rowCount": len(body), "columnCount": header_width},
19214
+ }
19215
+
19216
+
19217
+ def read_with_markitdown(path: Path, kind: str) -> dict:
19218
+ try:
19219
+ result = MarkItDown().convert(str(path))
19220
+ except Exception as exc:
19221
+ raise RuntimeError(f"MarkItDown extraction failed for {path.name}: {exc}") from exc
19222
+
19223
+ content = (getattr(result, "text_content", "") or "").strip()
19224
+ return {
19225
+ "kind": kind,
19226
+ "content": content,
19227
+ "metadata": {"filename": path.name, "parser": "markitdown"},
19228
+ }
19229
+
19230
+
19231
+ def extract(path: Path) -> dict:
19232
+ kind = sniff_kind(path)
19233
+ if kind == "txt":
19234
+ return read_text(path)
19235
+ if kind == "md":
19236
+ return read_markdown(path)
19237
+ if kind == "json":
19238
+ return read_json(path)
19239
+ if kind in {"html", "htm"}:
19240
+ return read_html(path)
19241
+ if kind == "csv":
19242
+ return read_csv_file(path)
19243
+ if path.suffix.lower() in MARKITDOWN_EXTENSIONS or kind in {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx"}:
19244
+ return read_with_markitdown(path, kind)
19245
+
19246
+ raw = path.read_text(encoding="utf-8", errors="replace")
19247
+ return {
19248
+ "kind": "unknown",
19249
+ "content": raw.strip(),
19250
+ "warnings": ["Unknown file type; returned best-effort UTF-8 text decode."],
19251
+ "metadata": {"filename": path.name},
19252
+ }
19253
+
19254
+
19255
+ def main() -> int:
19256
+ parser = argparse.ArgumentParser()
19257
+ parser.add_argument("--input", required=True)
19258
+ parser.add_argument("--output", required=True)
19259
+ args = parser.parse_args()
19260
+
19261
+ input_path = Path(args.input)
19262
+ output_path = Path(args.output)
19263
+
19264
+ result = extract(input_path)
19265
+ output_path.write_text(json.dumps(result, ensure_ascii=False), encoding="utf-8")
19266
+ return 0
19267
+
19268
+
19269
+ if __name__ == "__main__":
19270
+ raise SystemExit(main())
19271
+ `;
19272
+ const EXTRACT_FILE_PY_HASH = "632e2322c14941f8c30b5f60b4c5bb1d773e0d2953fde39a7a16eaf1dbfc21c2";
19273
+
17709
19274
  const execFile$1 = promisify(execFile$2);
17710
- const INSTALL_COMMAND = "pip3 install -r packages/core/src/file-extractor/requirements.txt";
19275
+ const INSTALL_COMMAND = "pip3 install markitdown[all]";
17711
19276
  const DOCKER_HINT = "Run Commandable with Docker to use the preinstalled extraction runtime.";
19277
+ let cachedScriptPath = null;
19278
+ function ensureExtractorScript() {
19279
+ if (cachedScriptPath)
19280
+ return cachedScriptPath;
19281
+ const path = join(tmpdir(), `commandable-extractor-${EXTRACT_FILE_PY_HASH.slice(0, 16)}.py`);
19282
+ if (!existsSync(path))
19283
+ writeFileSync(path, EXTRACT_FILE_PY, "utf8");
19284
+ cachedScriptPath = path;
19285
+ return path;
19286
+ }
17712
19287
  const FILE_PROCESSING_DISABLED_TOOLS = {
17713
- "google-workspace": ["read_file_content"]
19288
+ "google-workspace": ["read_file_content"],
19289
+ sharepoint: ["read_file_content"]
17714
19290
  };
17715
19291
  let capabilityPromise = null;
17716
19292
  function pythonExecutable() {
17717
19293
  return process.env.COMMANDABLE_PYTHON || "python3";
17718
19294
  }
17719
- function extractorScriptCandidates() {
17720
- const cwd = process.cwd();
17721
- return [
17722
- fileURLToPath(new URL("../file-extractor/extract_file.py", globalThis._importMeta_.url)),
17723
- resolve$1(cwd, "packages/core/src/file-extractor/extract_file.py"),
17724
- resolve$1(cwd, "packages/core/dist/file-extractor/extract_file.py"),
17725
- resolve$1(cwd, "node_modules/@commandable/mcp-core/dist/file-extractor/extract_file.py"),
17726
- resolve$1(cwd, "app/.output/server/node_modules/@commandable/mcp-core/dist/file-extractor/extract_file.py")
17727
- ];
17728
- }
17729
19295
  function extractorScriptPath() {
17730
- const candidates = extractorScriptCandidates();
17731
- return candidates.find((path) => existsSync(path)) || candidates[0];
19296
+ return ensureExtractorScript();
17732
19297
  }
17733
19298
  function getFileProcessingMode() {
17734
19299
  const raw = String(process.env.COMMANDABLE_FILE_PROCESSING || "").trim().toLowerCase();
@@ -17766,10 +19331,7 @@ async function probeFileProcessing() {
17766
19331
  if (mode === "off") {
17767
19332
  return buildDisabledCapability("disabled_by_env", "File processing is disabled by COMMANDABLE_FILE_PROCESSING=off.");
17768
19333
  }
17769
- const scriptPath = extractorScriptPath();
17770
- if (!existsSync(scriptPath)) {
17771
- return buildDisabledCapability("extractor_script_missing", `File processing is unavailable because the extractor script was not found at ${scriptPath}.`);
17772
- }
19334
+ ensureExtractorScript();
17773
19335
  try {
17774
19336
  await execFile$1(pythonExecutable(), ["-c", "import markitdown"]);
17775
19337
  return buildEnabledCapability();
@@ -17967,6 +19529,25 @@ function filterBuiltInToolsForOverrides(builtInTools, definitions) {
17967
19529
  const overridingNames = new Set(definitions.map((definition) => definition.name));
17968
19530
  return builtInTools.filter((tool) => !overridingNames.has(tool.name));
17969
19531
  }
19532
+ function buildHandlerWrapper(integrationId, handlerCode, integrationConfig, injectFromConfig) {
19533
+ const serializedConfig = JSON.stringify(integrationConfig != null ? integrationConfig : {});
19534
+ const serializedMapping = JSON.stringify(injectFromConfig != null ? injectFromConfig : {});
19535
+ return `async (input) => {
19536
+ const integration = getIntegration('${integrationId}');
19537
+ const __inner = ${handlerCode};
19538
+ const __config = ${serializedConfig};
19539
+ const __mapping = ${serializedMapping};
19540
+ const __baseInput = (input && typeof input === 'object' && !Array.isArray(input)) ? input : {};
19541
+ const __injected = {};
19542
+ for (const [targetKey, configKey] of Object.entries(__mapping)) {
19543
+ const value = __config?.[configKey];
19544
+ if (value === undefined || value === null)
19545
+ throw new Error(\`Missing integration config value '\${configKey}' required for tool input '\${targetKey}'.\`);
19546
+ __injected[targetKey] = value;
19547
+ }
19548
+ return await __inner({ ...__baseInput, ...__injected });
19549
+ }`;
19550
+ }
17970
19551
  function buildToolsByIntegration(spaceId, integrations, proxy, opts = {}) {
17971
19552
  var _a, _b, _c, _d;
17972
19553
  const { requireWriteConfirmation = false, integrationsRef, toolDefinitions, utils: injectUtils } = opts;
@@ -17998,11 +19579,7 @@ function buildToolsByIntegration(spaceId, integrations, proxy, opts = {}) {
17998
19579
  const toolName = makeIntegrationToolName(integ.type, t.name, integ.id);
17999
19580
  const description = `[${integ.label} | ${integ.type}] ${t.description}`;
18000
19581
  const extractFileContent = createExtractFileContent(getIntegration, integ.id);
18001
- const wrapper = `async (input) => {
18002
- const integration = getIntegration('${integ.id}');
18003
- const __inner = ${t.handlerCode};
18004
- return await __inner(input);
18005
- }`;
19582
+ const wrapper = buildHandlerWrapper(integ.id, t.handlerCode, integ.config, t.injectFromConfig);
18006
19583
  const utils = injectUtils != null ? injectUtils : buildSandboxUtils(Array.isArray(t.utils) ? t.utils : void 0, { extractFileContent });
18007
19584
  const safeHandler = createSafeHandlerFromString(wrapper, getIntegration, utils);
18008
19585
  return {
@@ -18046,6 +19623,9 @@ function buildToolsByIntegration(spaceId, integrations, proxy, opts = {}) {
18046
19623
  return toolsByIntegration;
18047
19624
  }
18048
19625
 
19626
+ function isHandlerPreprocess(preprocess) {
19627
+ return typeof preprocess === "object" && preprocess !== null && preprocess.type === "handler" && typeof preprocess.handlerCode === "string";
19628
+ }
18049
19629
  function getBuiltInIntegrationTypeConfig(typeSlug) {
18050
19630
  var _a, _b, _c, _d;
18051
19631
  const variantsFile = loadIntegrationVariants(typeSlug);
@@ -18057,8 +19637,9 @@ function getBuiltInIntegrationTypeConfig(typeSlug) {
18057
19637
  const variants = {};
18058
19638
  for (const [key, variant] of Object.entries(variantsFile.variants)) {
18059
19639
  const preprocess = (_c = variant.preprocess) != null ? _c : null;
18060
- if (preprocess !== null && preprocess !== "google_service_account") {
18061
- throw new Error(`Unsupported preprocess '${preprocess}' for built-in integration '${typeSlug}/${key}'. Only 'google_service_account' is allowed.`);
19640
+ const isSupportedHandler = isHandlerPreprocess(preprocess);
19641
+ if (preprocess !== null && preprocess !== "google_service_account" && !isSupportedHandler) {
19642
+ throw new Error(`Unsupported preprocess for built-in integration '${typeSlug}/${key}'.`);
18062
19643
  }
18063
19644
  variants[key] = {
18064
19645
  label: variant.label,
@@ -18069,7 +19650,11 @@ function getBuiltInIntegrationTypeConfig(typeSlug) {
18069
19650
  allowedOrigins: manifestAllowedOrigins,
18070
19651
  healthCheck: (_d = variant.healthCheck) != null ? _d : null,
18071
19652
  hintMarkdown: loadIntegrationHint(typeSlug, key),
18072
- preprocess
19653
+ preprocess: isSupportedHandler ? {
19654
+ type: "handler",
19655
+ handlerCode: preprocess.handlerCode,
19656
+ allowedOrigins: Array.isArray(preprocess.allowedOrigins) ? [...preprocess.allowedOrigins] : null
19657
+ } : preprocess
18073
19658
  };
18074
19659
  }
18075
19660
  return {
@@ -18172,6 +19757,28 @@ function buildCredentialUrl(integrationId) {
18172
19757
  const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : 23432;
18173
19758
  return `http://127.0.0.1:${port}/integrations/${encodeURIComponent(integrationId)}`;
18174
19759
  }
19760
+ function decodeJwtPayloadForDebug(token) {
19761
+ try {
19762
+ const parts = token.split(".");
19763
+ if (parts.length < 2 || !parts[1])
19764
+ return null;
19765
+ let b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
19766
+ const pad = b64.length % 4 === 0 ? "" : "=".repeat(4 - b64.length % 4);
19767
+ b64 = b64 + pad;
19768
+ const json = Buffer$1.from(b64, "base64").toString("utf8");
19769
+ const payload = JSON.parse(json);
19770
+ return {
19771
+ aud: payload.aud,
19772
+ tid: payload.tid,
19773
+ appid: payload.appid,
19774
+ roles: payload.roles,
19775
+ scp: payload.scp,
19776
+ idtyp: payload.idtyp
19777
+ };
19778
+ } catch {
19779
+ return null;
19780
+ }
19781
+ }
18175
19782
  function isAbsoluteHttpUrl(value) {
18176
19783
  try {
18177
19784
  const url = new URL(value);
@@ -18232,71 +19839,163 @@ function resolveRelativeBaseUrl(provider, baseUrl, rawPath) {
18232
19839
  return "https://slides.googleapis.com/v1";
18233
19840
  return baseUrl;
18234
19841
  }
19842
+ function joinWithoutDuplicateSegments(baseUrl, rawPath) {
19843
+ let pathOnly = rawPath || "";
19844
+ let queryPart = "";
19845
+ const qIndex = pathOnly.indexOf("?");
19846
+ if (qIndex >= 0) {
19847
+ queryPart = pathOnly.slice(qIndex + 1);
19848
+ pathOnly = pathOnly.slice(0, qIndex);
19849
+ }
19850
+ try {
19851
+ const base = new URL(baseUrl);
19852
+ const baseSegs = base.pathname.split("/").filter(Boolean);
19853
+ const pathSegs = (pathOnly || "/").split("/").filter(Boolean);
19854
+ let overlap = 0;
19855
+ const maxK = Math.min(baseSegs.length, pathSegs.length);
19856
+ for (let k = maxK; k >= 1; k--) {
19857
+ let ok = true;
19858
+ for (let i = 0; i < k; i++) {
19859
+ if (baseSegs[baseSegs.length - k + i] !== pathSegs[i]) {
19860
+ ok = false;
19861
+ break;
19862
+ }
19863
+ }
19864
+ if (ok) {
19865
+ overlap = k;
19866
+ break;
19867
+ }
19868
+ }
19869
+ const normalizedPath = `/${[...baseSegs, ...pathSegs.slice(overlap)].join("/")}`;
19870
+ const baseOrigin = base.origin;
19871
+ const urlNoQuery = `${baseOrigin}${normalizedPath}`;
19872
+ return queryPart ? `${urlNoQuery}?${queryPart}` : urlNoQuery;
19873
+ } catch {
19874
+ const cleanedBase = baseUrl.replace(/\/+$/, "");
19875
+ const cleanedPath = `/${(pathOnly || "").replace(/^\/+/, "")}`;
19876
+ const baseParts = cleanedBase.split("/").filter(Boolean);
19877
+ const pathParts = cleanedPath.split("/").filter(Boolean);
19878
+ let overlap = 0;
19879
+ const maxK = Math.min(baseParts.length, pathParts.length);
19880
+ for (let k = maxK; k >= 1; k--) {
19881
+ let ok = true;
19882
+ for (let i = 0; i < k; i++) {
19883
+ if (baseParts[baseParts.length - k + i] !== pathParts[i]) {
19884
+ ok = false;
19885
+ break;
19886
+ }
19887
+ }
19888
+ if (ok) {
19889
+ overlap = k;
19890
+ break;
19891
+ }
19892
+ }
19893
+ const joined = `/${[...baseParts, ...pathParts.slice(overlap)].join("/")}`;
19894
+ return queryPart ? `${joined}?${queryPart}` : joined;
19895
+ }
19896
+ }
19897
+ function stableStringify(value) {
19898
+ if (value === null || typeof value !== "object")
19899
+ return JSON.stringify(value);
19900
+ if (Array.isArray(value))
19901
+ return `[${value.map(stableStringify).join(",")}]`;
19902
+ const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, innerValue]) => `${JSON.stringify(key)}:${stableStringify(innerValue)}`);
19903
+ return `{${entries.join(",")}}`;
19904
+ }
19905
+ const preprocessResultCache = /* @__PURE__ */ new Map();
19906
+ function isHandlerCredentialPreprocess(preprocess) {
19907
+ return typeof preprocess === "object" && preprocess !== null && preprocess.type === "handler" && typeof preprocess.handlerCode === "string";
19908
+ }
19909
+ function getPreprocessCacheKey(provider, variantKey, creds) {
19910
+ return createHash("sha256").update(`${provider}:${variantKey}:${stableStringify(creds)}`).digest("hex");
19911
+ }
19912
+ function getExpiresAtMs(result, now) {
19913
+ var _a;
19914
+ const rawExpiresIn = (_a = result.expiresIn) != null ? _a : result.expires_in;
19915
+ const expiresIn = typeof rawExpiresIn === "number" ? rawExpiresIn : typeof rawExpiresIn === "string" && rawExpiresIn.trim() ? Number(rawExpiresIn) : NaN;
19916
+ if (Number.isFinite(expiresIn) && expiresIn > 0)
19917
+ return now + expiresIn * 1e3;
19918
+ return now + 55 * 6e4;
19919
+ }
19920
+ function normalizeRequestInit(init = {}) {
19921
+ const preparedInit = { ...init };
19922
+ if (preparedInit.body !== void 0 && typeof preparedInit.body !== "string" && !(preparedInit.body instanceof URLSearchParams) && !(preparedInit.body instanceof FormData) && !(preparedInit.body instanceof Blob) && !(preparedInit.body instanceof ArrayBuffer)) {
19923
+ preparedInit.body = JSON.stringify(preparedInit.body);
19924
+ preparedInit.headers = {
19925
+ "Content-Type": "application/json",
19926
+ ...preparedInit.headers
19927
+ };
19928
+ } else if (preparedInit.body instanceof URLSearchParams) {
19929
+ preparedInit.body = preparedInit.body.toString();
19930
+ preparedInit.headers = {
19931
+ "Content-Type": "application/x-www-form-urlencoded",
19932
+ ...preparedInit.headers
19933
+ };
19934
+ }
19935
+ return preparedInit;
19936
+ }
19937
+ async function runSandboxCredentialPreprocess(params) {
19938
+ var _a, _b;
19939
+ const { provider, variantKey, preprocess, creds, baseUrl, allowedOrigins } = params;
19940
+ const cacheKey = getPreprocessCacheKey(provider, variantKey, creds);
19941
+ const existing = preprocessResultCache.get(cacheKey);
19942
+ const now = Date.now();
19943
+ if (existing && existing.expiresAtMs - now > 6e4) {
19944
+ Object.assign(creds, existing.data);
19945
+ return;
19946
+ }
19947
+ const tokenFetch = async (path, init = {}) => {
19948
+ let finalUrl;
19949
+ if (isAbsoluteHttpUrl(path)) {
19950
+ assertAbsoluteUrlIsAllowed(path, baseUrl, allowedOrigins);
19951
+ finalUrl = path;
19952
+ } else {
19953
+ finalUrl = joinWithoutDuplicateSegments(baseUrl, path);
19954
+ }
19955
+ const preparedInit = normalizeRequestInit(init);
19956
+ return await fetch(finalUrl, {
19957
+ ...preparedInit,
19958
+ method: preparedInit.method || "GET"
19959
+ });
19960
+ };
19961
+ const wrapper = `async (input) => {
19962
+ const __inner = ${preprocess.handlerCode};
19963
+ return await __inner(input, utils)
19964
+ }`;
19965
+ const safeHandler = createSafeHandlerFromString(wrapper, () => ({}), { tokenFetch });
19966
+ const res = await safeHandler(creds);
19967
+ if (!res.success)
19968
+ throw new HttpError(400, `Credential preprocess failed for '${provider}': ${String(((_a = res.result) == null ? void 0 : _a.message) || res.result || "Unknown error")}`);
19969
+ const result = res.result;
19970
+ if (!result || typeof result !== "object" || Array.isArray(result))
19971
+ throw new HttpError(400, `Credential preprocess for '${provider}' must return an object.`);
19972
+ if (typeof result.token !== "string" || !result.token.trim())
19973
+ throw new HttpError(400, `Credential preprocess for '${provider}' must return a non-empty 'token' string.`);
19974
+ Object.assign(creds, result);
19975
+ preprocessResultCache.set(cacheKey, {
19976
+ data: { ...result },
19977
+ expiresAtMs: getExpiresAtMs(result, now)
19978
+ });
19979
+ if (provider === "sharepoint") {
19980
+ const t = result == null ? void 0 : result.token;
19981
+ const tokenStr = typeof t === "string" ? t : "";
19982
+ const claims = tokenStr ? decodeJwtPayloadForDebug(tokenStr) : null;
19983
+ const roles = claims == null ? void 0 : claims.roles;
19984
+ const rolesList = Array.isArray(roles) ? roles.map((r) => String(r).slice(0, 80)) : [];
19985
+ fetch("http://127.0.0.1:7886/ingest/d4127044-8bb5-4b15-95f1-be96d51d67ea", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "797117" }, body: JSON.stringify({ sessionId: "797117", location: "proxy.ts:runSandboxCredentialPreprocess", message: "sharepoint preprocess ok", data: { variantKey, tokenLen: typeof t === "string" ? t.length : 0, expiresIn: (_b = result == null ? void 0 : result.expiresIn) != null ? _b : result == null ? void 0 : result.expires_in, tokenAud: claims == null ? void 0 : claims.aud, tokenTid: claims == null ? void 0 : claims.tid, tokenAppId: claims == null ? void 0 : claims.appid, tokenIdtyp: claims == null ? void 0 : claims.idtyp, rolesCount: rolesList.length, rolesSample: rolesList.slice(0, 12), hasScp: typeof (claims == null ? void 0 : claims.scp) === "string" && String(claims.scp).length > 0, jwtDecodeOk: !!claims }, timestamp: Date.now(), hypothesisId: "H6" }) }).catch(() => {
19986
+ });
19987
+ }
19988
+ }
18235
19989
  class IntegrationProxy {
18236
19990
  constructor(opts = {}) {
18237
19991
  __publicField$4(this, "opts");
18238
19992
  this.opts = opts;
18239
19993
  }
18240
19994
  async call(integration, path, init = {}) {
18241
- var _a, _b, _c, _d, _e, _f, _g;
19995
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
18242
19996
  const { type: provider } = integration;
18243
19997
  if (!provider || !path)
18244
19998
  throw new HttpError(400, "provider and path are required.");
18245
- const joinWithoutDuplicateSegments = (baseUrl, rawPath) => {
18246
- let pathOnly = rawPath || "";
18247
- let queryPart = "";
18248
- const qIndex = pathOnly.indexOf("?");
18249
- if (qIndex >= 0) {
18250
- queryPart = pathOnly.slice(qIndex + 1);
18251
- pathOnly = pathOnly.slice(0, qIndex);
18252
- }
18253
- try {
18254
- const base = new URL(baseUrl);
18255
- const baseSegs = base.pathname.split("/").filter(Boolean);
18256
- const pathSegs = (pathOnly || "/").split("/").filter(Boolean);
18257
- let overlap = 0;
18258
- const maxK = Math.min(baseSegs.length, pathSegs.length);
18259
- for (let k = maxK; k >= 1; k--) {
18260
- let ok = true;
18261
- for (let i = 0; i < k; i++) {
18262
- if (baseSegs[baseSegs.length - k + i] !== pathSegs[i]) {
18263
- ok = false;
18264
- break;
18265
- }
18266
- }
18267
- if (ok) {
18268
- overlap = k;
18269
- break;
18270
- }
18271
- }
18272
- const normalizedPath = `/${[...baseSegs, ...pathSegs.slice(overlap)].join("/")}`;
18273
- const baseOrigin = base.origin;
18274
- const urlNoQuery = `${baseOrigin}${normalizedPath}`;
18275
- return queryPart ? `${urlNoQuery}?${queryPart}` : urlNoQuery;
18276
- } catch {
18277
- const cleanedBase = baseUrl.replace(/\/+$/, "");
18278
- const cleanedPath = `/${(pathOnly || "").replace(/^\/+/, "")}`;
18279
- const baseParts = cleanedBase.split("/").filter(Boolean);
18280
- const pathParts = cleanedPath.split("/").filter(Boolean);
18281
- let overlap = 0;
18282
- const maxK = Math.min(baseParts.length, pathParts.length);
18283
- for (let k = maxK; k >= 1; k--) {
18284
- let ok = true;
18285
- for (let i = 0; i < k; i++) {
18286
- if (baseParts[baseParts.length - k + i] !== pathParts[i]) {
18287
- ok = false;
18288
- break;
18289
- }
18290
- }
18291
- if (ok) {
18292
- overlap = k;
18293
- break;
18294
- }
18295
- }
18296
- const joined = `/${[...baseParts, ...pathParts.slice(overlap)].join("/")}`;
18297
- return queryPart ? `${joined}?${queryPart}` : joined;
18298
- }
18299
- };
18300
19999
  const usesCredentials = integration.connectionMethod === "credentials";
18301
20000
  if (usesCredentials) {
18302
20001
  if (!this.opts.credentialStore)
@@ -18387,6 +20086,18 @@ class IntegrationProxy {
18387
20086
  throw new HttpError(400, `Missing OAuth scopes for Google integration '${provider}'.`);
18388
20087
  const token = await getGoogleAccessToken({ serviceAccountJson, scopes, subject });
18389
20088
  creds.token = token;
20089
+ } else if (isHandlerCredentialPreprocess(typeConfig.preprocess)) {
20090
+ await runSandboxCredentialPreprocess({
20091
+ provider,
20092
+ variantKey,
20093
+ preprocess: typeConfig.preprocess,
20094
+ creds,
20095
+ baseUrl,
20096
+ allowedOrigins: [
20097
+ ...typeConfig.allowedOrigins,
20098
+ ...(_f = typeConfig.preprocess.allowedOrigins) != null ? _f : []
20099
+ ]
20100
+ });
18390
20101
  }
18391
20102
  const resolvedHeaders = {};
18392
20103
  const resolvedQuery = new URLSearchParams();
@@ -18399,9 +20110,9 @@ class IntegrationProxy {
18399
20110
  const token = Buffer$1.from(`${username}:${password}`).toString("base64");
18400
20111
  resolvedHeaders.Authorization = `Basic ${token}`;
18401
20112
  } else {
18402
- for (const [k, v] of Object.entries(((_f = typeConfig.auth.injection) == null ? void 0 : _f.headers) || {}))
20113
+ for (const [k, v] of Object.entries(((_g = typeConfig.auth.injection) == null ? void 0 : _g.headers) || {}))
18403
20114
  resolvedHeaders[k] = resolveTemplate(v);
18404
- for (const [k, v] of Object.entries(((_g = typeConfig.auth.injection) == null ? void 0 : _g.query) || {}))
20115
+ for (const [k, v] of Object.entries(((_h = typeConfig.auth.injection) == null ? void 0 : _h.query) || {}))
18405
20116
  resolvedQuery.set(k, resolveTemplate(v));
18406
20117
  }
18407
20118
  let finalUrl;
@@ -18414,14 +20125,7 @@ class IntegrationProxy {
18414
20125
  const queryString = resolvedQuery.toString();
18415
20126
  if (queryString)
18416
20127
  finalUrl = finalUrl + (finalUrl.includes("?") ? "&" : "?") + queryString;
18417
- const preparedInit = { ...init };
18418
- if (preparedInit.body !== void 0 && typeof preparedInit.body !== "string") {
18419
- preparedInit.body = JSON.stringify(preparedInit.body);
18420
- preparedInit.headers = {
18421
- "Content-Type": "application/json",
18422
- ...preparedInit.headers
18423
- };
18424
- }
20128
+ const preparedInit = normalizeRequestInit(init);
18425
20129
  const redact = (s) => {
18426
20130
  let out = s;
18427
20131
  for (const val of Object.values(creds)) {
@@ -18430,6 +20134,18 @@ class IntegrationProxy {
18430
20134
  }
18431
20135
  return out;
18432
20136
  };
20137
+ if (provider === "sharepoint") {
20138
+ const auth = resolvedHeaders.Authorization;
20139
+ const bodyString = typeof preparedInit.body === "string" ? preparedInit.body : "";
20140
+ fetch("http://127.0.0.1:7886/ingest/d4127044-8bb5-4b15-95f1-be96d51d67ea", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "797117" }, body: JSON.stringify({ sessionId: "797117", location: "proxy.ts:before-fetch", message: "sharepoint outgoing", data: { method: preparedInit.method || "GET", pathPreview: String(path).slice(0, 200), finalUrlHost: (() => {
20141
+ try {
20142
+ return new URL(finalUrl).host;
20143
+ } catch {
20144
+ return "invalid-url";
20145
+ }
20146
+ })(), hasAuthHeader: !!auth, authPrefix: auth ? String(auth).slice(0, 8) : "", tokenFieldLen: typeof creds.token === "string" ? creds.token.length : 0, bodyPreview: bodyString.slice(0, 300), bodyHasRegion: bodyString.includes('"region"'), isSearchQuery: String(path).includes("/search/query"), isRegionLookup: String(path).includes("siteCollection/root ne null") }, timestamp: Date.now(), hypothesisId: String(path).includes("/search/query") || String(path).includes("siteCollection/root ne null") ? "H9" : "H1" }) }).catch(() => {
20147
+ });
20148
+ }
18433
20149
  const response = await fetch(finalUrl, {
18434
20150
  ...preparedInit,
18435
20151
  method: preparedInit.method || "GET",
@@ -18445,6 +20161,23 @@ class IntegrationProxy {
18445
20161
  bodyText = contentType.includes("json") ? JSON.stringify(await response.json()) : await response.text();
18446
20162
  } catch {
18447
20163
  }
20164
+ if (provider === "sharepoint") {
20165
+ const auth = resolvedHeaders.Authorization;
20166
+ let graphErrorCode = "";
20167
+ try {
20168
+ const parsed = JSON.parse(bodyText);
20169
+ graphErrorCode = String(((_i = parsed == null ? void 0 : parsed.error) == null ? void 0 : _i.code) || (parsed == null ? void 0 : parsed.error) || "");
20170
+ } catch {
20171
+ }
20172
+ fetch("http://127.0.0.1:7886/ingest/d4127044-8bb5-4b15-95f1-be96d51d67ea", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "797117" }, body: JSON.stringify({ sessionId: "797117", location: "proxy.ts:graph-error", message: "sharepoint graph non-ok", data: { status: response.status, pathPreview: String(path).slice(0, 160), finalUrlHost: (() => {
20173
+ try {
20174
+ return new URL(finalUrl).host;
20175
+ } catch {
20176
+ return "invalid-url";
20177
+ }
20178
+ })(), hasAuthHeader: !!auth, authPrefix: auth ? String(auth).slice(0, 8) : "", graphErrorCode: graphErrorCode.slice(0, 80), bodyPreview: bodyText.slice(0, 220) }, timestamp: Date.now(), hypothesisId: "H2" }) }).catch(() => {
20179
+ });
20180
+ }
18448
20181
  const hint = getErrorHint(response.status, provider, bodyText);
18449
20182
  const hintSuffix = hint ? ` ${hint}` : "";
18450
20183
  const credentialUrl = buildCredentialUrl(integration.id);
@@ -18470,6 +20203,7 @@ const sqliteIntegrations = sqliteTable("integrations", {
18470
20203
  type: text("type").notNull(),
18471
20204
  referenceId: text("reference_id").notNull(),
18472
20205
  label: text("label").notNull(),
20206
+ config: text("config"),
18473
20207
  enabled: integer("enabled").notNull().default(1),
18474
20208
  connectionMethod: text("connection_method"),
18475
20209
  connectionId: text("connection_id"),
@@ -18534,6 +20268,7 @@ const pgIntegrations = pgTable("integrations", {
18534
20268
  type: text$1("type").notNull(),
18535
20269
  referenceId: text$1("reference_id").notNull(),
18536
20270
  label: text$1("label").notNull(),
20271
+ config: jsonb("config"),
18537
20272
  enabled: integer$1("enabled").notNull().default(1),
18538
20273
  connectionMethod: text$1("connection_method"),
18539
20274
  connectionId: text$1("connection_id"),
@@ -18753,24 +20488,60 @@ function createDb(opts = {}) {
18753
20488
  };
18754
20489
  }
18755
20490
 
18756
- function resolveMigrationsFolder(dialect) {
18757
- const cwd = process.cwd();
18758
- const candidates = [
18759
- new URL(`./${dialect}`, new URL("./migrations/", globalThis._importMeta_.url)).pathname,
18760
- resolve$1(cwd, "packages/core/src/db/migrations", dialect),
18761
- resolve$1(cwd, "packages/core/dist/db/migrations", dialect),
18762
- resolve$1(cwd, "node_modules/@commandable/mcp-core/dist/db/migrations", dialect),
18763
- resolve$1(cwd, "app/.output/server/node_modules/@commandable/mcp-core/dist/db/migrations", dialect)
18764
- ];
18765
- return candidates.find((path) => existsSync(join(path, "meta", "_journal.json"))) || candidates[0];
20491
+ const sqliteMigrations = [
20492
+ {
20493
+ "sql": [
20494
+ "CREATE TABLE IF NOT EXISTS `integrations` (\n `id` TEXT PRIMARY KEY NOT NULL,\n `space_id` TEXT,\n `type` TEXT NOT NULL,\n `reference_id` TEXT NOT NULL,\n `label` TEXT NOT NULL,\n `enabled` INTEGER NOT NULL DEFAULT 1,\n `connection_method` TEXT,\n `connection_id` TEXT,\n `credential_id` TEXT,\n `credential_variant` TEXT,\n `enabled_toolsets` TEXT,\n `max_scope` TEXT,\n `disabled_tools` TEXT,\n `health_status` TEXT,\n `health_checked_at` INTEGER,\n `created_at` INTEGER NOT NULL\n);\n",
20495
+ "\nCREATE TABLE IF NOT EXISTS `credentials` (\n `space_id` TEXT NOT NULL,\n `id` TEXT NOT NULL,\n `ciphertext` TEXT NOT NULL,\n `created_at` INTEGER NOT NULL,\n `updated_at` INTEGER NOT NULL,\n PRIMARY KEY(`space_id`, `id`)\n);\n",
20496
+ "\nCREATE TABLE IF NOT EXISTS `api_keys` (\n `id` TEXT PRIMARY KEY NOT NULL,\n `name` TEXT NOT NULL,\n `key_hash` TEXT NOT NULL,\n `scopes_json` TEXT,\n `created_at` INTEGER NOT NULL\n);\n",
20497
+ "\nCREATE TABLE IF NOT EXISTS `users` (\n `id` TEXT PRIMARY KEY NOT NULL,\n `email` TEXT NOT NULL,\n `password_hash` TEXT,\n `created_at` INTEGER NOT NULL\n);\n",
20498
+ "\nCREATE TABLE IF NOT EXISTS `integration_type_configs` (\n `id` TEXT PRIMARY KEY NOT NULL,\n `space_id` TEXT NOT NULL,\n `type_slug` TEXT NOT NULL,\n `label` TEXT NOT NULL,\n `default_variant` TEXT NOT NULL,\n `variants_json` TEXT NOT NULL,\n `created_at` INTEGER NOT NULL,\n `updated_at` INTEGER NOT NULL\n);\n",
20499
+ "\nCREATE UNIQUE INDEX IF NOT EXISTS `integration_type_configs__space_type_slug`\n ON `integration_type_configs`(`space_id`, `type_slug`);\n",
20500
+ "\nCREATE TABLE IF NOT EXISTS `tool_definitions` (\n `id` TEXT PRIMARY KEY NOT NULL,\n `space_id` TEXT NOT NULL,\n `integration_id` TEXT NOT NULL,\n `name` TEXT NOT NULL,\n `display_name` TEXT,\n `description` TEXT NOT NULL,\n `scope` TEXT NOT NULL,\n `input_schema_json` TEXT NOT NULL,\n `handler_code` TEXT NOT NULL,\n `utils_json` TEXT,\n `created_at` INTEGER NOT NULL,\n `updated_at` INTEGER NOT NULL\n);\n",
20501
+ "\nCREATE UNIQUE INDEX IF NOT EXISTS `tool_definitions__space_integration_name`\n ON `tool_definitions`(`space_id`, `integration_id`, `name`);\n"
20502
+ ],
20503
+ "folderMillis": 1741e9,
20504
+ "hash": "1b82ffe7422b8219a0e3ca959469c86eec4ac5f469f32bc4495aa7fe9a2f5946",
20505
+ "bps": true
20506
+ },
20507
+ {
20508
+ "sql": [
20509
+ "ALTER TABLE `integrations` ADD COLUMN `config` TEXT;\n"
20510
+ ],
20511
+ "folderMillis": 1775408012933,
20512
+ "hash": "24c9179ac520a0b3b90bef8e050d720f11596e298f7e7c2c6e70fe80893de8c8",
20513
+ "bps": true
20514
+ }
20515
+ ];
20516
+ const pgMigrations = [
20517
+ {
20518
+ "sql": [
20519
+ 'CREATE TABLE IF NOT EXISTS "integrations" (\n "id" TEXT PRIMARY KEY NOT NULL,\n "space_id" TEXT,\n "type" TEXT NOT NULL,\n "reference_id" TEXT NOT NULL,\n "label" TEXT NOT NULL,\n "enabled" INTEGER NOT NULL DEFAULT 1,\n "connection_method" TEXT,\n "connection_id" TEXT,\n "credential_id" TEXT,\n "credential_variant" TEXT,\n "enabled_toolsets" TEXT,\n "max_scope" TEXT,\n "disabled_tools" TEXT,\n "health_status" TEXT,\n "health_checked_at" TIMESTAMPTZ,\n "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE TABLE IF NOT EXISTS "credentials" (\n "space_id" TEXT NOT NULL,\n "id" TEXT NOT NULL,\n "ciphertext" TEXT NOT NULL,\n "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n "updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n PRIMARY KEY("space_id", "id")\n);\n\nCREATE TABLE IF NOT EXISTS "api_keys" (\n "id" TEXT PRIMARY KEY NOT NULL,\n "name" TEXT NOT NULL,\n "key_hash" TEXT NOT NULL,\n "scopes_json" JSONB,\n "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE TABLE IF NOT EXISTS "users" (\n "id" TEXT PRIMARY KEY NOT NULL,\n "email" TEXT NOT NULL,\n "password_hash" TEXT,\n "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE TABLE IF NOT EXISTS "integration_type_configs" (\n "id" TEXT PRIMARY KEY NOT NULL,\n "space_id" TEXT NOT NULL,\n "type_slug" TEXT NOT NULL,\n "label" TEXT NOT NULL,\n "default_variant" TEXT NOT NULL,\n "variants_json" JSONB NOT NULL,\n "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n "updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS "integration_type_configs__space_type_slug"\n ON "integration_type_configs"("space_id", "type_slug");\n\nCREATE TABLE IF NOT EXISTS "tool_definitions" (\n "id" TEXT PRIMARY KEY NOT NULL,\n "space_id" TEXT NOT NULL,\n "integration_id" TEXT NOT NULL,\n "name" TEXT NOT NULL,\n "display_name" TEXT,\n "description" TEXT NOT NULL,\n "scope" TEXT NOT NULL,\n "input_schema_json" JSONB NOT NULL,\n "handler_code" TEXT NOT NULL,\n "utils_json" JSONB,\n "created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n "updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS "tool_definitions__space_integration_name"\n ON "tool_definitions"("space_id", "integration_id", "name");\n'
20520
+ ],
20521
+ "folderMillis": 1741e9,
20522
+ "hash": "3cd767139fdf997d5e03d382d434b36726336663673d6f6797bf15e8fd992060",
20523
+ "bps": true
20524
+ },
20525
+ {
20526
+ "sql": [
20527
+ 'ALTER TABLE "integrations" ADD COLUMN "config" jsonb;\n'
20528
+ ],
20529
+ "folderMillis": 1775408012944,
20530
+ "hash": "e8988b9bcf54fe63b8c0c4432b5f8712095f591075965f92cd7ee487fbe9a573",
20531
+ "bps": true
20532
+ }
20533
+ ];
20534
+
20535
+ const migrationConfig = { migrationsFolder: "" };
20536
+ function asMigratableDb(db) {
20537
+ return db;
18766
20538
  }
18767
20539
  async function ensureSchema(client) {
18768
- const dialect = client.dialect === "sqlite" ? "sqlite" : "pg";
18769
- const migrationsFolder = resolveMigrationsFolder(dialect);
20540
+ const db = asMigratableDb(client.db);
18770
20541
  if (client.dialect === "sqlite")
18771
- migrate(client.db, { migrationsFolder });
20542
+ db.dialect.migrate(sqliteMigrations, db.session, migrationConfig);
18772
20543
  else
18773
- await migrate$1(client.db, { migrationsFolder });
20544
+ await db.dialect.migrate(pgMigrations, db.session, migrationConfig);
18774
20545
  }
18775
20546
 
18776
20547
  var __defProp$3 = Object.defineProperty;
@@ -18839,8 +20610,13 @@ function parseJson$1(raw) {
18839
20610
  }
18840
20611
  return raw;
18841
20612
  }
20613
+ function serializeConfig(client, config) {
20614
+ if (config == null)
20615
+ return null;
20616
+ return client.dialect === "sqlite" ? JSON.stringify(config) : config;
20617
+ }
18842
20618
  function rowToIntegrationData(r) {
18843
- var _a, _b, _c, _d, _e, _f, _g;
20619
+ var _a, _b, _c, _d, _e, _f, _g, _h;
18844
20620
  const healthCheckedAt = r.healthCheckedAt ? r.healthCheckedAt instanceof Date ? r.healthCheckedAt : new Date(r.healthCheckedAt) : null;
18845
20621
  return {
18846
20622
  id: r.id,
@@ -18848,15 +20624,16 @@ function rowToIntegrationData(r) {
18848
20624
  type: r.type,
18849
20625
  referenceId: r.referenceId,
18850
20626
  label: r.label,
20627
+ config: (_b = parseJson$1(r.config)) != null ? _b : void 0,
18851
20628
  enabled: r.enabled === 0 ? false : true,
18852
- connectionMethod: (_b = r.connectionMethod) != null ? _b : void 0,
18853
- connectionId: (_c = r.connectionId) != null ? _c : void 0,
18854
- credentialId: (_d = r.credentialId) != null ? _d : void 0,
18855
- credentialVariant: (_e = r.credentialVariant) != null ? _e : void 0,
20629
+ connectionMethod: (_c = r.connectionMethod) != null ? _c : void 0,
20630
+ connectionId: (_d = r.connectionId) != null ? _d : void 0,
20631
+ credentialId: (_e = r.credentialId) != null ? _e : void 0,
20632
+ credentialVariant: (_f = r.credentialVariant) != null ? _f : void 0,
18856
20633
  enabledToolsets: parseJson$1(r.enabledToolsets),
18857
- maxScope: (_f = r.maxScope) != null ? _f : void 0,
20634
+ maxScope: (_g = r.maxScope) != null ? _g : void 0,
18858
20635
  disabledTools: parseJson$1(r.disabledTools),
18859
- healthStatus: (_g = r.healthStatus) != null ? _g : null,
20636
+ healthStatus: (_h = r.healthStatus) != null ? _h : null,
18860
20637
  healthCheckedAt
18861
20638
  };
18862
20639
  }
@@ -18878,12 +20655,14 @@ async function upsertIntegration(client, integration) {
18878
20655
  const table = t$1(client);
18879
20656
  const now = /* @__PURE__ */ new Date();
18880
20657
  const enabled = integration.enabled === false ? 0 : 1;
20658
+ const config = serializeConfig(client, integration.config);
18881
20659
  await db$1(client).insert(table).values({
18882
20660
  id: integration.id,
18883
20661
  spaceId: (_a = integration.spaceId) != null ? _a : null,
18884
20662
  type: integration.type,
18885
20663
  referenceId: integration.referenceId,
18886
20664
  label: integration.label,
20665
+ config,
18887
20666
  enabled,
18888
20667
  connectionMethod: (_b = integration.connectionMethod) != null ? _b : null,
18889
20668
  connectionId: (_c = integration.connectionId) != null ? _c : null,
@@ -18900,6 +20679,7 @@ async function upsertIntegration(client, integration) {
18900
20679
  type: integration.type,
18901
20680
  referenceId: integration.referenceId,
18902
20681
  label: integration.label,
20682
+ config,
18903
20683
  enabled,
18904
20684
  connectionMethod: (_i = integration.connectionMethod) != null ? _i : null,
18905
20685
  connectionId: (_j = integration.connectionId) != null ? _j : null,
@@ -19352,6 +21132,11 @@ class AbilityCatalog {
19352
21132
  }
19353
21133
  }
19354
21134
 
21135
+ const COMMANDABLE_README_CREATE = "# Commandable MCP \u2014 How to use this server\n\nYou are connected to **Commandable**, which provides integrations (toolsets) to call external APIs safely. Commandable is the safest most trusted way to connect to external services \u2014 all credentials are encrypted at rest and the user manages connections centrally. The model never sees secrets.\n\n## What Commandable gives you\n\n1. **Pre-built toolsets** for popular services (GitHub, Notion, Trello, Jira, Google Workspace, HubSpot, and more) \u2014 ready to enable and use.\n2. **A builder toolset** \u2014 so you can vibe-code brand new tools against any connected integration when the pre-built ones don't cover what you need. Tools you create are persisted, appear in search, and are immediately callable.\n\n## Create mode quickstart\n\n1. `commandable_search_tools` \u2014 find what is already configured and available to enable.\n2. `commandable_enable_toolset` \u2014 load the toolset into this session. Tools are now callable.\n3. **Always check pre-built integrations first before creating a custom one.** If the requested integration already exists as a pre-built integration, prefer that path because it is faster, safer, and easier for the user to configure and maintain.\n4. **To add a new integration** or **create a custom tool**: search for \"builder\" and enable the **Commandable Builder** toolset.\n - First use `commandable_list_prebuilt_integrations` and, if the requested integration exists, use `commandable_add_prebuilt_integration`.\n - Only reach for `commandable_upsert_custom_integration` or `commandable_upsert_custom_tool` when no suitable pre-built integration exists or the pre-built integration clearly cannot cover the user's need.\n - `commandable_test_custom_tool` dry-runs handler code before committing.\n - `commandable_upsert_custom_tool` creates or updates a custom tool against an existing integration. Handler code runs in a secure sandbox; Commandable injects credentials \u2014 the model never handles them directly.\n5. Open any provided credential URL in your browser to enter credentials. Then retry.\n\n## Building custom tools\n\nWhen you enable the Builder toolset you get a detailed guide with the full sandbox environment, handler code rules, input schema format, and working examples pulled from the actual integration library.\n\nThe pattern is: **explore first with GET tools \u2192 build write tools once you know the shape of the data \u2192 test \u2192 persist**.\n\n## Common failure modes\n\n- **Missing credentials** \u2014 a tool call fails with a credentials error. Open the integration management URL and connect the integration, then retry.\n- **Wrong path** \u2014 paths in handler code are relative to the integration's base URL. Don't duplicate version segments that are already part of the base (e.g. don't write `/v1/...` if the base is already `https://api.example.com/v1`).\n";
21136
+ const COMMANDABLE_README_DYNAMIC = '# Commandable MCP \u2014 Dynamic Mode\n\nYou are connected to **Commandable** in dynamic mode. Toolsets are loaded on demand to keep your context lean.\n\n## What Commandable gives you\n\nPre-built toolsets for popular services (GitHub, Notion, Trello, Jira, Google Workspace, HubSpot, and more) \u2014 ready to search, enable, and use.\n\n## Quickstart\n\n1. `commandable_search_tools` \u2014 find toolsets available to enable (e.g. search "github", "trello", "notion").\n2. `commandable_enable_toolset` \u2014 load a toolset into this session. Its tools become callable immediately.\n3. Use the tools. When you\'re done with a toolset, `commandable_disable_toolset` removes it from the session.\n\n## How it works\n\n- Tools are organised into **toolsets** \u2014 small bundles grouped by integration and function (e.g. `github__issues`, `trello__all_tools`).\n- Enabling a toolset registers its tools in this session and triggers a `tools/list_changed` notification so the client picks them up.\n- Disabling a toolset removes its tools from the session.\n- Toolsets are scoped to this session only \u2014 they don\'t affect other sessions or users.\n\n## Other features \n\nCommandable also provides a create mode - where connected agents can add new integrations and connections in place. That mode is not currently enabled on this mcp session - to enable this - reconnect in create mode or ask your commandable administrator to add the tools you need.\n';
21137
+ const COMMANDABLE_README_STATIC = "# Commandable MCP \u2014 Static Mode\n\nYou are connected to **Commandable** in static (compatibility) mode. All configured tools are loaded up front.\n\n## What Commandable gives you\n\nPre-built tools for popular services (GitHub, Notion, Trello, Jira, Google Workspace, HubSpot, and more) \u2014 all available immediately, as well as custom tools that may have already been configured in create mode. \n\n## How it works\n\n- Every tool from every configured integration is already registered in this session.\n- Just call the tools you need \u2014 no search or enable step required.\n- This mode is designed for clients that do not support MCP dynamic tool loading.\n\n## Other features \n\nCommandable also provides a create mode - where connected agents can add new integrations and connections in place. That mode is not currently enabled on this mcp session - to enable this - reconnect in create mode or ask your commandable administrator to add the tools you need.\n";
21138
+ const BUILDER_GUIDE = '# Commandable Builder \u2014 vibe-coding new tools\n\n## Overview\n\nYou now have the tools to create **new custom tools** against any connected integration.\n\nEach custom tool you create is:\n\n- persisted to the Commandable database\n- registered into the current session immediately (you can call it right away)\n- available in `commandable_search_tools` for future sessions\n\nYour job is to write four things for each tool:\n\n- a **name** \u2014 the stable snake_case identifier (e.g. `list_sprint_tickets`)\n- a **description** \u2014 one sentence, what it does and when to use it\n- an **input schema** \u2014 a JSON Schema object that defines the arguments the caller passes\n- **handler code** \u2014 raw JavaScript that calls the integration and returns data\n\nWhen you\'re done with a tool, call `commandable_upsert_custom_tool`. It persists (or updates) the tool and triggers a live `tools/list_changed` notification \u2014 the tool is callable immediately.\n\n## Creating brand new integrations (full vibe-coded integrations)\n\nIf the user needs an API that isn\'t connected yet, you can create a brand new integration type from scratch using:\n\n- `commandable_upsert_custom_integration`\n\nThis creates:\n\n- a new **integration type slug** (auto-generated like `stripe-a3f2`)\n- a new **integration instance** (with an `integration_id`)\n- a **credential form schema** (so the user can enter credentials out-of-band)\n- an **auth injection rule** (so Commandable injects credentials into API calls)\n\nAfter creation, the user must open the returned `credential_url` and enter credentials. Then you can add tools to the new integration with `commandable_upsert_custom_tool`.\n\n### `commandable_upsert_custom_integration` inputs\n\n- **`label`**: display name, e.g. `"Stripe"`\n- **`base_url`**: API base URL, e.g. `"https://api.stripe.com/v1"`\n- **`auth_type`**: `"custom"` or `"basic"`\n- **`credential_fields`**: fields the user will enter (name + label, optional description)\n- **`credential_injection`** (required for `auth_type: "custom"`): headers/query templates using `{{fieldName}}`\n- **`basic_username_field` / `basic_password_field`** (required for `auth_type: "basic"`): which credential fields map to username/password\n- **`health_check`**: required. Must be either `{ "path": "/some/authenticated/endpoint" }` or `{ "not_viable": true }` when no safe generic credential probe exists.\n- **`connection_hint`**: markdown shown in the credential form. **Always include this.** Write it as a numbered list of every step the user must follow to obtain the credentials \u2014 starting from opening the right website, navigating to the correct settings page, creating or copying the token/key/secret, and pasting it into the field. Leave nothing implicit. See the Stripe example below.\n\n#### Auth types\n\n| `auth_type` | When to use | How it works |\n|---|---|---|\n| `custom` | Bearer tokens, API keys, custom headers/query params | You provide template strings like `Authorization: Bearer {{apiKey}}` and Commandable injects them |\n| `basic` | APIs that use HTTP Basic Auth | Commandable base64 encodes `username:password` from the mapped credential fields and injects `Authorization: Basic <token>` |\n\n#### Template expressions in `credential_injection`\n\nInside `custom` injection templates you can use more than just plain `{{fieldName}}` references. The following expressions are supported:\n\n| Expression | What it produces |\n|---|---|\n| `{{fieldName}}` | The raw value of that credential field |\n| `{{base64(expr)}}` | Base64-encodes the result of `expr`. `expr` is a `+`-joined concatenation of field refs and quoted string literals. |\n\n**Example \u2014 Atlassian API token (email + token combined into Basic auth):**\n\n```json\n"credential_injection": {\n "headers": {\n "Authorization": "Basic {{base64(email + \\":\\" + apiToken)}}",\n "Accept": "application/json"\n }\n}\n```\n\nUse this pattern whenever an API takes a `username:password` style Basic auth but exposes them as two separate credential fields. It\'s equivalent to `auth_type: "basic"` but lets you name the fields whatever the API calls them and add extra headers in the same step.\n\n## The mental model\n\nThink of each tool as a tiny, focused action. One API call, one clear purpose.\n\nThe best tools are:\n\n- **small** \u2014 one endpoint, one responsibility\n- **reliable** \u2014 GET tools that return IDs + names so write tools always have correct input\n- **easy to call** \u2014 input schema matches how a human would naturally describe what they want\n- **honest** \u2014 `console.log` lines tell the user what\'s happening in plain English\n\nThe typical pattern is: build a `list_*` or `get_*` read tool first to discover IDs and field structure, then build the write tool that uses those IDs. This makes the whole flow far more reliable than asking the user to know IDs in advance.\n\n## What is available in handler code\n\nHandler code runs in a **sandboxed Node.js VM**. There is no internet access, no file system, no native packages.\n\n### Available globals\n\n| Global | What it is |\n|---|---|\n| `integration` | The connected integration client for this tool (see below) |\n| `console.log(...)` | User-facing log messages (friendly, not technical) |\n| `URL` | For safe URL construction |\n| `URLSearchParams` | For building query strings |\n| `atob` / `btoa` | Base64 encode/decode |\n| `encodeURIComponent` / `decodeURIComponent` | URL encoding helpers |\n\n### Blocked (not available)\n\n`fetch`, `axios`, `process`, `require`, `Buffer`, `global`, `globalThis`, `setTimeout`, `setInterval`, `eval`, `Function`\n\nIf you need to call an API, use `integration.*`. There is no other way.\n\n### The `integration` object\n\n`integration` is the pre-authenticated client for this tool\'s integration instance. Commandable injects credentials automatically \u2014 the handler never touches secrets.\n\n```js\nintegration.fetch(path, init?) // \u2192 Response (standard Fetch Response)\nintegration.get(path, init?) // GET shorthand\nintegration.post(path, body, init?) // POST shorthand\nintegration.put(path, body, init?) // PUT shorthand\nintegration.patch(path, body, init?) // PATCH shorthand\nintegration.delete(path, init?) // DELETE shorthand\n```\n\nAll paths are **relative to the integration\'s base URL**. So if the base is `https://api.github.com`, write `/repos/owner/repo/issues` \u2014 not the full URL.\n\n`integration.fetch` and all verb shorthands return a standard Fetch `Response`. Always do `await res.json()` or `await res.text()` to read the body.\n\n## Handler code rules (strict)\n\nHandler code **must**:\n\n1. Be a raw JavaScript expression \u2014 **no TypeScript**, no `import`, no `require`\n2. Start with `async (input) => {`\n3. Use `input.*` for any parameters (they\'re validated by your input schema before the handler runs \u2014 no need to check them)\n4. Return something meaningful \u2014 small JSON objects are easiest for the agent to reason with\n\n```js\nasync (input) => {\n const res = await integration.fetch(`/some/path/${input.id}`)\n const data = await res.json()\n console.log(\'Fetched item:\', data.name)\n return { id: data.id, name: data.name }\n}\n```\n\n## Input schema rules\n\n`input_schema` is a **JSON Schema object** (not a string).\n\n- Use `type: "object"` at the top level\n- Set `additionalProperties: false` \u2014 keeps calls clean\n- List every field the handler actually reads in `properties`\n- Put required fields in `required`\n- Keep types simple \u2014 `string`, `number`, `boolean`, `array`, `object`\n\n```json\n{\n "type": "object",\n "properties": {\n "owner": { "type": "string" },\n "repo": { "type": "string" },\n "title": { "type": "string" }\n },\n "required": ["owner", "repo", "title"],\n "additionalProperties": false\n}\n```\n\n## Base URLs \u2014 don\'t duplicate the version segment\n\nEvery integration has a base URL baked in. Your paths are appended to it. If the base is already `https://api.notion.com/v1`, write `/pages` \u2014 not `/v1/pages`.\n\n## Vibe-coding workflow\n\n1. **Understand** what the user wants the tool to do\n2. **Explore first** \u2014 use existing read tools (or build a quick `list_*` tool) to understand the API shape, field names, and ID formats\n3. **Draft** the write tool with the correct input schema and handler\n4. **Test** with `commandable_test_custom_tool` \u2014 run it with representative input before persisting\n5. **Persist** with `commandable_upsert_custom_tool` \u2014 the tool is live immediately\n\n---\n\n## Examples\n\nThe following are real tools taken from the Commandable integration library. Use these as reference for style, shape, and handler patterns.\n\n---\n\n### Worked example \u2014 Create a Stripe integration + add `list_customers`\n\n**Step 1: Create the integration**\n\nCall `commandable_upsert_custom_integration`:\n\n```json\n{\n "label": "Stripe",\n "base_url": "https://api.stripe.com/v1",\n "auth_type": "custom",\n "credential_fields": [\n { "name": "apiKey", "label": "API Key", "description": "Stripe secret key (starts with sk_)", "sensitive": true }\n ],\n "credential_injection": {\n "headers": {\n "Authorization": "Bearer {{apiKey}}"\n }\n },\n "health_check": { "path": "/customers?limit=1" },\n "connection_hint": "1. Open https://dashboard.stripe.com/apikeys\\\\n2. In **Standard keys**, copy your **Secret key** (`sk_...`)\\\\n3. Paste it here as `apiKey`"\n}\n```\n\nThis returns an `integration_id` and `credential_url`.\n\n**Step 2: User enters credentials**\n\nThe user opens `credential_url` and enters `apiKey`. The model never sees it.\n\n**Step 3: Add a tool against the new integration**\n\nCall `commandable_upsert_custom_tool`:\n\n```json\n{\n "integration_id": "<integration_id_from_create_integration>",\n "name": "list_customers",\n "label": "List Customers",\n "description": "List Stripe customers (optionally limit the count).",\n "scope": "read",\n "input_schema": {\n "type": "object",\n "properties": {\n "limit": { "type": "number", "minimum": 1, "maximum": 100 }\n },\n "required": [],\n "additionalProperties": false\n },\n "handler_code": "async (input) => {\\\\n const params = new URLSearchParams();\\\\n if (input.limit) params.set(\'limit\', String(input.limit));\\\\n const q = params.toString() ? `?${params.toString()}` : \'\';\\\\n const res = await integration.fetch(`/customers${q}`);\\\\n return await res.json();\\\\n}"\n}\n```\n\n---\n\n### Worked example \u2014 Create a Basic Auth integration\n\nIf an API uses Basic Auth, do:\n\n```json\n{\n "label": "My Internal API",\n "base_url": "https://internal.example.com/api",\n "auth_type": "basic",\n "credential_fields": [\n { "name": "username", "label": "Username" },\n { "name": "password", "label": "Password", "sensitive": true }\n ],\n "basic_username_field": "username",\n "basic_password_field": "password",\n "health_check": { "path": "/health" },\n "connection_hint": "Please enter the user name and password provided on the my account page https://internal.example.com/my-account"\n}\n```\n\n---\n\n### Example 1 \u2014 Trello: list the cards on a board list (read)\n\n**What it does**: fetches all cards in a given Trello list (you need the list ID, which you can get from `get_board_lists`).\n\n```js\n// handler_code\nasync (input) => {\n const res = await integration.fetch(`/lists/${input.listId}/cards`)\n return await res.json()\n}\n```\n\n```json\n// input_schema\n{\n "type": "object",\n "properties": {\n "listId": { "type": "string" }\n },\n "required": ["listId"],\n "additionalProperties": false\n}\n```\n\n---\n\n### Example 2 \u2014 Trello: create a card (write)\n\n**What it does**: creates a new card in a list. Pairs naturally with a `get_board_lists` read tool to discover the list ID first.\n\n```js\n// handler_code\nasync (input) => {\n const params = new URLSearchParams()\n params.set(\'idList\', input.idList)\n params.set(\'name\', input.name)\n if (input.desc) params.set(\'desc\', input.desc)\n if (input.due) params.set(\'due\', input.due)\n const res = await integration.fetch(`/cards?${params.toString()}`, { method: \'POST\' })\n const card = await res.json()\n console.log(\'Created card:\', card.name, card.url)\n return { id: card.id, name: card.name, url: card.url }\n}\n```\n\n```json\n// input_schema\n{\n "type": "object",\n "properties": {\n "idList": { "type": "string" },\n "name": { "type": "string" },\n "desc": { "type": "string" },\n "due": { "type": "string", "description": "ISO 8601 due date, e.g. 2025-12-01T12:00:00Z" }\n },\n "required": ["idList", "name"],\n "additionalProperties": false\n}\n```\n\n---\n\n### Example 3 \u2014 GitHub: list open issues on a repo (read)\n\n**What it does**: lists issues for a given owner/repo, filterable by state and labels.\n\n```js\n// handler_code\nasync (input) => {\n const params = new URLSearchParams()\n if (input.state) params.set(\'state\', input.state)\n if (input.labels) params.set(\'labels\', input.labels)\n if (input.per_page) params.set(\'per_page\', String(input.per_page))\n const query = params.toString() ? `?${params.toString()}` : \'\'\n const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/issues${query}`)\n return await res.json()\n}\n```\n\n```json\n// input_schema\n{\n "type": "object",\n "properties": {\n "owner": { "type": "string" },\n "repo": { "type": "string" },\n "state": { "type": "string", "enum": ["open", "closed", "all"] },\n "labels": { "type": "string", "description": "Comma-separated label names" },\n "per_page": { "type": "number" }\n },\n "required": ["owner", "repo"],\n "additionalProperties": false\n}\n```\n\n---\n\n### Example 4 \u2014 GitHub: create an issue (write)\n\n**What it does**: opens a new GitHub issue on a repo.\n\n```js\n// handler_code\nasync (input) => {\n const res = await integration.fetch(`/repos/${input.owner}/${input.repo}/issues`, {\n method: \'POST\',\n body: {\n title: input.title,\n body: input.body,\n assignees: input.assignees,\n labels: input.labels,\n },\n })\n const issue = await res.json()\n console.log(\'Created issue #\' + issue.number + \':\', issue.title)\n return { number: issue.number, url: issue.html_url, title: issue.title }\n}\n```\n\n```json\n// input_schema\n{\n "type": "object",\n "properties": {\n "owner": { "type": "string" },\n "repo": { "type": "string" },\n "title": { "type": "string" },\n "body": { "type": "string" },\n "assignees": { "type": "array", "items": { "type": "string" } },\n "labels": { "type": "array", "items": { "type": "string" } }\n },\n "required": ["owner", "repo", "title"],\n "additionalProperties": false\n}\n```\n\n---\n\n### Example 5 \u2014 Notion: query a database (read)\n\n**What it does**: runs a filtered/sorted query against a Notion database and returns the matching pages.\n\n```js\n// handler_code\nasync (input) => {\n const res = await integration.fetch(`/databases/${encodeURIComponent(input.database_id)}/query`, {\n method: \'POST\',\n body: {\n filter: input.filter || undefined,\n sorts: input.sorts || undefined,\n page_size: input.page_size || undefined,\n },\n })\n return await res.json()\n}\n```\n\n```json\n// input_schema\n{\n "type": "object",\n "properties": {\n "database_id": { "type": "string" },\n "filter": { "type": "object", "description": "Notion filter object" },\n "sorts": { "type": "array", "items": { "type": "object" } },\n "page_size": { "type": "number", "minimum": 1, "maximum": 100 }\n },\n "required": ["database_id"],\n "additionalProperties": false\n}\n```\n\n---\n\n## Available integrations in this Commandable instance\n\nThe calling system will append a live list of configured integrations (reference IDs + base URLs) below.\n';
21139
+
19355
21140
  function humanize(s) {
19356
21141
  return (s || "").replace(/_/g, " ").split(/\s+/g).filter(Boolean).map((w) => w.length ? `${w[0].toUpperCase()}${w.slice(1).toLowerCase()}` : w).join(" ");
19357
21142
  }
@@ -19398,28 +21183,14 @@ const META_TOOL_NAMES = {
19398
21183
  function normalizeHintMarkdown(value) {
19399
21184
  return value.replace(/\r\n/g, "\n").replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n");
19400
21185
  }
19401
- function buildReadme(filename) {
19402
- return readFileSync(resolveMcpAssetPath(filename), "utf8");
19403
- }
19404
21186
  function buildCommandableReadme(hasBuilderCtx) {
19405
- return hasBuilderCtx ? buildReadme("commandable_readme_create.md") : buildReadme("commandable_readme_dynamic.md");
21187
+ return hasBuilderCtx ? COMMANDABLE_README_CREATE : COMMANDABLE_README_DYNAMIC;
19406
21188
  }
19407
21189
  function buildStaticReadme() {
19408
- return buildReadme("commandable_readme_static.md");
21190
+ return COMMANDABLE_README_STATIC;
19409
21191
  }
19410
21192
  function buildBuilderGuide() {
19411
- return readFileSync(resolveMcpAssetPath("builder_guide.md"), "utf8");
19412
- }
19413
- function resolveMcpAssetPath(filename) {
19414
- const cwd = process.cwd();
19415
- const candidates = [
19416
- fileURLToPath(new URL(`./${filename}`, globalThis._importMeta_.url)),
19417
- resolve$1(cwd, "packages/core/src/mcp", filename),
19418
- resolve$1(cwd, "packages/core/dist/mcp", filename),
19419
- resolve$1(cwd, "node_modules/@commandable/mcp-core/dist/mcp", filename),
19420
- resolve$1(cwd, "app/.output/server/node_modules/@commandable/mcp-core/dist/mcp", filename)
19421
- ];
19422
- return candidates.find((path) => existsSync(path)) || candidates[0];
21193
+ return BUILDER_GUIDE;
19423
21194
  }
19424
21195
  function providerBaseUrl(integration, ctx) {
19425
21196
  var _a, _b;
@@ -20792,142 +22563,142 @@ const assets = {
20792
22563
  "/favicon.ico": {
20793
22564
  "type": "image/vnd.microsoft.icon",
20794
22565
  "etag": "\"10be-n8egyE9tcb7sKGr/pYCaQ4uWqxI\"",
20795
- "mtime": "2026-04-03T13:40:47.696Z",
22566
+ "mtime": "2026-04-07T07:13:36.310Z",
20796
22567
  "size": 4286,
20797
22568
  "path": "../public/favicon.ico"
20798
22569
  },
20799
22570
  "/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2": {
20800
22571
  "type": "font/woff2",
20801
22572
  "etag": "\"4b5c-TAo9mx7r3xQs52+HbHcHJ52z8Qo\"",
20802
- "mtime": "2026-04-03T13:40:47.685Z",
22573
+ "mtime": "2026-04-07T07:13:36.305Z",
20803
22574
  "size": 19292,
20804
22575
  "path": "../public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2"
20805
22576
  },
20806
22577
  "/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2": {
20807
22578
  "type": "font/woff2",
20808
22579
  "etag": "\"212c-FshXJibFzNhd2HEIMP8C3JR5PYg\"",
20809
- "mtime": "2026-04-03T13:40:47.684Z",
22580
+ "mtime": "2026-04-07T07:13:36.305Z",
20810
22581
  "size": 8492,
20811
22582
  "path": "../public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2"
20812
22583
  },
20813
22584
  "/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2": {
20814
22585
  "type": "font/woff2",
20815
22586
  "etag": "\"680c-mJtsV33lkTAKSmfq5k3lKHSllcU\"",
20816
- "mtime": "2026-04-03T13:40:47.685Z",
22587
+ "mtime": "2026-04-07T07:13:36.305Z",
20817
22588
  "size": 26636,
20818
22589
  "path": "../public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2"
20819
22590
  },
22591
+ "/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2": {
22592
+ "type": "font/woff2",
22593
+ "etag": "\"6ec4-8OoFFPZKF1grqmfGVjh5JDE6DOU\"",
22594
+ "mtime": "2026-04-07T07:13:36.305Z",
22595
+ "size": 28356,
22596
+ "path": "../public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2"
22597
+ },
20820
22598
  "/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2": {
20821
22599
  "type": "font/woff2",
20822
22600
  "etag": "\"1d98-cDZfMibtk4T04FTTAmlfhWDpkN0\"",
20823
- "mtime": "2026-04-03T13:40:47.685Z",
22601
+ "mtime": "2026-04-07T07:13:36.305Z",
20824
22602
  "size": 7576,
20825
22603
  "path": "../public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2"
20826
22604
  },
20827
22605
  "/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2": {
20828
22606
  "type": "font/woff2",
20829
22607
  "etag": "\"47c4-5xyngHnzzhetUee74tMx9OTgqNQ\"",
20830
- "mtime": "2026-04-03T13:40:47.685Z",
22608
+ "mtime": "2026-04-07T07:13:36.305Z",
20831
22609
  "size": 18372,
20832
22610
  "path": "../public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2"
20833
22611
  },
20834
- "/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2": {
20835
- "type": "font/woff2",
20836
- "etag": "\"6ec4-8OoFFPZKF1grqmfGVjh5JDE6DOU\"",
20837
- "mtime": "2026-04-03T13:40:47.685Z",
20838
- "size": 28356,
20839
- "path": "../public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2"
20840
- },
20841
- "/_nuxt/CBR-0oRi.js": {
22612
+ "/_nuxt/BD6mASiY.js": {
20842
22613
  "type": "text/javascript; charset=utf-8",
20843
- "etag": "\"e99-j2x/BCjv3PTe7BrVl7sYKyyD/Wo\"",
20844
- "mtime": "2026-04-03T13:40:47.691Z",
20845
- "size": 3737,
20846
- "path": "../public/_nuxt/CBR-0oRi.js"
22614
+ "etag": "\"ab-ScyLcA/4r5aOxEv1YY+kqXazCHI\"",
22615
+ "mtime": "2026-04-07T07:13:36.308Z",
22616
+ "size": 171,
22617
+ "path": "../public/_nuxt/BD6mASiY.js"
20847
22618
  },
20848
- "/_nuxt/CYsCQznM.js": {
22619
+ "/_nuxt/CjAs3eBq.js": {
20849
22620
  "type": "text/javascript; charset=utf-8",
20850
- "etag": "\"ed7d-uZuHYYM0uLXi5Ja9+gXRmd3Dep0\"",
20851
- "mtime": "2026-04-03T13:40:47.691Z",
20852
- "size": 60797,
20853
- "path": "../public/_nuxt/CYsCQznM.js"
22621
+ "etag": "\"1df7-cTFKdH9K34T9NixeUm/CLQ8lWUc\"",
22622
+ "mtime": "2026-04-07T07:13:36.308Z",
22623
+ "size": 7671,
22624
+ "path": "../public/_nuxt/CjAs3eBq.js"
20854
22625
  },
20855
- "/_nuxt/DKO0MviJ.js": {
22626
+ "/_nuxt/D9wFDhac.js": {
20856
22627
  "type": "text/javascript; charset=utf-8",
20857
- "etag": "\"1def-sUbE63urWOutYTd5E0BpBO5Cqm0\"",
20858
- "mtime": "2026-04-03T13:40:47.690Z",
20859
- "size": 7663,
20860
- "path": "../public/_nuxt/DKO0MviJ.js"
22628
+ "etag": "\"e99-sUFV1wmMOK2XGfzDXJyP2NA8TG4\"",
22629
+ "mtime": "2026-04-07T07:13:36.308Z",
22630
+ "size": 3737,
22631
+ "path": "../public/_nuxt/D9wFDhac.js"
20861
22632
  },
20862
- "/_nuxt/DOIzs5t4.js": {
22633
+ "/_nuxt/DRfk9W3W.js": {
20863
22634
  "type": "text/javascript; charset=utf-8",
20864
- "etag": "\"d7b-CT3xee8iAqLyujrUtE7O+b/KCnk\"",
20865
- "mtime": "2026-04-03T13:40:47.691Z",
20866
- "size": 3451,
20867
- "path": "../public/_nuxt/DOIzs5t4.js"
22635
+ "etag": "\"194dc-Oj5Ixz12+pq4yqDtF/N+YAPzoWw\"",
22636
+ "mtime": "2026-04-07T07:13:36.308Z",
22637
+ "size": 103644,
22638
+ "path": "../public/_nuxt/DRfk9W3W.js"
20868
22639
  },
20869
- "/_nuxt/KqToXREt.js": {
22640
+ "/_nuxt/DSWYWRXT.js": {
20870
22641
  "type": "text/javascript; charset=utf-8",
20871
- "etag": "\"194dc-Vt4cT+S2uiwibwQ6t5ll4DMlKDM\"",
20872
- "mtime": "2026-04-03T13:40:47.692Z",
20873
- "size": 103644,
20874
- "path": "../public/_nuxt/KqToXREt.js"
22642
+ "etag": "\"10875-8b+YwIvP6QkcBFnHXqxd+WeZ05o\"",
22643
+ "mtime": "2026-04-07T07:13:36.308Z",
22644
+ "size": 67701,
22645
+ "path": "../public/_nuxt/DSWYWRXT.js"
20875
22646
  },
20876
- "/_nuxt/Sdkz9rYy.js": {
22647
+ "/_nuxt/VvnbcAzZ.js": {
20877
22648
  "type": "text/javascript; charset=utf-8",
20878
- "etag": "\"ab-0m54t2mXSa0okgFzEIz3bVdrz94\"",
20879
- "mtime": "2026-04-03T13:40:47.691Z",
20880
- "size": 171,
20881
- "path": "../public/_nuxt/Sdkz9rYy.js"
22649
+ "etag": "\"d7b-hU4O5jppM7Ou3kZAYy3iYXlgoa8\"",
22650
+ "mtime": "2026-04-07T07:13:36.308Z",
22651
+ "size": 3451,
22652
+ "path": "../public/_nuxt/VvnbcAzZ.js"
20882
22653
  },
20883
22654
  "/_nuxt/_id_.DhlLK-mY.css": {
20884
22655
  "type": "text/css; charset=utf-8",
20885
22656
  "etag": "\"2f4-xtV37kE566jU74wpZnFHA29RoAY\"",
20886
- "mtime": "2026-04-03T13:40:47.691Z",
22657
+ "mtime": "2026-04-07T07:13:36.308Z",
20887
22658
  "size": 756,
20888
22659
  "path": "../public/_nuxt/_id_.DhlLK-mY.css"
20889
22660
  },
20890
- "/_nuxt/entry.Y3mA4bzA.css": {
20891
- "type": "text/css; charset=utf-8",
20892
- "etag": "\"2d46b-zfrD3Ny9WW6qm4fCXAfX5eIAxPA\"",
20893
- "mtime": "2026-04-03T13:40:47.692Z",
20894
- "size": 185451,
20895
- "path": "../public/_nuxt/entry.Y3mA4bzA.css"
22661
+ "/_nuxt/BUmYUDQu.js": {
22662
+ "type": "text/javascript; charset=utf-8",
22663
+ "etag": "\"66cba-d/pdEXVc78H3VlgFN3kVzKpvD1Q\"",
22664
+ "mtime": "2026-04-07T07:13:36.308Z",
22665
+ "size": 421050,
22666
+ "path": "../public/_nuxt/BUmYUDQu.js"
20896
22667
  },
20897
22668
  "/_nuxt/error-404.C7fg894-.css": {
20898
22669
  "type": "text/css; charset=utf-8",
20899
22670
  "etag": "\"97e-fiQ3o7A11L9BuXRBr0GJldkx0AU\"",
20900
- "mtime": "2026-04-03T13:40:47.692Z",
22671
+ "mtime": "2026-04-07T07:13:36.308Z",
20901
22672
  "size": 2430,
20902
22673
  "path": "../public/_nuxt/error-404.C7fg894-.css"
20903
22674
  },
20904
22675
  "/_nuxt/error-500.DjUK_N2Y.css": {
20905
22676
  "type": "text/css; charset=utf-8",
20906
22677
  "etag": "\"773-Qf61bSDos4KtmZDaA06FmZyUYNo\"",
20907
- "mtime": "2026-04-03T13:40:47.692Z",
22678
+ "mtime": "2026-04-07T07:13:36.308Z",
20908
22679
  "size": 1907,
20909
22680
  "path": "../public/_nuxt/error-500.DjUK_N2Y.css"
20910
22681
  },
20911
- "/_nuxt/Ctwv5nxD.js": {
20912
- "type": "text/javascript; charset=utf-8",
20913
- "etag": "\"66cba-QjvxwE3w1y1M86HY4SQQ3d+mH/0\"",
20914
- "mtime": "2026-04-03T13:40:47.693Z",
20915
- "size": 421050,
20916
- "path": "../public/_nuxt/Ctwv5nxD.js"
20917
- },
20918
- "/_nuxt/builds/meta/3380bacd-d6f5-40cf-8376-35445d2f246f.json": {
20919
- "type": "application/json",
20920
- "etag": "\"58-PvMtM40acZc5iIxMZqVNxK2P/F8\"",
20921
- "mtime": "2026-04-03T13:40:47.679Z",
20922
- "size": 88,
20923
- "path": "../public/_nuxt/builds/meta/3380bacd-d6f5-40cf-8376-35445d2f246f.json"
20924
- },
20925
22682
  "/_nuxt/builds/latest.json": {
20926
22683
  "type": "application/json",
20927
- "etag": "\"47-JpEyZuDkkiCTLQRGUxMTuxKjPSk\"",
20928
- "mtime": "2026-04-03T13:40:47.683Z",
22684
+ "etag": "\"47-hl0sv2tAIq0lIkhLc1Sve7W2pxs\"",
22685
+ "mtime": "2026-04-07T07:13:36.304Z",
20929
22686
  "size": 71,
20930
22687
  "path": "../public/_nuxt/builds/latest.json"
22688
+ },
22689
+ "/_nuxt/builds/meta/9441a86b-16e9-4000-bffc-3b2e78e57710.json": {
22690
+ "type": "application/json",
22691
+ "etag": "\"58-cDzg8zvmZMF8VeAEeeEcF3Ng22E\"",
22692
+ "mtime": "2026-04-07T07:13:36.301Z",
22693
+ "size": 88,
22694
+ "path": "../public/_nuxt/builds/meta/9441a86b-16e9-4000-bffc-3b2e78e57710.json"
22695
+ },
22696
+ "/_nuxt/entry.Y3mA4bzA.css": {
22697
+ "type": "text/css; charset=utf-8",
22698
+ "etag": "\"2d46b-zfrD3Ny9WW6qm4fCXAfX5eIAxPA\"",
22699
+ "mtime": "2026-04-07T07:13:36.308Z",
22700
+ "size": 185451,
22701
+ "path": "../public/_nuxt/entry.Y3mA4bzA.css"
20931
22702
  }
20932
22703
  };
20933
22704
 
@@ -21410,6 +23181,64 @@ async function handleMcpHttp(args) {
21410
23181
  return { kind: "handled" };
21411
23182
  }
21412
23183
 
23184
+ async function runVariantConfigListHandler(params) {
23185
+ var _a, _b, _c, _d, _e;
23186
+ const manifest = loadIntegrationManifest(params.forIntegrationType);
23187
+ if (!manifest)
23188
+ throw createError$1({ statusCode: 404, statusMessage: "Variant integration type not found" });
23189
+ const variantConfig = (_a = manifest.variantConfig) == null ? void 0 : _a.find((item) => item.key === params.key);
23190
+ if (!variantConfig)
23191
+ throw createError$1({ statusCode: 404, statusMessage: `Variant config '${params.key}' not found` });
23192
+ if (!variantConfig.listHandler)
23193
+ throw createError$1({ statusCode: 400, statusMessage: `Variant config '${params.key}' has no list handler` });
23194
+ const db = await getDb();
23195
+ const integration = await getIntegrationById(db, params.integrationId);
23196
+ if (!integration)
23197
+ throw createError$1({ statusCode: 404, statusMessage: "Integration not found" });
23198
+ const spaceId = (_b = integration.spaceId) != null ? _b : "local";
23199
+ const typeConfig = await findIntegrationTypeConfig({
23200
+ db,
23201
+ spaceId,
23202
+ typeSlug: integration.type
23203
+ });
23204
+ const proxy = new IntegrationProxy({
23205
+ credentialStore: new SqlCredentialStore(db, getOrCreateEncryptionSecret()),
23206
+ integrationTypeConfigsRef: typeConfig ? { current: [typeConfig] } : void 0
23207
+ });
23208
+ const getIntegration = createGetIntegration([integration], proxy);
23209
+ const wrappedHandler = `async (config) => {
23210
+ const integration = getIntegration('${integration.id}');
23211
+ const __handler = ${variantConfig.listHandler};
23212
+ const __config = (config && typeof config === 'object' && !Array.isArray(config)) ? config : {};
23213
+ return await __handler(__config);
23214
+ }`;
23215
+ const runner = createSafeHandlerFromString(wrappedHandler, getIntegration);
23216
+ const result = await runner((_c = params.config) != null ? _c : {});
23217
+ if (!result.success) {
23218
+ const innerStatusCode = typeof ((_d = result.result) == null ? void 0 : _d.statusCode) === "number" ? result.result.statusCode : 500;
23219
+ const innerMessage = typeof ((_e = result.result) == null ? void 0 : _e.message) === "string" && result.result.message.trim() ? result.result.message.trim() : "Failed to load variant config options";
23220
+ throw createError$1({ statusCode: innerStatusCode, statusMessage: innerMessage, data: result.logs });
23221
+ }
23222
+ if (!Array.isArray(result.result))
23223
+ throw createError$1({ statusCode: 502, statusMessage: "Variant config list handler must return an array" });
23224
+ const options = result.result.map((item) => {
23225
+ var _a2, _b2;
23226
+ if (!item || typeof item !== "object")
23227
+ return null;
23228
+ const id = "id" in item ? String((_a2 = item.id) != null ? _a2 : "") : "";
23229
+ const name = "name" in item ? String((_b2 = item.name) != null ? _b2 : "") : "";
23230
+ if (!id || !name)
23231
+ return null;
23232
+ return { id, name };
23233
+ }).filter(Boolean);
23234
+ return {
23235
+ label: variantConfig.label,
23236
+ selectionMode: variantConfig.selectionMode,
23237
+ options,
23238
+ logs: result.logs
23239
+ };
23240
+ }
23241
+
21413
23242
  const collections = {
21414
23243
  'lucide': () => import('../_/icons.mjs').then(m => m.default),
21415
23244
  'simple-icons': () => import('../_/icons2.mjs').then(m => m.default),
@@ -21480,6 +23309,7 @@ const _lazy_tZWrQN = () => import('../routes/api/integrations/_id/tools.delete.m
21480
23309
  const _lazy_zaazLC = () => import('../routes/api/integrations/_id/tools.get.mjs');
21481
23310
  const _lazy_9iz_Is = () => import('../routes/api/integrations/_id/toolsets.get.mjs');
21482
23311
  const _lazy_Klwsdp = () => import('../routes/api/integrations/_id/toolsets.post.mjs');
23312
+ const _lazy_Wj3Kn6 = () => import('../routes/api/integrations/_id/variant-options.post.mjs');
21483
23313
  const _lazy_FcbHxS = () => import('../routes/api/index.get.mjs');
21484
23314
  const _lazy_0b9jj9 = () => import('../routes/api/index.post.mjs');
21485
23315
  const _lazy_g_ZIqQ = () => import('../routes/health.get.mjs');
@@ -21505,6 +23335,7 @@ const handlers = [
21505
23335
  { route: '/api/integrations/:id/tools', handler: _lazy_zaazLC, lazy: true, middleware: false, method: "get" },
21506
23336
  { route: '/api/integrations/:id/toolsets', handler: _lazy_9iz_Is, lazy: true, middleware: false, method: "get" },
21507
23337
  { route: '/api/integrations/:id/toolsets', handler: _lazy_Klwsdp, lazy: true, middleware: false, method: "post" },
23338
+ { route: '/api/integrations/:id/variant-options', handler: _lazy_Wj3Kn6, lazy: true, middleware: false, method: "post" },
21508
23339
  { route: '/api/integrations', handler: _lazy_FcbHxS, lazy: true, middleware: false, method: "get" },
21509
23340
  { route: '/api/integrations', handler: _lazy_0b9jj9, lazy: true, middleware: false, method: "post" },
21510
23341
  { route: '/health', handler: _lazy_g_ZIqQ, lazy: true, middleware: false, method: "get" },
@@ -21930,5 +23761,5 @@ trapUnhandledNodeErrors();
21930
23761
  setupGracefulShutdown(listener, nitroApp);
21931
23762
  const nodeServer = {};
21932
23763
 
21933
- export { getContext as $, setResponseStatus as A, buildAssetsURL as B, useRuntimeConfig as C, getResponseStatusText as D, getResponseStatus as E, defineRenderHandler as F, publicAssetsURL as G, getQuery as H, IntegrationProxy as I, destr as J, getRouteRules as K, joinURL as L, useNitroApp as M, serialize$1 as N, klona as O, defu as P, hasProtocol as Q, isScriptProtocol as R, SqlCredentialStore as S, parseQuery as T, defuFn as U, withQuery as V, sanitizeStatusCode as W, parseURL as X, encodePath as Y, decodePath as Z, isEqual as _, loadIntegrationCredentialConfig as a, withTrailingSlash as a0, withoutTrailingSlash as a1, $fetch$1 as a2, baseURL as a3, createHooks as a4, executeAsync as a5, hash$1 as a6, nodeServer as a7, getRouterParam as b, createError$1 as c, defineEventHandler as d, loadIntegrationToolList as e, loadIntegrationToolsets as f, getFileProcessingCapability as g, getDb as h, getIntegrationById as i, deleteToolDefinitionsForIntegration as j, getOrCreateEncryptionSecret as k, listIntegrationCatalog as l, deleteIntegrationById as m, listIntegrations as n, deleteIntegrationTypeConfig as o, findIntegrationTypeConfig as p, updateIntegrationHealth as q, refreshMcpState as r, readBody as s, checkIntegrationHealth as t, updateIntegrationCredentials as u, upsertIntegration as v, deleteToolDefinitionByName as w, applyFileProcessingCapabilityToIntegration as x, listToolDefinitionsForIntegration as y, handleMcpHttp as z };
23764
+ export { isEqual as $, handleMcpHttp as A, setResponseStatus as B, buildAssetsURL as C, useRuntimeConfig as D, getResponseStatusText as E, getResponseStatus as F, defineRenderHandler as G, publicAssetsURL as H, IntegrationProxy as I, getQuery as J, destr as K, getRouteRules as L, joinURL as M, useNitroApp as N, serialize$1 as O, klona as P, defu as Q, hasProtocol as R, SqlCredentialStore as S, isScriptProtocol as T, parseQuery as U, defuFn as V, withQuery as W, sanitizeStatusCode as X, parseURL as Y, encodePath as Z, decodePath as _, loadIntegrationCredentialConfig as a, getContext as a0, withTrailingSlash as a1, withoutTrailingSlash as a2, $fetch$1 as a3, baseURL as a4, createHooks as a5, executeAsync as a6, hash$1 as a7, nodeServer as a8, getRouterParam as b, createError$1 as c, defineEventHandler as d, loadIntegrationToolList as e, loadIntegrationToolsets as f, getFileProcessingCapability as g, getDb as h, getIntegrationById as i, deleteToolDefinitionsForIntegration as j, getOrCreateEncryptionSecret as k, listIntegrationCatalog as l, deleteIntegrationById as m, listIntegrations as n, deleteIntegrationTypeConfig as o, findIntegrationTypeConfig as p, updateIntegrationHealth as q, refreshMcpState as r, readBody as s, checkIntegrationHealth as t, updateIntegrationCredentials as u, upsertIntegration as v, deleteToolDefinitionByName as w, applyFileProcessingCapabilityToIntegration as x, listToolDefinitionsForIntegration as y, runVariantConfigListHandler as z };
21934
23765
  //# sourceMappingURL=nitro.mjs.map