Skip to content

Commit a7af6e6

Browse files
New client integration tests
1 parent bdb2f2c commit a7af6e6

File tree

2 files changed

+224
-6
lines changed

2 files changed

+224
-6
lines changed

index.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,37 @@ import { TaskManagerServer } from "./src/server/TaskManagerServer.js";
66
import { ALL_TOOLS } from "./src/types/tools.js";
77
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
88

9-
const server = new Server({
10-
name: "task-manager-server",
11-
version: "1.0.4"
9+
// Create server with capabilities BEFORE setting up handlers
10+
const server = new Server(
11+
{
12+
name: "task-manager-server",
13+
version: "1.0.4"
14+
},
15+
{
16+
capabilities: {
17+
tools: {
18+
list: true,
19+
call: true
20+
}
21+
}
22+
}
23+
);
24+
25+
// Debug logging
26+
console.error('Server starting with env:', {
27+
TASK_MANAGER_FILE_PATH: process.env.TASK_MANAGER_FILE_PATH,
28+
NODE_ENV: process.env.NODE_ENV
1229
});
1330

31+
// Initialize task manager
1432
const taskManager = new TaskManagerServer();
1533

16-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
17-
tools: ALL_TOOLS,
18-
}));
34+
// Set up request handlers AFTER capabilities are configured
35+
server.setRequestHandler(ListToolsRequestSchema, async () => {
36+
return {
37+
tools: ALL_TOOLS
38+
};
39+
});
1940

2041
server.setRequestHandler(CallToolRequestSchema, async (request) => {
2142
try {

tests/integration/mcp-client.test.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3+
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
4+
import * as path from 'node:path';
5+
import * as os from 'node:os';
6+
import * as fs from 'node:fs/promises';
7+
import { Tool } from "@modelcontextprotocol/sdk/types.js";
8+
9+
interface ToolResponse {
10+
isError: boolean;
11+
content: Array<{ text: string }>;
12+
}
13+
14+
describe('MCP Client Integration', () => {
15+
let client: Client;
16+
let transport: StdioClientTransport;
17+
let tempDir: string;
18+
let testFilePath: string;
19+
20+
beforeAll(async () => {
21+
// Create a unique temp directory for test
22+
tempDir = path.join(os.tmpdir(), `mcp-client-integration-test-${Date.now()}-${Math.floor(Math.random() * 10000)}`);
23+
await fs.mkdir(tempDir, { recursive: true });
24+
testFilePath = path.join(tempDir, 'test-tasks.json');
25+
26+
console.log('Setting up test with:');
27+
console.log('- Temp directory:', tempDir);
28+
console.log('- Test file path:', testFilePath);
29+
30+
// Set up the transport with environment variable for test file
31+
transport = new StdioClientTransport({
32+
command: "node",
33+
args: ["dist/index.js"],
34+
env: {
35+
TASK_MANAGER_FILE_PATH: testFilePath,
36+
NODE_ENV: "test",
37+
DEBUG: "mcp:*" // Enable MCP debug logging
38+
}
39+
});
40+
41+
console.log('Created transport with command:', 'node', 'dist/index.js');
42+
43+
// Set up the client
44+
client = new Client(
45+
{
46+
name: "test-client",
47+
version: "1.0.0"
48+
},
49+
{
50+
capabilities: {
51+
tools: {
52+
list: true,
53+
call: true
54+
}
55+
}
56+
}
57+
);
58+
59+
try {
60+
console.log('Attempting to connect to server...');
61+
// Connect to the server with a timeout
62+
const connectPromise = client.connect(transport);
63+
const timeoutPromise = new Promise((_, reject) => {
64+
setTimeout(() => reject(new Error('Connection timeout')), 5000);
65+
});
66+
67+
await Promise.race([connectPromise, timeoutPromise]);
68+
console.log('Successfully connected to server');
69+
70+
// Small delay to ensure server is ready
71+
await new Promise(resolve => setTimeout(resolve, 1000));
72+
} catch (error) {
73+
console.error('Failed to connect to server:', error);
74+
throw error;
75+
}
76+
});
77+
78+
afterAll(async () => {
79+
try {
80+
console.log('Cleaning up...');
81+
// Ensure transport is properly closed
82+
if (transport) {
83+
transport.close();
84+
console.log('Transport closed');
85+
// Give it a moment to clean up
86+
await new Promise(resolve => setTimeout(resolve, 100));
87+
}
88+
} catch (err) {
89+
console.error('Error closing transport:', err);
90+
}
91+
92+
// Clean up temp files
93+
try {
94+
await fs.rm(tempDir, { recursive: true, force: true });
95+
console.log('Temp directory cleaned up');
96+
} catch (err) {
97+
console.error('Error cleaning up temp directory:', err);
98+
}
99+
});
100+
101+
it('should list available tools', async () => {
102+
console.log('Testing tool listing...');
103+
const response = await client.listTools();
104+
expect(response).toBeDefined();
105+
expect(response).toHaveProperty('tools');
106+
expect(Array.isArray(response.tools)).toBe(true);
107+
expect(response.tools.length).toBeGreaterThan(0);
108+
109+
// Check for essential tools
110+
const toolNames = response.tools.map(tool => tool.name);
111+
console.log('Available tools:', toolNames);
112+
expect(toolNames).toContain('list_projects');
113+
expect(toolNames).toContain('create_project');
114+
expect(toolNames).toContain('read_project');
115+
expect(toolNames).toContain('get_next_task');
116+
});
117+
118+
it('should create and manage a project lifecycle', async () => {
119+
console.log('Testing project lifecycle...');
120+
// Create a new project
121+
const createResult = await client.callTool({
122+
name: "create_project",
123+
arguments: {
124+
initialPrompt: "Test Project",
125+
tasks: [
126+
{ title: "Task 1", description: "First test task" },
127+
{ title: "Task 2", description: "Second test task" }
128+
]
129+
}
130+
}) as ToolResponse;
131+
expect(createResult.isError).toBeFalsy();
132+
133+
// Parse the project ID from the response
134+
const responseData = JSON.parse((createResult.content[0] as { text: string }).text);
135+
const projectId = responseData.projectId;
136+
expect(projectId).toBeDefined();
137+
console.log('Created project with ID:', projectId);
138+
139+
// List projects and verify our new project exists
140+
const listResult = await client.callTool({
141+
name: "list_projects",
142+
arguments: {}
143+
}) as ToolResponse;
144+
expect(listResult.isError).toBeFalsy();
145+
const projects = JSON.parse((listResult.content[0] as { text: string }).text);
146+
expect(projects.projects.some((p: any) => p.projectId === projectId)).toBe(true);
147+
console.log('Project verified in list');
148+
149+
// Get next task
150+
const nextTaskResult = await client.callTool({
151+
name: "get_next_task",
152+
arguments: {
153+
projectId
154+
}
155+
}) as ToolResponse;
156+
expect(nextTaskResult.isError).toBeFalsy();
157+
const nextTask = JSON.parse((nextTaskResult.content[0] as { text: string }).text);
158+
expect(nextTask.status).toBe("next_task");
159+
expect(nextTask.task).toBeDefined();
160+
const taskId = nextTask.task.id;
161+
console.log('Got next task with ID:', taskId);
162+
163+
// Mark task as done
164+
const markDoneResult = await client.callTool({
165+
name: "update_task",
166+
arguments: {
167+
projectId,
168+
taskId,
169+
status: "done",
170+
completedDetails: "Task completed in test"
171+
}
172+
}) as ToolResponse;
173+
expect(markDoneResult.isError).toBeFalsy();
174+
console.log('Marked task as done');
175+
176+
// Approve the task
177+
const approveResult = await client.callTool({
178+
name: "approve_task",
179+
arguments: {
180+
projectId,
181+
taskId
182+
}
183+
}) as ToolResponse;
184+
expect(approveResult.isError).toBeFalsy();
185+
console.log('Approved task');
186+
187+
// Delete the project
188+
const deleteResult = await client.callTool({
189+
name: "delete_project",
190+
arguments: {
191+
projectId
192+
}
193+
}) as ToolResponse;
194+
expect(deleteResult.isError).toBeFalsy();
195+
console.log('Deleted project');
196+
});
197+
});

0 commit comments

Comments
 (0)