import type { NormalizedOperation } from '../types/index.js';
import type { MCPToolDefinition } from '../types/mcp-tool.js';
import { generateInputSchema, generateProxyMetadata } from './schema-generator.js';
import { isDangerousOperation } from '../parser/normalize.js';
import { log } from '../utils/logger.js';
/**
* Convert operationId to snake_case tool name
*/
export function generateToolName(operationId: string): string {
return operationId
.replace(/([a-z])([A-Z])/g, '$1_$2') // camelCase -> camel_Case
.toLowerCase()
.replace(/[^a-z0-9_]/g, '_') // Non-alphanumeric -> _
.replace(/_+/g, '_') // Collapse underscores
.replace(/^_|_$/g, '') // Trim leading/trailing
.slice(0, 64); // Limit length
}
/**
* Generate tool description from operation
*/
export function generateDescription(operation: NormalizedOperation): string {
const parts: string[] = [];
// Primary: use description or summary
if (operation.description) {
parts.push(operation.description);
} else if (operation.summary) {
parts.push(operation.summary);
} else {
// Fallback: generate from method and path
parts.push(`${operation.method} ${operation.path}`);
}
// Add deprecation warning
if (operation.deprecated) {
parts.push('DEPRECATED: This endpoint may be removed in future versions.');
}
// Limit length for AI context
let description = parts.join('\n\n');
if (description.length > 1000) {
description = description.slice(0, 997) + '...';
}
return description;
}
/**
* Generate title from operation
*/
export function generateTitle(operation: NormalizedOperation): string {
if (operation.summary) {
return operation.summary.slice(0, 100);
}
// Generate title from operationId
return operation.operationId
.replace(/_/g, ' ')
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/\b\w/g, (c) => c.toUpperCase())
.slice(0, 100);
}
/**
* Determine if tool should be enabled by default
*/
export function shouldEnableByDefault(operation: NormalizedOperation): boolean {
// Disable deprecated operations
if (operation.deprecated) {
return false;
}
// Disable dangerous operations (DELETE, etc.)
if (isDangerousOperation(operation)) {
return false;
}
return true;
}
/**
* Convert a single operation to MCP tool definition
*/
export function convertOperation(operation: NormalizedOperation): MCPToolDefinition {
const name = generateToolName(operation.operationId);
return {
name,
title: generateTitle(operation),
description: generateDescription(operation),
inputSchema: generateInputSchema(operation),
_proxy: generateProxyMetadata(operation),
_ui: {
enabled: shouldEnableByDefault(operation),
originalOperationId: operation.operationId,
modified: false,
},
};
}
/**
* Convert all operations to MCP tool definitions
*/
export function convertToMCPTools(operations: NormalizedOperation[]): MCPToolDefinition[] {
const tools: MCPToolDefinition[] = [];
const usedNames = new Set<string>();
for (const operation of operations) {
const tool = convertOperation(operation);
// Ensure unique name
let name = tool.name;
let counter = 1;
while (usedNames.has(name)) {
name = `${tool.name}_${counter++}`;
}
usedNames.add(name);
tools.push({
...tool,
name,
});
}
log.info('Converted operations to MCP tools', {
total: tools.length,
enabled: tools.filter((t) => t._ui.enabled).length,
disabled: tools.filter((t) => !t._ui.enabled).length,
});
return tools;
}
/**
* Get enabled tools only
*/
export function getEnabledTools(tools: MCPToolDefinition[]): MCPToolDefinition[] {
return tools.filter((t) => t._ui.enabled);
}