Part of #935. One mechanical PR — independent of #936.
Goal
Introduce the respond* family and route every tool response through it. Mechanical substitution; wire output stays byte-identical so apify-mcp-server-internal sees no change.
Scope
- Add to
src/utils/mcp.ts:
respondOk(text, opts?) — success with text ({ structuredContent?, meta? })
respondJson(value, opts?) — success carrying JSON; owns the ```json fence
respondEmpty(text) — success, intentionally no data (thin alias of respondOk for now)
respondUserError(text, opts?) — SOFT_FAIL; category defaults to INVALID_INPUT (Exclude<…, 'INTERNAL_ERROR'>), httpStatus?, detail?, structuredContent?
respondServerError(text, opts?) — FAILED; pass the caught error, category/httpStatus derived via existing classifyFailureCategory / getHttpStatusCode
- Migrate all
src/tools handlers + the inline error paths in src/mcp/server.ts.
- Delete the 6 domain builders:
buildActorNotFoundResponse, buildSearchActorsEmptyResponse, buildPermissionApprovalResponse, buildPermissionApprovalErrorResponse, buildGetActorRunError, buildCallActorErrorResponse, plus the inline storage not-found literals.
- Un-export
buildMCPResponse (make it private to utils/mcp.ts); shrink extractToolTelemetry accordingly.
- Collapse the ~8 hand-rolled
```json fences into respondJson.
Fixes within this PR
- P2 —
respondServerError fills failureCategory by default → no more "category unknown".
- P3 — the 10 duplicated
SOFT_FAIL + INVALID_INPUT literals collapse into respondUserError.
Out of scope
- The brand + narrowing
ToolEntry.call (the compile-time lock) — separate sub-issue. call stays Promise<object> here.
- A real
NO_DATA telemetry status (P5).
Acceptance
- No remaining call sites build raw response shapes or inline JSON fences.
pnpm run type-check, pnpm run lint, pnpm run test:unit clean; assertions migrated to the respond* shapes.
Part of #935. One mechanical PR — independent of #936.
Goal
Introduce the
respond*family and route every tool response through it. Mechanical substitution; wire output stays byte-identical soapify-mcp-server-internalsees no change.Scope
src/utils/mcp.ts:respondOk(text, opts?)— success with text ({ structuredContent?, meta? })respondJson(value, opts?)— success carrying JSON; owns the ```json fencerespondEmpty(text)— success, intentionally no data (thin alias ofrespondOkfor now)respondUserError(text, opts?)—SOFT_FAIL;categorydefaults toINVALID_INPUT(Exclude<…, 'INTERNAL_ERROR'>),httpStatus?,detail?,structuredContent?respondServerError(text, opts?)—FAILED; pass the caughterror, category/httpStatus derived via existingclassifyFailureCategory/getHttpStatusCodesrc/toolshandlers + the inline error paths insrc/mcp/server.ts.buildActorNotFoundResponse,buildSearchActorsEmptyResponse,buildPermissionApprovalResponse,buildPermissionApprovalErrorResponse,buildGetActorRunError,buildCallActorErrorResponse, plus the inline storage not-found literals.buildMCPResponse(make it private toutils/mcp.ts); shrinkextractToolTelemetryaccordingly.```jsonfences intorespondJson.Fixes within this PR
respondServerErrorfillsfailureCategoryby default → no more "category unknown".SOFT_FAIL + INVALID_INPUTliterals collapse intorespondUserError.Out of scope
ToolEntry.call(the compile-time lock) — separate sub-issue.callstaysPromise<object>here.NO_DATAtelemetry status (P5).Acceptance
pnpm run type-check,pnpm run lint,pnpm run test:unitclean; assertions migrated to therespond*shapes.