{"record":{"id":"4c51c53b66ffded8","repo":"thedotmack/claude-mem","slug":"key-is-required","errorCode":null,"errorMessage":"\"${key}\" is required","messagePattern":"\"(.+?)\" is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/server/mcp/recall-mcp-server.ts","lineNumber":90,"sourceCode":"      type: 'object',\n      properties: {\n        projectId: { type: 'string', description: 'Project to list.' },\n        limit: { type: 'integer', minimum: 1, maximum: RECENT_LIMIT.max },\n      },\n      required: ['projectId'],\n    },\n  },\n];\n\nfunction clampLimit(raw: unknown, spec: { default: number; max: number }): number {\n  if (typeof raw !== 'number' || !Number.isFinite(raw)) return spec.default;\n  return Math.min(Math.max(1, Math.trunc(raw)), spec.max);\n}\n\nfunction requireString(args: Record<string, unknown>, key: string): string {\n  const value = args[key];\n  if (typeof value !== 'string' || value.trim().length === 0) {\n    throw new Error(`\"${key}\" is required`);\n  }\n  return value;\n}\n\nfunction jsonResult(payload: unknown): CallToolResult {\n  return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };\n}\n\n// Dispatches a single tool call to the backend. Throws on unknown tools or\n// invalid arguments; `createRecallMcpServer` converts those into MCP errors.\nasync function dispatchToolCall(\n  backend: RecallBackend,\n  name: string,\n  args: Record<string, unknown>,\n): Promise<CallToolResult> {\n  if (name === 'search') {\n    const observations = await backend.search({\n      projectId: requireString(args, 'projectId'),","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/server/mcp/recall-mcp-server.ts#L72-L108","documentation":"Thrown by requireString() in the recall MCP server when a tool argument named by `key` is missing, not a string, or whitespace-only. It guards the search/context/recent tool dispatch: projectId is required for all three, and query is required for search/context. The server converts this thrown error into an MCP error response via createRecallMcpServer.","triggerScenarios":"An MCP client calls `search` or `context` without projectId or query, or with an empty/whitespace string; `recent` is called without projectId; the client sends a number/object where a string is expected.","commonSituations":"A client built against an older tool schema omits a field. An LLM-driven MCP client hallucinates partial args. A manual JSON-RPC call forgets a required key. A client passes query as a number.","solutions":["Pass projectId (and query for search/context) as non-empty strings in the tool arguments.","Validate args against the tool's inputSchema before calling (required: ['projectId','query'] for search/context; ['projectId'] for recent).","Update the client to the current tool list so it sends all required fields.","If driving the tool from an LLM, ensure the tool description makes the required fields explicit."],"exampleFix":"// before: query omitted -> \"\\\"query\\\" is required\"\nclient.callTool({ name: 'search', arguments: { projectId: 'p1' } });\n// after\nclient.callTool({ name: 'search', arguments: { projectId: 'p1', query: 'auth flow' } });","handlingStrategy":"validation","validationCode":"function validateToolArgs(name: string, args: Record<string, unknown>): void {\n  const needs = name === 'recent' ? ['projectId'] : ['projectId', 'query'];\n  for (const key of needs) {\n    const v = args[key];\n    if (typeof v !== 'string' || v.trim().length === 0) {\n      throw new Error(`\"${key}\" is required`);\n    }\n  }\n}\n// call validateToolArgs(name, args) before dispatchToolCall","typeGuard":"function isSearchArgs(a: unknown): a is { projectId: string; query: string; limit?: number } {\n  return typeof (a as { projectId?: unknown })?.projectId === 'string'\n    && typeof (a as { query?: unknown })?.query === 'string';\n}","tryCatchPattern":"// createRecallMcpServer already converts thrown errors to MCP error results.\n// In a custom wrapper:\ntry {\n  result = await dispatchToolCall(backend, name, args);\n} catch (error) {\n  return { content: [{ type: 'text', text: (error as Error).message }], isError: true };\n}","preventionTips":["Validate tool arguments against the declared inputSchema on the client before calling.","Keep MCP clients updated to the current tool list/schema.","When an LLM drives the tool, make required fields explicit in the tool description."],"tags":["mcp","validation","recall","arguments"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}