@a5c-ai/tool-mux 5.0.1-staging.016f0b0e8119

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +57 -0
  2. package/dist/__tests__/dispatch.test.d.ts +2 -0
  3. package/dist/__tests__/dispatch.test.d.ts.map +1 -0
  4. package/dist/__tests__/dispatch.test.js +209 -0
  5. package/dist/__tests__/dispatch.test.js.map +1 -0
  6. package/dist/__tests__/hooks.test.d.ts +2 -0
  7. package/dist/__tests__/hooks.test.d.ts.map +1 -0
  8. package/dist/__tests__/hooks.test.js +100 -0
  9. package/dist/__tests__/hooks.test.js.map +1 -0
  10. package/dist/__tests__/mcp-bridge.test.d.ts +2 -0
  11. package/dist/__tests__/mcp-bridge.test.d.ts.map +1 -0
  12. package/dist/__tests__/mcp-bridge.test.js +129 -0
  13. package/dist/__tests__/mcp-bridge.test.js.map +1 -0
  14. package/dist/__tests__/registry.test.d.ts +2 -0
  15. package/dist/__tests__/registry.test.d.ts.map +1 -0
  16. package/dist/__tests__/registry.test.js +162 -0
  17. package/dist/__tests__/registry.test.js.map +1 -0
  18. package/dist/__tests__/schema-translation.test.d.ts +2 -0
  19. package/dist/__tests__/schema-translation.test.d.ts.map +1 -0
  20. package/dist/__tests__/schema-translation.test.js +126 -0
  21. package/dist/__tests__/schema-translation.test.js.map +1 -0
  22. package/dist/dispatch.d.ts +40 -0
  23. package/dist/dispatch.d.ts.map +1 -0
  24. package/dist/dispatch.js +117 -0
  25. package/dist/dispatch.js.map +1 -0
  26. package/dist/hooks.d.ts +42 -0
  27. package/dist/hooks.d.ts.map +1 -0
  28. package/dist/hooks.js +25 -0
  29. package/dist/hooks.js.map +1 -0
  30. package/dist/index.d.ts +11 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +24 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/mcp-bridge.d.ts +54 -0
  35. package/dist/mcp-bridge.d.ts.map +1 -0
  36. package/dist/mcp-bridge.js +77 -0
  37. package/dist/mcp-bridge.js.map +1 -0
  38. package/dist/registry.d.ts +40 -0
  39. package/dist/registry.d.ts.map +1 -0
  40. package/dist/registry.js +83 -0
  41. package/dist/registry.js.map +1 -0
  42. package/dist/schema-translation.d.ts +38 -0
  43. package/dist/schema-translation.d.ts.map +1 -0
  44. package/dist/schema-translation.js +61 -0
  45. package/dist/schema-translation.js.map +1 -0
  46. package/dist/types.d.ts +39 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +2 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @a5c-ai/tool-mux
2
+
3
+ Tool lifecycle layer for the babysitter monorepo. Provides:
4
+
5
+ - **ToolRegistry** — in-memory, Map-backed registry for tool descriptors and servers
6
+ - **ToolDispatcher** — policy-driven resolution of tool-to-server mapping with glob matching and before/after hook integration
7
+ - **Schema translation** — adapters between `ToolDescriptor` and `NormalizedToolDefinition` (transport-mux), plus `translateTools` for provider-specific wire formats
8
+ - **ToolHookBridge** — interface (and no-op implementation) bridging tool dispatch to hooks-mux PreToolUse/PostToolUse lifecycle
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install @a5c-ai/tool-mux
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```typescript
19
+ import {
20
+ ToolRegistry,
21
+ ToolDispatcher,
22
+ NoopToolHookBridge,
23
+ toToolDescriptor,
24
+ translateTools,
25
+ } from '@a5c-ai/tool-mux';
26
+
27
+ // 1. Build a registry
28
+ const registry = new ToolRegistry();
29
+ registry.register({
30
+ name: 'read_file',
31
+ source: 'builtin',
32
+ description: 'Read a file from disk',
33
+ parameters: { type: 'object', properties: { path: { type: 'string' } } },
34
+ });
35
+
36
+ // 2. Create a dispatcher with a policy
37
+ const dispatcher = new ToolDispatcher({
38
+ registry,
39
+ policy: {
40
+ rules: [{ match: 'mcp_*', server: 'mcp-server-1', priority: 10 }],
41
+ defaultServer: 'local',
42
+ },
43
+ hooks: new NoopToolHookBridge(),
44
+ });
45
+
46
+ // 3. Dispatch a tool call
47
+ const result = await dispatcher.dispatch(
48
+ { toolName: 'read_file', input: { path: '/tmp/test.txt' } },
49
+ async (tool, ctx) => {
50
+ // your executor logic here
51
+ return { content: '...' };
52
+ },
53
+ );
54
+
55
+ // 4. Translate tool schemas for a specific provider
56
+ const anthropicTools = translateTools(registry.list(), 'anthropic');
57
+ ```
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dispatch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/dispatch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,209 @@
1
+ import { describe, expect, it, beforeEach, vi } from 'vitest';
2
+ import { ToolDispatcher } from '../dispatch.js';
3
+ import { ToolRegistry } from '../registry.js';
4
+ /* -------------------------------------------------------------------------- */
5
+ /* Helpers */
6
+ /* -------------------------------------------------------------------------- */
7
+ function makeTool(overrides = {}) {
8
+ return {
9
+ name: 'test_tool',
10
+ description: 'A test tool',
11
+ parameters: { type: 'object' },
12
+ source: 'builtin',
13
+ ...overrides,
14
+ };
15
+ }
16
+ function makeContext(overrides = {}) {
17
+ return {
18
+ toolName: 'test_tool',
19
+ input: {},
20
+ ...overrides,
21
+ };
22
+ }
23
+ const noopExecutor = async (_tool, _ctx) => 'executed';
24
+ /* ========================================================================== */
25
+ /* ToolDispatcher */
26
+ /* ========================================================================== */
27
+ describe('ToolDispatcher', () => {
28
+ let registry;
29
+ beforeEach(() => {
30
+ registry = new ToolRegistry();
31
+ });
32
+ /* ---------------------------------------------------------------------- */
33
+ /* resolveServer */
34
+ /* ---------------------------------------------------------------------- */
35
+ describe('resolveServer', () => {
36
+ it('resolves with exact match rule', () => {
37
+ registry.register(makeTool({ name: 'file_read', server: 'default' }));
38
+ const dispatcher = new ToolDispatcher({
39
+ registry,
40
+ policy: {
41
+ rules: [{ match: 'file_read', server: 'file-server', priority: 10 }],
42
+ },
43
+ });
44
+ expect(dispatcher.resolveServer('file_read')).toBe('file-server');
45
+ });
46
+ it('resolves with glob pattern (wildcard)', () => {
47
+ registry.register(makeTool({ name: 'file_read' }));
48
+ const dispatcher = new ToolDispatcher({
49
+ registry,
50
+ policy: {
51
+ rules: [{ match: 'file_*', server: 'file-server' }],
52
+ },
53
+ });
54
+ expect(dispatcher.resolveServer('file_read')).toBe('file-server');
55
+ expect(dispatcher.resolveServer('file_write')).toBe('file-server');
56
+ });
57
+ it('resolves with single-char glob pattern (?)', () => {
58
+ const dispatcher = new ToolDispatcher({
59
+ registry,
60
+ policy: {
61
+ rules: [{ match: 'tool_?', server: 'single-char-server' }],
62
+ },
63
+ });
64
+ expect(dispatcher.resolveServer('tool_a')).toBe('single-char-server');
65
+ expect(dispatcher.resolveServer('tool_ab')).toBeUndefined(); // two chars, no match
66
+ });
67
+ it('falls back to descriptor server when no rules match', () => {
68
+ registry.register(makeTool({ name: 'my_tool', server: 'desc-server' }));
69
+ const dispatcher = new ToolDispatcher({
70
+ registry,
71
+ policy: { rules: [] },
72
+ });
73
+ expect(dispatcher.resolveServer('my_tool')).toBe('desc-server');
74
+ });
75
+ it('falls back to defaultServer when no rules or descriptor match', () => {
76
+ registry.register(makeTool({ name: 'orphan' }));
77
+ const dispatcher = new ToolDispatcher({
78
+ registry,
79
+ policy: { rules: [], defaultServer: 'fallback' },
80
+ });
81
+ expect(dispatcher.resolveServer('orphan')).toBe('fallback');
82
+ });
83
+ it('returns undefined when nothing matches and no default is set', () => {
84
+ const dispatcher = new ToolDispatcher({
85
+ registry,
86
+ policy: { rules: [] },
87
+ });
88
+ expect(dispatcher.resolveServer('unknown_tool')).toBeUndefined();
89
+ });
90
+ it('higher priority rule wins over lower priority', () => {
91
+ const dispatcher = new ToolDispatcher({
92
+ registry,
93
+ policy: { rules: [] },
94
+ });
95
+ dispatcher.addRule({ match: 'file_*', server: 'low-priority', priority: 1 });
96
+ dispatcher.addRule({ match: 'file_*', server: 'high-priority', priority: 100 });
97
+ expect(dispatcher.resolveServer('file_read')).toBe('high-priority');
98
+ });
99
+ it('first matching rule wins among equal priorities', () => {
100
+ const dispatcher = new ToolDispatcher({
101
+ registry,
102
+ policy: {
103
+ rules: [
104
+ { match: 'tool_*', server: 'server-a', priority: 5 },
105
+ { match: 'tool_read', server: 'server-b', priority: 5 },
106
+ ],
107
+ },
108
+ });
109
+ // Both match 'tool_read', but the glob comes first (same priority, stable order)
110
+ expect(dispatcher.resolveServer('tool_read')).toBe('server-a');
111
+ });
112
+ it('setPolicy replaces the entire dispatch policy', () => {
113
+ const dispatcher = new ToolDispatcher({
114
+ registry,
115
+ policy: {
116
+ rules: [{ match: 'old_*', server: 'old-server' }],
117
+ defaultServer: 'old-default',
118
+ },
119
+ });
120
+ expect(dispatcher.resolveServer('old_tool')).toBe('old-server');
121
+ dispatcher.setPolicy({
122
+ rules: [{ match: 'new_*', server: 'new-server' }],
123
+ defaultServer: 'new-default',
124
+ });
125
+ expect(dispatcher.resolveServer('old_tool')).toBe('new-default');
126
+ expect(dispatcher.resolveServer('new_tool')).toBe('new-server');
127
+ });
128
+ });
129
+ /* ---------------------------------------------------------------------- */
130
+ /* dispatch */
131
+ /* ---------------------------------------------------------------------- */
132
+ describe('dispatch', () => {
133
+ it('returns error result when tool is not registered', async () => {
134
+ const dispatcher = new ToolDispatcher({ registry });
135
+ const result = await dispatcher.dispatch(makeContext({ toolName: 'nonexistent' }), noopExecutor);
136
+ expect(result.error).toBe('Tool not found: nonexistent');
137
+ expect(result.output).toBeNull();
138
+ expect(result.durationMs).toBe(0);
139
+ });
140
+ it('executes tool and returns output with duration', async () => {
141
+ registry.register(makeTool({ name: 'echo' }));
142
+ const dispatcher = new ToolDispatcher({ registry });
143
+ const executor = async () => 'hello world';
144
+ const result = await dispatcher.dispatch(makeContext({ toolName: 'echo', input: { text: 'hi' } }), executor);
145
+ expect(result.output).toBe('hello world');
146
+ expect(result.error).toBeUndefined();
147
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
148
+ });
149
+ it('catches executor errors and returns them in the result', async () => {
150
+ registry.register(makeTool({ name: 'failing' }));
151
+ const dispatcher = new ToolDispatcher({ registry });
152
+ const executor = async () => {
153
+ throw new Error('kaboom');
154
+ };
155
+ const result = await dispatcher.dispatch(makeContext({ toolName: 'failing' }), executor);
156
+ expect(result.error).toBe('kaboom');
157
+ expect(result.output).toBeUndefined();
158
+ });
159
+ it('denies execution when beforeToolUse hook returns deny', async () => {
160
+ registry.register(makeTool({ name: 'blocked' }));
161
+ const hooks = {
162
+ async beforeToolUse() {
163
+ return { decision: 'deny', reason: 'not allowed' };
164
+ },
165
+ async afterToolUse() {
166
+ return undefined;
167
+ },
168
+ };
169
+ const dispatcher = new ToolDispatcher({ registry, hooks });
170
+ const executor = vi.fn(noopExecutor);
171
+ const result = await dispatcher.dispatch(makeContext({ toolName: 'blocked' }), executor);
172
+ expect(result.error).toBe('not allowed');
173
+ expect(result.output).toBeNull();
174
+ expect(executor).not.toHaveBeenCalled();
175
+ });
176
+ it('calls afterToolUse hook with result after execution', async () => {
177
+ registry.register(makeTool({ name: 'hooked' }));
178
+ const afterSpy = vi.fn();
179
+ const hooks = {
180
+ async beforeToolUse() {
181
+ return undefined;
182
+ },
183
+ afterToolUse: afterSpy,
184
+ };
185
+ const dispatcher = new ToolDispatcher({ registry, hooks });
186
+ await dispatcher.dispatch(makeContext({ toolName: 'hooked' }), noopExecutor);
187
+ expect(afterSpy).toHaveBeenCalledOnce();
188
+ const [ctx, desc, res] = afterSpy.mock.calls[0];
189
+ expect(ctx.toolName).toBe('hooked');
190
+ expect(desc.name).toBe('hooked');
191
+ expect(res.output).toBe('executed');
192
+ });
193
+ it('provides default deny message when hook reason is absent', async () => {
194
+ registry.register(makeTool({ name: 'denied_no_reason' }));
195
+ const hooks = {
196
+ async beforeToolUse() {
197
+ return { decision: 'deny' };
198
+ },
199
+ async afterToolUse() {
200
+ return undefined;
201
+ },
202
+ };
203
+ const dispatcher = new ToolDispatcher({ registry, hooks });
204
+ const result = await dispatcher.dispatch(makeContext({ toolName: 'denied_no_reason' }), noopExecutor);
205
+ expect(result.error).toBe('Tool use denied by hook');
206
+ });
207
+ });
208
+ });
209
+ //# sourceMappingURL=dispatch.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.test.js","sourceRoot":"","sources":["../../src/__tests__/dispatch.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hooks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hooks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,100 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { NoopToolHookBridge } from '../hooks.js';
3
+ /* -------------------------------------------------------------------------- */
4
+ /* Helpers */
5
+ /* -------------------------------------------------------------------------- */
6
+ function makeContext(overrides = {}) {
7
+ return {
8
+ toolName: 'test_tool',
9
+ input: { key: 'value' },
10
+ ...overrides,
11
+ };
12
+ }
13
+ function makeDescriptor(overrides = {}) {
14
+ return {
15
+ name: 'test_tool',
16
+ description: 'A test tool',
17
+ source: 'builtin',
18
+ ...overrides,
19
+ };
20
+ }
21
+ function makeResult(overrides = {}) {
22
+ return {
23
+ output: 'ok',
24
+ durationMs: 42,
25
+ ...overrides,
26
+ };
27
+ }
28
+ /* ========================================================================== */
29
+ /* Hooks */
30
+ /* ========================================================================== */
31
+ describe('NoopToolHookBridge', () => {
32
+ const noop = new NoopToolHookBridge();
33
+ it('beforeToolUse returns undefined (implicitly allowed)', async () => {
34
+ const result = await noop.beforeToolUse(makeContext(), makeDescriptor());
35
+ expect(result).toBeUndefined();
36
+ });
37
+ it('afterToolUse resolves without error', async () => {
38
+ const result = await noop.afterToolUse(makeContext(), makeDescriptor(), makeResult());
39
+ expect(result).toBeUndefined();
40
+ });
41
+ });
42
+ describe('Custom ToolHookBridge', () => {
43
+ it('can deny tool use with a reason', async () => {
44
+ const denyBridge = {
45
+ async beforeToolUse() {
46
+ return { decision: 'deny', reason: 'Forbidden by policy' };
47
+ },
48
+ async afterToolUse() {
49
+ return undefined;
50
+ },
51
+ };
52
+ const result = await denyBridge.beforeToolUse(makeContext(), makeDescriptor());
53
+ expect(result).toBeDefined();
54
+ expect(result.decision).toBe('deny');
55
+ expect(result.reason).toBe('Forbidden by policy');
56
+ });
57
+ it('receives correct context and descriptor in beforeToolUse', async () => {
58
+ let capturedContext;
59
+ let capturedDescriptor;
60
+ const captureBridge = {
61
+ async beforeToolUse(ctx, desc) {
62
+ capturedContext = ctx;
63
+ capturedDescriptor = desc;
64
+ return { decision: 'allow' };
65
+ },
66
+ async afterToolUse() {
67
+ return undefined;
68
+ },
69
+ };
70
+ const ctx = makeContext({ toolName: 'special_tool', caller: 'agent-1', runId: 'run-99' });
71
+ const desc = makeDescriptor({ name: 'special_tool', source: 'mcp', server: 'srv-5' });
72
+ await captureBridge.beforeToolUse(ctx, desc);
73
+ expect(capturedContext).toBeDefined();
74
+ expect(capturedContext.toolName).toBe('special_tool');
75
+ expect(capturedContext.caller).toBe('agent-1');
76
+ expect(capturedContext.runId).toBe('run-99');
77
+ expect(capturedDescriptor).toBeDefined();
78
+ expect(capturedDescriptor.name).toBe('special_tool');
79
+ expect(capturedDescriptor.source).toBe('mcp');
80
+ expect(capturedDescriptor.server).toBe('srv-5');
81
+ });
82
+ it('afterToolUse receives the execution result', async () => {
83
+ let capturedResult;
84
+ const captureBridge = {
85
+ async beforeToolUse() {
86
+ return undefined;
87
+ },
88
+ async afterToolUse(_ctx, _desc, result) {
89
+ capturedResult = result;
90
+ return undefined;
91
+ },
92
+ };
93
+ const res = makeResult({ output: { data: [1, 2, 3] }, durationMs: 150 });
94
+ await captureBridge.afterToolUse(makeContext(), makeDescriptor(), res);
95
+ expect(capturedResult).toBeDefined();
96
+ expect(capturedResult.output).toEqual({ data: [1, 2, 3] });
97
+ expect(capturedResult.durationMs).toBe(150);
98
+ });
99
+ });
100
+ //# sourceMappingURL=hooks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.test.js","sourceRoot":"","sources":["../../src/__tests__/hooks.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mcp-bridge.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-bridge.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/mcp-bridge.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,129 @@
1
+ import { describe, expect, it, beforeEach } from 'vitest';
2
+ import { McpBridge } from '../mcp-bridge.js';
3
+ import { ToolRegistry } from '../registry.js';
4
+ /* -------------------------------------------------------------------------- */
5
+ /* Helpers */
6
+ /* -------------------------------------------------------------------------- */
7
+ function makeServerConfig(overrides = {}) {
8
+ return {
9
+ id: 'mcp-srv-1',
10
+ name: 'Test MCP Server',
11
+ transport: 'stdio',
12
+ command: 'node',
13
+ args: ['server.js'],
14
+ ...overrides,
15
+ };
16
+ }
17
+ function makeMcpTool(overrides = {}) {
18
+ return {
19
+ name: 'mcp_tool',
20
+ description: 'An MCP tool',
21
+ inputSchema: {
22
+ type: 'object',
23
+ properties: {
24
+ query: { type: 'string' },
25
+ },
26
+ },
27
+ ...overrides,
28
+ };
29
+ }
30
+ /* ========================================================================== */
31
+ /* McpBridge */
32
+ /* ========================================================================== */
33
+ describe('McpBridge', () => {
34
+ let registry;
35
+ let bridge;
36
+ beforeEach(() => {
37
+ registry = new ToolRegistry();
38
+ bridge = new McpBridge(registry);
39
+ });
40
+ it('registerServer adds tools to the underlying registry', () => {
41
+ const config = makeServerConfig({ id: 'srv-a', name: 'Server A' });
42
+ const tools = [
43
+ makeMcpTool({ name: 'search' }),
44
+ makeMcpTool({ name: 'fetch' }),
45
+ ];
46
+ bridge.registerServer(config, tools);
47
+ expect(registry.has('search')).toBe(true);
48
+ expect(registry.has('fetch')).toBe(true);
49
+ expect(registry.size).toBe(2);
50
+ });
51
+ it('unregisterServer removes tools from the registry', () => {
52
+ const config = makeServerConfig({ id: 'srv-b' });
53
+ bridge.registerServer(config, [makeMcpTool({ name: 'tool_to_remove' })]);
54
+ expect(registry.has('tool_to_remove')).toBe(true);
55
+ bridge.unregisterServer('srv-b');
56
+ expect(registry.has('tool_to_remove')).toBe(false);
57
+ expect(registry.getServer('srv-b')).toBeUndefined();
58
+ });
59
+ it('listServers returns all registered MCP server configs', () => {
60
+ bridge.registerServer(makeServerConfig({ id: 's1', name: 'S1' }), []);
61
+ bridge.registerServer(makeServerConfig({ id: 's2', name: 'S2' }), []);
62
+ const servers = bridge.listServers();
63
+ expect(servers).toHaveLength(2);
64
+ expect(servers.map((s) => s.id).sort()).toEqual(['s1', 's2']);
65
+ });
66
+ it('listServers returns empty array when nothing is registered', () => {
67
+ expect(bridge.listServers()).toEqual([]);
68
+ });
69
+ it('getServerTools returns the correct tools for a server', () => {
70
+ const config = makeServerConfig({ id: 'query-srv' });
71
+ bridge.registerServer(config, [
72
+ makeMcpTool({ name: 'q_search' }),
73
+ makeMcpTool({ name: 'q_list' }),
74
+ ]);
75
+ const tools = bridge.getServerTools('query-srv');
76
+ expect(tools).toHaveLength(2);
77
+ expect(tools.map((t) => t.name).sort()).toEqual(['q_list', 'q_search']);
78
+ });
79
+ it('getServerTools returns empty array for unknown server', () => {
80
+ expect(bridge.getServerTools('ghost-server')).toEqual([]);
81
+ });
82
+ /* ---------------------------------------------------------------------- */
83
+ /* mcpToolToDescriptor (static) */
84
+ /* ---------------------------------------------------------------------- */
85
+ describe('mcpToolToDescriptor', () => {
86
+ it('sets source to "mcp" and correct server id', () => {
87
+ const mcpTool = makeMcpTool({ name: 'convert_me' });
88
+ const descriptor = McpBridge.mcpToolToDescriptor(mcpTool, 'my-server');
89
+ expect(descriptor.name).toBe('convert_me');
90
+ expect(descriptor.source).toBe('mcp');
91
+ expect(descriptor.server).toBe('my-server');
92
+ });
93
+ it('maps inputSchema to parameters', () => {
94
+ const schema = {
95
+ type: 'object',
96
+ properties: {
97
+ path: { type: 'string' },
98
+ recursive: { type: 'boolean' },
99
+ },
100
+ required: ['path'],
101
+ };
102
+ const mcpTool = makeMcpTool({
103
+ name: 'file_list',
104
+ inputSchema: schema,
105
+ });
106
+ const descriptor = McpBridge.mcpToolToDescriptor(mcpTool, 'fs-server');
107
+ expect(descriptor.parameters).toEqual(schema);
108
+ });
109
+ it('preserves description from MCP tool definition', () => {
110
+ const mcpTool = makeMcpTool({
111
+ name: 'described_tool',
112
+ description: 'Does important things',
113
+ });
114
+ const descriptor = McpBridge.mcpToolToDescriptor(mcpTool, 'srv');
115
+ expect(descriptor.description).toBe('Does important things');
116
+ });
117
+ it('handles MCP tool with no inputSchema', () => {
118
+ const mcpTool = {
119
+ name: 'no_schema',
120
+ description: 'Schemaless',
121
+ };
122
+ const descriptor = McpBridge.mcpToolToDescriptor(mcpTool, 'srv');
123
+ expect(descriptor.name).toBe('no_schema');
124
+ expect(descriptor.parameters).toBeUndefined();
125
+ expect(descriptor.source).toBe('mcp');
126
+ });
127
+ });
128
+ });
129
+ //# sourceMappingURL=mcp-bridge.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-bridge.test.js","sourceRoot":"","sources":["../../src/__tests__/mcp-bridge.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/registry.test.ts"],"names":[],"mappings":""}