forked from grafana/mcp-grafana
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxied_client.go
More file actions
148 lines (125 loc) · 4.07 KB
/
proxied_client.go
File metadata and controls
148 lines (125 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package mcpgrafana
import (
"context"
"encoding/base64"
"fmt"
"log/slog"
"sync"
mcp_client "github.com/mark3labs/mcp-go/client"
"github.com/mark3labs/mcp-go/client/transport"
"github.com/mark3labs/mcp-go/mcp"
)
// ProxiedClient represents a connection to a remote MCP server (e.g., Tempo datasource)
type ProxiedClient struct {
DatasourceUID string
DatasourceName string
DatasourceType string
Client *mcp_client.Client
Tools []mcp.Tool
mutex sync.RWMutex
}
// NewProxiedClient creates a new connection to a remote MCP server
func NewProxiedClient(ctx context.Context, datasourceUID, datasourceName, datasourceType, mcpEndpoint string) (*ProxiedClient, error) {
// Get Grafana config for authentication
config := GrafanaConfigFromContext(ctx)
// Build headers for authentication
headers := make(map[string]string)
if config.APIKey != "" {
headers["Authorization"] = "Bearer " + config.APIKey
} else if config.BasicAuth != nil {
auth := config.BasicAuth.String()
headers["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}
// Add org ID header if configured
if config.OrgID != 0 {
headers["X-Grafana-Org-Id"] = fmt.Sprintf("%d", config.OrgID)
}
// Create HTTP transport with authentication and org ID headers
slog.DebugContext(ctx, "connecting to MCP server", "datasource", datasourceUID, "url", mcpEndpoint)
httpTransport, err := transport.NewStreamableHTTP(
mcpEndpoint,
transport.WithHTTPHeaders(headers),
)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP transport: %w", err)
}
// Create MCP client
mcpClient := mcp_client.NewClient(httpTransport)
// Initialize the connection
initReq := mcp.InitializeRequest{}
initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initReq.Params.ClientInfo = mcp.Implementation{
Name: "mcp-grafana-proxy",
Version: Version(),
}
_, err = mcpClient.Initialize(ctx, initReq)
if err != nil {
_ = mcpClient.Close()
return nil, fmt.Errorf("failed to initialize MCP client: %w", err)
}
// List available tools from the remote server
listReq := mcp.ListToolsRequest{}
toolsResult, err := mcpClient.ListTools(ctx, listReq)
if err != nil {
_ = mcpClient.Close()
return nil, fmt.Errorf("failed to list tools from remote MCP server: %w", err)
}
slog.DebugContext(ctx, "connected to proxied MCP server",
"datasource", datasourceUID,
"type", datasourceType,
"tools", len(toolsResult.Tools))
return &ProxiedClient{
DatasourceUID: datasourceUID,
DatasourceName: datasourceName,
DatasourceType: datasourceType,
Client: mcpClient,
Tools: toolsResult.Tools,
}, nil
}
// CallTool forwards a tool call to the remote MCP server
func (pc *ProxiedClient) CallTool(ctx context.Context, toolName string, arguments map[string]any) (*mcp.CallToolResult, error) {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
// Validate the tool exists
var toolExists bool
for _, tool := range pc.Tools {
if tool.Name == toolName {
toolExists = true
break
}
}
if !toolExists {
return nil, fmt.Errorf("tool %s not found in remote MCP server", toolName)
}
// Create the call tool request
req := mcp.CallToolRequest{}
req.Params.Name = toolName
req.Params.Arguments = arguments
// Forward the call to the remote server
result, err := pc.Client.CallTool(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to call tool on remote MCP server: %w", err)
}
return result, nil
}
// ListTools returns the tools available from this remote server
// Note: This method doesn't take a context parameter as the tools are cached locally
func (pc *ProxiedClient) ListTools() []mcp.Tool {
pc.mutex.RLock()
defer pc.mutex.RUnlock()
// Return a copy to prevent external modification
result := make([]mcp.Tool, len(pc.Tools))
copy(result, pc.Tools)
return result
}
// Close closes the connection to the remote MCP server
func (pc *ProxiedClient) Close() error {
pc.mutex.Lock()
defer pc.mutex.Unlock()
if pc.Client != nil {
if err := pc.Client.Close(); err != nil {
return fmt.Errorf("failed to close MCP client: %w", err)
}
}
return nil
}