@toriihq/torii-mcp
Version:
Model Context Protocol server for Torii API
137 lines (133 loc) • 6.93 kB
JavaScript
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