chore(mcp): clarify inline text limit and cover artifact edge cases

This commit is contained in:
afjcjsbx 2026-04-03 17:33:55 +02:00
parent cefec159ad
commit 728b7ad325
5 changed files with 33 additions and 7 deletions

View file

@ -533,4 +533,4 @@ For example:
Note: Nested map-style config (for example `tools.mcp.servers.<name>.*`) is configured in `config.json` rather than Note: Nested map-style config (for example `tools.mcp.servers.<name>.*`) is configured in `config.json` rather than
environment variables. 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.

View file

@ -943,8 +943,8 @@ type MCPServerConfig struct {
type MCPConfig struct { type MCPConfig struct {
ToolConfig ` envPrefix:"PICOCLAW_TOOLS_MCP_"` ToolConfig ` envPrefix:"PICOCLAW_TOOLS_MCP_"`
Discovery ToolDiscoveryConfig ` json:"discovery"` Discovery ToolDiscoveryConfig ` json:"discovery"`
// Max controls how much MCP text stays inline before it is saved as an artifact. // MaxInlineTextChars 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 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 is a map of server name to server configuration
Servers map[string]MCPServerConfig `json:"servers,omitempty"` Servers map[string]MCPServerConfig `json:"servers,omitempty"`
} }
@ -952,8 +952,8 @@ type MCPConfig struct {
const DefaultMCPMaxInlineTextChars = 16 * 1024 const DefaultMCPMaxInlineTextChars = 16 * 1024
func (c *MCPConfig) GetMaxInlineTextChars() int { func (c *MCPConfig) GetMaxInlineTextChars() int {
if c.Max > 0 { if c.MaxInlineTextChars > 0 {
return c.Max return c.MaxInlineTextChars
} }
return DefaultMCPMaxInlineTextChars return DefaultMCPMaxInlineTextChars
} }

View file

@ -462,8 +462,8 @@ func DefaultConfig() *Config {
UseBM25: true, UseBM25: true,
UseRegex: false, UseRegex: false,
}, },
Max: DefaultMCPMaxInlineTextChars, MaxInlineTextChars: DefaultMCPMaxInlineTextChars,
Servers: map[string]MCPServerConfig{}, Servers: map[string]MCPServerConfig{},
}, },
AppendFile: ToolConfig{ AppendFile: ToolConfig{
Enabled: true, Enabled: true,

View file

@ -362,6 +362,7 @@ func (t *MCPTool) persistLargeTextArtifact(text string) *ToolResult {
if err := os.MkdirAll(dir, 0o700); err != nil { if err := os.MkdirAll(dir, 0o700); err != nil {
return t.largeTextArtifactFallback(text, err) return t.largeTextArtifactFallback(text, err)
} }
// TODO: Add lifecycle cleanup/retention for MCP artifact files.
pattern := fmt.Sprintf( pattern := fmt.Sprintf(
"%s_%s_*.txt", "%s_%s_*.txt",

View file

@ -783,3 +783,28 @@ func TestMCPTool_Execute_LargeTextArtifactFailureStillOmitsContext(t *testing.T)
t.Fatalf("expected no artifact tags on persistence failure, got %+v", result.ArtifactTags) 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)
}
}