Skip to content

Commit 2fd0531

Browse files
Fix for the connector normalisation
1 parent 34d3792 commit 2fd0531

2 files changed

Lines changed: 69 additions & 22 deletions

File tree

src/utils/normalize.test.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { normalizeActionName } from './normalize';
22

33
describe('normalizeActionName', () => {
4-
test('strips versioned API name to MCP format', () => {
4+
test('prepends connector when actionId has no prefix', () => {
5+
expect(normalizeActionName('slack_1.0.0_send_message_global')).toBe('slack_send_message');
6+
});
7+
8+
test('does not duplicate connector when actionId already has prefix', () => {
59
expect(normalizeActionName('calendly_1.0.0_calendly_create_scheduling_link_global')).toBe(
610
'calendly_create_scheduling_link',
711
);
@@ -13,6 +17,12 @@ describe('normalizeActionName', () => {
1317
);
1418
});
1519

20+
test('handles actionId without connector prefix and multi-digit version', () => {
21+
expect(normalizeActionName('bamboohr_2.10.3_list_employees_global')).toBe(
22+
'bamboohr_list_employees',
23+
);
24+
});
25+
1626
test('returns input unchanged when no version pattern matches', () => {
1727
expect(normalizeActionName('bamboohr_create_employee')).toBe('bamboohr_create_employee');
1828
});
@@ -21,15 +31,9 @@ describe('normalizeActionName', () => {
2131
expect(normalizeActionName('')).toBe('');
2232
});
2333

24-
test('returns input unchanged when missing _global suffix', () => {
25-
expect(normalizeActionName('calendly_1.0.0_calendly_create_link')).toBe(
26-
'calendly_1.0.0_calendly_create_link',
27-
);
28-
});
29-
30-
test('returns input unchanged for uppercase names', () => {
34+
test('handles uppercase connector names', () => {
3135
expect(normalizeActionName('Calendly_1.0.0_create_link_global')).toBe(
32-
'Calendly_1.0.0_create_link_global',
36+
'Calendly_create_link',
3337
);
3438
});
3539

@@ -38,4 +42,16 @@ describe('normalizeActionName', () => {
3842
'api2cart_get_products',
3943
);
4044
});
45+
46+
test('handles connector with digits and no prefix in actionId', () => {
47+
expect(normalizeActionName('api2cart_1.0.0_get_products_global')).toBe(
48+
'api2cart_get_products',
49+
);
50+
});
51+
52+
test('handles non-global project IDs', () => {
53+
expect(normalizeActionName('jira_1.0.0_search_issues_103/dev-56501')).toBe(
54+
'jira_search_issues',
55+
);
56+
});
4157
});

src/utils/normalize.ts

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,64 @@
11
/**
22
* Action name normalization utilities.
33
*
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'
66
*
7-
* MCP tools use simplified names:
8-
* 'calendly_create_scheduling_link'
7+
* MCP tools use names like:
8+
* 'slack_send_message'
99
*
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.
1112
*/
1213

13-
const VERSIONED_ACTION_RE = /^[a-z][a-z0-9]*_\d+(?:\.\d+)+_(.+)_global$/;
14+
const VERSION_RE = /^\d+(?:\.\d+)+$/;
1415

1516
/**
16-
* Convert semantic search API action name to MCP tool name.
17+
* Convert semantic search API composite ID to MCP tool name.
1718
*
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
1922
* @returns The normalized MCP-compatible tool name
2023
*
2124
* @example
2225
* ```typescript
26+
* normalizeActionName('slack_1.0.0_send_message_global');
27+
* // => 'slack_send_message'
28+
*
2329
* normalizeActionName('calendly_1.0.0_calendly_create_scheduling_link_global');
24-
* // => 'calendly_create_scheduling_link'
30+
* // => 'calendly_create_scheduling_link' (no duplicate prefix)
2531
*
2632
* normalizeActionName('bamboohr_create_employee');
27-
* // => 'bamboohr_create_employee' (unchanged)
33+
* // => 'bamboohr_create_employee' (unchanged, no version)
2834
* ```
2935
*/
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}`;
3364
}

0 commit comments

Comments
 (0)