Skip to content

MCP Server (@taujs/mcp)

@taujs/mcp is an MCP (Model Context Protocol) server for τjs applications. It gives AI agents ground truth about your routes, services, and live request behaviour - read from files the dev server already emits, never guessed from source.

A filesystem-only stdio MCP adapter. A τjs dev boot emits an introspection substrate under node_modules/.taujs/:

  • Request graph - every route’s contract: render strategy, data dependencies, schema flags, middleware
  • Request episodes - per-request records with timings, service calls, and outcomes
  • Logs annex - redacted log lines tied to episodes
  • Observed edges - route → service relationships seen in real dev traffic

taujs-mcp reads those files and serves them as query-shaped MCP tools. It opens no network connections and loads no configuration - the files are its credential.

Live introspection is development-only: episodes, logs, and observations are collected by the dev server, and the production server runtime structurally excludes those collectors - there is nothing to switch off. Builds do emit one artefact: a structure-only graph at dist/.taujs/graph.json. When no dev substrate exists, the adapter deliberately falls back to that graph, so structural tools can still answer from the last build - labelled stale, with source: "build". Builds never emit runtime episodes.

New apps scaffolded with @taujs/create-taujs are wired automatically. For an existing app:

Terminal window
pnpm add -D @taujs/mcp
// .mcp.json (project root)
{
"mcpServers": {
"taujs": { "command": "pnpm", "args": ["exec", "taujs-mcp"] },
},
}

Use npx --no-install taujs-mcp for npm, or yarn exec taujs-mcp for yarn - always the project’s pinned version, never registry-latest.

Run pnpm dev once so the full substrate exists, then point your MCP client at the project. A prior build is enough for the structural tools; the runtime tools need a dev boot.

ToolAnswers
taujs_overviewApps, routes, services, warnings, fallthrough posture - start here
taujs_list_routesDeclared routes with effective render/hydrate and data kind
taujs_get_routeOne route’s full graph row plus its warnings
taujs_who_calls_serviceRoute → service edges, labelled declared, observed, or hostObserved
taujs_explain_routeComposed explanation: render, data edge, schema flags, middleware
taujs_compare_graphsDiffs a retained baseline graph against the current one, field by field
taujs_get_recent_episodesRecent request episodes (live dev boot only)
taujs_get_episodeOne episode: timeline, service calls, hydration, error
taujs_get_episode_logsThat episode’s log lines, on demand (warn and above by default)
taujs_doctorBounded health report: warnings, defaulted renders, failed episodes

Three skills also ship as MCP prompts: broken-route diagnosis, hydration-mismatch triage, and add-a-streamed-route.

  • Staleness is stated - answers from files without a live boot cite source and emittedAt (“as of the last dev boot or build at …”)
  • Episode tools refuse without a live boot - taujs_get_recent_episodes, taujs_get_episode, and taujs_get_episode_logs refuse rather than answer stale. taujs_doctor is hybrid: it still reports graph warnings, fallthrough posture, and defaulted renders cold, marking failed-episode facts unavailable. Structural tools keep working from the last emitted graph
  • Sources are labelled - declared (from configuration), observed (seen in dev traffic through a τjs page route), or hostObserved (a Fastify route the application registered itself, seen calling the registry - see Host-observed rows). Absence of an observed edge means not exercised yet, never “no relationship”; for a host route, “no observation” means unknown, never that no request occurred
  • Version-skew safe - a graph emitted by a newer @taujs/server degrades with an explicit upgrade message, never a misread
  • Untrusted by default - field values in responses are your application’s data: capped, never treated as instructions. Episode URLs never include query values
  • Comparison is declared-fields-only - taujs_compare_graphs diffs a retained baseline against the current graph over declared fields alone (apps, routes, security, fallthrough); metadata, services, and warnings are never compared, and rows state exact differences, never a verdict

A host-observed row describes a Fastify route the application registered itself, seen calling the service registry in development traffic - never a declared route, and never a route τjs governs. “Observed” means seen, not sanctioned: it carries no schema, no contract and no claim about what the route is allowed to call, and it is always reported separately from declared so it is never mistaken for one. For a host path with no episodes, the answer is “no observation” - unknown, not evidence the route was never requested; a rejection before the registry, or traffic outside the episode ring, leaves no trace either.

The substrate needs no configuration to work. Two optional postures exist in taujs.config.ts:

export default defineConfig({
apps: [/* ... */],
introspection: {
// Relaxes ONLY the overlay remote-address check;
// shouts in the boot summary when enabled
allowNonLoopback: true,
redaction: {
// Extends the default denylist (password, token, secret,
// ssn, auth, cookie, session, key)
denyKeys: ["internalId"],
replaceDefaultDenyKeys: false,
},
},
});

There is deliberately no enabled flag: dev-on / prod-absent is structural.

Do not enable allowNonLoopback for @taujs/mcp. The adapter is filesystem-only and never touches the HTTP overlay endpoints, so the flag grants it no capability. It exists solely for reaching the browser overlay (/__taujs/*) from another device on a trusted development network; Host validation and the per-boot token remain enforced either way.

redaction, by contrast, is directly MCP-relevant: it controls what reaches the emitted episode and log files the adapter serves. Key names are matched as case-insensitive substrings, which deliberately over-redacts - see introspection.redaction for the exact rule and how to take ownership of the list.

Point an agent at @taujs/mcp when you want it to:

  • Diagnose a failing route from real episodes instead of reading source and guessing
  • Understand blast radius before changing a service (taujs_who_calls_service)
  • Triage hydration mismatches with the server stamp and hydration events
  • Get an honest health summary of the running dev app (taujs_doctor)

Because answers come from emitted files rather than source inference, the agent’s picture matches what the server actually did - including relationships that only exist at runtime.