Skip to content

fix(mcp): treat empty optionalString args as absent so ctx_search file: "" searches unfiltered#80

Open
serhiizghama wants to merge 2 commits into
agentctxhq:mainfrom
serhiizghama:fix/optional-string-empty-as-absent
Open

fix(mcp): treat empty optionalString args as absent so ctx_search file: "" searches unfiltered#80
serhiizghama wants to merge 2 commits into
agentctxhq:mainfrom
serhiizghama:fix/optional-string-empty-as-absent

Conversation

@serhiizghama

Copy link
Copy Markdown
Contributor

Problem

Fixes #76.

The MCP arg helpers disagree on empty strings. requireString rejects an empty/whitespace-only string, but optionalString (packages/agentctx/src/mcp/tools.ts) only checks the type, so "" (or " ") passes through as a present value.

This has a concrete effect in ctx_search. file comes from optionalString, and the handler branches on file === undefined to decide whether to apply the file filter:

const file = optionalString(args, "file");
const searchLimit = file === undefined ? limit : SEARCH_LIMIT_MAX;
...
if (file !== undefined) {
  const linked = linkedRecordIds(ctx, file); // file = "" → no node matches
  hits = hits.filter((hit) => linked.has(hit.record.id)).slice(0, limit);
}

So ctx_search(query, file: "") is treated as "filter to a file named ''", matches no entity, and returns zero results instead of an unfiltered search. The same gap applies to supersedes (also read via optionalString).

Solution

Return undefined from optionalString for empty/whitespace-only strings, so an optional argument set to "" is equivalent to omitting it. This matches requireString's non-empty rule and the codebase's own nonEmptyString convention in extract/schema.ts. Returning undefined (rather than throwing) is appropriate because file/supersedes are genuinely optional.

No change to the non-empty file/supersedes paths or to requireString.

Testing

Added a regression test in test/mcp/tools.test.ts: with one file-linked record and one unrelated record, ctx_search with file: "" and file: " " now returns the same results as omitting file (both records), not an empty set. Verified it fails on the unfixed helper and passes with the fix.

npm run typecheck   # clean
npm run lint        # clean
npm test            # 298 passed

optionalString only checked the type, so an empty or whitespace-only string
passed through as a present value while its sibling requireString rejects the
same input. In ctx_search this made `file: ""` behave as "filter to a file
named ''", which links to no entity and silently returned zero results instead
of an unfiltered search. The same gap affected `supersedes`.

Return undefined for empty/whitespace-only strings so an optional argument set
to "" is equivalent to omitting it, aligning with requireString's non-empty rule.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant