mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 15:07:55 -05:00
Improvement(sharepoint): added more operations in sharepoint (#1369)
* added list tools * not working yet * improved read and create * added scopes * updated sharepoint tools * added greptile comments --------- Co-authored-by: Adam Gough <adamgough@Mac.attlocal.net>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Sharepoint
|
||||
description: Read and create pages
|
||||
description: Work with pages and lists
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
@@ -61,7 +61,7 @@ In Sim, the SharePoint integration empowers your agents to create and access Sha
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
Integrate Sharepoint into the workflow. Can read and create pages, and list sites. Requires OAuth.
|
||||
Integrate SharePoint into the workflow. Read/create pages, list sites, and work with lists (read, create, update items). Requires OAuth.
|
||||
|
||||
|
||||
|
||||
@@ -124,6 +124,65 @@ List details of all SharePoint sites
|
||||
| --------- | ---- | ----------- |
|
||||
| `site` | object | Information about the current SharePoint site |
|
||||
|
||||
### `sharepoint_create_list`
|
||||
|
||||
Create a new list in a SharePoint site
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteId` | string | No | The ID of the SharePoint site \(internal use\) |
|
||||
| `siteSelector` | string | No | Select the SharePoint site |
|
||||
| `listDisplayName` | string | Yes | Display name of the list to create |
|
||||
| `listDescription` | string | No | Description of the list |
|
||||
| `listTemplate` | string | No | List template name \(e.g., 'genericList'\) |
|
||||
| `pageContent` | string | No | Optional JSON of columns. Either a top-level array of column definitions or an object with \{ columns: \[...\] \}. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `list` | object | Created SharePoint list information |
|
||||
|
||||
### `sharepoint_get_list`
|
||||
|
||||
Get metadata (and optionally columns/items) for a SharePoint list
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteSelector` | string | No | Select the SharePoint site |
|
||||
| `siteId` | string | No | The ID of the SharePoint site \(internal use\) |
|
||||
| `listId` | string | No | The ID of the list to retrieve |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `list` | object | Information about the SharePoint list |
|
||||
|
||||
### `sharepoint_update_list`
|
||||
|
||||
Update the properties (fields) on a SharePoint list item
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `siteSelector` | string | No | Select the SharePoint site |
|
||||
| `siteId` | string | No | The ID of the SharePoint site \(internal use\) |
|
||||
| `listId` | string | No | The ID of the list containing the item |
|
||||
| `itemId` | string | Yes | The ID of the list item to update |
|
||||
| `listItemFields` | object | Yes | Field values to update on the list item |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `item` | object | Updated SharePoint list item |
|
||||
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { MicrosoftSharepointIcon } from '@/components/icons'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import type { SharepointResponse } from '@/tools/sharepoint/types'
|
||||
|
||||
const logger = createLogger('SharepointBlock')
|
||||
|
||||
export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
type: 'sharepoint',
|
||||
name: 'Sharepoint',
|
||||
description: 'Read and create pages',
|
||||
description: 'Work with pages and lists',
|
||||
longDescription:
|
||||
'Integrate Sharepoint into the workflow. Can read and create pages, and list sites. Requires OAuth.',
|
||||
'Integrate SharePoint into the workflow. Read/create pages, list sites, and work with lists (read, create, update items). Requires OAuth.',
|
||||
docsLink: 'https://docs.sim.ai/tools/sharepoint',
|
||||
category: 'tools',
|
||||
bgColor: '#E0E0E0',
|
||||
@@ -23,6 +26,9 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
{ label: 'Create Page', id: 'create_page' },
|
||||
{ label: 'Read Page', id: 'read_page' },
|
||||
{ label: 'List Sites', id: 'list_sites' },
|
||||
{ label: 'Create List', id: 'create_list' },
|
||||
{ label: 'Read List', id: 'read_list' },
|
||||
{ label: 'Update List', id: 'update_list' },
|
||||
],
|
||||
},
|
||||
// Sharepoint Credentials
|
||||
@@ -39,6 +45,8 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
'email',
|
||||
'Files.Read',
|
||||
'Files.ReadWrite',
|
||||
'Sites.Read.All',
|
||||
'Sites.ReadWrite.All',
|
||||
'offline_access',
|
||||
],
|
||||
placeholder: 'Select Microsoft account',
|
||||
@@ -64,7 +72,17 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
placeholder: 'Select a site',
|
||||
dependsOn: ['credential'],
|
||||
mode: 'basic',
|
||||
condition: { field: 'operation', value: ['create_page', 'read_page', 'list_sites'] },
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: [
|
||||
'create_page',
|
||||
'read_page',
|
||||
'list_sites',
|
||||
'create_list',
|
||||
'read_list',
|
||||
'update_list',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
@@ -86,13 +104,59 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
mode: 'advanced',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'listId',
|
||||
title: 'List ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter list ID (GUID). Required for Update; optional for Read.',
|
||||
canonicalParamId: 'listId',
|
||||
condition: { field: 'operation', value: ['read_list', 'update_list'] },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'listItemId',
|
||||
title: 'Item ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter item ID',
|
||||
canonicalParamId: 'itemId',
|
||||
condition: { field: 'operation', value: ['update_list'] },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'listDisplayName',
|
||||
title: 'List Display Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Name of the list',
|
||||
condition: { field: 'operation', value: 'create_list' },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'listTemplate',
|
||||
title: 'List Template',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: "Template (e.g., 'genericList')",
|
||||
condition: { field: 'operation', value: 'create_list' },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'pageContent',
|
||||
title: 'Page Content',
|
||||
type: 'long-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Content of the page',
|
||||
condition: { field: 'operation', value: 'create_page' },
|
||||
placeholder: 'Provide page content',
|
||||
condition: { field: 'operation', value: ['create_list'] },
|
||||
},
|
||||
{
|
||||
id: 'listDescription',
|
||||
title: 'List Description',
|
||||
type: 'long-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Optional description',
|
||||
condition: { field: 'operation', value: 'create_list' },
|
||||
},
|
||||
|
||||
{
|
||||
@@ -106,9 +170,26 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: 'create_page' },
|
||||
},
|
||||
|
||||
{
|
||||
id: 'listItemFields',
|
||||
title: 'List Item Fields',
|
||||
type: 'long-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter list item fields',
|
||||
canonicalParamId: 'listItemFields',
|
||||
condition: { field: 'operation', value: 'update_list' },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: ['sharepoint_create_page', 'sharepoint_read_page', 'sharepoint_list_sites'],
|
||||
access: [
|
||||
'sharepoint_create_page',
|
||||
'sharepoint_read_page',
|
||||
'sharepoint_list_sites',
|
||||
'sharepoint_create_list',
|
||||
'sharepoint_get_list',
|
||||
'sharepoint_update_list',
|
||||
],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
@@ -118,6 +199,12 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
return 'sharepoint_read_page'
|
||||
case 'list_sites':
|
||||
return 'sharepoint_list_sites'
|
||||
case 'create_list':
|
||||
return 'sharepoint_create_list'
|
||||
case 'read_list':
|
||||
return 'sharepoint_get_list'
|
||||
case 'update_list':
|
||||
return 'sharepoint_update_list'
|
||||
default:
|
||||
throw new Error(`Invalid Sharepoint operation: ${params.operation}`)
|
||||
}
|
||||
@@ -128,12 +215,71 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
// Use siteSelector if provided, otherwise use manualSiteId
|
||||
const effectiveSiteId = (siteSelector || manualSiteId || '').trim()
|
||||
|
||||
const {
|
||||
itemId: providedItemId,
|
||||
listItemId,
|
||||
listItemFields,
|
||||
includeColumns,
|
||||
includeItems,
|
||||
...others
|
||||
} = rest as any
|
||||
|
||||
let parsedItemFields: any = listItemFields
|
||||
if (typeof listItemFields === 'string' && listItemFields.trim()) {
|
||||
try {
|
||||
parsedItemFields = JSON.parse(listItemFields)
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse listItemFields JSON', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
// Ensure listItemFields is an object for the tool schema
|
||||
if (typeof parsedItemFields !== 'object' || parsedItemFields === null) {
|
||||
parsedItemFields = undefined
|
||||
}
|
||||
|
||||
// Sanitize item ID (required by tool)
|
||||
const rawItemId = providedItemId ?? listItemId
|
||||
const sanitizedItemId =
|
||||
rawItemId === undefined || rawItemId === null
|
||||
? undefined
|
||||
: String(rawItemId).trim() || undefined
|
||||
|
||||
const coerceBoolean = (value: any) => {
|
||||
if (typeof value === 'boolean') return value
|
||||
if (typeof value === 'string') return value.toLowerCase() === 'true'
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Debug logging for update_list param mapping
|
||||
if (others.operation === 'update_list') {
|
||||
try {
|
||||
logger.info('SharepointBlock update_list param check', {
|
||||
siteId: effectiveSiteId || undefined,
|
||||
listId: (others as any)?.listId,
|
||||
listTitle: (others as any)?.listTitle,
|
||||
itemId: sanitizedItemId,
|
||||
hasItemFields: !!parsedItemFields && typeof parsedItemFields === 'object',
|
||||
itemFieldKeys:
|
||||
parsedItemFields && typeof parsedItemFields === 'object'
|
||||
? Object.keys(parsedItemFields)
|
||||
: [],
|
||||
})
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return {
|
||||
credential,
|
||||
siteId: effectiveSiteId || undefined,
|
||||
pageSize: rest.pageSize ? Number.parseInt(rest.pageSize as string, 10) : undefined,
|
||||
pageSize: others.pageSize ? Number.parseInt(others.pageSize as string, 10) : undefined,
|
||||
mimeType: mimeType,
|
||||
...rest,
|
||||
...others,
|
||||
// Map to tool param names
|
||||
itemId: sanitizedItemId,
|
||||
listItemFields: parsedItemFields,
|
||||
includeColumns: coerceBoolean(includeColumns),
|
||||
includeItems: coerceBoolean(includeItems),
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -151,6 +297,18 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
siteSelector: { type: 'string', description: 'Site selector' },
|
||||
manualSiteId: { type: 'string', description: 'Manual site ID' },
|
||||
pageSize: { type: 'number', description: 'Results per page' },
|
||||
// Create List operation inputs
|
||||
listDisplayName: { type: 'string', description: 'List display name' },
|
||||
listDescription: { type: 'string', description: 'List description' },
|
||||
listTemplate: { type: 'string', description: 'List template' },
|
||||
// Read List operation inputs
|
||||
listId: { type: 'string', description: 'List ID' },
|
||||
listTitle: { type: 'string', description: 'List title' },
|
||||
includeColumns: { type: 'boolean', description: 'Include columns in response' },
|
||||
includeItems: { type: 'boolean', description: 'Include items in response' },
|
||||
// Update List Item operation inputs
|
||||
listItemId: { type: 'string', description: 'List item ID' },
|
||||
listItemFields: { type: 'string', description: 'List item fields' },
|
||||
},
|
||||
outputs: {
|
||||
sites: {
|
||||
@@ -158,5 +316,25 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
|
||||
description:
|
||||
'An array of SharePoint site objects, each containing details such as id, name, and more.',
|
||||
},
|
||||
list: {
|
||||
type: 'json',
|
||||
description: 'SharePoint list object (id, displayName, name, webUrl, etc.)',
|
||||
},
|
||||
item: {
|
||||
type: 'json',
|
||||
description: 'SharePoint list item with fields',
|
||||
},
|
||||
items: {
|
||||
type: 'json',
|
||||
description: 'Array of SharePoint list items with fields',
|
||||
},
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Success status',
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
description: 'Error message',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -565,6 +565,7 @@ export const auth = betterAuth({
|
||||
'email',
|
||||
'Sites.Read.All',
|
||||
'Sites.ReadWrite.All',
|
||||
'Sites.Manage.All',
|
||||
'offline_access',
|
||||
],
|
||||
responseType: 'code',
|
||||
|
||||
@@ -260,6 +260,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
'email',
|
||||
'Sites.Read.All',
|
||||
'Sites.ReadWrite.All',
|
||||
'Sites.Manage.All',
|
||||
'offline_access',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -145,9 +145,12 @@ import { redditGetCommentsTool, redditGetPostsTool, redditHotPostsTool } from '@
|
||||
import { s3GetObjectTool } from '@/tools/s3'
|
||||
import { searchTool as serperSearch } from '@/tools/serper'
|
||||
import {
|
||||
sharepointCreateListTool,
|
||||
sharepointCreatePageTool,
|
||||
sharepointGetListTool,
|
||||
sharepointListSitesTool,
|
||||
sharepointReadPageTool,
|
||||
sharepointUpdateListItemTool,
|
||||
} from '@/tools/sharepoint'
|
||||
import { slackCanvasTool, slackMessageReaderTool, slackMessageTool } from '@/tools/slack'
|
||||
import { smsSendTool } from '@/tools/sms'
|
||||
@@ -364,6 +367,9 @@ export const tools: Record<string, ToolConfig> = {
|
||||
sharepoint_create_page: sharepointCreatePageTool,
|
||||
sharepoint_read_page: sharepointReadPageTool,
|
||||
sharepoint_list_sites: sharepointListSitesTool,
|
||||
sharepoint_get_list: sharepointGetListTool,
|
||||
sharepoint_create_list: sharepointCreateListTool,
|
||||
sharepoint_update_list: sharepointUpdateListItemTool,
|
||||
// Provider chat tools
|
||||
// Provider chat tools - handled separately in agent blocks
|
||||
}
|
||||
|
||||
165
apps/sim/tools/sharepoint/create_list.ts
Normal file
165
apps/sim/tools/sharepoint/create_list.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
SharepointCreateListResponse,
|
||||
SharepointList,
|
||||
SharepointToolParams,
|
||||
} from '@/tools/sharepoint/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SharePointCreateList')
|
||||
|
||||
export const createListTool: ToolConfig<SharepointToolParams, SharepointCreateListResponse> = {
|
||||
id: 'sharepoint_create_list',
|
||||
name: 'Create SharePoint List',
|
||||
description: 'Create a new list in a SharePoint site',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'sharepoint',
|
||||
additionalScopes: ['openid', 'profile', 'email', 'Sites.ReadWrite.All', 'offline_access'],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the SharePoint API',
|
||||
},
|
||||
siteId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The ID of the SharePoint site (internal use)',
|
||||
},
|
||||
siteSelector: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Select the SharePoint site',
|
||||
},
|
||||
listDisplayName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Display name of the list to create',
|
||||
},
|
||||
listDescription: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Description of the list',
|
||||
},
|
||||
listTemplate: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: "List template name (e.g., 'genericList')",
|
||||
},
|
||||
pageContent: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Optional JSON of columns. Either a top-level array of column definitions or an object with { columns: [...] }.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const siteId = params.siteSelector || params.siteId || 'root'
|
||||
return `https://graph.microsoft.com/v1.0/sites/${siteId}/lists`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (!params.listDisplayName) {
|
||||
throw new Error('listDisplayName is required')
|
||||
}
|
||||
|
||||
// Derive columns from pageContent JSON (object or string) or top-level array
|
||||
let columns: unknown[] | undefined
|
||||
if (params.pageContent) {
|
||||
if (typeof params.pageContent === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(params.pageContent)
|
||||
if (Array.isArray(parsed)) columns = parsed
|
||||
else if (parsed && Array.isArray((parsed as any).columns))
|
||||
columns = (parsed as any).columns
|
||||
} catch (error) {
|
||||
logger.warn('Invalid JSON in pageContent for create list; ignoring', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}
|
||||
} else if (typeof params.pageContent === 'object') {
|
||||
const pc: any = params.pageContent
|
||||
if (Array.isArray(pc)) columns = pc
|
||||
else if (pc && Array.isArray(pc.columns)) columns = pc.columns
|
||||
}
|
||||
}
|
||||
|
||||
const payload: any = {
|
||||
displayName: params.listDisplayName,
|
||||
description: params.listDescription,
|
||||
list: { template: params.listTemplate || 'genericList' },
|
||||
}
|
||||
if (columns && columns.length > 0) payload.columns = columns
|
||||
|
||||
logger.info('Creating SharePoint list', {
|
||||
displayName: payload.displayName,
|
||||
template: payload.list.template,
|
||||
hasDescription: !!payload.description,
|
||||
})
|
||||
|
||||
return payload
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const list: SharepointList = {
|
||||
id: data.id,
|
||||
displayName: data.displayName ?? data.name,
|
||||
name: data.name,
|
||||
webUrl: data.webUrl,
|
||||
createdDateTime: data.createdDateTime,
|
||||
lastModifiedDateTime: data.lastModifiedDateTime,
|
||||
list: data.list,
|
||||
}
|
||||
|
||||
logger.info('SharePoint list created successfully', {
|
||||
listId: list.id,
|
||||
displayName: list.displayName,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { list },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
list: {
|
||||
type: 'object',
|
||||
description: 'Created SharePoint list information',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'The unique ID of the list' },
|
||||
displayName: { type: 'string', description: 'The display name of the list' },
|
||||
name: { type: 'string', description: 'The internal name of the list' },
|
||||
webUrl: { type: 'string', description: 'The web URL of the list' },
|
||||
createdDateTime: { type: 'string', description: 'When the list was created' },
|
||||
lastModifiedDateTime: {
|
||||
type: 'string',
|
||||
description: 'When the list was last modified',
|
||||
},
|
||||
list: { type: 'object', description: 'List properties (e.g., template)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
243
apps/sim/tools/sharepoint/get_list.ts
Normal file
243
apps/sim/tools/sharepoint/get_list.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
SharepointGetListResponse,
|
||||
SharepointList,
|
||||
SharepointToolParams,
|
||||
} from '@/tools/sharepoint/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SharePointGetList')
|
||||
|
||||
export const getListTool: ToolConfig<SharepointToolParams, SharepointGetListResponse> = {
|
||||
id: 'sharepoint_get_list',
|
||||
name: 'Get SharePoint List',
|
||||
description: 'Get metadata (and optionally columns/items) for a SharePoint list',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'sharepoint',
|
||||
additionalScopes: ['openid', 'profile', 'email', 'Sites.Read.All', 'offline_access'],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the SharePoint API',
|
||||
},
|
||||
siteSelector: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Select the SharePoint site',
|
||||
},
|
||||
siteId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The ID of the SharePoint site (internal use)',
|
||||
},
|
||||
listId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the list to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const siteId = params.siteId || params.siteSelector || 'root'
|
||||
|
||||
// If neither listId nor listTitle provided, list all lists in the site
|
||||
if (!params.listId) {
|
||||
const baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists`
|
||||
const url = new URL(baseUrl)
|
||||
const finalUrl = url.toString()
|
||||
logger.info('SharePoint List All Lists URL', { finalUrl, siteId })
|
||||
return finalUrl
|
||||
}
|
||||
|
||||
const listSegment = params.listId
|
||||
// Default to returning items when targeting a specific list unless explicitly disabled
|
||||
const wantsItems = typeof params.includeItems === 'boolean' ? params.includeItems : true
|
||||
|
||||
// If caller wants items for a specific list, prefer the items endpoint (no columns)
|
||||
if (wantsItems && !params.includeColumns) {
|
||||
const itemsUrl = new URL(
|
||||
`https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}/items`
|
||||
)
|
||||
itemsUrl.searchParams.set('$expand', 'fields')
|
||||
const finalItemsUrl = itemsUrl.toString()
|
||||
logger.info('SharePoint Get List Items URL', {
|
||||
finalUrl: finalItemsUrl,
|
||||
siteId,
|
||||
listId: params.listId,
|
||||
})
|
||||
return finalItemsUrl
|
||||
}
|
||||
|
||||
// Otherwise, fetch list metadata (optionally with columns/items via $expand)
|
||||
const baseUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}`
|
||||
const url = new URL(baseUrl)
|
||||
const expandParts: string[] = []
|
||||
if (params.includeColumns) expandParts.push('columns')
|
||||
if (wantsItems) expandParts.push('items($expand=fields)')
|
||||
if (expandParts.length > 0) url.searchParams.append('$expand', expandParts.join(','))
|
||||
|
||||
const finalUrl = url.toString()
|
||||
logger.info('SharePoint Get List URL', {
|
||||
finalUrl,
|
||||
siteId,
|
||||
listId: params.listId,
|
||||
includeColumns: !!params.includeColumns,
|
||||
includeItems: wantsItems,
|
||||
})
|
||||
return finalUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// If the response is a collection of items (from the items endpoint)
|
||||
if (
|
||||
Array.isArray((data as any).value) &&
|
||||
(data as any).value.length > 0 &&
|
||||
(data as any).value[0] &&
|
||||
'fields' in (data as any).value[0]
|
||||
) {
|
||||
const items = (data as any).value.map((i: any) => ({
|
||||
id: i.id,
|
||||
fields: i.fields as Record<string, unknown>,
|
||||
}))
|
||||
|
||||
const nextLink: string | undefined = (data as any)['@odata.nextLink']
|
||||
const nextPageToken = nextLink
|
||||
? (() => {
|
||||
try {
|
||||
const u = new URL(nextLink)
|
||||
return u.searchParams.get('$skiptoken') || u.searchParams.get('$skip') || undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
})()
|
||||
: undefined
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { list: { items } as SharepointList, nextPageToken },
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a collection of lists (site-level)
|
||||
if (Array.isArray((data as any).value)) {
|
||||
const lists: SharepointList[] = (data as any).value.map((l: any) => ({
|
||||
id: l.id,
|
||||
displayName: l.displayName ?? l.name,
|
||||
name: l.name,
|
||||
webUrl: l.webUrl,
|
||||
createdDateTime: l.createdDateTime,
|
||||
lastModifiedDateTime: l.lastModifiedDateTime,
|
||||
list: l.list,
|
||||
}))
|
||||
|
||||
const nextLink: string | undefined = (data as any)['@odata.nextLink']
|
||||
const nextPageToken = nextLink
|
||||
? (() => {
|
||||
try {
|
||||
const u = new URL(nextLink)
|
||||
return u.searchParams.get('$skiptoken') || u.searchParams.get('$skip') || undefined
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
})()
|
||||
: undefined
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { lists, nextPageToken },
|
||||
}
|
||||
}
|
||||
|
||||
// Single list response (with optional expands)
|
||||
const list: SharepointList = {
|
||||
id: data.id,
|
||||
displayName: data.displayName ?? data.name,
|
||||
name: data.name,
|
||||
webUrl: data.webUrl,
|
||||
createdDateTime: data.createdDateTime,
|
||||
lastModifiedDateTime: data.lastModifiedDateTime,
|
||||
list: data.list,
|
||||
columns: Array.isArray(data.columns)
|
||||
? data.columns.map((c: any) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
displayName: c.displayName,
|
||||
description: c.description,
|
||||
indexed: c.indexed,
|
||||
enforcedUniqueValues: c.enforcedUniqueValues,
|
||||
hidden: c.hidden,
|
||||
readOnly: c.readOnly,
|
||||
required: c.required,
|
||||
columnGroup: c.columnGroup,
|
||||
}))
|
||||
: undefined,
|
||||
items: Array.isArray(data.items)
|
||||
? data.items.map((i: any) => ({ id: i.id, fields: i.fields as Record<string, unknown> }))
|
||||
: undefined,
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { list },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
list: {
|
||||
type: 'object',
|
||||
description: 'Information about the SharePoint list',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'The unique ID of the list' },
|
||||
displayName: { type: 'string', description: 'The display name of the list' },
|
||||
name: { type: 'string', description: 'The internal name of the list' },
|
||||
webUrl: { type: 'string', description: 'The web URL of the list' },
|
||||
createdDateTime: { type: 'string', description: 'When the list was created' },
|
||||
lastModifiedDateTime: {
|
||||
type: 'string',
|
||||
description: 'When the list was last modified',
|
||||
},
|
||||
list: { type: 'object', description: 'List properties (e.g., template)' },
|
||||
columns: {
|
||||
type: 'array',
|
||||
description: 'List column definitions',
|
||||
items: { type: 'object' },
|
||||
},
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'List items (with fields when expanded)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Item ID' },
|
||||
fields: { type: 'object', description: 'Field values for the item' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
lists: {
|
||||
type: 'array',
|
||||
description: 'All lists in the site when no listId/title provided',
|
||||
items: { type: 'object' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
import { createListTool } from '@/tools/sharepoint/create_list'
|
||||
import { createPageTool } from '@/tools/sharepoint/create_page'
|
||||
import { getListTool } from '@/tools/sharepoint/get_list'
|
||||
import { listSitesTool } from '@/tools/sharepoint/list_sites'
|
||||
import { readPageTool } from '@/tools/sharepoint/read_page'
|
||||
import { updateListItemTool } from '@/tools/sharepoint/update_list'
|
||||
|
||||
export const sharepointCreatePageTool = createPageTool
|
||||
export const sharepointCreateListTool = createListTool
|
||||
export const sharepointGetListTool = getListTool
|
||||
export const sharepointListSitesTool = listSitesTool
|
||||
export const sharepointReadPageTool = readPageTool
|
||||
export const sharepointUpdateListItemTool = updateListItemTool
|
||||
|
||||
@@ -58,6 +58,39 @@ export interface SharepointPageContent {
|
||||
} | null
|
||||
}
|
||||
|
||||
export interface SharepointColumn {
|
||||
id?: string
|
||||
name?: string
|
||||
displayName?: string
|
||||
description?: string
|
||||
indexed?: boolean
|
||||
enforcedUniqueValues?: boolean
|
||||
hidden?: boolean
|
||||
readOnly?: boolean
|
||||
required?: boolean
|
||||
columnGroup?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export interface SharepointListItem {
|
||||
id: string
|
||||
fields?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface SharepointList {
|
||||
id: string
|
||||
displayName?: string
|
||||
name?: string
|
||||
webUrl?: string
|
||||
createdDateTime?: string
|
||||
lastModifiedDateTime?: string
|
||||
list?: {
|
||||
template?: string
|
||||
}
|
||||
columns?: SharepointColumn[]
|
||||
items?: SharepointListItem[]
|
||||
}
|
||||
|
||||
export interface SharepointListSitesResponse extends ToolResponse {
|
||||
output: {
|
||||
sites: SharepointSite[]
|
||||
@@ -131,6 +164,18 @@ export interface SharepointToolParams {
|
||||
serverRelativePath?: string
|
||||
groupId?: string
|
||||
maxPages?: number
|
||||
// Lists
|
||||
listId?: string
|
||||
listTitle?: string
|
||||
includeColumns?: boolean
|
||||
includeItems?: boolean
|
||||
// Create List
|
||||
listDisplayName?: string
|
||||
listDescription?: string
|
||||
listTemplate?: string
|
||||
// Update List Item
|
||||
itemId?: string
|
||||
listItemFields?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface GraphApiResponse {
|
||||
@@ -211,3 +256,29 @@ export type SharepointResponse =
|
||||
| SharepointCreatePageResponse
|
||||
| SharepointReadPageResponse
|
||||
| SharepointReadSiteResponse
|
||||
| SharepointGetListResponse
|
||||
| SharepointCreateListResponse
|
||||
| SharepointUpdateListItemResponse
|
||||
|
||||
export interface SharepointGetListResponse extends ToolResponse {
|
||||
output: {
|
||||
list?: SharepointList
|
||||
lists?: SharepointList[]
|
||||
nextPageToken?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface SharepointCreateListResponse extends ToolResponse {
|
||||
output: {
|
||||
list: SharepointList
|
||||
}
|
||||
}
|
||||
|
||||
export interface SharepointUpdateListItemResponse extends ToolResponse {
|
||||
output: {
|
||||
item: {
|
||||
id: string
|
||||
fields?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
169
apps/sim/tools/sharepoint/update_list.ts
Normal file
169
apps/sim/tools/sharepoint/update_list.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
SharepointToolParams,
|
||||
SharepointUpdateListItemResponse,
|
||||
} from '@/tools/sharepoint/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('SharePointUpdateListItem')
|
||||
|
||||
export const updateListItemTool: ToolConfig<
|
||||
SharepointToolParams,
|
||||
SharepointUpdateListItemResponse
|
||||
> = {
|
||||
id: 'sharepoint_update_list',
|
||||
name: 'Update SharePoint List Item',
|
||||
description: 'Update the properties (fields) on a SharePoint list item',
|
||||
version: '1.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'sharepoint',
|
||||
additionalScopes: ['openid', 'profile', 'email', 'Sites.ReadWrite.All', 'offline_access'],
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the SharePoint API',
|
||||
},
|
||||
siteSelector: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Select the SharePoint site',
|
||||
},
|
||||
siteId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The ID of the SharePoint site (internal use)',
|
||||
},
|
||||
listId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the list containing the item',
|
||||
},
|
||||
itemId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ID of the list item to update',
|
||||
},
|
||||
listItemFields: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Field values to update on the list item',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const siteId = params.siteId || params.siteSelector || 'root'
|
||||
if (!params.itemId) throw new Error('itemId is required')
|
||||
if (!params.listId) {
|
||||
throw new Error('listId must be provided')
|
||||
}
|
||||
const listSegment = params.listId
|
||||
return `https://graph.microsoft.com/v1.0/sites/${siteId}/lists/${listSegment}/items/${params.itemId}/fields`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (!params.listItemFields || Object.keys(params.listItemFields).length === 0) {
|
||||
throw new Error('listItemFields must not be empty')
|
||||
}
|
||||
|
||||
// Filter out system/read-only fields that cannot be updated via Graph
|
||||
const readOnlyFields = new Set<string>([
|
||||
'Id',
|
||||
'id',
|
||||
'UniqueId',
|
||||
'GUID',
|
||||
'ContentTypeId',
|
||||
'Created',
|
||||
'Modified',
|
||||
'Author',
|
||||
'Editor',
|
||||
'CreatedBy',
|
||||
'ModifiedBy',
|
||||
'AuthorId',
|
||||
'EditorId',
|
||||
'_UIVersionString',
|
||||
'Attachments',
|
||||
'FileRef',
|
||||
'FileDirRef',
|
||||
'FileLeafRef',
|
||||
])
|
||||
|
||||
const entries = Object.entries(params.listItemFields)
|
||||
const updatableEntries = entries.filter(([key]) => !readOnlyFields.has(key))
|
||||
|
||||
if (updatableEntries.length !== entries.length) {
|
||||
const removed = entries.filter(([key]) => readOnlyFields.has(key)).map(([key]) => key)
|
||||
logger.warn('Removed read-only SharePoint fields from update', {
|
||||
removed,
|
||||
})
|
||||
}
|
||||
|
||||
if (updatableEntries.length === 0) {
|
||||
const requestedKeys = Object.keys(params.listItemFields)
|
||||
throw new Error(
|
||||
`All provided fields are read-only and cannot be updated: ${requestedKeys.join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
const sanitizedFields = Object.fromEntries(updatableEntries)
|
||||
|
||||
logger.info('Updating SharePoint list item fields', {
|
||||
listItemId: params.itemId,
|
||||
listId: params.listId,
|
||||
fieldsKeys: Object.keys(sanitizedFields),
|
||||
})
|
||||
return sanitizedFields
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
let fields: Record<string, unknown> | undefined
|
||||
if (response.status !== 204) {
|
||||
try {
|
||||
fields = await response.json()
|
||||
} catch {
|
||||
// Fall back to submitted fields if no body is returned
|
||||
fields = params?.listItemFields
|
||||
}
|
||||
} else {
|
||||
fields = params?.listItemFields
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
item: {
|
||||
id: params?.itemId!,
|
||||
fields,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
item: {
|
||||
type: 'object',
|
||||
description: 'Updated SharePoint list item',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Item ID' },
|
||||
fields: { type: 'object', description: 'Updated field values' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user