Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions pkg/agent/conversation.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ type Agent struct {

// mcpManager manages MCP client connections
mcpManager *mcp.Manager

// mcpResourceContext holds external context from MCP resources
mcpResourceContext string
}

func (s *Agent) Session() *api.Session {
Expand Down Expand Up @@ -199,10 +202,24 @@ func (s *Agent) Init(ctx context.Context) error {

log.Info("Created temporary working directory", "workDir", workDir)

if s.MCPClientEnabled {
if err := s.InitializeMCPClient(ctx); err != nil {
klog.Errorf("Failed to initialize MCP client: %v", err)
return fmt.Errorf("failed to initialize MCP client: %w", err)
}

// Update MCP status in session
if err := s.UpdateMCPStatus(ctx, s.MCPClientEnabled); err != nil {
klog.Warningf("Failed to update MCP status: %v", err)
}
}

systemPrompt, err := s.generatePrompt(ctx, defaultSystemPromptTemplate, PromptData{
Tools: s.Tools,
EnableToolUseShim: s.EnableToolUseShim,
MCPResourceText: s.mcpResourceContext,
})

if err != nil {
return fmt.Errorf("generating system prompt: %w", err)
}
Expand All @@ -219,18 +236,6 @@ func (s *Agent) Init(ctx context.Context) error {
},
)

if s.MCPClientEnabled {
if err := s.InitializeMCPClient(ctx); err != nil {
klog.Errorf("Failed to initialize MCP client: %v", err)
return fmt.Errorf("failed to initialize MCP client: %w", err)
}

// Update MCP status in session
if err := s.UpdateMCPStatus(ctx, s.MCPClientEnabled); err != nil {
klog.Warningf("Failed to update MCP status: %v", err)
}
}

if !s.EnableToolUseShim {
var functionDefinitions []*gollm.FunctionDefinition
for _, tool := range s.Tools.AllTools() {
Expand Down Expand Up @@ -819,6 +824,8 @@ type PromptData struct {
Tools tools.Tools

EnableToolUseShim bool

MCPResourceText string
}

func (a *PromptData) ToolsAsJSON() string {
Expand Down
21 changes: 21 additions & 0 deletions pkg/agent/mcp_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ func (a *Agent) InitializeMCPClient(ctx context.Context) error {
// Store the manager for later use
a.mcpManager = manager

// Register MCP resources with the agent
var resourceTexts []string

resources, err := manager.ListAvailableResources(ctx)
if err != nil {
klog.Warningf("Failed to list MCP resources: %v", err)
} else {
for serverName, resList := range resources {
for _, res := range resList {
content, err := manager.ReadResource(ctx, serverName, res.URI)
if err != nil {
klog.Warningf("Failed to read resource %s from server %s: %v", res.URI, serverName, err)
continue
}
resourceTexts = append(resourceTexts, fmt.Sprintf("### %s (%s)\n%s", res.Name, serverName, content))
}
}
}

a.mcpResourceContext = strings.Join(resourceTexts, "\n\n")

return nil
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/agent/systemprompt_template_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,9 @@ Before creating ANY manifest, you MUST:
- Provide a final answer only when you're confident you have sufficient information.
- Provide clear, concise, and accurate responses.
- Feel free to respond with emojis where appropriate.

# External Resources:
{{if .MCPResourceText }}
## External context
{{.MCPResourceText}}
{{end}}
78 changes: 78 additions & 0 deletions pkg/mcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ type Tool struct {
InputSchema *gollm.Schema `json:"inputSchema,omitempty"`
}

// Resource represents an MCP resource with optional server information.
type Resource struct {
URI string `json:"uri"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
MIMEType string `json:"mimeType,omitempty"`
Server string `json:"server,omitempty"`
}

// NewClient creates a new MCP client with the given configuration.
// This function supports both stdio and HTTP-based MCP servers.
func NewClient(config ClientConfig) *Client {
Expand Down Expand Up @@ -156,6 +165,30 @@ func (c *Client) CallTool(ctx context.Context, toolName string, arguments map[st
return c.impl.CallTool(ctx, toolName, arguments)
}

// ListResources lists all available resources from the MCP server.
func (c *Client) ListResources(ctx context.Context) ([]Resource, error) {
if err := c.ensureConnected(); err != nil {
return nil, err
}

resources, err := c.impl.ListResources(ctx)
if err != nil {
return nil, err
}

klog.V(2).InfoS("Listed resources from MCP server", "count", len(resources), "server", c.Name)
return resources, nil
}

// ReadResource reads the content of a resource from the MCP server.
func (c *Client) ReadResource(ctx context.Context, uri string) (string, error) {
if err := c.ensureConnected(); err != nil {
return "", err
}

return c.impl.ReadResource(ctx, uri)
}

// ===================================================================
// Tool Factory Functions and Methods
// ===================================================================
Expand Down Expand Up @@ -436,3 +469,48 @@ func listClientTools(ctx context.Context, client *mcpclient.Client, serverName s

return tools, nil
}

// listClientResources implements the common ListResources functionality shared by both client types.
func listClientResources(ctx context.Context, client *mcpclient.Client, serverName string) ([]Resource, error) {
if err := ensureClientConnected(client); err != nil {
return nil, err
}

result, err := client.ListResources(ctx, mcp.ListResourcesRequest{})
if err != nil {
return nil, fmt.Errorf("listing resources: %w", err)
}

resources := make([]Resource, 0, len(result.Resources))
for _, r := range result.Resources {
resources = append(resources, Resource{
URI: r.URI,
Name: r.Name,
Description: r.Description,
MIMEType: r.MIMEType,
Server: serverName,
})
}

return resources, nil
}

// readClientResource reads the contents of a resource from the server and returns text.
func readClientResource(ctx context.Context, client *mcpclient.Client, uri string) (string, error) {
if err := ensureClientConnected(client); err != nil {
return "", err
}

result, err := client.ReadResource(ctx, mcp.ReadResourceRequest{Params: mcp.ReadResourceParams{URI: uri}})
if err != nil {
return "", fmt.Errorf("reading resource %s: %w", uri, err)
}

for _, c := range result.Contents {
if text, ok := c.(mcp.TextResourceContents); ok {
return text.Text, nil
}
}

return "", fmt.Errorf("no text content for resource %s", uri)
}
22 changes: 22 additions & 0 deletions pkg/mcp/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,28 @@ func (c *httpClient) ListTools(ctx context.Context) ([]Tool, error) {
return tools, nil
}

// ListResources lists all available resources from the MCP server
func (c *httpClient) ListResources(ctx context.Context) ([]Resource, error) {
resources, err := listClientResources(ctx, c.client, c.name)
if err != nil {
return nil, err
}

klog.V(2).InfoS("Listed resources from HTTP MCP server", "count", len(resources), "server", c.name)
return resources, nil
}

// ReadResource reads a resource from the MCP server and returns its text content
func (c *httpClient) ReadResource(ctx context.Context, uri string) (string, error) {
klog.V(2).InfoS("Reading MCP resource via HTTP", "server", c.name, "uri", uri)

if err := c.ensureConnected(); err != nil {
return "", err
}

return readClientResource(ctx, c.client, uri)
}

// CallTool calls a tool on the MCP server and returns the result as a string
func (c *httpClient) CallTool(ctx context.Context, toolName string, arguments map[string]interface{}) (string, error) {
klog.V(2).InfoS("Calling MCP tool via HTTP", "server", c.name, "tool", toolName)
Expand Down
6 changes: 6 additions & 0 deletions pkg/mcp/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ type MCPClient interface {
// CallTool calls a tool on the MCP server and returns the result as a string
CallTool(ctx context.Context, toolName string, arguments map[string]interface{}) (string, error)

// ListResources lists all available resources from the MCP server
ListResources(ctx context.Context) ([]Resource, error)

// ReadResource reads a resource from the MCP server and returns its text content
ReadResource(ctx context.Context, uri string) (string, error)

// ensureConnected makes sure the client is connected
ensureConnected() error

Expand Down
31 changes: 31 additions & 0 deletions pkg/mcp/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,37 @@ func (m *Manager) ListAvailableTools(ctx context.Context) (map[string][]Tool, er
return tools, nil
}

// ListAvailableResources returns resources from all connected servers
func (m *Manager) ListAvailableResources(ctx context.Context) (map[string][]Resource, error) {
m.mu.RLock()
defer m.mu.RUnlock()

resources := make(map[string][]Resource)

for name, client := range m.clients {
resList, err := client.ListResources(ctx)
if err != nil {
return nil, fmt.Errorf("listing resources from MCP server %q: %w", name, err)
}

resources[name] = resList
}

return resources, nil
}

// ReadResource reads a resource's content from a specific server
func (m *Manager) ReadResource(ctx context.Context, serverName, uri string) (string, error) {
m.mu.RLock()
client, exists := m.clients[serverName]
m.mu.RUnlock()
if !exists {
return "", fmt.Errorf("MCP server %q not found", serverName)
}

return client.ReadResource(ctx, uri)
}

// RefreshToolDiscovery discovers tools from all servers with retries
func (m *Manager) RefreshToolDiscovery(ctx context.Context) (map[string][]Tool, error) {
klog.V(1).Info("Starting tool discovery from MCP servers with retries")
Expand Down
22 changes: 22 additions & 0 deletions pkg/mcp/stdio_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,28 @@ func (c *stdioClient) ListTools(ctx context.Context) ([]Tool, error) {
return tools, nil
}

// ListResources lists all available resources from the MCP server
func (c *stdioClient) ListResources(ctx context.Context) ([]Resource, error) {
resources, err := listClientResources(ctx, c.client, c.name)
if err != nil {
return nil, err
}

klog.V(2).InfoS("Listed resources from stdio MCP server", "count", len(resources), "server", c.name)
return resources, nil
}

// ReadResource reads a resource from the MCP server and returns its text content
func (c *stdioClient) ReadResource(ctx context.Context, uri string) (string, error) {
klog.V(2).InfoS("Reading MCP resource via stdio", "server", c.name, "uri", uri)

if err := c.ensureConnected(); err != nil {
return "", err
}

return readClientResource(ctx, c.client, uri)
}

// CallTool calls a tool on the MCP server and returns the result as a string
func (c *stdioClient) CallTool(ctx context.Context, toolName string, arguments map[string]interface{}) (string, error) {
klog.V(2).InfoS("Calling MCP tool via stdio", "server", c.name, "tool", toolName)
Expand Down
Loading