@decocms/mesh-sdk 1.2.1 → 1.2.3
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.
- package/README.md +10 -10
- package/package.json +7 -4
- package/src/context/index.ts +5 -1
- package/src/context/project-context.tsx +68 -29
- package/src/hooks/index.ts +10 -0
- package/src/hooks/use-collections.ts +179 -63
- package/src/hooks/use-connection.ts +50 -4
- package/src/hooks/use-mcp-client.ts +81 -11
- package/src/hooks/use-mcp-prompts.ts +16 -6
- package/src/hooks/use-mcp-resources.ts +15 -5
- package/src/hooks/use-virtual-mcp.ts +64 -0
- package/src/index.ts +119 -4
- package/src/lib/bridge-transport.test.ts +368 -0
- package/src/lib/bridge-transport.ts +6 -0
- package/src/lib/constants.test.ts +26 -0
- package/src/lib/constants.ts +193 -36
- package/src/lib/default-model.ts +281 -0
- package/src/lib/mcp-oauth.ts +139 -17
- package/src/lib/query-keys.ts +20 -4
- package/src/lib/server-client-bridge.ts +4 -0
- package/src/lib/usage.test.ts +229 -0
- package/src/lib/usage.ts +187 -0
- package/src/plugins/index.ts +15 -0
- package/src/plugins/plugin-context-provider.tsx +99 -0
- package/src/plugins/topbar-portal.tsx +118 -0
- package/src/types/ai-providers.ts +86 -0
- package/src/types/connection.ts +43 -20
- package/src/types/decopilot-events.test.ts +78 -0
- package/src/types/decopilot-events.ts +171 -0
- package/src/types/index.ts +48 -1
- package/src/types/virtual-mcp.test.ts +202 -0
- package/src/types/virtual-mcp.ts +514 -109
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import {
|
|
6
|
+
createBridgeTransportPair,
|
|
7
|
+
BridgeClientTransport,
|
|
8
|
+
BridgeServerTransport,
|
|
9
|
+
} from "./bridge-transport";
|
|
10
|
+
|
|
11
|
+
/** Poll until `condition` returns true, checking every 5ms up to `timeoutMs`. */
|
|
12
|
+
async function waitFor(
|
|
13
|
+
condition: () => boolean,
|
|
14
|
+
timeoutMs = 200,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
while (!condition()) {
|
|
18
|
+
if (Date.now() - start > timeoutMs) return;
|
|
19
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("BridgeTransport", () => {
|
|
24
|
+
describe("createBridgeTransportPair", () => {
|
|
25
|
+
it("should create a pair of transports", () => {
|
|
26
|
+
const { client, server, channel } = createBridgeTransportPair();
|
|
27
|
+
|
|
28
|
+
expect(client).toBeInstanceOf(BridgeClientTransport);
|
|
29
|
+
expect(server).toBeInstanceOf(BridgeServerTransport);
|
|
30
|
+
expect(channel).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should create transports with microtask scheduling", () => {
|
|
34
|
+
const { client, server } = createBridgeTransportPair();
|
|
35
|
+
expect(client).toBeDefined();
|
|
36
|
+
expect(server).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("message delivery", () => {
|
|
41
|
+
it("should deliver messages in order client->server", async () => {
|
|
42
|
+
const { client, server } = createBridgeTransportPair();
|
|
43
|
+
|
|
44
|
+
const receivedMessages: JSONRPCMessage[] = [];
|
|
45
|
+
|
|
46
|
+
await server.start();
|
|
47
|
+
server.onmessage = (message) => {
|
|
48
|
+
receivedMessages.push(message);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
await client.start();
|
|
52
|
+
|
|
53
|
+
const msg1: JSONRPCMessage = {
|
|
54
|
+
jsonrpc: "2.0",
|
|
55
|
+
id: 1,
|
|
56
|
+
method: "test",
|
|
57
|
+
params: { foo: "bar" },
|
|
58
|
+
};
|
|
59
|
+
const msg2: JSONRPCMessage = {
|
|
60
|
+
jsonrpc: "2.0",
|
|
61
|
+
id: 2,
|
|
62
|
+
method: "test2",
|
|
63
|
+
params: { baz: "qux" },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
await client.send(msg1);
|
|
67
|
+
await client.send(msg2);
|
|
68
|
+
|
|
69
|
+
// Wait for microtask to process (or sync mode processes immediately)
|
|
70
|
+
await new Promise((resolve) => queueMicrotask(resolve));
|
|
71
|
+
|
|
72
|
+
expect(receivedMessages).toHaveLength(2);
|
|
73
|
+
expect(receivedMessages[0]).toEqual(msg1);
|
|
74
|
+
expect(receivedMessages[1]).toEqual(msg2);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should deliver messages in order server->client", async () => {
|
|
78
|
+
const { client, server } = createBridgeTransportPair();
|
|
79
|
+
|
|
80
|
+
const receivedMessages: JSONRPCMessage[] = [];
|
|
81
|
+
|
|
82
|
+
await client.start();
|
|
83
|
+
client.onmessage = (message) => {
|
|
84
|
+
receivedMessages.push(message);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
await server.start();
|
|
88
|
+
|
|
89
|
+
const msg1: JSONRPCMessage = {
|
|
90
|
+
jsonrpc: "2.0",
|
|
91
|
+
id: 1,
|
|
92
|
+
method: "test",
|
|
93
|
+
params: { foo: "bar" },
|
|
94
|
+
};
|
|
95
|
+
const msg2: JSONRPCMessage = {
|
|
96
|
+
jsonrpc: "2.0",
|
|
97
|
+
id: 2,
|
|
98
|
+
method: "test2",
|
|
99
|
+
params: { baz: "qux" },
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await server.send(msg1);
|
|
103
|
+
await server.send(msg2);
|
|
104
|
+
|
|
105
|
+
// Wait for microtask to process (or sync mode processes immediately)
|
|
106
|
+
await new Promise((resolve) => queueMicrotask(resolve));
|
|
107
|
+
|
|
108
|
+
expect(receivedMessages).toHaveLength(2);
|
|
109
|
+
expect(receivedMessages[0]).toEqual(msg1);
|
|
110
|
+
expect(receivedMessages[1]).toEqual(msg2);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should batch multiple messages in a single microtask", async () => {
|
|
114
|
+
const { client, server } = createBridgeTransportPair();
|
|
115
|
+
|
|
116
|
+
const receivedMessages: JSONRPCMessage[] = [];
|
|
117
|
+
let flushCount = 0;
|
|
118
|
+
|
|
119
|
+
await server.start();
|
|
120
|
+
server.onmessage = (message) => {
|
|
121
|
+
receivedMessages.push(message);
|
|
122
|
+
flushCount++;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
await client.start();
|
|
126
|
+
|
|
127
|
+
// Send multiple messages synchronously
|
|
128
|
+
await client.send({ jsonrpc: "2.0", id: 1, method: "test1" });
|
|
129
|
+
await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
|
|
130
|
+
await client.send({ jsonrpc: "2.0", id: 3, method: "test3" });
|
|
131
|
+
|
|
132
|
+
// Wait for microtask to process
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
134
|
+
|
|
135
|
+
expect(receivedMessages).toHaveLength(3);
|
|
136
|
+
// All messages should be delivered in a single flush
|
|
137
|
+
expect(flushCount).toBeGreaterThanOrEqual(1);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("start()", () => {
|
|
142
|
+
it("should allow starting client transport", async () => {
|
|
143
|
+
const { client } = createBridgeTransportPair();
|
|
144
|
+
await expect(client.start()).resolves.toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should allow starting server transport", async () => {
|
|
148
|
+
const { server } = createBridgeTransportPair();
|
|
149
|
+
await expect(server.start()).resolves.toBeUndefined();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should throw if started twice", async () => {
|
|
153
|
+
const { client } = createBridgeTransportPair();
|
|
154
|
+
await client.start();
|
|
155
|
+
await expect(client.start()).rejects.toThrow("already started");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("close()", () => {
|
|
160
|
+
it("should close client transport and notify server", async () => {
|
|
161
|
+
const { client, server } = createBridgeTransportPair();
|
|
162
|
+
|
|
163
|
+
await client.start();
|
|
164
|
+
await server.start();
|
|
165
|
+
|
|
166
|
+
let serverClosed = false;
|
|
167
|
+
server.onclose = () => {
|
|
168
|
+
serverClosed = true;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
await client.close();
|
|
172
|
+
|
|
173
|
+
expect(serverClosed).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should close server transport and notify client", async () => {
|
|
177
|
+
const { client, server } = createBridgeTransportPair();
|
|
178
|
+
|
|
179
|
+
await client.start();
|
|
180
|
+
await server.start();
|
|
181
|
+
|
|
182
|
+
let clientClosed = false;
|
|
183
|
+
client.onclose = () => {
|
|
184
|
+
clientClosed = true;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
await server.close();
|
|
188
|
+
|
|
189
|
+
expect(clientClosed).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should prevent sending messages after close", async () => {
|
|
193
|
+
const { client, server } = createBridgeTransportPair();
|
|
194
|
+
|
|
195
|
+
await client.start();
|
|
196
|
+
await server.start();
|
|
197
|
+
|
|
198
|
+
const receivedMessages: JSONRPCMessage[] = [];
|
|
199
|
+
server.onmessage = (msg) => receivedMessages.push(msg);
|
|
200
|
+
|
|
201
|
+
// Send first message and wait for delivery
|
|
202
|
+
await client.send({ jsonrpc: "2.0", id: 1, method: "test" });
|
|
203
|
+
await waitFor(() => receivedMessages.length >= 1);
|
|
204
|
+
|
|
205
|
+
expect(receivedMessages.length).toBeGreaterThanOrEqual(1);
|
|
206
|
+
const initialCount = receivedMessages.length;
|
|
207
|
+
|
|
208
|
+
// Close client
|
|
209
|
+
await client.close();
|
|
210
|
+
|
|
211
|
+
// Try to send after close (should be silent no-op)
|
|
212
|
+
await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
|
|
213
|
+
|
|
214
|
+
// Wait a bit to ensure no delivery
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
216
|
+
|
|
217
|
+
// Should not receive new messages after close
|
|
218
|
+
expect(receivedMessages.length).toBe(initialCount);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should fire onclose exactly once", async () => {
|
|
222
|
+
const { client } = createBridgeTransportPair();
|
|
223
|
+
|
|
224
|
+
await client.start();
|
|
225
|
+
|
|
226
|
+
let closeCount = 0;
|
|
227
|
+
client.onclose = () => {
|
|
228
|
+
closeCount++;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
await client.close();
|
|
232
|
+
await client.close(); // Try closing again
|
|
233
|
+
|
|
234
|
+
expect(closeCount).toBe(1);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe("error handling", () => {
|
|
239
|
+
it("should catch and forward errors from onmessage handler", async () => {
|
|
240
|
+
const { client, server } = createBridgeTransportPair();
|
|
241
|
+
|
|
242
|
+
await server.start();
|
|
243
|
+
await client.start();
|
|
244
|
+
|
|
245
|
+
const errors: Error[] = [];
|
|
246
|
+
server.onerror = (error) => {
|
|
247
|
+
errors.push(error);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
server.onmessage = () => {
|
|
251
|
+
throw new Error("Test error");
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await client.send({ jsonrpc: "2.0", id: 1, method: "test" });
|
|
255
|
+
|
|
256
|
+
// Wait for microtask to process and error to be forwarded
|
|
257
|
+
await waitFor(() => errors.length >= 1);
|
|
258
|
+
|
|
259
|
+
expect(errors).toHaveLength(1);
|
|
260
|
+
expect(errors[0].message).toBe("Test error");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("should continue processing messages after error", async () => {
|
|
264
|
+
const { client, server } = createBridgeTransportPair();
|
|
265
|
+
|
|
266
|
+
await server.start();
|
|
267
|
+
await client.start();
|
|
268
|
+
|
|
269
|
+
const receivedMessages: JSONRPCMessage[] = [];
|
|
270
|
+
const errors: Error[] = [];
|
|
271
|
+
|
|
272
|
+
let callCount = 0;
|
|
273
|
+
server.onmessage = (msg) => {
|
|
274
|
+
callCount++;
|
|
275
|
+
if (callCount === 1) {
|
|
276
|
+
throw new Error("First message error");
|
|
277
|
+
}
|
|
278
|
+
receivedMessages.push(msg);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
server.onerror = (error) => {
|
|
282
|
+
errors.push(error);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
await client.send({ jsonrpc: "2.0", id: 1, method: "test1" });
|
|
286
|
+
await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
|
|
287
|
+
|
|
288
|
+
// Wait for microtask to process
|
|
289
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
290
|
+
|
|
291
|
+
expect(errors).toHaveLength(1);
|
|
292
|
+
expect(receivedMessages).toHaveLength(1);
|
|
293
|
+
expect(receivedMessages[0].id).toBe(2);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("integration with MCP SDK", () => {
|
|
298
|
+
it("should work with MCP Client and Server", async () => {
|
|
299
|
+
const { client: clientTransport, server: serverTransport } =
|
|
300
|
+
createBridgeTransportPair();
|
|
301
|
+
|
|
302
|
+
const client = new Client({ name: "test-client", version: "1.0.0" });
|
|
303
|
+
const server = new Server({ name: "test-server", version: "1.0.0" });
|
|
304
|
+
|
|
305
|
+
// Connect server first
|
|
306
|
+
await server.connect(serverTransport);
|
|
307
|
+
|
|
308
|
+
// Connect client
|
|
309
|
+
await client.connect(clientTransport);
|
|
310
|
+
|
|
311
|
+
// Verify connection is established
|
|
312
|
+
expect(clientTransport.started).toBe(true);
|
|
313
|
+
expect(serverTransport.started).toBe(true);
|
|
314
|
+
|
|
315
|
+
// Clean up
|
|
316
|
+
await client.close();
|
|
317
|
+
await server.close();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should handle initialize handshake", async () => {
|
|
321
|
+
const { client: clientTransport, server: serverTransport } =
|
|
322
|
+
createBridgeTransportPair();
|
|
323
|
+
|
|
324
|
+
const client = new Client({ name: "test-client", version: "1.0.0" });
|
|
325
|
+
const server = new Server({ name: "test-server", version: "1.0.0" });
|
|
326
|
+
|
|
327
|
+
await server.connect(serverTransport);
|
|
328
|
+
await client.connect(clientTransport);
|
|
329
|
+
|
|
330
|
+
// Wait for initialization
|
|
331
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
332
|
+
|
|
333
|
+
// Verify connection is established (client should have received initialize response)
|
|
334
|
+
expect(clientTransport.started).toBe(true);
|
|
335
|
+
expect(serverTransport.started).toBe(true);
|
|
336
|
+
|
|
337
|
+
await client.close();
|
|
338
|
+
await server.close();
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe("send() before start()", () => {
|
|
343
|
+
it("should allow sending messages before start (messages may be queued)", async () => {
|
|
344
|
+
const { client, server } = createBridgeTransportPair();
|
|
345
|
+
|
|
346
|
+
// Send message before starting - should not throw
|
|
347
|
+
await expect(
|
|
348
|
+
client.send({ jsonrpc: "2.0", id: 1, method: "test1" }),
|
|
349
|
+
).resolves.toBeUndefined();
|
|
350
|
+
|
|
351
|
+
// Set handler and start
|
|
352
|
+
const receivedMessages: JSONRPCMessage[] = [];
|
|
353
|
+
server.onmessage = (msg) => receivedMessages.push(msg);
|
|
354
|
+
await server.start();
|
|
355
|
+
await client.start();
|
|
356
|
+
|
|
357
|
+
// Send another message after start to verify normal operation
|
|
358
|
+
await client.send({ jsonrpc: "2.0", id: 2, method: "test2" });
|
|
359
|
+
|
|
360
|
+
// Wait for delivery
|
|
361
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
362
|
+
|
|
363
|
+
// At least the message sent after start should be delivered
|
|
364
|
+
// (messages sent before start may or may not be delivered depending on timing)
|
|
365
|
+
expect(receivedMessages.length).toBeGreaterThanOrEqual(1);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { StudioPackAgentId, isStudioPackAgent } from "./constants";
|
|
3
|
+
|
|
4
|
+
describe("StudioPackAgentId", () => {
|
|
5
|
+
test("generates the Store Manager id with the org suffix", () => {
|
|
6
|
+
expect(StudioPackAgentId.STORE_MANAGER("org_xyz")).toBe(
|
|
7
|
+
"studio-store-manager_org_xyz",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("isStudioPackAgent", () => {
|
|
13
|
+
test("recognises every studio-pack manager id, including the new store-manager", () => {
|
|
14
|
+
expect(isStudioPackAgent("studio-agent-manager_org_xyz")).toBe(true);
|
|
15
|
+
expect(isStudioPackAgent("studio-automation-manager_org_xyz")).toBe(true);
|
|
16
|
+
expect(isStudioPackAgent("studio-connection-manager_org_xyz")).toBe(true);
|
|
17
|
+
expect(isStudioPackAgent("studio-store-manager_org_xyz")).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("rejects unrelated ids", () => {
|
|
21
|
+
expect(isStudioPackAgent(null)).toBe(false);
|
|
22
|
+
expect(isStudioPackAgent(undefined)).toBe(false);
|
|
23
|
+
expect(isStudioPackAgent("vir_abc")).toBe(false);
|
|
24
|
+
expect(isStudioPackAgent("decopilot_org_xyz")).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|