feat: add Model Context Protocol (MCP) server implementation#1463
feat: add Model Context Protocol (MCP) server implementation#1463YuZhangLarry wants to merge 3 commits intoapache:aifrom
Conversation
- Implement modular MCP architecture (core, registry, tools, transport, types) - Add comprehensive tool registration for Dubbo resources (cluster, search, service discovery, metrics, application/instance details) - Implement stdio transport for MCP communication - Add integration tests and live test utilities - Support Claude Desktop integration via MCP - Add cmd tools for MCP server testing
- Sync pkg/ directory with develop branch for admin startup - Add proto files for zk, application, condition_route, instance - Add resource types and helpers for mesh v1alpha1 - Add discovery, engine, governor, lock components - Add store implementations (dbcommon, memory, mysql, postgres) - Add console counter and mesh features - Add bizerror, constants, util packages - Fix zk.FlagPersistent compatibility issue - Fix DeltaFIFOOptions.Logger compatibility issue - Remove ai-specific handler files (traffic_affinity_rule, grafana) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds an MCP server implementation for Dubbo Admin AI integration while refactoring the Console layer to be mesh-aware, introducing unified biz errors/responses, and improving observability/config infrastructure.
Changes:
- Introduces MCP server/test binaries and related tool registration.
- Refactors Console APIs/models/services to support multi-mesh querying, new dashboard URL generation, and standardized response/error types.
- Adds CounterManager component and expands config schema (engine/discovery/observability/log/store) with validation and YAML support.
Reviewed changes
Copilot reviewed 91 out of 345 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/console/service/configurator_rule.go | Adds mesh-aware pagination/search and lock-protected CRUD for configurator rules |
| pkg/console/service/condition_rule.go | Adds mesh-aware paging/keyword search and lock-protected CRUD for condition rules |
| pkg/console/service/affinity_rule.go | Adds affinity rule service CRUD helpers using resource manager |
| pkg/console/router/router.go | Updates dashboard endpoints to unified Grafana handler; adds /meshes; removes Grafana reverse-proxy route |
| pkg/console/model/tag_rule.go | Standardizes tag rule response types and uses shared constants/success response |
| pkg/console/model/service.go | Updates service request/response models for mesh and new service keying |
| pkg/console/model/overview.go | Changes counters to int64 and distributions to map[string]int64 |
| pkg/console/model/observability.go | Refactors dashboard req/resp models for Grafana URL construction |
| pkg/console/model/mesh.go | Adds mesh response model for listing configured meshes |
| pkg/console/model/instance.go | Refactors instance models to use InstanceResource and updates pagination/result shapes |
| pkg/console/model/configurator_rule.go | Refactors configurator requests to use core paging and standardized success responses |
| pkg/console/model/common.go | Standardizes response envelope using string codes + biz errors; moves paging to coremodel |
| pkg/console/model/application.go | Refactors application models/merge logic to use InstanceResource and adds config response DTOs |
| pkg/console/handler/traffic_affinity_rule.go | Removes old affinity-rule handler helpers (moved/refactored into services) |
| pkg/console/handler/tag_rule.go | Refactors tag rule handlers to use new service APIs, mesh param, and error handling util |
| pkg/console/handler/search.go | Updates global search to new service methods and coremodel paging |
| pkg/console/handler/prometheus.go | Replaces reverse-proxy with direct HTTP call to PrometheusBaseURL query endpoint |
| pkg/console/handler/overview.go | Switches overview computation to CounterManager distributions and mesh filtering |
| pkg/console/handler/observability.go | Unifies Grafana dashboard endpoint and adds Prometheus base URL endpoint behavior |
| pkg/console/handler/mesh.go | Adds ListMeshes endpoint for configured discoveries |
| pkg/console/handler/configurator_rule.go | Refactors configurator handlers to mesh-aware service methods and new errors |
| pkg/console/handler/condition_rule.go | Refactors condition rule handlers to mesh-aware service methods and JSON binding |
| pkg/console/handler/auth.go | Standardizes auth errors via bizerror and changes success payloads |
| pkg/console/counter/manager.go | Adds event-driven counter manager for counts and distributions (mesh-scoped) |
| pkg/console/counter/counter.go | Adds thread-safe counters and grouped distribution counters |
| pkg/console/counter/component.go | Adds runtime component to initialize/bind counters to store/events |
| pkg/console/context/context.go | Adds CounterManager and LockManager to console context; removes ResourceStore from context API |
| pkg/console/component.go | Switches to gin.New + zap middleware, adjusts component order/deps, and standardizes auth failure response |
| pkg/config/store/config.go | Adds store types, YAML tags, and config validation |
| pkg/config/schema/gvk/resources.gen.go | Removes generated GVK mapping file |
| pkg/config/observability/config.go | Adds observability config with URL parsing/validation (Grafana/Prometheus) |
| pkg/config/mode/config.go | Removes legacy DeployMode constants/comments |
| pkg/config/log/config.go | Adds log config schema and validation |
| pkg/config/loader.go | Requires config file and removes env overlay; adds preprocessing and improved wrapping |
| pkg/config/engine/config.go | Expands engine config schema (id/type/properties) and validation/defaults |
| pkg/config/discovery/config.go | Expands discovery config (id/list/properties), preprocess defaults, and validation |
| pkg/config/diagnostics/config.go | Adds port validation and removes env tag |
| pkg/config/console/config.go | Updates console config schema (ginMode, grafana dashboards) and validation defaults |
| pkg/config/app/admin.go | Major admin config schema change: discovery list, log/observability, preprocess/postprocess, validation, helpers |
| pkg/common/util/discovery/discovery.go | Adds helper for registry name resolution with default fallback |
| pkg/common/log/logger.go | Minor formatting cleanup |
| pkg/common/constants/rule.go | Adds rule suffix/version constants and shared rule defaults |
| pkg/common/constants/nacos.go | Adds Nacos group constants and provider key pattern |
| pkg/common/constants/lock.go | Adds distributed lock timeouts and lock key prefixes |
| pkg/common/constants/constants.go | Consolidates many shared constants used across discovery/admin logic |
| pkg/common/constants/common.go | Adds separators/common constants and default mesh constant |
| pkg/common/bizerror/error.go | Introduces bizerror interfaces, codes, and wrapping |
| pkg/common/bizerror/common.go | Adds common bizerror constructors (unauthorized, mesh not found, etc.) |
| openspec/config.yaml | Adds OpenSpec config scaffold |
| cmd/mcp-test/main.go | Adds MCP runtime/tool test utility (interactive + scripted) |
| cmd/mcp-test-example/test-config.yaml | Adds example config for MCP testing |
| cmd/mcp-test-example/main.go | Adds example MCP tool invocation runner |
| cmd/mcp-server/main.go | Adds MCP server main with stdio transport and tool registration |
| app/dubbo-admin/dubbo-admin.yaml | Updates sample config to new schema defaults (console auth, store type, discovery fields) |
| app/dubbo-admin/cmd/run.go | Removes logging of removed mode field |
| api/mesh/v1alpha1/zk_metadata.proto | Adds ZKMetadata resource proto |
| api/mesh/v1alpha1/zk_metadata.pb.go | Adds generated ZKMetadata Go bindings |
| api/mesh/v1alpha1/zk_config.proto | Adds ZKConfig resource proto |
| api/mesh/v1alpha1/zk_config.pb.go | Adds generated ZKConfig Go bindings |
| api/mesh/v1alpha1/service_provider_metadata.proto | Adds service provider metadata resource proto |
| api/mesh/v1alpha1/service_provider_mapping.proto | Adds service provider mapping resource proto |
| api/mesh/v1alpha1/service_provider_mapping.pb.go | Adds generated service provider mapping Go bindings |
| api/mesh/v1alpha1/service_consumer_metadata.proto | Adds service consumer metadata resource proto |
| api/mesh/v1alpha1/runtime_instance_helper.go | Adds runtime instance constants helper |
| api/mesh/v1alpha1/runtime_instance.proto | Adds runtime instance proto (engine-derived instance shape) |
| api/mesh/v1alpha1/rpc_instance_metadata.proto | Adds RPC instance metadata proto |
| api/mesh/v1alpha1/rpc_instance.proto | Adds RPC instance proto (discovery-derived instance shape) |
| api/mesh/v1alpha1/nacos_service.proto | Adds nacos raw-service proto |
| api/mesh/v1alpha1/nacos_config.proto | Adds nacos raw-config proto |
| api/mesh/v1alpha1/nacos_config.pb.go | Adds generated nacos config Go bindings |
| api/mesh/v1alpha1/instance.proto | Redefines Instance proto as merged Runtime+RPC instance model |
| api/mesh/v1alpha1/condition_route_helper.go | Removes YAML/version helpers and switches equality constant to shared constants |
| api/mesh/v1alpha1/condition_route.proto | Redefines ConditionRoute proto/schema (removes v3/v3x1 oneof) |
| api/mesh/v1alpha1/application.proto | Redefines Application proto to include instanceCount |
| api/mesh/v1alpha1/application.pb.go | Updates generated Application Go bindings for new schema |
| ai/component/agent/react/react.go | Makes memory context request-scoped and avoids storing think/act messages |
| ai/component/agent/react/component.go | Injects memory context from runtime memory component into ReAct agent |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func (req *AppDashboardReq) GetKeyVariable() string { | ||
| return req.Application | ||
| AppName string `form:"appName"` | ||
| Mesh string `form:"appName"` |
There was a problem hiding this comment.
AppDashboardReq.Mesh is tagged as form:\"appName\", so mesh will never bind from query parameters and will overwrite/duplicate appName binding semantics. This should be tagged as form:\"mesh\" (and similarly validated/used downstream) to avoid generating incorrect Grafana URLs and mesh-scoped queries.
| Mesh string `form:"appName"` | |
| Mesh string `form:"mesh"` |
| } | ||
| if res == nil { | ||
| c.JSON(http.StatusOK, model.NewBizErrorResp( | ||
| bizerror.New(bizerror.NotFoundError, fmt.Sprintf("%s not found", ruleName)))) |
There was a problem hiding this comment.
After returning a NotFound response when res == nil, the handler continues and dereferences res.Spec, which will panic. Add a return after the NotFound JSON response to stop execution.
| bizerror.New(bizerror.NotFoundError, fmt.Sprintf("%s not found", ruleName)))) | |
| bizerror.New(bizerror.NotFoundError, fmt.Sprintf("%s not found", ruleName)))) | |
| return |
| if configurator == nil { | ||
| c.JSON(http.StatusOK, model.NewBizErrorResp( | ||
| bizerror.New(bizerror.NotFoundError, fmt.Sprintf("%s not found", ruleName)))) | ||
| } | ||
| if err = service.UpdateConfigurator(ctx, res); err != nil { | ||
| c.JSON(http.StatusOK, model.NewErrorResp(err.Error())) | ||
| return |
There was a problem hiding this comment.
Same control-flow issue as the GET handler: when configurator == nil, the handler writes a NotFound response but then continues to update anyway. Add return after the NotFound response, otherwise the API can incorrectly report success/error after already responding.
| instances, _ := service.SearchInstanceByIp(ctx, req) | ||
| res = convertInstancesToSearchRes(instances) | ||
| case "instanceName": | ||
| instances, _ := service.BannerSearchInstances(ctx, req) | ||
| instances, _ := service.SearchInstanceByName(ctx, req) | ||
| res = convertInstancesToSearchRes(instances) | ||
| case "appName": | ||
| applications, _ := service.BannerSearchApplications(ctx, req) | ||
| applications, _ := service.SearchApplicationsByKeywords(ctx, req) | ||
| res = convertApplicationsToSearchRes(applications) |
There was a problem hiding this comment.
Errors are ignored (_ := ...) and the conversion helpers assume non-nil results; if the service returns an error (and/or nil), this can lead to panics or misleading empty responses. Handle the returned error and validate instances/applications are non-nil before converting; route failures through the existing error response mechanism.
| func (d *Config) Validate() error { | ||
| func (c *Config) Validate() error { | ||
| if c.ServerPort < 1 || c.ServerPort > 65535 { | ||
| return bizerror.New(bizerror.ConfigError, "server port for diagnotics must between 1 to 65535") |
There was a problem hiding this comment.
Typo in error message: 'diagnotics' should be 'diagnostics' (and the phrasing is ungrammatical). Consider updating to a clearer message such as 'server port for diagnostics must be between 1 and 65535'.
| return bizerror.New(bizerror.ConfigError, "server port for diagnotics must between 1 to 65535") | |
| return bizerror.New(bizerror.ConfigError, "server port for diagnostics must be between 1 and 65535") |
| a.DubboVersions.Add(instance.ReleaseVersion) | ||
| a.Images.Add(instance.Image) | ||
| if d := cfg.FindDiscovery(instanceRes.Mesh); d != nil { | ||
| a.RegisterClusters.Add(instanceRes.Mesh) |
There was a problem hiding this comment.
This condition checks that a discovery exists, but then adds instanceRes.Mesh to RegisterClusters instead of using discovery attributes (e.g., d.Name) or consistently using the discovery ID. As written, d is only used as a nil-check, and the collected 'clusters' may be IDs rather than user-facing names. Use a consistent value (either d.Name or the mesh ID) and remove the redundant lookup if you intend to store IDs.
| a.RegisterClusters.Add(instanceRes.Mesh) | |
| a.RegisterClusters.Add(d.Name) |
| return &model.ConfiguratorSearchResp{ | ||
| Scope: item.Spec.Scope, | ||
| CreateTime: "", | ||
| Enabled: item.Spec.Enabled, | ||
| RuleName: item.Name, | ||
| } |
There was a problem hiding this comment.
CreateTime is always returned as an empty string for configurator rules, unlike condition rules where creation timestamp is populated. If the underlying resource provides timestamps (e.g., item.CreationTimestamp), populate it here to avoid regressions in list UX/API consumers expecting meaningful createTime.
| func Load(file string, cfg Config) error { | ||
| return LoadWithOption(file, cfg, false, true, true) | ||
| return LoadWithOption(file, cfg, false, true) | ||
| } | ||
|
|
||
| func LoadWithOption(file string, cfg Config, strict bool, includeEnv bool, validate bool) error { | ||
| func LoadWithOption(file string, cfg Config, strict bool, validate bool) error { | ||
| if file == "" { | ||
| core.Log.WithName("config").Info("skipping reading config from file") | ||
| } else if err := loadFromFile(file, cfg, strict); err != nil { | ||
| return err | ||
| return bizerror.New(bizerror.ConfigError, "config file is needed") | ||
| } | ||
| if err := loadFromFile(file, cfg, strict); err != nil { | ||
| return fmt.Errorf("configuration loading failed, %w", err) | ||
| } |
There was a problem hiding this comment.
This changes config loading behavior to require a config file and removes environment-variable overlay support (previously supported via envconfig.Process). That’s a breaking behavior change for deployments relying on env-only configuration. If backward compatibility is required, consider keeping env overlay optional (or at least preserving the previous LoadWithOption(... includeEnv ...) path) and documenting the new requirement.
| u := *promBaseUrl | ||
| u.RawQuery = c.Request.URL.RawQuery | ||
| u.Path = "/api/v1/query" | ||
| s := u.String() | ||
| resp, err := http.Get(s) | ||
| if err != nil { | ||
| c.JSON(http.StatusOK, model.NewBizErrorResp( | ||
| bizerror.New(bizerror.NetWorkError, err.Error()))) | ||
| return | ||
| } | ||
| defer resp.Body.Close() | ||
|
|
||
| body, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| c.JSON(http.StatusOK, model.NewBizErrorResp( | ||
| bizerror.New(bizerror.NetWorkError, err.Error()))) | ||
| return | ||
| } | ||
| proxy := &httputil.ReverseProxy{Director: director} | ||
| proxy.ServeHTTP(c.Writer, c.Request) | ||
| c.Data(http.StatusOK, resp.Header.Get("Content-Type"), body) |
There was a problem hiding this comment.
The handler uses http.Get without any timeout and ignores Prometheus response status codes (always returns http.StatusOK to the client). This can hang requests under network issues and can mask upstream 4xx/5xx as successful responses. Use an http.Client with a reasonable timeout (and preferably http.NewRequestWithContext using c.Request.Context()), and propagate resp.StatusCode when returning c.Data(...) (or map it appropriately).
| res := meshresource.NewTagRouteResourceWithAttributes(ruleName, mesh) | ||
| err = c.Bind(res.Spec) | ||
| if err != nil { | ||
| c.JSON(http.StatusBadRequest, model.NewErrorResp(err.Error())) | ||
| c.JSON(http.StatusOK, model.NewErrorResp(err.Error())) | ||
| return | ||
| } | ||
| if err = service.UpdateTagRule(ctx, name, res); err != nil { | ||
| c.JSON(http.StatusBadRequest, model.NewErrorResp(err.Error())) | ||
| if err = service.UpdateTagRule(ctx, res); err != nil { | ||
| c.JSON(http.StatusOK, model.NewErrorResp(err.Error())) |
There was a problem hiding this comment.
Binding and update failures are returned with http.StatusOK, while other handlers in this PR use http.StatusBadRequest (binding) and util.HandleServiceError (service failures). Returning 200 for invalid input makes client-side error handling harder and is inconsistent within the same API surface. Consider returning http.StatusBadRequest for bind errors and using the shared util.HandleServiceError for service-layer errors.



Add Model Context Protocol (MCP) server implementation for Dubbo Admin AI integration.
Review areas: