Skip to content

Commit 3d963b0

Browse files
authored
mcp: add more useful stats (#42282)
Risk Level: low Testing: Docs Changes: Release Notes: Platform Specific Features: #39174 --------- Signed-off-by: Boteng Yao <[email protected]>
1 parent 0828915 commit 3d963b0

File tree

4 files changed

+60
-22
lines changed

4 files changed

+60
-22
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ namespace HttpFilters {
1010
namespace Mcp {
1111

1212
Http::FilterFactoryCb McpFilterConfigFactory::createFilterFactoryFromProtoTyped(
13-
const envoy::extensions::filters::http::mcp::v3::Mcp& proto_config, const std::string&,
14-
Server::Configuration::FactoryContext&) {
13+
const envoy::extensions::filters::http::mcp::v3::Mcp& proto_config,
14+
const std::string& stats_prefix, Server::Configuration::FactoryContext& context) {
1515

16-
auto config = std::make_shared<McpFilterConfig>(proto_config);
16+
auto config = std::make_shared<McpFilterConfig>(proto_config, stats_prefix,
17+
context.serverFactoryContext().scope());
1718

1819
return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
1920
callbacks.addStreamFilter(std::make_shared<McpFilter>(config));

source/extensions/filters/http/mcp/mcp_filter.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ namespace Extensions {
1010
namespace HttpFilters {
1111
namespace Mcp {
1212

13+
namespace {
14+
McpFilterStats generateStats(const std::string& prefix, Stats::Scope& scope) {
15+
const std::string final_prefix = absl::StrCat(prefix, "mcp.");
16+
return McpFilterStats{MCP_FILTER_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))};
17+
}
18+
} // namespace
19+
20+
McpFilterConfig::McpFilterConfig(const envoy::extensions::filters::http::mcp::v3::Mcp& proto_config,
21+
const std::string& stats_prefix, Stats::Scope& scope)
22+
: traffic_mode_(proto_config.traffic_mode()),
23+
clear_route_cache_(proto_config.clear_route_cache()),
24+
max_request_body_size_(proto_config.has_max_request_body_size()
25+
? proto_config.max_request_body_size().value()
26+
: 8192), // Default: 8KB
27+
stats_(generateStats(stats_prefix, scope)) {}
28+
1329
bool McpFilter::isValidMcpSseRequest(const Http::RequestHeaderMap& headers) const {
1430
// Check if this is a GET request for SSE stream
1531
if (headers.getMethodValue() != Http::Headers::get().MethodValues.Get) {
@@ -111,6 +127,7 @@ Http::FilterHeadersStatus McpFilter::decodeHeaders(Http::RequestHeaderMap& heade
111127

112128
if (!is_mcp_request_ && shouldRejectRequest()) {
113129
ENVOY_LOG(debug, "rejecting non-MCP traffic");
130+
config_->stats().requests_rejected_.inc();
114131
decoder_callbacks_->sendLocalReply(Http::Code::BadRequest, "Only MCP traffic is allowed",
115132
nullptr, absl::nullopt, "mcp_filter_reject_no_mcp");
116133
return Http::FilterHeadersStatus::StopIteration;
@@ -134,6 +151,7 @@ Http::FilterDataStatus McpFilter::decodeData(Buffer::Instance& data, bool end_st
134151

135152
if (total_size > max_size) {
136153
ENVOY_LOG(debug, "request body size {} exceeds maximum {}", total_size, max_size);
154+
config_->stats().body_too_large_.inc();
137155
decoder_callbacks_->sendLocalReply(
138156
Http::Code::PayloadTooLarge,
139157
absl::StrCat("Request body size exceeds maximum allowed size of ", max_size, " bytes"),
@@ -155,6 +173,7 @@ Http::FilterDataStatus McpFilter::decodeData(Buffer::Instance& data, bool end_st
155173
if (!status.ok()) {
156174
is_mcp_request_ = false;
157175
ENVOY_LOG(debug, "failed to parse the JSON");
176+
config_->stats().invalid_json_.inc();
158177
decoder_callbacks_->sendLocalReply(Envoy::Http::Code::BadRequest,
159178
"Request body is not a valid JSON.", nullptr,
160179
absl::nullopt, "");
@@ -173,6 +192,7 @@ Http::FilterDataStatus McpFilter::decodeData(Buffer::Instance& data, bool end_st
173192
is_mcp_request_ = false;
174193
ENVOY_LOG(debug, "non-JSON-RPC 2.0 request is detected");
175194
if (shouldRejectRequest()) {
195+
config_->stats().requests_rejected_.inc();
176196
decoder_callbacks_->sendLocalReply(Http::Code::BadRequest,
177197
"request must be a valid JSON-RPC 2.0 message for MCP",
178198
nullptr, absl::nullopt, "mcp_filter_not_jsonrpc");

source/extensions/filters/http/mcp/mcp_filter.h

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include "envoy/extensions/filters/http/mcp/v3/mcp.pb.h"
77
#include "envoy/http/filter.h"
88
#include "envoy/server/filter_config.h"
9+
#include "envoy/stats/scope.h"
10+
#include "envoy/stats/stats_macros.h"
911

1012
#include "source/common/common/logger.h"
1113
#include "source/common/protobuf/protobuf.h"
@@ -26,17 +28,28 @@ namespace McpConstants {
2628
constexpr absl::string_view JsonRpcVersion = "2.0";
2729
} // namespace McpConstants
2830

31+
/**
32+
* All MCP filter stats. @see stats_macros.h
33+
*/
34+
#define MCP_FILTER_STATS(COUNTER) \
35+
COUNTER(requests_rejected) \
36+
COUNTER(invalid_json) \
37+
COUNTER(body_too_large)
38+
39+
/**
40+
* Struct definition for MCP filter stats. @see stats_macros.h
41+
*/
42+
struct McpFilterStats {
43+
MCP_FILTER_STATS(GENERATE_COUNTER_STRUCT)
44+
};
45+
2946
/**
3047
* Configuration for the MCP filter.
3148
*/
3249
class McpFilterConfig {
3350
public:
34-
explicit McpFilterConfig(const envoy::extensions::filters::http::mcp::v3::Mcp& proto_config)
35-
: traffic_mode_(proto_config.traffic_mode()),
36-
clear_route_cache_(proto_config.clear_route_cache()),
37-
max_request_body_size_(proto_config.has_max_request_body_size()
38-
? proto_config.max_request_body_size().value()
39-
: 8192) {} // Default: 8KB
51+
McpFilterConfig(const envoy::extensions::filters::http::mcp::v3::Mcp& proto_config,
52+
const std::string& stats_prefix, Stats::Scope& scope);
4053

4154
envoy::extensions::filters::http::mcp::v3::Mcp::TrafficMode trafficMode() const {
4255
return traffic_mode_;
@@ -50,10 +63,13 @@ class McpFilterConfig {
5063

5164
uint32_t maxRequestBodySize() const { return max_request_body_size_; }
5265

66+
McpFilterStats& stats() { return stats_; }
67+
5368
private:
5469
const envoy::extensions::filters::http::mcp::v3::Mcp::TrafficMode traffic_mode_;
5570
const bool clear_route_cache_;
5671
const uint32_t max_request_body_size_;
72+
McpFilterStats stats_;
5773
};
5874

5975
/**

test/extensions/filters/http/mcp/mcp_filter_test.cc

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class McpFilterTest : public testing::Test {
2323
// Default config with PASS_THROUGH mode
2424
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
2525
proto_config.set_traffic_mode(envoy::extensions::filters::http::mcp::v3::Mcp::PASS_THROUGH);
26-
config_ = std::make_shared<McpFilterConfig>(proto_config);
26+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
2727
filter_ = std::make_unique<McpFilter>(config_);
2828
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
2929
filter_->setEncoderFilterCallbacks(encoder_callbacks_);
@@ -32,7 +32,7 @@ class McpFilterTest : public testing::Test {
3232
void setupRejectMode() {
3333
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
3434
proto_config.set_traffic_mode(envoy::extensions::filters::http::mcp::v3::Mcp::REJECT_NO_MCP);
35-
config_ = std::make_shared<McpFilterConfig>(proto_config);
35+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
3636
filter_ = std::make_unique<McpFilter>(config_);
3737
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
3838
filter_->setEncoderFilterCallbacks(encoder_callbacks_);
@@ -42,13 +42,14 @@ class McpFilterTest : public testing::Test {
4242
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
4343
proto_config.set_traffic_mode(envoy::extensions::filters::http::mcp::v3::Mcp::PASS_THROUGH);
4444
proto_config.set_clear_route_cache(clear_route_cache);
45-
config_ = std::make_shared<McpFilterConfig>(proto_config);
45+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
4646
filter_ = std::make_unique<McpFilter>(config_);
4747
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
4848
filter_->setEncoderFilterCallbacks(encoder_callbacks_);
4949
}
5050

5151
protected:
52+
NiceMock<Server::Configuration::MockFactoryContext> factory_context_;
5253
NiceMock<Http::MockStreamDecoderFilterCallbacks> decoder_callbacks_;
5354
NiceMock<Http::MockStreamEncoderFilterCallbacks> encoder_callbacks_;
5455
McpFilterConfigSharedPtr config_;
@@ -286,31 +287,31 @@ TEST_F(McpFilterTest, PostWithWrongContentType) {
286287
TEST_F(McpFilterTest, DefaultMaxBodySizeIsEightKB) {
287288
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
288289
// Don't set max_request_body_size, should default to 8KB
289-
auto config = std::make_shared<McpFilterConfig>(proto_config);
290+
auto config = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
290291
EXPECT_EQ(8192u, config->maxRequestBodySize());
291292
}
292293

293294
// Test custom max body size configuration
294295
TEST_F(McpFilterTest, CustomMaxBodySizeConfiguration) {
295296
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
296297
proto_config.mutable_max_request_body_size()->set_value(16384);
297-
auto config = std::make_shared<McpFilterConfig>(proto_config);
298+
auto config = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
298299
EXPECT_EQ(16384u, config->maxRequestBodySize());
299300
}
300301

301302
// Test disabled max body size (0 = no limit)
302303
TEST_F(McpFilterTest, DisabledMaxBodySizeConfiguration) {
303304
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
304305
proto_config.mutable_max_request_body_size()->set_value(0);
305-
auto config = std::make_shared<McpFilterConfig>(proto_config);
306+
auto config = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
306307
EXPECT_EQ(0u, config->maxRequestBodySize());
307308
}
308309

309310
// Test request body under the limit succeeds
310311
TEST_F(McpFilterTest, RequestBodyUnderLimitSucceeds) {
311312
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
312313
proto_config.mutable_max_request_body_size()->set_value(1024); // 1KB limit
313-
config_ = std::make_shared<McpFilterConfig>(proto_config);
314+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
314315
filter_ = std::make_unique<McpFilter>(config_);
315316
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
316317

@@ -339,7 +340,7 @@ TEST_F(McpFilterTest, RequestBodyUnderLimitSucceeds) {
339340
TEST_F(McpFilterTest, RequestBodyExceedingLimitRejected) {
340341
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
341342
proto_config.mutable_max_request_body_size()->set_value(100); // Very small limit
342-
config_ = std::make_shared<McpFilterConfig>(proto_config);
343+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
343344
filter_ = std::make_unique<McpFilter>(config_);
344345
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
345346

@@ -373,7 +374,7 @@ TEST_F(McpFilterTest, RequestBodyExceedingLimitRejected) {
373374
TEST_F(McpFilterTest, RequestBodyWithDisabledLimitAllowsLargeBodies) {
374375
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
375376
proto_config.mutable_max_request_body_size()->set_value(0); // Disable limit
376-
config_ = std::make_shared<McpFilterConfig>(proto_config);
377+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
377378
filter_ = std::make_unique<McpFilter>(config_);
378379
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
379380

@@ -406,7 +407,7 @@ TEST_F(McpFilterTest, RequestBodyWithDisabledLimitAllowsLargeBodies) {
406407
TEST_F(McpFilterTest, RequestBodyExactlyAtLimitSucceeds) {
407408
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
408409
proto_config.mutable_max_request_body_size()->set_value(100); // 100 byte limit
409-
config_ = std::make_shared<McpFilterConfig>(proto_config);
410+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
410411
filter_ = std::make_unique<McpFilter>(config_);
411412
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
412413

@@ -446,7 +447,7 @@ TEST_F(McpFilterTest, RequestBodyExactlyAtLimitSucceeds) {
446447
TEST_F(McpFilterTest, BufferLimitSetForValidMcpPostRequest) {
447448
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
448449
proto_config.mutable_max_request_body_size()->set_value(8192);
449-
config_ = std::make_shared<McpFilterConfig>(proto_config);
450+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
450451
filter_ = std::make_unique<McpFilter>(config_);
451452
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
452453

@@ -463,7 +464,7 @@ TEST_F(McpFilterTest, BufferLimitSetForValidMcpPostRequest) {
463464
TEST_F(McpFilterTest, BufferLimitNotSetWhenDisabled) {
464465
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
465466
proto_config.mutable_max_request_body_size()->set_value(0); // Disabled
466-
config_ = std::make_shared<McpFilterConfig>(proto_config);
467+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
467468
filter_ = std::make_unique<McpFilter>(config_);
468469
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
469470

@@ -481,7 +482,7 @@ TEST_F(McpFilterTest, BodySizeLimitInPassThroughMode) {
481482
envoy::extensions::filters::http::mcp::v3::Mcp proto_config;
482483
proto_config.set_traffic_mode(envoy::extensions::filters::http::mcp::v3::Mcp::PASS_THROUGH);
483484
proto_config.mutable_max_request_body_size()->set_value(50); // Small limit
484-
config_ = std::make_shared<McpFilterConfig>(proto_config);
485+
config_ = std::make_shared<McpFilterConfig>(proto_config, "test.", factory_context_.scope());
485486
filter_ = std::make_unique<McpFilter>(config_);
486487
filter_->setDecoderFilterCallbacks(decoder_callbacks_);
487488

0 commit comments

Comments
 (0)