UNPKG

@toriihq/torii-mcp

Version:

Model Context Protocol server for Torii API

137 lines (133 loc) 6.93 kB
import { z } from 'zod'; import { makeApiRequest } from './api.js'; import { FUNCTION_NAME_TO_API_PARAMS } from '../api/filters.js'; import { convertArgsToParameters } from './shared.types.js'; // API Functions for Apps export async function getApps(params, headers) { const apiRequest = FUNCTION_NAME_TO_API_PARAMS.list_apps(params ? convertArgsToParameters(params) : []); return makeApiRequest(apiRequest.path, apiRequest.method, undefined, { ...apiRequest.headers, ...headers }); } export async function getAppById(idApp, params, headers) { const queryParams = params ? `?${new URLSearchParams(params).toString()}` : ''; return makeApiRequest(`/apps/${idApp}${queryParams}`, 'GET', undefined, headers); } export async function getAppFields() { return makeApiRequest('/apps/fields'); } // Register Apps tools with the MCP server export function registerAppsTools(server) { // List Apps Tool server.tool('list_apps', ` <use_case> Use this tool to list all apps used in the organization. </use_case> <important_notes> - ALWAYS use AT LEAST one of: aggs, baseFilters (either "q", or any app field used as a filter; you can use as many fields as needed; see fields_filters example below) and filters (see filters_schema below) for better results. - ALWAYS use the fields property and ask only for the fields you need. - The default fields included in the response are: id, name, primaryOwner - Use the get_app_fields tool to list the fields to pass in the "fields" parameter. - As long as you did not get the information you need to respond to the user, perform pagination by using the "cursor" parameter. - Use the aggs property to perform aggregations over the data, such as grouping, calculations: sum, average, minimum, maximum, etc, date range and more. Follow the aggregations_schema below. Aggregated data is returned under the "aggregations" property. </important_notes> <filters_schema> Array of { key: string op: equals|notEquals|contains|notContains|anyOf|noneOf|allOf|isExactly|isSet|isNotSet|gt|gte|lt|lte|dayAfter|dayOnOrAfter|dayBefore|dayOnOrBefore|nested|relativeDateToday|relativeDateOn|relativeDateLess|relativeDateMore|exists|notExists|or|and value: changes based on the type of field used as key } </filters_schema> <aggregations_schema> AGGREGATION_TYPES = metric | groupBy | date_range | date_histogram METRIC_FUNCTIONS = sum | avg | max | min (and "total" if aggregationType = groupBy) AGGREGATION_SORT_ORDERS = min | max <schema> { field: string, aggregationType: AGGREGATION_TYPES, options: { metricFunction: METRIC_FUNCTIONS, size: number (max: 100), sort: { field: string, aggFunc: METRIC_FUNCTIONS, order: AGGREGATION_SORT_ORDERS }, aggs: Nested aggregation with the same structure as the parent } </schema> </aggregations_schema> `.trim(), { baseFilters: z .string() .optional() .describe('JSON string representing baseFilters schema: \'{"q":"search for apps by name or other fields; ALWAYS use this when you know the name of the app", [field_name_1]: [field_value_1], [field_name_2]: [field_value_2], [field_name_n]: [field_value_n]}\''), filters: z .string() .optional() .describe('JSON string representing filters schema: \'[{"key":[field_name],"op":operation,"value":[field_value}]]\'; the list can contain multiple filters combined in an "AND" relation'), fields: z .string() .optional() .describe('Comma-separated list of fields to return for each application; Allowed fields: id, name, primaryOwner, appOwners, state, category, url, imageUrl, description, tags, activeUsersCount, score, isCustom, addedBy, creationTime, isHidden, lastUsageTime, sources, vendor, expenses, activeContractsTotalValue and all fields from the get_app_fields tool'), aggs: z .string() .optional() .describe('JSON string representing aggregation schema. Structure: \'{"field":"string", "aggregationType":"metric|groupBy|date_range|date_histogram", "options":{"size":integer, "sort":{"field":"string","order":"desc|asc","aggFunc":"total|sum|avg|max|min"}, "metricFunction":"sum|avg|max|min", "hardBounds":{"min":"string","max":"string"}, "extendedBounds":{"min":"string","max":"string"}, "datePeriod":"weekly|monthly|quarterly|yearly"}, "aggs":Nested aggregation with the same structure as the parent}\''), cursor: z .string() .optional() .describe('Pagination cursor taken from the previous response\'s nextCursor property; should only be used if the previous response returned a "nextCursor" property') }, async (args) => { const queryParams = {}; // Add parameters to query if (args.fields) queryParams.fields = args.fields; if (args.baseFilters) queryParams.baseFilters = args.baseFilters; if (args.filters) queryParams.filters = args.filters; if (args.aggs) queryParams.aggs = args.aggs; if (args.cursor) queryParams.cursor = args.cursor; try { const apps = await getApps(queryParams, { 'x-api-version': '1.1' }); return { content: [{ type: 'text', text: JSON.stringify(apps, null, 2) }] }; } catch (error) { return { content: [ { type: 'text', text: error.message + ' ' + `Use the get_app_fields tool to see if you are using correct field.` } ] }; } }); // Get App Tool server.tool('get_app', 'Get details of a specific app by its ID, any expense or cost is in cents not dollars', { id: z.number().describe('Unique app identifier'), fields: z.string().optional().describe('Comma-separated list of fields to return') }, async ({ id, fields }) => { const params = {}; if (fields) params.fields = fields; const headers = { 'x-api-version': '1.1' }; const app = await getAppById(id, params, headers); return { content: [{ type: 'text', text: JSON.stringify(app, null, 2) }] }; }); // Get App Fields Tool server.tool('get_app_fields', 'Use this tool to list the fields to pass in the "fields" parameter when calling list_apps or get_app_by_id', async () => { const fields = await getAppFields(); return { content: [{ type: 'text', text: JSON.stringify(fields, null, 2) }] }; }); } //# sourceMappingURL=apps.js.map