1+ /**
2+ * Model Context Protocol (MCP) Client
3+ *
4+ * This module implements a client for the Model Context Protocol (MCP), which allows
5+ * applications to provide standardized context to Large Language Models (LLMs).
6+ */
7+
8+ import fetch from 'node-fetch' ;
9+
10+ /**
11+ * Configuration for an MCP server
12+ */
13+ export interface McpServerConfig {
14+ /** Unique name for this MCP server */
15+ name : string ;
16+ /** URL of the MCP server */
17+ url : string ;
18+ /** Optional authentication configuration */
19+ auth ?: {
20+ /** Authentication type (currently only 'bearer' is supported) */
21+ type : 'bearer' ;
22+ /** Authentication token */
23+ token : string ;
24+ } ;
25+ }
26+
27+ /**
28+ * MCP Resource descriptor
29+ */
30+ export interface McpResource {
31+ /** Resource URI in the format 'scheme://path' */
32+ uri : string ;
33+ /** Optional metadata about the resource */
34+ metadata ?: Record < string , unknown > ;
35+ }
36+
37+ /**
38+ * MCP Client class for interacting with MCP servers
39+ */
40+ export class McpClient {
41+ private servers : Map < string , McpServerConfig > = new Map ( ) ;
42+
43+ /**
44+ * Create a new MCP client
45+ * @param servers Optional array of server configurations to add
46+ */
47+ constructor ( servers : McpServerConfig [ ] = [ ] ) {
48+ servers . forEach ( server => this . addServer ( server ) ) ;
49+ }
50+
51+ /**
52+ * Add an MCP server to the client
53+ * @param server Server configuration
54+ */
55+ addServer ( server : McpServerConfig ) : void {
56+ this . servers . set ( server . name , server ) ;
57+ }
58+
59+ /**
60+ * Remove an MCP server from the client
61+ * @param name Name of the server to remove
62+ */
63+ removeServer ( name : string ) : void {
64+ this . servers . delete ( name ) ;
65+ }
66+
67+ /**
68+ * Get all configured servers
69+ * @returns Array of server configurations
70+ */
71+ getServers ( ) : McpServerConfig [ ] {
72+ return Array . from ( this . servers . values ( ) ) ;
73+ }
74+
75+ /**
76+ * Fetch a resource from an MCP server
77+ * @param uri Resource URI in the format 'scheme://path'
78+ * @returns The resource content as a string
79+ */
80+ async fetchResource ( uri : string ) : Promise < string > {
81+ // Parse the URI to determine which server to use
82+ const serverName = this . getServerNameFromUri ( uri ) ;
83+ if ( ! serverName ) {
84+ throw new Error ( `Could not determine server from URI: ${ uri } ` ) ;
85+ }
86+
87+ const server = this . servers . get ( serverName ) ;
88+ if ( ! server ) {
89+ throw new Error ( `Server not found: ${ serverName } ` ) ;
90+ }
91+
92+ // Extract the path from the URI
93+ const path = this . getPathFromUri ( uri ) ;
94+
95+ // Construct the full URL
96+ const url = new URL ( path , server . url ) ;
97+
98+ // Prepare headers
99+ const headers : Record < string , string > = {
100+ 'Accept' : 'application/json' ,
101+ } ;
102+
103+ // Add authentication if configured
104+ if ( server . auth ) {
105+ if ( server . auth . type === 'bearer' ) {
106+ headers [ 'Authorization' ] = `Bearer ${ server . auth . token } ` ;
107+ }
108+ }
109+
110+ // Make the request
111+ const response = await fetch ( url . toString ( ) , {
112+ method : 'GET' ,
113+ headers,
114+ } ) ;
115+
116+ if ( ! response . ok ) {
117+ throw new Error ( `Failed to fetch resource: ${ response . status } ${ response . statusText } ` ) ;
118+ }
119+
120+ return await response . text ( ) ;
121+ }
122+
123+ /**
124+ * Get available resources from all configured servers
125+ * @returns Array of available resources
126+ */
127+ async getAvailableResources ( ) : Promise < McpResource [ ] > {
128+ const resources : McpResource [ ] = [ ] ;
129+
130+ for ( const server of this . servers . values ( ) ) {
131+ try {
132+ // Fetch resources from this server
133+ const url = new URL ( '/.well-known/mcp/resources' , server . url ) ;
134+
135+ // Prepare headers
136+ const headers : Record < string , string > = {
137+ 'Accept' : 'application/json' ,
138+ } ;
139+
140+ // Add authentication if configured
141+ if ( server . auth ) {
142+ if ( server . auth . type === 'bearer' ) {
143+ headers [ 'Authorization' ] = `Bearer ${ server . auth . token } ` ;
144+ }
145+ }
146+
147+ // Make the request
148+ const response = await fetch ( url . toString ( ) , {
149+ method : 'GET' ,
150+ headers,
151+ } ) ;
152+
153+ if ( response . ok ) {
154+ const data = await response . json ( ) as { resources : McpResource [ ] } ;
155+ resources . push ( ...data . resources ) ;
156+ }
157+ } catch ( error ) {
158+ console . error ( `Failed to fetch resources from server ${ server . name } :` , error ) ;
159+ }
160+ }
161+
162+ return resources ;
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+ // In a real implementation, this would be more sophisticated
174+ const match = uri . match ( / ^ ( [ ^ : ] + ) : \/ \/ / ) ;
175+ return match ? match [ 1 ] : undefined ;
176+ }
177+
178+ /**
179+ * Extract the path from a resource URI
180+ * @param uri Resource URI in the format 'scheme://path'
181+ * @returns The path part of the URI
182+ * @private
183+ */
184+ private getPathFromUri ( uri : string ) : string {
185+ // Remove the scheme:// part
186+ return uri . replace ( / ^ [ ^ : ] + : \/ \/ / , '' ) ;
187+ }
188+ }
0 commit comments