CherryHQ/cherry-studio · error · McpError
InvalidParams
InvalidParams
Error message
'query' is required for search_skills
What it means
The skills server's `search_skills` requires a non-empty `query` string. It checks `if (!query)` and throws McpError InvalidParams. As with 354, the outer try/catch converts this into a tool result `{ content, isError: true }` rather than surfacing InvalidParams at the protocol level. The query is then normalized (`-_` collapsed to spaces, trimmed) before marketplace search.
Source
Thrown at src/main/ai/mcp/servers/skills.ts:109
case 'install_skill':
return await this.installSkill(args)
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`)
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
logger.error(`Tool error: ${toolName}`, { agentId: this.agentId, error: message })
return {
content: [{ type: 'text' as const, text: `Error: ${message}` }],
isError: true
}
}
})
}
private async searchSkills(args: Record<string, string | undefined>) {
const query = args.query
if (!query) throw new McpError(ErrorCode.InvalidParams, "'query' is required for search_skills")
const results = await searchSkillMarketplaces(
query.replace(/[-_]+/g, ' ').trim(),
(url) => this.fetchMarketplaceJson(url),
(source, error) => {
logger.warn('Skill marketplace search source failed', {
agentId: this.agentId,
source,
error: error instanceof Error ? error.message : String(error)
})
}
)
if (results.length === 0) {
return { content: [{ type: 'text' as const, text: `No installable skills found for "${query}".` }] }
}
const view = results.map((r) => ({View on GitHub (pinned to 726446b54c)
Solutions
- Pass a non-empty `query`: `{ "query": "pdf reader" }`.
- Use the exact key `query` (not `q`, `term`, `search`).
- Check the returned `isError` flag and message rather than expecting a thrown exception.
Example fix
// before
{ query: "" }
// after
{ query: "pdf reader" } Defensive patterns
Strategy: validation
Validate before calling
function buildSearchSkillsArgs(raw: unknown): { query: string } {
if (typeof (raw as any)?.query !== 'string') throw new TypeError("'query' string required")
const q = (raw as any).query.trim()
if (q.length === 0) throw new TypeError("'query' must be non-empty")
return { query: q }
} Type guard
const isSearchSkillsArgs = (v: unknown): v is { query: string } =>
typeof v === 'object' && v !== null && typeof (v as any).query === 'string' && (v as any).query.trim().length > 0 Try / catch
const result = await client.callTool({ name: 'search_skills', arguments: buildSearchSkillsArgs(raw) })
if (result.isError) {
const text = (result.content[0] as any).text
if (/'query' is required/.test(text)) {/* rebuild with a non-empty query */}
} Prevention
- Use the exact key `query` (not `q`, `term`, `search`).
- Trim and ensure non-empty before sending.
- Remember this server returns errors as `isError` results, not throws.
When it happens
Trigger: Calling `search_skills` with no `query`, an empty string, null, or undefined. The arg type is `Record<string, string | undefined>`, so non-string values are coerced/ignored.
Common situations: The model calls `search_skills` with `{}`; passes a whitespace-only query; omits the field; uses a different key like `q` or `term`.
Related errors
- ErrorCode.InvalidParams
- InvalidParams
- Invalid thought: must be a string
- Invalid thoughtNumber: must be a number
- Invalid totalThoughts: must be a number
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/f518ef0aee5483b2.
Report an issue: GitHub.