foldcase-mcp

foldcase-mcp

Foldcase MCP server exposes three read-only tools over stdio that serve a coding agent the full catalog of Foldkit component Showcases — enumerating components and their states, describing Message payload schemas as JSON Schema documents, and documenting Model schemas — all derived from a single Showcase definition.

Category
访问服务器

README

<p align="center"> <img src="docs/brand/mark.svg#gh-light-mode-only" alt="Foldcase" width="88" height="88"> <img src="docs/brand/mark-inverse.svg#gh-dark-mode-only" alt="Foldcase" width="88" height="88"> </p>

Foldcase

One typed record per component state — read by your coding agent, your CI, your docs, and your coverage.

<!-- site:skip -->

📖 Read the documentation — this README, the changelog and every ADR, with search, per-page Markdown and an llms.txt for agents. It is generated from the files in this repository, so it cannot say anything they do not (see ADR-0003).

<!-- /site:skip -->

Foldcase exists so a coding agent can work on a Foldkit codebase without guessing. Foldkit ships two ways to test a component — a Story over update, a Scene over the rendered markup — but both are functions: they run, they pass, and afterwards nothing can ask them which components exist, which states are covered, or what a Message payload must contain. A Showcase writes those facts down. It is a record, not a function, so one declaration can be enumerated, introspected into JSON Schema, run by id, and reported as typed data.

Point an agent at foldcase mcp and it stops reading your source to learn what a component does: it lists the catalog, reads the Message Schema, builds a valid payload by construction, runs the Showcase, and gets back a structured pass/fail that names the file to open. The same loop runs from the shell as foldcase test --json. And the same declaration produces your CI exit code, your Markdown schema tables, and your per-state coverage — so what the agent reads and what CI enforces cannot drift apart.

  • foldcase test — every Showcase headless, one exit code for CI, --json for machines, --coverage attributed per component state.
  • foldcase mcp — six read-only tools that serve the catalog to an agent over stdio.
  • foldcase docs — one Markdown file per component, tabling its Model and Message Schemas.
  ✓ counter/starts-at-zero
  ✓ counter/step-of-ten
  ✗ tasks/adds-in-order — expected 2, got 1

3 total · 2 passed · 1 failed

Quickstart

From nothing to a green run:

mkdir hello && cd hello && bun init -y
bun add -d foldcase@alpha effect@4.0.0-beta.102 @effect/platform-bun@4.0.0-beta.102

Under Node 22.18+ swap the tooling: npm install -D foldcase@alpha effect@4.0.0-beta.102 @effect/platform-node@4.0.0-beta.102, then npx foldcase wherever this page says bunx foldcase-bun. Pin the platform package to the same Effect beta your app pins — latest is still the v3 major and will not load; see Peer dependencies.

hello.showcase.ts — self-contained, so the pipeline shows without Foldkit:

import assert from "node:assert/strict"

import type { Showcase } from "foldcase"

type Model = { count: number }
const update = (model: Model, step: number): Model => ({ count: model.count + step })

export const showcases: ReadonlyArray<Showcase> = [
  {
    id: "counter/counts-two-clicks",
    play: () => {
      const model = update(update({ count: 0 }, 1), 1)
      assert.equal(model.count, 2)
    },
  },
]
bunx foldcase-bun test
  ✓ counter/counts-two-clicks

1 total · 1 passed · 0 failed

Exit code 0. In a Foldkit app, play holds a Story and the record carries the real Message and Model Schemas — see Writing a Showcase.

Wire it into your agent

One command does the wiring:

npx foldcase init        # or: bunx foldcase-bun init

It adds the server entry below to .mcp.json — choosing bunx or npx by your lockfile, and setting FOLDCASE_SHOWCASE_DIR=src when src/ exists — and appends the AGENTS.md block. Both edits are idempotent and merge-safe: an existing .mcp.json keeps its other servers, a foldcase entry already there is left alone, and a second run reports kept and changes nothing. By hand, the same entry (Claude Code and most MCP hosts) is:

{
  "mcpServers": {
    "foldcase": {
      "command": "npx",
      "args": ["foldcase", "mcp"],
      "env": { "FOLDCASE_SHOWCASE_DIR": "src" }
    }
  }
}

Or in one line: claude mcp add foldcase -e FOLDCASE_SHOWCASE_DIR=src -- npx foldcase mcp. Under Bun the command is bunx with args ["foldcase-bun", "mcp"].

FOLDCASE_SHOWCASE_DIR (default .) is read by mcp only. A relative value resolves against the directory the host launches the server in — for Claude Code, the project root — and becomes the server's root: foldcase_load_catalog resolves relative directories against that root, never against the currently served one, so no sequence of loads can walk away from it.

The six tools, all annotated readOnlyHint: true, destructiveHint: false, openWorldHint: false — a host does not prompt for confirmation to list a catalog:

Tool What it does
foldcase_list_showcases Enumerate every Showcase — which Schemas it carries, which Messages it dispatches, the directory served — and per-component gaps: the Messages no play dispatches.
foldcase_get_showcase_schema Introspect a Showcase's Message union into a JSON Schema document, so the agent builds a valid payload by construction.
foldcase_get_showcase_model_schema The same for the Model — the shape a play asserts on, which an agent has to know before it writes one.
foldcase_run_showcase Run one Showcase's play — in a fresh subprocess, from the code on disk — and return the typed pass/fail report, naming the file it came from.
foldcase_run_catalog Run the whole catalog into one suite report, or the part of it under an id prefix: counter/ runs one component. Fresh from disk, like foldcase_run_showcase.
foldcase_load_catalog Refresh the listing after files appear or vanish, or point the server at another directory under the root. Reports how many Showcases came back and which files would not load. A run never needs it.

The loop an agent runs:

  1. foldcase_list_showcases — which components exist, in which states; the hasMessageSchema / hasModelSchema flags say which introspection call is worth making, and gaps names, per component, the Messages no play dispatches — the next Showcase to write.
  2. foldcase_get_showcase_schema — the Message union as a draft-2020-12 JSON Schema document: a valid payload by construction, not by reading the source.
  3. foldcase_get_showcase_model_schema — the shape a play asserts on; read it before writing one.
  4. Edit the code, or add a Showcase.
  5. foldcase_run_showcase for one state, or foldcase_run_catalog with id_prefix: "counter/" for one component. A run executes in a fresh subprocess and reads the disk, so the edit it is verifying — even a brand-new file — is already in it, no reload needed. A failing play is status: "failed" with a full SerializedError — data, never a tool error — and a mistyped id returns every available id, so it corrects itself in one round trip.
  6. foldcase_load_catalog to refresh the listing after files appear or vanish. The listing and the two schema tools read modules the server already imported, so after an edit inside a loaded file their metadata can lag until the server restarts; the run tools never lag.

No MCP host? The same loop is the CLI: foldcase test --json prints one typed JSON document on stdout, diagnostics on stderr, and every report names its file.

Foldcase is the static half of a two-server loop. @foldkit/devtools-mcp drives the live runtime — it needs a Vite dev server and an open browser tab; Foldcase needs only a directory of files, so it also runs in CI. The verbs pair up:

question live — @foldkit/devtools-mcp declared — foldcase mcp
what exists foldkit_list_runtimes (open tabs) foldcase_list_showcases
Message shape foldkit_get_message_schema foldcase_get_showcase_schema
the Model foldkit_get_model (the value now) foldcase_get_showcase_model_schema (the type)
act foldkit_dispatch_message foldcase_run_showcase

devtools-mcp answers what the app is doing right now; Foldcase answers what the app is supposed to do.

Paste into your AGENTS.md

## Showcases (Foldcase)

- Components are described by Showcases: `export const showcases: ReadonlyArray<Showcase>`
  in `*.showcase.ts` files. The record is `{ id, play, message?, model?, dispatches? }`;
  an id is `component/state`.
- To learn a component, use the `foldcase_*` MCP tools or `npx foldcase test --json` —
  do not parse `*.showcase.ts` files or crawl the source for the same facts.
- When you add a component state, add a Showcase for it, and name the Message tags its
  play sends in `dispatches` — the listing's `gaps` then says which Messages still have
  no Showcase.
- When you change a Message or Model Schema, regenerate the tables: `npx foldcase docs
  src docs/schemas`. In CI, `--check` fails on drift instead of writing.
- The run tools execute what is on disk, edits included. Only the listing and schema
  tools can lag behind an edit inside a loaded file; restart the server to refresh them.
- Exit 1 means a failed Showcase, an unloadable file, or an empty catalog. All three are
  reported as data; one bad file never hides the rest.

Why a Showcase

Most component-test loops mount a browser, poke the DOM and read untyped callbacks. Foldkit removes the need: the Model is one serializable value, the Message union is an Effect Schema, and update is pure — the same Messages always give the same Model. A test over those facts asserts on the whole state instead of scraped text, and it cannot flake.

But a test is a function that runs, not a description of anything. It cannot say which components exist, which states they are shown in, or what Messages they take — so the same knowledge gets written down again for the docs, and again for whatever a coding agent is told, and the copies drift. A Showcase declares the component, the state and the play once; the CI run, the Markdown docs, the coverage report and the agent's catalog are all read off that single record — never parsed back out of your source (ADR-0001).

bun test alone with Foldcase
Assert a Model after typed Messages yes yes — foldcase test, one exit code for CI
Enumerate the components and their states no foldcase mcp serves the whole catalog
Answer what a Message payload looks like no the Message Schema as a JSON Schema document
Document the Model no foldcase docs — one Markdown table per component
Attribute coverage to a component state no --coverage — per Showcase and in aggregate
Hand any of it to a coding agent no six read-only MCP tools over stdio

Story, Scene, Showcase

Foldkit already ships two ways to test a component. Foldcase adds a third that is not a way of testing at all.

Storyfoldkit/story — drives update and everything it returns: send Messages, resolve the Commands they produce (Command.resolveAll cascades through a whole async flow), assert on the Model and on OutMessages. It accounts for every Command the reducer returned, so a story cannot pass on one you forgot to think about.

story(
  update,
  given(initialModel),
  message(ClickedIncrement()),
  Command.expectNone(),
  model((m) => assert.equal(m.count, 1)),
)

Scenefoldkit/scene — mounts { update, view } and reads the rendered markup the way a user does: getByRole, getByLabel, click, type, dropFiles. It renders to Foldkit's virtual tree and queries that, so it needs no browser and no DOM — the vitest-plus-happy-dom setup in Foldkit's examples is convention, not a requirement; examples/counter runs two Scene Showcases under both bins with no DOM package installed. A failed assertion names the locator — Expected element matching button "Restart" to exist — so a miss reads as "the control you asked for is not in the markup". What a Scene deliberately cannot see is the Model: it asserts through the view.

scene(
  { update, view },
  given(homeModel),
  expect(role("link", { name: "Calendar" })).toExist(),
)

The two answer different questions, and neither catches the other's bugs — and some bugs escape both. Building the Foldkit component gallery showed the ceiling: a Story proved DragAndDrop moves a card between columns and lands it in the right place, and it was right — but a real browser showed that the card's element is rebuilt when it changes column, so focus drops to <body> halfway through a keyboard drag. Element identity across a re-render is invisible to a Model assertion and to a virtual render alike; only a browser sees it. Test the machine with a Story, the markup with a Scene, and keep a browser in the loop for what only it can show.

A Showcase is neither. It is a record, not a function:

{ id, play, message?, model?, dispatches? }

play usually holds a Story — the cheap, deterministic half — but the runner never looks inside it. What Foldcase reads is everything around it: the id names one component in one state, and message/model are the Schemas that state is built from. A Story and a Scene are functions a runner calls; they run, they pass, and they are gone. A Showcase is a description that stays readable, so one declaration feeds the CI run, the Markdown tables, the coverage attribution and the agent's catalog at once.

drives answers cannot see
Story update + Commands + OutMessages is the state machine right the markup
Scene update + view, virtually rendered does the markup reach the machine the Model — opaque by design
Showcase whatever play holds — usually a Story which components exist, in which states, built from which Schemas

Foldcase does not depend on Foldkit, and play is an opaque thunk: it can hold a Story, a Scene, another assertion library, or plain code, and stays headless either way.

Writing a Showcase

A Showcase file is named *.showcase.ts and exports one array. There is no metadata format to learn and no parse step — the array is the catalog.

import assert from "node:assert/strict"

import type { Showcase } from "foldcase"
import { Story } from "foldkit/test"

import { ClickedIncrement, initialModel, Message, Model, update } from "./counter"

export const showcases: ReadonlyArray<Showcase> = [
  {
    id: "counter/click-twice",
    play: () =>
      Story.story(
        update,
        Story.given(initialModel),
        Story.message(ClickedIncrement()),
        Story.message(ClickedIncrement()),
        Story.model((model) => assert.equal(model.count, 2)),
      ),
    message: Message, // optional — the Message-union Schema
    model: Model, // optional — the Model Schema
    dispatches: ["ClickedIncrement"], // optional — the tags this play sends
  },
]

Assertions come from node:assert rather than bun:test, because a catalog is loaded by whichever bin you run — foldcase under Node, foldcase-bun under Bun — and an import only one runtime has would tie the catalog to that runtime.

The seam is small on purpose:

export interface Showcase {
  readonly id: string
  readonly play: () => void | Promise<void> // throws on assertion failure
  readonly message?: Schema.Top // Message-union Schema — read by `mcp` and `docs`
  readonly model?: Schema.Top // Model Schema — read by `docs` and `mcp`
  readonly dispatches?: ReadonlyArray<string> // Message tags the play sends — validated against `message`
}

play is any thunk that throws when an assertion fails, so a Showcase is not tied to one assertion library or one framework. message and model are optional; a Showcase that declares neither still runs, and foldcase docs simply writes no page for it — there is nothing to table. dispatches is optional too, and declared rather than observed — a closure cannot be watched — so it is validated against the Message union: a tag the union does not carry fails foldcase docs, and an absent declaration means unknown, never "sends nothing" (an empty array says that).

Everything Foldcase does is derived from this one record. See ADR-0001.

A working app is in examples/counter — two Foldkit components, ten Showcases (two of them holding a Scene), every dispatches declared, and the Markdown foldcase docs writes from them. mise run dogfood drives it under both bins in CI, so the example is a check as well as a demo.

A type-only import must say import type

The foldcase bin loads your catalog through Node's type stripping, and Node cannot tell a type-only import from a value import — it emits a real ESM import for both. So a showcase, or any module it reaches, that writes

import { Document, Html } from "foldkit/html" // these are types

will not load: SyntaxError: The requested module 'foldkit/html' does not provide an export named 'Document'. Write import type { Document, Html } from "foldkit/html" instead. Foldcase names the cause and the fix in the failed-file line, so you need not recognise the error yourself.

foldcase-bun erases the import itself and has no such rule.

The four commands

foldcase test — Showcases as CI

Finds every *.showcase.ts under a path, runs each play, and prints a pass/fail line per Showcase plus a rolled-up count. The exit code is 1 if anything failed, and also 1 if nothing was found — an empty run must never read as "everything passed".

foldcase test              # the current directory
foldcase test src/ui       # a directory
foldcase test button.showcase.ts   # a single file

A *.showcase.ts that will not import — a bad path, a missing dependency, a type imported as a value — is reported as a failed entry for that file, and the rest of the run continues:

  ✗ src/ui/picker.showcase.ts — Error [ERR_MODULE_NOT_FOUND]: Cannot find module './picker'
  ✓ counter/click-twice

2 total · 1 passed · 1 failed

Extra positionals and unknown flags are refused, not ignored: foldcase test a.ts b.ts and foldcase test src --covrage both name what was not understood and exit 1, because running half of what you asked for behind a green exit would be a lie — and for an agent, a typo that silently no-ops is the worst failure mode.

--coverage

Adds a V8 line and function coverage summary of the code each play actually executed — per Showcase and in aggregate.

foldcase test src/ui --coverage
coverage:
  src/ui/Button.showcase.ts  lines 42/48 (88%)  fns 6/7 (86%)

not measured:
  src/ui/Picker.showcase.ts — Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import …

1 file(s) · lines 42/48 (88%) · fns 6/7 (86%) · 1 not measured

by showcase:
  ✓ button/default   lines 30/48 (63%)
  ✓ button/disabled  lines 24/48 (50%)
  ? picker/open      no coverage collected

Read the limitations before you rely on it:

  • It needs Node on PATH and runs the measurement in a spawned Node subprocess, because Bun exposes no programmatic V8 precise coverage.
  • The collector runs every play twice more — an aggregate pass and a per-Showcase pass — on top of the suite run. Keep plays pure and fast.
  • The subprocess resolves modules the way Node does, which is stricter than Bun. A file Bun imports happily can fail there — a directory import is the common one. Such a file is listed under not measured: with the reason, and counted on the summary line, so a truncated measurement never reads as a whole one.
  • It is additive: it never changes the run's pass/fail exit code, and a collection failure is a warning rather than an error.

--json

Prints the run as one JSON document instead of the summary, for a script or an agent that has to act on it rather than read it.

foldcase test src/ui --json
foldcase test src/ui --json --coverage    # coverage rides in the same document
{
  "suite": {
    "total": 2, "passed": 1, "failed": 1,
    "reports": [
      { "id": "counter/click-twice", "status": "passed",
        "file": "/abs/src/ui/counter.showcase.ts" },
      { "id": "button/disabled", "status": "failed",
        "file": "/abs/src/ui/button.showcase.ts",
        "error": { "name": "AssertionError", "message": "expected true, got false" } }
    ]
  }
}
  • Every report names its file. An id alone does not say what to open, and the loader — not the author — holds that fact, so it is carried in the report rather than declared on the Showcase.
  • A failure is a full SerializedErrorname, message, the stack, and code when the runtime set one — not the two-field sketch above.
  • stdout is the document and nothing else. Logs and warnings go to stderr.
  • The exit codes do not move: 1 on any failure, 1 when nothing was discovered — and a run that discovered nothing prints no document at all, because "failed": 0 would read as a clean run.

foldcase docs — Model and Message tables

Introspects each component's message and model Schemas and writes one Markdown file per component.

foldcase docs src/ui docs/schemas
foldcase docs                 # output goes to FOLDCASE_DOCS_DIR, default ./foldcase-docs

A component is the set of Showcases sharing an id namespace — everything before the last /. Five Showcases under ui/picker/* produce one ui-picker.md, button/starts-unclicked and button/counts-one-click are the button component, and an id with no / is its own. This is the same notion of a component foldcase_run_catalog filters on with an id_prefix.

Each table is read from the first Showcase, in id order, that declares that Schema, so a namespace holding both plain logic Showcases and schema-carrying ones still documents. A component whose Showcases declare neither Schema has nothing to table and gets no file.

The Message table is Message | Field | Type | Optional, one row per tag and payload field; the Model table is Field | Type | Optional. Types describe the value your Model holds, not the JSON it serializes to — from examples/counter:

Field Type Optional
autosaveAfter Duration no
filter "all" | "open" | "done" no
selected Option<string> yes
tasks Task[] no

A Schema.DurationFromMillis field reads Duration, not number; Schema.Option(T) reads Option<T> with Optional yes; a named class resolves to its definition name; a | inside a type is escaped so the table survives it. Output is sorted, so regenerating gives a clean diff.

A Showcase may declare dispatches — the Message tags its play sends. Once any Showcase of a component declares them, the page adds a Not showcased: line naming the union tags no play dispatches: the next Showcase to write. A declared tag the union does not carry is a lie in the catalog — it is named on stderr and the command exits 1. A component where no Showcase declares stays silent, because unknown must never read as covered.

A file that will not load is named on stderr and the command exits non-zero; the documents it could write are still written. With every file loaded, the exit is 0 — including when nothing was written because no Showcase declares a Schema.

--json works here too, and says what was written and what would not load:

{
  "docs": [{ "component": "counter", "path": "/abs/docs/schemas/counter.md" }],
  "failures": [{ "_tag": "foldcase/ShowcaseModuleError",
                 "path": "/abs/src/ui/picker.showcase.ts",
                 "reason": "Cannot find module './picker'" }],
  "gaps": [{ "component": "counter",
             "undispatched": ["ClickedReset"], "unknown": [] }]
}

--check

Compares instead of writing — nothing is created, nothing is touched. A document that would change or is missing is listed with its reason, and the exit is 1 on any drift, any load failure, or any unknown dispatch; 0 when everything is current. In --json the same appears as stale: [{ component, path, reason: "missing" | "changed" }]. Put it in CI beside foldcase test, so the tables cannot drift from the catalog.

foldcase mcp — the catalog server

The wiring and the six tools are above. Server semantics worth knowing:

  • The whole toolkit is registered before the server reads a byte of stdin, so a host that discovers its tools once at startup gets all six from its first tools/list.
  • The server serves one catalog at a time, and foldcase_load_catalog is the only thing that moves it. A directory parameter on every verb would make an id mean nothing on its own, so the directory is state, and every listing and load report says which one is being served. A load that fails leaves the last good catalog in place.
  • The run tools spawn a fresh subprocess of the server's own runtime and load from disk, so they always run current code. The child reuses the same single loader, and a spawn failure is folded into the report as a failed entry, never a silent pass.
  • A showcase file that will not load is logged to stderr and the rest of the catalog is served anyway. Only a FOLDCASE_SHOWCASE_DIR that cannot be read at launch fails the server itself.
foldcase mcp                                    # point your MCP host's stdio command here
FOLDCASE_SHOWCASE_DIR=src/ui foldcase mcp       # serve a specific directory

foldcase init — wire a consumer repo

Writes the .mcp.json server entry and the AGENTS.md section shown in Wire it into your agent, idempotently, and reports one line per artifact — foldcase init: .mcp.json created|updated|kept. --json prints the same outcome as one InitDocument. An .mcp.json that will not parse is refused and left untouched.

Reference

Exit codes:

Command 0 1
test every Showcase passed a failed Showcase, an unloadable file, or no *.showcase.ts found
docs every file loaded — even if nothing was written a load failure, a Schema that will not introspect, a dispatched tag the union does not carry, --check drift, or no *.showcase.ts found
mcp — (serves until the host closes stdio) the server would not launch
init wired — created, updated, or already there a target that does not exist, or an .mcp.json that will not parse
any unknown or missing verb, an unknown flag, or extra positionals — refused with the usage banner

--coverage never changes an exit code.

Environment:

Variable Read by Default Meaning
FOLDCASE_SHOWCASE_DIR mcp only . the served catalog root
FOLDCASE_DOCS_DIR docs only foldcase-docs the out-dir when no second positional names one

Discovery is a recursive walk for files ending in .showcase.ts, sorted so the report is deterministic run to run. node_modules is skipped wherever it appears, so a dependency cannot join your catalog.

CI is the exit code:

- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bunx foldcase-bun test src
- run: bunx foldcase-bun docs src docs/schemas --check

Add --json and redirect stdout when a later step consumes the report.

Install, runtimes, peers

npm  install -D foldcase@alpha    # or pnpm add -D / yarn add -D
bun  add     -d foldcase@alpha

Releases go out under the alpha dist-tag while the API can still break, so ask for it by name. Foldcase ships as a compiled dist/ — one .js and one .d.ts per source file, built by plain tsc, no bundler — so npm, pnpm, Vite and Bun all consume it as ordinary ESM. There is no single-file binary, and deliberately so: a compiled binary could never load a *.showcase.ts from your project, which is the tool's whole job (ADR-0002 › Amendment 1).

Two bins, one per runtime:

Bin Runtime Needs
foldcase Node Node 22.18 or newer, because it loads your *.showcase.ts through Node's own type stripping
foldcase-bun Bun Bun 1.3.14 or newer

Both run the same program; only the shell around it differs.

Peer dependencies

  • effect v4 — a peer dependency, so your app and Foldcase share one Effect instance. The range is >=4.0.0-beta.90, and both ends of it are run: the suite passes on the floor and on 4.0.0-beta.102, the version Foldkit pins today.

  • One @effect/platform-*, matched to the Effect beta your app pins. Ask for the version by name:

    bun add -d @effect/platform-node@4.0.0-beta.102   # or @effect/platform-bun@…
    

    latest on both packages is still the v3 major, and installing it pulls in a @effect/cluster built against Effect v3. The bin then dies inside a dependency you never asked for — Cannot find module 'effect/dist/FiberRef.js' imported from @effect/cluster — long before it reads a showcase. Both platform packages are optional peers: install only the one your bin runs. Without it, the bin names the package and the install command instead of printing a resolver stack trace.

  • Node on PATH — only for foldcase test --coverage. Under the Node bin you already have it.

Foldcase does not depend on Foldkit. The play thunk is opaque to the runner, so Foldkit Showcases run in your app's own closure while Foldcase stays framework-blind.

Foldcase is alpha. Any patch may break the public API.

Programmatic use

Import What you get
foldcase the Showcase type; runShowcase, runShowcases, suiteOf, suiteExitCode, formatSuite; the ShowcaseReport / SuiteReport Schemas; SerializedError and serializeError
foldcase/cli discoverShowcaseFiles, loadShowcasesFromFiles, runCatalog, runSuiteFromFiles, docsFromFiles; ShowcaseModuleError and loadFailureReason; the LoadedShowcase / CatalogLoad types
foldcase/mcp FoldcaseMcpServer, the launchable stdio server Layer, and makeFoldcaseMcpServer over your own catalog Layer
foldcase/mcp/catalog the FoldcaseCatalog service (.layer, .layerFromShowcases), makeCatalog, loadCatalogFromDir, and the listing / load-report / error Schemas
foldcase/mcp/tools FoldcaseToolkit, FoldcaseHandlers, makeHandlers
foldcase/reports the --json documents as Schemas — TestDocument, DocsDocument, InitDocument — plus CoverageReport, StaleDoc, ComponentGap and the report Schemas they carry, so a consumer decodes a document with the Schema that produced it

Every report is a decoded Schema value, not a loose object, so a failure is structured data you can act on rather than a string you have to parse. loadShowcasesFromFiles returns { loaded, showcases, failures } and never fails — loaded pairs each Showcase with its file, showcases drops the file for surfaces that only run records — so you decide what an unloadable file means for your surface.

Provenance

Foldcase grew out of a fork of Openstory — CSF-3 story files, a React shell, a Vite dev server. The headless runner carried the value, so it became the whole tool: one Showcase record instead of Meta plus StoryObj, Effect Schemas end to end, an MCP catalog, coverage per component state, no browser and no bundler. The play contract and the SerializedError shape are kept and credited — NOTICE records exactly what — and the old line lives on the foldkit branch as history, not as a dependency. The decisions are ADR-0001 (one definition, many surfaces) and ADR-0002 (the Bun + Effect + Foldkit stack).

The name: fold(kit) + (show)case — the component performing outside the app.

Contributing

Read AGENTS.md first; it is short and it is binding for humans and agents alike. Then:

mise run setup      # once, after installing
mise run lint && mise run typecheck && mise run test
mise run build && mise run smoke

CI runs exactly these on every push, against the same pinned Bun and Node.

License

MIT. NOTICE records what is still derived from Openstory.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选