You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
#9527 converted packages/loopover-mcp/bin/loopover-mcp.ts from hand-written JavaScript to TypeScript, and validate:no-hand-written-js keeps it that way. But the conversion kept the file compiling by annotating 213 parameters and locals as : any — so the published stdio server is TypeScript in name only. tsc checks almost nothing about the file that actually serves 100+ MCP tools and the whole loopover-mcp CLI.
Measured on main: 213 : any in that one file, versus 1 in src/mcp/server.ts and 1 in packages/loopover-mcp/lib/telemetry.ts. The remote server got real types; the stdio server did not.
This is not cosmetic. The anys sit on exactly the boundaries the contract exists to type:
parseOptions(args: any) returns const options: any, and that any then flows into 20(options: any) parameters across every command handler. A misspelled option field is not a compile error anywhere in the CLI.
runCli(args: any) and maintainCli(args: any) take any where argv is always string[] — 16 sites.
Tool handlers take (input: any) where getToolContract(name).input gives the exact zod type the server validates against.
The failure mode is the one #9521 was filed about: "the stdio CLI read every API response as payload: any and picked fields out by optional-chaining guesswork, so a renamed Worker field degraded silently at runtime instead of failing anywhere." #9521 built the typed accessors. This issue is the other half — actually using them.
Requirements
packages/loopover-mcp/bin/loopover-mcp.ts contains zero: any annotations.
parseOptions takes readonly string[] and returns a named, exported options type derived from CLI_FLAG_SPEC — repeatable flags typed as arrays, boolean flags as booleans, the rest as strings — so a handler reading a field the parser cannot produce is a compile error.
Every command handler's options parameter uses that type (or a Pick of it), not a restatement.
runCli, maintainCli, and every other argv-taking function take readonly string[].
Tool handlers registered through registerStdioTool receive the input type the contract declares, not any.
Where a value is genuinely unknown at that point (a raw JSON body before validation, a third-party payload), it is unknown and narrowed — notany. unknown is a real type; any is the absence of one.
A guard test asserts the file has zero : any, so the debt cannot silently return the way it arrived.
⚠️ Required pattern: this is a TYPING change, not a behavior change. No runtime logic may be altered to satisfy the compiler — no added guards, no changed defaults, no reordered checks, no new validation. If a genuine type error surfaces (a field read that the response cannot have, a flag handled that the parser never sets), that is a DEFECT the any was hiding: fix it and name it in the PR body. The full suite must pass unchanged, and npm run build:mcp's emitted JavaScript must be behaviorally identical.
Deliverables
Zero : any in packages/loopover-mcp/bin/loopover-mcp.ts
parseOptions typed from CLI_FLAG_SPEC, with its return type exported and used by every handler
Every argv parameter is readonly string[]
Tool handlers take their contract input type
Genuinely-unknown values are unknown + narrowing, with no any reintroduced under another name
A guard test failing on any : any in that file, asserted to find zero
Every defect the any was hiding is fixed and named in the PR body
npm run build:mcp and the full stdio CLI suite pass unchanged
Test Coverage Requirements
packages/loopover-mcp/{bin,lib}/** is in vitest's coverage.include and the 99% branch-counted patch gate applies. A pure typing change adds no branches, so coverage comes from the existing CLI suites — but any line that DOES change (a defect fix) needs its own case, and the guard test is the named regression test for this issue.
Expected Outcome
The stdio server is typed by the same contract the remote server is, so a renamed API field or a misspelled option is a compile error in CI rather than a silent undefined in a user's terminal.
Links & Resources
packages/loopover-mcp/bin/loopover-mcp.ts (parseOptions at :4140, runCli at :2852, maintainCli at :2526, the apiGet/apiPost overloads at :5619-5629); CLI_FLAG_SPEC at :332. Follows #9527 (the TypeScript lock) and #9521 (the typed accessors this uses). Part of #9515.
Context
#9527 converted
packages/loopover-mcp/bin/loopover-mcp.tsfrom hand-written JavaScript to TypeScript, andvalidate:no-hand-written-jskeeps it that way. But the conversion kept the file compiling by annotating 213 parameters and locals as: any— so the published stdio server is TypeScript in name only.tscchecks almost nothing about the file that actually serves 100+ MCP tools and the wholeloopover-mcpCLI.Measured on
main: 213: anyin that one file, versus 1 insrc/mcp/server.tsand 1 inpackages/loopover-mcp/lib/telemetry.ts. The remote server got real types; the stdio server did not.This is not cosmetic. The
anys sit on exactly the boundaries the contract exists to type:parseOptions(args: any)returnsconst options: any, and thatanythen flows into 20(options: any)parameters across every command handler. A misspelled option field is not a compile error anywhere in the CLI.runCli(args: any)andmaintainCli(args: any)takeanywhere argv is alwaysstring[]— 16 sites.(value: any)callbacks map over API responses thatapiGet/apiPost'sApiResponse<Path>overloads (contract: generate every derived surface — typed stdio client, CLI from one spec table, tool-reference docs, miner dedup, UI z.infer types #9521) already type precisely, so the type is available and discarded.(input: any)wheregetToolContract(name).inputgives the exact zod type the server validates against.The failure mode is the one #9521 was filed about: "the stdio CLI read every API response as
payload: anyand picked fields out by optional-chaining guesswork, so a renamed Worker field degraded silently at runtime instead of failing anywhere." #9521 built the typed accessors. This issue is the other half — actually using them.Requirements
packages/loopover-mcp/bin/loopover-mcp.tscontains zero: anyannotations.parseOptionstakesreadonly string[]and returns a named, exported options type derived fromCLI_FLAG_SPEC— repeatable flags typed as arrays, boolean flags as booleans, the rest as strings — so a handler reading a field the parser cannot produce is a compile error.optionsparameter uses that type (or aPickof it), not a restatement.runCli,maintainCli, and every other argv-taking function takereadonly string[].registerStdioToolreceive the input type the contract declares, notany.unknownand narrowed — notany.unknownis a real type;anyis the absence of one.: any, so the debt cannot silently return the way it arrived.Deliverables
: anyinpackages/loopover-mcp/bin/loopover-mcp.tsparseOptionstyped fromCLI_FLAG_SPEC, with its return type exported and used by every handlerreadonly string[]unknown+ narrowing, with noanyreintroduced under another name: anyin that file, asserted to find zeroanywas hiding is fixed and named in the PR bodynpm run build:mcpand the full stdio CLI suite pass unchangedTest Coverage Requirements
packages/loopover-mcp/{bin,lib}/**is in vitest'scoverage.includeand the 99% branch-counted patch gate applies. A pure typing change adds no branches, so coverage comes from the existing CLI suites — but any line that DOES change (a defect fix) needs its own case, and the guard test is the named regression test for this issue.Expected Outcome
The stdio server is typed by the same contract the remote server is, so a renamed API field or a misspelled option is a compile error in CI rather than a silent
undefinedin a user's terminal.Links & Resources
packages/loopover-mcp/bin/loopover-mcp.ts(parseOptionsat:4140,runCliat:2852,maintainCliat:2526, theapiGet/apiPostoverloads at:5619-5629);CLI_FLAG_SPECat:332. Follows #9527 (the TypeScript lock) and #9521 (the typed accessors this uses). Part of #9515.