@@ -17,13 +17,20 @@ type JsonRpcResponse = {
1717 error ?: { code : number ; message : string ; data ?: unknown } ;
1818} ;
1919
20+ type JsonRpcNotification = {
21+ jsonrpc : "2.0" ;
22+ method : string ;
23+ params ?: Record < string , unknown > ;
24+ } ;
25+
2026export type McpToolDefinition = {
2127 name : string ;
2228 description ?: string ;
2329 inputSchema : {
2430 type : "object" ;
2531 properties : Record < string , unknown > ;
2632 required ?: string [ ] ;
33+ additionalProperties ?: boolean ;
2734 } ;
2835} ;
2936
@@ -37,6 +44,58 @@ type CallToolResult = {
3744 isError ?: boolean ;
3845} ;
3946
47+ export type McpPromptArgument = {
48+ name : string ;
49+ description ?: string ;
50+ required ?: boolean ;
51+ } ;
52+
53+ export type McpPromptDefinition = {
54+ name : string ;
55+ description ?: string ;
56+ arguments ?: McpPromptArgument [ ] ;
57+ } ;
58+
59+ type ListPromptsResult = {
60+ prompts : McpPromptDefinition [ ] ;
61+ nextCursor ?: string ;
62+ } ;
63+
64+ export type McpPromptMessage = {
65+ role : "user" | "assistant" ;
66+ content : { type : string ; text ?: string } ;
67+ } ;
68+
69+ type GetPromptResult = {
70+ description ?: string ;
71+ messages : McpPromptMessage [ ] ;
72+ } ;
73+
74+ export type McpResourceDefinition = {
75+ uri : string ;
76+ name : string ;
77+ description ?: string ;
78+ mimeType ?: string ;
79+ } ;
80+
81+ type ListResourcesResult = {
82+ resources : McpResourceDefinition [ ] ;
83+ nextCursor ?: string ;
84+ } ;
85+
86+ export type McpResourceContent = {
87+ uri : string ;
88+ mimeType ?: string ;
89+ text ?: string ;
90+ blob ?: string ;
91+ } ;
92+
93+ type ReadResourceResult = {
94+ contents : McpResourceContent [ ] ;
95+ } ;
96+
97+ export type McpNotificationHandler = ( method : string , params ?: Record < string , unknown > ) => void ;
98+
4099export class McpClient {
41100 private process : ChildProcess | null = null ;
42101 private reader : Interface | null = null ;
@@ -46,13 +105,17 @@ export class McpClient {
46105 { resolve : ( value : unknown ) => void ; reject : ( error : Error ) => void ; timer : NodeJS . Timeout }
47106 > ( ) ;
48107 private stderrBuffer = "" ;
108+ private notificationHandler : McpNotificationHandler | null = null ;
49109
50110 constructor (
51111 private readonly serverName : string ,
52112 private readonly command : string ,
53113 private readonly args : string [ ] = [ ] ,
54- private readonly env ?: Record < string , string >
55- ) { }
114+ private readonly env ?: Record < string , string > ,
115+ onNotification ?: McpNotificationHandler
116+ ) {
117+ this . notificationHandler = onNotification ?? null ;
118+ }
56119
57120 async connect ( timeoutMs : number ) : Promise < void > {
58121 return new Promise ( ( resolve , reject ) => {
@@ -109,7 +172,7 @@ export class McpClient {
109172 this . sendRequest (
110173 "initialize" ,
111174 {
112- protocolVersion : "2024-11-05 " ,
175+ protocolVersion : "2025-03-26 " ,
113176 capabilities : { } ,
114177 clientInfo : { name : "deepcode-cli" , version : "0.1.0" } ,
115178 } ,
@@ -141,8 +204,50 @@ export class McpClient {
141204 throw this . withStderr ( `MCP server "${ this . serverName } " returned too many tools/list pages` ) ;
142205 }
143206
144- async callTool ( name : string , args : Record < string , unknown > ) : Promise < CallToolResult > {
145- return ( await this . sendRequest ( "tools/call" , { name, arguments : args } ) ) as CallToolResult ;
207+ async callTool ( name : string , args : Record < string , unknown > , timeoutMs = 60_000 ) : Promise < CallToolResult > {
208+ return ( await this . sendRequest ( "tools/call" , { name, arguments : args } , timeoutMs ) ) as CallToolResult ;
209+ }
210+
211+ async listPrompts ( timeoutMs : number ) : Promise < McpPromptDefinition [ ] > {
212+ const prompts : McpPromptDefinition [ ] = [ ] ;
213+ let cursor : string | undefined ;
214+
215+ for ( let page = 0 ; page < 100 ; page ++ ) {
216+ const params = cursor ? { cursor } : { } ;
217+ const result = ( await this . sendRequest ( "prompts/list" , params , timeoutMs ) ) as ListPromptsResult ;
218+ prompts . push ( ...( result . prompts ?? [ ] ) ) ;
219+ cursor = typeof result . nextCursor === "string" && result . nextCursor ? result . nextCursor : undefined ;
220+ if ( ! cursor ) {
221+ return prompts ;
222+ }
223+ }
224+
225+ throw this . withStderr ( `MCP server "${ this . serverName } " returned too many prompts/list pages` ) ;
226+ }
227+
228+ async getPrompt ( name : string , args : Record < string , unknown > , timeoutMs = 30_000 ) : Promise < GetPromptResult > {
229+ return ( await this . sendRequest ( "prompts/get" , { name, arguments : args } , timeoutMs ) ) as GetPromptResult ;
230+ }
231+
232+ async listResources ( timeoutMs : number ) : Promise < McpResourceDefinition [ ] > {
233+ const resources : McpResourceDefinition [ ] = [ ] ;
234+ let cursor : string | undefined ;
235+
236+ for ( let page = 0 ; page < 100 ; page ++ ) {
237+ const params = cursor ? { cursor } : { } ;
238+ const result = ( await this . sendRequest ( "resources/list" , params , timeoutMs ) ) as ListResourcesResult ;
239+ resources . push ( ...( result . resources ?? [ ] ) ) ;
240+ cursor = typeof result . nextCursor === "string" && result . nextCursor ? result . nextCursor : undefined ;
241+ if ( ! cursor ) {
242+ return resources ;
243+ }
244+ }
245+
246+ throw this . withStderr ( `MCP server "${ this . serverName } " returned too many resources/list pages` ) ;
247+ }
248+
249+ async readResource ( uri : string , timeoutMs = 30_000 ) : Promise < ReadResourceResult > {
250+ return ( await this . sendRequest ( "resources/read" , { uri } , timeoutMs ) ) as ReadResourceResult ;
146251 }
147252
148253 disconnect ( ) : void {
@@ -195,7 +300,23 @@ export class McpClient {
195300
196301 private handleLine ( line : string ) : void {
197302 try {
198- const message = JSON . parse ( line ) as JsonRpcResponse ;
303+ const parsed : unknown = JSON . parse ( line ) ;
304+
305+ // Handle notifications (no id field — server-initiated)
306+ if ( parsed && typeof parsed === "object" && ! ( "id" in parsed ) ) {
307+ const notification = parsed as JsonRpcNotification ;
308+ if ( this . notificationHandler && typeof notification . method === "string" ) {
309+ try {
310+ this . notificationHandler ( notification . method , notification . params ) ;
311+ } catch {
312+ // Swallow handler errors to avoid crashing the reader loop
313+ }
314+ }
315+ return ;
316+ }
317+
318+ // Handle responses to our requests
319+ const message = parsed as JsonRpcResponse ;
199320 if ( message . id !== undefined && this . pendingRequests . has ( message . id ) ) {
200321 const pending = this . pendingRequests . get ( message . id ) ! ;
201322 this . pendingRequests . delete ( message . id ) ;
0 commit comments