ProvenPin Install

Typed fetch API client tested with MSW + Zod: working versions (tested 2026-10-15)

A typed fetch client that parses responses with Zod, tested offline by intercepting requests with MSW's Node server.

passed4 passed · 0 failed2026-10-15 11:36 UTC
Status
passed
Last passed
2026-10-15 11:36 UTC
Tests
4 / 4
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
zod@4.6.7tsx@4.23.15@types/node@24.19.2msw@3.0.3
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38728226240

The code

Exactly the files that passed. Your agent can pull them with get_recipe("msw-fetch-api-client") (install), or see them on GitHub.

package.json
{
  "name": "msw-fetch-api-client",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "zod": "4.6.7"
  },
  "devDependencies": {
    "tsx": "4.23.15",
    "@types/node": "24.19.2",
    "msw": "3.0.3"
  }
}
src/client.ts
import { z } from "zod";

export const User = z.object({ id: z.number(), name: z.string(), email: z.email() });
export type User = z.infer<typeof User>;

export class ApiError extends Error { constructor(public status: number) { super(`HTTP ${status}`); } }

export function createClient(baseUrl: string, token: string) {
  async function call<T>(path: string, schema: z.ZodType<T>, init?: RequestInit): Promise<T> {
    const res = await fetch(new URL(path, baseUrl), { ...init, headers: { authorization: `Bearer ${token}`, "content-type": "application/json", ...init?.headers } });
    if (!res.ok) throw new ApiError(res.status);
    return schema.parse(await res.json());
  }
  return {
    getUser: (id: number) => call(`/users/${id}`, User),
    createUser: (data: Omit<User, "id">) => call("/users", User, { method: "POST", body: JSON.stringify(data) }),
  };
}
test/client.test.ts
import { test, before, after, afterEach } from "node:test";
import assert from "node:assert/strict";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { createClient, ApiError } from "../src/client.ts";

const BASE = "https://api.example.com";
const server = setupServer(
  http.get(`${BASE}/users/:id`, ({ params, request }) => {
    if (request.headers.get("authorization") !== "Bearer t") return new HttpResponse(null, { status: 401 });
    return HttpResponse.json({ id: Number(params.id), name: "Ada", email: "ada@example.com" });
  }),
  http.post(`${BASE}/users`, async ({ request }) => HttpResponse.json({ id: 7, ...(await request.json() as object) }, { status: 201 })),
);
before(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
after(() => server.close());

test("gets and parses a user", async () => {
  assert.deepEqual(await createClient(BASE, "t").getUser(1), { id: 1, name: "Ada", email: "ada@example.com" });
});

test("posts a user", async () => {
  const u = await createClient(BASE, "t").createUser({ name: "Bo", email: "bo@example.com" });
  assert.equal(u.id, 7);
});

test("throws ApiError on 401", async () => {
  await assert.rejects(createClient(BASE, "bad").getUser(1), (e) => e instanceof ApiError && e.status === 401);
});

test("rejects malformed payloads", async () => {
  server.use(http.get(`${BASE}/users/:id`, () => HttpResponse.json({ id: "x" })));
  await assert.rejects(createClient(BASE, "t").getUser(1));
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.