diff --git a/docs/tools_configuration.md b/docs/tools_configuration.md index 2c7e2ec70..adee9244a 100644 --- a/docs/tools_configuration.md +++ b/docs/tools_configuration.md @@ -533,4 +533,4 @@ For example: Note: Nested map-style config (for example `tools.mcp.servers..*`) is configured in `config.json` rather than environment variables. -For MCP tools, `tools.mcp.max_inline_text_chars` controls how much text result is kept inline in model context. Above this threshold, PicoClaw saves the MCP text result as a local artifact in the agent workspace and gives the model a short note plus a structured `[file:...]` artifact path instead of injecting the full payload into context. +For MCP tools, `tools.mcp.max_inline_text_chars` controls how much text result is kept inline in model context. The threshold is counted in Unicode characters (Go runes), not bytes. For example, `16384` means up to 16,384 characters inline, which may occupy more than 16 KB for multibyte text such as CJK. Above this threshold, PicoClaw saves the MCP text result as a local artifact in the agent workspace and gives the model a short note plus a structured `[file:...]` artifact path instead of injecting the full payload into context. diff --git a/pkg/config/config.go b/pkg/config/config.go index 5c5df09f8..7165246e5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -943,8 +943,8 @@ type MCPServerConfig struct { type MCPConfig struct { ToolConfig ` envPrefix:"PICOCLAW_TOOLS_MCP_"` Discovery ToolDiscoveryConfig ` json:"discovery"` - // Max controls how much MCP text stays inline before it is saved as an artifact. - Max int `json:"max_inline_text_chars,omitempty" env:"PICOCLAW_TOOLS_MCP_MAX_INLINE_TEXT_CHARS"` + // MaxInlineTextChars controls how much MCP text stays inline before it is saved as an artifact. + MaxInlineTextChars int `json:"max_inline_text_chars,omitempty" env:"PICOCLAW_TOOLS_MCP_MAX_INLINE_TEXT_CHARS"` // Servers is a map of server name to server configuration Servers map[string]MCPServerConfig `json:"servers,omitempty"` } @@ -952,8 +952,8 @@ type MCPConfig struct { const DefaultMCPMaxInlineTextChars = 16 * 1024 func (c *MCPConfig) GetMaxInlineTextChars() int { - if c.Max > 0 { - return c.Max + if c.MaxInlineTextChars > 0 { + return c.MaxInlineTextChars } return DefaultMCPMaxInlineTextChars } diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 0e8778a1b..c2e1a31f3 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -462,8 +462,8 @@ func DefaultConfig() *Config { UseBM25: true, UseRegex: false, }, - Max: DefaultMCPMaxInlineTextChars, - Servers: map[string]MCPServerConfig{}, + MaxInlineTextChars: DefaultMCPMaxInlineTextChars, + Servers: map[string]MCPServerConfig{}, }, AppendFile: ToolConfig{ Enabled: true, diff --git a/pkg/tools/mcp_tool.go b/pkg/tools/mcp_tool.go index 87ee9a40a..1caf390cf 100644 --- a/pkg/tools/mcp_tool.go +++ b/pkg/tools/mcp_tool.go @@ -362,6 +362,7 @@ func (t *MCPTool) persistLargeTextArtifact(text string) *ToolResult { if err := os.MkdirAll(dir, 0o700); err != nil { return t.largeTextArtifactFallback(text, err) } + // TODO: Add lifecycle cleanup/retention for MCP artifact files. pattern := fmt.Sprintf( "%s_%s_*.txt", diff --git a/pkg/tools/mcp_tool_test.go b/pkg/tools/mcp_tool_test.go index bc9edb6e1..f2b02d6f6 100644 --- a/pkg/tools/mcp_tool_test.go +++ b/pkg/tools/mcp_tool_test.go @@ -783,3 +783,28 @@ func TestMCPTool_Execute_LargeTextArtifactFailureStillOmitsContext(t *testing.T) t.Fatalf("expected no artifact tags on persistence failure, got %+v", result.ArtifactTags) } } + +func TestMCPTool_Execute_WhitespaceWorkspaceDisablesArtifactPersistence(t *testing.T) { + largeText := strings.Repeat("This is a large MCP text payload.\n", 800) + manager := &MockMCPManager{ + callToolFunc: func(ctx context.Context, serverName, toolName string, arguments map[string]any) (*mcp.CallToolResult, error) { + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: largeText}, + }, + }, nil + }, + } + + mcpTool := NewMCPTool(manager, "test_server", &mcp.Tool{Name: "dump_payload"}) + mcpTool.SetWorkspace(" \n\t ") + + result := mcpTool.Execute(context.Background(), nil) + + if len(result.ArtifactTags) != 0 { + t.Fatalf("expected no artifact tags for whitespace workspace, got %+v", result.ArtifactTags) + } + if !strings.Contains(result.ForLLM, "This is a large MCP text payload") { + t.Fatalf("expected large text to remain inline when workspace is blank, got %q", result.ForLLM) + } +}