Skip to main content
Glama
normalize.ts4.65 kB
import type { NormalizedOperation, HttpMethod } from '../types/index.js'; import type { JSONSchema } from '../types/mcp-tool.js'; /** * Ensure operation ID is unique by appending a counter if needed */ export function ensureUniqueOperationIds(operations: NormalizedOperation[]): NormalizedOperation[] { const idCount = new Map<string, number>(); return operations.map((op) => { const count = idCount.get(op.operationId) ?? 0; idCount.set(op.operationId, count + 1); if (count === 0) { return op; } return { ...op, operationId: `${op.operationId}_${count}`, }; }); } /** * Sort operations by path and method for consistent ordering */ export function sortOperations(operations: NormalizedOperation[]): NormalizedOperation[] { const methodOrder: Record<HttpMethod, number> = { GET: 1, POST: 2, PUT: 3, PATCH: 4, DELETE: 5, HEAD: 6, OPTIONS: 7, }; return [...operations].sort((a, b) => { const pathCompare = a.path.localeCompare(b.path); if (pathCompare !== 0) return pathCompare; return (methodOrder[a.method] ?? 99) - (methodOrder[b.method] ?? 99); }); } /** * Flatten nested object schemas to a simpler structure * This helps with generating cleaner tool input schemas */ export function simplifySchema(schema: JSONSchema, maxDepth = 3, currentDepth = 0): JSONSchema { if (currentDepth >= maxDepth) { // At max depth, convert complex types to string return { type: 'string', description: schema.description }; } if (schema.type === 'object' && schema.properties) { const simplified: JSONSchema = { type: 'object', properties: {}, description: schema.description, }; for (const [key, value] of Object.entries(schema.properties)) { simplified.properties![key] = simplifySchema(value, maxDepth, currentDepth + 1); } if (schema.required) { simplified.required = schema.required; } return simplified; } if (schema.type === 'array' && schema.items) { return { type: 'array', items: simplifySchema(schema.items, maxDepth, currentDepth + 1), description: schema.description, }; } // For allOf/anyOf/oneOf, take the first option if (schema.allOf?.[0]) { return simplifySchema(schema.allOf[0], maxDepth, currentDepth); } if (schema.anyOf?.[0]) { return simplifySchema(schema.anyOf[0], maxDepth, currentDepth); } if (schema.oneOf?.[0]) { return simplifySchema(schema.oneOf[0], maxDepth, currentDepth); } return schema; } /** * Check if an operation is considered "dangerous" * (modifies data or has side effects) */ export function isDangerousOperation(operation: NormalizedOperation): boolean { // DELETE is always dangerous if (operation.method === 'DELETE') { return true; } // Check for dangerous keywords in operation ID or description const dangerousKeywords = [ 'delete', 'remove', 'destroy', 'purge', 'reset', 'revoke', 'cancel', 'terminate', 'wipe', 'clear', ]; const text = `${operation.operationId} ${operation.summary ?? ''} ${operation.description ?? ''}`.toLowerCase(); return dangerousKeywords.some((keyword) => text.includes(keyword)); } /** * Filter operations by method */ export function filterByMethod( operations: NormalizedOperation[], methods: HttpMethod[] ): NormalizedOperation[] { const methodSet = new Set(methods); return operations.filter((op) => !methodSet.has(op.method)); } /** * Filter out deprecated operations */ export function filterDeprecated( operations: NormalizedOperation[], excludeDeprecated = true ): NormalizedOperation[] { if (!excludeDeprecated) return operations; return operations.filter((op) => !op.deprecated); } /** * Get statistics about operations */ export function getOperationStats(operations: NormalizedOperation[]) { const stats = { total: operations.length, byMethod: {} as Record<string, number>, deprecated: 0, withRequestBody: 0, withAuth: 0, tags: new Set<string>(), }; for (const op of operations) { // Count by method stats.byMethod[op.method] = (stats.byMethod[op.method] ?? 0) + 1; // Count deprecated if (op.deprecated) { stats.deprecated++; } // Count with request body if (op.requestBody) { stats.withRequestBody++; } // Count with security if (op.security.length > 0) { stats.withAuth++; } // Collect tags for (const tag of op.tags) { stats.tags.add(tag); } } return { ...stats, tags: Array.from(stats.tags), }; }

Latest Blog Posts

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/procoders/openapi-mcp-ts'

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