Skip to main content
Glama

mcp-google-sheets

index.ts9.66 kB
import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common"; import { DynamicPropsValue, Property } from "@activepieces/pieces-framework"; import { assertNotNullOrUndefined, CreateTableWebhookRequest, Field, FieldType, MarkdownVariant, PopulatedRecord, SeekPage, StaticDropdownEmptyOption, Table, TableWebhookEventType, ListTablesRequest } from "@activepieces/shared"; import { z } from 'zod'; import qs from 'qs'; type FormattedRecord = { id: string; created: string; updated: string; cells: Record<string, { fieldName: string; updated: string; created: string; value: unknown; }>; } const getFieldTypeText = (fieldType: FieldType) => { switch (fieldType) { case FieldType.STATIC_DROPDOWN: return 'Single Select'; case FieldType.DATE: return 'Date'; case FieldType.NUMBER: return 'Number'; case FieldType.TEXT: return 'Text'; } } export const tablesCommon = { table_id: Property.Dropdown({ displayName: 'Table Name', required: true, refreshers: [], refreshOnSearch: true, options: async (_propsValue, context) => { try { const tables = await fetchAllTables(context); if (!Array.isArray(tables) || tables.length === 0) { return { options: [], disabled: true, placeholder: 'No tables found. Please create a table first.', }; } return { options: tables.map((table: Table) => ({ label: table.name, value: table.externalId })), }; } catch (e) { console.error('Error fetching tables:', e); return { options: [], disabled: true, placeholder: 'Error loading tables. Please try again.', }; } }, }), record_id: Property.ShortText({ displayName: 'Record ID', description: 'The ID of the record to do the action on.', required: true, }), async getTableFields({ tableId, context }: { tableId: string, context: { server: { apiUrl: string, token: string } } }) { const fieldsResponse = await httpClient.sendRequest({ method: HttpMethod.GET, url: `${context.server.apiUrl}v1/fields`, queryParams: { tableId, }, authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.server.token, }, }); return fieldsResponse.body as Field[]; }, createFieldValidations(tableFields: Field[]) { const fieldValidations: Record<string, z.ZodType> = {}; tableFields.forEach(field => { switch (field.type) { case FieldType.NUMBER: fieldValidations[field.id] = z.union([z.number(), z.string().transform(val => { const num = Number(val); if (isNaN(num)) throw new Error(`Invalid number for field "${field.name}"`); return num; })]).optional(); break; case FieldType.DATE: fieldValidations[field.id] = z.union([z.date(), z.string().transform(val => { const date = new Date(val); if (isNaN(date.getTime())) throw new Error(`Invalid date for field "${field.name}"`); return date; })]).optional(); break; default: fieldValidations[field.id] = z.string().optional(); } }); return fieldValidations; }, async createFieldProperties({ tableId, context }: { tableId: string, context: { server: { apiUrl: string, token: string } } }): Promise<DynamicPropsValue> { const fields: DynamicPropsValue = {}; try { const tableFields = await this.getTableFields({ tableId, context }); if (!Array.isArray(tableFields) || tableFields.length === 0) { fields['markdown'] = Property.MarkDown({ value: `We couldn't find any fields in the selected table. Please add fields to the table first.`, variant: MarkdownVariant.INFO, }); return fields; } for (const field of tableFields) { const description = getFieldTypeText(field.type); switch (field.type) { case FieldType.NUMBER: fields[field.externalId] = Property.Number({ displayName: field.name, description, required: false, }); break; case FieldType.DATE: fields[field.externalId] = Property.DateTime({ displayName: field.name, description, required: false, }); break; case FieldType.STATIC_DROPDOWN: fields[field.externalId] = Property.StaticDropdown({ displayName: field.name, description, defaultValue:'', required: false, options: { options:[StaticDropdownEmptyOption,...field.data.options.map(option => ({ label: option.value, value: option.value }))], }, }); break; default: fields[field.externalId] = Property.ShortText({ displayName: field.name, description, required: false, defaultValue: '', }); break; } } return fields; } catch (e) { console.error('Error fetching fields:', e); fields['markdown'] = Property.MarkDown({ value: `We couldn't find any fields in the selected table. Please add fields to the table first.`, variant: MarkdownVariant.INFO, }); return fields; } }, async createWebhook({ tableId, events, webhookUrl, flowId, server, }: { tableId: string; events: TableWebhookEventType[]; webhookUrl: string; flowId: string; server: { apiUrl: string, token: string }; }) { const request: CreateTableWebhookRequest = { events, webhookUrl, flowId, } const response = await httpClient.sendRequest({ method: HttpMethod.POST, url: `${server.apiUrl}v1/tables/${tableId}/webhooks`, body: request, authentication: { type: AuthenticationType.BEARER_TOKEN, token: server.token, }, }); return response.body; }, async deleteWebhook({ tableId, webhookId, server, }: { tableId: string; webhookId: string; server: { apiUrl: string, token: string }; }) { const response = await httpClient.sendRequest({ method: HttpMethod.DELETE, url: `${server.apiUrl}v1/tables/${tableId}/webhooks/${webhookId}`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: server.token, }, }); return response.body; }, async getRecentRecords({ tableId, limit = 5, context }: { tableId: string, limit?: number, context: { server: { apiUrl: string, token: string } } }) { if ((tableId ?? '').toString().length === 0) { throw new Error(JSON.stringify({ message: 'Please add some records to the table before testing this trigger' })) } const response = await httpClient.sendRequest({ method: HttpMethod.GET, url: `${context.server.apiUrl}v1/records?tableId=${tableId}&limit=${limit}`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.server.token, }, }); return response.body.data.map(this.formatRecord); }, formatRecord(record: PopulatedRecord | { record: PopulatedRecord }): FormattedRecord { const actualRecord = 'record' in record ? record.record : record; return { id: actualRecord.id, created: actualRecord.created, updated: actualRecord.updated, cells: actualRecord.cells ? Object.fromEntries(Object.entries(actualRecord.cells).map(([fieldId, cell]) => { return [fieldId, { fieldName: cell.fieldName, updated: cell.updated, created: cell.created, value: cell.value }] })) : {}, } }, async convertTableExternalIdToId(tableId: string, context: { server: { apiUrl: string, token: string } }) { const list: ListTablesRequest = { externalIds: [tableId], } const res = await httpClient.sendRequest({ method: HttpMethod.GET, url: `${context.server.apiUrl}v1/tables?${qs.stringify(list)}`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.server.token, }, }); const table = (res.body as SeekPage<Table>).data[0]; assertNotNullOrUndefined(table, `Table with externalId ${tableId} not found`); return table.id; } } const fetchAllTables = async (context: { server: { apiUrl: string, token: string } }): Promise<Table[]> => { const res = await httpClient.sendRequest({ method: HttpMethod.GET, url: `${context.server.apiUrl}v1/tables?limit=100`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.server.token, }, }); const resultBody = res.body as SeekPage<Table> const tables = [...resultBody.data]; if (!Array.isArray(tables) || tables.length === 0) { return []; } let next = resultBody.next; while (next) { const nextPage = await httpClient.sendRequest({ method: HttpMethod.GET, url: `${context.server.apiUrl}v1/tables?cursor=${next}&limit=100`, authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.server.token, }, }); const nextPageBody = nextPage.body as SeekPage<Table> tables.push(...nextPageBody.data) next = nextPageBody.next } return tables; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/activepieces/activepieces'

If you have feedback or need assistance with the MCP directory API, please join our Discord server