Skip to content

feat: add Model Context Protocol (MCP) server implementation#1463

Open
YuZhangLarry wants to merge 3 commits intoapache:aifrom
YuZhangLarry:ai
Open

feat: add Model Context Protocol (MCP) server implementation#1463
YuZhangLarry wants to merge 3 commits intoapache:aifrom
YuZhangLarry:ai

Conversation

@YuZhangLarry
Copy link
Copy Markdown

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

  • Modular MCP architecture (core, registry, tools, transport, types)
  • Tool registration for Dubbo resources (cluster, search, service discovery, metrics, application/instance details)
  • Stdio transport for MCP communication
  • Integration tests and live test utilities

Review areas:

  • Core Component

YuZhangLarry and others added 3 commits April 22, 2026 15:51
- 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>
@sonarqubecloud
Copy link
Copy Markdown

@robocanic robocanic requested a review from Copilot April 26, 2026 11:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"`
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
Mesh string `form:"appName"`
Mesh string `form:"mesh"`

Copilot uses AI. Check for mistakes.
}
if res == nil {
c.JSON(http.StatusOK, model.NewBizErrorResp(
bizerror.New(bizerror.NotFoundError, fmt.Sprintf("%s not found", ruleName))))
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
bizerror.New(bizerror.NotFoundError, fmt.Sprintf("%s not found", ruleName))))
bizerror.New(bizerror.NotFoundError, fmt.Sprintf("%s not found", ruleName))))
return

Copilot uses AI. Check for mistakes.
Comment on lines +104 to 110
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
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to 49
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)
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
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")
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'.

Suggested change
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")

Copilot uses AI. Check for mistakes.
a.DubboVersions.Add(instance.ReleaseVersion)
a.Images.Add(instance.Image)
if d := cfg.FindDiscovery(instanceRes.Mesh); d != nil {
a.RegisterClusters.Add(instanceRes.Mesh)
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
a.RegisterClusters.Add(instanceRes.Mesh)
a.RegisterClusters.Add(d.Name)

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +63
return &model.ConfiguratorSearchResp{
Scope: item.Spec.Scope,
CreateTime: "",
Enabled: item.Spec.Enabled,
RuleName: item.Name,
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread pkg/config/loader.go
Comment on lines 30 to 40
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)
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +58
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)
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +107
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()))
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants