|
1 | 1 | /** |
2 | 2 | * Action name normalization utilities. |
3 | 3 | * |
4 | | - * The semantic search API returns versioned action names like: |
5 | | - * 'calendly_1.0.0_calendly_create_scheduling_link_global' |
| 4 | + * The semantic search API returns composite IDs like: |
| 5 | + * 'slack_1.0.0_send_message_global' |
6 | 6 | * |
7 | | - * MCP tools use simplified names: |
8 | | - * 'calendly_create_scheduling_link' |
| 7 | + * MCP tools use names like: |
| 8 | + * 'slack_send_message' |
9 | 9 | * |
10 | | - * This module bridges the two formats. |
| 10 | + * This module bridges the two formats by extracting the connector |
| 11 | + * and action parts, then combining them. |
11 | 12 | */ |
12 | 13 |
|
13 | | -const VERSIONED_ACTION_RE = /^[a-z][a-z0-9]*_\d+(?:\.\d+)+_(.+)_global$/; |
| 14 | +const VERSION_RE = /^\d+(?:\.\d+)+$/; |
14 | 15 |
|
15 | 16 | /** |
16 | | - * Convert semantic search API action name to MCP tool name. |
| 17 | + * Convert semantic search API composite ID to MCP tool name. |
17 | 18 | * |
18 | | - * @param actionName - The raw action name from the API |
| 19 | + * Composite ID format: {connector}_{version}_{actionId}_{projectId} |
| 20 | + * |
| 21 | + * @param compositeId - The composite ID from the API |
19 | 22 | * @returns The normalized MCP-compatible tool name |
20 | 23 | * |
21 | 24 | * @example |
22 | 25 | * ```typescript |
| 26 | + * normalizeActionName('slack_1.0.0_send_message_global'); |
| 27 | + * // => 'slack_send_message' |
| 28 | + * |
23 | 29 | * normalizeActionName('calendly_1.0.0_calendly_create_scheduling_link_global'); |
24 | | - * // => 'calendly_create_scheduling_link' |
| 30 | + * // => 'calendly_create_scheduling_link' (no duplicate prefix) |
25 | 31 | * |
26 | 32 | * normalizeActionName('bamboohr_create_employee'); |
27 | | - * // => 'bamboohr_create_employee' (unchanged) |
| 33 | + * // => 'bamboohr_create_employee' (unchanged, no version) |
28 | 34 | * ``` |
29 | 35 | */ |
30 | | -export function normalizeActionName(actionName: string): string { |
31 | | - const match = VERSIONED_ACTION_RE.exec(actionName); |
32 | | - return match ? match[1] : actionName; |
| 36 | +export function normalizeActionName(compositeId: string): string { |
| 37 | + const parts = compositeId.split('_'); |
| 38 | + |
| 39 | + // Find the version segment (e.g. "1.0.0") |
| 40 | + let versionIdx = -1; |
| 41 | + for (let i = 0; i < parts.length; i++) { |
| 42 | + if (VERSION_RE.test(parts[i])) { |
| 43 | + versionIdx = i; |
| 44 | + break; |
| 45 | + } |
| 46 | + } |
| 47 | + |
| 48 | + if (versionIdx < 1) return compositeId; |
| 49 | + |
| 50 | + const connector = parts.slice(0, versionIdx).join('_'); |
| 51 | + |
| 52 | + // Everything after version, excluding the last segment (projectId) |
| 53 | + const afterVersion = parts.slice(versionIdx + 1); |
| 54 | + if (afterVersion.length < 2) return compositeId; |
| 55 | + |
| 56 | + const actionParts = afterVersion.slice(0, -1); // drop projectId (last segment) |
| 57 | + const actionId = actionParts.join('_'); |
| 58 | + |
| 59 | + // If actionId already starts with connector prefix, don't duplicate |
| 60 | + if (actionId.startsWith(`${connector}_`)) { |
| 61 | + return actionId; |
| 62 | + } |
| 63 | + return `${connector}_${actionId}`; |
33 | 64 | } |
0 commit comments