Skip to content

Commit 3fd93ea

Browse files
authored
Add McpRouterConfig interface to allow route or cluster overrides (#44430)
Add interface for MCP router config to allow per route or cluster overrides. This is needed to integrate with the MCP Cluster. Risk Level: low Testing: unit tests Docs Changes: no Release Notes: no Platform Specific Features: no Signed-off-by: Yan Avlasov <yavlasov@google.com>
1 parent 15d9bde commit 3fd93ea

4 files changed

Lines changed: 56 additions & 30 deletions

File tree

source/extensions/filters/http/mcp_router/config.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Http::FilterFactoryCb McpRouterFilterConfigFactory::createFilterFactoryFromProto
1515
const std::string& stats_prefix, Server::Configuration::FactoryContext& context) {
1616

1717
auto config =
18-
std::make_shared<McpRouterConfig>(proto_config, stats_prefix, context.scope(), context);
18+
std::make_shared<McpRouterConfigImpl>(proto_config, stats_prefix, context.scope(), context);
1919

2020
return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
2121
callbacks.addStreamDecoderFilter(std::make_shared<McpRouterFilter>(config));

source/extensions/filters/http/mcp_router/filter_config.cc

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "source/extensions/filters/http/mcp_router/filter_config.h"
22

3+
#include <utility>
4+
#include <vector>
5+
36
#include "source/extensions/filters/common/mcp/filter_state.h"
47

58
#include "absl/strings/str_cat.h"
@@ -52,16 +55,11 @@ McpRouterStats generateStats(const std::string& prefix, Stats::Scope& scope) {
5255
const std::string final_prefix = absl::StrCat(prefix, "mcp_router.");
5356
return McpRouterStats{MCP_ROUTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))};
5457
}
55-
} // namespace
5658

57-
McpRouterConfig::McpRouterConfig(
58-
const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config,
59-
const std::string& stats_prefix, Stats::Scope& scope,
60-
Server::Configuration::FactoryContext& context)
61-
: factory_context_(context), session_identity_(parseSessionIdentity(proto_config)),
62-
metadata_namespace_(Filters::Common::Mcp::metadataNamespace()),
63-
stats_(generateStats(stats_prefix, scope)) {
64-
for (const auto& server : proto_config.servers()) {
59+
std::vector<McpBackendConfig>
60+
parseBackends(const envoy::extensions::filters::http::mcp_router::v3::McpRouter& config) {
61+
std::vector<McpBackendConfig> result;
62+
for (const auto& server : config.servers()) {
6563
McpBackendConfig backend;
6664
const auto& mcp_cluster = server.mcp_cluster();
6765
backend.name = server.name().empty() ? mcp_cluster.cluster() : server.name();
@@ -70,15 +68,23 @@ McpRouterConfig::McpRouterConfig(
7068
backend.timeout =
7169
std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(mcp_cluster, timeout, 5000));
7270
backend.host_rewrite_literal = mcp_cluster.host_rewrite_literal();
73-
backends_.push_back(std::move(backend));
74-
}
75-
76-
if (backends_.size() == 1) {
77-
default_backend_name_ = backends_[0].name;
71+
result.push_back(std::move(backend));
7872
}
73+
return result;
7974
}
75+
} // namespace
76+
77+
McpRouterConfigImpl::McpRouterConfigImpl(
78+
const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config,
79+
const std::string& stats_prefix, Stats::Scope& scope,
80+
Server::Configuration::FactoryContext& context)
81+
: backends_(parseBackends(proto_config)),
82+
default_backend_name_(backends_.size() == 1 ? backends_[0].name : ""),
83+
factory_context_(context), session_identity_(parseSessionIdentity(proto_config)),
84+
metadata_namespace_(Filters::Common::Mcp::metadataNamespace()),
85+
stats_(generateStats(stats_prefix, scope)) {}
8086

81-
const McpBackendConfig* McpRouterConfig::findBackend(const std::string& name) const {
87+
const McpBackendConfig* McpRouterConfigImpl::findBackend(const std::string& name) const {
8288
for (const auto& backend : backends_) {
8389
if (backend.name == name) {
8490
return &backend;

source/extensions/filters/http/mcp_router/filter_config.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,29 @@ struct McpRouterStats {
8282
*/
8383
class McpRouterConfig {
8484
public:
85-
McpRouterConfig(const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config,
86-
const std::string& stats_prefix, Stats::Scope& scope,
87-
Server::Configuration::FactoryContext& context);
85+
virtual ~McpRouterConfig() = default;
86+
87+
virtual const std::vector<McpBackendConfig>& backends() const = 0;
88+
virtual bool isMultiplexing() const = 0;
89+
virtual const std::string& defaultBackendName() const = 0;
90+
virtual Server::Configuration::FactoryContext& factoryContext() const = 0;
91+
virtual const McpBackendConfig* findBackend(const std::string& name) const = 0;
92+
93+
virtual bool hasSessionIdentity() const = 0;
94+
virtual const SubjectSource& subjectSource() const = 0;
95+
virtual ValidationMode validationMode() const = 0;
96+
virtual bool shouldEnforceValidation() const = 0;
97+
virtual const std::string& metadataNamespace() const = 0;
98+
99+
virtual McpRouterStats& stats() = 0;
100+
};
101+
102+
class McpRouterConfigImpl : public McpRouterConfig {
103+
public:
104+
McpRouterConfigImpl(
105+
const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config,
106+
const std::string& stats_prefix, Stats::Scope& scope,
107+
Server::Configuration::FactoryContext& context);
88108

89109
const std::vector<McpBackendConfig>& backends() const { return backends_; }
90110
bool isMultiplexing() const { return backends_.size() > 1; }

test/extensions/filters/http/mcp_router/mcp_router_test.cc

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ TEST_F(McpRouterConfigTest, MultipleBackendsEnablesMultiplexing) {
6363
server2->mutable_mcp_cluster()->set_cluster("calc_cluster");
6464
server2->mutable_mcp_cluster()->set_path("/mcp/calc");
6565

66-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
66+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
6767

6868
EXPECT_EQ(config.backends().size(), 2);
6969
EXPECT_TRUE(config.isMultiplexing());
@@ -90,7 +90,7 @@ TEST_F(McpRouterConfigTest, SingleBackendSetsDefaultName) {
9090
server->set_name("tools");
9191
server->mutable_mcp_cluster()->set_cluster("tools_cluster");
9292

93-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
93+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
9494

9595
EXPECT_EQ(config.backends().size(), 1);
9696
EXPECT_FALSE(config.isMultiplexing());
@@ -105,7 +105,7 @@ TEST_F(McpRouterConfigTest, DefaultPathWhenNotSpecified) {
105105
server->set_name("test");
106106
server->mutable_mcp_cluster()->set_cluster("test_cluster");
107107

108-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
108+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
109109

110110
const McpBackendConfig* backend = config.findBackend("test");
111111
ASSERT_NE(backend, nullptr);
@@ -120,7 +120,7 @@ TEST_F(McpRouterConfigTest, DefaultMetadataNamespace) {
120120
server->set_name("test");
121121
server->mutable_mcp_cluster()->set_cluster("test_cluster");
122122

123-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
123+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
124124
EXPECT_EQ(config.metadataNamespace(), "envoy.filters.http.mcp");
125125
}
126126

@@ -299,7 +299,7 @@ TEST_F(McpRouterConfigTest, SessionIdentityDisabledByDefault) {
299299
server->set_name("test");
300300
server->mutable_mcp_cluster()->set_cluster("test_cluster");
301301

302-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
302+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
303303
EXPECT_FALSE(config.hasSessionIdentity());
304304
EXPECT_FALSE(config.shouldEnforceValidation());
305305
}
@@ -314,7 +314,7 @@ TEST_F(McpRouterConfigTest, SessionIdentityWithHeaderSource) {
314314
auto* identity = proto_config.mutable_session_identity();
315315
identity->mutable_identity()->mutable_header()->set_name("x-user-id");
316316

317-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
317+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
318318
EXPECT_TRUE(config.hasSessionIdentity());
319319
EXPECT_TRUE(absl::holds_alternative<HeaderSubjectSource>(config.subjectSource()));
320320
EXPECT_FALSE(config.shouldEnforceValidation()); // DISABLED by default
@@ -333,7 +333,7 @@ TEST_F(McpRouterConfigTest, SessionIdentityWithMetadataSource) {
333333
metadata_key->add_path()->set_key("payload");
334334
metadata_key->add_path()->set_key("sub");
335335

336-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
336+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
337337
EXPECT_TRUE(config.hasSessionIdentity());
338338
EXPECT_TRUE(absl::holds_alternative<MetadataSubjectSource>(config.subjectSource()));
339339
}
@@ -351,7 +351,7 @@ TEST_F(McpRouterConfigTest, MetadataKeyPathParsed) {
351351
metadata_key->add_path()->set_key("payload");
352352
metadata_key->add_path()->set_key("sub");
353353

354-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
354+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
355355
const auto& source = absl::get<MetadataSubjectSource>(config.subjectSource());
356356
EXPECT_EQ(source.filter, "jwt");
357357
ASSERT_EQ(source.path_keys.size(), 2);
@@ -371,7 +371,7 @@ TEST_F(McpRouterConfigTest, ValidationModeEnforce) {
371371
identity->mutable_validation()->set_mode(
372372
envoy::extensions::filters::http::mcp_router::v3::ValidationPolicy::ENFORCE);
373373

374-
McpRouterConfig config(proto_config, "test.", *store_.rootScope(), factory_context_);
374+
McpRouterConfigImpl config(proto_config, "test.", *store_.rootScope(), factory_context_);
375375
EXPECT_TRUE(config.hasSessionIdentity());
376376
EXPECT_TRUE(config.shouldEnforceValidation());
377377
EXPECT_EQ(config.validationMode(), ValidationMode::Enforce);
@@ -388,8 +388,8 @@ class McpRouterFilterTest : public testing::Test {
388388

389389
McpRouterConfigSharedPtr
390390
createConfig(const envoy::extensions::filters::http::mcp_router::v3::McpRouter& proto_config) {
391-
return std::make_shared<McpRouterConfig>(proto_config, std::string("test."),
392-
*store_.rootScope(), factory_context_);
391+
return std::make_shared<McpRouterConfigImpl>(proto_config, std::string("test."),
392+
*store_.rootScope(), factory_context_);
393393
}
394394

395395
void setDynamicMetadata(const std::string& filter_name, const std::string& key,

0 commit comments

Comments
 (0)