Skip to content

feat(sdk,testing,cli): record client calls and replay them against a new version - #1006

Merged
alexmercerpo merged 7 commits into
feat/m07-hook-orderingfrom
feat/m09-call-replay
Nov 2, 2026
Merged

alexmercerpo merged 7 commits into
feat/m07-hook-orderingfrom
feat/m09-call-replay

Conversation

@frontegg-david

@frontegg-david frontegg-david commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1005

What and why

Proposal item 5.3: record real calls against one version of a server and replay them against the next, so a
change to a tool, resource or prompt shows up as a diff of the answers clients would get, not as a surprise in
production.

The reported problem: a server's tests cover the cases its authors thought of, while clients send the calls they
actually send. There was no way to capture those calls and check a new build against them.

What this PR adds:

  • CallRecorderPlugin, built into @frontmcp/sdk (like ConfigPlugin). A plugin rather than a server option:
    recording is a set of hooks, it is opt-in with options of its own, it works on the server (every app) or on one
    @App, and it needs no new @FrontMcp field. Being in the SDK, it needs no extra install, so a production-like
    build can record without depending on @frontmcp/testing.
    • Will('parseInput') hooks start the clock, and Finally hooks write the line, on tools:call-tool,
      resources:read-resource and prompts:get-prompt. A call is written at the Finally of the first answer stage
      that sees a failure (an earlier stage's failure, or its own), else at the Finally of the last answer stage
      (meterAnswer, fitAnswer, finalize). So the recorded answer is the one the client received: after every hook,
      the answer budget and shaping, and folded by the result-handles plugin when it folds.
    • One JSONL line per call: v, at, method, name (the tool or prompt name the client asked for, or the URI),
      input, answer or error, durationMs, session, client. error is { code, message }: the code the
      client saw (a tool error answer's _meta.code, or the JSON-RPC code, computed with the same functions the MCP
      handlers use) and, for a public error, the text the client reads as a production server words it (a tool
      error's text with its To fix: remedy line; a JSON-RPC message without the MCP error <code>: prefix). An
      internal error is { code } only: its text is generic and carries a new error id each time.
    • Only calls a client makes are recorded; a tool's this.callTool() is not (ctx.isClientCall()). A run that ends
      with neither an answer nor an error (a hook's handled()) writes no line. A task-augmented call is recorded
      once, by the task's own run (the run that creates the task answers only a handle), so it replays as a plain call.
    • Writing never blocks or fails the call: lines are queued and appended in order through @frontmcp/utils
      (a new appendFile, which takes a mode), to a file created owner-only (0600). The writer flushes on
      shutdown (onShutdown), a write failure is logged once, and maxBytes (10 MiB) stops the file growing, logged
      once; it counts the file's size on disk, so lines another process appends to the same path count too. While
      maxBytes of lines wait for a slow disk, later calls are dropped (logged once), so the queue never holds more.
    • Options are validated with Zod in CallRecorderPlugin.init(): path (.frontmcp/calls.jsonl), methods,
      redactFields, keepFields, maxBytes. Invalid options throw CallRecorderConfigurationError naming each
      problem.
  • Redaction (no third redactor). The request log's credential rules become isCredentialName(): a name
    matches as written (lowercased) or split into words (camelCase and snake_case), so apiKey, access_token,
    clientSecret and passWord match as x-api-key does; a word ending in token, secret, password,
    passwd or apikey (accesstoken, x-amzn-oidc-accesstoken) and the words jwt and passphrase
    (x-goog-iap-jwt-assertion) now match too. Observability's redactFields() moves to @frontmcp/utils, where
    it takes a key test as well as names and walks nested lists; redactJson() uses the same walker at any depth and
    inside text that holds JSON (a tool's text block repeats its structuredContent), and in any other text redacts
    a Bearer/Basic credential, a JWT, the user:password@ of a URL, and the value given to a redacted name
    (?access_token=…, #id_token=…, apiKey: "…", TOKEN=…). The recorder redacts the input, the answer, the
    error message and the resource URI with isCredentialName, session id names (sessionId), plus
    redactFields, minus keepFields; it redacts the handle of a folded result-handles answer (its encryption keys
    derive from it) wherever it appears, also in a later call that passes it back; it records no headers and no
    request _meta, and cuts the session id to 8 characters with headerValueForLog, as the request log does.
    @frontmcp/observability still exports redactFields: the same arguments work (it also takes a key test now),
    with the same depth limit (5) and return shape (the same object when no field is named).
  • replaySession(file, server, options) in @frontmcp/testing. Replays the calls in order, one client per
    recorded session with its recorded client info, and returns { file, calls, differences }. Each call is same,
    changed (with each differing path, through JSON text too), new-error or gone-error; two errors compare by
    code, then by message where the recording kept one (a public error's), so a reworded public error is
    changed at error.message. ignore takes dot-path globs (* one key, ** any number), and error.message.
    A redacted value is not compared, and a [REDACTED] inside a text matches any text there (so a folded answer's
    new handle is no change). formatReplayReport() prints the differences, and the toMatchRecording() matcher
    fails with that report. A spec in @frontmcp/testing records real calls with CallRecorderPlugin and replays
    them, so the line format the replay reads stays pinned to the one the SDK writes.
  • frontmcp test --replay <file> [--replay-ignore <paths>]. Starts the project server through TestServer
    (npx tsx <entry> on a free port, the entry resolved as frontmcp dev does, with the .env and config overlays
    of frontmcp test), replays the file with the project's own @frontmcp/testing, prints the report and exits 1
    on a difference.
  • prompts:get-prompt now keeps its answer in state.output, as the tool and resource flows do, so a Finally
    hook can read it.

User-facing change

  • What changed: new CallRecorderPlugin and CallRecorderConfigurationError in @frontmcp/sdk; new
    replaySession(), formatReplayReport(), toMatchRecording() and ReplayRecordingError in @frontmcp/testing;
    new frontmcp test --replay and --replay-ignore; redactFields(), redactJson() and appendFile() in
    @frontmcp/utils; redactFields() also redacts objects inside nested lists; the debug request log hides more
    credential headers.
  • Before → after: no way to record and replay calls → record with CallRecorderPlugin.init({ path }), replay
    with replaySession() in a spec or frontmcp test --replay in CI. The recorder matches field names by words, so
    besides the request log's credential names it redacts key, cacheKey, pageToken, nextPageToken,
    sessionId and the like (list a safe one in keepFields). Observability log redaction (redactFields): an
    object in a list of lists was logged unredacted → it is redacted; nothing it redacted before is logged now.
    Debug request log headers: x_api_key-style names (underscores), camelCase names (X-AuthToken), words ending
    in token/secret/password/passwd/apikey (x-amzn-oidc-accesstoken, x-csrftoken) and the words jwt
    and passphrase (x-goog-iap-jwt-assertion) were logged → they are [REDACTED]; every header hidden before
    stays hidden. prompts:get-prompt hooks: state.output was empty → it holds the answer.
  • frontmcp.dev pages affected (if known): https://frontmcp.dev/reference/testing (replay),
    https://frontmcp.dev/reference/cli (frontmcp test), https://frontmcp.dev/reference/server/observability (field
    redaction), and the plugins reference for the new built-in plugin.
  • Skills catalog entries updated: frontmcp-testing (new references/replay-calls.md and
    examples/replay-calls/record-and-replay.md, a routing row and a frontmcp test --replay row in SKILL.md),
    frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md (frontmcp test --replay),
    frontmcp-observability/references/structured-logging.md (where redactFields lives, nested lists), and
    skills-manifest.json.

Tests

  • Unit, @frontmcp/sdk: call-recorder.plugin.spec.ts (21): a tool call with input, answer, duration, session and
    client; tool errors (a thrown public error, an output-schema refusal, an unknown tool) with their client codes; a
    public error's text with its remedy line, no message for an internal error, and a JWT in a public message
    redacted; a tool error the client gets as JSON-RPC (-32601); resource reads and prompts, and a resource's
    JSON-RPC code and message; a resource URI's access_token redacted; the answer after a hook replaced it; no
    configured field or credential in the input, the answer or its JSON text; no credential under an innocuous key,
    in a URL (userinfo, query), a JWT, a nested headers.Authorization or a sessionId; no result handle in a folded
    answer or the later call reading it; the file created 0600; only the calls to the app it is installed on;
    keepFields; methods; in-process calls not recorded; a task-augmented call recorded once with the task's
    answer; no line for an unanswered run; a write failure logged once with the calls still answered; maxBytes
    logged once, and counted on the file's size after another writer appended; invalid options.
    call-record.writer.spec.ts (1): lines dropped while maxBytes wait for a slow disk, logged once.
    request-log.redaction.spec.ts (36), including passWord, x_api_key, accesstoken and session id names.
  • Unit, @frontmcp/testing: replay-diff.spec.ts (12), replay-session.spec.ts (8), mcp-matchers.replay.spec.ts
    (3), replay-recording.spec.ts (1: real calls recorded by CallRecorderPlugin hold only fields the replay knows
    and replay unchanged). @frontmcp/utils: the moved redaction.spec.ts plus 12 new cases (credentials in text,
    URLs and assignments), and appendFile with its mode. CLI: replay.spec.ts (6),
    test-replay.spec.ts, the --replay / --replay-ignore parsing in program.spec.ts and bridge.spec.ts.
  • E2E, apps/e2e/demo-e2e-testing/e2e/call-replay.e2e.spec.ts: records seven calls against fixture v1 (no password or
    token in the file); replayed against v2 the report names the changed quote (structuredContent.price 12 → 15),
    the new pay error and the gone refund error; replayed against v1 it differs only on the volatile fields, and is
    clean once they are ignored. demo-e2e-dts-consumer: call-recorder-must-compile.ts.
  • Ran: nx run-many -t lint test build -p utils observability sdk testing cli skills (all green),
    nx test demo-e2e-testing (7 suites, 26 tests), demo-e2e-dts-consumer (17), demo-e2e-hooks (23),
    demo-e2e-standalone (53, for the request-log header rules), prettier on the changed files,
    scripts/fix-unused-imports.mjs. The redaction of text was run against 400 KB adversarial strings (long dotted,
    hyphenated, {a: and quote runs) to check it stays linear.
  • skills-manifest.json is the output of libs/skills/scripts/generate-manifest.mjs (the build target runs it
    first and formats it with prettier), so the reflowed entries outside the testing skill are what the generator
    writes; rerunning it leaves the file unchanged.
  • By hand: recorded three calls against the fixture, then ran the built CLI's frontmcp test --replay on them: clean
    and exit 0 with --replay-ignore '**.quotedAt,**.quoteId', and exit 1 with the quote paths listed without it.

Summary by CodeRabbit

  • New Features
    • Added call recording for tool, resource, and prompt interactions, with configurable output, sensitive-data redaction, and recording-size limits.
    • Added replay support through the testing tools and CLI, with reports highlighting changed or failed calls and options to ignore selected fields.
    • Added a matcher for asserting that replayed calls match their recordings.
  • Documentation
    • Added guides and examples for recording and replaying calls, including CLI usage and comparison options.

…d replay them with replaySession() and frontmcp test --replay

- CallRecorderPlugin (built into @frontmcp/sdk) appends each tools/call, resources/read and prompts/get a
  client makes, with the answer or error it received, to a redacted JSONL file from Finally hooks; writing
  never blocks or fails a call, and a write failure or a full file is logged once.
- Redaction reuses the request log's credential names (now isCredentialName) and redactFields, moved to
  @frontmcp/utils with a key test, nested lists and a redactJson() that reaches into JSON text.
- replaySession(file, server) in @frontmcp/testing replays a recording, one client per recorded session,
  and reports each call as same, changed, new-error or gone-error, ignoring volatile paths and folded
  result-handle ids; toMatchRecording() fails with a readable report.
- frontmcp test --replay <file> [--replay-ignore <paths>] replays against the project server started
  through TestServer and exits 1 on a difference.
- prompts:get-prompt keeps its answer in state.output, as the tool and resource flows do.
…report when a client fails to disconnect

- The run that creates a task answers only its handle, so the recorder now records the task's own run,
  which answers; the call replays as a plain call instead of twice.
- replaySession() disconnects its clients with allSettled, so a failed disconnect no longer hides the report.
- The replay-calls reference says redaction goes by field name, never by value, and to review a recording
  before committing it.
… of call recordings, and create them owner-only

- redactJson() also redacts, in text that is not JSON, a Bearer or Basic credential, a JWT, the user:password@ of a
  URL and the value given to a redacted name (?access_token=, apiKey: "...", TOKEN=...), in linear time
- the recorder applies it to the input, the answer, a public error's message and a resource URI, redacts session id
  fields, and redacts a folded result-handles answer's handle wherever it appears, also in a later call reading it
- a public error is recorded with the text its client reads (a tool error's remedy line included); an internal error
  only with its code
- isCredentialName() matches a name as written as well as split into words, so passWord and X-PassWord are hidden
  again, and words ending in token, secret, password, passwd or apikey and the words jwt and passphrase match
- the recording is created 0600, maxBytes counts the file's size on disk, and lines waiting for a slow disk are
  capped at maxBytes, dropping later calls with one warning
- appendFile() takes the mode a new file gets
…a redacted text as a pattern

- two errors with the same code differ at error.message when the recording kept a public error's message;
  error.message can be ignored, and an internal error is compared by code
- a JSON-RPC error's message is read without its "MCP error <code>: " prefix
- a [REDACTED] inside a recorded text matches any text there, so a value redacted inside a text, such as a folded
  answer's handle, is not a change; compareResultHandles is gone, as recordings no longer hold handles
- a spec records real calls with CallRecorderPlugin and replays them, pinning the line format the replay reads
- the replay-calls catalog entry documents the recording's redaction, error messages and file mode
…t/m09-call-replay

# Conflicts:
#	apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts
#	apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 16:10
@frontegg-david
frontegg-david added this pull request to stack #997 November 2, 2026 16:10
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

This review is too large to run within your organization's remaining usage spending cap. Raise or remove your spending cap in the billing tab, then retry.

Next included review available in 40 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available. Your 51 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between d482cbd and f0f46db.

Files selected for processing: 58
  • apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/call-recorder-must-compile.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json
  • apps/e2e/demo-e2e-testing/e2e/call-replay.e2e.spec.ts
  • apps/e2e/demo-e2e-testing/src/apps/replay/index.ts
  • apps/e2e/demo-e2e-testing/src/main.replay.ts
  • libs/cli/src/commands/dev/__tests__/replay.spec.ts
  • libs/cli/src/commands/dev/__tests__/test-replay.spec.ts
  • libs/cli/src/commands/dev/register.ts
  • libs/cli/src/commands/dev/replay.ts
  • libs/cli/src/commands/dev/test.ts
  • libs/cli/src/core/__tests__/bridge.spec.ts
  • libs/cli/src/core/__tests__/program.spec.ts
  • libs/cli/src/core/args.ts
  • libs/cli/src/core/bridge.ts
  • libs/observability/src/__tests__/redaction.spec.ts
  • libs/observability/src/logging/index.ts
  • libs/observability/src/logging/redaction.ts
  • libs/observability/src/logging/structured-log-transport.ts
  • libs/sdk/src/builtin/call-recorder/__tests__/call-record.writer.spec.ts
  • libs/sdk/src/builtin/call-recorder/__tests__/call-recorder.plugin.spec.ts
  • libs/sdk/src/builtin/call-recorder/call-record.ts
  • libs/sdk/src/builtin/call-recorder/call-record.writer.ts
  • libs/sdk/src/builtin/call-recorder/call-recorder.options.ts
  • libs/sdk/src/builtin/call-recorder/call-recorder.plugin.ts
  • libs/sdk/src/builtin/call-recorder/index.ts
  • libs/sdk/src/errors/index.ts
  • libs/sdk/src/errors/sdk.errors.ts
  • libs/sdk/src/index.ts
  • libs/sdk/src/prompt/flows/get-prompt.flow.ts
  • libs/sdk/src/scope/flows/__tests__/request-log.redaction.spec.ts
  • libs/sdk/src/scope/flows/request-log.redaction.ts
  • libs/skills/catalog/frontmcp-observability/references/structured-logging.md
  • libs/skills/catalog/frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md
  • libs/skills/catalog/frontmcp-testing/SKILL.md
  • libs/skills/catalog/frontmcp-testing/examples/replay-calls/record-and-replay.md
  • libs/skills/catalog/frontmcp-testing/references/replay-calls.md
  • libs/skills/catalog/skills-manifest.json
  • libs/testing/src/client/mcp-test-client.ts
  • libs/testing/src/errors/index.ts
  • libs/testing/src/index.ts
  • libs/testing/src/matchers/__tests__/mcp-matchers.replay.spec.ts
  • libs/testing/src/matchers/matcher-types.ts
  • libs/testing/src/matchers/mcp-matchers.ts
  • libs/testing/src/replay/__tests__/replay-diff.spec.ts
  • libs/testing/src/replay/__tests__/replay-recording.spec.ts
  • libs/testing/src/replay/__tests__/replay-session.spec.ts
  • libs/testing/src/replay/replay-diff.ts
  • libs/testing/src/replay/replay-session.ts
  • libs/testing/src/transport/sse.transport.ts
  • libs/testing/src/transport/streamable-http.transport.ts
  • libs/utils/src/fs/fs.spec.ts
  • libs/utils/src/fs/fs.ts
  • libs/utils/src/fs/index.ts
  • libs/utils/src/index.ts
  • libs/utils/src/redaction/index.ts
  • libs/utils/src/redaction/redaction.spec.ts
  • libs/utils/src/redaction/redaction.ts
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between f5ff8e7 and d482cbd.

Files selected for processing: 55
  • apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/call-recorder-must-compile.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json
  • apps/e2e/demo-e2e-testing/e2e/call-replay.e2e.spec.ts
  • apps/e2e/demo-e2e-testing/src/apps/replay/index.ts
  • apps/e2e/demo-e2e-testing/src/main.replay.ts
  • libs/cli/src/commands/dev/__tests__/replay.spec.ts
  • libs/cli/src/commands/dev/__tests__/test-replay.spec.ts
  • libs/cli/src/commands/dev/register.ts
  • libs/cli/src/commands/dev/replay.ts
  • libs/cli/src/commands/dev/test.ts
  • libs/cli/src/core/__tests__/bridge.spec.ts
  • libs/cli/src/core/__tests__/program.spec.ts
  • libs/cli/src/core/args.ts
  • libs/cli/src/core/bridge.ts
  • libs/observability/src/__tests__/redaction.spec.ts
  • libs/observability/src/logging/index.ts
  • libs/observability/src/logging/redaction.ts
  • libs/observability/src/logging/structured-log-transport.ts
  • libs/sdk/src/builtin/call-recorder/__tests__/call-record.writer.spec.ts
  • libs/sdk/src/builtin/call-recorder/__tests__/call-recorder.plugin.spec.ts
  • libs/sdk/src/builtin/call-recorder/call-record.ts
  • libs/sdk/src/builtin/call-recorder/call-record.writer.ts
  • libs/sdk/src/builtin/call-recorder/call-recorder.options.ts
  • libs/sdk/src/builtin/call-recorder/call-recorder.plugin.ts
  • libs/sdk/src/builtin/call-recorder/index.ts
  • libs/sdk/src/errors/index.ts
  • libs/sdk/src/errors/sdk.errors.ts
  • libs/sdk/src/index.ts
  • libs/sdk/src/prompt/flows/get-prompt.flow.ts
  • libs/sdk/src/scope/flows/__tests__/request-log.redaction.spec.ts
  • libs/sdk/src/scope/flows/request-log.redaction.ts
  • libs/skills/catalog/frontmcp-observability/references/structured-logging.md
  • libs/skills/catalog/frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md
  • libs/skills/catalog/frontmcp-testing/SKILL.md
  • libs/skills/catalog/frontmcp-testing/examples/replay-calls/record-and-replay.md
  • libs/skills/catalog/frontmcp-testing/references/replay-calls.md
  • libs/skills/catalog/skills-manifest.json
  • libs/testing/src/errors/index.ts
  • libs/testing/src/index.ts
  • libs/testing/src/matchers/__tests__/mcp-matchers.replay.spec.ts
  • libs/testing/src/matchers/matcher-types.ts
  • libs/testing/src/matchers/mcp-matchers.ts
  • libs/testing/src/replay/__tests__/replay-diff.spec.ts
  • libs/testing/src/replay/__tests__/replay-recording.spec.ts
  • libs/testing/src/replay/__tests__/replay-session.spec.ts
  • libs/testing/src/replay/replay-diff.ts
  • libs/testing/src/replay/replay-session.ts
  • libs/utils/src/fs/fs.spec.ts
  • libs/utils/src/fs/fs.ts
  • libs/utils/src/fs/index.ts
  • libs/utils/src/index.ts
  • libs/utils/src/redaction/index.ts
  • libs/utils/src/redaction/redaction.spec.ts
  • libs/utils/src/redaction/redaction.ts
Files with no reviewable changes: 2
  • libs/observability/src/tests/redaction.spec.ts
  • libs/observability/src/logging/redaction.ts

Included review availability: This review used your included allowance. 1 included review remains after this review. Your included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Walkthrough

Adds SDK call recording to JSONL with configurable filtering, redaction, and size limits. Adds replay APIs that execute recorded calls against a server and compare results, plus CLI replay support. It also relocates shared redaction utilities and adds recorder/replay tests, examples, and documentation.

Changes: Call Recording and Replay

Layer / File(s) Summary
Shared redaction and file utilities
libs/utils/src/fs/*, libs/utils/src/redaction/*, libs/observability/src/logging/*, libs/sdk/src/scope/flows/request-log.redaction.ts, libs/sdk/src/scope/flows/__tests__/request-log.redaction.spec.ts, libs/skills/catalog/frontmcp-observability/references/structured-logging.md
Adds shared field and text redaction utilities and appendFile. Observability now re-exports redaction from @frontmcp/utils. Request-log credential and session-ID name checks use normalized names.
Recorder contract and capture pipeline
libs/sdk/src/builtin/call-recorder/*, libs/sdk/src/errors/*, libs/sdk/src/index.ts, libs/sdk/src/prompt/flows/get-prompt.flow.ts, apps/e2e/demo-e2e-dts-consumer/*
Adds recorder options and record types, a queued JSONL writer, and a plugin that records tool, resource, and prompt calls. Tests cover redaction, filtering, errors, file limits, and write failures. The strict declaration suite checks recorder types and options.
Replay execution and comparison
libs/testing/src/replay/*, libs/testing/src/matchers/*, libs/testing/src/errors/index.ts, libs/testing/src/index.ts, apps/e2e/demo-e2e-testing/e2e/call-replay.e2e.spec.ts, apps/e2e/demo-e2e-testing/src/apps/replay/*, apps/e2e/demo-e2e-testing/src/main.replay.ts
Adds JSONL validation, ordered replay, response comparison, difference reporting, and the toMatchRecording matcher. End-to-end tests exercise changed answers, new and resolved errors, and ignored volatile fields.
CLI replay and usage examples
libs/cli/src/commands/dev/*, libs/cli/src/core/*, libs/skills/catalog/frontmcp-testing/*, libs/skills/catalog/frontmcp-setup/examples/project-structure-standalone/dev-workflow-commands.md, libs/skills/catalog/skills-manifest.json
Adds frontmcp test --replay and --replay-ignore parsing and execution. Tests cover project testing-package resolution, server lifecycle, report output, and exit status. Skill references and examples document recording and replay.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant McpTestClient
  participant ReplayServer
  participant CallRecorderPlugin
  participant CallRecordWriter
  participant JSONLFile
  participant replaySession
  participant compareCall
  McpTestClient->>ReplayServer: Send tool, resource, or prompt call
  ReplayServer->>CallRecorderPlugin: Run call flow hooks
  CallRecorderPlugin->>CallRecordWriter: Write serialized call record
  CallRecordWriter->>JSONLFile: Append JSONL line
  replaySession->>JSONLFile: Read recorded calls
  replaySession->>McpTestClient: Replay calls in recording order
  McpTestClient->>ReplayServer: Send replay request
  ReplayServer-->>McpTestClient: Return response
  replaySession->>compareCall: Compare recorded and replayed results
Loading

Suggested reviewers: alexmercerpo

Merge Risk: Moderate · previous review d482c

Restrict permissions on existing recording files before merging to avoid exposing call data. The Windows test assertions and misplaced real-server spec also need correction.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 46.34% which is insufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely summarizes the main change: recording client calls and replaying them against a newer server version.
Description check Passed The description includes the required What and why, User-facing change, and Tests sections. It explains the implementation, affected APIs and CLI options, documentation updates, test coverage, and val…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes the required What and why, User-facing change, and Tests sections. It explains the implementation, affected APIs and CLI options, documentation updates, test coverage, and validation results.

Full details: Docstring Coverage

Explanation

Docstring coverage is 46.34% which is insufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 41 functions across 46 files. (7 skipped: 7 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Fix comments: Fix CodeRabbit comments on this PR

Comment thread libs/utils/src/fs/fs.spec.ts Resolved
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Already reviewed the last commit. Use @coderabbitai full review to rerun a review of the entire changeset.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-02T16:44:50.607Z
Commit: 2fd3c86b

@frontegg-david
frontegg-david marked this pull request as draft November 2, 2026 16:30
…lls to it

appendFile's mode applies only when it creates the file, so a recording path
that already existed kept whatever permissions it had, and recorded calls went
into a file others could read. The writer now sets an existing file to 0600
before its first line; a file it cannot do that for is not written to.

Also trim slashes in the testing client and transports with trimSlashes and
trimTrailing from @frontmcp/utils instead of regexes that backtrack
polynomially on runs of '/' (CodeQL js/polynomial-redos).
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 16:30
@frontegg-david

Copy link
Copy Markdown
Contributor Author

Also in f0f46db: CodeQL on d482cbd reported two js/polynomial-redos alerts, in mcp-test-client.ts and sse.transport.ts (code from #549, reported here as this is the first PR of the stack CodeQL analysed). The /^\/+|\/+$/g and /\/+$/ trims in the testing client and both transports now use trimSlashes and trimTrailing from @frontmcp/utils, which exist for this pattern.

Merged feat/m07-hook-ordering (529cbb1) into this branch. With it, the browser bundle measures 1,317,587 (create() + one tool) and 1,324,776 (+ WebMcpPlugin), within the budgets; this PR adds 76 bytes and raises no budget.

Local runs on f0f46db: SDK call-recorder suites 23/23, nx test testing 427 passed, nx test skills 249/249, nx test demo-e2e-testing 26/26, nx test demo-e2e-bundle-size 10/10.

@alexmercerpo
alexmercerpo merged commit e7a6c8a into main Nov 2, 2026
46 checks passed
@alexmercerpo
alexmercerpo deleted the feat/m09-call-replay branch November 2, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants