UNPKG

@access-mcp/shared

Version:

Shared utilities for ACCESS-CI MCP servers

144 lines (143 loc) 4.48 kB
export function sanitizeGroupId(groupId) { if (!groupId) { throw new Error("groupId parameter is required and cannot be null or undefined"); } return groupId.replace(/[^a-zA-Z0-9.-]/g, ""); } export function formatApiUrl(version, endpoint) { return `/${version}/${endpoint}`; } export function handleApiError(error) { const axiosError = error; if (axiosError.response?.data?.message) { return axiosError.response.data.message; } if (axiosError.response?.status) { return `API error: ${axiosError.response.status} ${axiosError.response.statusText}`; } if (error instanceof Error) { return error.message; } return "Unknown API error"; } /** * Add helpful next steps to a successful response */ export function addNextSteps(data, nextSteps) { return { data, next_steps: nextSteps, }; } /** * Create an LLM-friendly error response with suggestions */ export function createLLMError(error, errorType, options = {}) { return { error, error_type: errorType, ...options, }; } /** * Add discovery suggestions when returning empty results */ export function addDiscoverySuggestions(data, discoverySteps) { if (data.length === 0) { return { data, count: 0, next_steps: discoverySteps, suggestions: [ "No results found. Try the suggested next steps to discover available options.", ], }; } return { data, count: data.length, }; } /** * Common next step templates for cross-server consistency */ export const CommonNextSteps = { discoverResources: { action: "discover_resources", description: "Find available compute resources to filter by", tool: "search_resources", parameters: { include_resource_ids: true }, }, narrowResults: (currentCount, suggestedFilters) => ({ action: "narrow_results", description: `Currently showing ${currentCount} results. Add filters to narrow down: ${suggestedFilters.join(", ")}`, }), exploreRelated: (relatedTool, description) => ({ action: "explore_related", description, tool: relatedTool, }), refineSearch: (suggestions) => ({ action: "refine_search", description: `Try these refinements: ${suggestions.join(", ")}`, }), }; /** * Resolve a human-readable name to a resource ID. * * @param input - The input string (name or ID) * @param searchFn - A function that searches for resources by name and returns matches * @returns ResolveResult with either the resolved ID or an error message * * @example * ```ts * const result = await resolveResourceId("Anvil", async (query) => { * const resources = await searchResources({ query }); * return resources.map(r => ({ id: r.id, name: r.name })); * }); * * if (result.success) { * console.log(result.id); // "anvil.purdue.access-ci.org" * } else { * console.log(result.error); // "Multiple resources match..." * } * ``` */ export async function resolveResourceId(input, searchFn) { // If it already looks like a full resource ID (contains dots), return as-is if (input.includes(".")) { return { success: true, id: input }; } // Search for the resource by name const items = await searchFn(input); if (items.length === 0) { return { success: false, error: `No resource found matching '${input}'`, suggestion: "Use the search tool to find valid resource names.", }; } // Find exact name match first (case-insensitive) const inputLower = input.toLowerCase(); const exactMatch = items.find((item) => item.name?.toLowerCase() === inputLower); if (exactMatch && exactMatch.id) { return { success: true, id: exactMatch.id }; } // Multiple partial matches - ask user to be more specific if (items.length > 1) { const names = items.map((i) => i.name).join(", "); return { success: false, error: `Multiple resources match '${input}': ${names}`, suggestion: "Please specify the exact resource name.", }; } // Single partial match - use it if (items[0].id) { return { success: true, id: items[0].id }; } return { success: false, error: `Could not resolve resource '${input}'`, }; }