diff --git a/docs/content/docs/integrations/excel.mdx b/docs/content/docs/integrations/excel.mdx new file mode 100644 index 0000000..4279ebe --- /dev/null +++ b/docs/content/docs/integrations/excel.mdx @@ -0,0 +1,218 @@ +--- +title: Excel +description: Read and write Microsoft Excel workbooks stored in OneDrive +--- + +The Excel integration provides full workbook access to Excel files (.xlsx) in OneDrive through the Microsoft Graph API workbook endpoint. + +## Installation + +The Excel integration is included with the SDK: + +```typescript +import { excelIntegration } from "integrate-sdk/server"; +``` + +## Setup + +### 1. Create a Microsoft App Registration + +1. Go to [Azure Portal — App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps) +2. Create a new registration +3. Add your redirect URI under **Authentication** (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +4. Create a client secret under **Certificates & secrets** +5. Note your Application (client) ID and Client Secret + +### 2. Configure the Integration on Your Server + +The integration automatically reads `EXCEL_CLIENT_ID` and `EXCEL_CLIENT_SECRET` from your environment: + +```typescript +import { createMCPServer, excelIntegration } from "integrate-sdk/server"; + +export const { client: serverClient } = createMCPServer({ + apiKey: process.env.INTEGRATE_API_KEY, + integrations: [ + excelIntegration(), + ], +}); +``` + +You can override the environment variables by passing explicit values: + +```typescript +excelIntegration({ + clientId: process.env.CUSTOM_EXCEL_ID, + clientSecret: process.env.CUSTOM_EXCEL_SECRET, +}); +``` + +### 3. Client-Side Usage + +```typescript +import { client } from "integrate-sdk"; + +await client.authorize("excel"); +const workbooks = await client.excel.list({}); +``` + +For a custom client: + +```typescript +import { createMCPClient, excelIntegration } from "integrate-sdk"; + +const customClient = createMCPClient({ + integrations: [excelIntegration()], +}); +``` + +## Configuration Options + + + +## Available Tools + +### Workbook Management + +- **`excel_list`** - Search for Excel workbooks +- **`excel_get`** - Get workbook metadata by item ID +- **`excel_create`** - Create a new empty .xlsx workbook +- **`excel_delete`** - Permanently delete a workbook +- **`excel_share`** - Create a sharing link + +### Worksheets + +- **`excel_list_worksheets`** - List all sheets in a workbook +- **`excel_add_worksheet`** - Add a new worksheet +- **`excel_delete_worksheet`** - Delete a worksheet + +### Cell Ranges + +- **`excel_get_range`** - Get values, formulas, and formatting from a range +- **`excel_update_range`** - Write values to a range (2D JSON array) +- **`excel_clear_range`** - Clear contents and/or formatting +- **`excel_get_used_range`** - Get the bounding range of all data (Ctrl+End equivalent) + +### Tables + +- **`excel_list_tables`** - List tables in a worksheet +- **`excel_create_table`** - Create a table from a cell range +- **`excel_get_table_rows`** - Get rows from a table (supports pagination) +- **`excel_add_table_rows`** - Append rows to a table + +## Examples + +### Read Cell Values + +```typescript +const data = await client.excel.getRange({ + item_id: "ABC123", + worksheet: "Sheet1", + range: "A1:C10", +}); +``` + +### Write Values to a Range + +```typescript +await client.excel.updateRange({ + item_id: "ABC123", + worksheet: "Sheet1", + range: "A1:B3", + values: JSON.stringify([ + ["Name", "Score"], + ["Alice", 95], + ["Bob", 88], + ]), +}); +``` + +### Get All Data in a Sheet + +```typescript +// Returns bounding range of all non-empty cells +const used = await client.excel.getUsedRange({ + item_id: "ABC123", + worksheet: "Sheet1", +}); +``` + +### Work with Tables + +```typescript +// Create a table from existing data +await client.excel.createTable({ + item_id: "ABC123", + worksheet: "Sheet1", + range: "A1:D10", + has_headers: true, +}); + +// Append rows to the table +await client.excel.addTableRows({ + item_id: "ABC123", + worksheet: "Sheet1", + table: "Table1", + values: JSON.stringify([["Carol", 92, "Engineering", true]]), +}); +``` + +### Add a Worksheet + +```typescript +await client.excel.addWorksheet({ + item_id: "ABC123", + name: "Q4 Summary", +}); +``` + +### Create a Workbook + +```typescript +const wb = await client.excel.create({ + name: "Monthly Report", // .xlsx appended automatically + parent_id: "folderId123", // optional +}); +``` + +## Notes + +- `values` parameters accept a **JSON string** (not a pre-parsed array) — use `JSON.stringify(yourArray)`. +- The `worksheet` parameter accepts either the display name (`"Sheet1"`) or the Graph worksheet ID. +- Item IDs are Graph API drive item IDs. Use `excel_list` or `onedrive_search_files` to find them. + +## OAuth Scopes + +The default scope is `Files.ReadWrite.All`. Available scopes: + +- **`Files.ReadWrite.All`** - Read and write all files the user can access +- **`Files.Read.All`** - Read-only access + +## Error Handling + +```typescript +try { + const data = await client.excel.getRange({ + item_id: "ABC123", + worksheet: "Sheet1", + range: "A1:B10", + }); +} catch (error) { + if (error.message.includes("404")) { + console.error("Workbook or worksheet not found"); + } else if (error.message.includes("423")) { + console.error("Workbook is locked by another session"); + } else { + console.error("Unexpected error:", error); + } +} +``` + +## Next Steps + +- Manage Word documents with the [Word Integration](/docs/integrations/word) +- Manage PowerPoint presentations with the [PowerPoint Integration](/docs/integrations/powerpoint) +- Manage files and folders with the [OneDrive Integration](/docs/integrations/onedrive) diff --git a/docs/content/docs/integrations/gcal.mdx b/docs/content/docs/integrations/gcal.mdx index 9a77feb..cc1b296 100644 --- a/docs/content/docs/integrations/gcal.mdx +++ b/docs/content/docs/integrations/gcal.mdx @@ -178,5 +178,7 @@ try { ## Next Steps - Explore the [Gmail Integration](/docs/integrations/gmail) -- Explore the [Google Workspace Integration](/docs/integrations/gworkspace) +- Explore the [Google Docs Integration](/docs/integrations/gdocs) +- Explore the [Google Sheets Integration](/docs/integrations/gsheets) +- Explore the [Google Slides Integration](/docs/integrations/gslides) - See [Advanced Usage](/docs/guides/advanced-usage) for more examples diff --git a/docs/content/docs/integrations/gdocs.mdx b/docs/content/docs/integrations/gdocs.mdx new file mode 100644 index 0000000..1499725 --- /dev/null +++ b/docs/content/docs/integrations/gdocs.mdx @@ -0,0 +1,162 @@ +--- +title: Google Docs +description: Create and edit Google Docs documents +--- + +The Google Docs integration provides access to Google Docs through the Integrate MCP server. + +## Installation + +The Google Docs integration is included with the SDK: + +```typescript +import { gdocsIntegration } from "integrate-sdk/server"; +``` + +## Setup + +### 1. Create Google OAuth Credentials + +1. Go to [Google Cloud Console](https://console.cloud.google.com) +2. Create a new project or select an existing one +3. Enable the **Google Docs API** +4. Create OAuth 2.0 credentials (Web application) +5. Add your redirect URI (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +6. Note your Client ID and Client Secret + +### 2. Configure the Integration on Your Server + +Add the Google Docs integration to your server configuration. The integration automatically reads `GDOCS_CLIENT_ID` and `GDOCS_CLIENT_SECRET` from your environment variables: + +```typescript +import { createMCPServer, gdocsIntegration } from "integrate-sdk/server"; + +export const { client: serverClient } = createMCPServer({ + apiKey: process.env.INTEGRATE_API_KEY, + integrations: [ + gdocsIntegration(), + ], +}); +``` + +You can override the environment variables by passing explicit values: + +```typescript +gdocsIntegration({ + clientId: process.env.CUSTOM_GDOCS_ID, + clientSecret: process.env.CUSTOM_GDOCS_SECRET, +}); +``` + +### 3. Client-Side Usage + +The default client automatically includes all integrations. You can use it directly: + +```typescript +import { client } from "integrate-sdk"; + +await client.authorize("gdocs"); +const docs = await client.gdocs.list({}); +``` + +If you're using a custom client, add the integration to the integrations array: + +```typescript +import { createMCPClient, gdocsIntegration } from "integrate-sdk"; + +const customClient = createMCPClient({ + integrations: [gdocsIntegration()], +}); +``` + +## Configuration Options + + + +## Available Tools + +- **`gdocs_list`** - List Google Docs documents +- **`gdocs_get`** - Get a document and its content +- **`gdocs_create`** - Create a new document +- **`gdocs_append_text`** - Append text to a document +- **`gdocs_replace_text`** - Find and replace text in a document + +## Examples + +### List Documents + +```typescript +const result = await client.gdocs.list({ + page_size: 20, +}); +``` + +### Get a Document + +```typescript +const doc = await client.gdocs.get({ + document_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", +}); +``` + +### Create a Document + +```typescript +const doc = await client.gdocs.create({ + title: "Q4 Planning Notes", +}); +``` + +### Append Text + +```typescript +await client.gdocs.appendText({ + document_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + text: "\n## New Section\n\nContent goes here.", +}); +``` + +### Find and Replace Text + +```typescript +await client.gdocs.replaceText({ + document_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + find: "{{company_name}}", + replace: "Acme Corp", + match_case: false, +}); +``` + +## OAuth Scopes + +The default scope provides full read/write access to documents. You can restrict it: + +- **`https://www.googleapis.com/auth/documents`** - Read and write documents +- **`https://www.googleapis.com/auth/documents.readonly`** - Read-only access + +## Error Handling + +```typescript +try { + const doc = await client.gdocs.get({ + document_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + }); +} catch (error) { + if (error.message.includes("404")) { + console.error("Document not found"); + } else if (error.message.includes("403")) { + console.error("Insufficient permissions"); + } else { + console.error("Unexpected error:", error); + } +} +``` + +## Next Steps + +- Explore the [Google Sheets Integration](/docs/integrations/gsheets) +- Explore the [Google Slides Integration](/docs/integrations/gslides) +- Explore the [Google Drive Integration](/docs/integrations/gdrive) diff --git a/docs/content/docs/integrations/gdrive.mdx b/docs/content/docs/integrations/gdrive.mdx new file mode 100644 index 0000000..d0b254e --- /dev/null +++ b/docs/content/docs/integrations/gdrive.mdx @@ -0,0 +1,244 @@ +--- +title: Google Drive +description: Manage files, folders, and sharing permissions in Google Drive +--- + +The Google Drive integration provides access to Google Drive through the Integrate MCP server. + +## Installation + +The Google Drive integration is included with the SDK: + +```typescript +import { gdriveIntegration } from "integrate-sdk/server"; +``` + +## Setup + +### 1. Create Google OAuth Credentials + +1. Go to [Google Cloud Console](https://console.cloud.google.com) +2. Create a new project or select an existing one +3. Enable the **Google Drive API** +4. Create OAuth 2.0 credentials (Web application) +5. Add your redirect URI (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +6. Note your Client ID and Client Secret + +### 2. Configure the Integration on Your Server + +Add the Google Drive integration to your server configuration. The integration automatically reads `GDRIVE_CLIENT_ID` and `GDRIVE_CLIENT_SECRET` from your environment variables: + +```typescript +import { createMCPServer, gdriveIntegration } from "integrate-sdk/server"; + +export const { client: serverClient } = createMCPServer({ + apiKey: process.env.INTEGRATE_API_KEY, + integrations: [ + gdriveIntegration(), + ], +}); +``` + +You can override the environment variables by passing explicit values: + +```typescript +gdriveIntegration({ + clientId: process.env.CUSTOM_GDRIVE_ID, + clientSecret: process.env.CUSTOM_GDRIVE_SECRET, +}); +``` + +### 3. Client-Side Usage + +The default client automatically includes all integrations. You can use it directly: + +```typescript +import { client } from "integrate-sdk"; + +await client.authorize("gdrive"); +const files = await client.gdrive.listFiles({}); +``` + +If you're using a custom client, add the integration to the integrations array: + +```typescript +import { createMCPClient, gdriveIntegration } from "integrate-sdk"; + +const customClient = createMCPClient({ + integrations: [gdriveIntegration()], +}); +``` + +## Configuration Options + + + +## Available Tools + +### Files & Folders + +- **`gdrive_list_files`** - List files and folders (supports query, pagination, sorting) +- **`gdrive_get_file`** - Get metadata for a file or folder +- **`gdrive_create_folder`** - Create a new folder +- **`gdrive_rename_file`** - Rename a file or folder +- **`gdrive_move_file`** - Move a file or folder to a different parent +- **`gdrive_copy_file`** - Copy a file +- **`gdrive_delete_file`** - Permanently delete a file or folder +- **`gdrive_trash_file`** - Move a file or folder to trash (recoverable) + +### Content + +- **`gdrive_upload_text_file`** - Create a new file with text content +- **`gdrive_download_file`** - Download file content as text (Google Workspace files are auto-exported) + +### Sharing & Permissions + +- **`gdrive_list_permissions`** - List sharing permissions on a file or folder +- **`gdrive_share_file`** - Share a file or folder with a user, group, domain, or anyone +- **`gdrive_remove_permission`** - Remove a sharing permission + +### Account + +- **`gdrive_get_about`** - Get current user info and storage quota + +## Examples + +### List Recent Files + +```typescript +const result = await client.gdrive.listFiles({ + order_by: "modifiedTime desc", + page_size: 20, +}); +``` + +### Search for Files + +```typescript +const result = await client.gdrive.listFiles({ + query: "name contains 'budget' and mimeType != 'application/vnd.google-apps.folder'", +}); +``` + +### List Files in a Folder + +```typescript +const result = await client.gdrive.listFiles({ + parent_id: "folderId123", + order_by: "name", +}); +``` + +### Create a Folder + +```typescript +const folder = await client.gdrive.createFolder({ + name: "Q4 Reports", + parent_id: "parentFolderId", // optional, defaults to root +}); +``` + +### Upload a Text File + +```typescript +const file = await client.gdrive.uploadTextFile({ + name: "notes.md", + content: "# Meeting Notes\n\n- Item 1\n- Item 2", + mime_type: "text/markdown", + parent_id: "folderId123", +}); +``` + +### Download a File + +```typescript +const result = await client.gdrive.downloadFile({ + file_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", +}); + +// For Google Docs/Sheets/Slides, content is auto-exported as plain text or CSV +console.log(result); +``` + +### Move and Rename a File + +```typescript +// Move to a different folder +await client.gdrive.moveFile({ + file_id: "fileId123", + new_parent_id: "destinationFolderId", +}); + +// Rename +await client.gdrive.renameFile({ + file_id: "fileId123", + name: "Final Report Q4 2024", +}); +``` + +### Share a File + +```typescript +// Share with a specific user +await client.gdrive.shareFile({ + file_id: "fileId123", + role: "writer", + type: "user", + email: "colleague@example.com", +}); + +// Share with anyone who has the link +await client.gdrive.shareFile({ + file_id: "fileId123", + role: "reader", + type: "anyone", + send_notification: false, +}); +``` + +### Check Storage Quota + +```typescript +const about = await client.gdrive.getAbout(); +// storageQuota values are returned as strings (bytes) +console.log("Storage used:", about); +``` + +## OAuth Scopes + +The default scope is `https://www.googleapis.com/auth/drive`. You can restrict access with narrower scopes: + +- **`https://www.googleapis.com/auth/drive`** - Full Drive access +- **`https://www.googleapis.com/auth/drive.file`** - Access only files created or opened by the app +- **`https://www.googleapis.com/auth/drive.readonly`** - Read-only access to files and metadata +- **`https://www.googleapis.com/auth/drive.metadata`** - Read/write file metadata only (no content) +- **`https://www.googleapis.com/auth/drive.metadata.readonly`** - Read-only file metadata + +## Error Handling + +```typescript +try { + const file = await client.gdrive.getFile({ + file_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + }); +} catch (error) { + if (error.message.includes("404")) { + console.error("File not found"); + } else if (error.message.includes("403")) { + console.error("Insufficient permissions"); + } else { + console.error("Unexpected error:", error); + } +} +``` + +## Next Steps + +- Explore the [Google Docs Integration](/docs/integrations/gdocs) +- Explore the [Google Sheets Integration](/docs/integrations/gsheets) +- Explore the [Google Slides Integration](/docs/integrations/gslides) +- Explore the [Google Calendar Integration](/docs/integrations/gcal) +- Explore the [OneDrive Integration](/docs/integrations/onedrive) diff --git a/docs/content/docs/integrations/gsheets.mdx b/docs/content/docs/integrations/gsheets.mdx new file mode 100644 index 0000000..af2b499 --- /dev/null +++ b/docs/content/docs/integrations/gsheets.mdx @@ -0,0 +1,174 @@ +--- +title: Google Sheets +description: Read and write Google Sheets spreadsheets +--- + +The Google Sheets integration provides access to Google Sheets through the Integrate MCP server. + +## Installation + +The Google Sheets integration is included with the SDK: + +```typescript +import { gsheetsIntegration } from "integrate-sdk/server"; +``` + +## Setup + +### 1. Create Google OAuth Credentials + +1. Go to [Google Cloud Console](https://console.cloud.google.com) +2. Create a new project or select an existing one +3. Enable the **Google Sheets API** +4. Create OAuth 2.0 credentials (Web application) +5. Add your redirect URI (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +6. Note your Client ID and Client Secret + +### 2. Configure the Integration on Your Server + +Add the Google Sheets integration to your server configuration. The integration automatically reads `GSHEETS_CLIENT_ID` and `GSHEETS_CLIENT_SECRET` from your environment variables: + +```typescript +import { createMCPServer, gsheetsIntegration } from "integrate-sdk/server"; + +export const { client: serverClient } = createMCPServer({ + apiKey: process.env.INTEGRATE_API_KEY, + integrations: [ + gsheetsIntegration(), + ], +}); +``` + +You can override the environment variables by passing explicit values: + +```typescript +gsheetsIntegration({ + clientId: process.env.CUSTOM_GSHEETS_ID, + clientSecret: process.env.CUSTOM_GSHEETS_SECRET, +}); +``` + +### 3. Client-Side Usage + +The default client automatically includes all integrations. You can use it directly: + +```typescript +import { client } from "integrate-sdk"; + +await client.authorize("gsheets"); +const sheets = await client.gsheets.list({}); +``` + +If you're using a custom client, add the integration to the integrations array: + +```typescript +import { createMCPClient, gsheetsIntegration } from "integrate-sdk"; + +const customClient = createMCPClient({ + integrations: [gsheetsIntegration()], +}); +``` + +## Configuration Options + + + +## Available Tools + +- **`gsheets_list`** - List spreadsheets +- **`gsheets_get`** - Get spreadsheet metadata and sheet names +- **`gsheets_get_values`** - Read cell values from a range +- **`gsheets_update_values`** - Write cell values to a range +- **`gsheets_create`** - Create a new spreadsheet +- **`gsheets_append_values`** - Append rows to a sheet +- **`gsheets_clear_values`** - Clear a range of cells +- **`gsheets_batch_update_values`** - Write to multiple ranges in one call + +## Examples + +### Read Cell Values + +```typescript +const result = await client.gsheets.getValues({ + spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + range: "Sheet1!A1:C10", +}); +``` + +### Update Cell Values + +```typescript +await client.gsheets.updateValues({ + spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + range: "Sheet1!A1:B2", + values: JSON.stringify([["Name", "Score"], ["Alice", 95]]), + value_input_option: "USER_ENTERED", +}); +``` + +### Append Rows + +```typescript +await client.gsheets.appendValues({ + spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + range: "Sheet1!A:B", + values: JSON.stringify([["Bob", 88], ["Carol", 92]]), +}); +``` + +### Create a Spreadsheet + +```typescript +const sheet = await client.gsheets.create({ + title: "Monthly Report", + sheet_titles: JSON.stringify(["Summary", "Details", "Raw Data"]), +}); +``` + +### Batch Update Multiple Ranges + +```typescript +await client.gsheets.batchUpdateValues({ + spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + data: JSON.stringify([ + { range: "Sheet1!A1", values: [["Updated"]] }, + { range: "Sheet2!B2", values: [["Also updated"]] }, + ]), + value_input_option: "USER_ENTERED", +}); +``` + +## OAuth Scopes + +The default scope provides full read/write access. You can restrict it: + +- **`https://www.googleapis.com/auth/spreadsheets`** - Read and write spreadsheets +- **`https://www.googleapis.com/auth/spreadsheets.readonly`** - Read-only access + +## Error Handling + +```typescript +try { + const result = await client.gsheets.getValues({ + spreadsheet_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", + range: "Sheet1!A1:B10", + }); +} catch (error) { + if (error.message.includes("404")) { + console.error("Spreadsheet not found"); + } else if (error.message.includes("403")) { + console.error("Insufficient permissions"); + } else { + console.error("Unexpected error:", error); + } +} +``` + +## Next Steps + +- Explore the [Google Docs Integration](/docs/integrations/gdocs) +- Explore the [Google Slides Integration](/docs/integrations/gslides) +- Explore the [Google Drive Integration](/docs/integrations/gdrive) diff --git a/docs/content/docs/integrations/gslides.mdx b/docs/content/docs/integrations/gslides.mdx new file mode 100644 index 0000000..6509c5e --- /dev/null +++ b/docs/content/docs/integrations/gslides.mdx @@ -0,0 +1,183 @@ +--- +title: Google Slides +description: Create and edit Google Slides presentations +--- + +The Google Slides integration provides access to Google Slides through the Integrate MCP server. + +## Installation + +The Google Slides integration is included with the SDK: + +```typescript +import { gslidesIntegration } from "integrate-sdk/server"; +``` + +## Setup + +### 1. Create Google OAuth Credentials + +1. Go to [Google Cloud Console](https://console.cloud.google.com) +2. Create a new project or select an existing one +3. Enable the **Google Slides API** +4. Create OAuth 2.0 credentials (Web application) +5. Add your redirect URI (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +6. Note your Client ID and Client Secret + +### 2. Configure the Integration on Your Server + +Add the Google Slides integration to your server configuration. The integration automatically reads `GSLIDES_CLIENT_ID` and `GSLIDES_CLIENT_SECRET` from your environment variables: + +```typescript +import { createMCPServer, gslidesIntegration } from "integrate-sdk/server"; + +export const { client: serverClient } = createMCPServer({ + apiKey: process.env.INTEGRATE_API_KEY, + integrations: [ + gslidesIntegration(), + ], +}); +``` + +You can override the environment variables by passing explicit values: + +```typescript +gslidesIntegration({ + clientId: process.env.CUSTOM_GSLIDES_ID, + clientSecret: process.env.CUSTOM_GSLIDES_SECRET, +}); +``` + +### 3. Client-Side Usage + +The default client automatically includes all integrations. You can use it directly: + +```typescript +import { client } from "integrate-sdk"; + +await client.authorize("gslides"); +const presentations = await client.gslides.list({}); +``` + +If you're using a custom client, add the integration to the integrations array: + +```typescript +import { createMCPClient, gslidesIntegration } from "integrate-sdk"; + +const customClient = createMCPClient({ + integrations: [gslidesIntegration()], +}); +``` + +## Configuration Options + + + +## Available Tools + +- **`gslides_list`** - List presentations +- **`gslides_get`** - Get a presentation and its slides +- **`gslides_get_page`** - Get a specific slide by page ID +- **`gslides_create`** - Create a new presentation +- **`gslides_add_slide`** - Add a slide to a presentation +- **`gslides_delete_slide`** - Delete a slide from a presentation +- **`gslides_update_text`** - Find and replace text across a presentation + +## Examples + +### List Presentations + +```typescript +const result = await client.gslides.list({ + page_size: 20, +}); +``` + +### Get a Presentation + +```typescript +const presentation = await client.gslides.get({ + presentation_id: "1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Bnc", +}); +``` + +### Get a Specific Slide + +```typescript +const slide = await client.gslides.getPage({ + presentation_id: "1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Bnc", + page_id: "g123abc456", +}); +``` + +### Create a Presentation + +```typescript +const presentation = await client.gslides.create({ + title: "Q4 Review", +}); +``` + +### Add a Slide + +```typescript +await client.gslides.addSlide({ + presentation_id: "1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Bnc", + insertion_index: 2, + layout: "TITLE_AND_BODY", +}); +``` + +### Find and Replace Text + +```typescript +await client.gslides.updateText({ + presentation_id: "1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Bnc", + find: "{{quarter}}", + replace: "Q4 2024", + match_case: false, +}); +``` + +### Delete a Slide + +```typescript +await client.gslides.deleteSlide({ + presentation_id: "1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Bnc", + page_id: "g123abc456", +}); +``` + +## OAuth Scopes + +The default scope provides full read/write access. You can restrict it: + +- **`https://www.googleapis.com/auth/presentations`** - Read and write presentations +- **`https://www.googleapis.com/auth/presentations.readonly`** - Read-only access + +## Error Handling + +```typescript +try { + const presentation = await client.gslides.get({ + presentation_id: "1EAYk18WDjIG-zp_0vLm3CsfQh_i8eXc67Jo2O9C6Bnc", + }); +} catch (error) { + if (error.message.includes("404")) { + console.error("Presentation not found"); + } else if (error.message.includes("403")) { + console.error("Insufficient permissions"); + } else { + console.error("Unexpected error:", error); + } +} +``` + +## Next Steps + +- Explore the [Google Docs Integration](/docs/integrations/gdocs) +- Explore the [Google Sheets Integration](/docs/integrations/gsheets) +- Explore the [Google Drive Integration](/docs/integrations/gdrive) diff --git a/docs/content/docs/integrations/gworkspace.mdx b/docs/content/docs/integrations/gworkspace.mdx deleted file mode 100644 index f532c99..0000000 --- a/docs/content/docs/integrations/gworkspace.mdx +++ /dev/null @@ -1,182 +0,0 @@ ---- -title: Google Workspace -description: Manage Google Sheets, Docs, and Slides ---- - -The Google Workspace integration provides access to Google Sheets, Docs, and Slides APIs through the Integrate MCP server. - -## Installation - -The Google Workspace integration is included with the SDK: - -```typescript -import { gworkspaceIntegration } from "integrate-sdk/server"; -``` - -## Setup - -### 1. Create Google OAuth Credentials - -1. Go to [Google Cloud Console](https://console.cloud.google.com) -2. Create a new project or select an existing one -3. Enable the Google Sheets, Docs, Slides, and Drive APIs -4. Create OAuth 2.0 credentials -5. Note your Client ID and Client Secret - -### 2. Configure the Integration on Your Server - -Add the Google Workspace integration to your server configuration. The integration automatically reads `GWORKSPACE_CLIENT_ID` and `GWORKSPACE_CLIENT_SECRET` from your environment variables: - -```typescript -import { createMCPServer, gworkspaceIntegration } from "integrate-sdk/server"; - -export const { client: serverClient } = createMCPServer({ - apiKey: process.env.INTEGRATE_API_KEY, - integrations: [ - gworkspaceIntegration({ - scopes: [ - "https://www.googleapis.com/auth/spreadsheets", - "https://www.googleapis.com/auth/documents", - "https://www.googleapis.com/auth/presentations", - "https://www.googleapis.com/auth/drive.readonly", - ], // Optional - }), - ], -}); -``` - -You can override the environment variables by passing explicit values: - -```typescript -gworkspaceIntegration({ - clientId: process.env.CUSTOM_GWORKSPACE_ID, - clientSecret: process.env.CUSTOM_GWORKSPACE_SECRET, - scopes: ["https://www.googleapis.com/auth/spreadsheets"], -}); -``` - -### 3. Client-Side Usage - -The default client automatically includes all integrations. You can use it directly: - -```typescript -import { client } from "integrate-sdk"; - -await client.authorize("gworkspace"); -const sheets = await client.gworkspace.sheetsList({}); -``` - -If you're using a custom client, add the integration to the integrations array: - -```typescript -import { createMCPClient, gworkspaceIntegration } from "integrate-sdk"; - -const customClient = createMCPClient({ - integrations: [gworkspaceIntegration()], -}); -``` - -## Configuration Options - - - -## Available Tools - -### Sheets - -- **`gworkspace_sheets_list`** - List spreadsheets -- **`gworkspace_sheets_get`** - Get spreadsheet details -- **`gworkspace_sheets_get_values`** - Get cell values -- **`gworkspace_sheets_update_values`** - Update cell values -- **`gworkspace_sheets_create`** - Create a new spreadsheet - -### Docs - -- **`gworkspace_docs_list`** - List documents -- **`gworkspace_docs_get`** - Get document content -- **`gworkspace_docs_create`** - Create a new document - -### Slides - -- **`gworkspace_slides_list`** - List presentations -- **`gworkspace_slides_get`** - Get presentation details -- **`gworkspace_slides_get_page`** - Get specific slide -- **`gworkspace_slides_create`** - Create a new presentation - -## Examples - -### Update Spreadsheet Values - -```typescript -const result = await client.gworkspace.sheetsUpdateValues({ - spreadsheetId: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms", - range: "Sheet1!A1:B2", - values: [ - ["Name", "Value"], - ["Item 1", "100"], - ], -}); - -console.log("Updated cells:", result); -``` - -### Read Spreadsheet Values - -```typescript -const result = await client.gworkspace.sheetsGetValues({ - spreadsheetId: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms", - range: "Sheet1!A1:B10", -}); - -console.log("Values:", result); -``` - -### Create a Document - -```typescript -const result = await client.gworkspace.docsCreate({ - title: "My New Document", -}); - -console.log("Document created:", result); -``` - -## OAuth Scopes - -The default scopes provide access to Sheets, Docs, Slides, and Drive. You may need different scopes: - -- **`https://www.googleapis.com/auth/spreadsheets`** - Read and write spreadsheets -- **`https://www.googleapis.com/auth/spreadsheets.readonly`** - Read-only spreadsheets -- **`https://www.googleapis.com/auth/documents`** - Read and write documents -- **`https://www.googleapis.com/auth/documents.readonly`** - Read-only documents -- **`https://www.googleapis.com/auth/presentations`** - Read and write presentations -- **`https://www.googleapis.com/auth/drive.readonly`** - Read-only Drive access - -## Error Handling - -```typescript -try { - const result = await client.gworkspace.sheetsUpdateValues({ - spreadsheetId: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms", - range: "Sheet1!A1", - values: [["Hello"]], - }); -} catch (error) { - if (error.message.includes("not found")) { - console.error("Spreadsheet not found"); - } else if (error.message.includes("authentication")) { - console.error("Authentication failed"); - } else { - console.error("Unexpected error:", error); - } -} -``` - -## Next Steps - -- Explore the [Google Calendar Integration](/docs/integrations/gcal) -- Explore the [Gmail Integration](/docs/integrations/gmail) -- See [Advanced Usage](/docs/guides/advanced-usage) for more examples diff --git a/docs/content/docs/integrations/meta.json b/docs/content/docs/integrations/meta.json index 89cc006..390bf1c 100644 --- a/docs/content/docs/integrations/meta.json +++ b/docs/content/docs/integrations/meta.json @@ -9,14 +9,18 @@ "figma", "gcal", "gdocs", + "gdrive", "gsheets", "gslides", "hubspot", "intercom", "linear", "notion", + "excel", "onedrive", "outlook", + "powerpoint", + "word", "polar", "ramp", "slack", diff --git a/docs/content/docs/integrations/onedrive.mdx b/docs/content/docs/integrations/onedrive.mdx index bee481e..2b556b5 100644 --- a/docs/content/docs/integrations/onedrive.mdx +++ b/docs/content/docs/integrations/onedrive.mdx @@ -1,9 +1,9 @@ --- title: OneDrive -description: Manage files, folders, and Office documents +description: Manage files and folders in Microsoft OneDrive --- -The OneDrive integration provides access to Microsoft OneDrive and Office files through the Integrate MCP server. +The OneDrive integration provides file management access to Microsoft OneDrive through the Integrate MCP server. For Office document tools, see the dedicated [Word](/docs/integrations/word), [Excel](/docs/integrations/excel), and [PowerPoint](/docs/integrations/powerpoint) integrations. ## Installation @@ -17,15 +17,15 @@ import { onedriveIntegration } from "integrate-sdk/server"; ### 1. Create a Microsoft App Registration -1. Go to [Azure Portal - App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps) +1. Go to [Azure Portal — App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps) 2. Create a new registration -3. Configure redirect URIs -4. Create a client secret +3. Add your redirect URI under **Authentication** (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +4. Create a client secret under **Certificates & secrets** 5. Note your Application (client) ID and Client Secret ### 2. Configure the Integration on Your Server -Add the OneDrive integration to your server configuration. The integration automatically reads `ONEDRIVE_CLIENT_ID` and `ONEDRIVE_CLIENT_SECRET` from your environment variables: +The integration automatically reads `ONEDRIVE_CLIENT_ID` and `ONEDRIVE_CLIENT_SECRET` from your environment: ```typescript import { createMCPServer, onedriveIntegration } from "integrate-sdk/server"; @@ -33,9 +33,7 @@ import { createMCPServer, onedriveIntegration } from "integrate-sdk/server"; export const { client: serverClient } = createMCPServer({ apiKey: process.env.INTEGRATE_API_KEY, integrations: [ - onedriveIntegration({ - scopes: ["Files.Read", "Files.ReadWrite", "offline_access"], // Optional - }), + onedriveIntegration(), ], }); ``` @@ -46,14 +44,11 @@ You can override the environment variables by passing explicit values: onedriveIntegration({ clientId: process.env.CUSTOM_ONEDRIVE_ID, clientSecret: process.env.CUSTOM_ONEDRIVE_SECRET, - scopes: ["Files.Read", "Files.ReadWrite"], }); ``` ### 3. Client-Side Usage -The default client automatically includes all integrations. You can use it directly: - ```typescript import { client } from "integrate-sdk"; @@ -61,7 +56,7 @@ await client.authorize("onedrive"); const files = await client.onedrive.listFiles({}); ``` -If you're using a custom client, add the integration to the integrations array: +For a custom client: ```typescript import { createMCPClient, onedriveIntegration } from "integrate-sdk"; @@ -80,91 +75,73 @@ const customClient = createMCPClient({ ## Available Tools -### Files - -- **`onedrive_list_files`** - List files and folders -- **`onedrive_get_file`** - Get file metadata -- **`onedrive_download_file`** - Download file content -- **`onedrive_upload_file`** - Upload a file -- **`onedrive_delete_file`** - Delete a file -- **`onedrive_search_files`** - Search for files -- **`onedrive_share_file`** - Create sharing link - -### Word Documents - -- **`onedrive_word_get_content`** - Get Word document content - -### Excel Spreadsheets - -- **`onedrive_excel_get_worksheets`** - List worksheets in an Excel file -- **`onedrive_excel_get_range`** - Get cell range values -- **`onedrive_excel_update_range`** - Update cell range values - -### PowerPoint Presentations - -- **`onedrive_powerpoint_get_slides`** - List slides in a presentation +- **`onedrive_list_files`** - List files and folders (supports path, filter, sort) +- **`onedrive_get_file`** - Get metadata for a file or folder by item ID +- **`onedrive_download_file`** - Download a file's content +- **`onedrive_upload_file`** - Upload a file via PUT (base64 content) +- **`onedrive_delete_file`** - Permanently delete a file or folder +- **`onedrive_search_files`** - Full-text search across all files +- **`onedrive_share_file`** - Create a sharing link ## Examples -### List Files +### List Files in a Folder ```typescript const result = await client.onedrive.listFiles({ path: "/Documents", + order_by: "lastModifiedDateTime desc", + top: 50, }); +``` + +### Search for Files -console.log("Files:", result); +```typescript +const result = await client.onedrive.searchFiles({ + query: "budget 2024", + top: 20, +}); ``` ### Upload a File ```typescript -const result = await client.onedrive.uploadFile({ - path: "/Documents/report.pdf", - content: fileBuffer, +await client.onedrive.uploadFile({ + path: "/Documents", + filename: "report.txt", + content: Buffer.from("Hello, world!").toString("base64"), }); - -console.log("File uploaded:", result); ``` -### Update Excel Range +### Create a Sharing Link ```typescript -const result = await client.onedrive.excelUpdateRange({ - fileId: "01ABCDEF123456789", - worksheetId: "Sheet1", - range: "A1:B2", - values: [ - ["Name", "Value"], - ["Item 1", "100"], - ], +const result = await client.onedrive.shareFile({ + item_id: "ABC123", + type: "view", + scope: "anonymous", }); - -console.log("Range updated:", result); ``` ## OAuth Scopes -The default scopes are `['Files.Read', 'Files.ReadWrite', 'offline_access']`. You may need different scopes: +The default scope is `Files.ReadWrite.All`. Available scopes: -- **`Files.Read`** - Read user files -- **`Files.Read.All`** - Read all files user can access -- **`Files.ReadWrite`** - Read and write user files -- **`Files.ReadWrite.All`** - Read and write all files user can access -- **`offline_access`** - Maintain access without user presence +- **`Files.ReadWrite.All`** - Read and write all files the user can access +- **`Files.Read.All`** - Read-only access to all files +- **`Files.ReadWrite`** - Read and write the user's own files only ## Error Handling ```typescript try { - const result = await client.onedrive.getFile({ - fileId: "01ABCDEF123456789", - }); + const file = await client.onedrive.getFile({ item_id: "ABC123" }); } catch (error) { - if (error.message.includes("not found")) { + if (error.message.includes("404")) { console.error("File not found"); - } else if (error.message.includes("authentication")) { - console.error("Authentication failed"); + } else if (error.message.includes("403")) { + console.error("Insufficient permissions"); } else { console.error("Unexpected error:", error); } @@ -173,6 +150,6 @@ try { ## Next Steps -- Explore the [Google Workspace Integration](/docs/integrations/gworkspace) -- Explore the [Outlook Integration](/docs/integrations/outlook) -- See [Advanced Usage](/docs/guides/advanced-usage) for more examples +- Manage Word documents with the [Word Integration](/docs/integrations/word) +- Manage Excel workbooks with the [Excel Integration](/docs/integrations/excel) +- Manage PowerPoint presentations with the [PowerPoint Integration](/docs/integrations/powerpoint) diff --git a/docs/content/docs/integrations/powerpoint.mdx b/docs/content/docs/integrations/powerpoint.mdx new file mode 100644 index 0000000..b83f378 --- /dev/null +++ b/docs/content/docs/integrations/powerpoint.mdx @@ -0,0 +1,152 @@ +--- +title: PowerPoint +description: Manage Microsoft PowerPoint presentations stored in OneDrive +--- + +The PowerPoint integration provides access to PowerPoint presentations (.pptx) stored in OneDrive through the Integrate MCP server. It uses the Microsoft Graph API for file-level operations — the Graph API does not expose a slide-level editing API for PowerPoint. + +## Installation + +The PowerPoint integration is included with the SDK: + +```typescript +import { powerpointIntegration } from "integrate-sdk/server"; +``` + +## Setup + +### 1. Create a Microsoft App Registration + +1. Go to [Azure Portal — App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps) +2. Create a new registration +3. Add your redirect URI under **Authentication** (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +4. Create a client secret under **Certificates & secrets** +5. Note your Application (client) ID and Client Secret + +### 2. Configure the Integration on Your Server + +The integration automatically reads `POWERPOINT_CLIENT_ID` and `POWERPOINT_CLIENT_SECRET` from your environment: + +```typescript +import { createMCPServer, powerpointIntegration } from "integrate-sdk/server"; + +export const { client: serverClient } = createMCPServer({ + apiKey: process.env.INTEGRATE_API_KEY, + integrations: [ + powerpointIntegration(), + ], +}); +``` + +You can override the environment variables by passing explicit values: + +```typescript +powerpointIntegration({ + clientId: process.env.CUSTOM_POWERPOINT_ID, + clientSecret: process.env.CUSTOM_POWERPOINT_SECRET, +}); +``` + +### 3. Client-Side Usage + +```typescript +import { client } from "integrate-sdk"; + +await client.authorize("powerpoint"); +const presentations = await client.powerpoint.list({}); +``` + +For a custom client: + +```typescript +import { createMCPClient, powerpointIntegration } from "integrate-sdk"; + +const customClient = createMCPClient({ + integrations: [powerpointIntegration()], +}); +``` + +## Configuration Options + + + +## Available Tools + +- **`powerpoint_list`** - Search for PowerPoint presentations +- **`powerpoint_get`** - Get metadata for a presentation by item ID +- **`powerpoint_create`** - Create a new empty .pptx file in OneDrive +- **`powerpoint_copy`** - Copy a presentation (async for large files) +- **`powerpoint_delete`** - Permanently delete a presentation +- **`powerpoint_share`** - Create a sharing link + +## Examples + +### Search for Presentations + +```typescript +const result = await client.powerpoint.list({ query: "quarterly review" }); +``` + +### Create a Presentation + +```typescript +const ppt = await client.powerpoint.create({ + name: "Q4 Investor Deck", // .pptx appended automatically + parent_id: "folderId123", // optional, defaults to root +}); +``` + +### Copy a Presentation + +```typescript +const result = await client.powerpoint.copy({ + item_id: "ABC123", + name: "Q4 Investor Deck — Draft", + parent_id: "draftsFolderId", +}); + +// Large files return a pending status — poll monitor_url to confirm +if (result.status === "pending") { + console.log("Copy in progress:", result.monitor_url); +} +``` + +### Create a Sharing Link + +```typescript +const link = await client.powerpoint.share({ + item_id: "ABC123", + type: "view", + scope: "anonymous", +}); +``` + +## OAuth Scopes + +The default scope is `Files.ReadWrite.All`. Available scopes: + +- **`Files.ReadWrite.All`** - Read and write all files the user can access +- **`Files.Read.All`** - Read-only access + +## Error Handling + +```typescript +try { + const ppt = await client.powerpoint.get({ item_id: "ABC123" }); +} catch (error) { + if (error.message.includes("404")) { + console.error("Presentation not found"); + } else { + console.error("Unexpected error:", error); + } +} +``` + +## Next Steps + +- Manage Word documents with the [Word Integration](/docs/integrations/word) +- Manage Excel workbooks with the [Excel Integration](/docs/integrations/excel) +- Manage files and folders with the [OneDrive Integration](/docs/integrations/onedrive) diff --git a/docs/content/docs/integrations/word.mdx b/docs/content/docs/integrations/word.mdx new file mode 100644 index 0000000..378916d --- /dev/null +++ b/docs/content/docs/integrations/word.mdx @@ -0,0 +1,152 @@ +--- +title: Word +description: Manage Microsoft Word documents stored in OneDrive +--- + +The Word integration provides access to Word documents (.docx) stored in OneDrive through the Integrate MCP server. It uses the Microsoft Graph API for file-level operations — the Graph API does not support paragraph-level editing for Word. + +## Installation + +The Word integration is included with the SDK: + +```typescript +import { wordIntegration } from "integrate-sdk/server"; +``` + +## Setup + +### 1. Create a Microsoft App Registration + +1. Go to [Azure Portal — App Registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps) +2. Create a new registration +3. Add your redirect URI under **Authentication** (e.g. `https://yourdomain.com/api/integrate/oauth/callback`) +4. Create a client secret under **Certificates & secrets** +5. Note your Application (client) ID and Client Secret + +### 2. Configure the Integration on Your Server + +The integration automatically reads `WORD_CLIENT_ID` and `WORD_CLIENT_SECRET` from your environment: + +```typescript +import { createMCPServer, wordIntegration } from "integrate-sdk/server"; + +export const { client: serverClient } = createMCPServer({ + apiKey: process.env.INTEGRATE_API_KEY, + integrations: [ + wordIntegration(), + ], +}); +``` + +You can override the environment variables by passing explicit values: + +```typescript +wordIntegration({ + clientId: process.env.CUSTOM_WORD_ID, + clientSecret: process.env.CUSTOM_WORD_SECRET, +}); +``` + +### 3. Client-Side Usage + +```typescript +import { client } from "integrate-sdk"; + +await client.authorize("word"); +const docs = await client.word.list({}); +``` + +For a custom client: + +```typescript +import { createMCPClient, wordIntegration } from "integrate-sdk"; + +const customClient = createMCPClient({ + integrations: [wordIntegration()], +}); +``` + +## Configuration Options + + + +## Available Tools + +- **`word_list`** - Search for Word documents +- **`word_get`** - Get metadata for a document by item ID +- **`word_create`** - Create a new empty .docx file in OneDrive +- **`word_copy`** - Copy a document (async for large files) +- **`word_delete`** - Permanently delete a document +- **`word_share`** - Create a sharing link + +## Examples + +### Search for Documents + +```typescript +const result = await client.word.list({ query: "meeting notes" }); +``` + +### Create a Document + +```typescript +const doc = await client.word.create({ + name: "Project Brief", // .docx appended automatically + parent_id: "folderId123", // optional, defaults to root +}); +``` + +### Copy a Document + +```typescript +const result = await client.word.copy({ + item_id: "ABC123", + name: "Project Brief — Copy", + parent_id: "archiveFolderId", +}); + +// Large files return a pending status — poll monitor_url to confirm +if (result.status === "pending") { + console.log("Copy in progress:", result.monitor_url); +} +``` + +### Create a Sharing Link + +```typescript +const link = await client.word.share({ + item_id: "ABC123", + type: "edit", + scope: "organization", +}); +``` + +## OAuth Scopes + +The default scope is `Files.ReadWrite.All`. Available scopes: + +- **`Files.ReadWrite.All`** - Read and write all files the user can access +- **`Files.Read.All`** - Read-only access + +## Error Handling + +```typescript +try { + const doc = await client.word.get({ item_id: "ABC123" }); +} catch (error) { + if (error.message.includes("404")) { + console.error("Document not found"); + } else { + console.error("Unexpected error:", error); + } +} +``` + +## Next Steps + +- Manage spreadsheets with the [Excel Integration](/docs/integrations/excel) +- Manage presentations with the [PowerPoint Integration](/docs/integrations/powerpoint) +- Manage files and folders with the [OneDrive Integration](/docs/integrations/onedrive) diff --git a/docs/content/docs/integrations/youtube.mdx b/docs/content/docs/integrations/youtube.mdx index 89e816b..c15769d 100644 --- a/docs/content/docs/integrations/youtube.mdx +++ b/docs/content/docs/integrations/youtube.mdx @@ -192,6 +192,8 @@ try { ## Next Steps -- Explore the [Google Workspace Integration](/docs/integrations/gworkspace) +- Explore the [Google Drive Integration](/docs/integrations/gdrive) +- Explore the [Google Docs Integration](/docs/integrations/gdocs) +- Explore the [Google Sheets Integration](/docs/integrations/gsheets) - Explore the [Gmail Integration](/docs/integrations/gmail) - See [Advanced Usage](/docs/guides/advanced-usage) for more examples diff --git a/index.ts b/index.ts index e48c728..ef7b5d1 100644 --- a/index.ts +++ b/index.ts @@ -48,7 +48,11 @@ import { whatsappIntegration } from './src/integrations/whatsapp.js'; import { calcomIntegration } from './src/integrations/calcom.js'; import { rampIntegration } from './src/integrations/ramp.js'; import { onedriveIntegration } from './src/integrations/onedrive.js'; +import { wordIntegration } from './src/integrations/word.js'; +import { excelIntegration } from './src/integrations/excel.js'; +import { powerpointIntegration } from './src/integrations/powerpoint.js'; import { gdocsIntegration } from './src/integrations/gdocs.js'; +import { gdriveIntegration } from './src/integrations/gdrive.js'; import { gsheetsIntegration } from './src/integrations/gsheets.js'; import { gslidesIntegration } from './src/integrations/gslides.js'; import { polarIntegration } from './src/integrations/polar.js'; @@ -111,7 +115,11 @@ export const client = createMCPClient({ calcomIntegration(), rampIntegration(), onedriveIntegration(), + wordIntegration(), + excelIntegration(), + powerpointIntegration(), gdocsIntegration(), + gdriveIntegration(), gsheetsIntegration(), gslidesIntegration(), polarIntegration(), diff --git a/package.json b/package.json index 8d6f28c..39ff4b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "integrate-sdk", - "version": "0.9.25", + "version": "0.9.26", "description": "Type-safe 3rd party integration SDK for the Integrate MCP server", "type": "module", "main": "./dist/index.js", diff --git a/src/code-mode/runtime-stub.ts b/src/code-mode/runtime-stub.ts index cd82e6d..e1a26cd 100644 --- a/src/code-mode/runtime-stub.ts +++ b/src/code-mode/runtime-stub.ts @@ -77,7 +77,11 @@ function createIntegrationProxy(integrationId) { return new Proxy({}, { get(_target, methodName) { if (typeof methodName !== 'string') return undefined; - return (args) => callTool(integrationId + '_' + camelToSnake(methodName), args); + return (args) => { + const alreadyFull = methodName.startsWith(integrationId + '_') || methodName.startsWith('___'); + const toolName = alreadyFull ? methodName : integrationId + '_' + camelToSnake(methodName); + return callTool(toolName, args); + }; }, }); } diff --git a/src/code-mode/tool-builder.ts b/src/code-mode/tool-builder.ts index 3e4a39c..12095a1 100644 --- a/src/code-mode/tool-builder.ts +++ b/src/code-mode/tool-builder.ts @@ -89,6 +89,12 @@ const DEFAULT_INSTRUCTIONS = [ "Each method returns `ToolResult { content: [{ type, text? }], isError? }` — parse `result.content[0].text` as JSON.", "Only `client`, `callTool`, `fetch`, `console`, `JSON` are available (no npm imports).", "", + "IMPORTANT — method naming rules (violations cause -32602 'tool not found'):", + " • Always use camelCase method names: `client.github.getFileContents(args)` ✓", + " • NEVER use the full tool ID as the method key: `client.github['github_get_file_contents']` ✗ (produces double prefix)", + " • NEVER use snake_case method names: `client.github['get_file_contents']` ✗", + " • If you use `callTool` directly, pass the exact tool name as listed (e.g. `callTool('github_get_file_contents', args)`) — do NOT add the integration prefix again.", + "", "Call `get_integration_types` with an integration name to get full parameter types before writing code.", "", "Available methods:", diff --git a/src/integrations/excel-client.ts b/src/integrations/excel-client.ts new file mode 100644 index 0000000..29a02af --- /dev/null +++ b/src/integrations/excel-client.ts @@ -0,0 +1,300 @@ +/** + * Excel Integration Client Types + * Fully typed interface for Excel workbook methods + */ + +import type { MCPToolCallResponse } from "../protocol/messages.js"; + +/** + * Excel Integration Client Interface + * Provides type-safe methods for managing Excel workbooks in OneDrive + */ +export interface ExcelIntegrationClient { + /** + * Search for Excel workbooks + * + * @example + * ```typescript + * const workbooks = await client.excel.list({ query: "budget" }); + * ``` + */ + list(params?: { + /** Filter by name (default: searches .xlsx) */ + query?: string; + /** Max results (default 25) */ + top?: number; + }): Promise; + + /** + * Get metadata for an Excel workbook + * + * @example + * ```typescript + * const wb = await client.excel.get({ item_id: "ABC123" }); + * ``` + */ + get(params: { + /** Workbook item ID */ + item_id: string; + }): Promise; + + /** + * Create a new empty .xlsx workbook in OneDrive + * + * @example + * ```typescript + * const wb = await client.excel.create({ name: "Monthly Report" }); + * ``` + */ + create(params: { + /** File name (.xlsx appended automatically if missing) */ + name: string; + /** Parent folder item ID (defaults to root) */ + parent_id?: string; + }): Promise; + + /** + * Delete an Excel workbook permanently + * + * @example + * ```typescript + * await client.excel.delete({ item_id: "ABC123" }); + * ``` + */ + delete(params: { + /** Workbook item ID */ + item_id: string; + }): Promise; + + /** + * Create a sharing link for an Excel workbook + * + * @example + * ```typescript + * const link = await client.excel.share({ item_id: "ABC123", type: "edit" }); + * ``` + */ + share(params: { + /** Workbook item ID */ + item_id: string; + /** view, edit, or embed (default: view) */ + type?: "view" | "edit" | "embed"; + /** anonymous or organization (default: anonymous) */ + scope?: "anonymous" | "organization"; + }): Promise; + + /** + * List all worksheets in a workbook + * + * @example + * ```typescript + * const sheets = await client.excel.listWorksheets({ item_id: "ABC123" }); + * ``` + */ + listWorksheets(params: { + /** Workbook item ID */ + item_id: string; + }): Promise; + + /** + * Add a new worksheet to a workbook + * + * @example + * ```typescript + * await client.excel.addWorksheet({ item_id: "ABC123", name: "Summary" }); + * ``` + */ + addWorksheet(params: { + /** Workbook item ID */ + item_id: string; + /** New worksheet name */ + name: string; + }): Promise; + + /** + * Delete a worksheet from a workbook + * + * @example + * ```typescript + * await client.excel.deleteWorksheet({ item_id: "ABC123", worksheet: "Sheet2" }); + * ``` + */ + deleteWorksheet(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + }): Promise; + + /** + * Get values, formulas, and formatting from a cell range + * + * @example + * ```typescript + * const data = await client.excel.getRange({ + * item_id: "ABC123", + * worksheet: "Sheet1", + * range: "A1:C10", + * }); + * ``` + */ + getRange(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + /** A1 notation e.g. A1:C10 */ + range: string; + }): Promise; + + /** + * Update cell values in a range + * + * @example + * ```typescript + * await client.excel.updateRange({ + * item_id: "ABC123", + * worksheet: "Sheet1", + * range: "A1:B2", + * values: JSON.stringify([["Name", "Age"], ["Alice", 30]]), + * }); + * ``` + */ + updateRange(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + /** A1 notation e.g. A1:C3 */ + range: string; + /** JSON 2D array — must match range dimensions */ + values: string; + }): Promise; + + /** + * Clear contents and/or formatting from a cell range + * + * @example + * ```typescript + * await client.excel.clearRange({ + * item_id: "ABC123", + * worksheet: "Sheet1", + * range: "A1:Z100", + * }); + * ``` + */ + clearRange(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + /** A1 notation */ + range: string; + /** All, Contents, Formats, or Hyperlinks (default: All) */ + apply_to?: "All" | "Contents" | "Formats" | "Hyperlinks"; + }): Promise; + + /** + * Get the bounding range of all data in a worksheet + * + * @example + * ```typescript + * const used = await client.excel.getUsedRange({ item_id: "ABC123", worksheet: "Sheet1" }); + * ``` + */ + getUsedRange(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + }): Promise; + + /** + * List all tables in a worksheet + * + * @example + * ```typescript + * const tables = await client.excel.listTables({ item_id: "ABC123", worksheet: "Sheet1" }); + * ``` + */ + listTables(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + }): Promise; + + /** + * Create a table from a cell range + * + * @example + * ```typescript + * await client.excel.createTable({ + * item_id: "ABC123", + * worksheet: "Sheet1", + * range: "A1:D10", + * has_headers: true, + * }); + * ``` + */ + createTable(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + /** Cell range for the table e.g. A1:D10 */ + range: string; + /** Whether first row is a header row (default: true) */ + has_headers?: boolean; + }): Promise; + + /** + * Get rows from a table + * + * @example + * ```typescript + * const rows = await client.excel.getTableRows({ + * item_id: "ABC123", + * worksheet: "Sheet1", + * table: "Table1", + * top: 100, + * }); + * ``` + */ + getTableRows(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + /** Table name or ID */ + table: string; + /** Max rows to return */ + top?: number; + /** Rows to skip (for pagination) */ + skip?: number; + }): Promise; + + /** + * Append rows to a table + * + * @example + * ```typescript + * await client.excel.addTableRows({ + * item_id: "ABC123", + * worksheet: "Sheet1", + * table: "Table1", + * values: JSON.stringify([["Alice", 30], ["Bob", 25]]), + * }); + * ``` + */ + addTableRows(params: { + /** Workbook item ID */ + item_id: string; + /** Worksheet name or ID */ + worksheet: string; + /** Table name or ID */ + table: string; + /** JSON 2D array of rows */ + values: string; + }): Promise; +} diff --git a/src/integrations/excel.ts b/src/integrations/excel.ts new file mode 100644 index 0000000..88c2983 --- /dev/null +++ b/src/integrations/excel.ts @@ -0,0 +1,73 @@ +/** + * Excel Integration + * Enables Excel workbook tools with OAuth configuration + */ + +import type { MCPIntegration, OAuthConfig } from "./types.js"; +import { getEnv } from "../utils/env.js"; +import { createLogger } from "../utils/logger.js"; + +const logger = createLogger('Excel'); + +export interface ExcelIntegrationConfig { + /** Microsoft OAuth client ID (defaults to EXCEL_CLIENT_ID env var) */ + clientId?: string; + /** Microsoft OAuth client secret (defaults to EXCEL_CLIENT_SECRET env var) */ + clientSecret?: string; + /** Additional OAuth scopes */ + scopes?: string[]; + /** Optional OAuth scopes */ + optionalScopes?: string[]; + /** OAuth redirect URI */ + redirectUri?: string; +} + +const EXCEL_TOOLS = [ + "excel_list", + "excel_get", + "excel_create", + "excel_delete", + "excel_share", + "excel_list_worksheets", + "excel_add_worksheet", + "excel_delete_worksheet", + "excel_get_range", + "excel_update_range", + "excel_clear_range", + "excel_get_used_range", + "excel_list_tables", + "excel_create_table", + "excel_get_table_rows", + "excel_add_table_rows", +] as const; + +export function excelIntegration(config: ExcelIntegrationConfig = {}): MCPIntegration<"excel"> { + const oauth: OAuthConfig = { + provider: "excel", + clientId: config.clientId ?? getEnv('EXCEL_CLIENT_ID'), + clientSecret: config.clientSecret ?? getEnv('EXCEL_CLIENT_SECRET'), + scopes: config.scopes, + optionalScopes: config.optionalScopes, + redirectUri: config.redirectUri, + config, + }; + + return { + id: "excel", + name: "Excel", + logoUrl: "https://wdvtnli2jn3texa6.public.blob.vercel-storage.com/excel.png", + tools: [...EXCEL_TOOLS], + oauth, + + async onInit(_client) { + logger.debug("Excel integration initialized"); + }, + + async onAfterConnect(_client) { + logger.debug("Excel integration connected"); + }, + }; +} + +export type ExcelTools = typeof EXCEL_TOOLS[number]; +export type { ExcelIntegrationClient } from "./excel-client.js"; diff --git a/src/integrations/gdrive-client.ts b/src/integrations/gdrive-client.ts new file mode 100644 index 0000000..691b2a4 --- /dev/null +++ b/src/integrations/gdrive-client.ts @@ -0,0 +1,277 @@ +/** + * Google Drive Integration Client Types + * Fully typed interface for Google Drive integration methods + */ + +import type { MCPToolCallResponse } from "../protocol/messages.js"; + +export interface GDriveFile { + id: string; + name: string; + mimeType: string; + size?: string; + parents?: string[]; + webViewLink?: string; + modifiedTime?: string; + createdTime?: string; + owners?: Array<{ displayName: string; emailAddress: string; photoLink?: string }>; + shared?: boolean; + trashed?: boolean; +} + +export interface GDrivePermission { + id: string; + type: "user" | "group" | "domain" | "anyone"; + role: "owner" | "organizer" | "fileOrganizer" | "writer" | "commenter" | "reader"; + emailAddress?: string; + domain?: string; + displayName?: string; + expirationTime?: string; +} + +export interface GDriveAbout { + user: { + displayName: string; + emailAddress: string; + photoLink?: string; + }; + storageQuota: { + limit?: string; + usage: string; + usageInDrive: string; + usageInDriveTrash: string; + }; +} + +/** + * Google Drive Integration Client Interface + * Provides type-safe methods for all Google Drive operations + */ +export interface GDriveIntegrationClient { + /** + * List files and folders in Google Drive + * + * @example + * ```typescript + * const result = await client.gdrive.listFiles({ + * query: "name contains 'report'", + * order_by: "modifiedTime desc", + * page_size: 50, + * }); + * ``` + */ + listFiles(params?: { + /** Files to return (default 20, max 1000) */ + page_size?: number; + /** Pagination token */ + page_token?: string; + /** Drive query string e.g. "name contains 'report'" */ + query?: string; + /** Only list files in this folder ID */ + parent_id?: string; + /** Sort order e.g. "modifiedTime desc" */ + order_by?: string; + }): Promise; + + /** + * Get metadata for a file or folder + * + * @example + * ```typescript + * const file = await client.gdrive.getFile({ file_id: "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms" }); + * ``` + */ + getFile(params: { + /** File or folder ID */ + file_id: string; + }): Promise; + + /** + * Create a new folder + * + * @example + * ```typescript + * const folder = await client.gdrive.createFolder({ name: "Reports 2024" }); + * ``` + */ + createFolder(params: { + /** Folder name */ + name: string; + /** Parent folder ID (defaults to root) */ + parent_id?: string; + }): Promise; + + /** + * Rename a file or folder + * + * @example + * ```typescript + * await client.gdrive.renameFile({ file_id: "abc123", name: "Q4 Report" }); + * ``` + */ + renameFile(params: { + /** File or folder ID */ + file_id: string; + /** New name */ + name: string; + }): Promise; + + /** + * Move a file or folder to a different parent + * + * @example + * ```typescript + * await client.gdrive.moveFile({ file_id: "abc123", new_parent_id: "folderId" }); + * ``` + */ + moveFile(params: { + /** File or folder ID to move */ + file_id: string; + /** Destination folder ID */ + new_parent_id: string; + }): Promise; + + /** + * Copy a file + * + * @example + * ```typescript + * const copy = await client.gdrive.copyFile({ file_id: "abc123", name: "Budget Copy" }); + * ``` + */ + copyFile(params: { + /** File ID to copy */ + file_id: string; + /** Name for the copy (defaults to "Copy of ") */ + name?: string; + /** Destination folder ID */ + parent_id?: string; + }): Promise; + + /** + * Permanently delete a file or folder (not recoverable) + * + * @example + * ```typescript + * await client.gdrive.deleteFile({ file_id: "abc123" }); + * ``` + */ + deleteFile(params: { + /** File or folder ID */ + file_id: string; + }): Promise; + + /** + * Move a file or folder to trash (recoverable) + * + * @example + * ```typescript + * await client.gdrive.trashFile({ file_id: "abc123" }); + * ``` + */ + trashFile(params: { + /** File or folder ID */ + file_id: string; + }): Promise; + + /** + * Create a new file with text content + * + * @example + * ```typescript + * const file = await client.gdrive.uploadTextFile({ + * name: "notes.txt", + * content: "Hello, world!", + * }); + * ``` + */ + uploadTextFile(params: { + /** File name */ + name: string; + /** Text content */ + content: string; + /** MIME type (default: text/plain) */ + mime_type?: string; + /** Parent folder ID */ + parent_id?: string; + }): Promise; + + /** + * Download file content as text. Google Workspace files are auto-exported. + * + * @example + * ```typescript + * const result = await client.gdrive.downloadFile({ file_id: "abc123" }); + * ``` + */ + downloadFile(params: { + /** File ID */ + file_id: string; + }): Promise; + + /** + * List sharing permissions on a file or folder + * + * @example + * ```typescript + * const perms = await client.gdrive.listPermissions({ file_id: "abc123" }); + * ``` + */ + listPermissions(params: { + /** File or folder ID */ + file_id: string; + }): Promise; + + /** + * Share a file or folder + * + * @example + * ```typescript + * await client.gdrive.shareFile({ + * file_id: "abc123", + * role: "reader", + * type: "user", + * email: "colleague@example.com", + * }); + * ``` + */ + shareFile(params: { + /** File or folder ID */ + file_id: string; + /** reader, commenter, writer, or owner */ + role: "reader" | "commenter" | "writer" | "owner"; + /** user, group, domain, or anyone */ + type: "user" | "group" | "domain" | "anyone"; + /** Required when type is user or group */ + email?: string; + /** Required when type is domain */ + domain?: string; + /** Send notification email (default: true) */ + send_notification?: boolean; + }): Promise; + + /** + * Remove a sharing permission + * + * @example + * ```typescript + * await client.gdrive.removePermission({ file_id: "abc123", permission_id: "perm456" }); + * ``` + */ + removePermission(params: { + /** File or folder ID */ + file_id: string; + /** Permission ID (from gdrive_list_permissions) */ + permission_id: string; + }): Promise; + + /** + * Get current user info and storage quota + * + * @example + * ```typescript + * const about = await client.gdrive.getAbout(); + * ``` + */ + getAbout(params?: Record): Promise; +} diff --git a/src/integrations/gdrive.ts b/src/integrations/gdrive.ts new file mode 100644 index 0000000..0ee87d3 --- /dev/null +++ b/src/integrations/gdrive.ts @@ -0,0 +1,71 @@ +/** + * Google Drive Integration + * Enables Google Drive tools with OAuth configuration + */ + +import type { MCPIntegration, OAuthConfig } from "./types.js"; +import { getEnv } from "../utils/env.js"; +import { createLogger } from "../utils/logger.js"; + +const logger = createLogger('Google Drive'); + +export interface GDriveIntegrationConfig { + /** Google OAuth client ID (defaults to GDRIVE_CLIENT_ID env var) */ + clientId?: string; + /** Google OAuth client secret (defaults to GDRIVE_CLIENT_SECRET env var) */ + clientSecret?: string; + /** Additional OAuth scopes */ + scopes?: string[]; + /** Optional OAuth scopes (user may choose to grant or deny) */ + optionalScopes?: string[]; + /** OAuth redirect URI */ + redirectUri?: string; +} + +const GDRIVE_TOOLS = [ + "gdrive_list_files", + "gdrive_get_file", + "gdrive_create_folder", + "gdrive_rename_file", + "gdrive_move_file", + "gdrive_copy_file", + "gdrive_delete_file", + "gdrive_trash_file", + "gdrive_upload_text_file", + "gdrive_download_file", + "gdrive_list_permissions", + "gdrive_share_file", + "gdrive_remove_permission", + "gdrive_get_about", +] as const; + +export function gdriveIntegration(config: GDriveIntegrationConfig = {}): MCPIntegration<"gdrive"> { + const oauth: OAuthConfig = { + provider: "gdrive", + clientId: config.clientId ?? getEnv('GDRIVE_CLIENT_ID'), + clientSecret: config.clientSecret ?? getEnv('GDRIVE_CLIENT_SECRET'), + scopes: config.scopes, + optionalScopes: config.optionalScopes, + redirectUri: config.redirectUri, + config, + }; + + return { + id: "gdrive", + name: "Google Drive", + logoUrl: "https://wdvtnli2jn3texa6.public.blob.vercel-storage.com/google_drive.png", + tools: [...GDRIVE_TOOLS], + oauth, + + async onInit(_client) { + logger.debug("Google Drive integration initialized"); + }, + + async onAfterConnect(_client) { + logger.debug("Google Drive integration connected"); + }, + }; +} + +export type GDriveTools = typeof GDRIVE_TOOLS[number]; +export type { GDriveIntegrationClient } from "./gdrive-client.js"; diff --git a/src/integrations/onedrive-client.ts b/src/integrations/onedrive-client.ts index 89790c2..62b926f 100644 --- a/src/integrations/onedrive-client.ts +++ b/src/integrations/onedrive-client.ts @@ -1,13 +1,10 @@ /** * OneDrive Integration Client Types - * Fully typed interface for OneDrive integration methods + * Fully typed interface for OneDrive file management methods */ import type { MCPToolCallResponse } from "../protocol/messages.js"; -/** - * OneDrive Drive Item - */ export interface OneDriveDriveItem { id: string; name: string; @@ -15,342 +12,124 @@ export interface OneDriveDriveItem { lastModifiedDateTime: string; size?: number; webUrl: string; - createdBy?: { - user?: { - displayName: string; - email?: string; - }; - }; - lastModifiedBy?: { - user?: { - displayName: string; - email?: string; - }; - }; - parentReference?: { - driveId: string; - driveType: string; - id: string; - path: string; - }; - file?: { - mimeType: string; - hashes?: { - quickXorHash?: string; - sha1Hash?: string; - }; - }; - folder?: { - childCount: number; - }; - fileSystemInfo?: { - createdDateTime: string; - lastModifiedDateTime: string; - }; -} - -/** - * OneDrive Permission - */ -export interface OneDrivePermission { - id: string; - roles: string[]; - link?: { - type: "view" | "edit" | "embed"; - scope: "anonymous" | "organization"; - webUrl: string; - }; - grantedTo?: { - user?: { - displayName: string; - email?: string; - id: string; - }; - }; - grantedToIdentities?: Array<{ - user?: { - displayName: string; - email?: string; - id: string; - }; - }>; - shareId?: string; -} - -/** - * OneDrive Word Document Content - */ -export interface OneDriveWordContent { - content: string; - contentType: "text" | "html"; -} - -/** - * OneDrive Excel Worksheet - */ -export interface OneDriveExcelWorksheet { - id: string; - name: string; - position: number; - visibility: "visible" | "hidden" | "veryHidden"; -} - -/** - * OneDrive Excel Range - */ -export interface OneDriveExcelRange { - address: string; - addressLocal: string; - cellCount: number; - columnCount: number; - rowCount: number; - columnIndex: number; - rowIndex: number; - text?: string[][]; - values?: any[][]; - formulas?: string[][]; - numberFormat?: string[][]; -} - -/** - * OneDrive PowerPoint Slide - */ -export interface OneDrivePowerPointSlide { - id: string; - slideIndex: number; + file?: { mimeType: string }; + folder?: { childCount: number }; + parentReference?: { driveId: string; id: string; path: string }; } /** * OneDrive Integration Client Interface - * Provides type-safe methods for all OneDrive operations + * Provides type-safe methods for OneDrive file management */ export interface OneDriveIntegrationClient { /** - * List files in a folder - * + * List files and folders in OneDrive + * * @example * ```typescript - * const files = await client.onedrive.listFiles({ - * folder_id: "root", - * order_by: "lastModifiedDateTime desc" - * }); + * const files = await client.onedrive.listFiles({ path: "/Documents", top: 50 }); * ``` */ listFiles(params?: { - /** Folder ID (default: "root") */ - folder_id?: string; - /** Filter query */ + /** Folder path to list (defaults to root) */ + path?: string; + /** Items to return (default 100) */ + top?: number; + /** OData filter e.g. "file ne null" */ filter?: string; - /** Order by field */ + /** OData sort e.g. "lastModifiedDateTime desc" */ order_by?: string; - /** Number of items to return */ - top?: number; - /** Number of items to skip */ - skip?: number; }): Promise; /** - * Get file metadata - * + * Get metadata for a file or folder + * * @example * ```typescript - * const file = await client.onedrive.getFile({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF" - * }); + * const file = await client.onedrive.getFile({ item_id: "ABC123" }); * ``` */ getFile(params: { - /** File ID */ - file_id: string; + /** Item ID */ + item_id: string; }): Promise; /** - * Download file content - * + * Get the download URL for a file + * * @example * ```typescript - * const content = await client.onedrive.downloadFile({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF" - * }); + * const content = await client.onedrive.downloadFile({ item_id: "ABC123" }); * ``` */ downloadFile(params: { - /** File ID */ - file_id: string; + /** File item ID */ + item_id: string; }): Promise; /** - * Upload a file - * + * Upload a file to OneDrive + * * @example * ```typescript - * const file = await client.onedrive.uploadFile({ - * folder_id: "root", - * file_name: "document.txt", - * content: "Hello, World!", - * conflict_behavior: "rename" + * await client.onedrive.uploadFile({ + * path: "/Documents", + * filename: "report.pdf", + * content: base64Content, * }); * ``` */ uploadFile(params: { - /** Parent folder ID (default: "root") */ - folder_id?: string; + /** Target folder path in OneDrive */ + path: string; /** File name */ - file_name: string; - /** File content (base64 encoded for binary files) */ + filename: string; + /** File content (base64 encoded) */ content: string; - /** Conflict behavior */ - conflict_behavior?: "rename" | "replace" | "fail"; }): Promise; /** - * Delete a file - * + * Delete a file or folder permanently + * * @example * ```typescript - * await client.onedrive.deleteFile({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF" - * }); + * await client.onedrive.deleteFile({ item_id: "ABC123" }); * ``` */ deleteFile(params: { - /** File ID */ - file_id: string; + /** Item ID to delete */ + item_id: string; }): Promise; /** - * Search for files - * + * Search across all files in OneDrive + * * @example * ```typescript - * const results = await client.onedrive.searchFiles({ - * query: "presentation", - * top: 20 - * }); + * const results = await client.onedrive.searchFiles({ query: "budget", top: 20 }); * ``` */ searchFiles(params: { - /** Search query */ + /** Search text */ query: string; - /** Number of items to return */ + /** Max results (default 25) */ top?: number; }): Promise; /** - * Create a sharing link - * + * Create a sharing link for a file or folder + * * @example * ```typescript - * const permission = await client.onedrive.shareFile({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF", - * type: "view", - * scope: "anonymous" - * }); + * const link = await client.onedrive.shareFile({ item_id: "ABC123", type: "view" }); * ``` */ shareFile(params: { - /** File ID */ - file_id: string; - /** Link type */ - type: "view" | "edit" | "embed"; - /** Link scope */ - scope: "anonymous" | "organization"; - /** Expiration date (ISO 8601) */ - expiration_date_time?: string; - /** Password protection */ - password?: string; - }): Promise; - - /** - * Get Word document content - * - * @example - * ```typescript - * const content = await client.onedrive.wordGetContent({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF", - * format: "text" - * }); - * ``` - */ - wordGetContent(params: { - /** Word document file ID */ - file_id: string; - /** Content format */ - format?: "text" | "html"; - }): Promise; - - /** - * Get Excel worksheets - * - * @example - * ```typescript - * const worksheets = await client.onedrive.excelGetWorksheets({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF" - * }); - * ``` - */ - excelGetWorksheets(params: { - /** Excel file ID */ - file_id: string; - }): Promise; - - /** - * Get Excel range data - * - * @example - * ```typescript - * const range = await client.onedrive.excelGetRange({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF", - * worksheet_name: "Sheet1", - * range_address: "A1:B10" - * }); - * ``` - */ - excelGetRange(params: { - /** Excel file ID */ - file_id: string; - /** Worksheet name or ID */ - worksheet_name: string; - /** Range address (e.g., "A1:B10") */ - range_address: string; - }): Promise; - - /** - * Update Excel range data - * - * @example - * ```typescript - * await client.onedrive.excelUpdateRange({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF", - * worksheet_name: "Sheet1", - * range_address: "A1:B2", - * values: [["Name", "Age"], ["John", 30]] - * }); - * ``` - */ - excelUpdateRange(params: { - /** Excel file ID */ - file_id: string; - /** Worksheet name or ID */ - worksheet_name: string; - /** Range address (e.g., "A1:B10") */ - range_address: string; - /** Values to set */ - values: any[][]; - }): Promise; - - /** - * Get PowerPoint slides - * - * @example - * ```typescript - * const slides = await client.onedrive.powerpointGetSlides({ - * file_id: "01234567-89AB-CDEF-0123-456789ABCDEF" - * }); - * ``` - */ - powerpointGetSlides(params: { - /** PowerPoint file ID */ - file_id: string; + /** Item ID */ + item_id: string; + /** view, edit, or embed (default: view) */ + type?: "view" | "edit" | "embed"; + /** anonymous or organization (default: anonymous) */ + scope?: "anonymous" | "organization"; }): Promise; } diff --git a/src/integrations/onedrive.ts b/src/integrations/onedrive.ts index 0cf1544..61dc6b7 100644 --- a/src/integrations/onedrive.ts +++ b/src/integrations/onedrive.ts @@ -41,11 +41,6 @@ const ONEDRIVE_TOOLS = [ "onedrive_delete_file", "onedrive_search_files", "onedrive_share_file", - "onedrive_word_get_content", - "onedrive_excel_get_worksheets", - "onedrive_excel_get_range", - "onedrive_excel_update_range", - "onedrive_powerpoint_get_slides", ] as const; diff --git a/src/integrations/powerpoint-client.ts b/src/integrations/powerpoint-client.ts new file mode 100644 index 0000000..a2b243a --- /dev/null +++ b/src/integrations/powerpoint-client.ts @@ -0,0 +1,105 @@ +/** + * PowerPoint Integration Client Types + * Fully typed interface for PowerPoint presentation methods + */ + +import type { MCPToolCallResponse } from "../protocol/messages.js"; + +/** + * PowerPoint Integration Client Interface + * Provides type-safe methods for managing PowerPoint files in OneDrive + */ +export interface PowerPointIntegrationClient { + /** + * Search for PowerPoint presentations + * + * @example + * ```typescript + * const presentations = await client.powerpoint.list({ query: "deck" }); + * ``` + */ + list(params?: { + /** Filter by name (default: searches .pptx) */ + query?: string; + /** Max results (default 25) */ + top?: number; + }): Promise; + + /** + * Get metadata for a presentation + * + * @example + * ```typescript + * const ppt = await client.powerpoint.get({ item_id: "ABC123" }); + * ``` + */ + get(params: { + /** Presentation item ID */ + item_id: string; + }): Promise; + + /** + * Create a new empty .pptx file in OneDrive + * + * @example + * ```typescript + * const ppt = await client.powerpoint.create({ name: "Q4 Review" }); + * ``` + */ + create(params: { + /** File name (.pptx appended automatically if missing) */ + name: string; + /** Parent folder item ID (defaults to root) */ + parent_id?: string; + }): Promise; + + /** + * Copy a presentation + * + * For large files the API returns `{ status: "pending", monitor_url }` — poll + * `monitor_url` until it returns a DriveItem to confirm completion. + * + * @example + * ```typescript + * const copy = await client.powerpoint.copy({ item_id: "ABC123", name: "Deck Copy" }); + * ``` + */ + copy(params: { + /** Presentation item ID to copy */ + item_id: string; + /** Name for the copy */ + name?: string; + /** Destination folder item ID */ + parent_id?: string; + }): Promise; + + /** + * Delete a presentation permanently + * + * @example + * ```typescript + * await client.powerpoint.delete({ item_id: "ABC123" }); + * ``` + */ + delete(params: { + /** Presentation item ID */ + item_id: string; + }): Promise; + + /** + * Create a sharing link for a presentation + * + * @example + * ```typescript + * const link = await client.powerpoint.share({ item_id: "ABC123", type: "view" }); + * ``` + */ + share(params: { + /** Presentation item ID */ + item_id: string; + /** view, edit, or embed (default: view) */ + type?: "view" | "edit" | "embed"; + /** anonymous or organization (default: anonymous) */ + scope?: "anonymous" | "organization"; + }): Promise; +} diff --git a/src/integrations/powerpoint.ts b/src/integrations/powerpoint.ts new file mode 100644 index 0000000..32c35de --- /dev/null +++ b/src/integrations/powerpoint.ts @@ -0,0 +1,63 @@ +/** + * PowerPoint Integration + * Enables PowerPoint presentation tools with OAuth configuration + */ + +import type { MCPIntegration, OAuthConfig } from "./types.js"; +import { getEnv } from "../utils/env.js"; +import { createLogger } from "../utils/logger.js"; + +const logger = createLogger('PowerPoint'); + +export interface PowerPointIntegrationConfig { + /** Microsoft OAuth client ID (defaults to POWERPOINT_CLIENT_ID env var) */ + clientId?: string; + /** Microsoft OAuth client secret (defaults to POWERPOINT_CLIENT_SECRET env var) */ + clientSecret?: string; + /** Additional OAuth scopes */ + scopes?: string[]; + /** Optional OAuth scopes */ + optionalScopes?: string[]; + /** OAuth redirect URI */ + redirectUri?: string; +} + +const POWERPOINT_TOOLS = [ + "powerpoint_list", + "powerpoint_get", + "powerpoint_create", + "powerpoint_copy", + "powerpoint_delete", + "powerpoint_share", +] as const; + +export function powerpointIntegration(config: PowerPointIntegrationConfig = {}): MCPIntegration<"powerpoint"> { + const oauth: OAuthConfig = { + provider: "powerpoint", + clientId: config.clientId ?? getEnv('POWERPOINT_CLIENT_ID'), + clientSecret: config.clientSecret ?? getEnv('POWERPOINT_CLIENT_SECRET'), + scopes: config.scopes, + optionalScopes: config.optionalScopes, + redirectUri: config.redirectUri, + config, + }; + + return { + id: "powerpoint", + name: "PowerPoint", + logoUrl: "https://wdvtnli2jn3texa6.public.blob.vercel-storage.com/powerpoint.png", + tools: [...POWERPOINT_TOOLS], + oauth, + + async onInit(_client) { + logger.debug("PowerPoint integration initialized"); + }, + + async onAfterConnect(_client) { + logger.debug("PowerPoint integration connected"); + }, + }; +} + +export type PowerPointTools = typeof POWERPOINT_TOOLS[number]; +export type { PowerPointIntegrationClient } from "./powerpoint-client.js"; diff --git a/src/integrations/whatsapp-client.ts b/src/integrations/whatsapp-client.ts index d9a6ce7..d9f295b 100644 --- a/src/integrations/whatsapp-client.ts +++ b/src/integrations/whatsapp-client.ts @@ -1,320 +1,531 @@ /** * WhatsApp Business Integration Client Types - * Fully typed interface for WhatsApp Business integration methods + * Fully typed interface for WhatsApp Business Cloud API methods */ import type { MCPToolCallResponse } from "../protocol/messages.js"; -/** - * WhatsApp Message - */ -export interface WhatsAppMessage { - id: string; - from: string; - to: string; - timestamp: string; - type: "text" | "image" | "video" | "audio" | "document" | "template"; - text?: { - body: string; - }; - image?: { - id: string; - mime_type: string; - sha256: string; - }; - video?: { - id: string; - mime_type: string; - sha256: string; - }; - audio?: { - id: string; - mime_type: string; - sha256: string; - }; - document?: { - id: string; - mime_type: string; - sha256: string; - filename: string; - }; - status?: "sent" | "delivered" | "read" | "failed"; +export interface WhatsAppSendResult { + messages: Array<{ id: string }>; + contacts: Array<{ input: string; wa_id: string }>; } -/** - * WhatsApp Message Template - */ export interface WhatsAppTemplate { id: string; name: string; + status: "APPROVED" | "PENDING" | "REJECTED" | "PAUSED" | "DISABLED"; + category: "MARKETING" | "UTILITY" | "AUTHENTICATION"; language: string; - status: "approved" | "pending" | "rejected"; - category: "marketing" | "utility" | "authentication"; components: Array<{ - type: "header" | "body" | "footer" | "button"; - text?: string; + type: "HEADER" | "BODY" | "FOOTER" | "BUTTONS"; format?: string; - example?: { - header_text?: string[]; - body_text?: string[][]; - }; + text?: string; + buttons?: Array<{ type: string; text: string; url?: string }>; }>; } -/** - * WhatsApp Phone Number - */ export interface WhatsAppPhoneNumber { id: string; display_phone_number: string; verified_name: string; code_verification_status: string; - quality_rating: "green" | "yellow" | "red"; + quality_rating: "GREEN" | "YELLOW" | "RED"; platform_type: string; - throughput: { - level: string; - }; + throughput: { level: string }; } -/** - * WhatsApp Business Profile - */ export interface WhatsAppBusinessProfile { about?: string; address?: string; description?: string; email?: string; - messaging_product: string; profile_picture_url?: string; websites?: string[]; vertical?: string; } /** - * WhatsApp Message Status - */ -export interface WhatsAppMessageStatus { - id: string; - status: "sent" | "delivered" | "read" | "failed"; - timestamp: string; - recipient_id: string; - errors?: Array<{ - code: number; - title: string; - message: string; - error_data?: { - details: string; - }; - }>; -} - -/** - * WhatsApp Integration Client Interface - * Provides type-safe methods for all WhatsApp Business operations + * WhatsApp Business Integration Client Interface */ export interface WhatsAppIntegrationClient { /** - * Send a text message - * + * Send a plain text message + * * @example * ```typescript - * const result = await client.whatsapp.sendMessage({ - * to: "+1234567890", - * type: "text", - * text: { body: "Hello, World!" } + * await client.whatsapp.sendMessage({ + * phone_number_id: "123456789", + * to: "+15551234567", + * text: "Hello!", * }); * ``` */ sendMessage(params: { - /** Recipient phone number (E.164 format) */ + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number in E.164 format */ + to: string; + /** Message body text */ + text: string; + }): Promise; + + /** + * Reply to a specific message (quoted reply) + * + * @example + * ```typescript + * await client.whatsapp.replyMessage({ + * phone_number_id: "123456789", + * to: "+15551234567", + * message_id: "wamid.XXX", + * text: "Got it!", + * }); + * ``` + */ + replyMessage(params: { + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ to: string; - /** Message type */ - type: "text"; - /** Text message content */ - text: { - /** Message body text */ - body: string; - /** Preview URL (optional) */ - preview_url?: boolean; - }; + /** The wamid of the message to reply to */ + message_id: string; + /** Reply text */ + text: string; }): Promise; /** - * Send a template message - * + * Send a pre-approved template message + * * @example * ```typescript - * const result = await client.whatsapp.sendTemplate({ - * to: "+1234567890", - * template: { - * name: "welcome_message", - * language: { code: "en_US" }, - * components: [ - * { - * type: "body", - * parameters: [{ type: "text", text: "John" }] - * } - * ] - * } + * await client.whatsapp.sendTemplate({ + * phone_number_id: "123456789", + * to: "+15551234567", + * template_name: "order_confirmation", + * language_code: "en_US", + * components: JSON.stringify([{ + * type: "body", + * parameters: [{ type: "text", text: "Alice" }, { type: "text", text: "ORDER-123" }], + * }]), * }); * ``` */ sendTemplate(params: { - /** Recipient phone number (E.164 format) */ + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ to: string; - /** Template configuration */ - template: { - /** Template name */ - name: string; - /** Template language */ - language: { - /** Language code */ - code: string; - }; - /** Template components with parameters */ - components?: Array<{ - type: "header" | "body" | "button"; - parameters?: Array<{ - type: "text" | "currency" | "date_time" | "image" | "document" | "video"; - text?: string; - currency?: { fallback_value: string; code: string; amount_1000: number }; - date_time?: { fallback_value: string }; - image?: { link: string }; - document?: { link: string; filename?: string }; - video?: { link: string }; - }>; - sub_type?: string; - index?: number; - }>; - }; + /** Approved template name */ + template_name: string; + /** Language code e.g. en_US, es, pt_BR */ + language_code: string; + /** JSON array of component objects for variable substitution */ + components?: string; }): Promise; /** - * Send media (image/video/document/audio) - * + * Send an image, video, document, or audio file by URL + * * @example * ```typescript - * const result = await client.whatsapp.sendMedia({ - * to: "+1234567890", - * type: "image", - * image: { - * link: "https://example.com/image.jpg", - * caption: "Check this out!" - * } + * await client.whatsapp.sendMedia({ + * phone_number_id: "123456789", + * to: "+15551234567", + * media_type: "image", + * media_url: "https://example.com/photo.jpg", + * caption: "Check this out", * }); * ``` */ sendMedia(params: { - /** Recipient phone number (E.164 format) */ + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ to: string; - /** Media type */ - type: "image" | "video" | "document" | "audio"; - /** Image media (when type is "image") */ - image?: { - /** Media URL or ID */ - link?: string; - id?: string; - /** Image caption */ - caption?: string; - }; - /** Video media (when type is "video") */ - video?: { - /** Media URL or ID */ - link?: string; - id?: string; - /** Video caption */ - caption?: string; - }; - /** Document media (when type is "document") */ - document?: { - /** Media URL or ID */ - link?: string; - id?: string; - /** Document caption */ - caption?: string; - /** Document filename */ - filename?: string; - }; - /** Audio media (when type is "audio") */ - audio?: { - /** Media URL or ID */ - link?: string; - id?: string; - }; + /** image, video, document, or audio */ + media_type: "image" | "video" | "document" | "audio"; + /** Publicly accessible URL to the media file */ + media_url: string; + /** Caption text (supported for image, video, document) */ + caption?: string; }): Promise; /** - * List message templates - * + * React to a message with an emoji + * * @example * ```typescript - * const templates = await client.whatsapp.listTemplates({ - * limit: 50 + * await client.whatsapp.sendReaction({ + * phone_number_id: "123456789", + * to: "+15551234567", + * message_id: "wamid.XXX", + * emoji: "👍", * }); * ``` */ - listTemplates(params?: { - /** Maximum number of templates to return */ - limit?: number; - /** Pagination cursor */ - after?: string; - /** Pagination cursor */ - before?: string; + sendReaction(params: { + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ + to: string; + /** The wamid of the message to react to */ + message_id: string; + /** A single emoji character */ + emoji: string; }): Promise; /** - * Get registered phone numbers - * + * Send a location pin + * * @example * ```typescript - * const phoneNumbers = await client.whatsapp.getPhoneNumbers(); + * await client.whatsapp.sendLocation({ + * phone_number_id: "123456789", + * to: "+15551234567", + * latitude: "37.7749", + * longitude: "-122.4194", + * name: "Golden Gate Park", + * }); * ``` */ - getPhoneNumbers(params?: { - /** Maximum number of phone numbers to return */ - limit?: number; + sendLocation(params: { + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ + to: string; + /** Decimal latitude */ + latitude: string; + /** Decimal longitude */ + longitude: string; + /** Location name */ + name?: string; + /** Address string */ + address?: string; }): Promise; /** - * Get message delivery status - * + * Send one or more vCard-style contact cards + * * @example * ```typescript - * const status = await client.whatsapp.getMessageStatus({ - * message_id: "wamid.xxxxx" + * await client.whatsapp.sendContact({ + * phone_number_id: "123456789", + * to: "+15551234567", + * contacts: JSON.stringify([{ + * name: { formatted_name: "Alice Smith", first_name: "Alice", last_name: "Smith" }, + * phones: [{ phone: "+15551234567", type: "CELL" }], + * }]), * }); * ``` */ - getMessageStatus(params: { - /** WhatsApp message ID */ - message_id: string; + sendContact(params: { + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ + to: string; + /** JSON array of contact objects */ + contacts: string; }): Promise; /** - * Mark message as read - * + * Send a message with up to 3 quick-reply buttons + * + * @example + * ```typescript + * await client.whatsapp.sendInteractiveButtons({ + * phone_number_id: "123456789", + * to: "+15551234567", + * body_text: "Do you confirm your order?", + * buttons: JSON.stringify([{ id: "yes", title: "Yes" }, { id: "no", title: "No" }]), + * }); + * ``` + */ + sendInteractiveButtons(params: { + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ + to: string; + /** Main message body */ + body_text: string; + /** JSON array of 1–3 button objects [{"id":"btn1","title":"Yes"}] */ + buttons: string; + /** Optional header above the body */ + header_text?: string; + /** Optional footer below the buttons */ + footer_text?: string; + }): Promise; + + /** + * Send a message with a scrollable list of options + * + * @example + * ```typescript + * await client.whatsapp.sendInteractiveList({ + * phone_number_id: "123456789", + * to: "+15551234567", + * body_text: "Choose your shipping method", + * button_text: "See options", + * sections: JSON.stringify([{ + * title: "Shipping", + * rows: [ + * { id: "standard", title: "Standard (5-7 days)", description: "Free" }, + * { id: "express", title: "Express (1-2 days)", description: "$9.99" }, + * ], + * }]), + * }); + * ``` + */ + sendInteractiveList(params: { + /** Sending phone number ID */ + phone_number_id: string; + /** Recipient phone number */ + to: string; + /** Main message body */ + body_text: string; + /** Label on the button that opens the list (max 20 chars) */ + button_text: string; + /** JSON array of section objects */ + sections: string; + /** Optional header */ + header_text?: string; + /** Optional footer */ + footer_text?: string; + }): Promise; + + /** + * Mark a received message as read (shows blue ticks to sender) + * * @example * ```typescript * await client.whatsapp.markRead({ - * message_id: "wamid.xxxxx" + * phone_number_id: "123456789", + * message_id: "wamid.XXX", * }); * ``` */ markRead(params: { - /** WhatsApp message ID to mark as read */ + /** Your phone number ID */ + phone_number_id: string; + /** The wamid of the received message */ + message_id: string; + }): Promise; + + /** + * Get the download URL and metadata for a received media object + * + * @example + * ```typescript + * const media = await client.whatsapp.getMediaUrl({ + * media_id: "media123", + * phone_number_id: "123456789", + * }); + * // media.url expires in ~5 minutes — download promptly + * ``` + */ + getMediaUrl(params: { + /** Media object ID (from webhook payload) */ + media_id: string; + /** Your phone number ID */ + phone_number_id: string; + }): Promise; + + /** + * Delete an uploaded media object from Meta's servers + * + * @example + * ```typescript + * await client.whatsapp.deleteMedia({ media_id: "media123", phone_number_id: "123456789" }); + * ``` + */ + deleteMedia(params: { + /** Media object ID */ + media_id: string; + /** Your phone number ID */ + phone_number_id: string; + }): Promise; + + /** + * List all message templates for a WhatsApp Business Account + * + * @example + * ```typescript + * const templates = await client.whatsapp.listTemplates({ business_account_id: "WABA123" }); + * ``` + */ + listTemplates(params: { + /** WABA ID */ + business_account_id: string; + }): Promise; + + /** + * Get a specific template by name + * + * @example + * ```typescript + * const tpl = await client.whatsapp.getTemplate({ business_account_id: "WABA123", name: "order_confirmation" }); + * ``` + */ + getTemplate(params: { + /** WABA ID */ + business_account_id: string; + /** Template name */ + name: string; + }): Promise; + + /** + * Create a new message template (submitted for Meta review) + * + * @example + * ```typescript + * await client.whatsapp.createTemplate({ + * business_account_id: "WABA123", + * name: "order_ready", + * language: "en_US", + * category: "UTILITY", + * components: JSON.stringify([ + * { type: "BODY", text: "Hi {{1}}, your order {{2}} is ready for pickup." }, + * ]), + * }); + * ``` + */ + createTemplate(params: { + /** WABA ID */ + business_account_id: string; + /** Template name — lowercase letters, numbers, underscores only */ + name: string; + /** Language code e.g. en_US */ + language: string; + /** MARKETING, UTILITY, or AUTHENTICATION */ + category: "MARKETING" | "UTILITY" | "AUTHENTICATION"; + /** JSON array of component objects */ + components: string; + }): Promise; + + /** + * Delete a template by name (all language variants) + * + * @example + * ```typescript + * await client.whatsapp.deleteTemplate({ business_account_id: "WABA123", name: "old_promo" }); + * ``` + */ + deleteTemplate(params: { + /** WABA ID */ + business_account_id: string; + /** Template name to delete */ + name: string; + }): Promise; + + /** + * List all phone numbers registered to a WABA + * + * @example + * ```typescript + * const numbers = await client.whatsapp.getPhoneNumbers({ business_account_id: "WABA123" }); + * ``` + */ + getPhoneNumbers(params: { + /** WABA ID */ + business_account_id: string; + }): Promise; + + /** + * Get full details for a specific phone number + * + * @example + * ```typescript + * const number = await client.whatsapp.getPhoneNumber({ phone_number_id: "123456789" }); + * ``` + */ + getPhoneNumber(params: { + /** Phone number ID */ + phone_number_id: string; + }): Promise; + + /** + * Get the public business profile for a phone number + * + * @example + * ```typescript + * const profile = await client.whatsapp.getProfile({ phone_number_id: "123456789" }); + * ``` + */ + getProfile(params: { + /** Phone number ID */ + phone_number_id: string; + }): Promise; + + /** + * Update the business profile (pass only the fields you want to change) + * + * @example + * ```typescript + * await client.whatsapp.updateProfile({ + * phone_number_id: "123456789", + * about: "Best prices guaranteed", + * email: "support@example.com", + * }); + * ``` + */ + updateProfile(params: { + /** Phone number ID */ + phone_number_id: string; + /** Short about text (max 139 chars) */ + about?: string; + /** Business address */ + address?: string; + /** Business description */ + description?: string; + /** Business email */ + email?: string; + /** Industry vertical */ + vertical?: string; + /** JSON array of URLs (max 2) */ + websites?: string; + }): Promise; + + /** + * Get status of a sent message by message ID + * + * @example + * ```typescript + * const status = await client.whatsapp.getMessageStatus({ message_id: "wamid.XXX" }); + * ``` + */ + getMessageStatus(params: { + /** The wamid of the message */ message_id: string; }): Promise; /** - * Get business profile - * + * Create a QR code that opens a chat with a prefilled message + * + * @example + * ```typescript + * const qr = await client.whatsapp.createQrCode({ + * phone_number_id: "123456789", + * prefilled_message: "Hi, I have a question", + * }); + * console.log(qr.deep_link_url, qr.qr_image_url); + * ``` + */ + createQrCode(params: { + /** Phone number ID */ + phone_number_id: string; + /** Message text to prefill when QR is scanned */ + prefilled_message: string; + }): Promise; + + /** + * List all QR codes for a phone number + * * @example * ```typescript - * const profile = await client.whatsapp.getProfile(); + * const codes = await client.whatsapp.listQrCodes({ phone_number_id: "123456789" }); * ``` */ - getProfile(params?: { - /** Fields to retrieve */ - fields?: string[]; + listQrCodes(params: { + /** Phone number ID */ + phone_number_id: string; }): Promise; } diff --git a/src/integrations/whatsapp.ts b/src/integrations/whatsapp.ts index a01a2d3..3e3477a 100644 --- a/src/integrations/whatsapp.ts +++ b/src/integrations/whatsapp.ts @@ -36,14 +36,35 @@ export interface WhatsAppIntegrationConfig { * These should match the tool names exposed by your MCP server */ const WHATSAPP_TOOLS = [ + // Messaging "whatsapp_send_message", + "whatsapp_reply_message", "whatsapp_send_template", "whatsapp_send_media", + "whatsapp_send_reaction", + "whatsapp_send_location", + "whatsapp_send_contact", + "whatsapp_send_interactive_buttons", + "whatsapp_send_interactive_list", + "whatsapp_mark_read", + // Media + "whatsapp_get_media_url", + "whatsapp_delete_media", + // Templates "whatsapp_list_templates", + "whatsapp_get_template", + "whatsapp_create_template", + "whatsapp_delete_template", + // Phone numbers "whatsapp_get_phone_numbers", - "whatsapp_get_message_status", - "whatsapp_mark_read", + "whatsapp_get_phone_number", + // Business profile "whatsapp_get_profile", + "whatsapp_update_profile", + // Misc + "whatsapp_get_message_status", + "whatsapp_create_qr_code", + "whatsapp_list_qr_codes", ] as const; diff --git a/src/integrations/word-client.ts b/src/integrations/word-client.ts new file mode 100644 index 0000000..ba172ef --- /dev/null +++ b/src/integrations/word-client.ts @@ -0,0 +1,105 @@ +/** + * Word Integration Client Types + * Fully typed interface for Word document methods + */ + +import type { MCPToolCallResponse } from "../protocol/messages.js"; + +/** + * Word Integration Client Interface + * Provides type-safe methods for managing Word documents in OneDrive + */ +export interface WordIntegrationClient { + /** + * Search for Word documents + * + * @example + * ```typescript + * const docs = await client.word.list({ query: "report" }); + * ``` + */ + list(params?: { + /** Filter by name (default: searches .docx) */ + query?: string; + /** Max results (default 25) */ + top?: number; + }): Promise; + + /** + * Get metadata for a Word document + * + * @example + * ```typescript + * const doc = await client.word.get({ item_id: "ABC123" }); + * ``` + */ + get(params: { + /** Document item ID */ + item_id: string; + }): Promise; + + /** + * Create a new empty .docx file in OneDrive + * + * @example + * ```typescript + * const doc = await client.word.create({ name: "Meeting Notes" }); + * ``` + */ + create(params: { + /** File name (.docx appended automatically if missing) */ + name: string; + /** Parent folder item ID (defaults to root) */ + parent_id?: string; + }): Promise; + + /** + * Copy a Word document + * + * For large files the API returns `{ status: "pending", monitor_url }` — poll + * `monitor_url` until it returns a DriveItem to confirm completion. + * + * @example + * ```typescript + * const copy = await client.word.copy({ item_id: "ABC123", name: "Notes Copy" }); + * ``` + */ + copy(params: { + /** Document item ID to copy */ + item_id: string; + /** Name for the copy */ + name?: string; + /** Destination folder item ID */ + parent_id?: string; + }): Promise; + + /** + * Delete a Word document permanently + * + * @example + * ```typescript + * await client.word.delete({ item_id: "ABC123" }); + * ``` + */ + delete(params: { + /** Document item ID */ + item_id: string; + }): Promise; + + /** + * Create a sharing link for a Word document + * + * @example + * ```typescript + * const link = await client.word.share({ item_id: "ABC123", type: "edit" }); + * ``` + */ + share(params: { + /** Document item ID */ + item_id: string; + /** view, edit, or embed (default: view) */ + type?: "view" | "edit" | "embed"; + /** anonymous or organization (default: anonymous) */ + scope?: "anonymous" | "organization"; + }): Promise; +} diff --git a/src/integrations/word.ts b/src/integrations/word.ts new file mode 100644 index 0000000..0e0b4e4 --- /dev/null +++ b/src/integrations/word.ts @@ -0,0 +1,63 @@ +/** + * Word Integration + * Enables Word document tools with OAuth configuration + */ + +import type { MCPIntegration, OAuthConfig } from "./types.js"; +import { getEnv } from "../utils/env.js"; +import { createLogger } from "../utils/logger.js"; + +const logger = createLogger('Word'); + +export interface WordIntegrationConfig { + /** Microsoft OAuth client ID (defaults to WORD_CLIENT_ID env var) */ + clientId?: string; + /** Microsoft OAuth client secret (defaults to WORD_CLIENT_SECRET env var) */ + clientSecret?: string; + /** Additional OAuth scopes */ + scopes?: string[]; + /** Optional OAuth scopes */ + optionalScopes?: string[]; + /** OAuth redirect URI */ + redirectUri?: string; +} + +const WORD_TOOLS = [ + "word_list", + "word_get", + "word_create", + "word_copy", + "word_delete", + "word_share", +] as const; + +export function wordIntegration(config: WordIntegrationConfig = {}): MCPIntegration<"word"> { + const oauth: OAuthConfig = { + provider: "word", + clientId: config.clientId ?? getEnv('WORD_CLIENT_ID'), + clientSecret: config.clientSecret ?? getEnv('WORD_CLIENT_SECRET'), + scopes: config.scopes, + optionalScopes: config.optionalScopes, + redirectUri: config.redirectUri, + config, + }; + + return { + id: "word", + name: "Word", + logoUrl: "https://wdvtnli2jn3texa6.public.blob.vercel-storage.com/word.png", + tools: [...WORD_TOOLS], + oauth, + + async onInit(_client) { + logger.debug("Word integration initialized"); + }, + + async onAfterConnect(_client) { + logger.debug("Word integration connected"); + }, + }; +} + +export type WordTools = typeof WORD_TOOLS[number]; +export type { WordIntegrationClient } from "./word-client.js"; diff --git a/src/integrations/youtube-client.ts b/src/integrations/youtube-client.ts index 36f559c..9b694a4 100644 --- a/src/integrations/youtube-client.ts +++ b/src/integrations/youtube-client.ts @@ -1,13 +1,10 @@ /** * YouTube Integration Client Types - * Fully typed interface for YouTube integration methods + * Fully typed interface for YouTube Data API v3 methods */ import type { MCPToolCallResponse } from "../protocol/messages.js"; -/** - * YouTube Video - */ export interface YouTubeVideo { kind: "youtube#video"; etag: string; @@ -17,40 +14,24 @@ export interface YouTubeVideo { channelId: string; title: string; description: string; - thumbnails: { - default?: { url: string; width: number; height: number }; - medium?: { url: string; width: number; height: number }; - high?: { url: string; width: number; height: number }; - standard?: { url: string; width: number; height: number }; - maxres?: { url: string; width: number; height: number }; - }; + thumbnails: Record; channelTitle: string; tags?: string[]; categoryId: string; - liveBroadcastContent: string; - localized?: { - title: string; - description: string; - }; }; contentDetails?: { duration: string; - dimension: string; definition: string; caption: string; - licensedContent: boolean; - projection: string; }; statistics?: { viewCount: string; likeCount: string; commentCount: string; + favoriteCount: string; }; } -/** - * YouTube Channel - */ export interface YouTubeChannel { kind: "youtube#channel"; etag: string; @@ -60,34 +41,18 @@ export interface YouTubeChannel { description: string; customUrl?: string; publishedAt: string; - thumbnails: { - default?: { url: string; width: number; height: number }; - medium?: { url: string; width: number; height: number }; - high?: { url: string; width: number; height: number }; - }; - localized?: { - title: string; - description: string; - }; - country?: string; + thumbnails: Record; }; contentDetails?: { - relatedPlaylists: { - likes?: string; - uploads?: string; - }; + relatedPlaylists: { likes?: string; uploads?: string }; }; statistics?: { viewCount: string; subscriberCount: string; - hiddenSubscriberCount: boolean; videoCount: string; }; } -/** - * YouTube Playlist - */ export interface YouTubePlaylist { kind: "youtube#playlist"; etag: string; @@ -97,294 +62,419 @@ export interface YouTubePlaylist { channelId: string; title: string; description: string; - thumbnails: { - default?: { url: string; width: number; height: number }; - medium?: { url: string; width: number; height: number }; - high?: { url: string; width: number; height: number }; - standard?: { url: string; width: number; height: number }; - maxres?: { url: string; width: number; height: number }; - }; + thumbnails: Record; channelTitle: string; - localized?: { - title: string; - description: string; - }; - }; - contentDetails?: { - itemCount: number; }; + contentDetails?: { itemCount: number }; } -/** - * YouTube Playlist Item - */ export interface YouTubePlaylistItem { kind: "youtube#playlistItem"; etag: string; id: string; snippet?: { - publishedAt: string; - channelId: string; title: string; - description: string; - thumbnails: { - default?: { url: string; width: number; height: number }; - medium?: { url: string; width: number; height: number }; - high?: { url: string; width: number; height: number }; - standard?: { url: string; width: number; height: number }; - maxres?: { url: string; width: number; height: number }; - }; - channelTitle: string; - playlistId: string; position: number; - resourceId: { - kind: string; - videoId: string; - }; - }; - contentDetails?: { - videoId: string; - videoPublishedAt: string; - }; -} - -/** - * YouTube Comment - */ -export interface YouTubeComment { - kind: "youtube#comment"; - etag: string; - id: string; - snippet: { - authorDisplayName: string; - authorProfileImageUrl: string; - authorChannelUrl: string; - authorChannelId: { - value: string; - }; - videoId: string; - textDisplay: string; - textOriginal: string; - canRate: boolean; - viewerRating: string; - likeCount: number; - publishedAt: string; - updatedAt: string; + playlistId: string; + resourceId: { kind: string; videoId: string }; }; } -/** - * YouTube Subscription - */ export interface YouTubeSubscription { kind: "youtube#subscription"; etag: string; id: string; snippet: { - publishedAt: string; title: string; - description: string; - resourceId: { - kind: string; - channelId: string; - }; - channelId: string; - thumbnails: { - default?: { url: string; width: number; height: number }; - medium?: { url: string; width: number; height: number }; - high?: { url: string; width: number; height: number }; - }; + publishedAt: string; + resourceId: { kind: string; channelId: string }; }; + contentDetails?: { totalItemCount: number }; } /** * YouTube Integration Client Interface - * Provides type-safe methods for all YouTube operations */ export interface YouTubeIntegrationClient { + // ── Read ────────────────────────────────────────────────────────────────── + /** * Search for videos, channels, or playlists - * + * * @example * ```typescript - * const results = await client.youtube.search({ - * q: "typescript tutorial", - * type: "video", - * maxResults: 10 - * }); + * const results = await client.youtube.search({ query: "typescript tutorial", max_results: 10 }); * ``` */ search(params: { - /** Search query */ - q: string; - /** Resource type */ + /** Search query text */ + query: string; + /** video, channel, or playlist (default: video) */ type?: "video" | "channel" | "playlist"; - /** Maximum results (1-50) */ - maxResults?: number; - /** Order results */ + /** 1–50 (default: 5) */ + max_results?: number; + /** date, rating, relevance, title, videoCount, viewCount */ order?: "date" | "rating" | "relevance" | "title" | "videoCount" | "viewCount"; - /** Channel ID filter */ - channelId?: string; - /** Page token for pagination */ - pageToken?: string; - /** Published after (RFC 3339) */ - publishedAfter?: string; - /** Published before (RFC 3339) */ - publishedBefore?: string; - /** Region code */ - regionCode?: string; - /** Safe search */ - safeSearch?: "moderate" | "none" | "strict"; }): Promise; /** - * Get video details - * + * Get full details for a specific video + * * @example * ```typescript - * const video = await client.youtube.getVideo({ - * video_id: "dQw4w9WgXcQ" - * }); + * const video = await client.youtube.getVideo({ video_id: "dQw4w9WgXcQ" }); * ``` */ getVideo(params: { - /** Video ID */ + /** YouTube video ID */ video_id: string; - /** Parts to include */ - part?: string[]; }): Promise; /** - * List playlists for a channel - * + * Get the authenticated user's own channel + * * @example * ```typescript - * const playlists = await client.youtube.listPlaylists({ - * channel_id: "UCxxxxxx", - * maxResults: 25 - * }); + * const channel = await client.youtube.getMyChannel(); * ``` */ - listPlaylists(params: { - /** Channel ID */ + getMyChannel(params?: Record): Promise; + + /** + * Get details for any channel by ID + * + * @example + * ```typescript + * const channel = await client.youtube.getChannel({ channel_id: "UCxxxxxx" }); + * ``` + */ + getChannel(params: { + /** Channel ID (e.g. UCxxxxxx) */ channel_id: string; - /** Maximum results (1-50) */ - maxResults?: number; - /** Page token for pagination */ - pageToken?: string; }): Promise; /** - * Get playlist details - * + * List videos uploaded by the authenticated user + * + * Makes 2 API calls internally (fetches uploads playlist, then paginates it). + * * @example * ```typescript - * const playlist = await client.youtube.getPlaylist({ - * playlist_id: "PLxxxxxx" - * }); + * const videos = await client.youtube.listMyVideos({ max_results: 20 }); + * ``` + */ + listMyVideos(params?: { + /** 1–50 (default: 10) */ + max_results?: number; + /** Pagination token from previous response */ + page_token?: string; + }): Promise; + + /** + * Get the authenticated user's rating for one or more videos + * + * @example + * ```typescript + * const rating = await client.youtube.getVideoRating({ video_id: "dQw4w9WgXcQ" }); + * ``` + */ + getVideoRating(params: { + /** Video ID, or comma-separated list (max 50) */ + video_id: string; + }): Promise; + + /** + * List playlists for the authenticated user or a specific channel + * + * @example + * ```typescript + * const playlists = await client.youtube.listPlaylists({ max_results: 25 }); + * ``` + */ + listPlaylists(params?: { + /** Channel ID, or omit for authenticated user's playlists */ + channel_id?: string; + /** Default: 5 */ + max_results?: number; + }): Promise; + + /** + * Get details for a specific playlist + * + * @example + * ```typescript + * const playlist = await client.youtube.getPlaylist({ playlist_id: "PLxxxxxx" }); * ``` */ getPlaylist(params: { - /** Playlist ID */ + /** Playlist ID (e.g. PLxxxxxx) */ playlist_id: string; - /** Parts to include */ - part?: string[]; }): Promise; /** * List videos in a playlist - * + * + * Each item's `id` is the playlist item ID needed for `removeFromPlaylist`. + * * @example * ```typescript - * const items = await client.youtube.listPlaylistItems({ - * playlist_id: "PLxxxxxx", - * maxResults: 50 - * }); + * const items = await client.youtube.listPlaylistItems({ playlist_id: "PLxxxxxx" }); * ``` */ listPlaylistItems(params: { /** Playlist ID */ playlist_id: string; - /** Maximum results (1-50) */ - maxResults?: number; - /** Page token for pagination */ - pageToken?: string; + /** Default: 10 */ + max_results?: number; }): Promise; /** - * Get channel details - * + * List channels the authenticated user is subscribed to + * + * Each item's `id` is the subscription ID needed for `unsubscribe`. + * * @example * ```typescript - * const channel = await client.youtube.getChannel({ - * channel_id: "UCxxxxxx" - * }); + * const subs = await client.youtube.listSubscriptions({ max_results: 20 }); * ``` */ - getChannel(params: { - /** Channel ID */ - channel_id: string; - /** Parts to include */ - part?: string[]; + listSubscriptions(params?: { + /** Default: 5 */ + max_results?: number; }): Promise; /** - * List channel subscriptions - * + * List top-level comment threads on a video + * + * Each item's `id` is the comment thread ID needed for `replyToComment`. + * * @example * ```typescript - * const subscriptions = await client.youtube.listSubscriptions({ - * maxResults: 50 - * }); + * const threads = await client.youtube.listComments({ video_id: "dQw4w9WgXcQ" }); * ``` */ - listSubscriptions(params?: { - /** Channel ID */ - channel_id?: string; - /** Maximum results (1-50) */ - maxResults?: number; - /** Page token for pagination */ - pageToken?: string; - /** Order results */ - order?: "alphabetical" | "relevance" | "unread"; + listComments(params: { + /** Video ID */ + video_id: string; + /** Default: 10 */ + max_results?: number; }): Promise; /** - * List comments on a video - * + * List replies to a specific comment thread + * * @example * ```typescript - * const comments = await client.youtube.listComments({ - * video_id: "dQw4w9WgXcQ", - * maxResults: 100 - * }); + * const replies = await client.youtube.listCommentReplies({ comment_thread_id: "Ugw..." }); * ``` */ - listComments(params: { + listCommentReplies(params: { + /** Comment thread ID (the id from listComments) */ + comment_thread_id: string; + /** Default: 20 */ + max_results?: number; + }): Promise; + + /** + * List available caption tracks for a video + * + * @example + * ```typescript + * const captions = await client.youtube.getCaptions({ video_id: "dQw4w9WgXcQ" }); + * ``` + */ + getCaptions(params: { /** Video ID */ video_id: string; - /** Maximum results (1-100) */ - maxResults?: number; - /** Page token for pagination */ - pageToken?: string; - /** Text format */ - textFormat?: "html" | "plainText"; - /** Order results */ - order?: "time" | "relevance"; }): Promise; + // ── Write — engagement ──────────────────────────────────────────────────── + /** - * Get video captions/subtitles - * + * Like, dislike, or remove a rating from a video + * * @example * ```typescript - * const captions = await client.youtube.getCaptions({ - * video_id: "dQw4w9WgXcQ" - * }); + * await client.youtube.rateVideo({ video_id: "dQw4w9WgXcQ", rating: "like" }); * ``` */ - getCaptions(params: { + rateVideo(params: { /** Video ID */ video_id: string; + /** like, dislike, or none */ + rating: "like" | "dislike" | "none"; + }): Promise; + + /** + * Subscribe to a channel + * + * @example + * ```typescript + * await client.youtube.subscribe({ channel_id: "UCxxxxxx" }); + * ``` + */ + subscribe(params: { + /** Channel ID to subscribe to */ + channel_id: string; + }): Promise; + + /** + * Unsubscribe from a channel + * + * Requires the subscription ID from `listSubscriptions`, not the channel ID. + * + * @example + * ```typescript + * await client.youtube.unsubscribe({ subscription_id: "sub_abc123" }); + * ``` + */ + unsubscribe(params: { + /** Subscription ID (the id field from listSubscriptions) */ + subscription_id: string; + }): Promise; + + /** + * Post a new top-level comment on a video + * + * @example + * ```typescript + * await client.youtube.addComment({ video_id: "dQw4w9WgXcQ", text: "Great video!" }); + * ``` + */ + addComment(params: { + /** Video ID to comment on */ + video_id: string; + /** Comment text */ + text: string; + }): Promise; + + /** + * Reply to an existing comment thread + * + * @example + * ```typescript + * await client.youtube.replyToComment({ comment_thread_id: "Ugw...", text: "Thanks!" }); + * ``` + */ + replyToComment(params: { + /** Comment thread ID (the id from listComments) */ + comment_thread_id: string; + /** Reply text */ + text: string; + }): Promise; + + // ── Write — playlists ──────────────────────────────────────────────────── + + /** + * Create a new playlist + * + * @example + * ```typescript + * const pl = await client.youtube.createPlaylist({ title: "My Favorites", privacy_status: "private" }); + * ``` + */ + createPlaylist(params: { + /** Playlist title */ + title: string; + /** Playlist description */ + description?: string; + /** public, private, or unlisted (default: public) */ + privacy_status?: "public" | "private" | "unlisted"; + }): Promise; + + /** + * Update a playlist's metadata + * + * Fetches current state first — only specified fields are changed. + * + * @example + * ```typescript + * await client.youtube.updatePlaylist({ playlist_id: "PLxxxxxx", title: "New Title" }); + * ``` + */ + updatePlaylist(params: { + /** Playlist ID */ + playlist_id: string; + /** New title */ + title?: string; + /** New description */ + description?: string; + /** public, private, or unlisted */ + privacy_status?: "public" | "private" | "unlisted"; + }): Promise; + + /** + * Permanently delete a playlist + * + * @example + * ```typescript + * await client.youtube.deletePlaylist({ playlist_id: "PLxxxxxx" }); + * ``` + */ + deletePlaylist(params: { + /** Playlist ID */ + playlist_id: string; + }): Promise; + + /** + * Add a video to a playlist + * + * @example + * ```typescript + * await client.youtube.addToPlaylist({ playlist_id: "PLxxxxxx", video_id: "dQw4w9WgXcQ" }); + * ``` + */ + addToPlaylist(params: { + /** Playlist ID */ + playlist_id: string; + /** Video ID to add */ + video_id: string; + /** 0-indexed position in the playlist */ + position?: number; + }): Promise; + + /** + * Remove an item from a playlist + * + * Requires the playlist item ID (the `id` from `listPlaylistItems`), not the video ID. + * + * @example + * ```typescript + * await client.youtube.removeFromPlaylist({ playlist_item_id: "PLitem_abc" }); + * ``` + */ + removeFromPlaylist(params: { + /** Playlist item ID (the id field from listPlaylistItems) */ + playlist_item_id: string; + }): Promise; + + // ── Write — video management ────────────────────────────────────────────── + + /** + * Update a video's metadata + * + * Fetches current snippet first — only specified fields are changed. + * Video must be owned by the authenticated user. + * + * @example + * ```typescript + * await client.youtube.updateVideo({ + * video_id: "dQw4w9WgXcQ", + * title: "Updated Title", + * tags: "music,classic", + * }); + * ``` + */ + updateVideo(params: { + /** Video ID (must be owned by authenticated user) */ + video_id: string; + /** New title (max 100 chars) */ + title?: string; + /** New description (max 5000 chars) */ + description?: string; + /** Comma-separated tag list */ + tags?: string; + /** YouTube category ID (e.g. 22 = People & Blogs, 28 = Science & Technology) */ + category_id?: string; }): Promise; } diff --git a/src/integrations/youtube.ts b/src/integrations/youtube.ts index 0849695..676e12c 100644 --- a/src/integrations/youtube.ts +++ b/src/integrations/youtube.ts @@ -21,7 +21,7 @@ export interface YouTubeIntegrationConfig { clientId?: string; /** YouTube OAuth client secret (defaults to YOUTUBE_CLIENT_SECRET env var) */ clientSecret?: string; - /** Additional OAuth scopes (default: ['https://www.googleapis.com/auth/youtube.readonly']) */ + /** Additional OAuth scopes (default: ['https://www.googleapis.com/auth/youtube.force-ssl']) */ scopes?: string[]; /** Optional OAuth scopes (user may choose to grant or deny) */ optionalScopes?: string[]; @@ -34,15 +34,34 @@ export interface YouTubeIntegrationConfig { * These should match the tool names exposed by your MCP server */ const YOUTUBE_TOOLS = [ + // Read "youtube_search", "youtube_get_video", + "youtube_get_my_channel", + "youtube_get_channel", + "youtube_list_my_videos", + "youtube_get_video_rating", "youtube_list_playlists", "youtube_get_playlist", "youtube_list_playlist_items", - "youtube_get_channel", "youtube_list_subscriptions", "youtube_list_comments", + "youtube_list_comment_replies", "youtube_get_captions", + // Write — engagement + "youtube_rate_video", + "youtube_subscribe", + "youtube_unsubscribe", + "youtube_add_comment", + "youtube_reply_to_comment", + // Write — playlists + "youtube_create_playlist", + "youtube_update_playlist", + "youtube_delete_playlist", + "youtube_add_to_playlist", + "youtube_remove_from_playlist", + // Write — video management + "youtube_update_video", ] as const; diff --git a/src/server.ts b/src/server.ts index 22e6777..00f7417 100644 --- a/src/server.ts +++ b/src/server.ts @@ -81,6 +81,44 @@ function resolveProviderFromToolName(toolName: string, candidates: string[]): st return best; } +const TOOL_ALIASES: Record = { + // Known hallucinated → real tool name mappings (extend as discovered) + github_list_repo_contents: 'github_get_file_contents', + gdrive_list: 'gdrive_list_files', + gdrive_get: 'gdrive_get_file', + gdrive_delete: 'gdrive_delete_file', + gdrive_trash: 'gdrive_trash_file', + gdrive_upload: 'gdrive_upload_text_file', + gdrive_download: 'gdrive_download_file', +}; + +/** + * Normalize common client-side tool name mistakes before forwarding to mcp.integrate.dev: + * 1. Alias table for known hallucinated names + * 2. Strip integration prefix from MCP meta-tools: github___list_tools → ___list_tools + * 3. Strip duplicate integration prefix: github_github_X → github_X + */ +function normalizeToolName(toolName: string, candidates: string[]): string { + if (TOOL_ALIASES[toolName]) return TOOL_ALIASES[toolName]; + + const tripleIdx = toolName.indexOf('___'); + if (tripleIdx > 0) { + const prefix = toolName.slice(0, tripleIdx); + if (candidates.some(c => c === prefix)) { + return toolName.slice(tripleIdx); + } + } + + for (const candidate of candidates) { + const doublePrefix = `${candidate}_${candidate}_`; + if (toolName.startsWith(doublePrefix)) { + return `${candidate}_${toolName.slice(doublePrefix.length)}`; + } + } + + return toolName; +} + const unauthenticatedCodeModeWarnings = new Set(); function warnUnauthenticatedCodeModeCallback(details: { @@ -465,14 +503,19 @@ export function createMCPServer // The API key from createMCPServer config is automatically included in requests if (action === 'mcp' && method === 'POST') { try { - const body = await webRequest.json(); + let body = await webRequest.json(); let authHeader = webRequest.headers.get('authorization'); const integrationsHeader = webRequest.headers.get('x-integrations'); const codeModeHeader = webRequest.headers.get('x-integrate-code-mode'); const contextHeader = webRequest.headers.get('x-integrate-context'); const callbackApiKey = webRequest.headers.get('x-integrate-api-key'); const tokensHeader = webRequest.headers.get('x-integrate-tokens'); - const toolName = typeof body?.name === 'string' ? body.name : ''; + const integrationCandidates: string[] = integrationsHeader + ? integrationsHeader.split(',').map((s: string) => s.trim()).filter(Boolean) + : (config.integrations ?? []).map((i: any) => i.id).filter(Boolean); + const rawToolName = typeof body?.name === 'string' ? body.name : ''; + const toolName = rawToolName ? normalizeToolName(rawToolName, integrationCandidates) : rawToolName; + if (toolName !== rawToolName) body = { ...body, name: toolName }; let tokensResolvedProvider: string | null = null; // Code Mode fallback: when the sandbox callback passes a multi-provider @@ -1359,7 +1402,11 @@ export { whatsappIntegration } from './integrations/whatsapp.js'; export { calcomIntegration } from './integrations/calcom.js'; export { rampIntegration } from './integrations/ramp.js'; export { onedriveIntegration } from './integrations/onedrive.js'; +export { wordIntegration } from './integrations/word.js'; +export { excelIntegration } from './integrations/excel.js'; +export { powerpointIntegration } from './integrations/powerpoint.js'; export { gdocsIntegration } from './integrations/gdocs.js'; +export { gdriveIntegration } from './integrations/gdrive.js'; export { gsheetsIntegration } from './integrations/gsheets.js'; export { gslidesIntegration } from './integrations/gslides.js'; export { polarIntegration } from './integrations/polar.js'; diff --git a/tests/integrations/integration-system.test.ts b/tests/integrations/integration-system.test.ts index b0c5188..a88b77e 100644 --- a/tests/integrations/integration-system.test.ts +++ b/tests/integrations/integration-system.test.ts @@ -1235,9 +1235,9 @@ describe("Integration System", () => { expect(integration.tools).toContain("onedrive_list_files"); expect(integration.tools).toContain("onedrive_get_file"); expect(integration.tools).toContain("onedrive_upload_file"); - expect(integration.tools).toContain("onedrive_excel_get_worksheets"); - expect(integration.tools).toContain("onedrive_word_get_content"); - expect(integration.tools).toContain("onedrive_powerpoint_get_slides"); + expect(integration.tools).toContain("onedrive_delete_file"); + expect(integration.tools).toContain("onedrive_search_files"); + expect(integration.tools).toContain("onedrive_share_file"); }); test("has lifecycle hooks defined", () => {