@commandable/mcp 0.11.0 → 0.12.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.
@@ -4431,7 +4431,7 @@ function _expandFromEnv(value) {
4431
4431
  const _inlineRuntimeConfig = {
4432
4432
  "app": {
4433
4433
  "baseURL": "/",
4434
- "buildId": "9441a86b-16e9-4000-bffc-3b2e78e57710",
4434
+ "buildId": "886deef4-f3b5-464c-b4e2-11735eb5272e",
4435
4435
  "buildAssetsDir": "/_nuxt/",
4436
4436
  "cdnURL": ""
4437
4437
  },
@@ -5034,7 +5034,7 @@ const GENERATED_INTEGRATIONS = {
5034
5034
  }
5035
5035
  ]
5036
5036
  },
5037
- "prompt": null,
5037
+ "usageGuide": null,
5038
5038
  "variants": {
5039
5039
  "variants": {
5040
5040
  "personal_access_token": {
@@ -5480,7 +5480,7 @@ const GENERATED_INTEGRATIONS = {
5480
5480
  }
5481
5481
  ]
5482
5482
  },
5483
- "prompt": '# Confluence usage guide\n\n## Recommended workflow\n\n1. Use `list_spaces` (or `search_pages`) to discover where content lives.\n2. Use `search_pages` with CQL to find the right page ID(s).\n3. Use `read_page` to get the page content as Confluence storage format (XHTML).\n4. For edits, use `update_page` with storage XHTML in `bodyStorage` (it automatically handles version increments).\n\n## Content format\n\nAll content is exchanged in **Confluence storage format (XHTML)**. This applies to both reads (`contentStorage` field in `read_page`) and writes (`bodyStorage` field in `create_page`, `update_page`, `add_comment`).\n\nCommon markup:\n\n- Headings: `<h1>Title</h1>`, `<h2>Section</h2>`\n- Paragraphs: `<p>Text</p>`\n- Lists: `<ul><li>Item</li></ul>`, `<ol><li>Item</li></ol>`\n- Inline code: `<code>const x = 1</code>`\n- Code blocks: `<pre><code>...</code></pre>`\n- Links: `<a href="https://example.com">Example</a>`\n- Tables: `<table><tr><th>A</th></tr><tr><td>1</td></tr></table>`\n- Macros: `<ac:structured-macro ac:name="info"><ac:rich-text-body><p>Note</p></ac:rich-text-body></ac:structured-macro>`\n\nUsing XHTML natively means round-tripping pages preserves all formatting, macros, and Confluence-specific markup.\n\n`read_page` accepts `outputMarkdown: true` to return content as Markdown instead of XHTML. \n\n## CQL (Confluence Query Language) quick reference\n\nCommon patterns for `search_pages.cql`:\n\n- Restrict to a space:\n - `space = "ENG" AND type = page`\n- Title match:\n - `title ~ "runbook" AND type = page`\n- Full-text match:\n - `text ~ "oncall" AND type = page`\n- Label match:\n - `label = "runbook" AND type = page`\n- Combine filters:\n - `space = "ENG" AND type = page AND (title ~ "onboarding" OR text ~ "onboarding")`\n- Sort:\n - `... ORDER BY lastmodified DESC`\n\nTips:\n- Prefer small `limit` (e.g. 10) and paginate with `start`.\n- Use labels as a stable way to group pages for later discovery.\n\n## Page hierarchy\n\n- Spaces contain pages.\n- Pages can be nested under a parent page (`parentId`).\n- Use `get_page_children` to traverse a documentation tree (e.g. a handbook or runbook index).\n\n',
5483
+ "usageGuide": '# Confluence usage guide\n\n## Recommended workflow\n\n1. Use `list_spaces` (or `search_pages`) to discover where content lives.\n2. Use `search_pages` with CQL to find the right page ID(s).\n3. Use `read_page` to get the page content as Confluence storage format (XHTML).\n4. For edits, use `update_page` with storage XHTML in `bodyStorage` (it automatically handles version increments).\n\n## Content format\n\nAll content is exchanged in **Confluence storage format (XHTML)**. This applies to both reads (`contentStorage` field in `read_page`) and writes (`bodyStorage` field in `create_page`, `update_page`, `add_comment`).\n\nCommon markup:\n\n- Headings: `<h1>Title</h1>`, `<h2>Section</h2>`\n- Paragraphs: `<p>Text</p>`\n- Lists: `<ul><li>Item</li></ul>`, `<ol><li>Item</li></ol>`\n- Inline code: `<code>const x = 1</code>`\n- Code blocks: `<pre><code>...</code></pre>`\n- Links: `<a href="https://example.com">Example</a>`\n- Tables: `<table><tr><th>A</th></tr><tr><td>1</td></tr></table>`\n- Macros: `<ac:structured-macro ac:name="info"><ac:rich-text-body><p>Note</p></ac:rich-text-body></ac:structured-macro>`\n\nUsing XHTML natively means round-tripping pages preserves all formatting, macros, and Confluence-specific markup.\n\n`read_page` accepts `outputMarkdown: true` to return content as Markdown instead of XHTML. \n\n## CQL (Confluence Query Language) quick reference\n\nCommon patterns for `search_pages.cql`:\n\n- Restrict to a space:\n - `space = "ENG" AND type = page`\n- Title match:\n - `title ~ "runbook" AND type = page`\n- Full-text match:\n - `text ~ "oncall" AND type = page`\n- Label match:\n - `label = "runbook" AND type = page`\n- Combine filters:\n - `space = "ENG" AND type = page AND (title ~ "onboarding" OR text ~ "onboarding")`\n- Sort:\n - `... ORDER BY lastmodified DESC`\n\nTips:\n- Prefer small `limit` (e.g. 10) and paginate with `start`.\n- Use labels as a stable way to group pages for later discovery.\n\n## Page hierarchy\n\n- Spaces contain pages.\n- Pages can be nested under a parent page (`parentId`).\n- Use `get_page_children` to traverse a documentation tree (e.g. a handbook or runbook index).\n\n',
5484
5484
  "variants": {
5485
5485
  "variants": {
5486
5486
  "api_token": {
@@ -6381,7 +6381,7 @@ const GENERATED_INTEGRATIONS = {
6381
6381
  }
6382
6382
  ]
6383
6383
  },
6384
- "prompt": "# GitHub coding workflow\n\n## Branch-based workflow\n\nAlways work on a feature branch, never commit directly to main:\n\n1. `create_branch` from the default branch\n2. Make changes with `edit_file`, `edit_files`, `create_file`, or `delete_file` -- each call auto-commits to the branch\n3. `create_pull_request` when done\n4. `merge_pull_request` with `merge_method: \"squash\"` to collapse all commits into one clean commit on main\n\nMultiple small commits on a feature branch are fine -- they get squash-merged into a single commit.\n\n## Choosing the right write tool\n\n- **`edit_file`** -- Surgical edits to a single existing file. Use for most code changes. Each call is a commit.\n- **`edit_files`** -- Atomic multi-file changes (create + edit + delete in one commit). Use when files must change together to stay consistent (e.g. renaming across files, adding a module + updating imports).\n- **`create_file`** -- Create a new file or completely replace an existing file's content. Use for new files or full rewrites.\n- **`delete_file`** -- Remove a file.\n\n## Search/replace rules for edit_file and edit_files\n\nThe `old_text` field must be an **exact match** of the text currently in the file:\n\n- Whitespace matters: spaces, tabs, and indentation must match exactly\n- Line breaks matter: include the exact newline characters\n- Include enough surrounding context to uniquely identify the location\n- Each edit replaces the **first occurrence** only. To replace multiple occurrences, use separate edits.\n\n**Before editing**, call `get_file_contents` to see the file's current content. This avoids failed edits from stale or incorrect assumptions about file content.\n\n## Reading before writing\n\n- Use `get_repo_tree` to discover the project structure and file paths\n- Use `get_file_contents` to read a file before editing it\n- Use `search_code` to find where something is defined or used across the repo\n",
6384
+ "usageGuide": "# GitHub coding workflow\n\n## Branch-based workflow\n\nAlways work on a feature branch, never commit directly to main:\n\n1. `create_branch` from the default branch\n2. Make changes with `edit_file`, `edit_files`, `create_file`, or `delete_file` -- each call auto-commits to the branch\n3. `create_pull_request` when done\n4. `merge_pull_request` with `merge_method: \"squash\"` to collapse all commits into one clean commit on main\n\nMultiple small commits on a feature branch are fine -- they get squash-merged into a single commit.\n\n## Choosing the right write tool\n\n- **`edit_file`** -- Surgical edits to a single existing file. Use for most code changes. Each call is a commit.\n- **`edit_files`** -- Atomic multi-file changes (create + edit + delete in one commit). Use when files must change together to stay consistent (e.g. renaming across files, adding a module + updating imports).\n- **`create_file`** -- Create a new file or completely replace an existing file's content. Use for new files or full rewrites.\n- **`delete_file`** -- Remove a file.\n\n## Search/replace rules for edit_file and edit_files\n\nThe `old_text` field must be an **exact match** of the text currently in the file:\n\n- Whitespace matters: spaces, tabs, and indentation must match exactly\n- Line breaks matter: include the exact newline characters\n- Include enough surrounding context to uniquely identify the location\n- Each edit replaces the **first occurrence** only. To replace multiple occurrences, use separate edits.\n\n**Before editing**, call `get_file_contents` to see the file's current content. This avoids failed edits from stale or incorrect assumptions about file content.\n\n## Reading before writing\n\n- Use `get_repo_tree` to discover the project structure and file paths\n- Use `get_file_contents` to read a file before editing it\n- Use `search_code` to find where something is defined or used across the repo\n",
6385
6385
  "variants": {
6386
6386
  "variants": {
6387
6387
  "classic_pat": {
@@ -8436,7 +8436,7 @@ const GENERATED_INTEGRATIONS = {
8436
8436
  }
8437
8437
  ]
8438
8438
  },
8439
- "prompt": '## Calendar IDs\n\n- Use `calendarId=\'primary\'` for the authenticated user\'s main calendar\n- Use `list_calendars` to discover other calendar IDs (work, shared, subscribed calendars)\n- Calendar IDs typically look like email addresses (e.g. `user@example.com`) or opaque strings for subscribed calendars\n\n## Date and time format\n\nAll times must be in RFC3339 format:\n- Timed events: `\'2024-01-15T10:00:00-05:00\'` (with timezone offset) or `\'2024-01-15T15:00:00Z\'` (UTC)\n- All-day events use date-only format: `\'2024-01-15\'`\n\n## Creating events\n\nFor `create_event`, required fields are `calendarId`, `summary`, `start`, and `end`:\n\n**Timed event:**\n```json\n{\n "calendarId": "primary",\n "summary": "Team Meeting",\n "start": { "dateTime": "2024-01-15T10:00:00", "timeZone": "America/New_York" },\n "end": { "dateTime": "2024-01-15T11:00:00", "timeZone": "America/New_York" }\n}\n```\n\n**All-day event:**\n```json\n{\n "calendarId": "primary",\n "summary": "Company Holiday",\n "start": { "date": "2024-01-15" },\n "end": { "date": "2024-01-16" }\n}\n```\n\nNote: For all-day events, `end.date` should be the day *after* the last day (exclusive end).\n\n## Listing events in chronological order\n\nTo list upcoming events in start-time order (e.g. "what\'s on my calendar this week"):\n- Set `singleEvents=true` to expand recurring events into individual instances\n- Set `orderBy=\'startTime\'` (requires `singleEvents=true`)\n- Set `timeMin` to now (current ISO timestamp) and `timeMax` to the end of the desired range\n\n## Quick add\n\n`quick_add` parses natural language:\n- `"Meeting with Bob tomorrow at 3pm for 1 hour"`\n- `"Dentist appointment on Friday at 2pm"`\n- `"Weekly standup every Monday at 9am"`\n\n## Free/busy queries\n\nUse `freebusy_query` to check availability before scheduling:\n```json\n{\n "timeMin": "2024-01-15T00:00:00Z",\n "timeMax": "2024-01-15T23:59:59Z",\n "items": [{ "id": "primary" }, { "id": "colleague@example.com" }]\n}\n```\n\n## Updating events\n\n- Use `update_event` for a full replacement (all fields must be provided)\n- Use `patch_event` for partial updates (only provide the fields you want to change in `body`)\n- `patch_event` is preferred when modifying one or two fields to avoid accidentally clearing others\n',
8439
+ "usageGuide": '## Calendar IDs\n\n- Use `calendarId=\'primary\'` for the authenticated user\'s main calendar\n- Use `list_calendars` to discover other calendar IDs (work, shared, subscribed calendars)\n- Calendar IDs typically look like email addresses (e.g. `user@example.com`) or opaque strings for subscribed calendars\n\n## Date and time format\n\nAll times must be in RFC3339 format:\n- Timed events: `\'2024-01-15T10:00:00-05:00\'` (with timezone offset) or `\'2024-01-15T15:00:00Z\'` (UTC)\n- All-day events use date-only format: `\'2024-01-15\'`\n\n## Creating events\n\nFor `create_event`, required fields are `calendarId`, `summary`, `start`, and `end`:\n\n**Timed event:**\n```json\n{\n "calendarId": "primary",\n "summary": "Team Meeting",\n "start": { "dateTime": "2024-01-15T10:00:00", "timeZone": "America/New_York" },\n "end": { "dateTime": "2024-01-15T11:00:00", "timeZone": "America/New_York" }\n}\n```\n\n**All-day event:**\n```json\n{\n "calendarId": "primary",\n "summary": "Company Holiday",\n "start": { "date": "2024-01-15" },\n "end": { "date": "2024-01-16" }\n}\n```\n\nNote: For all-day events, `end.date` should be the day *after* the last day (exclusive end).\n\n## Listing events in chronological order\n\nTo list upcoming events in start-time order (e.g. "what\'s on my calendar this week"):\n- Set `singleEvents=true` to expand recurring events into individual instances\n- Set `orderBy=\'startTime\'` (requires `singleEvents=true`)\n- Set `timeMin` to now (current ISO timestamp) and `timeMax` to the end of the desired range\n\n## Quick add\n\n`quick_add` parses natural language:\n- `"Meeting with Bob tomorrow at 3pm for 1 hour"`\n- `"Dentist appointment on Friday at 2pm"`\n- `"Weekly standup every Monday at 9am"`\n\n## Free/busy queries\n\nUse `freebusy_query` to check availability before scheduling:\n```json\n{\n "timeMin": "2024-01-15T00:00:00Z",\n "timeMax": "2024-01-15T23:59:59Z",\n "items": [{ "id": "primary" }, { "id": "colleague@example.com" }]\n}\n```\n\n## Updating events\n\n- Use `update_event` for a full replacement (all fields must be provided)\n- Use `patch_event` for partial updates (only provide the fields you want to change in `body`)\n- `patch_event` is preferred when modifying one or two fields to avoid accidentally clearing others\n',
8440
8440
  "variants": {
8441
8441
  "variants": {
8442
8442
  "service_account": {
@@ -9242,7 +9242,7 @@ const GENERATED_INTEGRATIONS = {
9242
9242
  }
9243
9243
  ]
9244
9244
  },
9245
- "prompt": "## Gmail search query syntax\n\nGmail's `q` parameter supports a powerful search language. Key operators:\n\n- `is:unread` / `is:read` \u2014 filter by read status\n- `is:starred`, `is:important` \u2014 filter by markers\n- `from:user@example.com` \u2014 sender filter\n- `to:user@example.com`, `cc:user@example.com` \u2014 recipient filters\n- `subject:keyword` \u2014 subject line search\n- `has:attachment` \u2014 messages with attachments\n- `filename:report.pdf` \u2014 specific attachment filename\n- `label:INBOX` \u2014 filter by label (use label name or ID)\n- `after:2024/01/01`, `before:2024/12/31` \u2014 date range (YYYY/MM/DD)\n- `newer_than:7d`, `older_than:1y` \u2014 relative time (d=days, m=months, y=years)\n- `in:sent`, `in:drafts`, `in:trash`, `in:spam` \u2014 folder filters\n- `larger:5M`, `smaller:1M` \u2014 size filters\n\nCombine operators with spaces (implicit AND): `from:alice is:unread has:attachment`\n\n## Recommended workflows\n\n**Reading emails:**\n1. Use `list_messages` with a `q` query to find relevant message IDs\n2. Use `read_email` on each ID to get decoded subject, from, to, date, and body text\n3. For raw access or advanced format options, use `get_message` with `format='full'`\n\n**Searching for threads:**\n1. Use `list_threads` with `q` to find conversation threads\n2. Use `get_thread` to retrieve all messages in a conversation at once\n\n**Sending email:**\n- Use `send_email` for the vast majority of cases -- it accepts plain `to`, `subject`, `body` fields\n- Use `create_draft_email` + `send_draft` when you want to create a draft for review before sending\n\n**Replying to an email:**\n1. Get the original message with `read_email` to obtain its `threadId` and `id`\n2. Call `send_email` with `replyToMessageId` = original message `id` and `threadId` = original `threadId`\n3. The reply will appear in the same conversation thread\n\n## Label IDs\n\nSystem label IDs (always uppercase): `INBOX`, `UNREAD`, `STARRED`, `IMPORTANT`, `SENT`, `DRAFT`, `SPAM`, `TRASH`, `CATEGORY_PERSONAL`, `CATEGORY_SOCIAL`, `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_FORUMS`\n\nUser-created labels have auto-generated IDs. Use `list_labels` to discover them.\n\n## Archiving and organizing\n\n- Archive a message: `modify_message` with `removeLabelIds=['INBOX']`\n- Mark as read: `modify_message` with `removeLabelIds=['UNREAD']`\n- Star a message: `modify_message` with `addLabelIds=['STARRED']`\n- Apply a label: `modify_message` with `addLabelIds=['<labelId>']`\n- Use `modify_thread` to apply the same operation to all messages in a thread at once\n",
9245
+ "usageGuide": "## Gmail search query syntax\n\nGmail's `q` parameter supports a powerful search language. Key operators:\n\n- `is:unread` / `is:read` \u2014 filter by read status\n- `is:starred`, `is:important` \u2014 filter by markers\n- `from:user@example.com` \u2014 sender filter\n- `to:user@example.com`, `cc:user@example.com` \u2014 recipient filters\n- `subject:keyword` \u2014 subject line search\n- `has:attachment` \u2014 messages with attachments\n- `filename:report.pdf` \u2014 specific attachment filename\n- `label:INBOX` \u2014 filter by label (use label name or ID)\n- `after:2024/01/01`, `before:2024/12/31` \u2014 date range (YYYY/MM/DD)\n- `newer_than:7d`, `older_than:1y` \u2014 relative time (d=days, m=months, y=years)\n- `in:sent`, `in:drafts`, `in:trash`, `in:spam` \u2014 folder filters\n- `larger:5M`, `smaller:1M` \u2014 size filters\n\nCombine operators with spaces (implicit AND): `from:alice is:unread has:attachment`\n\n## Recommended workflows\n\n**Reading emails:**\n1. Use `list_messages` with a `q` query to find relevant message IDs\n2. Use `read_email` on each ID to get decoded subject, from, to, date, and body text\n3. For raw access or advanced format options, use `get_message` with `format='full'`\n\n**Searching for threads:**\n1. Use `list_threads` with `q` to find conversation threads\n2. Use `get_thread` to retrieve all messages in a conversation at once\n\n**Sending email:**\n- Use `send_email` for the vast majority of cases -- it accepts plain `to`, `subject`, `body` fields\n- Use `create_draft_email` + `send_draft` when you want to create a draft for review before sending\n\n**Replying to an email:**\n1. Get the original message with `read_email` to obtain its `threadId` and `id`\n2. Call `send_email` with `replyToMessageId` = original message `id` and `threadId` = original `threadId`\n3. The reply will appear in the same conversation thread\n\n## Label IDs\n\nSystem label IDs (always uppercase): `INBOX`, `UNREAD`, `STARRED`, `IMPORTANT`, `SENT`, `DRAFT`, `SPAM`, `TRASH`, `CATEGORY_PERSONAL`, `CATEGORY_SOCIAL`, `CATEGORY_PROMOTIONS`, `CATEGORY_UPDATES`, `CATEGORY_FORUMS`\n\nUser-created labels have auto-generated IDs. Use `list_labels` to discover them.\n\n## Archiving and organizing\n\n- Archive a message: `modify_message` with `removeLabelIds=['INBOX']`\n- Mark as read: `modify_message` with `removeLabelIds=['UNREAD']`\n- Star a message: `modify_message` with `addLabelIds=['STARRED']`\n- Apply a label: `modify_message` with `addLabelIds=['<labelId>']`\n- Use `modify_thread` to apply the same operation to all messages in a thread at once\n",
9246
9246
  "variants": {
9247
9247
  "variants": {
9248
9248
  "service_account": {
@@ -10627,7 +10627,7 @@ const GENERATED_INTEGRATIONS = {
10627
10627
  }
10628
10628
  ]
10629
10629
  },
10630
- "prompt": null,
10630
+ "usageGuide": null,
10631
10631
  "variants": {
10632
10632
  "variants": {
10633
10633
  "service_account": {
@@ -10827,11 +10827,17 @@ const GENERATED_INTEGRATIONS = {
10827
10827
  "mimeType": {
10828
10828
  "type": "string",
10829
10829
  "description": "Optional Drive MIME type from get_file_meta or search_files."
10830
+ },
10831
+ "previewPages": {
10832
+ "type": "integer",
10833
+ "minimum": 1,
10834
+ "maximum": 10,
10835
+ "description": "Number of pages to render as images and return alongside the text (PDF only). Omit or set to 0 to skip. Useful for visually checking signatures, logos, or layout."
10830
10836
  }
10831
10837
  },
10832
10838
  "additionalProperties": false
10833
10839
  },
10834
- "handlerCode": "async (input) => {\n const googleNativeExports = {\n 'application/vnd.google-apps.document': 'text/markdown',\n 'application/vnd.google-apps.spreadsheet': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.google-apps.presentation': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.google-apps.drawing': 'image/svg+xml',\n 'application/vnd.google-apps.script': 'application/vnd.google-apps.script+json',\n }\n const isTextLikeMimeType = (value) => {\n const mimeType = String(value || '').split(';', 1)[0].trim().toLowerCase()\n return mimeType.startsWith('text/')\n || mimeType.includes('json')\n || mimeType.includes('csv')\n || mimeType === 'application/xml'\n || mimeType === 'text/xml'\n || mimeType.endsWith('+xml')\n || mimeType.includes('javascript')\n || mimeType.includes('svg')\n }\n const resolveMimeType = async () => {\n if (typeof input.mimeType === 'string' && input.mimeType.trim())\n return input.mimeType.trim()\n\n const metaRes = await integration.fetch(`/files/${fileId}?fields=id,name,mimeType`)\n const meta = await metaRes.json()\n return meta?.mimeType || ''\n }\n const readTextContent = async (source) => {\n const res = await integration.fetch(source)\n const contentMimeType = res.headers?.get?.('content-type') || ''\n const content = await res.text()\n return { contentMimeType, content }\n }\n\n const fileId = encodeURIComponent(input.fileId)\n const mimeType = await resolveMimeType()\n\n if (!mimeType) {\n return {\n fileId: input.fileId,\n mimeType: null,\n content: null,\n message: 'Could not determine the Drive file MIME type.',\n }\n }\n\n if (mimeType === 'application/vnd.google-apps.folder') {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const isGoogleNative = mimeType.startsWith('application/vnd.google-apps.')\n const exportMimeType = isGoogleNative\n ? (typeof input.exportMimeType === 'string' && input.exportMimeType.trim())\n ? input.exportMimeType.trim()\n : googleNativeExports[mimeType] || null\n : null\n\n if (isGoogleNative && !exportMimeType) {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'This Google-native file type does not have a configured export path for read_file_content.',\n }\n }\n\n const source = isGoogleNative\n ? `/files/${fileId}/export?mimeType=${encodeURIComponent(exportMimeType)}`\n : `/files/${fileId}?alt=media`\n\n if (isTextLikeMimeType(exportMimeType || mimeType)) {\n const textResult = await readTextContent(source)\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: textResult.contentMimeType || exportMimeType || mimeType,\n content: textResult.content,\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source,\n })\n\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: exportMimeType || mimeType,\n ...extracted,\n }\n}",
10840
+ "handlerCode": "async (input) => {\n const googleNativeExports = {\n 'application/vnd.google-apps.document': 'text/markdown',\n 'application/vnd.google-apps.spreadsheet': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.google-apps.presentation': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.google-apps.drawing': 'image/svg+xml',\n 'application/vnd.google-apps.script': 'application/vnd.google-apps.script+json',\n }\n const isTextLikeMimeType = (value) => {\n const mimeType = String(value || '').split(';', 1)[0].trim().toLowerCase()\n return mimeType.startsWith('text/')\n || mimeType.includes('json')\n || mimeType.includes('csv')\n || mimeType === 'application/xml'\n || mimeType === 'text/xml'\n || mimeType.endsWith('+xml')\n || mimeType.includes('javascript')\n || mimeType.includes('svg')\n }\n const resolveMimeType = async () => {\n if (typeof input.mimeType === 'string' && input.mimeType.trim())\n return input.mimeType.trim()\n\n const metaRes = await integration.fetch(`/files/${fileId}?fields=id,name,mimeType`)\n const meta = await metaRes.json()\n return meta?.mimeType || ''\n }\n const readTextContent = async (source) => {\n const res = await integration.fetch(source)\n const contentMimeType = res.headers?.get?.('content-type') || ''\n const content = await res.text()\n return { contentMimeType, content }\n }\n\n const fileId = encodeURIComponent(input.fileId)\n const mimeType = await resolveMimeType()\n\n if (!mimeType) {\n return {\n fileId: input.fileId,\n mimeType: null,\n content: null,\n message: 'Could not determine the Drive file MIME type.',\n }\n }\n\n if (mimeType === 'application/vnd.google-apps.folder') {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'Folders do not have readable file content.',\n }\n }\n\n const isGoogleNative = mimeType.startsWith('application/vnd.google-apps.')\n const exportMimeType = isGoogleNative\n ? (typeof input.exportMimeType === 'string' && input.exportMimeType.trim())\n ? input.exportMimeType.trim()\n : googleNativeExports[mimeType] || null\n : null\n\n if (isGoogleNative && !exportMimeType) {\n return {\n fileId: input.fileId,\n mimeType,\n content: null,\n message: 'This Google-native file type does not have a configured export path for read_file_content.',\n }\n }\n\n const source = isGoogleNative\n ? `/files/${fileId}/export?mimeType=${encodeURIComponent(exportMimeType)}`\n : `/files/${fileId}?alt=media`\n\n if (isTextLikeMimeType(exportMimeType || mimeType)) {\n const textResult = await readTextContent(source)\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: textResult.contentMimeType || exportMimeType || mimeType,\n content: textResult.content,\n }\n }\n\n const extracted = await utils.extractFileContent({\n auth: true,\n source,\n previewPages: input.previewPages || 0,\n })\n\n return {\n fileId: input.fileId,\n mimeType,\n contentMimeType: exportMimeType || mimeType,\n ...extracted,\n }\n}",
10835
10841
  "scope": "read",
10836
10842
  "toolset": "drive"
10837
10843
  },
@@ -12523,7 +12529,7 @@ const GENERATED_INTEGRATIONS = {
12523
12529
  }
12524
12530
  ]
12525
12531
  },
12526
- "prompt": '## HubSpot guidance\n\nThis integration uses HubSpot CRM v3 object endpoints and CRM v4 association endpoints.\n\n- Prefer `search_*` tools for discovery (they support free-text `query` and structured `filters`).\n- Use `get_*` tools when you already have an object ID and want full details / associations.\n\n### Search filters\n\nAll `search_*` tools accept:\n\n- `query`: free-text search (optional)\n- `filters`: property-based filtering. Each filter is `{ propertyName, operator, value? }`.\n\nCommon operators:\n\n- `EQ`, `NEQ`\n- `LT`, `LTE`, `GT`, `GTE` (numbers or millisecond timestamps)\n- `CONTAINS_TOKEN` (tokenized contains)\n- `HAS_PROPERTY`, `NOT_HAS_PROPERTY` (value ignored)\n- `BETWEEN` (pass `value` as a string `"low,high"`; timestamps in ms recommended)\n\n### Common property names (quick reference)\n\nContacts:\n- `firstname`, `lastname`, `email`\n\nCompanies:\n- `name`, `domain`\n\nDeals:\n- `dealname`, `amount`, `pipeline`, `dealstage`, `closedate`\n\nTickets:\n- `subject`, `content`, `hs_pipeline`, `hs_pipeline_stage`\n\n### Pipelines and stages (deals/tickets)\n\nPipelines and stages are stored as IDs (not human-friendly names). Recommended workflow:\n\n1. Call `list_pipelines` with `objectType: "deals"` or `objectType: "tickets"`.\n2. Pick a pipeline ID and stage ID from the response.\n3. Use those IDs when calling `create_deal` / `update_deal` (via `pipeline` / `dealstage`) or `create_ticket` / `update_ticket` (via `hs_pipeline` / `hs_pipeline_stage`).\n\n### Associations\n\n- Use `get_associations` to list linked records (returns associated IDs).\n- Use `create_association` to link two records (default/unlabeled association).\n- Use `remove_association` to unlink records.\n\n### Engagement objects\n\nNotes:\n- Content: `hs_note_body`\n- Timestamp: `hs_timestamp` (milliseconds)\n\nTasks:\n- Subject: `hs_task_subject`\n- Body: `hs_task_body`\n- Status: `hs_task_status` (`NOT_STARTED` or `COMPLETED`)\n- Priority: `hs_task_priority` (`LOW`, `MEDIUM`, `HIGH`)\n- Due timestamp: `hs_timestamp` (milliseconds)\n\nThe `create_note` and `create_task` tools can also associate the engagement to CRM records in the same call.\n\n### Pagination\n\nHubSpot uses cursor-based pagination. When a response includes `paging.next.after`, pass that value back as `after` in your next call.\n\n',
12532
+ "usageGuide": '## HubSpot guidance\n\nThis integration uses HubSpot CRM v3 object endpoints and CRM v4 association endpoints.\n\n- Prefer `search_*` tools for discovery (they support free-text `query` and structured `filters`).\n- Use `get_*` tools when you already have an object ID and want full details / associations.\n\n### Search filters\n\nAll `search_*` tools accept:\n\n- `query`: free-text search (optional)\n- `filters`: property-based filtering. Each filter is `{ propertyName, operator, value? }`.\n\nCommon operators:\n\n- `EQ`, `NEQ`\n- `LT`, `LTE`, `GT`, `GTE` (numbers or millisecond timestamps)\n- `CONTAINS_TOKEN` (tokenized contains)\n- `HAS_PROPERTY`, `NOT_HAS_PROPERTY` (value ignored)\n- `BETWEEN` (pass `value` as a string `"low,high"`; timestamps in ms recommended)\n\n### Common property names (quick reference)\n\nContacts:\n- `firstname`, `lastname`, `email`\n\nCompanies:\n- `name`, `domain`\n\nDeals:\n- `dealname`, `amount`, `pipeline`, `dealstage`, `closedate`\n\nTickets:\n- `subject`, `content`, `hs_pipeline`, `hs_pipeline_stage`\n\n### Pipelines and stages (deals/tickets)\n\nPipelines and stages are stored as IDs (not human-friendly names). Recommended workflow:\n\n1. Call `list_pipelines` with `objectType: "deals"` or `objectType: "tickets"`.\n2. Pick a pipeline ID and stage ID from the response.\n3. Use those IDs when calling `create_deal` / `update_deal` (via `pipeline` / `dealstage`) or `create_ticket` / `update_ticket` (via `hs_pipeline` / `hs_pipeline_stage`).\n\n### Associations\n\n- Use `get_associations` to list linked records (returns associated IDs).\n- Use `create_association` to link two records (default/unlabeled association).\n- Use `remove_association` to unlink records.\n\n### Engagement objects\n\nNotes:\n- Content: `hs_note_body`\n- Timestamp: `hs_timestamp` (milliseconds)\n\nTasks:\n- Subject: `hs_task_subject`\n- Body: `hs_task_body`\n- Status: `hs_task_status` (`NOT_STARTED` or `COMPLETED`)\n- Priority: `hs_task_priority` (`LOW`, `MEDIUM`, `HIGH`)\n- Due timestamp: `hs_timestamp` (milliseconds)\n\nThe `create_note` and `create_task` tools can also associate the engagement to CRM records in the same call.\n\n### Pagination\n\nHubSpot uses cursor-based pagination. When a response includes `paging.next.after`, pass that value back as `after` in your next call.\n\n',
12527
12533
  "variants": {
12528
12534
  "variants": {
12529
12535
  "private_app_token": {
@@ -14100,7 +14106,7 @@ const GENERATED_INTEGRATIONS = {
14100
14106
  }
14101
14107
  ]
14102
14108
  },
14103
- "prompt": "# Jira usage guide\n\n## Core workflow patterns\n\n### Discover projects and issue types (before creating issues)\n\n1. Call `list_projects` to find the project key (e.g. `PROJ`).\n2. Call `get_project` with `projectIdOrKey=PROJ` to see available `issueTypes`.\n3. Use the returned issue type name with `create_issue.issueTypeName`.\n\n### Search issues (JQL)\n\nUse `search_issues` with JQL. Common examples:\n\n- My open issues:\n - `assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC`\n- Recently updated issues in a project:\n - `project = PROJ ORDER BY updated DESC`\n- Unassigned bugs:\n - `project = PROJ AND issuetype = Bug AND assignee is EMPTY ORDER BY created DESC`\n- Blocked issues (label-based):\n - `project = PROJ AND labels = blocked ORDER BY priority DESC, updated DESC`\n\nPagination:\n- `search_issues` uses `nextPageToken`. If `nextPageToken` is returned and `isLast=false`, pass it back to get the next page.\n\n### Read issue content\n\n- Use `get_issue` to read a compact issue summary.\n- `get_issue` converts Jira's ADF description into `descriptionMarkdown` when possible (fallback: `descriptionText`).\n- Use `get_issue_comments` to read the comment thread (comment bodies are converted to Markdown).\n\n### Transition an issue (change status)\n\nJira workflows are project-specific, so you must discover valid transitions:\n\n1. Call `get_transitions` to see available transition names/IDs for the issue.\n2. Call `transition_issue` using either `transitionId` (preferred) or `transitionName`.\n\n### Assigning issues\n\n1. Call `search_users` to find the user's `accountId`.\n2. Assign:\n - `assign_issue { issueIdOrKey, accountId }`\n3. Unassign:\n - `assign_issue { issueIdOrKey, accountId: null }`\n\n## Notes on Jira rich text (ADF)\n\nJira Cloud REST API v3 uses **Atlassian Document Format (ADF)** for fields like `description` and comment bodies.\n\n- Read tools convert ADF to Markdown so you can read it directly.\n- Write tools accept **Markdown** in their `*Text` fields and convert it to ADF (`descriptionText`, `bodyText`, `commentText`).\n\n### Round-trip Markdown workflow (recommended)\n\nYou can safely do a Markdown round-trip:\n\n1. `get_issue` -> edit `descriptionMarkdown`\n2. `update_issue { descriptionText: <your edited Markdown> }`\n\nSupported Markdown features when writing:\n\n- Headings (`#`, `##`, ...)\n- Bold/italic/strikethrough (`**bold**`, `*italic*`, `~~strike~~`)\n- Inline code and fenced code blocks (```), including optional language fences\n- Lists (ordered/unordered), nested lists\n- Blockquotes (`>`)\n- Horizontal rules (`---`)\n- Tables\n- Links (`[text](url)`)\n\n## Boards & sprints\n\nIf you\u2019re using Jira Software:\n\n1. Call `list_boards` (optionally filter by `projectKeyOrId`).\n2. Call `list_sprints` for a board to find active/future sprints.\n3. Use `move_issues_to_sprint` to pull work into a sprint (sprint planning).\n\n",
14109
+ "usageGuide": "# Jira usage guide\n\n## Core workflow patterns\n\n### Discover projects and issue types (before creating issues)\n\n1. Call `list_projects` to find the project key (e.g. `PROJ`).\n2. Call `get_project` with `projectIdOrKey=PROJ` to see available `issueTypes`.\n3. Use the returned issue type name with `create_issue.issueTypeName`.\n\n### Search issues (JQL)\n\nUse `search_issues` with JQL. Common examples:\n\n- My open issues:\n - `assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC`\n- Recently updated issues in a project:\n - `project = PROJ ORDER BY updated DESC`\n- Unassigned bugs:\n - `project = PROJ AND issuetype = Bug AND assignee is EMPTY ORDER BY created DESC`\n- Blocked issues (label-based):\n - `project = PROJ AND labels = blocked ORDER BY priority DESC, updated DESC`\n\nPagination:\n- `search_issues` uses `nextPageToken`. If `nextPageToken` is returned and `isLast=false`, pass it back to get the next page.\n\n### Read issue content\n\n- Use `get_issue` to read a compact issue summary.\n- `get_issue` converts Jira's ADF description into `descriptionMarkdown` when possible (fallback: `descriptionText`).\n- Use `get_issue_comments` to read the comment thread (comment bodies are converted to Markdown).\n\n### Transition an issue (change status)\n\nJira workflows are project-specific, so you must discover valid transitions:\n\n1. Call `get_transitions` to see available transition names/IDs for the issue.\n2. Call `transition_issue` using either `transitionId` (preferred) or `transitionName`.\n\n### Assigning issues\n\n1. Call `search_users` to find the user's `accountId`.\n2. Assign:\n - `assign_issue { issueIdOrKey, accountId }`\n3. Unassign:\n - `assign_issue { issueIdOrKey, accountId: null }`\n\n## Notes on Jira rich text (ADF)\n\nJira Cloud REST API v3 uses **Atlassian Document Format (ADF)** for fields like `description` and comment bodies.\n\n- Read tools convert ADF to Markdown so you can read it directly.\n- Write tools accept **Markdown** in their `*Text` fields and convert it to ADF (`descriptionText`, `bodyText`, `commentText`).\n\n### Round-trip Markdown workflow (recommended)\n\nYou can safely do a Markdown round-trip:\n\n1. `get_issue` -> edit `descriptionMarkdown`\n2. `update_issue { descriptionText: <your edited Markdown> }`\n\nSupported Markdown features when writing:\n\n- Headings (`#`, `##`, ...)\n- Bold/italic/strikethrough (`**bold**`, `*italic*`, `~~strike~~`)\n- Inline code and fenced code blocks (```), including optional language fences\n- Lists (ordered/unordered), nested lists\n- Blockquotes (`>`)\n- Horizontal rules (`---`)\n- Tables\n- Links (`[text](url)`)\n\n## Boards & sprints\n\nIf you\u2019re using Jira Software:\n\n1. Call `list_boards` (optionally filter by `projectKeyOrId`).\n2. Call `list_sprints` for a board to find active/future sprints.\n3. Use `move_issues_to_sprint` to pull work into a sprint (sprint planning).\n\n",
14104
14110
  "variants": {
14105
14111
  "variants": {
14106
14112
  "api_token": {
@@ -15188,7 +15194,7 @@ const GENERATED_INTEGRATIONS = {
15188
15194
  }
15189
15195
  ]
15190
15196
  },
15191
- "prompt": '## Appending paragraph blocks\n\nWhen appending a paragraph block to a Notion page, ensure the `rich_text` field is correctly defined within the `paragraph` type. Example format:\n\n```json\n{\n "block_id": "<page_id>",\n "children": [\n {\n "object": "block",\n "type": "paragraph",\n "paragraph": {\n "rich_text": [\n {\n "type": "text",\n "text": {\n "content": "Your text here"\n }\n }\n ]\n }\n }\n ]\n}\n```\n\n',
15197
+ "usageGuide": '## Appending paragraph blocks\n\nWhen appending a paragraph block to a Notion page, ensure the `rich_text` field is correctly defined within the `paragraph` type. Example format:\n\n```json\n{\n "block_id": "<page_id>",\n "children": [\n {\n "object": "block",\n "type": "paragraph",\n "paragraph": {\n "rich_text": [\n {\n "type": "text",\n "text": {\n "content": "Your text here"\n }\n }\n ]\n }\n }\n ]\n}\n```\n\n',
15192
15198
  "variants": {
15193
15199
  "variants": {
15194
15200
  "internal_integration": {
@@ -15929,7 +15935,7 @@ const GENERATED_INTEGRATIONS = {
15929
15935
  }
15930
15936
  ]
15931
15937
  },
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",
15938
+ "usageGuide": "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",
15933
15939
  "variants": {
15934
15940
  "variants": {
15935
15941
  "app_credentials": {
@@ -16043,44 +16049,481 @@ const GENERATED_INTEGRATIONS = {
16043
16049
  "description": "Microsoft Graph SharePoint site ID."
16044
16050
  }
16045
16051
  },
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"
16052
+ "additionalProperties": false
16053
+ },
16054
+ "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}",
16055
+ "scope": "read"
16056
+ },
16057
+ {
16058
+ "name": "list_site_drives",
16059
+ "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.",
16060
+ "inputSchema": {
16061
+ "$schema": "http://json-schema.org/draft-07/schema#",
16062
+ "type": "object",
16063
+ "required": [
16064
+ "siteId"
16065
+ ],
16066
+ "properties": {
16067
+ "siteId": {
16068
+ "type": "string",
16069
+ "description": "Microsoft Graph SharePoint site ID."
16070
+ },
16071
+ "top": {
16072
+ "type": "integer",
16073
+ "minimum": 1,
16074
+ "maximum": 200,
16075
+ "description": "Maximum number of drives to return."
16076
+ },
16077
+ "includeSystem": {
16078
+ "type": "boolean",
16079
+ "description": "Set true to include hidden/system drives."
16080
+ }
16081
+ },
16082
+ "additionalProperties": false
16083
+ },
16084
+ "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}",
16085
+ "scope": "read"
16086
+ },
16087
+ {
16088
+ "name": "list_drive_children",
16089
+ "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.",
16090
+ "inputSchema": {
16091
+ "$schema": "http://json-schema.org/draft-07/schema#",
16092
+ "type": "object",
16093
+ "required": [
16094
+ "driveId"
16095
+ ],
16096
+ "properties": {
16097
+ "driveId": {
16098
+ "type": "string",
16099
+ "description": "Document library drive ID."
16100
+ },
16101
+ "itemId": {
16102
+ "type": "string",
16103
+ "description": "Optional folder item ID. Omit to list the drive root."
16104
+ },
16105
+ "top": {
16106
+ "type": "integer",
16107
+ "minimum": 1,
16108
+ "maximum": 200,
16109
+ "description": "Maximum number of children to return."
16110
+ },
16111
+ "orderBy": {
16112
+ "type": "string",
16113
+ "description": "Optional Microsoft Graph orderBy expression, such as name or lastModifiedDateTime desc."
16114
+ }
16115
+ },
16116
+ "additionalProperties": false
16117
+ },
16118
+ "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}",
16119
+ "scope": "read"
16120
+ },
16121
+ {
16122
+ "name": "get_drive_item_meta",
16123
+ "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.",
16124
+ "inputSchema": {
16125
+ "$schema": "http://json-schema.org/draft-07/schema#",
16126
+ "type": "object",
16127
+ "required": [
16128
+ "driveId",
16129
+ "itemId"
16130
+ ],
16131
+ "properties": {
16132
+ "driveId": {
16133
+ "type": "string",
16134
+ "description": "Document library drive ID."
16135
+ },
16136
+ "itemId": {
16137
+ "type": "string",
16138
+ "description": "Drive item ID for the file or folder."
16139
+ }
16140
+ },
16141
+ "additionalProperties": false
16142
+ },
16143
+ "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}",
16144
+ "scope": "read"
16145
+ },
16146
+ {
16147
+ "name": "search_files",
16148
+ "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.",
16149
+ "inputSchema": {
16150
+ "$schema": "http://json-schema.org/draft-07/schema#",
16151
+ "type": "object",
16152
+ "required": [
16153
+ "query"
16154
+ ],
16155
+ "properties": {
16156
+ "query": {
16157
+ "type": "string",
16158
+ "description": "Search query string. Microsoft Graph KQL syntax is supported."
16159
+ },
16160
+ "siteId": {
16161
+ "type": "string",
16162
+ "description": "Optional site ID to keep only hits from a specific SharePoint site."
16163
+ },
16164
+ "driveId": {
16165
+ "type": "string",
16166
+ "description": "Optional drive ID to keep only hits from a specific document library."
16167
+ },
16168
+ "from": {
16169
+ "type": "integer",
16170
+ "minimum": 0,
16171
+ "description": "Offset into the Graph search results."
16172
+ },
16173
+ "size": {
16174
+ "type": "integer",
16175
+ "minimum": 1,
16176
+ "maximum": 50,
16177
+ "description": "Maximum number of hits to request from Graph."
16178
+ }
16179
+ },
16180
+ "additionalProperties": false
16181
+ },
16182
+ "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}",
16183
+ "scope": "read"
16184
+ },
16185
+ {
16186
+ "name": "read_file_content",
16187
+ "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.",
16188
+ "inputSchema": {
16189
+ "$schema": "http://json-schema.org/draft-07/schema#",
16190
+ "type": "object",
16191
+ "required": [
16192
+ "driveId",
16193
+ "itemId"
16194
+ ],
16195
+ "properties": {
16196
+ "driveId": {
16197
+ "type": "string",
16198
+ "description": "Document library drive ID."
16199
+ },
16200
+ "itemId": {
16201
+ "type": "string",
16202
+ "description": "Drive item ID for the file."
16203
+ },
16204
+ "mimeType": {
16205
+ "type": "string",
16206
+ "description": "Optional MIME type from get_drive_item_meta or search_files."
16207
+ },
16208
+ "previewPages": {
16209
+ "type": "integer",
16210
+ "minimum": 1,
16211
+ "maximum": 10,
16212
+ "description": "Number of pages to render as images and return alongside the text (PDF only). Omit or set to 0 to skip. Useful for visually checking signatures, logos, or layout."
16213
+ }
16214
+ },
16215
+ "additionalProperties": false
16216
+ },
16217
+ "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 previewPages: input.previewPages || 0,\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}",
16218
+ "scope": "read"
16219
+ },
16220
+ {
16221
+ "name": "create_folder",
16222
+ "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.",
16223
+ "inputSchema": {
16224
+ "$schema": "http://json-schema.org/draft-07/schema#",
16225
+ "type": "object",
16226
+ "required": [
16227
+ "driveId",
16228
+ "name"
16229
+ ],
16230
+ "properties": {
16231
+ "driveId": {
16232
+ "type": "string",
16233
+ "description": "Document library drive ID."
16234
+ },
16235
+ "name": {
16236
+ "type": "string",
16237
+ "description": "Folder name to create."
16238
+ },
16239
+ "parentItemId": {
16240
+ "type": "string",
16241
+ "description": "Optional parent folder item ID. Omit to create in the drive root."
16242
+ },
16243
+ "conflictBehavior": {
16244
+ "type": "string",
16245
+ "enum": [
16246
+ "rename",
16247
+ "replace",
16248
+ "fail"
16249
+ ],
16250
+ "description": "How Graph should handle name conflicts. Defaults to rename."
16251
+ }
16252
+ },
16253
+ "additionalProperties": false
16254
+ },
16255
+ "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}",
16256
+ "scope": "write"
16257
+ },
16258
+ {
16259
+ "name": "move_drive_item",
16260
+ "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.",
16261
+ "inputSchema": {
16262
+ "$schema": "http://json-schema.org/draft-07/schema#",
16263
+ "type": "object",
16264
+ "required": [
16265
+ "driveId",
16266
+ "itemId",
16267
+ "destinationParentId"
16268
+ ],
16269
+ "properties": {
16270
+ "driveId": {
16271
+ "type": "string",
16272
+ "description": "Document library drive ID."
16273
+ },
16274
+ "itemId": {
16275
+ "type": "string",
16276
+ "description": "Drive item ID for the file or folder to move."
16277
+ },
16278
+ "destinationParentId": {
16279
+ "type": "string",
16280
+ "description": "Destination folder item ID."
16281
+ },
16282
+ "newName": {
16283
+ "type": "string",
16284
+ "description": "Optional new item name to apply during the move."
16285
+ }
16286
+ },
16287
+ "additionalProperties": false
16288
+ },
16289
+ "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}",
16290
+ "scope": "write"
16291
+ },
16292
+ {
16293
+ "name": "delete_drive_item",
16294
+ "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.",
16295
+ "inputSchema": {
16296
+ "$schema": "http://json-schema.org/draft-07/schema#",
16297
+ "type": "object",
16298
+ "required": [
16299
+ "driveId",
16300
+ "itemId"
16301
+ ],
16302
+ "properties": {
16303
+ "driveId": {
16304
+ "type": "string",
16305
+ "description": "Document library drive ID."
16306
+ },
16307
+ "itemId": {
16308
+ "type": "string",
16309
+ "description": "Drive item ID for the file or folder to delete."
16310
+ }
16311
+ },
16312
+ "additionalProperties": false
16313
+ },
16314
+ "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}",
16315
+ "scope": "write"
16316
+ }
16317
+ ],
16318
+ "variantOwnerType": null
16319
+ },
16320
+ "sharepoint-folder": {
16321
+ "manifest": {
16322
+ "name": "sharepoint",
16323
+ "version": "0.1.0",
16324
+ "baseUrl": "https://graph.microsoft.com/v1.0",
16325
+ "variantLabel": "Single folder",
16326
+ "variantConfig": [
16327
+ {
16328
+ "key": "site",
16329
+ "label": "Site",
16330
+ "selectionMode": "single",
16331
+ "listHandler": "async (config) => {\n const res = await integration.fetch('/sites?search=*&$select=id,displayName,name&$top=50')\n const data = await res.json()\n if (!Array.isArray(data?.value)) return []\n return data.value\n .map(site => ({ id: site.id, name: site.displayName || site.name || site.id }))\n .filter(s => s.id && s.name)\n}"
16332
+ },
16333
+ {
16334
+ "key": "drive",
16335
+ "label": "Document library",
16336
+ "selectionMode": "single",
16337
+ "listHandler": "async (config) => {\n if (!config.siteId) return []\n const res = await integration.fetch(`/sites/${encodeURIComponent(config.siteId)}/drives?$select=id,name,driveType,system`)\n const data = await res.json()\n if (!Array.isArray(data?.value)) return []\n return data.value\n .filter(d => !d.system)\n .map(d => ({ id: d.id, name: d.name || d.id }))\n .filter(d => d.id && d.name)\n}"
16338
+ },
16339
+ {
16340
+ "key": "folder",
16341
+ "label": "Folder",
16342
+ "selectionMode": "single",
16343
+ "listHandler": "async (config) => {\n if (!config.driveId) return []\n const res = await integration.fetch(`/drives/${encodeURIComponent(config.driveId)}/root/children?$select=id,name,folder&$top=100`)\n const data = await res.json()\n if (!Array.isArray(data?.value)) return []\n return data.value\n .filter(item => item.folder)\n .map(item => ({ id: item.id, name: item.name || item.id }))\n .filter(item => item.id && item.name)\n}"
16344
+ }
16345
+ ],
16346
+ "tools": [
16347
+ {
16348
+ "name": "list_folder",
16349
+ "description": "List files and folders inside the connected SharePoint folder.",
16350
+ "inputSchema": "../../schemas/list_drive_children.json",
16351
+ "handler": "../../handlers/list_drive_children.js",
16352
+ "scope": "read",
16353
+ "injectFromConfig": {
16354
+ "driveId": "driveId",
16355
+ "itemId": "folderId"
16356
+ }
16357
+ },
16358
+ {
16359
+ "name": "browse_folder",
16360
+ "description": "List files and folders inside a sub-folder by item ID. Use list_folder first to discover sub-folder IDs.",
16361
+ "inputSchema": "../../schemas/list_drive_children.json",
16362
+ "handler": "../../handlers/list_drive_children.js",
16363
+ "scope": "read",
16364
+ "injectFromConfig": {
16365
+ "driveId": "driveId"
16366
+ }
16367
+ },
16368
+ {
16369
+ "name": "get_drive_item_meta",
16370
+ "description": "Get metadata for a file or folder by item ID.",
16371
+ "inputSchema": "../../schemas/get_drive_item.json",
16372
+ "handler": "../../handlers/get_drive_item.js",
16373
+ "scope": "read",
16374
+ "injectFromConfig": {
16375
+ "driveId": "driveId"
16376
+ }
16377
+ },
16378
+ {
16379
+ "name": "search_files",
16380
+ "description": "Search files within the connected document library. KQL syntax is supported.",
16381
+ "inputSchema": "../../schemas/search_files.json",
16382
+ "handler": "../../handlers/search_files.js",
16383
+ "scope": "read",
16384
+ "injectFromConfig": {
16385
+ "siteId": "siteId",
16386
+ "driveId": "driveId"
16387
+ }
16388
+ },
16389
+ {
16390
+ "name": "read_file_content",
16391
+ "description": "Read a file's contents. Provide the file item ID. Use list_folder or search_files to discover file IDs.",
16392
+ "inputSchema": "../../schemas/read_file_content.json",
16393
+ "handler": "../../handlers/read_file_content.js",
16394
+ "scope": "read",
16395
+ "injectFromConfig": {
16396
+ "driveId": "driveId"
16397
+ }
16398
+ },
16399
+ {
16400
+ "name": "create_folder",
16401
+ "description": "Create a new sub-folder inside the connected folder. Omit parentItemId to create directly inside the connected folder.",
16402
+ "inputSchema": "../../schemas/create_folder.json",
16403
+ "handler": "../../handlers/create_folder.js",
16404
+ "scope": "write",
16405
+ "injectFromConfig": {
16406
+ "driveId": "driveId",
16407
+ "parentItemId": "folderId"
16408
+ }
16409
+ },
16410
+ {
16411
+ "name": "move_drive_item",
16412
+ "description": "Move a file or folder to a different parent folder within the document library.",
16413
+ "inputSchema": "../../schemas/move_drive_item.json",
16414
+ "handler": "../../handlers/move_drive_item.js",
16415
+ "scope": "write",
16416
+ "injectFromConfig": {
16417
+ "driveId": "driveId"
16418
+ }
16419
+ },
16420
+ {
16421
+ "name": "delete_drive_item",
16422
+ "description": "Delete a file or folder by item ID.",
16423
+ "inputSchema": "../../schemas/delete_drive_item.json",
16424
+ "handler": "../../handlers/delete_drive_item.js",
16425
+ "scope": "write",
16426
+ "injectFromConfig": {
16427
+ "driveId": "driveId"
16428
+ }
16429
+ }
16430
+ ]
16431
+ },
16432
+ "usageGuide": "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",
16433
+ "variants": {
16434
+ "variants": {
16435
+ "app_credentials": {
16436
+ "label": "Microsoft Graph App Credentials",
16437
+ "schema": {
16438
+ "type": "object",
16439
+ "properties": {
16440
+ "tenantId": {
16441
+ "type": "string",
16442
+ "title": "Tenant ID",
16443
+ "description": "Microsoft Entra tenant ID (GUID) that owns the SharePoint tenant."
16444
+ },
16445
+ "clientId": {
16446
+ "type": "string",
16447
+ "title": "Client ID",
16448
+ "description": "Application (client) ID of the Microsoft Entra app registration."
16449
+ },
16450
+ "clientSecret": {
16451
+ "type": "string",
16452
+ "title": "Client Secret",
16453
+ "description": "Client secret value for the Microsoft Entra app registration.",
16454
+ "format": "password"
16455
+ }
16456
+ },
16457
+ "required": [
16458
+ "tenantId",
16459
+ "clientId",
16460
+ "clientSecret"
16461
+ ],
16462
+ "additionalProperties": false
16463
+ },
16464
+ "preprocess": {
16465
+ "type": "handler",
16466
+ "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}",
16467
+ "allowedOrigins": [
16468
+ "https://login.microsoftonline.com"
16469
+ ]
16470
+ },
16471
+ "injection": {
16472
+ "headers": {
16473
+ "Authorization": "Bearer {{token}}"
16474
+ }
16475
+ },
16476
+ "healthCheck": {
16477
+ "notViable": true
16478
+ }
16479
+ }
16050
16480
  },
16481
+ "default": "app_credentials"
16482
+ },
16483
+ "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.",
16484
+ "hintsByVariant": {},
16485
+ "tools": [
16051
16486
  {
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.",
16487
+ "name": "list_folder",
16488
+ "description": "List files and folders inside the connected SharePoint folder.",
16054
16489
  "inputSchema": {
16055
16490
  "$schema": "http://json-schema.org/draft-07/schema#",
16056
16491
  "type": "object",
16057
16492
  "required": [
16058
- "siteId"
16493
+ "driveId"
16059
16494
  ],
16060
16495
  "properties": {
16061
- "siteId": {
16496
+ "driveId": {
16062
16497
  "type": "string",
16063
- "description": "Microsoft Graph SharePoint site ID."
16498
+ "description": "Document library drive ID."
16499
+ },
16500
+ "itemId": {
16501
+ "type": "string",
16502
+ "description": "Optional folder item ID. Omit to list the drive root."
16064
16503
  },
16065
16504
  "top": {
16066
16505
  "type": "integer",
16067
16506
  "minimum": 1,
16068
16507
  "maximum": 200,
16069
- "description": "Maximum number of drives to return."
16508
+ "description": "Maximum number of children to return."
16070
16509
  },
16071
- "includeSystem": {
16072
- "type": "boolean",
16073
- "description": "Set true to include hidden/system drives."
16510
+ "orderBy": {
16511
+ "type": "string",
16512
+ "description": "Optional Microsoft Graph orderBy expression, such as name or lastModifiedDateTime desc."
16074
16513
  }
16075
16514
  },
16076
16515
  "additionalProperties": false
16077
16516
  },
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"
16517
+ "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}",
16518
+ "scope": "read",
16519
+ "injectFromConfig": {
16520
+ "driveId": "driveId",
16521
+ "itemId": "folderId"
16522
+ }
16080
16523
  },
16081
16524
  {
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.",
16525
+ "name": "browse_folder",
16526
+ "description": "List files and folders inside a sub-folder by item ID. Use list_folder first to discover sub-folder IDs.",
16084
16527
  "inputSchema": {
16085
16528
  "$schema": "http://json-schema.org/draft-07/schema#",
16086
16529
  "type": "object",
@@ -16110,11 +16553,14 @@ const GENERATED_INTEGRATIONS = {
16110
16553
  "additionalProperties": false
16111
16554
  },
16112
16555
  "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"
16556
+ "scope": "read",
16557
+ "injectFromConfig": {
16558
+ "driveId": "driveId"
16559
+ }
16114
16560
  },
16115
16561
  {
16116
16562
  "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.",
16563
+ "description": "Get metadata for a file or folder by item ID.",
16118
16564
  "inputSchema": {
16119
16565
  "$schema": "http://json-schema.org/draft-07/schema#",
16120
16566
  "type": "object",
@@ -16135,11 +16581,14 @@ const GENERATED_INTEGRATIONS = {
16135
16581
  "additionalProperties": false
16136
16582
  },
16137
16583
  "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"
16584
+ "scope": "read",
16585
+ "injectFromConfig": {
16586
+ "driveId": "driveId"
16587
+ }
16139
16588
  },
16140
16589
  {
16141
16590
  "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.",
16591
+ "description": "Search files within the connected document library. KQL syntax is supported.",
16143
16592
  "inputSchema": {
16144
16593
  "$schema": "http://json-schema.org/draft-07/schema#",
16145
16594
  "type": "object",
@@ -16174,11 +16623,15 @@ const GENERATED_INTEGRATIONS = {
16174
16623
  "additionalProperties": false
16175
16624
  },
16176
16625
  "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"
16626
+ "scope": "read",
16627
+ "injectFromConfig": {
16628
+ "siteId": "siteId",
16629
+ "driveId": "driveId"
16630
+ }
16178
16631
  },
16179
16632
  {
16180
16633
  "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.",
16634
+ "description": "Read a file's contents. Provide the file item ID. Use list_folder or search_files to discover file IDs.",
16182
16635
  "inputSchema": {
16183
16636
  "$schema": "http://json-schema.org/draft-07/schema#",
16184
16637
  "type": "object",
@@ -16198,16 +16651,25 @@ const GENERATED_INTEGRATIONS = {
16198
16651
  "mimeType": {
16199
16652
  "type": "string",
16200
16653
  "description": "Optional MIME type from get_drive_item_meta or search_files."
16654
+ },
16655
+ "previewPages": {
16656
+ "type": "integer",
16657
+ "minimum": 1,
16658
+ "maximum": 10,
16659
+ "description": "Number of pages to render as images and return alongside the text (PDF only). Omit or set to 0 to skip. Useful for visually checking signatures, logos, or layout."
16201
16660
  }
16202
16661
  },
16203
16662
  "additionalProperties": false
16204
16663
  },
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"
16664
+ "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 previewPages: input.previewPages || 0,\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}",
16665
+ "scope": "read",
16666
+ "injectFromConfig": {
16667
+ "driveId": "driveId"
16668
+ }
16207
16669
  },
16208
16670
  {
16209
16671
  "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.",
16672
+ "description": "Create a new sub-folder inside the connected folder. Omit parentItemId to create directly inside the connected folder.",
16211
16673
  "inputSchema": {
16212
16674
  "$schema": "http://json-schema.org/draft-07/schema#",
16213
16675
  "type": "object",
@@ -16241,11 +16703,15 @@ const GENERATED_INTEGRATIONS = {
16241
16703
  "additionalProperties": false
16242
16704
  },
16243
16705
  "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"
16706
+ "scope": "write",
16707
+ "injectFromConfig": {
16708
+ "driveId": "driveId",
16709
+ "parentItemId": "folderId"
16710
+ }
16245
16711
  },
16246
16712
  {
16247
16713
  "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.",
16714
+ "description": "Move a file or folder to a different parent folder within the document library.",
16249
16715
  "inputSchema": {
16250
16716
  "$schema": "http://json-schema.org/draft-07/schema#",
16251
16717
  "type": "object",
@@ -16275,11 +16741,14 @@ const GENERATED_INTEGRATIONS = {
16275
16741
  "additionalProperties": false
16276
16742
  },
16277
16743
  "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"
16744
+ "scope": "write",
16745
+ "injectFromConfig": {
16746
+ "driveId": "driveId"
16747
+ }
16279
16748
  },
16280
16749
  {
16281
16750
  "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.",
16751
+ "description": "Delete a file or folder by item ID.",
16283
16752
  "inputSchema": {
16284
16753
  "$schema": "http://json-schema.org/draft-07/schema#",
16285
16754
  "type": "object",
@@ -16300,10 +16769,13 @@ const GENERATED_INTEGRATIONS = {
16300
16769
  "additionalProperties": false
16301
16770
  },
16302
16771
  "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"
16772
+ "scope": "write",
16773
+ "injectFromConfig": {
16774
+ "driveId": "driveId"
16775
+ }
16304
16776
  }
16305
16777
  ],
16306
- "variantOwnerType": null
16778
+ "variantOwnerType": "sharepoint"
16307
16779
  },
16308
16780
  "trello": {
16309
16781
  "manifest": {
@@ -16551,7 +17023,7 @@ const GENERATED_INTEGRATIONS = {
16551
17023
  }
16552
17024
  ]
16553
17025
  },
16554
- "prompt": null,
17026
+ "usageGuide": null,
16555
17027
  "variants": {
16556
17028
  "variants": {
16557
17029
  "api_key_token": {
@@ -17532,7 +18004,7 @@ const GENERATED_INTEGRATIONS = {
17532
18004
  }
17533
18005
  ]
17534
18006
  },
17535
- "prompt": null,
18007
+ "usageGuide": null,
17536
18008
  "variants": {
17537
18009
  "variants": {
17538
18010
  "api_key_token": {
@@ -18250,9 +18722,9 @@ function loadIntegrationManifest(type) {
18250
18722
  const entry = getIntegration(type);
18251
18723
  return entry ? cloneManifest(entry.manifest) : null;
18252
18724
  }
18253
- function loadIntegrationPrompt(type) {
18725
+ function loadIntegrationUsageGuide(type) {
18254
18726
  var _a, _b;
18255
- return (_b = (_a = getIntegration(type)) == null ? void 0 : _a.prompt) != null ? _b : null;
18727
+ return (_b = (_a = getIntegration(type)) == null ? void 0 : _a.usageGuide) != null ? _b : null;
18256
18728
  }
18257
18729
  function loadIntegrationToolsets(type) {
18258
18730
  var _a;
@@ -18457,6 +18929,35 @@ function makeIntegrationToolName(type, name, nodeId) {
18457
18929
  return `${base}${suffix}`;
18458
18930
  }
18459
18931
 
18932
+ const hoistedArtifactsByResult = /* @__PURE__ */ new WeakMap();
18933
+ function attachHoistedArtifacts(result, artifacts) {
18934
+ if (artifacts.length)
18935
+ hoistedArtifactsByResult.set(result, artifacts);
18936
+ return result;
18937
+ }
18938
+ function getHoistedArtifacts(result) {
18939
+ var _a;
18940
+ return (_a = hoistedArtifactsByResult.get(result)) != null ? _a : [];
18941
+ }
18942
+ function hoistExtractFileContentArtifacts(result) {
18943
+ if (!result || typeof result !== "object" || Array.isArray(result))
18944
+ return { cleanedResult: result, artifacts: [] };
18945
+ const images = Array.isArray(result.pageImages) ? result.pageImages.filter((value) => typeof value === "string") : [];
18946
+ if (!images.length)
18947
+ return { cleanedResult: result, artifacts: [] };
18948
+ const { pageImages: _dropped, ...rest } = result;
18949
+ return {
18950
+ cleanedResult: rest,
18951
+ artifacts: images.map((data) => ({
18952
+ type: "image",
18953
+ mimeType: "image/jpeg",
18954
+ data
18955
+ }))
18956
+ };
18957
+ }
18958
+
18959
+ const EXECUTION_RESULT = /* @__PURE__ */ Symbol("executionResult");
18960
+ const EXECUTION_HOISTED_ARTIFACTS = /* @__PURE__ */ Symbol("executionHoistedArtifacts");
18460
18961
  function createSafeHandlerFromString(handlerString, getIntegration, utils) {
18461
18962
  const realConsole = console;
18462
18963
  const isolatedConsole = {
@@ -18526,7 +19027,15 @@ function createSafeHandlerFromString(handlerString, getIntegration, utils) {
18526
19027
  const code = `module.exports = async function(input) { return (${handlerString})(input) }`;
18527
19028
  const script = new vm.Script(code);
18528
19029
  script.runInContext(context);
18529
- return withLogging(context.module.exports, isolatedConsole);
19030
+ return withLogging(async (args) => {
19031
+ const hoistedArtifacts = [];
19032
+ context.utils = createRuntimeUtils(utils, hoistedArtifacts);
19033
+ const result = await context.module.exports(args);
19034
+ return {
19035
+ [EXECUTION_RESULT]: result,
19036
+ [EXECUTION_HOISTED_ARTIFACTS]: hoistedArtifacts
19037
+ };
19038
+ }, isolatedConsole);
18530
19039
  }
18531
19040
  function withLogging(handler, vmConsole) {
18532
19041
  function safeSerializeForLog(value) {
@@ -18564,8 +19073,14 @@ function withLogging(handler, vmConsole) {
18564
19073
  logs.push(line);
18565
19074
  originalLog.apply(vmConsole, args2);
18566
19075
  };
18567
- const result = await handler(args);
18568
- return { success: true, result, logs };
19076
+ const execution = await handler(args);
19077
+ const { result, hoistedArtifacts } = unwrapExecutionEnvelope(execution);
19078
+ const toolRunResult = {
19079
+ success: true,
19080
+ result,
19081
+ logs
19082
+ };
19083
+ return hoistedArtifacts.length ? attachHoistedArtifacts(toolRunResult, hoistedArtifacts) : toolRunResult;
18569
19084
  } catch (err) {
18570
19085
  logs.push((err == null ? void 0 : err.stack) || String(err));
18571
19086
  const result = err && typeof err === "object" ? {
@@ -18580,6 +19095,32 @@ function withLogging(handler, vmConsole) {
18580
19095
  }
18581
19096
  };
18582
19097
  }
19098
+ function createRuntimeUtils(baseUtils, hoistedArtifacts) {
19099
+ const runtimeUtils = { ...baseUtils || {} };
19100
+ const extractFileContent = runtimeUtils.extractFileContent;
19101
+ if (typeof extractFileContent === "function") {
19102
+ runtimeUtils.extractFileContent = async (...args) => {
19103
+ const extracted = await extractFileContent(...args);
19104
+ const { cleanedResult, artifacts } = hoistExtractFileContentArtifacts(extracted);
19105
+ hoistedArtifacts.push(...artifacts);
19106
+ return cleanedResult;
19107
+ };
19108
+ }
19109
+ return runtimeUtils;
19110
+ }
19111
+ function unwrapExecutionEnvelope(value) {
19112
+ if (!value || typeof value !== "object" || !(EXECUTION_RESULT in value)) {
19113
+ return {
19114
+ result: value,
19115
+ hoistedArtifacts: []
19116
+ };
19117
+ }
19118
+ const envelope = value;
19119
+ return {
19120
+ result: envelope[EXECUTION_RESULT],
19121
+ hoistedArtifacts: Array.isArray(envelope[EXECUTION_HOISTED_ARTIFACTS]) ? envelope[EXECUTION_HOISTED_ARTIFACTS] : []
19122
+ };
19123
+ }
18583
19124
 
18584
19125
  const turndown = new TurndownService({
18585
19126
  headingStyle: "atx",
@@ -19074,7 +19615,10 @@ function createGetIntegration(integrations, proxy) {
19074
19615
 
19075
19616
  const EXTRACT_FILE_PY = `#!/usr/bin/env python3
19076
19617
  import argparse
19618
+ import base64
19077
19619
  import csv
19620
+ import email
19621
+ import email.policy
19078
19622
  import html
19079
19623
  import json
19080
19624
  import mimetypes
@@ -19084,6 +19628,18 @@ from pathlib import Path
19084
19628
 
19085
19629
  from markitdown import MarkItDown
19086
19630
 
19631
+ try:
19632
+ import fitz # PyMuPDF
19633
+ _FITZ_AVAILABLE = True
19634
+ except ImportError:
19635
+ _FITZ_AVAILABLE = False
19636
+
19637
+ try:
19638
+ import extract_msg as _extract_msg
19639
+ _EXTRACT_MSG_AVAILABLE = True
19640
+ except ImportError:
19641
+ _EXTRACT_MSG_AVAILABLE = False
19642
+
19087
19643
 
19088
19644
  def collapse_whitespace(value: str) -> str:
19089
19645
  return re.sub(r"\\s+", " ", value or "").strip()
@@ -19128,6 +19684,12 @@ def sniff_kind(path: Path) -> str:
19128
19684
  suffix = path.suffix.lower()
19129
19685
  if suffix in DIRECT_TEXT_EXTENSIONS | MARKITDOWN_EXTENSIONS:
19130
19686
  return suffix.lstrip(".")
19687
+ if suffix == ".msg":
19688
+ return "msg"
19689
+ if suffix == ".eml":
19690
+ return "eml"
19691
+ if suffix == ".zip":
19692
+ return "zip"
19131
19693
 
19132
19694
  mime_guess, _ = mimetypes.guess_type(path.name)
19133
19695
  if mime_guess == "application/pdf":
@@ -19147,10 +19709,78 @@ def sniff_kind(path: Path) -> str:
19147
19709
  return "xlsx"
19148
19710
  if "ppt/presentation.xml" in names:
19149
19711
  return "pptx"
19712
+ return "zip"
19150
19713
 
19151
19714
  return "unknown"
19152
19715
 
19153
19716
 
19717
+ def parse_pdf_date(date_str: str):
19718
+ """Parse a PDF date string (D:YYYYMMDDHHmmSS...) to ISO-8601."""
19719
+ if not date_str or not isinstance(date_str, str) or not date_str.startswith("D:"):
19720
+ return None
19721
+ try:
19722
+ raw = date_str[2:]
19723
+ m = re.match(r"(\\d{4})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})", raw)
19724
+ if not m:
19725
+ return None
19726
+ y, mo, d, h, mi, s = m.groups()
19727
+ return f"{y}-{mo}-{d}T{h}:{mi}:{s}Z"
19728
+ except Exception:
19729
+ return None
19730
+
19731
+
19732
+ def get_xlsx_sheet_names(path: Path) -> list:
19733
+ try:
19734
+ with zipfile.ZipFile(path, "r") as archive:
19735
+ if "xl/workbook.xml" not in archive.namelist():
19736
+ return []
19737
+ content = archive.read("xl/workbook.xml").decode("utf-8", errors="replace")
19738
+ return re.findall(r'<sheet\\b[^>]+\\bname="([^"]+)"', content)
19739
+ except Exception:
19740
+ return []
19741
+
19742
+
19743
+ def get_pptx_slide_count(path: Path) -> int:
19744
+ try:
19745
+ with zipfile.ZipFile(path, "r") as archive:
19746
+ return sum(
19747
+ 1 for name in archive.namelist()
19748
+ if re.match(r"ppt/slides/slide\\d+\\.xml$", name)
19749
+ )
19750
+ except Exception:
19751
+ return 0
19752
+
19753
+
19754
+ def get_office_core_props(path: Path) -> dict:
19755
+ """Read author and lastModifiedBy from docProps/core.xml (all Office formats)."""
19756
+ try:
19757
+ with zipfile.ZipFile(path, "r") as archive:
19758
+ if "docProps/core.xml" not in archive.namelist():
19759
+ return {}
19760
+ content = archive.read("docProps/core.xml").decode("utf-8", errors="replace")
19761
+ props = {}
19762
+ m = re.search(r"<dc:creator[^>]*>([^<]+)</dc:creator>", content)
19763
+ if m:
19764
+ props["author"] = m.group(1).strip()
19765
+ m = re.search(r"<cp:lastModifiedBy[^>]*>([^<]+)</cp:lastModifiedBy>", content)
19766
+ if m:
19767
+ props["lastModifiedBy"] = m.group(1).strip()
19768
+ return props
19769
+ except Exception:
19770
+ return {}
19771
+
19772
+
19773
+ def has_tracked_changes_docx(path: Path) -> bool:
19774
+ try:
19775
+ with zipfile.ZipFile(path, "r") as archive:
19776
+ if "word/document.xml" not in archive.namelist():
19777
+ return False
19778
+ content = archive.read("word/document.xml").decode("utf-8", errors="replace")
19779
+ return bool(re.search(r"<w:(del|ins)\\s", content))
19780
+ except Exception:
19781
+ return False
19782
+
19783
+
19154
19784
  def read_text(path: Path) -> dict:
19155
19785
  content = path.read_text(encoding="utf-8", errors="replace").strip()
19156
19786
  return {"kind": "text", "content": content, "metadata": {"filename": path.name}}
@@ -19214,6 +19844,278 @@ def read_csv_file(path: Path) -> dict:
19214
19844
  }
19215
19845
 
19216
19846
 
19847
+ def _strip_html_to_text(raw_html: str) -> str:
19848
+ """Strip HTML to plain text, removing style/script blocks first."""
19849
+ text = re.sub(r"<style[^>]*>[\\s\\S]*?</style>", " ", raw_html, flags=re.IGNORECASE)
19850
+ text = re.sub(r"<script[^>]*>[\\s\\S]*?<\/script>", " ", text, flags=re.IGNORECASE)
19851
+ text = re.sub(r"<br\\s*/?>", "\\n", text, flags=re.IGNORECASE)
19852
+ text = re.sub(r"</(p|div|li|tr|h[1-6])>", "\\n", text, flags=re.IGNORECASE)
19853
+ text = re.sub(r"<[^>]+>", " ", text)
19854
+ text = html.unescape(text)
19855
+ return re.sub(r"\\s+", " ", text).strip()
19856
+
19857
+
19858
+ def read_msg(path: Path) -> dict:
19859
+ import tempfile as _tempfile
19860
+
19861
+ if not _EXTRACT_MSG_AVAILABLE:
19862
+ raise RuntimeError(
19863
+ f"extract-msg is not installed; cannot read {path.name}. "
19864
+ "Install with: pip install extract-msg"
19865
+ )
19866
+
19867
+ try:
19868
+ msg = _extract_msg.openMsg(str(path))
19869
+ except Exception as exc:
19870
+ raise RuntimeError(f"MSG extraction failed for {path.name}: {exc}") from exc
19871
+
19872
+ try:
19873
+ subject = (msg.subject or "").strip() or None
19874
+ sender = (msg.sender or "").strip() or None
19875
+ to = (msg.to or "").strip() or None
19876
+ cc = (msg.cc or "").strip() or None
19877
+ date = str(msg.date).strip() if msg.date else None
19878
+ body = (msg.body or "").strip()
19879
+
19880
+ # Fall back to HTML body stripped to plain text when no plain-text body.
19881
+ if not body:
19882
+ raw_html = getattr(msg, "htmlBody", None) or b""
19883
+ if isinstance(raw_html, bytes):
19884
+ raw_html = raw_html.decode("utf-8", errors="replace")
19885
+ if raw_html:
19886
+ body = _strip_html_to_text(raw_html)
19887
+
19888
+ blocks = []
19889
+ if subject:
19890
+ blocks.append(f"# {subject}")
19891
+ header_lines = []
19892
+ if sender:
19893
+ header_lines.append(f"From: {sender}")
19894
+ if to:
19895
+ header_lines.append(f"To: {to}")
19896
+ if cc:
19897
+ header_lines.append(f"Cc: {cc}")
19898
+ if date:
19899
+ header_lines.append(f"Date: {date}")
19900
+ if header_lines:
19901
+ blocks.append("\\n".join(header_lines))
19902
+ if body:
19903
+ blocks.append(body)
19904
+
19905
+ attachments = list(msg.attachments or [])
19906
+ att_names = []
19907
+ warnings = []
19908
+
19909
+ with _tempfile.TemporaryDirectory() as tmp:
19910
+ tmp_path = Path(tmp)
19911
+ for i, att in enumerate(attachments):
19912
+ att_name = (
19913
+ getattr(att, "longFilename", None)
19914
+ or getattr(att, "shortFilename", None)
19915
+ or ""
19916
+ ).strip() or "attachment"
19917
+ att_names.append(att_name)
19918
+ try:
19919
+ att_path = None
19920
+
19921
+ # Primary: use save() \u2014 handles all attachment types including
19922
+ # OLE embedded objects where .data returns None.
19923
+ att_dir = tmp_path / f"att_{i}"
19924
+ att_dir.mkdir()
19925
+ try:
19926
+ att.save(customPath=str(att_dir))
19927
+ saved = list(att_dir.iterdir())
19928
+ if saved:
19929
+ att_path = saved[0]
19930
+ except Exception:
19931
+ pass
19932
+
19933
+ # Fallback: direct .data bytes.
19934
+ if att_path is None:
19935
+ data = att.data
19936
+ if data is None:
19937
+ warnings.append(f"Attachment '{att_name}' has no data.")
19938
+ continue
19939
+ att_path = att_dir / att_name
19940
+ att_path.write_bytes(data)
19941
+
19942
+ inner = extract(att_path)
19943
+ inner_content = (inner.get("content") or "").strip()
19944
+ if inner_content:
19945
+ blocks.append(f"## Attachment: {att_name}\\n\\n{inner_content}")
19946
+ except Exception as exc:
19947
+ warnings.append(f"Could not extract attachment '{att_name}': {exc}")
19948
+
19949
+ content = join_blocks(blocks)
19950
+ metadata: dict = {"filename": path.name, "attachmentCount": len(attachments)}
19951
+ if subject:
19952
+ metadata["subject"] = subject
19953
+ if sender:
19954
+ metadata["sender"] = sender
19955
+ if to:
19956
+ metadata["to"] = to
19957
+ if cc:
19958
+ metadata["cc"] = cc
19959
+ if date:
19960
+ metadata["date"] = date
19961
+ if att_names:
19962
+ metadata["attachmentNames"] = att_names
19963
+
19964
+ result: dict = {"kind": "msg", "content": content, "metadata": metadata}
19965
+ if warnings:
19966
+ result["warnings"] = warnings
19967
+ return result
19968
+
19969
+ finally:
19970
+ msg.close()
19971
+
19972
+
19973
+ def read_eml(path: Path) -> dict:
19974
+ import tempfile as _tempfile
19975
+
19976
+ raw = path.read_bytes()
19977
+ msg = email.message_from_bytes(raw, policy=email.policy.compat32)
19978
+
19979
+ subject = collapse_whitespace(msg.get("Subject", "") or "")
19980
+ sender = collapse_whitespace(msg.get("From", "") or "")
19981
+ to = collapse_whitespace(msg.get("To", "") or "")
19982
+ cc = collapse_whitespace(msg.get("Cc", "") or "")
19983
+ date = collapse_whitespace(msg.get("Date", "") or "")
19984
+
19985
+ # Walk parts: collect plain-text body and attachments.
19986
+ body_parts = []
19987
+ attachments = [] # list of (filename, bytes)
19988
+
19989
+ for part in msg.walk():
19990
+ content_type = part.get_content_type()
19991
+ disposition = (part.get("Content-Disposition") or "").lower()
19992
+ filename = part.get_filename()
19993
+
19994
+ if filename:
19995
+ try:
19996
+ data = part.get_payload(decode=True)
19997
+ if data:
19998
+ attachments.append((filename, data))
19999
+ except Exception:
20000
+ pass
20001
+ elif content_type == "text/plain" and "attachment" not in disposition:
20002
+ try:
20003
+ charset = part.get_content_charset() or "utf-8"
20004
+ payload = part.get_payload(decode=True)
20005
+ if payload:
20006
+ body_parts.append(payload.decode(charset, errors="replace"))
20007
+ except Exception:
20008
+ pass
20009
+
20010
+ body = "\\n\\n".join(p.strip() for p in body_parts if p.strip())
20011
+
20012
+ blocks = []
20013
+ if subject:
20014
+ blocks.append(f"# {subject}")
20015
+ header_lines = []
20016
+ if sender:
20017
+ header_lines.append(f"From: {sender}")
20018
+ if to:
20019
+ header_lines.append(f"To: {to}")
20020
+ if cc:
20021
+ header_lines.append(f"Cc: {cc}")
20022
+ if date:
20023
+ header_lines.append(f"Date: {date}")
20024
+ if header_lines:
20025
+ blocks.append("\\n".join(header_lines))
20026
+ if body:
20027
+ blocks.append(body)
20028
+
20029
+ att_names = []
20030
+ warnings = []
20031
+
20032
+ with _tempfile.TemporaryDirectory() as tmp:
20033
+ tmp_path = Path(tmp)
20034
+ for att_name, data in attachments:
20035
+ att_names.append(att_name)
20036
+ try:
20037
+ att_path = tmp_path / Path(att_name).name
20038
+ att_path.write_bytes(data)
20039
+ inner = extract(att_path)
20040
+ inner_content = (inner.get("content") or "").strip()
20041
+ if inner_content:
20042
+ blocks.append(f"## Attachment: {att_name}\\n\\n{inner_content}")
20043
+ except Exception as exc:
20044
+ warnings.append(f"Could not extract attachment '{att_name}': {exc}")
20045
+
20046
+ content = join_blocks(blocks)
20047
+ metadata: dict = {"filename": path.name, "attachmentCount": len(attachments)}
20048
+ if subject:
20049
+ metadata["subject"] = subject
20050
+ if sender:
20051
+ metadata["sender"] = sender
20052
+ if to:
20053
+ metadata["to"] = to
20054
+ if cc:
20055
+ metadata["cc"] = cc
20056
+ if date:
20057
+ metadata["date"] = date
20058
+ if att_names:
20059
+ metadata["attachmentNames"] = att_names
20060
+
20061
+ result: dict = {"kind": "eml", "content": content, "metadata": metadata}
20062
+ if warnings:
20063
+ result["warnings"] = warnings
20064
+ return result
20065
+
20066
+
20067
+ def read_zip(path: Path) -> dict:
20068
+ import tempfile as _tempfile
20069
+
20070
+ try:
20071
+ with zipfile.ZipFile(path, "r") as archive:
20072
+ all_names = archive.namelist()
20073
+ except Exception as exc:
20074
+ raise RuntimeError(f"ZIP extraction failed for {path.name}: {exc}") from exc
20075
+
20076
+ # Skip directories and hidden/system files.
20077
+ file_entries = [
20078
+ n for n in all_names
20079
+ if not n.endswith("/") and not Path(n).name.startswith(".")
20080
+ ]
20081
+
20082
+ blocks = []
20083
+ warnings = []
20084
+
20085
+ try:
20086
+ with zipfile.ZipFile(path, "r") as archive:
20087
+ with _tempfile.TemporaryDirectory() as tmp:
20088
+ tmp_path = Path(tmp)
20089
+ for entry in file_entries:
20090
+ entry_name = Path(entry).name
20091
+ if not entry_name:
20092
+ continue
20093
+ try:
20094
+ data = archive.read(entry)
20095
+ entry_path = tmp_path / entry_name
20096
+ entry_path.write_bytes(data)
20097
+ inner = extract(entry_path)
20098
+ inner_content = (inner.get("content") or "").strip()
20099
+ if inner_content:
20100
+ blocks.append(f"## {entry}\\n\\n{inner_content}")
20101
+ except Exception as exc:
20102
+ warnings.append(f"Could not extract '{entry}': {exc}")
20103
+ except Exception as exc:
20104
+ raise RuntimeError(f"ZIP extraction failed for {path.name}: {exc}") from exc
20105
+
20106
+ content = join_blocks(blocks)
20107
+ metadata: dict = {
20108
+ "filename": path.name,
20109
+ "fileCount": len(file_entries),
20110
+ "fileNames": file_entries,
20111
+ }
20112
+
20113
+ result: dict = {"kind": "zip", "content": content, "metadata": metadata}
20114
+ if warnings:
20115
+ result["warnings"] = warnings
20116
+ return result
20117
+
20118
+
19217
20119
  def read_with_markitdown(path: Path, kind: str) -> dict:
19218
20120
  try:
19219
20121
  result = MarkItDown().convert(str(path))
@@ -19221,14 +20123,132 @@ def read_with_markitdown(path: Path, kind: str) -> dict:
19221
20123
  raise RuntimeError(f"MarkItDown extraction failed for {path.name}: {exc}") from exc
19222
20124
 
19223
20125
  content = (getattr(result, "text_content", "") or "").strip()
20126
+ metadata: dict = {"filename": path.name, "parser": "markitdown"}
20127
+
20128
+ # XLSX: surface sheet names so the agent knows the workbook structure upfront.
20129
+ if kind == "xlsx":
20130
+ sheet_names = get_xlsx_sheet_names(path)
20131
+ if sheet_names:
20132
+ metadata["sheetNames"] = sheet_names
20133
+
20134
+ # DOCX: tracked changes flag + author metadata from core properties.
20135
+ if kind == "docx":
20136
+ metadata["hasTrackedChanges"] = has_tracked_changes_docx(path)
20137
+ core = get_office_core_props(path)
20138
+ if core.get("author"):
20139
+ metadata["author"] = core["author"]
20140
+ if core.get("lastModifiedBy"):
20141
+ metadata["lastModifiedBy"] = core["lastModifiedBy"]
20142
+
20143
+ # PPTX: slide count.
20144
+ if kind == "pptx":
20145
+ count = get_pptx_slide_count(path)
20146
+ if count:
20147
+ metadata["slideCount"] = count
20148
+
19224
20149
  return {
19225
20150
  "kind": kind,
19226
20151
  "content": content,
19227
- "metadata": {"filename": path.name, "parser": "markitdown"},
20152
+ "metadata": metadata,
19228
20153
  }
19229
20154
 
19230
20155
 
19231
- def extract(path: Path) -> dict:
20156
+ def read_pdf(path: Path, preview_pages: int = 0) -> dict:
20157
+ # Text extraction via MarkItDown (higher quality than pymupdf for most PDFs).
20158
+ try:
20159
+ md_result = MarkItDown().convert(str(path))
20160
+ content = (getattr(md_result, "text_content", "") or "").strip()
20161
+ except Exception as exc:
20162
+ raise RuntimeError(f"MarkItDown extraction failed for {path.name}: {exc}") from exc
20163
+
20164
+ metadata: dict = {"filename": path.name, "parser": "markitdown"}
20165
+
20166
+ if not _FITZ_AVAILABLE:
20167
+ return {"kind": "pdf", "content": content, "metadata": metadata}
20168
+
20169
+ page_images = []
20170
+
20171
+ try:
20172
+ doc = fitz.open(str(path))
20173
+ page_count = doc.page_count
20174
+ metadata["pageCount"] = page_count
20175
+
20176
+ # Document info dictionary (title, author, etc.)
20177
+ info = doc.metadata or {}
20178
+ for src_key, out_key in [
20179
+ ("title", "title"),
20180
+ ("author", "author"),
20181
+ ("subject", "subject"),
20182
+ ("keywords", "keywords"),
20183
+ ("creator", "creator"),
20184
+ ]:
20185
+ val = (info.get(src_key) or "").strip()
20186
+ if val:
20187
+ metadata[out_key] = val
20188
+
20189
+ created = parse_pdf_date(info.get("creationDate", ""))
20190
+ if created:
20191
+ metadata["createdAt"] = created
20192
+ modified = parse_pdf_date(info.get("modDate", ""))
20193
+ if modified:
20194
+ metadata["modifiedAt"] = modified
20195
+
20196
+ metadata["isEncrypted"] = bool(doc.is_encrypted)
20197
+
20198
+ # Single pass over all pages: signatures, annotations, form fields.
20199
+ sig_widget_type = getattr(fitz, "PDF_WIDGET_TYPE_SIGNATURE", 7)
20200
+ non_content_annot_types = {"Widget", "Link"}
20201
+ has_annotations = False
20202
+ has_form_fields = False
20203
+ signatures = []
20204
+
20205
+ for i in range(page_count):
20206
+ page = doc.load_page(i)
20207
+
20208
+ # Widgets: split into signatures and fillable form fields.
20209
+ for widget in (page.widgets() or []):
20210
+ if widget.field_type == sig_widget_type:
20211
+ signer = (widget.field_value or "").strip() or None
20212
+ signatures.append({
20213
+ "fieldName": (widget.field_name or "").strip() or None,
20214
+ "signer": signer,
20215
+ "isSigned": bool(signer),
20216
+ })
20217
+ else:
20218
+ has_form_fields = True
20219
+
20220
+ # Annotations: exclude widget and link annotations.
20221
+ if not has_annotations:
20222
+ for annot in page.annots():
20223
+ if annot.type[1] not in non_content_annot_types:
20224
+ has_annotations = True
20225
+ break
20226
+
20227
+ metadata["hasAnnotations"] = has_annotations
20228
+ metadata["hasFormFields"] = has_form_fields
20229
+ if signatures:
20230
+ metadata["signatures"] = signatures
20231
+
20232
+ # Page preview images (opt-in via previewPages > 0).
20233
+ if preview_pages > 0:
20234
+ n = min(preview_pages, page_count)
20235
+ for i in range(n):
20236
+ pixmap = doc.load_page(i).get_pixmap(dpi=96)
20237
+ jpeg_bytes = pixmap.tobytes("jpeg")
20238
+ page_images.append(base64.b64encode(jpeg_bytes).decode("ascii"))
20239
+
20240
+ doc.close()
20241
+
20242
+ except Exception:
20243
+ pass # Metadata and image enhancement is best-effort; don't fail the extraction.
20244
+
20245
+ result: dict = {"kind": "pdf", "content": content, "metadata": metadata}
20246
+ if page_images:
20247
+ result["pageImages"] = page_images
20248
+ return result
20249
+
20250
+
20251
+ def extract(path: Path, preview_pages: int = 0) -> dict:
19232
20252
  kind = sniff_kind(path)
19233
20253
  if kind == "txt":
19234
20254
  return read_text(path)
@@ -19240,7 +20260,15 @@ def extract(path: Path) -> dict:
19240
20260
  return read_html(path)
19241
20261
  if kind == "csv":
19242
20262
  return read_csv_file(path)
19243
- if path.suffix.lower() in MARKITDOWN_EXTENSIONS or kind in {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx"}:
20263
+ if kind == "pdf":
20264
+ return read_pdf(path, preview_pages)
20265
+ if kind == "msg":
20266
+ return read_msg(path)
20267
+ if kind == "eml":
20268
+ return read_eml(path)
20269
+ if kind == "zip":
20270
+ return read_zip(path)
20271
+ if path.suffix.lower() in MARKITDOWN_EXTENSIONS or kind in {"doc", "docx", "xls", "xlsx", "ppt", "pptx"}:
19244
20272
  return read_with_markitdown(path, kind)
19245
20273
 
19246
20274
  raw = path.read_text(encoding="utf-8", errors="replace")
@@ -19256,12 +20284,13 @@ def main() -> int:
19256
20284
  parser = argparse.ArgumentParser()
19257
20285
  parser.add_argument("--input", required=True)
19258
20286
  parser.add_argument("--output", required=True)
20287
+ parser.add_argument("--preview-pages", type=int, default=0)
19259
20288
  args = parser.parse_args()
19260
20289
 
19261
20290
  input_path = Path(args.input)
19262
20291
  output_path = Path(args.output)
19263
20292
 
19264
- result = extract(input_path)
20293
+ result = extract(input_path, preview_pages=args.preview_pages)
19265
20294
  output_path.write_text(json.dumps(result, ensure_ascii=False), encoding="utf-8")
19266
20295
  return 0
19267
20296
 
@@ -19269,10 +20298,10 @@ def main() -> int:
19269
20298
  if __name__ == "__main__":
19270
20299
  raise SystemExit(main())
19271
20300
  `;
19272
- const EXTRACT_FILE_PY_HASH = "632e2322c14941f8c30b5f60b4c5bb1d773e0d2953fde39a7a16eaf1dbfc21c2";
20301
+ const EXTRACT_FILE_PY_HASH = "bc00c9206b20fc39f1ab718271dd49ae335f95c0b1683bf393d0b3f1376d8f33";
19273
20302
 
19274
20303
  const execFile$1 = promisify(execFile$2);
19275
- const INSTALL_COMMAND = "pip3 install markitdown[all]";
20304
+ const INSTALL_COMMAND = "pip3 install markitdown[all] pymupdf";
19276
20305
  const DOCKER_HINT = "Run Commandable with Docker to use the preinstalled extraction runtime.";
19277
20306
  let cachedScriptPath = null;
19278
20307
  function ensureExtractorScript() {
@@ -19491,14 +20520,19 @@ function createExtractFileContent(getIntegration, defaultIntegrationId) {
19491
20520
  const outputPath = join(tempDir, "result.json");
19492
20521
  const bytes = Buffer.from(await response.arrayBuffer());
19493
20522
  await writeFile$1(filePath, bytes);
19494
- await execFile(pythonExecutable(), [extractorScriptPath(), "--input", filePath, "--output", outputPath], { cwd: tempDir, maxBuffer: 10 * 1024 * 1024 });
20523
+ const pythonArgs = [extractorScriptPath(), "--input", filePath, "--output", outputPath];
20524
+ const previewPages = typeof resolvedArgs.previewPages === "number" && resolvedArgs.previewPages > 0 ? Math.floor(resolvedArgs.previewPages) : 0;
20525
+ if (previewPages > 0)
20526
+ pythonArgs.push("--preview-pages", String(previewPages));
20527
+ await execFile(pythonExecutable(), pythonArgs, { cwd: tempDir, maxBuffer: 50 * 1024 * 1024 });
19495
20528
  const raw = await readFile$1(outputPath, "utf8");
19496
20529
  const parsed = JSON.parse(raw);
19497
20530
  return {
19498
20531
  kind: typeof (parsed == null ? void 0 : parsed.kind) === "string" ? parsed.kind : "unknown",
19499
20532
  content: typeof (parsed == null ? void 0 : parsed.content) === "string" ? parsed.content : "",
19500
20533
  warnings: Array.isArray(parsed == null ? void 0 : parsed.warnings) ? parsed.warnings.map((item) => String(item)) : void 0,
19501
- metadata: (parsed == null ? void 0 : parsed.metadata) && typeof parsed.metadata === "object" ? parsed.metadata : void 0
20534
+ metadata: (parsed == null ? void 0 : parsed.metadata) && typeof parsed.metadata === "object" ? parsed.metadata : void 0,
20535
+ pageImages: Array.isArray(parsed == null ? void 0 : parsed.pageImages) ? parsed.pageImages.filter((v) => typeof v === "string") : void 0
19502
20536
  };
19503
20537
  } catch (error) {
19504
20538
  const stderr = typeof (error == null ? void 0 : error.stderr) === "string" ? error.stderr.trim() : "";
@@ -21457,7 +22491,7 @@ ${lines ? `${lines}
21457
22491
  ` : "No integrations configured yet.\n"}`;
21458
22492
  }
21459
22493
  try {
21460
- return loadIntegrationPrompt(ability.integrationtype);
22494
+ return loadIntegrationUsageGuide(ability.integrationtype);
21461
22495
  } catch {
21462
22496
  return null;
21463
22497
  }
@@ -22057,6 +23091,32 @@ function formatAsText(value) {
22057
23091
  return String(value);
22058
23092
  }
22059
23093
  }
23094
+ function renderArtifact(artifact) {
23095
+ if (artifact.type === "image" && artifact.data) {
23096
+ return [{
23097
+ type: "image",
23098
+ data: artifact.data,
23099
+ mimeType: artifact.mimeType
23100
+ }];
23101
+ }
23102
+ if (artifact.type === "image" && artifact.url) {
23103
+ return [{
23104
+ type: "text",
23105
+ text: `Image artifact URL (${artifact.mimeType}): ${artifact.url}`
23106
+ }];
23107
+ }
23108
+ return [];
23109
+ }
23110
+ function buildToolSuccessContent(res) {
23111
+ var _a;
23112
+ const artifacts = getHoistedArtifacts(res);
23113
+ return [
23114
+ { type: "text", text: formatAsText(res.result) },
23115
+ ...artifacts.flatMap(renderArtifact),
23116
+ ...((_a = res.logs) == null ? void 0 : _a.length) ? [{ type: "text", text: `Logs:
23117
+ ${res.logs.join("\n")}` }] : []
23118
+ ];
23119
+ }
22060
23120
  function registerToolHandlers(server, tools, options = { mode: "static" }) {
22061
23121
  const metaToolDefs = getMetaToolDefinitions();
22062
23122
  const { mode, dynamicMode } = options;
@@ -22074,7 +23134,7 @@ function registerToolHandlers(server, tools, options = { mode: "static" }) {
22074
23134
  return { tools: [...metaToolDefs, ...toolDefs] };
22075
23135
  });
22076
23136
  server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
22077
- var _a, _b, _c, _d, _e;
23137
+ var _a, _b, _c;
22078
23138
  const name = req.params.name;
22079
23139
  const args = (_a = req.params.arguments) != null ? _a : {};
22080
23140
  const sessionId = extra == null ? void 0 : extra.sessionId;
@@ -22112,11 +23172,7 @@ ${res2.logs.join("\n")}` }] : []
22112
23172
  };
22113
23173
  }
22114
23174
  return {
22115
- content: [
22116
- { type: "text", text: formatAsText(res2.result) },
22117
- ...((_c = res2.logs) == null ? void 0 : _c.length) ? [{ type: "text", text: `Logs:
22118
- ${res2.logs.join("\n")}` }] : []
22119
- ]
23175
+ content: buildToolSuccessContent(res2)
22120
23176
  };
22121
23177
  }
22122
23178
  const staticMeta = handleStaticReadmeCall(name);
@@ -22133,17 +23189,13 @@ ${res2.logs.join("\n")}` }] : []
22133
23189
  return {
22134
23190
  content: [
22135
23191
  { type: "text", text: `Tool error: ${formatAsText(res.result)}` },
22136
- ...((_d = res.logs) == null ? void 0 : _d.length) ? [{ type: "text", text: `Logs:
23192
+ ...((_c = res.logs) == null ? void 0 : _c.length) ? [{ type: "text", text: `Logs:
22137
23193
  ${res.logs.join("\n")}` }] : []
22138
23194
  ]
22139
23195
  };
22140
23196
  }
22141
23197
  return {
22142
- content: [
22143
- { type: "text", text: formatAsText(res.result) },
22144
- ...((_e = res.logs) == null ? void 0 : _e.length) ? [{ type: "text", text: `Logs:
22145
- ${res.logs.join("\n")}` }] : []
22146
- ]
23198
+ content: buildToolSuccessContent(res)
22147
23199
  };
22148
23200
  });
22149
23201
  }
@@ -22563,140 +23615,140 @@ const assets = {
22563
23615
  "/favicon.ico": {
22564
23616
  "type": "image/vnd.microsoft.icon",
22565
23617
  "etag": "\"10be-n8egyE9tcb7sKGr/pYCaQ4uWqxI\"",
22566
- "mtime": "2026-04-07T07:13:36.310Z",
23618
+ "mtime": "2026-04-12T18:58:38.513Z",
22567
23619
  "size": 4286,
22568
23620
  "path": "../public/favicon.ico"
22569
23621
  },
22570
23622
  "/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2": {
22571
23623
  "type": "font/woff2",
22572
23624
  "etag": "\"4b5c-TAo9mx7r3xQs52+HbHcHJ52z8Qo\"",
22573
- "mtime": "2026-04-07T07:13:36.305Z",
23625
+ "mtime": "2026-04-12T18:58:38.507Z",
22574
23626
  "size": 19292,
22575
23627
  "path": "../public/_fonts/57NSSoFy1VLVs2gqly8Ls9awBnZMFyXGrefpmqvdqmc-zJfbBtpgM4cDmcXBsqZNW79_kFnlpPd62b48glgdydA.woff2"
22576
23628
  },
22577
23629
  "/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2": {
22578
23630
  "type": "font/woff2",
22579
23631
  "etag": "\"212c-FshXJibFzNhd2HEIMP8C3JR5PYg\"",
22580
- "mtime": "2026-04-07T07:13:36.305Z",
23632
+ "mtime": "2026-04-12T18:58:38.507Z",
22581
23633
  "size": 8492,
22582
23634
  "path": "../public/_fonts/8VR2wSMN-3U4NbWAVYXlkRV6hA0jFBXP-0RtL3X7fko-x2gYI4qfmkRdxyQQUPaBZdZdgl1TeVrquF_TxHeM4lM.woff2"
22583
23635
  },
22584
23636
  "/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2": {
22585
23637
  "type": "font/woff2",
22586
23638
  "etag": "\"680c-mJtsV33lkTAKSmfq5k3lKHSllcU\"",
22587
- "mtime": "2026-04-07T07:13:36.305Z",
23639
+ "mtime": "2026-04-12T18:58:38.507Z",
22588
23640
  "size": 26636,
22589
23641
  "path": "../public/_fonts/GsKUclqeNLJ96g5AU593ug6yanivOiwjW_7zESNPChw-jHA4tBeM1bjF7LATGUpfBuSTyomIFrWBTzjF7txVYfg.woff2"
22590
23642
  },
22591
23643
  "/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2": {
22592
23644
  "type": "font/woff2",
22593
23645
  "etag": "\"6ec4-8OoFFPZKF1grqmfGVjh5JDE6DOU\"",
22594
- "mtime": "2026-04-07T07:13:36.305Z",
23646
+ "mtime": "2026-04-12T18:58:38.507Z",
22595
23647
  "size": 28356,
22596
23648
  "path": "../public/_fonts/Ld1FnTo3yTIwDyGfTQ5-Fws9AWsCbKfMvgxduXr7JcY-W25bL8NF1fjpLRSOgJb7RoZPHqGQNwMTM7S9tHVoxx8.woff2"
22597
23649
  },
22598
23650
  "/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2": {
22599
23651
  "type": "font/woff2",
22600
23652
  "etag": "\"1d98-cDZfMibtk4T04FTTAmlfhWDpkN0\"",
22601
- "mtime": "2026-04-07T07:13:36.305Z",
23653
+ "mtime": "2026-04-12T18:58:38.507Z",
22602
23654
  "size": 7576,
22603
23655
  "path": "../public/_fonts/NdzqRASp2bovDUhQT1IRE_EMqKJ2KYQdTCfFcBvL8yw-KhwZiS86o3fErOe5GGMExHUemmI_dBfaEFxjISZrBd0.woff2"
22604
23656
  },
22605
23657
  "/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2": {
22606
23658
  "type": "font/woff2",
22607
23659
  "etag": "\"47c4-5xyngHnzzhetUee74tMx9OTgqNQ\"",
22608
- "mtime": "2026-04-07T07:13:36.305Z",
23660
+ "mtime": "2026-04-12T18:58:38.507Z",
22609
23661
  "size": 18372,
22610
23662
  "path": "../public/_fonts/iTkrULNFJJkTvihIg1Vqi5IODRH_9btXCioVF5l98I8-AndUyau2HR2felA_ra8V2mutQgschhasE5FD1dXGJX8.woff2"
22611
23663
  },
22612
23664
  "/_nuxt/BD6mASiY.js": {
22613
23665
  "type": "text/javascript; charset=utf-8",
22614
23666
  "etag": "\"ab-ScyLcA/4r5aOxEv1YY+kqXazCHI\"",
22615
- "mtime": "2026-04-07T07:13:36.308Z",
23667
+ "mtime": "2026-04-12T18:58:38.509Z",
22616
23668
  "size": 171,
22617
23669
  "path": "../public/_nuxt/BD6mASiY.js"
22618
23670
  },
22619
- "/_nuxt/CjAs3eBq.js": {
22620
- "type": "text/javascript; charset=utf-8",
22621
- "etag": "\"1df7-cTFKdH9K34T9NixeUm/CLQ8lWUc\"",
22622
- "mtime": "2026-04-07T07:13:36.308Z",
22623
- "size": 7671,
22624
- "path": "../public/_nuxt/CjAs3eBq.js"
22625
- },
22626
23671
  "/_nuxt/D9wFDhac.js": {
22627
23672
  "type": "text/javascript; charset=utf-8",
22628
23673
  "etag": "\"e99-sUFV1wmMOK2XGfzDXJyP2NA8TG4\"",
22629
- "mtime": "2026-04-07T07:13:36.308Z",
23674
+ "mtime": "2026-04-12T18:58:38.510Z",
22630
23675
  "size": 3737,
22631
23676
  "path": "../public/_nuxt/D9wFDhac.js"
22632
23677
  },
22633
- "/_nuxt/DRfk9W3W.js": {
23678
+ "/_nuxt/CjAs3eBq.js": {
22634
23679
  "type": "text/javascript; charset=utf-8",
22635
- "etag": "\"194dc-Oj5Ixz12+pq4yqDtF/N+YAPzoWw\"",
22636
- "mtime": "2026-04-07T07:13:36.308Z",
22637
- "size": 103644,
22638
- "path": "../public/_nuxt/DRfk9W3W.js"
23680
+ "etag": "\"1df7-cTFKdH9K34T9NixeUm/CLQ8lWUc\"",
23681
+ "mtime": "2026-04-12T18:58:38.510Z",
23682
+ "size": 7671,
23683
+ "path": "../public/_nuxt/CjAs3eBq.js"
22639
23684
  },
22640
23685
  "/_nuxt/DSWYWRXT.js": {
22641
23686
  "type": "text/javascript; charset=utf-8",
22642
23687
  "etag": "\"10875-8b+YwIvP6QkcBFnHXqxd+WeZ05o\"",
22643
- "mtime": "2026-04-07T07:13:36.308Z",
23688
+ "mtime": "2026-04-12T18:58:38.510Z",
22644
23689
  "size": 67701,
22645
23690
  "path": "../public/_nuxt/DSWYWRXT.js"
22646
23691
  },
23692
+ "/_nuxt/DRfk9W3W.js": {
23693
+ "type": "text/javascript; charset=utf-8",
23694
+ "etag": "\"194dc-Oj5Ixz12+pq4yqDtF/N+YAPzoWw\"",
23695
+ "mtime": "2026-04-12T18:58:38.510Z",
23696
+ "size": 103644,
23697
+ "path": "../public/_nuxt/DRfk9W3W.js"
23698
+ },
22647
23699
  "/_nuxt/VvnbcAzZ.js": {
22648
23700
  "type": "text/javascript; charset=utf-8",
22649
23701
  "etag": "\"d7b-hU4O5jppM7Ou3kZAYy3iYXlgoa8\"",
22650
- "mtime": "2026-04-07T07:13:36.308Z",
23702
+ "mtime": "2026-04-12T18:58:38.510Z",
22651
23703
  "size": 3451,
22652
23704
  "path": "../public/_nuxt/VvnbcAzZ.js"
22653
23705
  },
22654
23706
  "/_nuxt/_id_.DhlLK-mY.css": {
22655
23707
  "type": "text/css; charset=utf-8",
22656
23708
  "etag": "\"2f4-xtV37kE566jU74wpZnFHA29RoAY\"",
22657
- "mtime": "2026-04-07T07:13:36.308Z",
23709
+ "mtime": "2026-04-12T18:58:38.510Z",
22658
23710
  "size": 756,
22659
23711
  "path": "../public/_nuxt/_id_.DhlLK-mY.css"
22660
23712
  },
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"
22667
- },
22668
23713
  "/_nuxt/error-404.C7fg894-.css": {
22669
23714
  "type": "text/css; charset=utf-8",
22670
23715
  "etag": "\"97e-fiQ3o7A11L9BuXRBr0GJldkx0AU\"",
22671
- "mtime": "2026-04-07T07:13:36.308Z",
23716
+ "mtime": "2026-04-12T18:58:38.510Z",
22672
23717
  "size": 2430,
22673
23718
  "path": "../public/_nuxt/error-404.C7fg894-.css"
22674
23719
  },
22675
23720
  "/_nuxt/error-500.DjUK_N2Y.css": {
22676
23721
  "type": "text/css; charset=utf-8",
22677
23722
  "etag": "\"773-Qf61bSDos4KtmZDaA06FmZyUYNo\"",
22678
- "mtime": "2026-04-07T07:13:36.308Z",
23723
+ "mtime": "2026-04-12T18:58:38.510Z",
22679
23724
  "size": 1907,
22680
23725
  "path": "../public/_nuxt/error-500.DjUK_N2Y.css"
22681
23726
  },
22682
23727
  "/_nuxt/builds/latest.json": {
22683
23728
  "type": "application/json",
22684
- "etag": "\"47-hl0sv2tAIq0lIkhLc1Sve7W2pxs\"",
22685
- "mtime": "2026-04-07T07:13:36.304Z",
23729
+ "etag": "\"47-e/86GxbuxL65vE0JCcC0T3jGAp4\"",
23730
+ "mtime": "2026-04-12T18:58:38.505Z",
22686
23731
  "size": 71,
22687
23732
  "path": "../public/_nuxt/builds/latest.json"
22688
23733
  },
22689
- "/_nuxt/builds/meta/9441a86b-16e9-4000-bffc-3b2e78e57710.json": {
23734
+ "/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json": {
22690
23735
  "type": "application/json",
22691
- "etag": "\"58-cDzg8zvmZMF8VeAEeeEcF3Ng22E\"",
22692
- "mtime": "2026-04-07T07:13:36.301Z",
23736
+ "etag": "\"58-BEsLMe80cnxppLc/ZshfoAR3oqs\"",
23737
+ "mtime": "2026-04-12T18:58:38.503Z",
22693
23738
  "size": 88,
22694
- "path": "../public/_nuxt/builds/meta/9441a86b-16e9-4000-bffc-3b2e78e57710.json"
23739
+ "path": "../public/_nuxt/builds/meta/886deef4-f3b5-464c-b4e2-11735eb5272e.json"
23740
+ },
23741
+ "/_nuxt/BUmYUDQu.js": {
23742
+ "type": "text/javascript; charset=utf-8",
23743
+ "etag": "\"66cba-d/pdEXVc78H3VlgFN3kVzKpvD1Q\"",
23744
+ "mtime": "2026-04-12T18:58:38.510Z",
23745
+ "size": 421050,
23746
+ "path": "../public/_nuxt/BUmYUDQu.js"
22695
23747
  },
22696
23748
  "/_nuxt/entry.Y3mA4bzA.css": {
22697
23749
  "type": "text/css; charset=utf-8",
22698
23750
  "etag": "\"2d46b-zfrD3Ny9WW6qm4fCXAfX5eIAxPA\"",
22699
- "mtime": "2026-04-07T07:13:36.308Z",
23751
+ "mtime": "2026-04-12T18:58:38.510Z",
22700
23752
  "size": 185451,
22701
23753
  "path": "../public/_nuxt/entry.Y3mA4bzA.css"
22702
23754
  }