{"record":{"id":"f518ef0aee5483b2","repo":"CherryHQ/cherry-studio","slug":"invalidparams-f518ef","errorCode":"InvalidParams","errorMessage":"'query' is required for search_skills","messagePattern":"'query' is required for search_skills","errorType":"exception","errorClass":"McpError","httpStatus":null,"severity":"error","filePath":"src/main/ai/mcp/servers/skills.ts","lineNumber":109,"sourceCode":"          case 'install_skill':\n            return await this.installSkill(args)\n          default:\n            throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${toolName}`)\n        }\n      } catch (error) {\n        const message = error instanceof Error ? error.message : String(error)\n        logger.error(`Tool error: ${toolName}`, { agentId: this.agentId, error: message })\n        return {\n          content: [{ type: 'text' as const, text: `Error: ${message}` }],\n          isError: true\n        }\n      }\n    })\n  }\n\n  private async searchSkills(args: Record<string, string | undefined>) {\n    const query = args.query\n    if (!query) throw new McpError(ErrorCode.InvalidParams, \"'query' is required for search_skills\")\n\n    const results = await searchSkillMarketplaces(\n      query.replace(/[-_]+/g, ' ').trim(),\n      (url) => this.fetchMarketplaceJson(url),\n      (source, error) => {\n        logger.warn('Skill marketplace search source failed', {\n          agentId: this.agentId,\n          source,\n          error: error instanceof Error ? error.message : String(error)\n        })\n      }\n    )\n\n    if (results.length === 0) {\n      return { content: [{ type: 'text' as const, text: `No installable skills found for \"${query}\".` }] }\n    }\n\n    const view = results.map((r) => ({","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/mcp/servers/skills.ts#L91-L127","documentation":"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.","triggerScenarios":"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.","commonSituations":"The model calls `search_skills` with `{}`; passes a whitespace-only query; omits the field; uses a different key like `q` or `term`.","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."],"exampleFix":"// before\n{ query: \"\" }\n// after\n{ query: \"pdf reader\" }","handlingStrategy":"validation","validationCode":"function buildSearchSkillsArgs(raw: unknown): { query: string } {\n  if (typeof (raw as any)?.query !== 'string') throw new TypeError(\"'query' string required\")\n  const q = (raw as any).query.trim()\n  if (q.length === 0) throw new TypeError(\"'query' must be non-empty\")\n  return { query: q }\n}","typeGuard":"const isSearchSkillsArgs = (v: unknown): v is { query: string } =>\n  typeof v === 'object' && v !== null && typeof (v as any).query === 'string' && (v as any).query.trim().length > 0","tryCatchPattern":"const result = await client.callTool({ name: 'search_skills', arguments: buildSearchSkillsArgs(raw) })\nif (result.isError) {\n  const text = (result.content[0] as any).text\n  if (/'query' is required/.test(text)) {/* rebuild with a non-empty query */}\n}","preventionTips":["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."],"tags":["mcp","validation","skills-server","invalid-params","soft-error"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}