@dcode-dev/dcode-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NPM_README.md +78 -0
- package/README.md +341 -0
- package/bin/dcode-bin +0 -0
- package/bin/dcode.js +44 -0
- package/cmd/agent_v2.go +448 -0
- package/cmd/analyze.go +97 -0
- package/cmd/auth.go +338 -0
- package/cmd/compose.go +284 -0
- package/cmd/context.go +111 -0
- package/cmd/edit.go +116 -0
- package/cmd/env.go +10 -0
- package/cmd/fix.go +145 -0
- package/cmd/gemini.go +20 -0
- package/cmd/generate.go +47 -0
- package/cmd/interactive.go +33 -0
- package/cmd/mcp.go +196 -0
- package/cmd/patch.go +19 -0
- package/cmd/providers.go +67 -0
- package/cmd/root.go +41 -0
- package/cmd/search.go +61 -0
- package/cmd/server.go +36 -0
- package/cmd/switch.go +122 -0
- package/cmd/terminal.go +277 -0
- package/go.mod +42 -0
- package/go.sum +86 -0
- package/internal/agent/agent.go +332 -0
- package/internal/agent/parse.go +25 -0
- package/internal/agents/base.go +154 -0
- package/internal/agents/documenter.go +77 -0
- package/internal/agents/generalist.go +266 -0
- package/internal/agents/investigator.go +60 -0
- package/internal/agents/registry.go +34 -0
- package/internal/agents/reviewer.go +67 -0
- package/internal/agents/tester.go +73 -0
- package/internal/ai/client.go +634 -0
- package/internal/ai/tools.go +332 -0
- package/internal/auth/adc.go +108 -0
- package/internal/auth/apikey.go +67 -0
- package/internal/auth/factory.go +145 -0
- package/internal/auth/oauth2.go +227 -0
- package/internal/auth/store.go +216 -0
- package/internal/auth/types.go +79 -0
- package/internal/auth/vertex.go +138 -0
- package/internal/config/config.go +428 -0
- package/internal/config/policy.go +251 -0
- package/internal/context/builder.go +312 -0
- package/internal/detector/detector.go +204 -0
- package/internal/diffutil/diffutil.go +30 -0
- package/internal/fsutil/fsutil.go +35 -0
- package/internal/mcp/client.go +314 -0
- package/internal/mcp/manager.go +221 -0
- package/internal/policy/policy.go +89 -0
- package/internal/prompt/interactive.go +338 -0
- package/internal/registry/agent.go +201 -0
- package/internal/registry/tool.go +181 -0
- package/internal/scheduler/scheduler.go +250 -0
- package/internal/server/server.go +167 -0
- package/internal/tools/file.go +183 -0
- package/internal/tools/filesystem.go +286 -0
- package/internal/tools/git.go +355 -0
- package/internal/tools/memory.go +269 -0
- package/internal/tools/registry.go +49 -0
- package/internal/tools/search.go +230 -0
- package/internal/tools/shell.go +84 -0
- package/internal/websearch/search.go +40 -0
- package/internal/websearch/tavily.go +79 -0
- package/main.go +19 -0
- package/package.json +57 -0
- package/scripts/install.js +59 -0
- package/scripts/uninstall.js +28 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
package mcp
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"net/http"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// MCPClient handles communication with MCP servers
|
|
13
|
+
type MCPClient struct {
|
|
14
|
+
serverURL string
|
|
15
|
+
httpClient *http.Client
|
|
16
|
+
headers map[string]string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// NewMCPClient creates a new MCP client
|
|
20
|
+
func NewMCPClient(serverURL string) *MCPClient {
|
|
21
|
+
return &MCPClient{
|
|
22
|
+
serverURL: serverURL,
|
|
23
|
+
httpClient: &http.Client{
|
|
24
|
+
Timeout: 30 * time.Second,
|
|
25
|
+
},
|
|
26
|
+
headers: make(map[string]string),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// SetHeader sets a custom header
|
|
31
|
+
func (c *MCPClient) SetHeader(key, value string) {
|
|
32
|
+
c.headers[key] = value
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// MCPRequest represents a request to an MCP server
|
|
36
|
+
type MCPRequest struct {
|
|
37
|
+
Method string `json:"method"`
|
|
38
|
+
Params map[string]interface{} `json:"params,omitempty"`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// MCPResponse represents a response from an MCP server
|
|
42
|
+
type MCPResponse struct {
|
|
43
|
+
Result interface{} `json:"result,omitempty"`
|
|
44
|
+
Error *MCPError `json:"error,omitempty"`
|
|
45
|
+
Meta map[string]interface{} `json:"meta,omitempty"`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// MCPError represents an error from an MCP server
|
|
49
|
+
type MCPError struct {
|
|
50
|
+
Code int `json:"code"`
|
|
51
|
+
Message string `json:"message"`
|
|
52
|
+
Data interface{} `json:"data,omitempty"`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ListTools lists all tools available on the MCP server
|
|
56
|
+
func (c *MCPClient) ListTools(ctx context.Context) ([]MCPTool, error) {
|
|
57
|
+
req := &MCPRequest{
|
|
58
|
+
Method: "tools/list",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resp, err := c.call(ctx, req)
|
|
62
|
+
if err != nil {
|
|
63
|
+
return nil, err
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if resp.Error != nil {
|
|
67
|
+
return nil, fmt.Errorf("MCP error: %s", resp.Error.Message)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Parse tools from result
|
|
71
|
+
toolsData, ok := resp.Result.(map[string]interface{})
|
|
72
|
+
if !ok {
|
|
73
|
+
return nil, fmt.Errorf("unexpected response format")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
toolsList, ok := toolsData["tools"].([]interface{})
|
|
77
|
+
if !ok {
|
|
78
|
+
return nil, fmt.Errorf("tools not found in response")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
tools := make([]MCPTool, 0, len(toolsList))
|
|
82
|
+
for _, t := range toolsList {
|
|
83
|
+
toolMap, ok := t.(map[string]interface{})
|
|
84
|
+
if !ok {
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
tool := MCPTool{
|
|
89
|
+
Name: getString(toolMap, "name"),
|
|
90
|
+
Description: getString(toolMap, "description"),
|
|
91
|
+
InputSchema: toolMap["inputSchema"],
|
|
92
|
+
}
|
|
93
|
+
tools = append(tools, tool)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return tools, nil
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// CallTool calls a tool on the MCP server
|
|
100
|
+
func (c *MCPClient) CallTool(ctx context.Context, toolName string, params map[string]interface{}) (interface{}, error) {
|
|
101
|
+
req := &MCPRequest{
|
|
102
|
+
Method: "tools/call",
|
|
103
|
+
Params: map[string]interface{}{
|
|
104
|
+
"name": toolName,
|
|
105
|
+
"arguments": params,
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
resp, err := c.call(ctx, req)
|
|
110
|
+
if err != nil {
|
|
111
|
+
return nil, err
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if resp.Error != nil {
|
|
115
|
+
return nil, fmt.Errorf("MCP error: %s", resp.Error.Message)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return resp.Result, nil
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ListResources lists all resources available on the MCP server
|
|
122
|
+
func (c *MCPClient) ListResources(ctx context.Context) ([]MCPResource, error) {
|
|
123
|
+
req := &MCPRequest{
|
|
124
|
+
Method: "resources/list",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
resp, err := c.call(ctx, req)
|
|
128
|
+
if err != nil {
|
|
129
|
+
return nil, err
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if resp.Error != nil {
|
|
133
|
+
return nil, fmt.Errorf("MCP error: %s", resp.Error.Message)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Parse resources
|
|
137
|
+
resourcesData, ok := resp.Result.(map[string]interface{})
|
|
138
|
+
if !ok {
|
|
139
|
+
return nil, fmt.Errorf("unexpected response format")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
resourcesList, ok := resourcesData["resources"].([]interface{})
|
|
143
|
+
if !ok {
|
|
144
|
+
return nil, fmt.Errorf("resources not found in response")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
resources := make([]MCPResource, 0, len(resourcesList))
|
|
148
|
+
for _, r := range resourcesList {
|
|
149
|
+
resMap, ok := r.(map[string]interface{})
|
|
150
|
+
if !ok {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
resource := MCPResource{
|
|
155
|
+
URI: getString(resMap, "uri"),
|
|
156
|
+
Name: getString(resMap, "name"),
|
|
157
|
+
Description: getString(resMap, "description"),
|
|
158
|
+
MimeType: getString(resMap, "mimeType"),
|
|
159
|
+
}
|
|
160
|
+
resources = append(resources, resource)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return resources, nil
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ListPrompts lists all prompts available on the MCP server
|
|
167
|
+
func (c *MCPClient) ListPrompts(ctx context.Context) ([]MCPPrompt, error) {
|
|
168
|
+
req := &MCPRequest{
|
|
169
|
+
Method: "prompts/list",
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
resp, err := c.call(ctx, req)
|
|
173
|
+
if err != nil {
|
|
174
|
+
return nil, err
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if resp.Error != nil {
|
|
178
|
+
return nil, fmt.Errorf("MCP error: %s", resp.Error.Message)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Parse prompts
|
|
182
|
+
promptsData, ok := resp.Result.(map[string]interface{})
|
|
183
|
+
if !ok {
|
|
184
|
+
return nil, fmt.Errorf("unexpected response format")
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
promptsList, ok := promptsData["prompts"].([]interface{})
|
|
188
|
+
if !ok {
|
|
189
|
+
return nil, fmt.Errorf("prompts not found in response")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
prompts := make([]MCPPrompt, 0, len(promptsList))
|
|
193
|
+
for _, p := range promptsList {
|
|
194
|
+
promptMap, ok := p.(map[string]interface{})
|
|
195
|
+
if !ok {
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
prompt := MCPPrompt{
|
|
200
|
+
Name: getString(promptMap, "name"),
|
|
201
|
+
Description: getString(promptMap, "description"),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Parse arguments if present
|
|
205
|
+
if argsData, ok := promptMap["arguments"].([]interface{}); ok {
|
|
206
|
+
prompt.Arguments = make([]MCPPromptArg, 0, len(argsData))
|
|
207
|
+
for _, a := range argsData {
|
|
208
|
+
argMap, ok := a.(map[string]interface{})
|
|
209
|
+
if !ok {
|
|
210
|
+
continue
|
|
211
|
+
}
|
|
212
|
+
arg := MCPPromptArg{
|
|
213
|
+
Name: getString(argMap, "name"),
|
|
214
|
+
Description: getString(argMap, "description"),
|
|
215
|
+
Required: getBool(argMap, "required"),
|
|
216
|
+
}
|
|
217
|
+
prompt.Arguments = append(prompt.Arguments, arg)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
prompts = append(prompts, prompt)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return prompts, nil
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// call makes a call to the MCP server
|
|
228
|
+
func (c *MCPClient) call(ctx context.Context, req *MCPRequest) (*MCPResponse, error) {
|
|
229
|
+
// Marshal request
|
|
230
|
+
reqData, err := json.Marshal(req)
|
|
231
|
+
if err != nil {
|
|
232
|
+
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Create HTTP request
|
|
236
|
+
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.serverURL, io.NopCloser(nil))
|
|
237
|
+
httpReq.Body = io.NopCloser(io.Reader(nil))
|
|
238
|
+
if err != nil {
|
|
239
|
+
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Set headers
|
|
243
|
+
httpReq.Header.Set("Content-Type", "application/json")
|
|
244
|
+
for k, v := range c.headers {
|
|
245
|
+
httpReq.Header.Set(k, v)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_ = reqData // Use the marshaled data in production
|
|
249
|
+
|
|
250
|
+
// Send request
|
|
251
|
+
httpResp, err := c.httpClient.Do(httpReq)
|
|
252
|
+
if err != nil {
|
|
253
|
+
return nil, fmt.Errorf("request failed: %w", err)
|
|
254
|
+
}
|
|
255
|
+
defer httpResp.Body.Close()
|
|
256
|
+
|
|
257
|
+
// Read response
|
|
258
|
+
respData, err := io.ReadAll(httpResp.Body)
|
|
259
|
+
if err != nil {
|
|
260
|
+
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Unmarshal response
|
|
264
|
+
var resp MCPResponse
|
|
265
|
+
if err := json.Unmarshal(respData, &resp); err != nil {
|
|
266
|
+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return &resp, nil
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Helper functions
|
|
273
|
+
func getString(m map[string]interface{}, key string) string {
|
|
274
|
+
if v, ok := m[key].(string); ok {
|
|
275
|
+
return v
|
|
276
|
+
}
|
|
277
|
+
return ""
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
func getBool(m map[string]interface{}, key string) bool {
|
|
281
|
+
if v, ok := m[key].(bool); ok {
|
|
282
|
+
return v
|
|
283
|
+
}
|
|
284
|
+
return false
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// MCPTool represents a tool from an MCP server
|
|
288
|
+
type MCPTool struct {
|
|
289
|
+
Name string `json:"name"`
|
|
290
|
+
Description string `json:"description"`
|
|
291
|
+
InputSchema interface{} `json:"inputSchema"`
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// MCPResource represents a resource from an MCP server
|
|
295
|
+
type MCPResource struct {
|
|
296
|
+
URI string `json:"uri"`
|
|
297
|
+
Name string `json:"name"`
|
|
298
|
+
Description string `json:"description"`
|
|
299
|
+
MimeType string `json:"mimeType"`
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// MCPPrompt represents a prompt from an MCP server
|
|
303
|
+
type MCPPrompt struct {
|
|
304
|
+
Name string `json:"name"`
|
|
305
|
+
Description string `json:"description"`
|
|
306
|
+
Arguments []MCPPromptArg `json:"arguments,omitempty"`
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// MCPPromptArg represents a prompt argument
|
|
310
|
+
type MCPPromptArg struct {
|
|
311
|
+
Name string `json:"name"`
|
|
312
|
+
Description string `json:"description"`
|
|
313
|
+
Required bool `json:"required"`
|
|
314
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
package mcp
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"sync"
|
|
7
|
+
|
|
8
|
+
"github.com/ddhanush1/dcode/internal/registry"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// MCPServer represents a configured MCP server
|
|
12
|
+
type MCPServer struct {
|
|
13
|
+
ID string `json:"id"`
|
|
14
|
+
Name string `json:"name"`
|
|
15
|
+
URL string `json:"url"`
|
|
16
|
+
Enabled bool `json:"enabled"`
|
|
17
|
+
Config map[string]interface{} `json:"config,omitempty"`
|
|
18
|
+
client *MCPClient
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// MCPManager manages multiple MCP server connections
|
|
22
|
+
type MCPManager struct {
|
|
23
|
+
servers map[string]*MCPServer
|
|
24
|
+
toolRegistry *registry.ToolRegistry
|
|
25
|
+
mu sync.RWMutex
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// NewMCPManager creates a new MCP manager
|
|
29
|
+
func NewMCPManager(toolRegistry *registry.ToolRegistry) *MCPManager {
|
|
30
|
+
return &MCPManager{
|
|
31
|
+
servers: make(map[string]*MCPServer),
|
|
32
|
+
toolRegistry: toolRegistry,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// AddServer adds a new MCP server
|
|
37
|
+
func (m *MCPManager) AddServer(server *MCPServer) error {
|
|
38
|
+
m.mu.Lock()
|
|
39
|
+
defer m.mu.Unlock()
|
|
40
|
+
|
|
41
|
+
if _, exists := m.servers[server.ID]; exists {
|
|
42
|
+
return fmt.Errorf("server %s already exists", server.ID)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create client
|
|
46
|
+
server.client = NewMCPClient(server.URL)
|
|
47
|
+
|
|
48
|
+
m.servers[server.ID] = server
|
|
49
|
+
return nil
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// RemoveServer removes an MCP server
|
|
53
|
+
func (m *MCPManager) RemoveServer(serverID string) error {
|
|
54
|
+
m.mu.Lock()
|
|
55
|
+
defer m.mu.Unlock()
|
|
56
|
+
|
|
57
|
+
server, exists := m.servers[serverID]
|
|
58
|
+
if !exists {
|
|
59
|
+
return fmt.Errorf("server %s not found", serverID)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Unregister all tools from this server
|
|
63
|
+
if err := m.unregisterServerTools(server); err != nil {
|
|
64
|
+
return fmt.Errorf("failed to unregister tools: %w", err)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
delete(m.servers, serverID)
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// EnableServer enables an MCP server
|
|
72
|
+
func (m *MCPManager) EnableServer(serverID string) error {
|
|
73
|
+
m.mu.Lock()
|
|
74
|
+
defer m.mu.Unlock()
|
|
75
|
+
|
|
76
|
+
server, exists := m.servers[serverID]
|
|
77
|
+
if !exists {
|
|
78
|
+
return fmt.Errorf("server %s not found", serverID)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
server.Enabled = true
|
|
82
|
+
|
|
83
|
+
// Discover and register tools
|
|
84
|
+
return m.discoverAndRegisterTools(server)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// DisableServer disables an MCP server
|
|
88
|
+
func (m *MCPManager) DisableServer(serverID string) error {
|
|
89
|
+
m.mu.Lock()
|
|
90
|
+
defer m.mu.Unlock()
|
|
91
|
+
|
|
92
|
+
server, exists := m.servers[serverID]
|
|
93
|
+
if !exists {
|
|
94
|
+
return fmt.Errorf("server %s not found", serverID)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
server.Enabled = false
|
|
98
|
+
|
|
99
|
+
// Unregister tools
|
|
100
|
+
return m.unregisterServerTools(server)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ListServers lists all configured MCP servers
|
|
104
|
+
func (m *MCPManager) ListServers() []*MCPServer {
|
|
105
|
+
m.mu.RLock()
|
|
106
|
+
defer m.mu.RUnlock()
|
|
107
|
+
|
|
108
|
+
servers := make([]*MCPServer, 0, len(m.servers))
|
|
109
|
+
for _, server := range m.servers {
|
|
110
|
+
servers = append(servers, server)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return servers
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// GetServer gets a specific server
|
|
117
|
+
func (m *MCPManager) GetServer(serverID string) (*MCPServer, error) {
|
|
118
|
+
m.mu.RLock()
|
|
119
|
+
defer m.mu.RUnlock()
|
|
120
|
+
|
|
121
|
+
server, exists := m.servers[serverID]
|
|
122
|
+
if !exists {
|
|
123
|
+
return nil, fmt.Errorf("server %s not found", serverID)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return server, nil
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// DiscoverAll discovers tools from all enabled servers
|
|
130
|
+
func (m *MCPManager) DiscoverAll(ctx context.Context) error {
|
|
131
|
+
m.mu.RLock()
|
|
132
|
+
servers := make([]*MCPServer, 0)
|
|
133
|
+
for _, server := range m.servers {
|
|
134
|
+
if server.Enabled {
|
|
135
|
+
servers = append(servers, server)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
m.mu.RUnlock()
|
|
139
|
+
|
|
140
|
+
for _, server := range servers {
|
|
141
|
+
if err := m.discoverAndRegisterTools(server); err != nil {
|
|
142
|
+
// Log error but continue
|
|
143
|
+
fmt.Printf("Warning: failed to discover tools from %s: %v\n", server.Name, err)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return nil
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// discoverAndRegisterTools discovers tools from a server and registers them
|
|
151
|
+
func (m *MCPManager) discoverAndRegisterTools(server *MCPServer) error {
|
|
152
|
+
ctx := context.Background()
|
|
153
|
+
|
|
154
|
+
// List tools from MCP server
|
|
155
|
+
tools, err := server.client.ListTools(ctx)
|
|
156
|
+
if err != nil {
|
|
157
|
+
return fmt.Errorf("failed to list tools: %w", err)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Register each tool
|
|
161
|
+
for _, mcpTool := range tools {
|
|
162
|
+
toolDef := ®istry.ToolDefinition{
|
|
163
|
+
ID: fmt.Sprintf("mcp_%s_%s", server.ID, mcpTool.Name),
|
|
164
|
+
Name: mcpTool.Name,
|
|
165
|
+
Description: mcpTool.Description,
|
|
166
|
+
RiskLevel: "low", // Default to low, could be configured
|
|
167
|
+
Category: "mcp",
|
|
168
|
+
Executor: m.createMCPToolExecutor(server, mcpTool.Name),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Register tool (ignore if already exists)
|
|
172
|
+
if err := m.toolRegistry.Register(toolDef); err != nil {
|
|
173
|
+
// Log but continue
|
|
174
|
+
fmt.Printf("Warning: failed to register tool %s: %v\n", toolDef.ID, err)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return nil
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// unregisterServerTools unregisters all tools from a server
|
|
182
|
+
func (m *MCPManager) unregisterServerTools(server *MCPServer) error {
|
|
183
|
+
// Get all tools
|
|
184
|
+
allTools := m.toolRegistry.List()
|
|
185
|
+
|
|
186
|
+
// Find and unregister tools from this server
|
|
187
|
+
prefix := fmt.Sprintf("mcp_%s_", server.ID)
|
|
188
|
+
for _, tool := range allTools {
|
|
189
|
+
if len(tool.ID) > len(prefix) && tool.ID[:len(prefix)] == prefix {
|
|
190
|
+
// Unregister would need to be added to registry
|
|
191
|
+
// For now, just skip
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return nil
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// createMCPToolExecutor creates an executor function for an MCP tool
|
|
199
|
+
func (m *MCPManager) createMCPToolExecutor(server *MCPServer, toolName string) func(*registry.ToolInput) (*registry.ToolOutput, error) {
|
|
200
|
+
return func(input *registry.ToolInput) (*registry.ToolOutput, error) {
|
|
201
|
+
ctx := context.Background()
|
|
202
|
+
|
|
203
|
+
// Call the MCP tool
|
|
204
|
+
result, err := server.client.CallTool(ctx, toolName, input.Parameters)
|
|
205
|
+
if err != nil {
|
|
206
|
+
return ®istry.ToolOutput{
|
|
207
|
+
Success: false,
|
|
208
|
+
Error: err.Error(),
|
|
209
|
+
}, err
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return ®istry.ToolOutput{
|
|
213
|
+
Success: true,
|
|
214
|
+
Data: result,
|
|
215
|
+
Metadata: map[string]interface{}{
|
|
216
|
+
"mcp_server": server.Name,
|
|
217
|
+
"tool_name": toolName,
|
|
218
|
+
},
|
|
219
|
+
}, nil
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
package policy
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
|
|
6
|
+
"github.com/ddhanush1/dcode/internal/config"
|
|
7
|
+
"github.com/ddhanush1/dcode/internal/registry"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// Policy represents a confirmation policy
|
|
11
|
+
type Policy interface {
|
|
12
|
+
// Evaluate returns whether a tool execution should be confirmed
|
|
13
|
+
Evaluate(tool *registry.ToolDefinition, params map[string]interface{}) bool
|
|
14
|
+
|
|
15
|
+
// GetName returns the policy name
|
|
16
|
+
GetName() string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// AlwaysConfirmPolicy always requires confirmation
|
|
20
|
+
type AlwaysConfirmPolicy struct{}
|
|
21
|
+
|
|
22
|
+
func (p *AlwaysConfirmPolicy) Evaluate(tool *registry.ToolDefinition, params map[string]interface{}) bool {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (p *AlwaysConfirmPolicy) GetName() string {
|
|
27
|
+
return "always"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// AutoApprovePolicy auto-approves all tool executions
|
|
31
|
+
type AutoApprovePolicy struct{}
|
|
32
|
+
|
|
33
|
+
func (p *AutoApprovePolicy) Evaluate(tool *registry.ToolDefinition, params map[string]interface{}) bool {
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
func (p *AutoApprovePolicy) GetName() string {
|
|
38
|
+
return "auto"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// RiskBasedPolicy evaluates based on risk level
|
|
42
|
+
type RiskBasedPolicy struct {
|
|
43
|
+
policy *config.PolicyFile
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func NewRiskBasedPolicy(policy *config.PolicyFile) *RiskBasedPolicy {
|
|
47
|
+
return &RiskBasedPolicy{
|
|
48
|
+
policy: policy,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func (p *RiskBasedPolicy) Evaluate(tool *registry.ToolDefinition, params map[string]interface{}) bool {
|
|
53
|
+
riskLevel := p.policy.GetRiskLevel(tool.ID)
|
|
54
|
+
|
|
55
|
+
switch riskLevel {
|
|
56
|
+
case "low":
|
|
57
|
+
return !p.policy.RiskBased.AutoApproveLowRisk
|
|
58
|
+
case "medium":
|
|
59
|
+
return !p.policy.RiskBased.AutoApproveMediumRisk
|
|
60
|
+
case "high":
|
|
61
|
+
return true // Always confirm high risk
|
|
62
|
+
default:
|
|
63
|
+
return true // Unknown risk, be safe
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func (p *RiskBasedPolicy) GetName() string {
|
|
68
|
+
return "risk_based"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// PolicyFactory creates policies based on configuration
|
|
72
|
+
type PolicyFactory struct{}
|
|
73
|
+
|
|
74
|
+
func NewPolicyFactory() *PolicyFactory {
|
|
75
|
+
return &PolicyFactory{}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func (f *PolicyFactory) CreatePolicy(policyFile *config.PolicyFile) (Policy, error) {
|
|
79
|
+
switch policyFile.Mode {
|
|
80
|
+
case "always":
|
|
81
|
+
return &AlwaysConfirmPolicy{}, nil
|
|
82
|
+
case "auto":
|
|
83
|
+
return &AutoApprovePolicy{}, nil
|
|
84
|
+
case "risk_based":
|
|
85
|
+
return NewRiskBasedPolicy(policyFile), nil
|
|
86
|
+
default:
|
|
87
|
+
return nil, fmt.Errorf("unknown policy mode: %s", policyFile.Mode)
|
|
88
|
+
}
|
|
89
|
+
}
|