@@ -19,7 +19,7 @@ import {
1919} from "@modelcontextprotocol/sdk/types.js" ;
2020import { z } from "zod" ;
2121import { zodToJsonSchema } from "zod-to-json-schema" ;
22- import JSZip from "jszip " ;
22+ import { gzipSync } from "node:zlib " ;
2323
2424type ToolInput = {
2525 type : "object" ;
@@ -80,14 +80,13 @@ const GetResourceReferenceSchema = z.object({
8080 . describe ( "ID of the resource to reference (1-100)" ) ,
8181} ) ;
8282
83- const ZipResourcesInputSchema = z . object ( {
84- files : z
85- . record ( z . string ( ) . url ( ) . describe ( "URL of the file to include in the zip" ) )
86- . describe ( "Mapping of file names to URLs to include in the zip" ) ,
83+ const GzipInputSchema = z . object ( {
84+ name : z . string ( ) . describe ( "Name of the output file" ) . default ( "README.md.gz" ) ,
85+ data : z . string ( ) . url ( ) . describe ( "URL or data URI of the file content to compress" ) . default ( "https://raw.githubusercontent.com/modelcontextprotocol/servers/refs/heads/main/README.md" ) ,
8786 outputType : z . enum ( [
8887 'resourceLink' ,
8988 'resource'
90- ] ) . default ( 'resource' ) . describe ( "How the resulting zip file should be returned. 'resourceLink' returns a linked to a resource that can be read later, 'resource' returns a full resource object." ) ,
89+ ] ) . default ( 'resource' ) . describe ( "How the resulting gzipped file should be returned. 'resourceLink' returns a link to a resource that can be read later, 'resource' returns a full resource object." ) ,
9190} ) ;
9291
9392enum ToolName {
@@ -98,7 +97,7 @@ enum ToolName {
9897 GET_TINY_IMAGE = "getTinyImage" ,
9998 ANNOTATED_MESSAGE = "annotatedMessage" ,
10099 GET_RESOURCE_REFERENCE = "getResourceReference" ,
101- ZIP_RESOURCES = "zip " ,
100+ GZIP = "gzip " ,
102101}
103102
104103enum PromptName {
@@ -459,10 +458,9 @@ export const createMcpServer = (): McpServerWrapper => {
459458 inputSchema : zodToJsonSchema ( GetResourceReferenceSchema ) as ToolInput ,
460459 } ,
461460 {
462- name : ToolName . ZIP_RESOURCES ,
463- description :
464- "Compresses the provided resource files (mapping of name to URI, which can be a data URI) to a zip file. Supports multiple output formats: inlined data URI (default), resource link, or full resource object" ,
465- inputSchema : zodToJsonSchema ( ZipResourcesInputSchema ) as ToolInput ,
461+ name : ToolName . GZIP ,
462+ description : "Compresses a single file using gzip compression. Takes a file name and data URI, returns the compressed data as a gzipped resource." ,
463+ inputSchema : zodToJsonSchema ( GzipInputSchema ) as ToolInput ,
466464 } ,
467465 ] ;
468466
@@ -642,75 +640,66 @@ export const createMcpServer = (): McpServerWrapper => {
642640 return { content } ;
643641 }
644642
645- if ( name === ToolName . ZIP_RESOURCES ) {
646- const ZIP_MAX_FETCH_SIZE = Number ( process . env . ZIP_MAX_FETCH_SIZE ?? String ( 10 * 1024 * 1024 ) ) ; // 10 MB default
647- const ZIP_MAX_FETCH_TIME_MILLIS = Number ( process . env . ZIP_MAX_FETCH_TIME_MILLIS ?? String ( 30 * 1000 ) ) ; // 30 seconds default.
643+ if ( name === ToolName . GZIP ) {
644+ const GZIP_MAX_FETCH_SIZE = Number ( process . env . GZIP_MAX_FETCH_SIZE ?? String ( 10 * 1024 * 1024 ) ) ; // 10 MB default
645+ const GZIP_MAX_FETCH_TIME_MILLIS = Number ( process . env . GZIP_MAX_FETCH_TIME_MILLIS ?? String ( 30 * 1000 ) ) ; // 30 seconds default.
648646 // Comma-separated list of allowed domains. Empty means all domains are allowed.
649- const ZIP_ALLOWED_DOMAINS = ( process . env . ZIP_ALLOWED_DOMAINS ?? "raw.githubusercontent.com" ) . split ( "," ) . map ( d => d . trim ( ) . toLowerCase ( ) ) . filter ( d => d . length > 0 ) ;
650-
651- const { files, outputType } = ZipResourcesInputSchema . parse ( args ) ;
652- const zip = new JSZip ( ) ;
647+ const GZIP_ALLOWED_DOMAINS = ( process . env . GZIP_ALLOWED_DOMAINS ?? "raw.githubusercontent.com" ) . split ( "," ) . map ( d => d . trim ( ) . toLowerCase ( ) ) . filter ( d => d . length > 0 ) ;
653648
654- let remainingUploadBytes = ZIP_MAX_FETCH_SIZE ;
655- const uploadEndTime = Date . now ( ) + ZIP_MAX_FETCH_TIME_MILLIS ;
649+ const { name, data : dataUri , outputType } = GzipInputSchema . parse ( args ) ;
656650
657- for ( const [ fileName , urlString ] of Object . entries ( files ) ) {
658- try {
659- if ( remainingUploadBytes <= 0 ) {
660- throw new Error ( `Max upload size of ${ ZIP_MAX_FETCH_SIZE } bytes exceeded` ) ;
651+ try {
652+ const url = new URL ( dataUri ) ;
653+ if ( url . protocol !== 'http:' && url . protocol !== 'https:' && url . protocol !== 'data:' ) {
654+ throw new Error ( `Unsupported URL protocol for ${ dataUri } . Only http, https, and data URLs are supported.` ) ;
655+ }
656+ if ( GZIP_ALLOWED_DOMAINS . length > 0 && ( url . protocol === 'http:' || url . protocol === 'https:' ) ) {
657+ const domain = url . hostname ;
658+ const domainAllowed = GZIP_ALLOWED_DOMAINS . some ( allowedDomain => {
659+ return domain === allowedDomain || domain . endsWith ( `.${ allowedDomain } ` ) ;
660+ } ) ;
661+ if ( ! domainAllowed ) {
662+ throw new Error ( `Domain ${ domain } is not in the allowed domains list.` ) ;
661663 }
664+ }
662665
663- const url = new URL ( urlString ) ;
664- if ( url . protocol !== 'http:' && url . protocol !== 'https:' && url . protocol !== 'data:' ) {
665- throw new Error ( `Unsupported URL protocol for ${ urlString } . Only http, https, and data URLs are supported.` ) ;
666- }
667- if ( ZIP_ALLOWED_DOMAINS . length > 0 && ( url . protocol === 'http:' || url . protocol === 'https:' ) ) {
668- const domain = url . hostname ;
669- const domainAllowed = ZIP_ALLOWED_DOMAINS . some ( allowedDomain => {
670- return domain === allowedDomain || domain . endsWith ( `.${ allowedDomain } ` ) ;
671- } ) ;
672- if ( ! domainAllowed ) {
673- throw new Error ( `Domain ${ domain } is not in the allowed domains list.` ) ;
674- }
675- }
666+ const response = await fetchSafely ( url , {
667+ maxBytes : GZIP_MAX_FETCH_SIZE ,
668+ timeoutMillis : GZIP_MAX_FETCH_TIME_MILLIS
669+ } ) ;
676670
677- const response = await fetchSafely ( url , {
678- maxBytes : remainingUploadBytes ,
679- timeoutMillis : uploadEndTime - Date . now ( )
680- } ) ;
681- remainingUploadBytes -= response . byteLength ;
671+ // Compress the data using gzip
672+ const inputBuffer = Buffer . from ( response ) ;
673+ const compressedBuffer = gzipSync ( inputBuffer ) ;
674+ const blob = compressedBuffer . toString ( "base64" ) ;
682675
683- zip . file ( fileName , response ) ;
684- } catch ( error ) {
685- throw new Error (
686- `Error fetching file ${ urlString } : ${ error instanceof Error ? error . message : String ( error ) } `
687- ) ;
688- }
689- }
676+ const mimeType = "application/gzip" ;
677+ const uri = `test://static/resource/${ ALL_RESOURCES . length + 1 } ` ;
678+ const resource = < Resource > { uri, name, mimeType, blob} ;
690679
691- const blob = await zip . generateAsync ( { type : "base64" } ) ;
692- const mimeType = "application/zip" ;
693- const name = `out_ ${ Date . now ( ) } .zip` ;
694- const uri = `test://static/resource/ ${ ALL_RESOURCES . length + 1 } ` ;
695- const resource = < Resource > { uri , name , mimeType , blob } ;
696- if ( outputType === 'resource' ) {
697- return {
698- content : [ {
699- type : " resource" ,
700- resource
701- } ]
702- } ;
703- } else if ( outputType === 'resourceLink' ) {
704- ALL_RESOURCES . push ( resource ) ;
705- return {
706- content : [ {
707- type : "resource_link" ,
708- mimeType ,
709- uri
710- } ]
711- } ;
712- } else {
713- throw new Error ( `Unknown outputType: ${ outputType } ` ) ;
680+ if ( outputType === 'resource' ) {
681+ return {
682+ content : [ {
683+ type : "resource" ,
684+ resource
685+ } ]
686+ } ;
687+ } else if ( outputType === 'resourceLink' ) {
688+ ALL_RESOURCES . push ( resource ) ;
689+ return {
690+ content : [ {
691+ type : "resource_link" ,
692+ mimeType ,
693+ uri
694+ } ]
695+ } ;
696+ } else {
697+ throw new Error ( `Unknown outputType: ${ outputType } ` ) ;
698+ }
699+ } catch ( error ) {
700+ throw new Error (
701+ `Error processing file ${ dataUri } : ${ error instanceof Error ? error . message : String ( error ) } `
702+ ) ;
714703 }
715704 }
716705
0 commit comments