kernl 0.1.1

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 (257) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +53 -0
  3. package/LICENSE +201 -0
  4. package/dist/agent.d.ts +43 -0
  5. package/dist/agent.d.ts.map +1 -0
  6. package/dist/agent.js +130 -0
  7. package/dist/context.d.ts +70 -0
  8. package/dist/context.d.ts.map +1 -0
  9. package/dist/context.js +111 -0
  10. package/dist/env.d.ts +45 -0
  11. package/dist/env.d.ts.map +1 -0
  12. package/dist/env.js +31 -0
  13. package/dist/error.d.ts +1 -0
  14. package/dist/error.d.ts.map +1 -0
  15. package/dist/error.js +1 -0
  16. package/dist/guardrail.d.ts +178 -0
  17. package/dist/guardrail.d.ts.map +1 -0
  18. package/dist/guardrail.js +34 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +2 -0
  22. package/dist/kernel.d.ts +7 -0
  23. package/dist/kernel.d.ts.map +1 -0
  24. package/dist/kernel.js +7 -0
  25. package/dist/kernl.d.ts +18 -0
  26. package/dist/kernl.d.ts.map +1 -0
  27. package/dist/kernl.js +16 -0
  28. package/dist/lib/env.d.ts +43 -0
  29. package/dist/lib/env.d.ts.map +1 -0
  30. package/dist/lib/env.js +29 -0
  31. package/dist/lib/error.d.ts +88 -0
  32. package/dist/lib/error.d.ts.map +1 -0
  33. package/dist/lib/error.js +117 -0
  34. package/dist/lib/logger.d.ts +36 -0
  35. package/dist/lib/logger.d.ts.map +1 -0
  36. package/dist/lib/logger.js +43 -0
  37. package/dist/lib/serde/__tests__/codec.test.d.ts +2 -0
  38. package/dist/lib/serde/__tests__/codec.test.d.ts.map +1 -0
  39. package/dist/lib/serde/__tests__/codec.test.js +75 -0
  40. package/dist/lib/serde/codec.d.ts +12 -0
  41. package/dist/lib/serde/codec.d.ts.map +1 -0
  42. package/dist/lib/serde/codec.js +54 -0
  43. package/dist/lib/serde/json.d.ts +8 -0
  44. package/dist/lib/serde/json.d.ts.map +1 -0
  45. package/dist/lib/serde/json.js +13 -0
  46. package/dist/lib/serde/thread.d.ts +1 -0
  47. package/dist/lib/serde/thread.d.ts.map +1 -0
  48. package/dist/lib/serde/thread.js +172 -0
  49. package/dist/lib/serde/tool.d.ts +36 -0
  50. package/dist/lib/serde/tool.d.ts.map +1 -0
  51. package/dist/lib/serde/tool.js +1 -0
  52. package/dist/lib/utils.d.ts +19 -0
  53. package/dist/lib/utils.d.ts.map +1 -0
  54. package/dist/lib/utils.js +41 -0
  55. package/dist/lifecycle.d.ts +133 -0
  56. package/dist/lifecycle.d.ts.map +1 -0
  57. package/dist/lifecycle.js +29 -0
  58. package/dist/logger.d.ts +36 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +43 -0
  61. package/dist/mcp/__tests__/base.test.d.ts +2 -0
  62. package/dist/mcp/__tests__/base.test.d.ts.map +1 -0
  63. package/dist/mcp/__tests__/base.test.js +268 -0
  64. package/dist/mcp/__tests__/fixtures/echo-server.d.ts +3 -0
  65. package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +1 -0
  66. package/dist/mcp/__tests__/fixtures/echo-server.js +92 -0
  67. package/dist/mcp/__tests__/fixtures/math-server.d.ts +3 -0
  68. package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +1 -0
  69. package/dist/mcp/__tests__/fixtures/math-server.js +98 -0
  70. package/dist/mcp/__tests__/fixtures/server.d.ts +3 -0
  71. package/dist/mcp/__tests__/fixtures/server.d.ts.map +1 -0
  72. package/dist/mcp/__tests__/fixtures/server.js +162 -0
  73. package/dist/mcp/__tests__/fixtures/test-server.d.ts +3 -0
  74. package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +1 -0
  75. package/dist/mcp/__tests__/fixtures/test-server.js +163 -0
  76. package/dist/mcp/__tests__/fixtures/utils.d.ts +17 -0
  77. package/dist/mcp/__tests__/fixtures/utils.d.ts.map +1 -0
  78. package/dist/mcp/__tests__/fixtures/utils.js +42 -0
  79. package/dist/mcp/__tests__/integration.test.d.ts +2 -0
  80. package/dist/mcp/__tests__/integration.test.d.ts.map +1 -0
  81. package/dist/mcp/__tests__/integration.test.js +360 -0
  82. package/dist/mcp/__tests__/stdio.test.d.ts +2 -0
  83. package/dist/mcp/__tests__/stdio.test.d.ts.map +1 -0
  84. package/dist/mcp/__tests__/stdio.test.js +180 -0
  85. package/dist/mcp/__tests__/test-utils.d.ts +17 -0
  86. package/dist/mcp/__tests__/test-utils.d.ts.map +1 -0
  87. package/dist/mcp/__tests__/test-utils.js +42 -0
  88. package/dist/mcp/__tests__/utils.test.d.ts +2 -0
  89. package/dist/mcp/__tests__/utils.test.d.ts.map +1 -0
  90. package/dist/mcp/__tests__/utils.test.js +300 -0
  91. package/dist/mcp/base.d.ts +88 -0
  92. package/dist/mcp/base.d.ts.map +1 -0
  93. package/dist/mcp/base.js +68 -0
  94. package/dist/mcp/http.d.ts +34 -0
  95. package/dist/mcp/http.d.ts.map +1 -0
  96. package/dist/mcp/http.js +100 -0
  97. package/dist/mcp/node.d.ts +60 -0
  98. package/dist/mcp/node.d.ts.map +1 -0
  99. package/dist/mcp/node.js +297 -0
  100. package/dist/mcp/sse.d.ts +34 -0
  101. package/dist/mcp/sse.d.ts.map +1 -0
  102. package/dist/mcp/sse.js +97 -0
  103. package/dist/mcp/stdio.d.ts +32 -0
  104. package/dist/mcp/stdio.d.ts.map +1 -0
  105. package/dist/mcp/stdio.js +96 -0
  106. package/dist/mcp/types.d.ts +172 -0
  107. package/dist/mcp/types.d.ts.map +1 -0
  108. package/dist/mcp/types.js +16 -0
  109. package/dist/mcp/utils.d.ts +23 -0
  110. package/dist/mcp/utils.d.ts.map +1 -0
  111. package/dist/mcp/utils.js +44 -0
  112. package/dist/model.d.ts +175 -0
  113. package/dist/model.d.ts.map +1 -0
  114. package/dist/model.js +1 -0
  115. package/dist/providers/ai.d.ts +1 -0
  116. package/dist/providers/ai.d.ts.map +1 -0
  117. package/dist/providers/ai.js +1 -0
  118. package/dist/providers/default.d.ts +16 -0
  119. package/dist/providers/default.d.ts.map +1 -0
  120. package/dist/providers/default.js +17 -0
  121. package/dist/providers/registry.d.ts +1 -0
  122. package/dist/providers/registry.d.ts.map +1 -0
  123. package/dist/providers/registry.js +1 -0
  124. package/dist/sched/scheduler.d.ts +20 -0
  125. package/dist/sched/scheduler.d.ts.map +1 -0
  126. package/dist/sched/scheduler.js +1 -0
  127. package/dist/sched/task.d.ts +92 -0
  128. package/dist/sched/task.d.ts.map +1 -0
  129. package/dist/sched/task.js +102 -0
  130. package/dist/serde/__tests__/codec.test.d.ts +2 -0
  131. package/dist/serde/__tests__/codec.test.d.ts.map +1 -0
  132. package/dist/serde/__tests__/codec.test.js +75 -0
  133. package/dist/serde/codec.d.ts +12 -0
  134. package/dist/serde/codec.d.ts.map +1 -0
  135. package/dist/serde/codec.js +54 -0
  136. package/dist/serde/json.d.ts +8 -0
  137. package/dist/serde/json.d.ts.map +1 -0
  138. package/dist/serde/json.js +13 -0
  139. package/dist/serde/thread.d.ts +687 -0
  140. package/dist/serde/thread.d.ts.map +1 -0
  141. package/dist/serde/thread.js +158 -0
  142. package/dist/serde/tool.d.ts +36 -0
  143. package/dist/serde/tool.d.ts.map +1 -0
  144. package/dist/serde/tool.js +1 -0
  145. package/dist/session.d.ts +1 -0
  146. package/dist/session.d.ts.map +1 -0
  147. package/dist/session.js +1 -0
  148. package/dist/task.d.ts +87 -0
  149. package/dist/task.d.ts.map +1 -0
  150. package/dist/task.js +97 -0
  151. package/dist/thread/__tests__/mock.d.ts +28 -0
  152. package/dist/thread/__tests__/mock.d.ts.map +1 -0
  153. package/dist/thread/__tests__/mock.js +74 -0
  154. package/dist/thread/__tests__/thread.test.d.ts +2 -0
  155. package/dist/thread/__tests__/thread.test.d.ts.map +1 -0
  156. package/dist/thread/__tests__/thread.test.js +1412 -0
  157. package/dist/thread/index.d.ts +2 -0
  158. package/dist/thread/index.d.ts.map +1 -0
  159. package/dist/thread/index.js +1 -0
  160. package/dist/thread/thread.d.ts +66 -0
  161. package/dist/thread/thread.d.ts.map +1 -0
  162. package/dist/thread/thread.js +472 -0
  163. package/dist/thread/utils.d.ts +19 -0
  164. package/dist/thread/utils.d.ts.map +1 -0
  165. package/dist/thread/utils.js +50 -0
  166. package/dist/tool/__tests__/fixtures.d.ts +45 -0
  167. package/dist/tool/__tests__/fixtures.d.ts.map +1 -0
  168. package/dist/tool/__tests__/fixtures.js +97 -0
  169. package/dist/tool/__tests__/tool.test.d.ts +2 -0
  170. package/dist/tool/__tests__/tool.test.d.ts.map +1 -0
  171. package/dist/tool/__tests__/tool.test.js +172 -0
  172. package/dist/tool/__tests__/toolkit.test.d.ts +2 -0
  173. package/dist/tool/__tests__/toolkit.test.d.ts.map +1 -0
  174. package/dist/tool/__tests__/toolkit.test.js +134 -0
  175. package/dist/tool/index.d.ts +4 -0
  176. package/dist/tool/index.d.ts.map +1 -0
  177. package/dist/tool/index.js +2 -0
  178. package/dist/tool/mcp.d.ts +75 -0
  179. package/dist/tool/mcp.d.ts.map +1 -0
  180. package/dist/tool/mcp.js +111 -0
  181. package/dist/tool/tool.d.ts +95 -0
  182. package/dist/tool/tool.d.ts.map +1 -0
  183. package/dist/tool/tool.js +176 -0
  184. package/dist/tool/toolkit.d.ts +121 -0
  185. package/dist/tool/toolkit.d.ts.map +1 -0
  186. package/dist/tool/toolkit.js +180 -0
  187. package/dist/tool/types.d.ts +187 -0
  188. package/dist/tool/types.d.ts.map +1 -0
  189. package/dist/tool/types.js +1 -0
  190. package/dist/tools.d.ts +362 -0
  191. package/dist/tools.d.ts.map +1 -0
  192. package/dist/tools.js +220 -0
  193. package/dist/trace/processor.d.ts +1 -0
  194. package/dist/trace/processor.d.ts.map +1 -0
  195. package/dist/trace/processor.js +1 -0
  196. package/dist/trace/traces.d.ts +1 -0
  197. package/dist/trace/traces.d.ts.map +1 -0
  198. package/dist/trace/traces.js +73 -0
  199. package/dist/trace/utils.d.ts +22 -0
  200. package/dist/trace/utils.d.ts.map +1 -0
  201. package/dist/trace/utils.js +30 -0
  202. package/dist/types/agent.d.ts +91 -0
  203. package/dist/types/agent.d.ts.map +1 -0
  204. package/dist/types/agent.js +1 -0
  205. package/dist/types/proto.d.ts +1551 -0
  206. package/dist/types/proto.d.ts.map +1 -0
  207. package/dist/types/proto.js +531 -0
  208. package/dist/types/thread.d.ts +71 -0
  209. package/dist/types/thread.d.ts.map +1 -0
  210. package/dist/types/thread.js +5 -0
  211. package/dist/usage.d.ts +43 -0
  212. package/dist/usage.d.ts.map +1 -0
  213. package/dist/usage.js +61 -0
  214. package/package.json +52 -0
  215. package/src/agent.ts +203 -0
  216. package/src/context.ts +265 -0
  217. package/src/guardrail.ts +277 -0
  218. package/src/index.ts +3 -0
  219. package/src/kernl.ts +22 -0
  220. package/src/lib/env.ts +36 -0
  221. package/src/lib/error.ts +158 -0
  222. package/src/lib/logger.ts +78 -0
  223. package/src/lib/serde/json.ts +18 -0
  224. package/src/lib/serde/thread.ts +188 -0
  225. package/src/lifecycle.ts +181 -0
  226. package/src/mcp/__tests__/base.test.ts +344 -0
  227. package/src/mcp/__tests__/fixtures/server.ts +179 -0
  228. package/src/mcp/__tests__/fixtures/utils.ts +58 -0
  229. package/src/mcp/__tests__/integration.test.ts +447 -0
  230. package/src/mcp/__tests__/stdio.test.ts +236 -0
  231. package/src/mcp/__tests__/utils.test.ts +360 -0
  232. package/src/mcp/base.ts +162 -0
  233. package/src/mcp/http.ts +147 -0
  234. package/src/mcp/sse.ts +137 -0
  235. package/src/mcp/stdio.ts +136 -0
  236. package/src/mcp/types.ts +202 -0
  237. package/src/mcp/utils.ts +62 -0
  238. package/src/task.ts +119 -0
  239. package/src/thread/__tests__/mock.ts +95 -0
  240. package/src/thread/__tests__/thread.test.ts +1574 -0
  241. package/src/thread/index.ts +1 -0
  242. package/src/thread/thread.ts +611 -0
  243. package/src/thread/utils.ts +67 -0
  244. package/src/tool/__tests__/fixtures.ts +106 -0
  245. package/src/tool/__tests__/tool.test.ts +235 -0
  246. package/src/tool/__tests__/toolkit.test.ts +174 -0
  247. package/src/tool/index.ts +10 -0
  248. package/src/tool/tool.ts +264 -0
  249. package/src/tool/toolkit.ts +234 -0
  250. package/src/tool/types.ts +243 -0
  251. package/src/trace/processor.ts +0 -0
  252. package/src/trace/traces.ts +86 -0
  253. package/src/trace/utils.ts +38 -0
  254. package/src/types/agent.ts +145 -0
  255. package/src/types/thread.ts +86 -0
  256. package/tsconfig.json +13 -0
  257. package/vitest.config.ts +14 -0
@@ -0,0 +1,236 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { MCPServerStdio } from "../stdio";
3
+ import { withMCPServer, createMCPServer } from "./fixtures/utils";
4
+ import path from "path";
5
+
6
+ const TEST_SERVER = path.join(__dirname, "fixtures", "server.ts");
7
+
8
+ describe("MCPServerStdio", () => {
9
+ describe("Connection Lifecycle", () => {
10
+ it("should connect successfully to a server", async () => {
11
+ await withMCPServer(TEST_SERVER, async (server) => {
12
+ expect(server).toBeDefined();
13
+ // If we get here, connection was successful
14
+ });
15
+ });
16
+
17
+ it("should handle connection failure gracefully", async () => {
18
+ const server = createMCPServer("/nonexistent/server.ts");
19
+
20
+ await expect(server.connect()).rejects.toThrow();
21
+ });
22
+
23
+ it("should close connection cleanly", async () => {
24
+ const server = createMCPServer(TEST_SERVER);
25
+ await server.connect();
26
+ await server.close();
27
+
28
+ // Attempting to call a tool after close should fail
29
+ await expect(server.callTool("add", { a: 1, b: 2 })).rejects.toThrow();
30
+ });
31
+
32
+ it("should allow reconnection after close", async () => {
33
+ const server = createMCPServer(TEST_SERVER);
34
+
35
+ await server.connect();
36
+ await server.close();
37
+
38
+ await server.connect();
39
+ const tools = await server.listTools();
40
+ expect(tools.length).toBeGreaterThan(0);
41
+
42
+ await server.close();
43
+ });
44
+ });
45
+
46
+ describe("Tool Discovery", () => {
47
+ it("should list tools after connect", async () => {
48
+ await withMCPServer(TEST_SERVER, async (server) => {
49
+ const tools = await server.listTools();
50
+
51
+ expect(tools).toHaveLength(6);
52
+ expect(tools.map((t: any) => t.name)).toEqual(
53
+ expect.arrayContaining([
54
+ "add",
55
+ "multiply",
56
+ "divide",
57
+ "echo",
58
+ "uppercase",
59
+ "reverse",
60
+ ]),
61
+ );
62
+ });
63
+ });
64
+
65
+ it("should include tool metadata", async () => {
66
+ await withMCPServer(TEST_SERVER, async (server) => {
67
+ const tools = await server.listTools();
68
+ const addTool = tools.find((t: any) => t.name === "add");
69
+
70
+ expect(addTool).toBeDefined();
71
+ expect(addTool!.description).toBe("Add two numbers");
72
+ expect(addTool!.inputSchema).toBeDefined();
73
+ expect(addTool!.inputSchema.properties).toHaveProperty("a");
74
+ expect(addTool!.inputSchema.properties).toHaveProperty("b");
75
+ });
76
+ });
77
+
78
+ it("should handle empty tool list", async () => {
79
+ // We'd need a server with no tools for this, skip for now
80
+ // or create a minimal-server fixture
81
+ });
82
+ });
83
+
84
+ describe("Tool Execution", () => {
85
+ it("should call tool with valid params", async () => {
86
+ await withMCPServer(TEST_SERVER, async (server) => {
87
+ const result = await server.callTool("add", { a: 5, b: 3 });
88
+
89
+ expect(result).toHaveLength(1);
90
+ expect(result[0].type).toBe("text");
91
+ expect(result[0].text).toBe("8");
92
+ });
93
+ });
94
+
95
+ it("should handle multiple different tool calls", async () => {
96
+ await withMCPServer(TEST_SERVER, async (server) => {
97
+ const add = await server.callTool("add", { a: 10, b: 20 });
98
+ expect(add[0].text).toBe("30");
99
+
100
+ const multiply = await server.callTool("multiply", { a: 4, b: 5 });
101
+ expect(multiply[0].text).toBe("20");
102
+
103
+ const divide = await server.callTool("divide", { a: 100, b: 4 });
104
+ expect(divide[0].text).toBe("25");
105
+ });
106
+ });
107
+
108
+ it("should handle tool execution error", async () => {
109
+ await withMCPServer(TEST_SERVER, async (server) => {
110
+ // Division by zero should throw
111
+ await expect(
112
+ server.callTool("divide", { a: 10, b: 0 }),
113
+ ).rejects.toThrow("Division by zero");
114
+ });
115
+ });
116
+
117
+ it("should handle tool not found", async () => {
118
+ await withMCPServer(TEST_SERVER, async (server) => {
119
+ await expect(
120
+ server.callTool("nonexistent", { foo: "bar" }),
121
+ ).rejects.toThrow();
122
+ });
123
+ });
124
+
125
+ it("should work with string manipulation tools", async () => {
126
+ await withMCPServer(TEST_SERVER, async (server) => {
127
+ const echo = await server.callTool("echo", { text: "hello" });
128
+ expect(echo[0].text).toBe("hello");
129
+
130
+ const upper = await server.callTool("uppercase", { text: "hello" });
131
+ expect(upper[0].text).toBe("HELLO");
132
+
133
+ const reverse = await server.callTool("reverse", { text: "hello" });
134
+ expect(reverse[0].text).toBe("olleh");
135
+ });
136
+ });
137
+ });
138
+
139
+ describe("Caching", () => {
140
+ it("should not cache tools list by default", async () => {
141
+ const server = createMCPServer(TEST_SERVER, {
142
+ cacheToolsList: false,
143
+ });
144
+
145
+ await server.connect();
146
+
147
+ const tools1 = await server.listTools();
148
+ const tools2 = await server.listTools();
149
+
150
+ // Both should succeed (no cache = fresh fetch each time)
151
+ expect(tools1).toHaveLength(6);
152
+ expect(tools2).toHaveLength(6);
153
+
154
+ await server.close();
155
+ });
156
+
157
+ it("should cache tools list when enabled", async () => {
158
+ const server = createMCPServer(TEST_SERVER, {
159
+ cacheToolsList: true,
160
+ });
161
+
162
+ await server.connect();
163
+
164
+ const tools1 = await server.listTools();
165
+ const tools2 = await server.listTools();
166
+
167
+ // Should get same results from cache
168
+ expect(tools1).toHaveLength(6);
169
+ expect(tools2).toHaveLength(6);
170
+ expect(tools1).toEqual(tools2);
171
+
172
+ await server.close();
173
+ });
174
+
175
+ it("should invalidate cache when requested", async () => {
176
+ const server = createMCPServer(TEST_SERVER, {
177
+ cacheToolsList: true,
178
+ });
179
+
180
+ await server.connect();
181
+
182
+ const tools1 = await server.listTools();
183
+ expect(tools1).toHaveLength(6);
184
+
185
+ await server.invalidateCache();
186
+
187
+ const tools2 = await server.listTools();
188
+ expect(tools2).toHaveLength(6);
189
+
190
+ await server.close();
191
+ });
192
+ });
193
+
194
+ describe("Constructor options", () => {
195
+ it("should accept command and args", async () => {
196
+ const server = new MCPServerStdio({
197
+ id: "test",
198
+ command: "npx",
199
+ args: ["tsx", TEST_SERVER],
200
+ });
201
+
202
+ await server.connect();
203
+ const tools = await server.listTools();
204
+ expect(tools.length).toBeGreaterThan(0);
205
+ await server.close();
206
+ });
207
+
208
+ it("should use custom id", async () => {
209
+ const server = createMCPServer(TEST_SERVER, { id: "my-test-server" });
210
+ expect(server.id).toBe("my-test-server");
211
+ });
212
+
213
+ it("should generate default id from command", async () => {
214
+ const server = new MCPServerStdio({
215
+ command: "npx",
216
+ args: ["tsx", TEST_SERVER],
217
+ });
218
+
219
+ expect(server.id).toContain("npx");
220
+ });
221
+ });
222
+
223
+ describe("Error handling", () => {
224
+ it("should throw error when calling tool before connect", async () => {
225
+ const server = createMCPServer(TEST_SERVER);
226
+
227
+ await expect(server.callTool("add", { a: 1, b: 2 })).rejects.toThrow();
228
+ });
229
+
230
+ it("should throw error when listing tools before connect", async () => {
231
+ const server = createMCPServer(TEST_SERVER);
232
+
233
+ await expect(server.listTools()).rejects.toThrow();
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,360 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { mcpToFunctionTool, createMCPToolStaticFilter } from "../utils";
3
+ import type { MCPServer } from "../base";
4
+ import type { MCPTool, MCPToolFilterContext } from "../types";
5
+ import { Context } from "@/context";
6
+ import { Agent } from "@/agent";
7
+
8
+ describe("mcpToFunctionTool", () => {
9
+ // Create a mock MCP server
10
+ const createMockServer = (): MCPServer => ({
11
+ id: "mock-server",
12
+ cacheToolsList: false,
13
+ toolFilter: async () => true,
14
+ connect: vi.fn(),
15
+ close: vi.fn(),
16
+ listTools: vi.fn(),
17
+ callTool: vi.fn(),
18
+ invalidateCache: vi.fn(),
19
+ });
20
+
21
+ it("should convert MCPTool to FunctionTool correctly", () => {
22
+ const server = createMockServer();
23
+ const mcpTool: MCPTool = {
24
+ name: "test_tool",
25
+ description: "A test tool",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ arg1: { type: "string" },
30
+ },
31
+ },
32
+ };
33
+
34
+ const functionTool = mcpToFunctionTool(server, mcpTool);
35
+
36
+ expect(functionTool.id).toBe("test_tool");
37
+ expect(functionTool.name).toBe("test_tool");
38
+ expect(functionTool.description).toBe("A test tool");
39
+ expect(functionTool.parameters).toBeDefined();
40
+ // execute is private, but we can verify the tool has an invoke method
41
+ expect(functionTool.invoke).toBeDefined();
42
+ });
43
+
44
+ it("should map tool metadata (name, description, inputSchema)", () => {
45
+ const server = createMockServer();
46
+ const mcpTool: MCPTool = {
47
+ name: "calculator",
48
+ description: "Performs calculations",
49
+ inputSchema: {
50
+ type: "object",
51
+ properties: {
52
+ operation: { type: "string" },
53
+ values: { type: "array" },
54
+ },
55
+ },
56
+ };
57
+
58
+ const functionTool = mcpToFunctionTool(server, mcpTool);
59
+
60
+ expect(functionTool.id).toBe("calculator");
61
+ expect(functionTool.name).toBe("calculator");
62
+ expect(functionTool.description).toBe("Performs calculations");
63
+ expect(functionTool.parameters).toBeDefined();
64
+ });
65
+
66
+ it("should handle tools without inputSchema (undefined parameters)", () => {
67
+ const server = createMockServer();
68
+ // In practice, MCP SDK tools require inputSchema, but our function handles
69
+ // the case where it might not be present. We use 'as any' to test this edge case.
70
+ const mcpTool = {
71
+ name: "no_params",
72
+ description: "Tool without parameters",
73
+ } as any as MCPTool;
74
+
75
+ const functionTool = mcpToFunctionTool(server, mcpTool);
76
+
77
+ expect(functionTool.id).toBe("no_params");
78
+ expect(functionTool.parameters).toBeUndefined();
79
+ });
80
+
81
+ it("should invoke server.callTool with correct params", async () => {
82
+ const server = createMockServer();
83
+ server.callTool = vi
84
+ .fn()
85
+ .mockResolvedValue([{ type: "text", text: "result" }]);
86
+
87
+ const mcpTool: MCPTool = {
88
+ name: "test_tool",
89
+ inputSchema: {
90
+ type: "object",
91
+ properties: {
92
+ arg1: { type: "string" },
93
+ },
94
+ },
95
+ };
96
+
97
+ const functionTool = mcpToFunctionTool(server, mcpTool);
98
+ const ctx = new Context({});
99
+ const input = { arg1: "value1" };
100
+
101
+ await functionTool.invoke(ctx, JSON.stringify(input));
102
+
103
+ expect(server.callTool).toHaveBeenCalledWith("test_tool", input);
104
+ expect(server.callTool).toHaveBeenCalledTimes(1);
105
+ });
106
+
107
+ it("should return single content item correctly", async () => {
108
+ const server = createMockServer();
109
+ server.callTool = vi
110
+ .fn()
111
+ .mockResolvedValue([{ type: "text", text: "single result" }]);
112
+
113
+ const mcpTool: MCPTool = {
114
+ name: "test_tool",
115
+ inputSchema: {
116
+ type: "object",
117
+ properties: {},
118
+ },
119
+ };
120
+
121
+ const functionTool = mcpToFunctionTool(server, mcpTool);
122
+ const ctx = new Context({});
123
+ const toolResult = await functionTool.invoke(ctx, JSON.stringify({}));
124
+
125
+ expect(toolResult.state).toBe("completed");
126
+ expect(toolResult.result).toEqual({ type: "text", text: "single result" });
127
+ });
128
+
129
+ it("should return multiple content items as array", async () => {
130
+ const server = createMockServer();
131
+ const multipleItems = [
132
+ { type: "text", text: "result 1" },
133
+ { type: "text", text: "result 2" },
134
+ { type: "text", text: "result 3" },
135
+ ];
136
+ server.callTool = vi.fn().mockResolvedValue(multipleItems);
137
+
138
+ const mcpTool: MCPTool = {
139
+ name: "test_tool",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: {},
143
+ },
144
+ };
145
+
146
+ const functionTool = mcpToFunctionTool(server, mcpTool);
147
+ const ctx = new Context({});
148
+ const toolResult = await functionTool.invoke(ctx, JSON.stringify({}));
149
+
150
+ expect(toolResult.state).toBe("completed");
151
+ expect(toolResult.result).toEqual(multipleItems);
152
+ expect(Array.isArray(toolResult.result)).toBe(true);
153
+ expect(toolResult.result).toHaveLength(3);
154
+ });
155
+
156
+ it("should preserve tool name in closure", async () => {
157
+ const server = createMockServer();
158
+ server.callTool = vi
159
+ .fn()
160
+ .mockResolvedValue([{ type: "text", text: "result" }]);
161
+
162
+ const tool1: MCPTool = {
163
+ name: "tool1",
164
+ inputSchema: { type: "object", properties: {} },
165
+ };
166
+ const tool2: MCPTool = {
167
+ name: "tool2",
168
+ inputSchema: { type: "object", properties: {} },
169
+ };
170
+
171
+ const functionTool1 = mcpToFunctionTool(server, tool1);
172
+ const functionTool2 = mcpToFunctionTool(server, tool2);
173
+
174
+ const ctx = new Context({});
175
+
176
+ await functionTool1.invoke(ctx, JSON.stringify({}));
177
+ expect(server.callTool).toHaveBeenCalledWith("tool1", {});
178
+
179
+ await functionTool2.invoke(ctx, JSON.stringify({}));
180
+ expect(server.callTool).toHaveBeenCalledWith("tool2", {});
181
+ });
182
+
183
+ it("should use correct parameter order (context, params)", async () => {
184
+ const server = createMockServer();
185
+ server.callTool = vi
186
+ .fn()
187
+ .mockResolvedValue([{ type: "text", text: "result" }]);
188
+
189
+ const mcpTool: MCPTool = {
190
+ name: "test_tool",
191
+ inputSchema: {
192
+ type: "object",
193
+ properties: {
194
+ foo: { type: "string" },
195
+ },
196
+ },
197
+ };
198
+
199
+ const functionTool = mcpToFunctionTool(server, mcpTool);
200
+
201
+ // Invoke takes (context, params as JSON string) in that order
202
+ const ctx = new Context({});
203
+ const params = { foo: "bar" };
204
+ await functionTool.invoke(ctx, JSON.stringify(params));
205
+
206
+ // Verify the tool was called with the params (not the context)
207
+ expect(server.callTool).toHaveBeenCalledWith("test_tool", params);
208
+ });
209
+ });
210
+
211
+ describe("createMCPToolStaticFilter", () => {
212
+ // Helper to create mock filter context
213
+ const createMockFilterContext = (): MCPToolFilterContext => ({
214
+ context: new Context({}),
215
+ agent: {} as Agent,
216
+ serverId: "test-server",
217
+ });
218
+
219
+ it("should return undefined when no options provided", () => {
220
+ const filter = createMCPToolStaticFilter();
221
+ expect(filter).toBeUndefined();
222
+ });
223
+
224
+ it("should return undefined when options is empty object", () => {
225
+ const filter = createMCPToolStaticFilter({});
226
+ expect(filter).toBeUndefined();
227
+ });
228
+
229
+ it("should create allowlist filter correctly", async () => {
230
+ const filter = createMCPToolStaticFilter({
231
+ allowed: ["tool1", "tool2"],
232
+ });
233
+
234
+ expect(filter).toBeDefined();
235
+
236
+ const mockContext = createMockFilterContext();
237
+ const tool1: MCPTool = {
238
+ name: "tool1",
239
+ inputSchema: { type: "object", properties: {} },
240
+ };
241
+ const tool2: MCPTool = {
242
+ name: "tool2",
243
+ inputSchema: { type: "object", properties: {} },
244
+ };
245
+ const tool3: MCPTool = {
246
+ name: "tool3",
247
+ inputSchema: { type: "object", properties: {} },
248
+ };
249
+
250
+ expect(await filter!(mockContext, tool1)).toBe(true);
251
+ expect(await filter!(mockContext, tool2)).toBe(true);
252
+ expect(await filter!(mockContext, tool3)).toBe(false);
253
+ });
254
+
255
+ it("should create blocklist filter correctly", async () => {
256
+ const filter = createMCPToolStaticFilter({
257
+ blocked: ["dangerous_tool", "risky_tool"],
258
+ });
259
+
260
+ expect(filter).toBeDefined();
261
+
262
+ const mockContext = createMockFilterContext();
263
+ const safeTool: MCPTool = {
264
+ name: "safe_tool",
265
+ inputSchema: { type: "object", properties: {} },
266
+ };
267
+ const dangerousTool: MCPTool = {
268
+ name: "dangerous_tool",
269
+ inputSchema: { type: "object", properties: {} },
270
+ };
271
+ const riskyTool: MCPTool = {
272
+ name: "risky_tool",
273
+ inputSchema: { type: "object", properties: {} },
274
+ };
275
+
276
+ expect(await filter!(mockContext, safeTool)).toBe(true);
277
+ expect(await filter!(mockContext, dangerousTool)).toBe(false);
278
+ expect(await filter!(mockContext, riskyTool)).toBe(false);
279
+ });
280
+
281
+ it("should combine allowlist and blocklist", async () => {
282
+ const filter = createMCPToolStaticFilter({
283
+ allowed: ["tool1", "tool2", "tool3"],
284
+ blocked: ["tool2"],
285
+ });
286
+
287
+ expect(filter).toBeDefined();
288
+
289
+ const mockContext = createMockFilterContext();
290
+ const tool1: MCPTool = {
291
+ name: "tool1",
292
+ inputSchema: { type: "object", properties: {} },
293
+ };
294
+ const tool2: MCPTool = {
295
+ name: "tool2",
296
+ inputSchema: { type: "object", properties: {} },
297
+ };
298
+ const tool3: MCPTool = {
299
+ name: "tool3",
300
+ inputSchema: { type: "object", properties: {} },
301
+ };
302
+ const tool4: MCPTool = {
303
+ name: "tool4",
304
+ inputSchema: { type: "object", properties: {} },
305
+ };
306
+
307
+ expect(await filter!(mockContext, tool1)).toBe(true);
308
+ expect(await filter!(mockContext, tool2)).toBe(false); // In allowed but blocked
309
+ expect(await filter!(mockContext, tool3)).toBe(true);
310
+ expect(await filter!(mockContext, tool4)).toBe(false); // Not in allowed list
311
+ });
312
+
313
+ it("should default to allow-all when lists empty", async () => {
314
+ const filter = createMCPToolStaticFilter({
315
+ allowed: [],
316
+ blocked: [],
317
+ });
318
+
319
+ expect(filter).toBeDefined();
320
+
321
+ const mockContext = createMockFilterContext();
322
+ const tool1: MCPTool = {
323
+ name: "tool1",
324
+ inputSchema: { type: "object", properties: {} },
325
+ };
326
+ const tool2: MCPTool = {
327
+ name: "tool2",
328
+ inputSchema: { type: "object", properties: {} },
329
+ };
330
+
331
+ expect(await filter!(mockContext, tool1)).toBe(true);
332
+ expect(await filter!(mockContext, tool2)).toBe(true);
333
+ });
334
+
335
+ it("should filter based on tool.name property", async () => {
336
+ const filter = createMCPToolStaticFilter({
337
+ allowed: ["exact_name"],
338
+ });
339
+
340
+ expect(filter).toBeDefined();
341
+
342
+ const mockContext = createMockFilterContext();
343
+ const exactMatch: MCPTool = {
344
+ name: "exact_name",
345
+ inputSchema: { type: "object", properties: {} },
346
+ };
347
+ const partialMatch: MCPTool = {
348
+ name: "exact_name_with_suffix",
349
+ inputSchema: { type: "object", properties: {} },
350
+ };
351
+ const noMatch: MCPTool = {
352
+ name: "different_name",
353
+ inputSchema: { type: "object", properties: {} },
354
+ };
355
+
356
+ expect(await filter!(mockContext, exactMatch)).toBe(true);
357
+ expect(await filter!(mockContext, partialMatch)).toBe(false);
358
+ expect(await filter!(mockContext, noMatch)).toBe(false);
359
+ });
360
+ });