Migrate to McpServer.registerTool; fix double-encoded SoQL like pattern - #5
Migrate to McpServer.registerTool; fix double-encoded SoQL like pattern#5noneck wants to merge 2 commits into
Conversation
…lim data typing - Replace low-level Server (hand-written ListTools JSON schemas, per-case zod .parse, 7-way switch, repeated JSON content envelopes) with McpServer.registerTool; tool names, descriptions, and input schemas are unchanged from a client's perspective. - Read the server version from package.json via createRequire instead of a hardcoded "1.0.0" that had drifted from package.json's 1.0.1. - Replace the 32-field CityRecordNotice type with Record<string, string> (results are only JSON.stringify'd); field list kept as a doc comment. - Inline buildUrl into its single caller sodaFetch. - Add minimal node:test setup (npm test) with a tool-list regression test using the SDK's InMemoryTransport. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The $where value embedded %25 and encodeURIComponent output, which URLSearchParams.set then encoded again — Socrata received a literal '%25NAME%25' pattern (with %20 for spaces) instead of the % wildcard, so agency matches with spaces or special characters silently failed. Per the SoQL like docs (https://dev.socrata.com/docs/functions/like.html) the wildcard is a literal % in the query; %25 is only its URL encoding, which the HTTP layer already applies once. Build the SoQL value unencoded (escaping single quotes by doubling) and let searchParams.set do the single encoding pass. Test written first; it fails against the old code and passes now. Also fix the test script glob (node --test with a bare directory arg fails on newer Node). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The double-encoding bug fix (a034f12) has been extracted into its own PR against main (fix/agency-search-double-encoding) so it can merge without waiting on the refactor review. This branch should be rebased once that merges. |
|
Heads up: main has moved — v1.0.2 shipped today (SoQL escaping + NY-timezone fixes from #7 squash-merged, plus a version-from-package.json read and a |
|
Re-evaluated against current This PR remains open and unrebased. Its CI status is from 2026-07-06 against a 2026-07-01 merge base and should not be read as current. |
Problem
src/index.tsused the low-levelServerAPI: hand-written JSON-schema ListTools handler, per-casez.object(...).parse, a 7-way switch, and 7 copies of the JSON content envelope (~200 lines of boilerplate).1.0.0while package.json says1.0.1.CityRecordNoticedeclared 32 optional fields that were never field-accessed (results are onlyJSON.stringify'd).buildUrlhad a single caller.getNoticesByAgencyembedded%25+encodeURIComponent()inside the SoQL string, whichsearchParams.setencoded again — Socrata received a literal%25NAME%25pattern (spaces as%20), not the%wildcard, so agency matches with spaces/special characters silently failed.Fix
McpServer.registerTool(name, {description, inputSchema: zodShape}, handler)(SDK 1.29.0). Tool names, descriptions, and input schemas are unchanged from a client's perspective. Server construction lives insrc/server.ts(buildServer()) so tests can attach an in-memory transport;src/index.tsis now just the stdio entrypoint.createRequire(import.meta.url)— one source of truth. No version bump.CityRecordNoticeis nowRecord<string, string>; the field list is preserved as a doc comment.buildUrlinlined intosodaFetch.likevalue unencoded (literal%wildcard per the SoQL like docs, single quotes escaped by doubling) and letURLSearchParamsdo the single encoding pass.Test plan (already run)
node:testsetup (npm test): builds, then runstest/*.test.mjs.test/tools.test.mjs— SDK Client overInMemoryTransportasserts the 7 tool names are unchanged and each has a description + object input schema. Passes.test/encoding.test.mjs— mocksfetch, captures the final URL, asserts the decoded$whereequalsupper(agency_name) like upper('%Parks & Recreation%')and the raw query string contains%25but not%2525. Written first: fails on the old code (%25Parks%20%26%20Recreation%25), passes after the fix.npm run buildclean.🤖 Generated with Claude Code