mirror of
https://github.com/coder/coder.git
synced 2026-06-04 21:48:22 +00:00
e00e85765b
This PR merges code from `coder/aibridge` repository into `coder/coder`. It was split into 4 PRs for easier review but stacked PRs will need to be merged into this PR so all checks pass. * https://github.com/coder/coder/pull/24190 -> raw code copy (this PR, before merging PRs on top of it, it was just 1 commit: https://github.com/coder/coder/commit/70d33f33200c7e77df910957595715f81f9bec24) * https://github.com/coder/coder/pull/24570 -> update imports in `coder/coder` to use copied code * https://github.com/coder/coder/pull/24586 -> linter fixes and CI integration (also added README.md) * https://github.com/coder/coder/pull/24571 -> added exclude to scripts/check_emdash.sh check Original PR message (before PR squash): Moves coder/aibridge code into coder/coder repository. Omitted files: - `go.mod`, `go.sum`, `.gitignore`, `.github/workflows/ci.yml,` `Makefile`, `LICENSE`, `README.md` (modified README.md is added later) - `.github`, `example`, `buildinfo,` `scripts` directories Simple verification script (will list omitted files) ``` tmp=$(mktemp -d) echo "$tmp" git clone --depth=1 https://github.com/coder/aibridge "$tmp/aibridge" git clone --depth=1 --branch pb/aibridge-code-move https://github.com/coder/coder "$tmp/coder" diff -rq --exclude=.git "$tmp/aibridge" "$tmp/coder/aibridge" # rm -rf "$tmp" ```
131 lines
2.9 KiB
Go
131 lines
2.9 KiB
Go
package mcp
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
"go.opentelemetry.io/otel/trace"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/aibridge/tracing"
|
|
"github.com/coder/coder/v2/aibridge/utils"
|
|
)
|
|
|
|
var _ ServerProxier = &ServerProxyManager{}
|
|
|
|
// ServerProxyManager can act on behalf of multiple [ServerProxier]s.
|
|
// It aggregates all server resources (currently just tools) across all MCP servers
|
|
// for the purpose of injection into bridged requests and invocation.
|
|
type ServerProxyManager struct {
|
|
proxiers map[string]ServerProxier
|
|
tracer trace.Tracer
|
|
|
|
// Protects access to the tools map.
|
|
toolsMu sync.RWMutex
|
|
tools map[string]*Tool
|
|
}
|
|
|
|
func NewServerProxyManager(proxiers map[string]ServerProxier, tracer trace.Tracer) *ServerProxyManager {
|
|
return &ServerProxyManager{
|
|
proxiers: proxiers,
|
|
tracer: tracer,
|
|
}
|
|
}
|
|
|
|
func (s *ServerProxyManager) addTools(tools []*Tool) {
|
|
s.toolsMu.Lock()
|
|
defer s.toolsMu.Unlock()
|
|
|
|
if s.tools == nil {
|
|
s.tools = make(map[string]*Tool, len(tools))
|
|
}
|
|
|
|
for _, tool := range tools {
|
|
s.tools[tool.ID] = tool
|
|
}
|
|
}
|
|
|
|
// Init concurrently initializes all of its [ServerProxier]s.
|
|
func (s *ServerProxyManager) Init(ctx context.Context) (outErr error) {
|
|
ctx, span := s.tracer.Start(ctx, "ServerProxyManager.Init")
|
|
defer tracing.EndSpanErr(span, &outErr)
|
|
|
|
cg := utils.NewConcurrentGroup()
|
|
for _, proxy := range s.proxiers {
|
|
cg.Go(func() error {
|
|
return proxy.Init(ctx)
|
|
})
|
|
}
|
|
|
|
// Wait for all servers to initialize and load their tools.
|
|
err := cg.Wait()
|
|
|
|
// Aggregate all proxiers' tools.
|
|
for _, proxy := range s.proxiers {
|
|
s.addTools(proxy.ListTools())
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (s *ServerProxyManager) GetTool(name string) *Tool {
|
|
s.toolsMu.RLock()
|
|
defer s.toolsMu.RUnlock()
|
|
|
|
if s.tools == nil {
|
|
return nil
|
|
}
|
|
|
|
return s.tools[name]
|
|
}
|
|
|
|
func (s *ServerProxyManager) ListTools() []*Tool {
|
|
s.toolsMu.RLock()
|
|
defer s.toolsMu.RUnlock()
|
|
|
|
if s.tools == nil {
|
|
return nil
|
|
}
|
|
|
|
var out []*Tool
|
|
for _, tool := range s.tools {
|
|
out = append(out, tool)
|
|
}
|
|
|
|
slices.SortStableFunc(out, func(a, b *Tool) int {
|
|
return strings.Compare(a.ID, b.ID)
|
|
})
|
|
|
|
return out
|
|
}
|
|
|
|
// CallTool locates the proxier to which the requested tool is associated and
|
|
// delegates the tool call to it.
|
|
func (s *ServerProxyManager) CallTool(ctx context.Context, name string, input any) (*mcp.CallToolResult, error) {
|
|
tool := s.GetTool(name)
|
|
if tool == nil {
|
|
return nil, xerrors.Errorf("%q tool not known", name)
|
|
}
|
|
|
|
proxy, ok := s.proxiers[tool.ServerName]
|
|
if !ok {
|
|
return nil, xerrors.Errorf("%q server not known", tool.ServerName)
|
|
}
|
|
|
|
return proxy.CallTool(ctx, name, input)
|
|
}
|
|
|
|
// Shutdown concurrently shuts down all known proxiers and waits for them *all* to complete.
|
|
func (s *ServerProxyManager) Shutdown(ctx context.Context) error {
|
|
cg := utils.NewConcurrentGroup()
|
|
for _, proxy := range s.proxiers {
|
|
cg.Go(func() error {
|
|
return proxy.Shutdown(ctx)
|
|
})
|
|
}
|
|
return cg.Wait()
|
|
}
|