77 * Uses the official MCP SDK: https://www.npmjs.com/package/@modelcontextprotocol/sdk
88 */
99
10- import { MCP , McpConfig , McpServerConfig } from '../core/mcp' ;
11- import { Tool } from './tool' ;
12- import { ToolContext } from './types' ;
10+ import { z } from 'zod' ;
11+ import { zodToJsonSchema } from 'zod-to-json-schema' ;
1312
14- /**
15- * Parameters for listResources method
16- */
17- interface ListResourcesParams {
18- /** Optional server name to filter resources by */
19- server ?: string ;
20- }
13+ import { Tool } from '../core/types.js' ;
14+ import { McpConfig } from '../core/mcp/index.js' ;
2115
22- /**
23- * Parameters for getResource method
24- */
25- interface GetResourceParams {
26- /** URI of the resource to fetch */
27- uri : string ;
28- }
16+ // Parameters for listResources method
17+ const listResourcesSchema = z . object ( {
18+ server : z
19+ . string ( )
20+ . optional ( )
21+ . describe ( 'Optional server name to filter resources by' ) ,
22+ } ) ;
23+
24+ // Parameters for getResource method
25+ const getResourceSchema = z . object ( {
26+ uri : z
27+ . string ( )
28+ . describe ( 'URI of the resource to fetch in the format "scheme://path"' ) ,
29+ } ) ;
30+
31+ // Return type for listResources
32+ const listResourcesReturnSchema = z . array (
33+ z . object ( {
34+ uri : z . string ( ) ,
35+ metadata : z . record ( z . unknown ( ) ) . optional ( ) ,
36+ } )
37+ ) ;
38+
39+ // Return type for getResource
40+ const getResourceReturnSchema = z . string ( ) ;
41+
42+ type ListResourcesParams = z . infer < typeof listResourcesSchema > ;
43+ type GetResourceParams = z . infer < typeof getResourceSchema > ;
44+ type ListResourcesReturn = z . infer < typeof listResourcesReturnSchema > ;
45+ type GetResourceReturn = z . infer < typeof getResourceReturnSchema > ;
46+
47+ // Map to store MCP clients
48+ const mcpClients = new Map < string , any > ( ) ;
2949
3050/**
31- * MCP Tool for interacting with MCP servers
51+ * Create a new MCP tool with the specified configuration
52+ * @param config MCP configuration
53+ * @returns The MCP tool
3254 */
33- export class McpTool extends Tool {
34- private clients : Map < string , MCP . Client > = new Map ( ) ;
55+ export function createMcpTool ( config : McpConfig ) : Tool {
56+ // We'll import the MCP SDK dynamically to avoid TypeScript errors
57+ // This is a temporary solution until we can properly add type declarations
58+ const mcpSdk = require ( '@modelcontextprotocol/sdk' ) ;
3559
36- /**
37- * Create a new MCP tool
38- * @param config MCP configuration
39- */
40- constructor ( private config : McpConfig = { servers : [ ] , defaultResources : [ ] } ) {
41- super ( {
42- name : 'mcp' ,
43- description : 'Interact with Model Context Protocol (MCP) servers to retrieve resources' ,
44- schema : {
45- listResources : {
46- description : 'List available resources from MCP servers' ,
47- parameters : {
48- server : {
49- type : 'string' ,
50- description : 'Optional server name to filter resources by' ,
51- required : false ,
52- } ,
53- } ,
54- } ,
55- getResource : {
56- description : 'Fetch a resource from an MCP server' ,
57- parameters : {
58- uri : {
59- type : 'string' ,
60- description : 'URI of the resource to fetch in the format "scheme://path"' ,
61- required : true ,
62- } ,
63- } ,
64- } ,
65- } ,
66- } ) ;
67-
68- // Initialize MCP clients for each configured server
69- this . initializeClients ( ) ;
70- }
60+ // Initialize MCP clients for each configured server
61+ mcpClients . clear ( ) ;
7162
72- /**
73- * Initialize MCP clients for each configured server
74- */
75- private initializeClients ( ) : void {
76- if ( ! this . config . servers || this . config . servers . length === 0 ) {
77- return ;
78- }
79-
80- for ( const server of this . config . servers ) {
63+ if ( config . servers && config . servers . length > 0 ) {
64+ for ( const server of config . servers ) {
8165 try {
82- const clientOptions : MCP . ClientOptions = {
66+ let clientOptions : any = {
8367 baseURL : server . url ,
8468 } ;
8569
8670 // Add authentication if configured
8771 if ( server . auth && server . auth . type === 'bearer' ) {
88- clientOptions . headers = {
89- Authorization : `Bearer ${ server . auth . token } ` ,
72+ clientOptions = {
73+ ...clientOptions ,
74+ headers : {
75+ Authorization : `Bearer ${ server . auth . token } ` ,
76+ } ,
9077 } ;
9178 }
9279
93- const client = new MCP . Client ( clientOptions ) ;
94- this . clients . set ( server . name , client ) ;
80+ const client = new mcpSdk . Client ( clientOptions ) ;
81+ mcpClients . set ( server . name , client ) ;
9582 } catch ( error ) {
9683 console . error ( `Failed to initialize MCP client for server ${ server . name } :` , error ) ;
9784 }
9885 }
9986 }
10087
101- /**
102- * List available resources from MCP servers
103- * @param params Optional parameters
104- * @param _context Tool context
105- * @returns List of available resources
106- */
107- async listResources (
108- params : ListResourcesParams = { } ,
109- _context : ToolContext ,
110- ) : Promise < MCP . Resource [ ] > {
111- const resources : MCP . Resource [ ] = [ ] ;
88+ // Define the MCP tool
89+ return {
90+ name : 'mcp' ,
91+ description : 'Interact with Model Context Protocol (MCP) servers to retrieve resources' ,
92+ parameters : z . discriminatedUnion ( 'method' , [
93+ z . object ( {
94+ method : z . literal ( 'listResources' ) ,
95+ params : listResourcesSchema . optional ( ) ,
96+ } ) ,
97+ z . object ( {
98+ method : z . literal ( 'getResource' ) ,
99+ params : getResourceSchema ,
100+ } ) ,
101+ ] ) ,
102+ parametersJsonSchema : zodToJsonSchema (
103+ z . discriminatedUnion ( 'method' , [
104+ z . object ( {
105+ method : z . literal ( 'listResources' ) ,
106+ params : listResourcesSchema . optional ( ) ,
107+ } ) ,
108+ z . object ( {
109+ method : z . literal ( 'getResource' ) ,
110+ params : getResourceSchema ,
111+ } ) ,
112+ ] )
113+ ) ,
114+ returns : z . union ( [ listResourcesReturnSchema , getResourceReturnSchema ] ) ,
115+ returnsJsonSchema : zodToJsonSchema ( z . union ( [ listResourcesReturnSchema , getResourceReturnSchema ] ) ) ,
112116
113- // If a specific server is requested, only check that server
114- if ( params . server ) {
115- const client = this . clients . get ( params . server ) ;
116- if ( client ) {
117- try {
118- const serverResources = await client . resources ( ) ;
119- resources . push ( ...serverResources ) ;
120- } catch ( error ) {
121- console . error ( `Failed to fetch resources from server ${ params . server } :` , error ) ;
122- }
117+ execute : async ( { method, params } , { logger } ) => {
118+ // Extract the server name from a resource URI
119+ function getServerNameFromUri ( uri : string ) : string | undefined {
120+ const match = uri . match ( / ^ ( [ ^ : ] + ) : \/ \/ / ) ;
121+ return match ? match [ 1 ] : undefined ;
123122 }
124- } else {
125- // Otherwise, check all servers
126- for ( const [ serverName , client ] of this . clients . entries ( ) ) {
127- try {
128- const serverResources = await client . resources ( ) ;
129- resources . push ( ...serverResources ) ;
130- } catch ( error ) {
131- console . error ( `Failed to fetch resources from server ${ serverName } :` , error ) ;
123+
124+ if ( method === 'listResources' ) {
125+ // List available resources from MCP servers
126+ const resources : any [ ] = [ ] ;
127+ const serverFilter = params ?. server ;
128+
129+ // If a specific server is requested, only check that server
130+ if ( serverFilter ) {
131+ const client = mcpClients . get ( serverFilter ) ;
132+ if ( client ) {
133+ try {
134+ logger . verbose ( `Fetching resources from server: ${ serverFilter } ` ) ;
135+ const serverResources = await client . resources ( ) ;
136+ resources . push ( ...( serverResources as any [ ] ) ) ;
137+ } catch ( error ) {
138+ logger . error ( `Failed to fetch resources from server ${ serverFilter } :` , error ) ;
139+ }
140+ } else {
141+ logger . warn ( `Server not found: ${ serverFilter } ` ) ;
142+ }
143+ } else {
144+ // Otherwise, check all servers
145+ for ( const [ serverName , client ] of mcpClients . entries ( ) ) {
146+ try {
147+ logger . verbose ( `Fetching resources from server: ${ serverName } ` ) ;
148+ const serverResources = await client . resources ( ) ;
149+ resources . push ( ...( serverResources as any [ ] ) ) ;
150+ } catch ( error ) {
151+ logger . error ( `Failed to fetch resources from server ${ serverName } :` , error ) ;
152+ }
153+ }
132154 }
155+
156+ return resources ;
157+ } else if ( method === 'getResource' ) {
158+ // Fetch a resource from an MCP server
159+ const uri = params . uri ;
160+
161+ // Parse the URI to determine which server to use
162+ const serverName = getServerNameFromUri ( uri ) ;
163+ if ( ! serverName ) {
164+ throw new Error ( `Could not determine server from URI: ${ uri } ` ) ;
165+ }
166+
167+ const client = mcpClients . get ( serverName ) ;
168+ if ( ! client ) {
169+ throw new Error ( `Server not found: ${ serverName } ` ) ;
170+ }
171+
172+ // Use the MCP SDK to fetch the resource
173+ logger . verbose ( `Fetching resource: ${ uri } ` ) ;
174+ const resource = await client . resource ( uri ) ;
175+ return resource . content ;
133176 }
134- }
177+
178+ throw new Error ( `Unknown method: ${ method } ` ) ;
179+ } ,
135180
136- return resources ;
137- }
138-
139- /**
140- * Fetch a resource from an MCP server
141- * @param params Parameters
142- * @param _context Tool context
143- * @returns The resource content
144- */
145- async getResource (
146- params : GetResourceParams ,
147- _context : ToolContext ,
148- ) : Promise < string > {
149- // Parse the URI to determine which server to use
150- const serverName = this . getServerNameFromUri ( params . uri ) ;
151- if ( ! serverName ) {
152- throw new Error ( `Could not determine server from URI: ${ params . uri } ` ) ;
153- }
154-
155- const client = this . clients . get ( serverName ) ;
156- if ( ! client ) {
157- throw new Error ( `Server not found: ${ serverName } ` ) ;
158- }
181+ logParameters : ( params , { logger } ) => {
182+ if ( params . method === 'listResources' ) {
183+ logger . verbose ( `Listing MCP resources${ params . params ?. server ? ` from server: ${ params . params . server } ` : '' } ` ) ;
184+ } else if ( params . method === 'getResource' ) {
185+ logger . verbose ( `Fetching MCP resource: ${ params . params . uri } ` ) ;
186+ }
187+ } ,
159188
160- // Use the MCP SDK to fetch the resource
161- const resource = await client . resource ( params . uri ) ;
162- return resource . content ;
163- }
164-
165- /**
166- * Extract the server name from a resource URI
167- * @param uri Resource URI in the format 'scheme://path'
168- * @returns The server name or undefined if not found
169- * @private
170- */
171- private getServerNameFromUri ( uri : string ) : string | undefined {
172- // For simplicity, we'll use the first part of the URI as the server name
173- const match = uri . match ( / ^ ( [ ^ : ] + ) : \/ \/ / ) ;
174- return match ? match [ 1 ] : undefined ;
175- }
189+ logReturns : ( result , { logger } ) => {
190+ if ( Array . isArray ( result ) ) {
191+ logger . verbose ( `Found ${ result . length } MCP resources` ) ;
192+ } else {
193+ logger . verbose ( `Retrieved MCP resource content (${ result . length } characters)` ) ;
194+ }
195+ } ,
196+ } ;
176197}
0 commit comments