UNPKG

@tulip/mcp-server

Version:

Model Context Protocol server for Tulip API

497 lines (411 loc) 20.9 kB
/** * Tool handlers for the Tulip MCP server */ import { logger } from '../utils/Logger.js'; /** * Tool handlers class that contains all the business logic for executing tools */ export class ToolHandlers { constructor(apiClient, toolSelection) { this.apiClient = apiClient; this.toolSelection = toolSelection; } async handleToolCall(name, args) { // Check if tool is enabled if (!this.toolSelection.isToolEnabled(name)) { return { content: [ { type: 'text', text: `Tool '${name}' is not enabled.\n\nCurrent Configuration:\n Categories: ${Array.from(this.toolSelection.getEnabledToolCategories()).join(', ') || 'none'}\n Types: ${Array.from(this.toolSelection.getEnabledToolTypes()).join(', ') || 'none'}\n\nTool Information:\n Category: ${this.toolSelection.getToolCategory(name)}\n Type: ${this.toolSelection.getToolType(name)}\n\nTo enable this tool, set ENABLED_TOOLS with one of:\n 1. Individual tool: ENABLED_TOOLS=${name}\n 2. By category: ENABLED_TOOLS=${this.toolSelection.getToolCategory(name)}\n 3. By type: ENABLED_TOOLS=${this.toolSelection.getToolType(name)}\n 4. Combined: ENABLED_TOOLS=table,read-only,${name}\n\nAvailable categories: ${this.toolSelection.getAvailableToolCategories().join(', ')}\nAvailable types: ${this.toolSelection.getAvailableToolTypes().join(', ')}`, }, ], isError: true, }; } // Add safety warning for dangerous tools if (this.toolSelection.isToolDangerous(name)) { logger.debug(`⚠️ EXECUTING DANGEROUS TOOL: ${name} - This operation can modify or delete data permanently`); } try { let result; switch (name) { case 'listAppGroups': result = await this.apiClient.makeRequest('/appGroups'); break; case 'listMachineTypes': result = await this.apiClient.makeRequest('/machines'); break; case 'generateMachineActivityArchive': result = await this.apiClient.makeRequest(`/machines/archive/${args.machineId}`, 'POST'); break; case 'retrieveMachineActivityArchive': result = await this.apiClient.makeRequest(`/machines/archive/csv/${args.jobId}`); break; case 'reportAttributes': result = await this.apiClient.makeRequest('/attributes/report', 'POST', args.attributes); break; case 'createUser': const userData = { role: args.role, name: args.name }; if (args.badge_id) { userData.badge_id = args.badge_id; } result = await this.apiClient.makeRequest('/users', 'POST', userData); break; case 'listTables': result = await this.apiClient.makeRequest('/tables'); break; case 'createTable': result = await this.apiClient.makeRequest('/tables', 'POST', args.tableData); break; case 'getTable': result = await this.apiClient.makeRequest(`/tables/${args.tableId}`); break; case 'updateTable': result = await this.apiClient.makeRequest(`/tables/${args.tableId}`, 'PUT', args.tableData); break; case 'listTableRecords': const queryParams = {}; if (args.limit) queryParams.limit = args.limit; if (args.offset) queryParams.offset = args.offset; result = await this.apiClient.makeRequest(`/tables/${args.tableId}/records`, 'GET', null, queryParams); break; case 'createTableRecord': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/records`, 'POST', args.recordData); break; case 'getTableRecord': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/records/${args.recordId}`); break; case 'updateTableRecord': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/records/${args.recordId}`, 'PUT', args.recordData); break; case 'deleteTableRecord': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/records/${args.recordId}`, 'DELETE'); break; case 'deleteAllTableRecords': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/records`, 'DELETE'); break; case 'countTableRecords': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/count`); break; case 'runTableAggregation': const aggQueryParams = { 'function': args['function'], fieldId: args.fieldId, limit: args.limit, }; if (args.sortOptions) { aggQueryParams.sortOptions = args.sortOptions; } if (args.filters) { aggQueryParams.filters = args.filters; } if (args.filterAggregator) { aggQueryParams.filterAggregator = args.filterAggregator; } result = await this.apiClient.makeRequest(`/tables/${args.tableId}/runAggregation`, 'GET', null, aggQueryParams); break; case 'incrementTableRecordField': const incrementData = { fieldName: args.fieldName, value: args.value }; result = await this.apiClient.makeRequest(`/tables/${args.tableId}/records/${args.recordId}/increment`, 'PATCH', incrementData); break; case 'createTableLink': result = await this.apiClient.makeRequest('/tableLinks', 'POST', args.linkData); break; case 'getTableLink': result = await this.apiClient.makeRequest(`/tableLinks/${args.tableLinkId}`); break; case 'updateTableLinkLabels': const updateTableLinkData = { leftColumnLabel: args.leftColumnLabel, rightColumnLabel: args.rightColumnLabel }; result = await this.apiClient.makeRequest(`/tableLinks/${args.tableLinkId}`, 'PUT', updateTableLinkData); break; case 'linkTableRecords': const linkData = { leftRecord: args.leftRecord, rightRecord: args.rightRecord }; result = await this.apiClient.makeRequest(`/tableLinks/${args.tableLinkId}/link`, 'PUT', linkData); break; case 'unlinkTableRecords': const unlinkData = { leftRecord: args.leftRecord, rightRecord: args.rightRecord }; result = await this.apiClient.makeRequest(`/tableLinks/${args.tableLinkId}/unlink`, 'PUT', unlinkData); break; // ======================================== // TABLE AGGREGATIONS API // ======================================== case 'createTableAggregation': const createAggData = { field: args.field, fn: args.fn, label: args.label, tableId: args.tableId, }; result = await this.apiClient.makeRequest(`/tables/${args.tableId}/aggregations`, 'POST', createAggData); break; case 'getTableAggregation': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/aggregation/${args.aggregationId}`); break; case 'listTableAggregations': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/aggregations`); break; case 'updateTableAggregation': const updateAggData = { field: args.field, fn: args.fn, label: args.label, tableId: args.tableId }; result = await this.apiClient.makeRequest(`/tables/${args.tableId}/aggregation/${args.aggregationId}`, 'PUT', updateAggData); break; case 'deleteTableAggregation': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/aggregation/${args.aggregationId}`, 'DELETE'); break; // ======================================== // TABLE QUERIES API // ======================================== case 'createTableQuery': const createQueryData = { label: args.label, tableId: args.tableId, filters: args.filters || [], filterAggregator: args.filterAggregator, sortOptions: args.sortOptions || [], limit: args.limit }; result = await this.apiClient.makeRequest(`/tables/${args.tableId}/queries`, 'POST', createQueryData); break; case 'getTableQuery': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/query/${args.queryId}`); break; case 'listTableQueries': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/queries`); break; case 'updateTableQuery': const updateQueryData = { label: args.label, tableId: args.tableId, filters: args.filters || [], filterAggregator: args.filterAggregator, sortOptions: args.sortOptions || [], limit: args.limit }; result = await this.apiClient.makeRequest(`/tables/${args.tableId}/query/${args.queryId}`, 'PUT', updateQueryData); break; case 'deleteTableQuery': result = await this.apiClient.makeRequest(`/tables/${args.tableId}/query/${args.queryId}`, 'DELETE'); break; case 'signUrls': result = await this.apiClient.makeRequest('/signURLs', 'POST', { urls: args.urls }); break; // ======================================== // STATIONS/INTERFACES API (Next-Gen API) // ======================================== case 'listInterfaces': const interfaceQueryParams = {}; if (args.limit) interfaceQueryParams.limit = args.limit; if (args.offset) interfaceQueryParams.offset = args.offset; if (args.sort) interfaceQueryParams.sort = args.sort; result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl('/interfaces'), 'GET', null, interfaceQueryParams); break; case 'createInterface': const createInterfaceBody = { name: args.name, stationId: args.stationId, }; const createInterfaceQuery = {}; if (args.replaceInterfaceAtStation !== undefined) { createInterfaceQuery.replaceInterfaceAtStation = args.replaceInterfaceAtStation; } result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl('/interfaces'), 'POST', createInterfaceBody, createInterfaceQuery); break; case 'getInterface': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/interfaces/${args.interfaceId}`)); break; case 'updateInterface': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/interfaces/${args.interfaceId}`), 'PATCH', args.interfaceData); break; case 'archiveInterface': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/interfaces/${args.interfaceId}/archive`), 'POST'); break; case 'deleteInterfaceStationAssignment': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/interfaces/${args.interfaceId}/station-assignment`), 'DELETE'); break; case 'updateInterfaceStationAssignment': const updateStationAssignmentData = {}; if (args.stationId) updateStationAssignmentData.stationId = args.stationId; if (args.replaceInterfaceAtStation !== undefined) updateStationAssignmentData.replaceInterfaceAtStation = args.replaceInterfaceAtStation; result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/interfaces/${args.interfaceId}/station-assignment`), 'PUT', updateStationAssignmentData); break; // ======================================== // STATION GROUPS API (Next-Gen API) // ======================================== case 'listStationGroups': const stationGroupQueryParams = {}; if (args.limit) stationGroupQueryParams.limit = args.limit; if (args.offset) stationGroupQueryParams.offset = args.offset; if (args.sort) stationGroupQueryParams.sort = args.sort; result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl('/station-groups'), 'GET', null, stationGroupQueryParams); break; case 'createStationGroup': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl('/station-groups'), 'POST', args.stationGroupData); break; case 'getStationGroup': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}`)); break; case 'updateStationGroup': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}`), 'PATCH', args.stationGroupData); break; case 'archiveStationGroup': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}/archive`), 'POST'); break; case 'updateStationGroupStations': const stationGroupStationsData = {}; if (args.stationIds) stationGroupStationsData.stationIds = args.stationIds; result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}/stations`), 'PATCH', stationGroupStationsData); break; // ======================================== // STATIONS API (Next-Gen API) // ======================================== case 'listStations': const stationQueryParams = {}; if (args.limit) stationQueryParams.limit = args.limit; if (args.offset) stationQueryParams.offset = args.offset; if (args.sort) stationQueryParams.sort = args.sort; result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl('/stations'), 'GET', null, stationQueryParams); break; case 'createStation': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl('/stations'), 'POST', args.stationData); break; case 'getStation': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}`)); break; case 'updateStation': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}`), 'PATCH', args.stationData); break; case 'archiveStation': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}/archive`), 'POST'); break; // ======================================== // STATION APP ASSIGNMENTS API // ======================================== case 'listStationAppAssignments': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}/app-assignments`)); break; case 'createStationAppAssignment': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}/app-assignments`), 'POST', args.appAssignmentData); break; case 'deleteStationAppAssignment': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}/app-assignments/${args.appAssignmentId}`), 'DELETE'); break; case 'manageStationAppAssignment': const manageStationAppData = { type: args.type, tag: args.tag }; if (args.appFolderId) manageStationAppData.appFolderId = args.appFolderId; if (args.appId) manageStationAppData.appId = args.appId; if (args.appVersionId) manageStationAppData.appVersionId = args.appVersionId; result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}/app-assignments/${args.appAssignmentId}`), 'PUT', manageStationAppData); break; case 'removeAllStationAppAssignments': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/stations/${args.stationId}/app-assignments`), 'DELETE'); break; // ======================================== // STATION GROUP APP ASSIGNMENTS API // ======================================== case 'listStationGroupAppAssignments': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}/app-assignments`)); break; case 'createStationGroupAppAssignment': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}/app-assignments`), 'POST', args.appAssignmentData); break; case 'deleteStationGroupAppAssignment': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}/app-assignments/${args.appAssignmentId}`), 'DELETE'); break; case 'manageStationGroupAppAssignment': const manageStationGroupAppData = { type: args.type, tag: args.tag }; if (args.appFolderId) manageStationGroupAppData.appFolderId = args.appFolderId; if (args.appId) manageStationGroupAppData.appId = args.appId; if (args.appVersionId) manageStationGroupAppData.appVersionId = args.appVersionId; result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}/app-assignments/${args.appAssignmentId}`), 'PUT', manageStationGroupAppData); break; case 'removeAllStationGroupAppAssignments': result = await this.apiClient.makeRequest(this.apiClient.createNextGenApiUrl(`/station-groups/${args.stationGroupId}/app-assignments`), 'DELETE'); break; // ======================================== // USERS API (Next-Gen API) // ======================================== case 'listUserRoles': result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl('/roles')); break; case 'getUserRole': result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl(`/roles/${args.userRoleId}`)); break; case 'listUserGroups': result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl('/user-groups')); break; case 'getUserGroup': result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl(`/user-groups/${args.userGroupId}`)); break; case 'getUserGroupUsers': result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl(`/user-groups/${args.userGroupId}/users`)); break; case 'listUsers': result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl('/users')); break; case 'getUser': result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl(`/users/${args.userId}`)); break; case 'listUserUserGroups': const userGroupsQueryParams = {}; if (args.limit) userGroupsQueryParams.limit = args.limit; if (args.offset) userGroupsQueryParams.offset = args.offset; if (args.filter) userGroupsQueryParams.filter = args.filter; result = await this.apiClient.makeRequest(this.apiClient.createNextGenUsersApiUrl(`/users/${args.userId}/user-groups`), 'GET', null, userGroupsQueryParams); break; default: return { content: [ { type: 'text', text: `Tool implementation needed: ${name} - This tool is defined but needs specific implementation based on the Tulip API parameters.`, }, ], }; } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error: ${error.message}`, }, ], isError: true, }; } } }