Skip to content
Merged
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
52 changes: 42 additions & 10 deletions pkg/parser/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func ExtractMCPConfigurations(frontmatter map[string]any, serverFilter string) (

mcpServers, ok := mcpServersSection.(map[string]any)
if !ok {
return nil, fmt.Errorf("mcp-servers section is not a valid map")
return nil, fmt.Errorf("mcp-servers section must be a map, got %T. Example:\nmcp-servers:\n my-server:\n command: \"npx @my/tool\"\n args: [\"--port\", \"3000\"]", mcpServersSection)
}

// Process built-in MCP tools from tools section
Expand Down Expand Up @@ -501,7 +501,7 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
return config, fmt.Errorf("invalid JSON in mcp configuration: %w", err)
}
default:
return config, fmt.Errorf("invalid mcp configuration format")
return config, fmt.Errorf("mcp configuration must be a map or JSON string, got %T. Example:\nmcp-servers:\n %s:\n command: \"npx @my/tool\"\n args: [\"--port\", \"3000\"]", v, toolName)
}

// Extract type (explicit or inferred)
Expand All @@ -514,7 +514,7 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
config.Type = typeStr
}
} else {
return config, fmt.Errorf("type must be a string")
return config, fmt.Errorf("type field must be a string, got %T. Valid types are: stdio, http. Example:\nmcp-servers:\n %s:\n type: stdio\n command: \"npx @my/tool\"", typeVal, toolName)
}
} else {
// Infer type from presence of fields
Expand All @@ -528,7 +528,7 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
config.Type = "stdio"
mcpLog.Printf("Inferred MCP type 'stdio' for tool %s based on container field", toolName)
} else {
return config, fmt.Errorf("unable to determine MCP type for tool '%s': missing type, url, command, or container", toolName)
return config, fmt.Errorf("unable to determine MCP type for tool '%s': missing type, url, command, or container. Must specify one of: 'type' (stdio/http), 'url' (for HTTP MCP), 'command' (for command-based), or 'container' (for Docker-based). Example:\nmcp-servers:\n %s:\n command: \"npx @my/tool\"\n args: [\"--port\", \"3000\"]", toolName, toolName)
}
}

Expand All @@ -537,7 +537,7 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
if registryStr, ok := registry.(string); ok {
config.Registry = registryStr
} else {
return config, fmt.Errorf("registry must be a string")
return config, fmt.Errorf("registry field must be a string, got %T. Example:\nmcp-servers:\n %s:\n registry: \"https://registry.npmjs.org/@my/tool\"\n command: \"npx @my/tool\"", registry, toolName)
}
}

Expand Down Expand Up @@ -591,10 +591,24 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
if commandStr, ok := command.(string); ok {
config.Command = commandStr
} else {
return config, fmt.Errorf("command must be a string")
return config, fmt.Errorf("command field must be a string, got %T. Example:\nmcp-servers:\n %s:\n command: \"npx @my/tool\"\n args: [\"--port\", \"3000\"]", command, toolName)
}
} else {
return config, fmt.Errorf("stdio type requires 'command' or 'container' field")
return config, fmt.Errorf(
"stdio MCP tool '%s' must specify either 'command' or 'container' field. Cannot specify both. "+
"Example with command:\n"+
"mcp-servers:\n"+
" %s:\n"+
" command: \"npx @my/tool\"\n"+
" args: [\"--port\", \"3000\"]\n\n"+
"Example with container:\n"+
"mcp-servers:\n"+
" %s:\n"+
" container: \"myorg/my-tool:latest\"\n"+
" env:\n"+
" API_KEY: \"${{ secrets.API_KEY }}\"",
toolName, toolName, toolName,
)
}

if args, hasArgs := mcpConfig["args"]; hasArgs {
Expand Down Expand Up @@ -640,10 +654,28 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
if urlStr, ok := url.(string); ok {
config.URL = urlStr
} else {
return config, fmt.Errorf("url must be a string")
return config, fmt.Errorf(
"url field must be a string, got %T. Example:\n"+
"mcp-servers:\n"+
" %s:\n"+
" type: http\n"+
" url: \"https://api.example.com/mcp\"\n"+
" headers:\n"+
" Authorization: \"Bearer ${{ secrets.API_KEY }}\"",
url, toolName)
}
} else {
return config, fmt.Errorf("http type requires 'url' field")
return config, fmt.Errorf(
"http MCP tool '%s' missing required 'url' field. HTTP MCP servers must specify a URL endpoint. "+
"Example:\n"+
"mcp-servers:\n"+
" %s:\n"+
" type: http\n"+
" url: \"https://api.example.com/mcp\"\n"+
" headers:\n"+
" Authorization: \"Bearer ${{ secrets.API_KEY }}\"",
toolName, toolName,
)
}

// Extract headers
Expand All @@ -658,7 +690,7 @@ func ParseMCPConfig(toolName string, mcpSection any, toolConfig map[string]any)
}

default:
return config, fmt.Errorf("unsupported MCP type: %s", config.Type)
return config, fmt.Errorf("unsupported MCP type '%s' for tool '%s'. Valid types are: stdio, http. Example:\nmcp-servers:\n %s:\n type: stdio\n command: \"npx @my/tool\"\n args: [\"--port\", \"3000\"]", config.Type, toolName, toolName)
}

return config, nil
Expand Down
48 changes: 44 additions & 4 deletions pkg/workflow/mcp-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,20 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer

for key := range toolConfig {
if !knownProperties[key] {
return nil, fmt.Errorf("unknown property '%s' in MCP configuration for tool '%s'", key, toolName)
// Build list of valid properties
validProps := []string{}
for prop := range knownProperties {
validProps = append(validProps, prop)
}
sort.Strings(validProps)
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing import: This code uses sort.Strings() but the sort package import is not added to the file. Add "sort" to the imports at the top of the file.

import (
    "sort"
    "strings"
    // ... other imports
)

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sort package import was already present in the file after merging main. No changes needed.

return nil, fmt.Errorf(
"unknown property '%s' in MCP configuration for tool '%s'. Valid properties are: %s. "+
"Example:\n"+
"mcp-servers:\n"+
" %s:\n"+
" command: \"npx @my/tool\"\n"+
" args: [\"--port\", \"3000\"]",
key, toolName, strings.Join(validProps, ", "), toolName)
}
}

Expand All @@ -723,7 +736,16 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer
result.Type = "stdio"
mcpLog.Printf("Inferred MCP type as stdio (has container field)")
} else {
return nil, fmt.Errorf("unable to determine MCP type for tool '%s': missing type, url, command, or container", toolName)
return nil, fmt.Errorf(
"unable to determine MCP type for tool '%s': missing type, url, command, or container. "+
"Must specify one of: 'type' (stdio/http), 'url' (for HTTP MCP), 'command' (for command-based), or 'container' (for Docker-based). "+
"Example:\n"+
"mcp-servers:\n"+
" %s:\n"+
" command: \"npx @my/tool\"\n"+
" args: [\"--port\", \"3000\"]",
toolName, toolName,
)
}
}

Expand Down Expand Up @@ -761,13 +783,31 @@ func getMCPConfig(toolConfig map[string]any, toolName string) (*parser.MCPServer
if url, hasURL := config.GetString("url"); hasURL {
result.URL = url
} else {
return nil, fmt.Errorf("http MCP tool '%s' missing required 'url' field", toolName)
return nil, fmt.Errorf(
"http MCP tool '%s' missing required 'url' field. HTTP MCP servers must specify a URL endpoint. "+
"Example:\n"+
"mcp-servers:\n"+
" %s:\n"+
" type: http\n"+
" url: \"https://api.example.com/mcp\"\n"+
" headers:\n"+
" Authorization: \"Bearer ${{ secrets.API_KEY }}\"",
toolName, toolName,
)
}
if headers, hasHeaders := config.GetStringMap("headers"); hasHeaders {
result.Headers = headers
}
default:
return nil, fmt.Errorf("unsupported MCP type '%s' for tool '%s'", result.Type, toolName)
return nil, fmt.Errorf(
"unsupported MCP type '%s' for tool '%s'. Valid types are: stdio, http. "+
"Example:\n"+
"mcp-servers:\n"+
" %s:\n"+
" type: stdio\n"+
" command: \"npx @my/tool\"\n"+
" args: [\"--port\", \"3000\"]",
result.Type, toolName, toolName)
}

// Extract allowed tools
Expand Down
Loading
Loading