Skip to content

Commit 0cea048

Browse files
YuZhangLarryclaude
andcommitted
feat(mcp): add Model Context Protocol (MCP) server with HTTP transport and authentication
## Summary Implement Model Context Protocol (MCP) server for Dubbo Admin to enable AI integration through standardized tool interfaces. ## Key Features - **Modular Architecture**: Core components (server, registry, tools, transport, types) - **Comprehensive Tool Support**: 11 tools covering cluster info, service discovery, instance management, metrics, and application details - **Dual Transport Support**: - Stdio transport for local Claude Desktop integration - HTTP transport for remote connections with JSON-RPC 2.0 - **Security**: Optional Bearer Token authentication for HTTP endpoint ## Configuration ## Test plan - [x] Unit tests for core components - [x] Integration tests - [x] Manual testing with Claude Desktop Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1df75cd commit 0cea048

30 files changed

Lines changed: 5631 additions & 46 deletions

pkg/config/app/admin.go

Lines changed: 123 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,82 +18,176 @@
1818
package app
1919

2020
import (
21-
"github.com/pkg/errors"
21+
"github.com/duke-git/lancet/v2/slice"
2222
"go.uber.org/multierr"
2323

24+
"github.com/apache/dubbo-admin/pkg/common/bizerror"
2425
"github.com/apache/dubbo-admin/pkg/config"
2526
"github.com/apache/dubbo-admin/pkg/config/console"
2627
"github.com/apache/dubbo-admin/pkg/config/diagnostics"
2728
"github.com/apache/dubbo-admin/pkg/config/discovery"
2829
"github.com/apache/dubbo-admin/pkg/config/engine"
29-
"github.com/apache/dubbo-admin/pkg/config/mode"
30+
"github.com/apache/dubbo-admin/pkg/config/log"
31+
"github.com/apache/dubbo-admin/pkg/config/observability"
3032
"github.com/apache/dubbo-admin/pkg/config/store"
3133
)
3234

3335
type AdminConfig struct {
3436
config.BaseConfig
35-
// Mode in which dubbo admin is running. Available values are: "test", "global", "zone"
36-
Mode mode.Mode `json:"mode" envconfig:"DUBBO_MODE"`
37+
// Log configuration
38+
Log *log.Config `json:"log" yaml:"log"`
3739
// Diagnostics configuration
38-
Diagnostics *diagnostics.Config `json:"diagnostics,omitempty"`
40+
Diagnostics *diagnostics.Config `json:"diagnostics,omitempty" yaml:"diagnostics"`
41+
// Observability configuration
42+
Observability *observability.Config `json:"observability" yaml:"observability"`
3943
// Console configuration
40-
Console *console.Config `json:"admin"`
44+
Console *console.Config `json:"console" yaml:"console"`
4145
// Store configuration
42-
Store *store.Config `json:"store"`
46+
Store *store.Config `json:"store" yaml:"store"`
4347
// Discovery configuration
44-
Discovery *discovery.Config `json:"discovery"`
48+
Discovery []*discovery.Config `json:"discovery" yaml:"discovery"`
4549
// Engine configuration
46-
Engine *engine.Config `json:"engine"`
50+
Engine *engine.Config `json:"engine" yaml:"engine"`
51+
// MCP configuration
52+
MCP *MCPConfig `json:"mcp,omitempty" yaml:"mcp"`
53+
}
54+
55+
// MCPConfig MCP配置
56+
type MCPConfig struct {
57+
// Enabled 是否启用MCP端点
58+
Enabled bool `json:"enabled" yaml:"enabled"`
59+
// Path MCP端点路径,默认 /api/mcp
60+
Path string `json:"path,omitempty" yaml:"path"`
61+
// APIKey MCP API密钥,用于认证。如果为空则不需要认证
62+
APIKey string `json:"apiKey,omitempty" yaml:"apiKey"`
4763
}
4864

4965
var _ = &AdminConfig{}
5066

51-
func (c *AdminConfig) Sanitize() {
67+
var DefaultAdminConfig = func() AdminConfig {
68+
return AdminConfig{
69+
Log: log.DefaultLogConfig(),
70+
Store: store.DefaultStoreConfig(),
71+
Engine: engine.DefaultResourceEngineConfig(),
72+
Observability: observability.DefaultObservabilityConfig(),
73+
Diagnostics: diagnostics.DefaultDiagnosticsConfig(),
74+
Console: console.DefaultConsoleConfig(),
75+
}
76+
}
77+
78+
func (c AdminConfig) Sanitize() {
5279
c.Engine.Sanitize()
53-
c.Discovery.Sanitize()
80+
for _, d := range c.Discovery {
81+
d.Sanitize()
82+
}
5483
c.Store.Sanitize()
5584
c.Console.Sanitize()
85+
c.Observability.Sanitize()
5686
c.Diagnostics.Sanitize()
87+
c.Log.Sanitize()
5788
}
5889

59-
func (c *AdminConfig) PostProcess() error {
90+
func (c AdminConfig) PreProcess() error {
91+
discoveryPreProcess := func() error {
92+
for _, d := range c.Discovery {
93+
if err := d.PreProcess(); err != nil {
94+
return err
95+
}
96+
}
97+
return nil
98+
}
99+
return multierr.Combine(
100+
c.Engine.PreProcess(),
101+
discoveryPreProcess(),
102+
c.Store.PreProcess(),
103+
c.Console.PreProcess(),
104+
c.Observability.PreProcess(),
105+
c.Diagnostics.PreProcess(),
106+
c.Log.PreProcess(),
107+
)
108+
}
109+
110+
func (c AdminConfig) PostProcess() error {
111+
discoveryPostProcess := func() error {
112+
for _, d := range c.Discovery {
113+
if err := d.PostProcess(); err != nil {
114+
return err
115+
}
116+
}
117+
return nil
118+
}
60119
return multierr.Combine(
61120
c.Engine.PostProcess(),
62-
c.Discovery.PostProcess(),
121+
discoveryPostProcess(),
63122
c.Store.PostProcess(),
64123
c.Console.PostProcess(),
124+
c.Observability.PostProcess(),
65125
c.Diagnostics.PostProcess(),
126+
c.Log.PostProcess(),
66127
)
67128
}
68129

69-
var DefaultAdminConfig = func() AdminConfig {
70-
return AdminConfig{
71-
Mode: mode.Zone,
72-
Store: store.DefaultStoreConfig(),
73-
Engine: engine.DefaultResourceEngineConfig(),
74-
Diagnostics: diagnostics.DefaultDiagnosticsConfig(),
75-
Console: console.DefaultConsoleConfig(),
76-
}
77-
}
78-
79-
func (c *AdminConfig) Validate() error {
80-
if err := mode.ValidateMode(c.Mode); err != nil {
81-
return errors.Wrap(err, "Mode Config validation failed")
130+
func (c AdminConfig) Validate() error {
131+
if c.Log == nil {
132+
c.Log = log.DefaultLogConfig()
133+
} else if err := c.Log.Validate(); err != nil {
134+
return bizerror.Wrap(err, bizerror.ConfigError, "log config validation failed")
82135
}
83136
if c.Store == nil {
84137
c.Store = store.DefaultStoreConfig()
85138
} else if err := c.Store.Validate(); err != nil {
86-
return errors.Wrap(err, "Store Config validation failed")
139+
return bizerror.Wrap(err, bizerror.ConfigError, "store config validation failed")
87140
}
88141
if c.Diagnostics == nil {
89142
c.Diagnostics = diagnostics.DefaultDiagnosticsConfig()
90143
} else if err := c.Diagnostics.Validate(); err != nil {
91-
return errors.Wrap(err, "Diagnostics Config validation failed")
144+
return bizerror.Wrap(err, bizerror.ConfigError, "diagnostics config validation failed")
92145
}
93146
if c.Console == nil {
94147
c.Console = console.DefaultConsoleConfig()
95148
} else if err := c.Console.Validate(); err != nil {
96-
return errors.Wrap(err, "Admin validation failed")
149+
return bizerror.Wrap(err, bizerror.ConfigError, "console config validation failed")
150+
}
151+
if c.Observability == nil {
152+
c.Observability = observability.DefaultObservabilityConfig()
153+
} else if err := c.Observability.Validate(); err != nil {
154+
return bizerror.Wrap(err, bizerror.ConfigError, "observability config validation failed")
155+
}
156+
if c.Discovery == nil || len(c.Discovery) == 0 {
157+
return bizerror.New(bizerror.ConfigError, "discover config is needed")
158+
}
159+
for _, d := range c.Discovery {
160+
if err := d.Validate(); err != nil {
161+
return bizerror.Wrap(err, bizerror.ConfigError, "discovery config validation failed")
162+
}
163+
}
164+
discoveryIDList := slice.Map(c.Discovery, func(index int, item *discovery.Config) string {
165+
return item.ID
166+
})
167+
if len(discoveryIDList) != len(slice.Unique(discoveryIDList)) {
168+
return bizerror.New(bizerror.ConfigError, "discovery id must be unique")
169+
}
170+
if c.Engine == nil {
171+
c.Engine = engine.DefaultResourceEngineConfig()
172+
} else if err := c.Engine.Validate(); err != nil {
173+
return bizerror.Wrap(err, bizerror.ConfigError, "engine config validation failed")
97174
}
98175
return nil
99176
}
177+
178+
// FindDiscovery finds the DiscoveryConfig by id, returns nil if not found
179+
func (c AdminConfig) FindDiscovery(id string) *discovery.Config {
180+
for _, d := range c.Discovery {
181+
if d.ID == id {
182+
return d
183+
}
184+
}
185+
return nil
186+
}
187+
188+
// Meshes return the mesh id list of discoveries
189+
func (c AdminConfig) Meshes() []string {
190+
return slice.Map(c.Discovery, func(index int, item *discovery.Config) string {
191+
return item.ID
192+
})
193+
}

0 commit comments

Comments
 (0)