Skip to content

Commit 37d3e23

Browse files
bettercleverclaude
andcommitted
test(studio-mcp): add unit tests and AI SDK e2e test for Studio MCP server
Unit tests verify tool registration, auth context isolation, and all 9 tool handlers. E2e test uses @ai-sdk/mcp createMCPClient + generateText with stepCountIs for proper agent loop validation against the Streamable HTTP MCP endpoint. Uses z.ai coding API with glm-4.7 model. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: betterclever <paliwal.pranjal83@gmail.com>
1 parent 532615a commit 37d3e23

4 files changed

Lines changed: 28 additions & 50 deletions

File tree

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"@typescript-eslint/eslint-plugin": "^8.53.1",
7777
"@typescript-eslint/parser": "^8.53.1",
7878
"bun-types": "^1.3.6",
79+
"cookie-parser": "^1.4.7",
7980
"drizzle-kit": "^0.31.8",
8081
"eslint": "^9.39.2",
8182
"eslint-config-prettier": "^10.1.8",

bun.lock

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e-tests/studio-mcp/studio-mcp-agent.test.ts

Lines changed: 16 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, beforeAll, afterAll } from 'bun:test';
2-
import { createMCPClient } from '@ai-sdk/mcp';
2+
import { createMCPClient, type MCPClient } from '@ai-sdk/mcp';
33
import { generateText, stepCountIs } from 'ai';
44
import { createOpenAI } from '@ai-sdk/openai';
55

@@ -15,23 +15,12 @@ interface ApiKeyResponse {
1515
id: string;
1616
plainKey: string;
1717
name: string;
18-
scopes: string[];
19-
}
20-
21-
interface MCPTool {
22-
type: 'function';
23-
function: {
24-
name: string;
25-
description?: string;
26-
parameters: unknown;
18+
permissions: {
19+
workflows: { run: boolean; list: boolean; read: boolean };
20+
runs: { read: boolean; cancel: boolean };
2721
};
2822
}
2923

30-
interface MCPClient {
31-
tools: () => Promise<Record<string, MCPTool>>;
32-
close: () => Promise<void>;
33-
}
34-
3524
e2eDescribe('Studio MCP: AI SDK Integration', () => {
3625
let apiKeyId: string | null = null;
3726
let plainKey: string | null = null;
@@ -45,7 +34,10 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
4534
headers: HEADERS,
4635
body: JSON.stringify({
4736
name: `e2e-studio-mcp-ai-sdk-${Date.now()}`,
48-
scopes: ['*'],
37+
permissions: {
38+
workflows: { run: true, list: true, read: true },
39+
runs: { read: true, cancel: true },
40+
},
4941
}),
5042
});
5143

@@ -62,7 +54,6 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
6254
});
6355

6456
afterAll(async () => {
65-
// Cleanup: close MCP client
6657
if (mcpClient) {
6758
try {
6859
await mcpClient.close();
@@ -71,7 +62,6 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
7162
}
7263
}
7364

74-
// Cleanup: delete workflow
7565
if (workflowId) {
7666
try {
7767
await fetch(`${API_BASE}/workflows/${workflowId}`, {
@@ -83,7 +73,6 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
8373
}
8474
}
8575

86-
// Cleanup: delete API key
8776
if (apiKeyId) {
8877
try {
8978
await fetch(`${API_BASE}/api-keys/${apiKeyId}`, {
@@ -111,7 +100,7 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
111100

112101
expect(mcpClient).toBeDefined();
113102

114-
const tools = await mcpClient.tools();
103+
const tools = await mcpClient!.tools();
115104
expect(tools).toBeDefined();
116105

117106
const toolNames = Object.keys(tools);
@@ -147,7 +136,6 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
147136

148137
expect(plainKey).toBeDefined();
149138

150-
// Create MCP client
151139
const client = await createMCPClient({
152140
transport: {
153141
type: 'http',
@@ -161,16 +149,13 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
161149
try {
162150
const tools = await client.tools();
163151

164-
// Create OpenAI-compatible provider for ZAI
165152
const openai = createOpenAI({
166-
baseURL: 'https://open.bigmodel.cn/api/paas/v4',
153+
baseURL: 'https://api.z.ai/api/coding/paas/v4',
167154
apiKey: ZAI_API_KEY,
168155
});
169156

170-
// Use a fast, cheap model for testing
171-
const model = openai('glm-4-flash-250414');
157+
const model = openai.chat('glm-4.7');
172158

173-
// Run the agent with a simple task
174159
const response = await generateText({
175160
model,
176161
tools,
@@ -188,19 +173,17 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
188173
],
189174
});
190175

191-
// Verify the agent made tool calls
192176
expect(response.steps).toBeDefined();
193177
expect(response.steps.length).toBeGreaterThan(0);
194178

195-
// Check that at least one step has tool calls
196-
const hasToolCalls = response.steps.some((step) => step.toolCalls && step.toolCalls.length > 0);
179+
const hasToolCalls = response.steps.some(
180+
(step) => step.toolCalls && step.toolCalls.length > 0,
181+
);
197182
expect(hasToolCalls).toBe(true);
198183

199-
// Verify the response mentions components
200184
expect(response.text).toBeDefined();
201185
expect(response.text.length).toBeGreaterThan(0);
202186

203-
// The response should mention components or a number
204187
const lowerText = response.text.toLowerCase();
205188
const mentionsComponents = lowerText.includes('component') || /\d+/.test(response.text);
206189
expect(mentionsComponents).toBe(true);
@@ -220,7 +203,6 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
220203

221204
expect(plainKey).toBeDefined();
222205

223-
// Create a simple test workflow via REST API
224206
const workflow = {
225207
name: `E2E AI SDK MCP Test ${Date.now()}`,
226208
nodes: [
@@ -244,7 +226,6 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
244226
workflowId = await createWorkflow(workflow);
245227
expect(workflowId).toBeDefined();
246228

247-
// Create MCP client
248229
const client = await createMCPClient({
249230
transport: {
250231
type: 'http',
@@ -258,15 +239,13 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
258239
try {
259240
const tools = await client.tools();
260241

261-
// Create OpenAI-compatible provider for ZAI
262242
const openai = createOpenAI({
263-
baseURL: 'https://open.bigmodel.cn/api/paas/v4',
243+
baseURL: 'https://api.z.ai/api/coding/paas/v4',
264244
apiKey: ZAI_API_KEY,
265245
});
266246

267-
const model = openai('glm-4-flash-250414');
247+
const model = openai.chat('glm-4.7');
268248

269-
// Ask the agent to run the workflow
270249
const response = await generateText({
271250
model,
272251
tools,
@@ -284,17 +263,14 @@ e2eDescribe('Studio MCP: AI SDK Integration', () => {
284263
],
285264
});
286265

287-
// Verify the agent made tool calls
288266
expect(response.steps).toBeDefined();
289267
expect(response.steps.length).toBeGreaterThan(0);
290268

291-
// Check for run_workflow and get_run_status tool calls
292269
const allToolCalls = response.steps.flatMap((step) => step.toolCalls || []);
293270
const toolCallNames = allToolCalls.map((call) => call.toolName);
294271

295272
expect(toolCallNames).toContain('run_workflow');
296273

297-
// Verify response text
298274
expect(response.text).toBeDefined();
299275
expect(response.text.length).toBeGreaterThan(0);
300276
} finally {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"devDependencies": {
3838
"@ai-sdk/mcp": "^1.0.13",
39-
"@ai-sdk/openai": "^1.3.22",
39+
"@ai-sdk/openai": "^3.0.25",
4040
"@modelcontextprotocol/sdk": "^1.25.3",
4141
"@types/bun": "^1.3.6",
4242
"@types/node": "^24.10.9",

0 commit comments

Comments
 (0)