ollama/server/mcp_code_api.go

149 lines
4.8 KiB
Go

package server
import (
"fmt"
"log/slog"
"strings"
"github.com/ollama/ollama/api"
)
// MCPCodeAPI provides a code-like interface for MCP tools
type MCPCodeAPI struct {
manager *MCPManager
}
// NewMCPCodeAPI creates a new MCP code API
func NewMCPCodeAPI(manager *MCPManager) *MCPCodeAPI {
return &MCPCodeAPI{
manager: manager,
}
}
// GenerateMinimalContext returns essential context for tool usage
func (m *MCPCodeAPI) GenerateMinimalContext(configs []api.MCPServerConfig) string {
slog.Debug("GenerateMinimalContext called", "configs_count", len(configs))
if len(configs) == 0 {
slog.Debug("No MCP configs provided, returning empty context")
return ""
}
var context strings.Builder
context.WriteString("\n=== MCP Tool Context ===\n")
for _, config := range configs {
slog.Debug("Processing MCP config", "command", config.Command, "args", config.Args)
// Check if this is a filesystem server (command or first arg contains filesystem)
isFilesystem := strings.Contains(config.Command, "filesystem") ||
(len(config.Args) > 0 && strings.Contains(config.Args[0], "filesystem"))
if isFilesystem && len(config.Args) > 1 {
// Extract working directory from filesystem server
workingDir := config.Args[1]
slog.Debug("Adding filesystem context", "working_dir", workingDir)
context.WriteString(fmt.Sprintf(`
Filesystem tools are available with these constraints:
- Working directory: %s
- All file operations must use paths within this directory
- Example usage:
- List files: "List all files in %s"
- Read file: "Read %s/filename.txt"
- Create file: "Create %s/newfile.txt with content"
- Paths outside %s will be rejected
When working with files, ALWAYS use the full path starting with %s
`, workingDir, workingDir, workingDir, workingDir, workingDir, workingDir))
}
// Add other server types as needed
}
context.WriteString("\n")
result := context.String()
slog.Debug("Generated MCP context", "length", len(result))
return result
}
// GenerateProgressiveContext returns context based on what tools are being used
func (m *MCPCodeAPI) GenerateProgressiveContext(toolNames []string) string {
var context strings.Builder
// Group tools by server
serverTools := make(map[string][]string)
for _, toolName := range toolNames {
if clientName, exists := m.manager.GetToolClient(toolName); exists {
serverTools[clientName] = append(serverTools[clientName], toolName)
}
}
// Generate context for each server's tools
for serverName, tools := range serverTools {
context.WriteString(fmt.Sprintf("\n%s tools being used:\n", serverName))
for _, tool := range tools {
// Get tool definition from manager
if toolDef := m.manager.GetToolDefinition(serverName, tool); toolDef != nil {
context.WriteString(fmt.Sprintf("- %s: %s\n", tool, toolDef.Function.Description))
}
}
}
return context.String()
}
// InjectContextIntoMessages intelligently injects context into the message stream
func (m *MCPCodeAPI) InjectContextIntoMessages(messages []api.Message, configs []api.MCPServerConfig) []api.Message {
// Generate minimal context
context := m.GenerateMinimalContext(configs)
if context == "" {
return messages
}
// Check if there's already a system message
if len(messages) > 0 && messages[0].Role == "system" {
// Append to existing system message
messages[0].Content += context
} else {
// Create new system message
systemMsg := api.Message{
Role: "system",
Content: context,
}
messages = append([]api.Message{systemMsg}, messages...)
}
return messages
}
// ExtractWorkingDirectory extracts the working directory from MCP server args
func ExtractWorkingDirectory(config api.MCPServerConfig) string {
if strings.Contains(config.Command, "filesystem") && len(config.Args) > 1 {
return config.Args[1]
}
return ""
}
// GenerateToolCallExample generates an example of how to call a specific tool
func (m *MCPCodeAPI) GenerateToolCallExample(serverName, toolName string) string {
workingDir := ""
// Get working directory if filesystem
if serverName == "filesystem" {
if clients := m.manager.GetServerNames(); len(clients) > 0 {
// This is a simplified approach - in production we'd properly track server configs
workingDir = "/home/velvetm/Desktop/mcp-test-files" // Would be extracted from actual config
}
}
// Generate appropriate example based on tool
switch toolName {
case "list_directory":
return fmt.Sprintf(`"List all files in %s"`, workingDir)
case "read_file":
return fmt.Sprintf(`"Read the file %s/example.txt"`, workingDir)
case "write_file":
return fmt.Sprintf(`"Create a file at %s/output.txt with content 'Hello World'"`, workingDir)
case "create_directory":
return fmt.Sprintf(`"Create a directory called %s/newdir"`, workingDir)
default:
return fmt.Sprintf(`"Use the %s tool"`, toolName)
}
}