@@ -10,11 +10,20 @@ import {
1010 McpError ,
1111} from '@modelcontextprotocol/sdk/types.js' ;
1212import axios , { AxiosInstance , AxiosRequestConfig , Method } from 'axios' ;
13- import { VERSION , PACKAGE_NAME , SERVER_NAME } from './version.js' ;
13+ import { VERSION , SERVER_NAME } from './version.js' ;
1414
1515if ( ! process . env . REST_BASE_URL ) {
1616 throw new Error ( 'REST_BASE_URL environment variable is required' ) ;
1717}
18+
19+ // Default response size limit: 10KB (10000 bytes)
20+ const RESPONSE_SIZE_LIMIT = process . env . REST_RESPONSE_SIZE_LIMIT
21+ ? parseInt ( process . env . REST_RESPONSE_SIZE_LIMIT , 10 )
22+ : 10000 ;
23+
24+ if ( isNaN ( RESPONSE_SIZE_LIMIT ) || RESPONSE_SIZE_LIMIT <= 0 ) {
25+ throw new Error ( 'REST_RESPONSE_SIZE_LIMIT must be a positive number' ) ;
26+ }
1827const AUTH_BASIC_USERNAME = process . env . AUTH_BASIC_USERNAME ;
1928const AUTH_BASIC_PASSWORD = process . env . AUTH_BASIC_PASSWORD ;
2029const AUTH_BEARER = process . env . AUTH_BEARER ;
@@ -29,6 +38,35 @@ interface EndpointArgs {
2938 headers ? : Record < string , string > ;
3039}
3140
41+ interface ValidationResult {
42+ isError: boolean ;
43+ messages: string [ ] ;
44+ truncated ?: {
45+ originalSize : number ;
46+ returnedSize: number ;
47+ truncationPoint: number ;
48+ sizeLimit: number ;
49+ } ;
50+ }
51+
52+ interface ResponseObject {
53+ request: {
54+ url: string ;
55+ method: string ;
56+ headers: Record < string , string | undefined > ;
57+ body: any ;
58+ authMethod: string ;
59+ } ;
60+ response: {
61+ statusCode: number ;
62+ statusText: string ;
63+ timing: string ;
64+ headers: Record < string , any > ;
65+ body: any ;
66+ } ;
67+ validation: ValidationResult ;
68+ }
69+
3270const isValidEndpointArgs = ( args : any ) : args is EndpointArgs => {
3371 if ( typeof args !== 'object' || args === null ) return false ;
3472 if ( ! [ 'GET' , 'POST' , 'PUT' , 'DELETE' ] . includes ( args . method ) ) return false ;
@@ -145,7 +183,7 @@ class RestTester {
145183 this . server . setRequestHandler ( ListToolsRequestSchema , async ( ) => ( {
146184 tools : [
147185 {
148- name : 'endpoint ' ,
186+ name : 'test_request ' ,
149187 description : `Test a REST API endpoint and get detailed response information.
150188
151189Base URL: ${ process . env . REST_BASE_URL }
@@ -166,6 +204,7 @@ The tool automatically:
166204- Normalizes endpoints (adds leading slash, removes trailing slashes)
167205- Handles authentication header injection
168206- Accepts any HTTP status code as valid
207+ - Limits response size to ${ RESPONSE_SIZE_LIMIT } bytes (configurable via REST_RESPONSE_SIZE_LIMIT)
169208- Returns detailed response information including:
170209 * Full URL called
171210 * Status code and text
@@ -211,7 +250,7 @@ Error Handling:
211250 } ) ) ;
212251
213252 this . server . setRequestHandler ( CallToolRequestSchema , async ( request ) => {
214- if ( request . params . name !== 'endpoint ' ) {
253+ if ( request . params . name !== 'test_request ' ) {
215254 throw new McpError (
216255 ErrorCode . MethodNotFound ,
217256 `Unknown tool: ${ request . params . name } `
@@ -271,32 +310,62 @@ Error Handling:
271310 else if ( hasBearerAuth ( ) ) authMethod = 'bearer' ;
272311 else if ( hasApiKeyAuth ( ) ) authMethod = 'apikey' ;
273312
313+ // Prepare response object
314+ const responseObj : ResponseObject = {
315+ request : {
316+ url : fullUrl ,
317+ method : config . method || 'GET' ,
318+ headers : config . headers as Record < string , string | undefined > ,
319+ body : config . data ,
320+ authMethod
321+ } ,
322+ response : {
323+ statusCode : response . status ,
324+ statusText : response . statusText ,
325+ timing : `${ endTime - startTime } ms` ,
326+ headers : response . headers as Record < string , any> ,
327+ body : response . data ,
328+ } ,
329+ validation : {
330+ isError : response . status >= 400 ,
331+ messages : response . status >= 400 ?
332+ [ `Request failed with status ${ response . status } ` ] :
333+ [ 'Request completed successfully' ]
334+ }
335+ } ;
336+
337+ // Check response size
338+ const stringified = JSON . stringify ( responseObj , null , 2 ) ;
339+ const totalBytes = Buffer . from ( stringified ) . length ;
340+
341+ if ( totalBytes > RESPONSE_SIZE_LIMIT ) {
342+ // Convert body to string if it isn't already
343+ const bodyStr = typeof response . data === 'string'
344+ ? response . data
345+ : JSON . stringify ( response . data ) ;
346+
347+ // Calculate how much we need to truncate
348+ const currentSize = Buffer . from ( bodyStr ) . length ;
349+ const targetSize = Math . max ( 0 , currentSize - ( totalBytes - RESPONSE_SIZE_LIMIT ) ) ;
350+
351+ // Create truncated response
352+ responseObj . response . body = bodyStr . slice ( 0 , targetSize ) ;
353+ responseObj . validation . messages . push (
354+ `Response truncated: ${ targetSize } of ${ currentSize } bytes returned due to size limit (${ RESPONSE_SIZE_LIMIT } bytes)`
355+ ) ;
356+ responseObj . validation . truncated = {
357+ originalSize : currentSize ,
358+ returnedSize : targetSize ,
359+ truncationPoint : targetSize ,
360+ sizeLimit : RESPONSE_SIZE_LIMIT
361+ } ;
362+ }
363+
274364 return {
275365 content : [
276366 {
277367 type : 'text' ,
278- text : JSON . stringify ( {
279- request : {
280- url : fullUrl ,
281- method : config . method ,
282- headers : config . headers ,
283- body : config . data ,
284- authMethod
285- } ,
286- response : {
287- statusCode : response . status ,
288- statusText : response . statusText ,
289- timing : `${ endTime - startTime } ms` ,
290- headers : response . headers ,
291- body : response . data ,
292- } ,
293- validation : {
294- isError : response . status >= 400 ,
295- messages : response . status >= 400 ?
296- [ `Request failed with status ${ response . status } ` ] :
297- [ 'Request completed successfully' ]
298- }
299- } , null , 2 ) ,
368+ text : JSON . stringify ( responseObj , null , 2 ) ,
300369 } ,
301370 ] ,
302371 } ;
0 commit comments