ProvenPin Install

openapi-fetch typed client with MSW: working versions (tested 2026-10-15)

Call a typed OpenAPI paths client with openapi-fetch against MSW handlers, covering GET/POST and error status offline.

passed2 passed · 0 failed2026-10-15 16:03 UTC
Status
passed
Last passed
2026-10-15 16:03 UTC
Tests
2 / 2
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
openapi-fetch@0.17.0@types/node@26.6.5tsx@4.23.15msw@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 38764699834

The code

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

package.json
{
  "name": "openapi-fetch-msw",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {"openapi-fetch":"0.17.0"},
  "devDependencies": {"@types/node":"26.6.5","tsx":"4.23.15","msw":"3.0.3"}
}
src/client.ts
import createClient from "openapi-fetch";
import type { paths } from "./schema.ts";

export function createApi(baseUrl: string) {
  const client = createClient<paths>({ baseUrl });
  return {
    async list(q?: string) {
      const { data, error } = await client.GET("/items", { params: { query: { q } } });
      if (error) throw error;
      return data ?? [];
    },
    async create(name: string) {
      const { data, error, response } = await client.POST("/items", {
        body: { name },
      });
      if (error) throw error;
      if (!data) throw new Error(`create failed: ${response.status}`);
      return data;
    },
    async get(id: number) {
      const { data, error, response } = await client.GET("/items/{id}", {
        params: { path: { id } },
      });
      if (response.status === 404) throw Object.assign(new Error("not found"), { status: 404 });
      if (error) throw error;
      return data!;
    },
  };
}
src/schema.ts
/** Minimal OpenAPI paths typing for openapi-fetch (hand-written stand-in for openapi-typescript output). */
export type paths = {
  "/items": {
    get: {
      parameters: { query?: { q?: string } };
      responses: {
        200: { content: { "application/json": { id: number; name: string }[] } };
      };
    };
    post: {
      requestBody: { content: { "application/json": { name: string } } };
      responses: {
        201: { content: { "application/json": { id: number; name: string } } };
      };
    };
  };
  "/items/{id}": {
    get: {
      parameters: { path: { id: number } };
      responses: {
        200: { content: { "application/json": { id: number; name: string } } };
        404: { content: { "application/json": { error: string } } };
      };
    };
  };
};
test/client.test.ts
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { createApi } from "../src/client.ts";

const items = [{ id: 1, name: "bolt" }];
const server = setupServer(
  http.get("http://local/items", ({ request }) => {
    const q = new URL(request.url).searchParams.get("q");
    return HttpResponse.json(q ? items.filter((i) => i.name.includes(q)) : items);
  }),
  http.post("http://local/items", async ({ request }) => {
    const body = (await request.json()) as { name: string };
    const item = { id: items.length + 1, name: body.name };
    items.push(item);
    return HttpResponse.json(item, { status: 201 });
  }),
  http.get("http://local/items/:id", ({ params }) => {
    const item = items.find((i) => i.id === Number(params.id));
    if (!item) return HttpResponse.json({ error: "missing" }, { status: 404 });
    return HttpResponse.json(item);
  }),
);

before(() => server.listen({ onUnhandledRequest: "error" }));
after(() => server.close());

test("lists filters and creates", async () => {
  const api = createApi("http://local");
  assert.equal((await api.list()).length, 1);
  assert.equal((await api.list("bo"))[0].name, "bolt");
  const created = await api.create("nut");
  assert.equal(created.name, "nut");
  assert.equal((await api.get(created.id)).name, "nut");
});

test("404 becomes error", async () => {
  const api = createApi("http://local");
  await assert.rejects(() => api.get(999), (e: any) => e.status === 404);
});

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.