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