@@ -138,7 +138,7 @@ The SDK supports the canonical listener-based configuration format that matches
138138import {
139139 createRealDispatcher ,
140140 createFilterChainFromConfig ,
141- CanonicalConfig
141+ CanonicalConfig ,
142142} from " @mcp/filter-sdk" ;
143143
144144// Create dispatcher
@@ -152,20 +152,20 @@ const config: CanonicalConfig = {
152152 address: {
153153 socket_address: {
154154 address: " 127.0.0.1" ,
155- port_value: 9090
156- }
155+ port_value: 9090 ,
156+ },
157157 },
158158 filter_chains: [
159159 {
160160 filters: [
161161 { name: " http.codec" , type: " http.codec" },
162162 { name: " sse.codec" , type: " sse.codec" },
163- { name: " json_rpc.dispatcher" , type: " json_rpc.dispatcher" }
164- ]
165- }
166- ]
167- }
168- ]
163+ { name: " json_rpc.dispatcher" , type: " json_rpc.dispatcher" },
164+ ],
165+ },
166+ ],
167+ },
168+ ],
169169};
170170
171171// Create filter chain from configuration
@@ -450,6 +450,7 @@ The SDK uses the canonical listener-based configuration format that matches the
450450```
451451
452452This format provides:
453+
453454- Clear separation of network configuration (listeners) from processing logic (filter chains)
454455- Support for multiple listeners on different ports
455456- Consistent structure across C++ and TypeScript implementations
@@ -502,32 +503,36 @@ The `FilterChain` class provides a Koffi-based FFI bridge to C++ filter implemen
502503#### ** Quick Start**
503504
504505``` typescript
505- import { FilterChain } from ' ./filter-chain-ffi' ;
506- import { createRealDispatcher } from ' ./mcp-filter-api' ;
506+ import { FilterChain } from " ./filter-chain-ffi" ;
507+ import { createRealDispatcher } from " ./mcp-filter-api" ;
507508
508509// Create dispatcher
509510const dispatcher = createRealDispatcher ();
510511
511512// Define filter configuration
512513const config = {
513- listeners: [{
514- name: ' filters' ,
515- filter_chains: [{
516- filters: [
517- { type: ' rate_limiter' , name: ' limiter' , config: { rps: 100 } },
518- { type: ' circuit_breaker' , name: ' breaker' , config: { threshold: 5 } },
519- { type: ' metrics' , name: ' metrics' }
520- ]
521- }]
522- }]
514+ listeners: [
515+ {
516+ name: " filters" ,
517+ filter_chains: [
518+ {
519+ filters: [
520+ { type: " rate_limiter" , name: " limiter" , config: { rps: 100 } },
521+ { type: " circuit_breaker" , name: " breaker" , config: { threshold: 5 } },
522+ { type: " metrics" , name: " metrics" },
523+ ],
524+ },
525+ ],
526+ },
527+ ],
523528};
524529
525530// Create and use filter chain
526531const chain = new FilterChain (dispatcher , config );
527532await chain .initialize ();
528533
529- const result = await chain .processIncoming ({ method: ' test' });
530- console .log (' Filter decision:' , result .decision ); // 0=ALLOW, 1=DENY
534+ const result = await chain .processIncoming ({ method: " test" });
535+ console .log (" Filter decision:" , result .decision ); // 0=ALLOW, 1=DENY
531536
532537await chain .shutdown ();
533538chain .destroy ();
@@ -551,44 +556,49 @@ chain.destroy();
551556#### ** API Reference**
552557
553558** Lifecycle:**
559+
554560``` typescript
555- constructor (dispatcher , config ) // Create chain
556- await initialize () // Start processing
557- await shutdown () // Stop gracefully
558- destroy () // Release resources
561+ constructor (dispatcher , config ); // Create chain
562+ await initialize (); // Start processing
563+ await shutdown (); // Stop gracefully
564+ destroy (); // Release resources
559565```
560566
561567** Message Processing:**
568+
562569``` typescript
563- await processIncoming (message ) // Filter incoming message
564- await processOutgoing (message ) // Filter outgoing message
570+ await processIncoming (message ); // Filter incoming message
571+ await processOutgoing (message ); // Filter outgoing message
565572```
566573
567574** Metrics & Stats:**
575+
568576``` typescript
569577await getChainStats () // Get chain statistics
570578await getMetrics (filterName ? ) // Get filter metrics
571579` ` `
572580
573581**Dynamic Configuration:**
582+
574583` ` ` typescript
575- await enableFilter (name ) // Enable a filter
576- await disableFilter (name ) // Disable a filter
577- await exportConfig () // Export current config
584+ await enableFilter (name ); // Enable a filter
585+ await disableFilter (name ); // Disable a filter
586+ await exportConfig (); // Export current config
578587` ` `
579588
580589**Validation:**
590+
581591` ` ` typescript
582- FilterChain .validateConfig (config ) // Validate before creation
592+ FilterChain .validateConfig (config ); // Validate before creation
583593` ` `
584594
585595#### **Usage with Official MCP SDK**
586596
587597The FilterChain can be wrapped in a custom transport to use with the official MCP SDK:
588598
589599` ` ` typescript
590- import { FilterChain } from ' ./filter-chain-ffi' ;
591- import { StdioServerTransport } from ' @modelcontextprotocol/sdk/server/stdio.js' ;
600+ import { FilterChain } from " ./filter-chain-ffi" ;
601+ import { StdioServerTransport } from " @modelcontextprotocol/sdk/server/stdio.js" ;
592602
593603class FilteredTransport {
594604 constructor (baseTransport , filterChain ) {
@@ -598,16 +608,18 @@ class FilteredTransport {
598608
599609 async send(message ) {
600610 const result = await this .filterChain .processOutgoing (message );
601- if (result .decision === 0 ) { // ALLOW
611+ if (result .decision === 0 ) {
612+ // ALLOW
602613 return this .baseTransport .send (result .transformedMessage || message );
603614 }
604615 throw new Error (` Message blocked: ${result .reason } ` );
605616 }
606617
607618 onMessage(handler ) {
608- this .baseTransport .onMessage (async ( message ) => {
619+ this .baseTransport .onMessage (async message => {
609620 const result = await this .filterChain .processIncoming (message );
610- if (result .decision === 0 ) { // ALLOW
621+ if (result .decision === 0 ) {
622+ // ALLOW
611623 handler (result .transformedMessage || message );
612624 }
613625 });
@@ -652,18 +664,22 @@ const stdioTransport = new StdioServerTransport();
652664const transport = new GopherFilteredTransport (stdioTransport , {
653665 dispatcherHandle: dispatcher ,
654666 filterConfig: {
655- listeners: [{
656- name: " hybrid_filters" ,
657- filter_chains: [{
658- filters: [
659- { type: " rate_limiter" , name: " limiter" , config: { rps: 100 } },
660- { type: " circuit_breaker" , name: " breaker" , config: { threshold: 5 } },
661- { type: " metrics" , name: " metrics" , config: { export_port: 9090 } }
662- ]
663- }]
664- }]
667+ listeners: [
668+ {
669+ name: " hybrid_filters" ,
670+ filter_chains: [
671+ {
672+ filters: [
673+ { type: " rate_limiter" , name: " limiter" , config: { rps: 100 } },
674+ { type: " circuit_breaker" , name: " breaker" , config: { threshold: 5 } },
675+ { type: " metrics" , name: " metrics" , config: { export_port: 9090 } },
676+ ],
677+ },
678+ ],
679+ },
680+ ],
665681 },
666- debugLogging: false
682+ debugLogging: false ,
667683});
668684
669685// Use with MCP SDK server
@@ -705,30 +721,34 @@ Note: In hybrid SDK mode, the `address` field is **optional** since the SDK tran
705721
706722` ` ` json
707723{
708- " listeners" : [{
709- " name" : " hybrid_filters" ,
710- " filter_chains" : [{
711- " name" : " default" ,
712- " filters" : [
713- {
714- " name" : " rate_limiter" ,
715- " type" : " rate_limiter" ,
716- " config" : {
717- " requests_per_second" : 100 ,
718- " burst_size" : 20
719- }
720- },
724+ " listeners" : [
725+ {
726+ " name" : " hybrid_filters" ,
727+ " filter_chains" : [
721728 {
722- " name" : " circuit_breaker" ,
723- " type" : " circuit_breaker" ,
724- " config" : {
725- " failure_threshold" : 5 ,
726- " timeout_ms" : 60000
727- }
729+ " name" : " default" ,
730+ " filters" : [
731+ {
732+ " name" : " rate_limiter" ,
733+ " type" : " rate_limiter" ,
734+ " config" : {
735+ " requests_per_second" : 100 ,
736+ " burst_size" : 20
737+ }
738+ },
739+ {
740+ " name" : " circuit_breaker" ,
741+ " type" : " circuit_breaker" ,
742+ " config" : {
743+ " failure_threshold" : 5 ,
744+ " timeout_ms" : 60000
745+ }
746+ }
747+ ]
728748 }
729749 ]
730- }]
731- } ]
750+ }
751+ ]
732752}
733753` ` `
734754
@@ -737,18 +757,18 @@ Note: In hybrid SDK mode, the `address` field is **optional** since the SDK tran
737757` ` ` typescript
738758// Get metrics
739759const metrics = await transport .getMetrics ();
740- console .log (' Total processed:' , metrics .chain .requests_total );
760+ console .log (" Total processed:" , metrics .chain .requests_total );
741761
742762// Enable/disable filters
743- await transport .setFilterEnabled (' limiter' , false ); // Disable rate limiter
744- await transport .setFilterEnabled (' breaker' , true ); // Enable circuit breaker
763+ await transport .setFilterEnabled (" limiter" , false ); // Disable rate limiter
764+ await transport .setFilterEnabled (" breaker" , true ); // Enable circuit breaker
745765
746766// Export current configuration
747767const config = await transport .exportFilterConfig ();
748768
749769// Get queue stats (for backpressure monitoring)
750770const queueStats = transport .getQueueStats ();
751- console .log (' Queued messages:' , queueStats .size );
771+ console .log (" Queued messages:" , queueStats .size );
752772` ` `
753773
754774#### **Error Handling**
@@ -758,10 +778,10 @@ try {
758778 await transport.send(message);
759779} catch (error ) {
760780 if (error instanceof FilterDeniedError ) {
761- console .log (' Message blocked by filter:' , error .reason );
781+ console .log (" Message blocked by filter:" , error .reason );
762782 // Handle rate limiting, circuit breaker open, etc.
763783 } else {
764- console .error (' Transport error:' , error );
784+ console .error (" Transport error:" , error );
765785 }
766786}
767787` ` `
@@ -783,18 +803,22 @@ app.post("/sse", async (req, res) => {
783803 const transport = new GopherFilteredTransport (sseTransport , {
784804 dispatcherHandle: dispatcher ,
785805 filterConfig: {
786- listeners: [{
787- name: " http_filters" ,
788- filter_chains: [{
789- filters: [
790- { type: " rate_limiter" , name: " limiter" , config: { rps: 50 } },
791- { type: " circuit_breaker" , name: " breaker" , config: { threshold: 3 } },
792- { type: " metrics" , name: " metrics" }
793- ]
794- }]
795- }]
806+ listeners: [
807+ {
808+ name: " http_filters" ,
809+ filter_chains: [
810+ {
811+ filters: [
812+ { type: " rate_limiter" , name: " limiter" , config: { rps: 50 } },
813+ { type: " circuit_breaker" , name: " breaker" , config: { threshold: 3 } },
814+ { type: " metrics" , name: " metrics" },
815+ ],
816+ },
817+ ],
818+ },
819+ ],
796820 },
797- onValidationWarning : ( warnings ) => console .warn (' Filter warnings:' , warnings )
821+ onValidationWarning : warnings => console .warn (" Filter warnings:" , warnings ),
798822 });
799823
800824 const server = new Server ({ name: " http-server" , version: " 1.0.0" });
@@ -807,17 +831,19 @@ app.listen(3000);
807831#### **Migration from Pure SDK**
808832
809833**Before (Pure SDK):**
834+
810835` ` ` typescript
811836const transport = new StdioServerTransport ();
812837await server .connect (transport );
813838` ` `
814839
815840**After (With Gopher Filters - One Line Change!):**
841+
816842` ` ` typescript
817- const transport = new GopherFilteredTransport (
818- new StdioServerTransport () ,
819- { dispatcherHandle: dispatcher , filterConfig: config }
820- );
843+ const transport = new GopherFilteredTransport (new StdioServerTransport (), {
844+ dispatcherHandle: dispatcher ,
845+ filterConfig: config ,
846+ } );
821847await server .connect (transport );
822848` ` `
823849
@@ -831,14 +857,14 @@ await server.connect(transport);
831857
832858#### **Comparison: Native vs Hybrid Approach**
833859
834- | Aspect | Native C++ | Hybrid SDK |
835- |--------| ---------------------| --------------------- |
836- | Protocol | Custom C++ | Official SDK |
837- | Filters | C++ | C++ (via wrapper) |
838- | Overhead | ~0.5ms | ~0.66ms |
839- | SDK Updates | Manual | Automatic |
840- | Complexity | High | Low |
841- | Use Case | Full control | Quick adoption |
860+ | Aspect | Native C++ | Hybrid SDK |
861+ | ----------- | ------------ | ----------------- |
862+ | Protocol | Custom C++ | Official SDK |
863+ | Filters | C++ | C++ (via wrapper) |
864+ | Overhead | ~0.5ms | ~0.66ms |
865+ | SDK Updates | Manual | Automatic |
866+ | Complexity | High | Low |
867+ | Use Case | Full control | Quick adoption |
842868
843869See ` examples / configs / hybrid - wrapper - config .json ` for complete configuration examples.
844870
0 commit comments