-
Notifications
You must be signed in to change notification settings - Fork 28
[feat] 中间件架构与核心实现 #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jadeproheshan
wants to merge
1
commit into
trpc-group:main
Choose a base branch
from
trpc-mcp-team:feat/middleware
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5dc8c33 to
87af127
Compare
This Pull Request introduces a pluggable middleware architecture to the `trpc-mcp-go` server, inspired by the robust Filter pattern in `tRPC-Go`. This architecture provides a powerful mechanism for handling cross-cutting concerns in a modular and reusable way.
In addition to the core engine, this PR delivers three fundamental and critical middleware implementations: `Recovery`, `Logging`, and `Metrics`, laying a solid foundation for future functional extensions.
Notably, this integration effort also drove significant improvements to the core engine. We identified and fixed several key underlying bugs and ultimately established a clear, sustainable development model for future contributors to reference.
---
- **Pluggable Architecture**: Implemented a `MiddlewareChain` that wraps the final request handler in an "onion model," allowing requests to flow sequentially through a series of middlewares before reaching the business logic.
- **Seamless Integration**: The engine is directly integrated into `mcpHandler.handleRequest`, ensuring that all incoming requests automatically pass through the middleware chain.
- **Robustness**: Added a defensive null pointer check in `handler.go` to prevent panics when the `server` is not initialized (e.g., in `SSEServer` tests), ensuring backward compatibility.
- **Panic Safety**: Catches panics occurring anywhere in the request lifecycle (including other middlewares and the final handler).
- **Standard-Compliant Error Response**: Returns a clean, JSON-RPC 2.0 compliant `Internal Server Error` response, avoiding the exposure of stack traces or other internal details to the client.
- **Highly Configurable**: Supports advanced options such as custom loggers, stack trace control, and panic filters.
- **Usage**:
The `Recovery` middleware should be registered as the first (outermost) middleware to ensure it can catch all subsequent panics.
```go
// Default usage for basic panic recovery
s.Use(middlewares.Recovery())
// Advanced usage, e.g., with a custom error response
s.Use(middlewares.RecoveryWithOptions(
middlewares.WithCustomErrorResponse(func(ctx context.Context, req *mcp.JSONRPCRequest, panicErr interface{}) mcp.JSONRPCMessage {
// Return a more user-friendly, custom error message
return mcp.NewJSONRPCErrorResponse(
req.ID,
mcp.ErrCodeInternal,
"Service is temporarily unavailable. Please try again later.",
nil,
)
}),
))
```
- **Structured Logging**: Provides structured, leveled logging for the entire request lifecycle (request start, request end, request failure).
- **Adheres to Project Standards**: Refactored to fully adapt to the project's standard `mcp.Logger` interface, making it a "good citizen" within the ecosystem.
- **Rich Context**: Supports configurable logging of request/response payloads and can extract custom fields from the `context` to be included in logs.
- **Usage**:
This middleware offers multiple configuration options for flexible logging.
```go
// Import the project's standard logger
logger := mcp.GetDefaultLogger()
// Register the middleware, enabling all request logs and payload recording
s.Use(middlewares.NewLoggingMiddleware(logger,
// The default is to only log errors; this option logs all requests
middlewares.WithShouldLog(func(level logging.Level, duration time.Duration, err error) bool {
return true
}),
// Log the detailed content of requests and responses
middlewares.WithPayloadLogging(true),
))
```
- **Based on OpenTelemetry**: Built on the OpenTelemetry standard, ensuring broad compatibility with modern observability platforms (like Prometheus, Jaeger).
- **Core Metrics**: Provides out-of-the-box monitoring of core metrics:
- `mcp.server.requests.count`: Total number of requests
- `mcp.server.errors.count`: Number of failed requests
- `mcp.server.request.latency`: Request latency histogram
- `mcp.server.requests.in_flight`: Number of in-flight requests
- **Usage**:
The `metrics` middleware depends on an `OpenTelemetry Collector` and `Prometheus`. We have provided a `docker-compose.yaml` file in the `examples/middlewares/metrics/` directory to start all dependencies with a single command.
**Verification Steps**:
1. `cd examples/middlewares/metrics/`
2. `docker-compose up -d`
3. Run the `metrics_integration_main.go` example and send some requests.
4. Access `http://localhost:9090` to query the above core metrics in the Prometheus UI.
---
**Status**: All changes have been rebased on the latest `main` branch (`dd0bd82e69c7ae947a66059264c20940f46a4eb5`) in the `feat/middleware-final` branch. All tests pass, ensuring this PR can be merged without conflicts.
We not only added middleware functionality but also made necessary improvements and fixes to the core engine during the integration process.
- **Symptom**: In the early stages of development, we lacked a standard way to independently test middlewares.
- **Reason**: Testing middlewares requires simulating `request`, `session`, and `next` functions, which is tedious to write manually.
- **Solution**: We created a new `mcptest` public testing package, providing `RunMiddlewareTest` and `CheckMiddlewareFunc` helper functions. This greatly simplified the unit testing of middlewares and enabled parallel development within the team.
- **Symptom**: E2E tests exposed a `nil pointer dereference` panic after middleware integration.
- **Reason**: `SSEServer` did not provide a `server` instance when creating `mcpHandler`, causing the program to crash when accessing `h.server.middlewares`.
- **Solution**:
- Implemented the `MiddlewareChain` core engine in `middleware.go`.
- Integrated the engine into the `handleRequest` method of `handler.go` and added a null pointer check for `handler.server`.
- **Impact**: This change elegantly resolved the panic with minimal intrusion and without modifying any test files, ensuring backward compatibility. The scope of the change is limited to the `handleRequest` function.
- **Symptom**: When a middleware returned a `*JSONRPCError`, the HTTP handler would incorrectly wrap it within the `result` field, violating the JSON-RPC 2.0 specification.
- **Reason**: The `handlePostRequest` function incorrectly assumed that all returns from `mcpHandler` were successful results.
- **Solution**: We introduced a type check in `handlePostRequest`. If the response type is already `*JSONRPCError`, it will be sent directly to the client.
- **Impact**: This change is limited to the `handlePostRequest` function and ensures the correctness of error handling.
- **Symptom**: The server would silently discard all requests to the root path (`/`) when no path was explicitly configured.
- **Reason**: A logic flaw in the `isValidPath` check.
- **Solution**: Although this fix was ultimately applied in the integration tests (via `mcp.WithServerPath("")`), identifying this behavior was crucial for correctly documenting the server's usage.
- **Impact**: No code change, but contributed important "best practice" documentation to the project.
- **Problem**: The internal function for creating error responses (`newJSONRPCErrorResponse`) was not exported and was used inconsistently throughout the codebase.
- **Solution**: We performed a global refactoring, renaming it to `NewJSONRPCErrorResponse` and unifying all call sites.
- **Impact**: This was a safe refactoring guaranteed by Go's compile-time checks. It affected multiple `manager_*.go` and `server_*.go` files but was limited to function call modifications and did not alter any business logic.
---
This large-scale feature integration allowed us to establish and document a set of best practices for future development.
We established a clear workflow:
1. **Write Tests First**: For any new middleware, start by writing a minimal, end-to-end integration test in `examples/middleware_usage/server/`.
2. **Let Tests Drive Development**: Run the test and let compiler and runtime errors guide all necessary fixes, refactoring, and adaptations.
This model proved to be invaluable in ensuring quality and accelerating development.
We adopted a two-tier convention for organizing example code:
- **Simple Middlewares**: Single-file, self-contained middlewares are placed directly in `examples/middlewares/`.
- **Complex Middlewares**: Modules with multiple files, documentation, and configurations (like `metrics`) are given their own subdirectories and separate packages (e.g., `examples/middlewares/metrics/`) to maintain high cohesion.
---
The following is a brief example demonstrating how to configure all three new middlewares on a single server:
```go
package main
import (
"context"
"fmt"
"net/http"
mcp "trpc.group/trpc-go/trpc-mcp-go"
"trpc.group.com/trpc-go/trpc-mcp-go/examples/middlewares"
metricmw "trpc.group.com/trpc-go/trpc-mcp-go/examples/middlewares/metrics"
)
func main() {
// 1. Create a new server instance
s := mcp.NewServer(
"my-server", "1.0.0",
mcp.WithStatelessMode(true),
mcp.WithServerPath(""),
)
// 2. Set up and register the Metrics middleware
rec, shutdown, _ := metricmw.NewOtelMetricsRecorder()
defer func() { _ = shutdown(context.Background()) }()
s.Use(metricmw.NewMetricsMiddleware(metricmw.WithRecorder(rec)))
// 3. Register the Logging and Recovery middlewares
// Note: The Recovery middleware should generally be the first (outermost) middleware
s.Use(middlewares.Recovery())
s.Use(middlewares.NewLoggingMiddleware(mcp.GetDefaultLogger()))
// 4. Register your business tools...
// s.RegisterTool(...)
// 5. Start the server
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", s.Handler())
}
```
Co-authored-by: Wang jia <RangelJara195@gmail.com>
Co-authored-by: Lin Yuze <jadeproheshan@gmail.com>
Co-authored-by: Chang Mingyue <xmkdgdz@foxmail.com>
Co-authored-by: Chen Lei <123213112a@gmail.com>
87af127 to
acf22a8
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
本次 Pull Request 为
trpc-mcp-go服务器引入了一套可插拔的中间件架构,其设计灵感来源于tRPC-Go中经过验证的 Filter 模式。该架构为以模块化、可复用的方式处理横切关注点提供了强大的支持。除了核心引擎外,本次 PR 还交付了三个基础且关键的中间件实现:
Recovery(异常恢复)、Logging(日志记录) 和Metrics(指标监控),为未来的功能扩展奠定了坚实的基础。1. 新增功能:核心特性与实现
a. 中间件引擎 (
middleware.go,handler.go)MiddlewareChain(中间件链),它以“洋葱模型”将最终的请求处理器包裹起来,允许请求在触达业务逻辑前,按顺序流经一系列中间件。mcpHandler.handleRequest中,确保了所有到来的请求都能自动地通过中间件链进行处理。handler.go中增加了防御性的空指针检查,以防止在server未初始化的情况下(例如在SSEServer的测试中)产生 panic,保证了向后兼容性。b. Recovery 中间件 (
examples/middlewares/recovery.go)panic。Internal Server Error响应,避免了向客户端暴露堆栈跟踪等内部细节。panic过滤器等高级选项。Recovery中间件应作为第一个(最外层的)中间件来注册,以确保它能捕获后续所有环节的 panic。c. Logging 中间件 (
examples/middlewares/logging.go)日志中间件为 tRPC-MCP-Go 服务器提供请求/响应日志记录功能,支持结构化日志、自定义过滤和多种输出格式。该中间件设计用于帮助开发者监控服务器请求、调试问题以及分析性能。
功能特性
该中间件提供多个配置选项,允许进行灵活的日志记录。
d. Metrics 中间件 (
examples/middlewares/metrics/)mcp.server.requests.count: 请求总数mcp.server.errors.count: 失败请求数mcp.server.request.latency: 请求延迟直方图mcp.server.requests.in_flight: 在途请求数metrics中间件依赖OpenTelemetry Collector和Prometheus。我们已在examples/middlewares/metrics/目录下提供了docker-compose.yaml文件,用于一键启动所有依赖。验证步骤:
cd examples/middlewares/metrics/docker-compose up -dmetrics_integration_main.go示例并发送一些请求。http://localhost:9090,在 Prometheus UI 中查询以上核心指标。2. 对原有代码的更改
状态: 所有改动已在
feat/middleware-final分支上,基于最新的main分支 (dd0bd8) 完成变基。经测试,所有功能正常,可确保本次 PR 无冲突合入。a. 新增
mcptest测试工具包request,session和next函数,手动编写很繁琐。mcptest公共测试包,提供了RunMiddlewareTest和CheckMiddlewareFunc两个辅助函数,极大地简化了中间件的单元测试编写。b. [修复]
handler.go: 集成中间件引擎并增强健壮性e2e测试在集成中间件后,暴露出nil pointer dereference的 panic。SSEServer在创建mcpHandler时未提供server实例,导致访问h.server.middlewares时程序崩溃。middleware.go中实现了MiddlewareChain核心引擎。handler.go的handleRequest方法中集成了该引擎,并增加了对handler.server的空指针检查。handleRequest函数内部。c. [修复]
streamable_server.go: 不合规的错误响应嵌套问题*JSONRPCError时,HTTP 处理器会错误地将其包裹在result字段内,违反了 JSON-RPC 2.0 规范。handlePostRequest函数错误地假设了所有来自mcpHandler的返回都是成功的结果。handlePostRequest中引入了类型检查。如果响应的类型已经是*JSONRPCError,它将被直接发送给客户端。handlePost-Request函数,确保了错误处理的规范性。d. [修复] 根路径请求被静默丢弃的问题
/) 的请求。isValidPath检查中的一个逻辑缺陷。mcp.WithServerPath("")),但识别出这一行为对于正确地文档化服务器的用法至关重要。e. [修复] 全局统一错误处理
newJSONRPCErrorResponse) 未被导出,且在代码库中被不一致地使用。NewJSONRPCErrorResponse,并统一了所有调用点。manager_*.go和server_*.go文件,但仅限于函数调用层面的修改,未改变任何业务逻辑。3.中间件存放约定
我们采纳了一套双层约定来组织示例代码:
examples/middlewares/。metrics),则拥有自己的子目录和独立的包(如examples/middlewares/metrics/),以保持其高内聚性。4. 如何使用
以下是一个简短的示例,展示了如何在一个服务器上同时配置这三个新的中间件:
Co-authored-by:
middleware-core: jadeproheshan https://github.com/jadeproheshan
middleware-metrics: no-regret666 https://github.com/no-regret666
middleware-recovery: xmkdgdz https://github.com/xmkdgdz
middleware-log: et21ff https://github.com/et21ff