Skip to content

Commit 9ac244b

Browse files
committed
wip
1 parent 02c7542 commit 9ac244b

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import { StreamableHttpRunner } from "../../../src/transports/streamableHttp.js";
2+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
4+
import { describe, expect, it, beforeAll, afterAll } from "vitest";
5+
import { config } from "../../../src/common/config.js";
6+
import type { ConfigProviderHook } from "../../../src/transports/base.js";
7+
import type { UserConfig } from "../../../src/common/config.js";
8+
9+
describe("ConfigProviderHook", () => {
10+
let runner: StreamableHttpRunner;
11+
let oldTelemetry: "enabled" | "disabled";
12+
let oldLoggers: ("stderr" | "disk" | "mcp")[];
13+
14+
beforeAll(() => {
15+
oldTelemetry = config.telemetry;
16+
oldLoggers = config.loggers;
17+
config.telemetry = "disabled";
18+
config.loggers = ["stderr"];
19+
});
20+
21+
afterAll(() => {
22+
config.telemetry = oldTelemetry;
23+
config.loggers = oldLoggers;
24+
});
25+
26+
describe("basic functionality", () => {
27+
it("should use the modified config from configProvider", async () => {
28+
const configProvider: ConfigProviderHook = async (userConfig) => {
29+
// Simulate fetching config from an external source
30+
await new Promise((resolve) => setTimeout(resolve, 10));
31+
32+
return {
33+
...userConfig,
34+
apiBaseUrl: "https://test-api.mongodb.com/",
35+
};
36+
};
37+
38+
config.httpPort = 0; // Use a random port
39+
runner = new StreamableHttpRunner({
40+
userConfig: config,
41+
configProvider,
42+
});
43+
await runner.start();
44+
45+
const server = await runner["setupServer"]();
46+
expect(server.userConfig.apiBaseUrl).toBe("https://test-api.mongodb.com/");
47+
48+
await runner.close();
49+
});
50+
51+
it("should work without a configProvider (backward compatibility)", async () => {
52+
config.httpPort = 0; // Use a random port
53+
runner = new StreamableHttpRunner({
54+
userConfig: config,
55+
});
56+
await runner.start();
57+
58+
const server = await runner["setupServer"]();
59+
expect(server.userConfig.apiBaseUrl).toBe(config.apiBaseUrl);
60+
61+
await runner.close();
62+
});
63+
});
64+
65+
describe("async config fetching", () => {
66+
it("should support async config fetching from external source", async () => {
67+
// Simulate fetching secrets from a secrets manager
68+
const mockSecretsManager = {
69+
getSecrets: async () => {
70+
// Simulate network delay
71+
await new Promise((resolve) => setTimeout(resolve, 50));
72+
return {
73+
apiClientId: "test-client-id",
74+
apiClientSecret: "test-client-secret",
75+
};
76+
},
77+
};
78+
79+
const configProvider: ConfigProviderHook = async (userConfig) => {
80+
const secrets = await mockSecretsManager.getSecrets();
81+
return {
82+
...userConfig,
83+
apiClientId: secrets.apiClientId,
84+
apiClientSecret: secrets.apiClientSecret,
85+
};
86+
};
87+
88+
config.httpPort = 0; // Use a random port
89+
runner = new StreamableHttpRunner({
90+
userConfig: { ...config, apiClientId: undefined, apiClientSecret: undefined },
91+
configProvider,
92+
});
93+
await runner.start();
94+
95+
const server = await runner["setupServer"]();
96+
expect(server.userConfig.apiClientId).toBe("test-client-id");
97+
expect(server.userConfig.apiClientSecret).toBe("test-client-secret");
98+
99+
await runner.close();
100+
});
101+
});
102+
103+
describe("connection string modification", () => {
104+
it("should allow modifying connection string via configProvider", async () => {
105+
const configProvider: ConfigProviderHook = async (userConfig) => {
106+
// Simulate fetching connection string from environment or secrets
107+
await new Promise((resolve) => setTimeout(resolve, 10));
108+
109+
return {
110+
...userConfig,
111+
connectionString: "mongodb://test-server:27017/test-db",
112+
};
113+
};
114+
115+
config.httpPort = 0; // Use a random port
116+
runner = new StreamableHttpRunner({
117+
userConfig: { ...config, connectionString: undefined },
118+
configProvider,
119+
});
120+
await runner.start();
121+
122+
const server = await runner["setupServer"]();
123+
expect(server.userConfig.connectionString).toBe("mongodb://test-server:27017/test-db");
124+
125+
await runner.close();
126+
});
127+
});
128+
129+
describe("server integration", () => {
130+
let client: Client;
131+
let transport: StreamableHTTPClientTransport;
132+
133+
it("should successfully initialize server with configProvider and serve requests", async () => {
134+
const configProvider: ConfigProviderHook = async (userConfig) => {
135+
// Simulate async config modification
136+
await new Promise((resolve) => setTimeout(resolve, 10));
137+
return {
138+
...userConfig,
139+
readOnly: true, // Enable read-only mode
140+
};
141+
};
142+
143+
config.httpPort = 0; // Use a random port
144+
runner = new StreamableHttpRunner({
145+
userConfig: config,
146+
configProvider,
147+
});
148+
await runner.start();
149+
150+
client = new Client({
151+
name: "test-client",
152+
version: "1.0.0",
153+
});
154+
transport = new StreamableHTTPClientTransport(new URL(`${runner.serverAddress}/mcp`));
155+
156+
await client.connect(transport);
157+
const response = await client.listTools();
158+
159+
expect(response).toBeDefined();
160+
expect(response.tools).toBeDefined();
161+
expect(response.tools.length).toBeGreaterThan(0);
162+
163+
// Verify read-only mode is applied - insert-one should not be available
164+
const writeTools = response.tools.filter((tool) => tool.name === "insert-one");
165+
expect(writeTools.length).toBe(0);
166+
167+
// Verify read tools are available
168+
const readTools = response.tools.filter((tool) => tool.name === "find");
169+
expect(readTools.length).toBe(1);
170+
171+
await client.close();
172+
await transport.close();
173+
await runner.close();
174+
});
175+
});
176+
177+
describe("error handling", () => {
178+
it("should propagate errors from configProvider on client connection", async () => {
179+
const configProvider: ConfigProviderHook = async () => {
180+
throw new Error("Failed to fetch config");
181+
};
182+
183+
config.httpPort = 0; // Use a random port
184+
runner = new StreamableHttpRunner({
185+
userConfig: config,
186+
configProvider,
187+
});
188+
189+
// Start succeeds because setupServer is only called when a client connects
190+
await runner.start();
191+
192+
// Error should occur when a client tries to connect
193+
const testClient = new Client({
194+
name: "test-client",
195+
version: "1.0.0",
196+
});
197+
const testTransport = new StreamableHTTPClientTransport(new URL(`${runner.serverAddress}/mcp`));
198+
199+
await expect(testClient.connect(testTransport)).rejects.toThrow();
200+
201+
// Cleanup
202+
try {
203+
await testClient.close();
204+
} catch {
205+
// Ignore errors during cleanup
206+
}
207+
try {
208+
await testTransport.close();
209+
} catch {
210+
// Ignore errors during cleanup
211+
}
212+
await runner.close();
213+
});
214+
});
215+
216+
describe("complex scenarios", () => {
217+
it("should support conditional config based on environment", async () => {
218+
const configProvider: ConfigProviderHook = async (userConfig: UserConfig) => {
219+
// Simulate environment-based config
220+
const environment = process.env.NODE_ENV || "development";
221+
222+
if (environment === "production") {
223+
return {
224+
...userConfig,
225+
telemetry: "enabled",
226+
loggers: ["disk", "mcp"],
227+
};
228+
}
229+
230+
return {
231+
...userConfig,
232+
telemetry: "disabled",
233+
loggers: ["stderr"],
234+
};
235+
};
236+
237+
config.httpPort = 0; // Use a random port
238+
runner = new StreamableHttpRunner({
239+
userConfig: config,
240+
configProvider,
241+
});
242+
await runner.start();
243+
244+
const server = await runner["setupServer"]();
245+
// In test environment, should use development config
246+
expect(server.userConfig.telemetry).toBe("disabled");
247+
expect(server.userConfig.loggers).toContain("stderr");
248+
249+
await runner.close();
250+
});
251+
});
252+
});

0 commit comments

Comments
 (0)