1
+ /**
2
+ * Base patterns and utilities for MCP server development
3
+ * This provides common functionality that all MCP servers can use
4
+ */
5
+
6
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js' ;
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' ;
8
+ import {
9
+ CallToolRequestSchema ,
10
+ ListToolsRequestSchema ,
11
+ type CallToolRequest ,
12
+ type ListToolsRequest ,
13
+ } from '@modelcontextprotocol/sdk/types.js' ;
14
+
15
+ export interface MCPTool {
16
+ name : string ;
17
+ description : string ;
18
+ inputSchema : object ;
19
+ execute : ( input : any ) => Promise < any > ;
20
+ }
21
+
22
+ export interface MCPServerConfig {
23
+ name : string ;
24
+ version : string ;
25
+ description ?: string ;
26
+ tools : MCPTool [ ] ;
27
+ enableCLI ?: boolean ;
28
+ cliCommands ?: Record < string , ( args : any ) => Promise < void > > ;
29
+ }
30
+
31
+ /**
32
+ * Base class for MCP servers with common functionality
33
+ */
34
+ export class MCPServerBase {
35
+ protected server : Server ;
36
+ protected config : MCPServerConfig ;
37
+ protected tools : Map < string , MCPTool > = new Map ( ) ;
38
+
39
+ constructor ( config : MCPServerConfig ) {
40
+ this . config = config ;
41
+
42
+ this . server = new Server (
43
+ {
44
+ name : config . name ,
45
+ version : config . version ,
46
+ } ,
47
+ {
48
+ capabilities : {
49
+ tools : { } ,
50
+ } ,
51
+ }
52
+ ) ;
53
+
54
+ // Register tools
55
+ config . tools . forEach ( tool => {
56
+ this . tools . set ( tool . name , tool ) ;
57
+ } ) ;
58
+
59
+ this . setupHandlers ( ) ;
60
+ }
61
+
62
+ /**
63
+ * Set up MCP request handlers
64
+ */
65
+ private setupHandlers ( ) : void {
66
+ // List tools handler
67
+ this . server . setRequestHandler ( ListToolsRequestSchema , async ( ) => ( {
68
+ tools : Array . from ( this . tools . values ( ) ) . map ( tool => ( {
69
+ name : tool . name ,
70
+ description : tool . description ,
71
+ inputSchema : tool . inputSchema ,
72
+ } ) ) ,
73
+ } ) ) ;
74
+
75
+ // Call tool handler
76
+ this . server . setRequestHandler ( CallToolRequestSchema , async ( request : CallToolRequest ) => {
77
+ const { name, arguments : args } = request . params ;
78
+
79
+ const tool = this . tools . get ( name ) ;
80
+ if ( ! tool ) {
81
+ throw new Error ( `Unknown tool: ${ name } ` ) ;
82
+ }
83
+
84
+ try {
85
+ const result = await tool . execute ( args ) ;
86
+ return {
87
+ content : [
88
+ {
89
+ type : 'text' ,
90
+ text : typeof result === 'string' ? result : JSON . stringify ( result , null , 2 ) ,
91
+ } ,
92
+ ] ,
93
+ } ;
94
+ } catch ( error ) {
95
+ const errorMessage = error instanceof Error ? error . message : String ( error ) ;
96
+ return {
97
+ content : [
98
+ {
99
+ type : 'text' ,
100
+ text : `Error executing ${ name } : ${ errorMessage } ` ,
101
+ } ,
102
+ ] ,
103
+ isError : true ,
104
+ } ;
105
+ }
106
+ } ) ;
107
+ }
108
+
109
+ /**
110
+ * Add a tool to the server
111
+ */
112
+ addTool ( tool : MCPTool ) : void {
113
+ this . tools . set ( tool . name , tool ) ;
114
+ }
115
+
116
+ /**
117
+ * Remove a tool from the server
118
+ */
119
+ removeTool ( name : string ) : void {
120
+ this . tools . delete ( name ) ;
121
+ }
122
+
123
+ /**
124
+ * Start the MCP server
125
+ */
126
+ async start ( ) : Promise < void > {
127
+ // Check if running as CLI
128
+ if ( this . config . enableCLI && this . isRunningAsCLI ( ) ) {
129
+ await this . runCLI ( ) ;
130
+ return ;
131
+ }
132
+
133
+ // Start as MCP server
134
+ const transport = new StdioServerTransport ( ) ;
135
+ await this . server . connect ( transport ) ;
136
+ }
137
+
138
+ /**
139
+ * Check if running as CLI (not as MCP server)
140
+ */
141
+ private isRunningAsCLI ( ) : boolean {
142
+ // Check for CLI-specific arguments or environment
143
+ return process . argv . includes ( '--cli' ) ||
144
+ process . argv . includes ( 'cli' ) ||
145
+ process . env . MCP_CLI_MODE === 'true' ;
146
+ }
147
+
148
+ /**
149
+ * Run in CLI mode
150
+ */
151
+ private async runCLI ( ) : Promise < void > {
152
+ if ( ! this . config . cliCommands ) {
153
+ console . error ( 'CLI mode enabled but no CLI commands defined' ) ;
154
+ process . exit ( 1 ) ;
155
+ }
156
+
157
+ const command = process . argv [ 2 ] ;
158
+ const cliHandler = this . config . cliCommands [ command ] ;
159
+
160
+ if ( ! cliHandler ) {
161
+ console . error ( `Unknown command: ${ command } ` ) ;
162
+ console . error ( 'Available commands:' , Object . keys ( this . config . cliCommands ) . join ( ', ' ) ) ;
163
+ process . exit ( 1 ) ;
164
+ }
165
+
166
+ try {
167
+ await cliHandler ( process . argv . slice ( 3 ) ) ;
168
+ } catch ( error ) {
169
+ console . error ( 'CLI Error:' , error instanceof Error ? error . message : String ( error ) ) ;
170
+ process . exit ( 1 ) ;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Create a tool with validation
176
+ */
177
+ static createTool ( config : {
178
+ name : string ;
179
+ description : string ;
180
+ inputSchema : object ;
181
+ execute : ( input : any ) => Promise < any > ;
182
+ validate ?: ( input : any ) => boolean | string ;
183
+ } ) : MCPTool {
184
+ return {
185
+ name : config . name ,
186
+ description : config . description ,
187
+ inputSchema : config . inputSchema ,
188
+ execute : async ( input : any ) => {
189
+ // Run validation if provided
190
+ if ( config . validate ) {
191
+ const validation = config . validate ( input ) ;
192
+ if ( validation !== true ) {
193
+ throw new Error ( typeof validation === 'string' ? validation : 'Invalid input' ) ;
194
+ }
195
+ }
196
+
197
+ return config . execute ( input ) ;
198
+ } ,
199
+ } ;
200
+ }
201
+
202
+ /**
203
+ * Helper to create error responses
204
+ */
205
+ protected createErrorResponse ( message : string ) : { content : Array < { type : string ; text : string } > ; isError : boolean } {
206
+ return {
207
+ content : [ { type : 'text' , text : `Error: ${ message } ` } ] ,
208
+ isError : true ,
209
+ } ;
210
+ }
211
+
212
+ /**
213
+ * Helper to create success responses
214
+ */
215
+ protected createSuccessResponse ( data : any ) : { content : Array < { type : string ; text : string } > } {
216
+ return {
217
+ content : [
218
+ {
219
+ type : 'text' ,
220
+ text : typeof data === 'string' ? data : JSON . stringify ( data , null , 2 ) ,
221
+ } ,
222
+ ] ,
223
+ } ;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Utility function to create and start an MCP server
229
+ */
230
+ export async function createMCPServer ( config : MCPServerConfig ) : Promise < void > {
231
+ const server = new MCPServerBase ( config ) ;
232
+ await server . start ( ) ;
233
+ }
234
+
235
+ /**
236
+ * Decorator for tool methods (for class-based tool definitions)
237
+ */
238
+ export function tool ( name : string , description : string , inputSchema : object ) {
239
+ return function ( target : any , propertyKey : string , descriptor : PropertyDescriptor ) {
240
+ // Store tool metadata
241
+ if ( ! target . constructor . _tools ) {
242
+ target . constructor . _tools = [ ] ;
243
+ }
244
+
245
+ target . constructor . _tools . push ( {
246
+ name,
247
+ description,
248
+ inputSchema,
249
+ execute : descriptor . value ,
250
+ } ) ;
251
+ } ;
252
+ }
253
+
254
+ /**
255
+ * Helper to extract tools from a class decorated with @tool
256
+ */
257
+ export function extractToolsFromClass ( instance : any ) : MCPTool [ ] {
258
+ const tools = instance . constructor . _tools || [ ] ;
259
+ return tools . map ( ( tool : any ) => ( {
260
+ ...tool ,
261
+ execute : tool . execute . bind ( instance ) ,
262
+ } ) ) ;
263
+ }
0 commit comments