Skip to content

feat(sdk): answer budget, client profiles, an answer cost meter and forwarded headers - #1001

Merged
alexmercerpo merged 7 commits into
feat/m04-aliases-remedyfrom
feat/m05-answer-shaping
Nov 2, 2026
Merged

alexmercerpo merged 7 commits into
feat/m04-aliases-remedyfrom
feat/m05-answer-shaping

Conversation

@frontegg-david

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

Copy link
Copy Markdown
Contributor

Stacked on #1000

What and why

Proposal items 2.4 (b), 5.2 and 1.10: answers sized for the model reading them, per-client answer shaping, a meter
for what answers cost, and custom request headers beyond x-frontmcp-*.

The reported problems:

  • An answer has no size limit. One tools/call that returns a 300 KB list fills the model's context. The only
    truncation lived in two plugins (CodeCall's output sanitizer, skilled-openapi's 256 KiB cap).
  • Every object answer is sent twice. The JSON goes in content[0].text and the same value in
    structuredContent, also for tools without an output schema. A client reads one of them; the other is wasted.
  • clientInfo was captured but never used to shape answers, and platform detection had no value for Codex
    (codex-mcp-client matched mcp and became generic-mcp) nor told a claude-code client from other claude clients.
  • Nothing measured it. /metrics had a frontmcp_tool_ category with no counters behind it.
  • Only x-frontmcp-* headers reached a tool. A server whose clients send x-acme-tenant had to rename the
    header or read the raw request in a hook.

What this PR changes:

  • output.budget: { maxBytes, lists?: 'keep-newest' | 'keep-oldest' }, on @FrontMcp, @App and @Tool
    (Tool > App > server, like undeclaredKeys; budget: false turns off a wider budget). Off unless set.
    • It fits tool answers and resource reads. maxBytes (at least 1024) is measured as UTF-8 JSON on each copy of
      the data, so an answer with both the JSON text and structuredContent sends each within the budget.
    • It never cuts inside JSON: it leaves out whole list items (the first ones with 'keep-newest', the default; the
      last with 'keep-oldest'), then the end of long strings, largest values first. Plain text is cut at its end.
      Images, audio, embedded resources, HTML (ui:// widgets) and blobs are left alone.
    • What it left out goes to _meta['frontmcp/budget'] ({ maxBytes, lists, omitted: [{ path: '$.rows', items: 880, of: 1000 }] }) and to a last text content (a last text/plain content for a resource read):
      This answer was shortened to fit its 65536-byte budget: left out the first 880 of 1000 items of $.rows.
    • A cut never breaks the outputSchema a client validates structuredContent against (the reference client
      rejects such an answer with -32602): when leaving items out would go below .min(n), shorten a tuple or break a
      pattern, the answer is sent whole and the server logs a warning once per tool.
    • Answers a tool, agent or job gets from this.callTool() are never cut.
  • clients profiles on @FrontMcp, keyed by a glob of clientInfo.name (first match wins, case-insensitive; a
    name over 256 characters matches none, so a long client-chosen name cannot stall a multi-* glob). With profiles
    set, every 2026-07-28 cacheable result is cacheScope: 'private', since tools/list, resources/list and
    server/discover now differ per client:
    • structured: 'both' | 'only' | 'text'. 'only' replaces the text that repeats structuredContent with a
      one-line summary (The answer is in structuredContent: rows (30 items), total: 1000.), only for tools whose
      outputSchema tools/list lists to that client, never for errors. 'text' drops structuredContent and leaves
      outputSchema out of that client's tools/list, so a client that validates structuredContent does not fail.
    • annotations: 'full' | 'no-numbers' overrides skillsConfig.resourceAnnotations (Cherry-pick: fix(sdk): send no numeric skill resource annotation and a compact skills line in the instructions by default #994) per client in
      resources/list: 'no-numbers' drops priority from every resource, 'full' gives each SKILL.md
      priority: 0.8.
    • instructions: 'full' | 'compact' overrides skillsConfig.injectInstructions per client, on initialize and on
      the 2026-07-28 server/discover.
  • Platform detection learns codex (any name containing codex; Codex sends codex-mcp-client, some versions
    Codex) and claude-code (a client named claude-code). Codex keeps structuredContent on the UI path, as
    it did when it was detected as generic-mcp.
  • Answer cost on /metrics: frontmcp_tool_answer_bytes_total, frontmcp_tool_answer_estimated_tokens_total
    (bytes / 4, rounded up per answer) and frontmcp_tool_answer_duplicated_bytes_total (text that is exactly the JSON
    of structuredContent), each labelled tool (full name) and client. client is the detected platform, never
    the raw client name, so a client cannot mint label values. Counted only with metrics.enabled and the tools
    category included.
  • http.forwardHeaders: string[] (names or globs): more request headers kept in
    this.context.metadata.customHeaders, over HTTP and for direct calls to a createDirect() server
    (DirectCallOptions.metadata.customHeaders).
    authorization, cookie, set-cookie, proxy-*, hop-by-hop headers and the client-address headers host,
    forwarded, x-forwarded-* and x-real-ip (a client could forge them for an upstream that trusts this server) are
    never kept, whatever header a glob matches at request time, and a name or glob that matches one fails startup
    (x-* does: it matches x-forwarded-for). A header name over 256 characters is never kept. The request-log redaction is unchanged, and this.fetch() still sends custom headers only to the
    fetch.forwardCustomHeadersTo origins.
  • Hookable stages. The budget, the profile and the meter are three new tools:call-tool stages, fitAnswer,
    shapeAnswer and meterAnswer; resources:read-resource gets fitAnswer. Each answer the flow sends, the
    tool's or a hook's ctx.respond() (an Around('finalize') hook that redacts it included), becomes state.output,
    which these stages work on. A plugin wraps or replaces them and reads state.output or responds; BUDGET_META_KEY, fitToolAnswer(), fitResourceAnswer() and ANSWER_COUNTERS are
    exported for that.
  • Validation. Zod schemas for the three options. An invalid http, output or clients option stops @FrontMcp
    with InvalidDecoratorMetadataError (@FrontMcp invalid metadata for "http": … FrontMCP never forwards authorization, cookie, proxy-*, hop-by-hop or client-address (host, forwarded, x-forwarded-*, x-real-ip) headers …); it was a bare ZodError before.

Different from the plan, and why:

  • The new stages run after finalize, not before it. finalize parses the answer and can throw
    InvalidOutputError; a finalize-phase stage placed before it that throws skips finalize and its Finally hooks
    (the observability span would never close). tools:call-tool and resources:read-resource record each answer they
    respond with in state.output, and each later stage that changes it responds again, so an answer a hook replaced
    around finalize is the one fitted and shaped. Existing Will/Did/Finally('finalize') hooks are unaffected; a
    hook's own answer (ctx.respond() before finalize) is now fitted, shaped and counted like the tool's.
  • The budget counts each copy of the data, not the whole result. A client reads one copy, so that is what the
    model pays for; the duplicate is what clients profiles remove, and the meter counts it separately.
  • structured: 'text' also hides outputSchema from that client's tools/list. The MCP spec requires
    structuredContent when a tool lists an output schema, and the reference client SDK rejects an answer without it.
  • The meter's client label is the platform, for bounded label cardinality.
  • forwardHeaders also applies to direct calls, which read custom headers the way HTTP requests do (DirectCallOptions.metadata (custom headers, user agent, client IP) is dropped in direct mode #709).

User-facing change

  • What changed:
    • New options: output.budget (@FrontMcp, @App, @Tool), clients (@FrontMcp), http.forwardHeaders.
    • New exported types OutputBudget, BudgetListPolicy, ClientProfile, StructuredAnswerMode,
      ClientAnnotationsMode, ClientInstructionsMode, ClientProfilesOptionsInput, BudgetNote, BudgetOmission;
      exported BUDGET_META_KEY, fitToolAnswer(), fitResourceAnswer(), ANSWER_COUNTERS.
    • New tools:call-tool stages fitAnswer, shapeAnswer, meterAnswer; new resources:read-resource stage
      fitAnswer.
    • New AIPlatformType values 'codex' and 'claude-code' (also accepted by transport.platformDetection.mappings).
    • New /metrics counters frontmcp_tool_answer_bytes_total, frontmcp_tool_answer_estimated_tokens_total,
      frontmcp_tool_answer_duplicated_bytes_total.
    • composeCallerInstructions() takes an optional clientName; McpHandlerOptions.composeInstructions gets the
      client name as a second argument.
  • Before → after:
    • A client named codex-mcp-client was platform generic-mcp → codex; claude-code was claude →
      claude-code. UI serving is unchanged for both. Code that compares platformType with 'generic-mcp' or
      'claude' to recognise these clients needs the new values.
    • An invalid http, output or clients option threw a ZodError from @FrontMcp → InvalidDecoratorMetadataError
      naming the field.
    • Nothing else changes until a server sets the new options.
  • frontmcp.dev pages affected (if known): the @FrontMcp options reference (http, output, new clients), the
    @Tool / @App output option, the tool output-schema page (new answer budget section), the HTTP configuration
    page (forwarded headers), the transport / platform detection page, the /metrics page (answer cost counters), the
    request context page (customHeaders), and a new client profiles page.
  • Skills catalog entries updated:
    • create-tool/references/output-schema.md (new "Answer budget" section) and decorator-options.md (output row);
    • frontmcp-config/references/configure-client-profiles.md (new) with
      examples/configure-client-profiles/coding-agent-profiles.md (new), the SKILL.md routing and examples tables,
      and skills-manifest.json;
    • frontmcp-config/references/configure-http.md (new "Forwarded Request Headers" section, checklist,
      troubleshooting);
    • frontmcp-config/references/configure-transport.md (new "Client Platform Detection" section);
    • frontmcp-config/references/configure-skills-http.md (per-client overrides of resourceAnnotations and
      injectInstructions);
    • frontmcp-observability/references/metrics-endpoint.md (answer cost counters);
    • frontmcp-development/references/decorators-guide.md (http, output, clients rows),
      create-plugin-hooks.md (which headers reach customHeaders; the stages after finalize and state.output) and
      create-tool/references/execution-context.md (which headers reach customHeaders).

Tests

Unit (libs/sdk):

  • answer/__tests__/answer-budget.spec.ts: a 300 KB answer fitted to the budget as valid JSON with the newest items
    kept and contiguous; keep-oldest; a long string cut with the characters counted; the single item kept and fitted
    when no whole item fits; no split surrogate pair, UTF-8 bytes counted; an answer of numbers and keys left as it is;
    structuredContent and the text that repeats it fitted alike with the note text and _meta; JSON text without
    structuredContent; plain text; text that only looks like JSON; the note naming five parts and counting the rest;
    pretty JSON compacted without a note; images, HTML and blobs left alone; the resource note content; the
    outputSchema check naming a cut below minItems or a shortened tuple, and a schema it cannot check.
  • answer/__tests__/client-profile.spec.ts: profile resolution (first glob, case-insensitive, no match); where the
    client name comes from; a name over 256 characters matches no profile; the one-line summary; 'only' / 'text' / 'both', and 'only' left out for tools whose
    schema is not listed and for errors; annotations for 'no-numbers' and 'full'.
  • answer/__tests__/answer-meter.spec.ts: bytes, estimated tokens and duplicated bytes; when metering is on; the three
    counters with their labels.
  • answer/__tests__/answer-shaping.flows.spec.ts, through connected clients: a tool answer and a resource read fitted
    to output.budget; budget: false; a this.callTool() answer left whole; 'only', 'text' with no
    outputSchema listed (the upstream client accepts the answer), 'both' for a tool without a schema; no-numbers
    and full annotations against another client; the instructions profile; the counters with client: 'codex';
    a plugin that replaces the fitAnswer stage with Around('fitAnswer') answers in place of the budget; an answer
    whose cut would break its .min(200) schema is sent whole (before, the upstream client rejected it with -32602);
    an answer an Around('finalize') plugin redacted stays redacted for a structured: 'text' client.
  • common/types/options/__tests__/answer-shaping.options.spec.ts: the budget, profile and forwardHeaders schemas,
    budget resolution, and InvalidDecoratorMetadataError at boot for each option.
  • common/utils/__tests__/forward-headers.utils.spec.ts and context/__tests__/forwarded-headers.spec.ts: refused
    headers and globs (set-cookie, host, forwarded, x-forwarded-*, x-real-ip, and x-* at boot), a header
    name over 256 characters, case-insensitive matching, HTTP and in-process metadata, the context storage, and this.fetch()
    forwarding a kept header only to a forwardCustomHeadersTo origin.
  • notification/__tests__/platform-detection.spec.ts: codex and claude-code from clientInfo and the user agent.
  • transport/mcp-handlers/__tests__/initialize-request.handler.spec.ts: the handler passes the client name to the
    instructions composer.
  • transport/mcp-20260728/__tests__/cache-scope.per-caller.spec.ts: each cacheable 2026-07-28 result is
    cacheScope: 'private' on a server with clients profiles.
  • libs/uipack/src/adapters/__tests__/serving-mode.spec.ts: Codex keeps structuredContent; a claude-code client is served
    as claude. template-renderer.spec.ts: a claude-code client gets resourceMode: 'inline' widgets, as when it was
    detected as claude.

E2E:

  • New app apps/e2e/demo-e2e-answer-shaping (10 tests): a 300 KB tool answer fitted to the 64 KB budget, valid JSON,
    newest rows kept, with the note and _meta; an answer within the budget left alone; an answer whose
    rows.min(500) a cut would break sent whole; a resource read fitted; a
    client named summary-agent gets structured: 'only'; a text-agent gets no outputSchema and no
    structuredContent; another client gets both; the three counters on /metrics; an x-acme-tenant header reaches a
    tool while x-other does not; src/refused-header.main.ts (forwardHeaders: ['authorization']) fails at boot with
    the message.
  • apps/e2e/demo-e2e-dts-consumer/fixture/answer-shaping-must-compile.ts: the options, types and BUDGET_META_KEY
    type-check from the published declarations; tool-must-fail.ts rejects budget.lists: 'newest'.

Red first: the unit specs failed before the implementation (flows 8 of 12, forwardHeaders validation 8, boot errors
3, metadata 3, platform detection 5). The review fixes were red first too: the cut below .min(200) (the upstream
client threw -32602), the redacted answer (the client got the tool's original), the five cacheScope cases, 14
header refusals, the two 256-character caps and the claude-code resourceMode.

Ran: nx test sdk (586 suites, 7628 tests), nx run-many -t test -p uipack observability plugin-cache plugin-codecall plugin-remember plugin-approval plugin-feature-flags skills testing auth, nx test demo-e2e-answer-shaping (10/10), nx test demo-e2e-dts-consumer (16/16), and the e2e apps covering the changed
flows: demo-e2e-hooks (23), demo-e2e-protocol-20260728 (145), demo-e2e-cache (14), demo-e2e-tasks (15),
demo-e2e-standalone (53), demo-e2e-ui (361); eslint on the changed files (no errors), nx build sdk uipack,
node scripts/fix-unused-imports.mjs, prettier --check on the changed files, and tsc --noEmit of the sdk and
uipack libraries.

Summary by CodeRabbit

  • New Features
    • Added configurable response-size budgets for tool and resource answers, with list retention, truncation, and omission details. Answers that cannot be shortened without violating their output schema remain intact.
    • Added client-specific response profiles for structured content, resource annotations, and skill instructions.
    • Added opt-in forwarding for approved request headers, with sensitive headers rejected.
    • Added support for Claude Code and Codex platform detection and response formatting.
    • Added metrics for answer size, estimated tokens, and duplicated content.
  • Documentation
    • Added configuration guides and examples for response budgets, client profiles, and forwarded headers.

…r and http.forwardHeaders

- output.budget { maxBytes, lists } (server, app, tool; false turns it off) fits tool answers
  and resource reads without cutting inside JSON: whole list items, then string tails, with
  the note in _meta['frontmcp/budget'] and a last text line. Runs in the new fitAnswer stage.
- clients profiles keyed by a clientInfo.name glob: structured both | only | text (shapeAnswer
  stage; text also leaves outputSchema out of tools/list), annotations full | no-numbers for
  resources/list, instructions full | compact for initialize and server/discover.
- Platform detection learns codex (codex-mcp-client) and claude-code.
- /metrics counts frontmcp_tool_answer_bytes_total, _estimated_tokens_total (bytes / 4) and
  _duplicated_bytes_total per tool and client platform (meterAnswer stage).
- http.forwardHeaders keeps more request headers in customHeaders, over HTTP and for direct
  calls; authorization, cookie, proxy-* and hop-by-hop headers are refused at startup.
- An invalid http, output or clients option fails @FrontMcp with InvalidDecoratorMetadataError.
- New e2e app demo-e2e-answer-shaping; skills catalog updated.
…reate() docs to the headers it keeps

- The UI metadata stub kept its own platform union, so its exported options rejected codex,
  claude-code, generic-mcp and ext-apps; it uses AIPlatformType now.
- create() takes no http option, so its direct calls keep x-frontmcp-* headers only: the
  build-for-sdk page no longer claims forwardHeaders there, and the HTTP page names createDirect().
- Tests: a plugin replaces the fitAnswer stage with Around('fitAnswer'), and this.fetch() sends a
  forwarded header only to a fetch.forwardCustomHeadersTo origin.
…ping on the answer a hook replaced, safer forwardHeaders

- output.budget sends an answer whole, with a warning once per tool, when the cut
  would break the outputSchema a client validates structuredContent against
  (minItems, tuples, patterns); the reference client rejected such answers with -32602.
- tools:call-tool and resources:read-resource record every answer they respond
  with in state.output, so an answer a hook replaced around finalize is the one
  fitted, shaped and counted, instead of the tool's original.
- A server with clients profiles marks 2026-07-28 cacheable results private:
  tools/list, resources/list and server/discover now differ per client.
- http.forwardHeaders also refuses set-cookie, host, forwarded, x-forwarded-* and
  x-real-ip; a glob matching one (x-*) fails at startup.
- Client names and header names over 256 characters match no profile or glob, so a
  long client-chosen name cannot stall a multi-wildcard glob.
…claude

A claude-code client was detected as 'claude' and got resourceMode 'inline' by default; its
new 'claude-code' platform type fell back to 'cdn'.
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between e57c0d4 and ff2bde0.

Files selected for processing: 83
  • apps/e2e/demo-e2e-answer-shaping/e2e/answer-shaping.e2e.spec.ts
  • apps/e2e/demo-e2e-answer-shaping/jest.e2e.config.ts
  • apps/e2e/demo-e2e-answer-shaping/project.json
  • apps/e2e/demo-e2e-answer-shaping/src/apps/reports/index.ts
  • apps/e2e/demo-e2e-answer-shaping/src/apps/reports/resources/report-log.resource.ts
  • apps/e2e/demo-e2e-answer-shaping/src/apps/reports/tools/big-report.tool.ts
  • apps/e2e/demo-e2e-answer-shaping/src/apps/reports/tools/recent-report.tool.ts
  • apps/e2e/demo-e2e-answer-shaping/src/apps/reports/tools/request-headers.tool.ts
  • apps/e2e/demo-e2e-answer-shaping/src/main.ts
  • apps/e2e/demo-e2e-answer-shaping/src/refused-header.main.ts
  • apps/e2e/demo-e2e-answer-shaping/tsconfig.app.json
  • apps/e2e/demo-e2e-answer-shaping/tsconfig.json
  • apps/e2e/demo-e2e-answer-shaping/webpack.config.js
  • apps/e2e/demo-e2e-dts-consumer/e2e/dts-consumer.e2e.spec.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/answer-shaping-must-compile.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/tool-must-fail.ts
  • apps/e2e/demo-e2e-dts-consumer/fixture/tsconfig.json
  • libs/auth/src/common/session.types.ts
  • libs/sdk/src/answer/__tests__/answer-budget.spec.ts
  • libs/sdk/src/answer/__tests__/answer-meter.spec.ts
  • libs/sdk/src/answer/__tests__/answer-shaping.flows.spec.ts
  • libs/sdk/src/answer/__tests__/client-profile.spec.ts
  • libs/sdk/src/answer/answer-budget.ts
  • libs/sdk/src/answer/answer-meter.ts
  • libs/sdk/src/answer/client-profile.ts
  • libs/sdk/src/answer/index.ts
  • libs/sdk/src/common/decorators/front-mcp.decorator.ts
  • libs/sdk/src/common/metadata/front-mcp.metadata.ts
  • libs/sdk/src/common/metadata/output-policy.ts
  • libs/sdk/src/common/tokens/front-mcp.tokens.ts
  • libs/sdk/src/common/types/options/__tests__/answer-shaping.options.spec.ts
  • libs/sdk/src/common/types/options/clients/index.ts
  • libs/sdk/src/common/types/options/clients/interfaces.ts
  • libs/sdk/src/common/types/options/clients/schema.ts
  • libs/sdk/src/common/types/options/http/interfaces.ts
  • libs/sdk/src/common/types/options/http/schema.ts
  • libs/sdk/src/common/types/options/index.ts
  • libs/sdk/src/common/utils/__tests__/forward-headers.utils.spec.ts
  • libs/sdk/src/common/utils/forward-headers.utils.ts
  • libs/sdk/src/context/__tests__/forwarded-headers.spec.ts
  • libs/sdk/src/context/frontmcp-context-storage.ts
  • libs/sdk/src/context/metadata.utils.ts
  • libs/sdk/src/flows/flow.registry.ts
  • libs/sdk/src/front-mcp/front-mcp.ts
  • libs/sdk/src/index.ts
  • libs/sdk/src/notification/__tests__/platform-detection.spec.ts
  • libs/sdk/src/notification/notification.service.ts
  • libs/sdk/src/resource/flows/read-resource.flow.ts
  • libs/sdk/src/resource/flows/resources-list.flow.ts
  • libs/sdk/src/scope/scope.instance.ts
  • libs/sdk/src/skill/skill-instructions.helper.ts
  • libs/sdk/src/tool/flows/call-tool.flow.ts
  • libs/sdk/src/tool/flows/tools-list.flow.ts
  • libs/sdk/src/tool/ui/platform-adapters.ts
  • libs/sdk/src/transport/adapters/transport.local.adapter.ts
  • libs/sdk/src/transport/flows/handle.mcp-20260728.flow.ts
  • libs/sdk/src/transport/flows/handle.sse.flow.ts
  • libs/sdk/src/transport/flows/handle.streamable-http.flow.ts
  • libs/sdk/src/transport/in-memory-server.ts
  • libs/sdk/src/transport/mcp-20260728/__tests__/cache-scope.per-caller.spec.ts
  • libs/sdk/src/transport/mcp-20260728/result-decorator.ts
  • libs/sdk/src/transport/mcp-handlers/__tests__/initialize-request.handler.spec.ts
  • libs/sdk/src/transport/mcp-handlers/initialize-request.handler.ts
  • libs/sdk/src/transport/mcp-handlers/mcp-handlers.types.ts
  • libs/sdk/src/transport/web-standard-mcp.ts
  • libs/skills/catalog/create-tool/references/decorator-options.md
  • libs/skills/catalog/create-tool/references/execution-context.md
  • libs/skills/catalog/create-tool/references/output-schema.md
  • libs/skills/catalog/frontmcp-config/SKILL.md
  • libs/skills/catalog/frontmcp-config/examples/configure-client-profiles/coding-agent-profiles.md
  • libs/skills/catalog/frontmcp-config/references/configure-client-profiles.md
  • libs/skills/catalog/frontmcp-config/references/configure-http.md
  • libs/skills/catalog/frontmcp-config/references/configure-skills-http.md
  • libs/skills/catalog/frontmcp-config/references/configure-transport.md
  • libs/skills/catalog/frontmcp-development/references/create-plugin-hooks.md
  • libs/skills/catalog/frontmcp-development/references/decorators-guide.md
  • libs/skills/catalog/frontmcp-observability/references/metrics-endpoint.md
  • libs/skills/catalog/skills-manifest.json
  • libs/testing/src/server/port-registry.ts
  • libs/uipack/src/adapters/__tests__/serving-mode.spec.ts
  • libs/uipack/src/adapters/__tests__/template-renderer.spec.ts
  • libs/uipack/src/adapters/serving-mode.ts
  • libs/uipack/src/adapters/template-renderer.ts

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

Walkthrough

The SDK adds answer budgets, client-specific response shaping, answer-cost metrics, and configurable request-header forwarding. It also adds Claude Code and Codex platform support, an E2E demo, tests, and configuration documentation.

Changes: Answer shaping and client profiles

Layer / File(s) Summary
Budget and client-profile contracts
libs/sdk/src/common/metadata/output-policy.ts, libs/sdk/src/common/types/options/clients/*, libs/sdk/src/common/metadata/front-mcp.metadata.ts, libs/sdk/src/common/decorators/front-mcp.decorator.ts, libs/sdk/src/answer/index.ts, libs/sdk/src/index.ts, apps/e2e/demo-e2e-dts-consumer/*, libs/skills/catalog/create-tool/references/output-schema.md
Output policies accept byte budgets and list-retention settings. @FrontMcp accepts validated client profiles with structured-answer, annotation, and instruction modes. Public exports and consumer type fixtures cover the options.
Budget fitting and response flows
libs/sdk/src/answer/answer-budget.ts, libs/sdk/src/answer/__tests__/answer-budget.spec.ts, libs/sdk/src/resource/flows/read-resource.flow.ts, libs/sdk/src/tool/flows/call-tool.flow.ts, libs/sdk/src/answer/__tests__/answer-shaping.flows.spec.ts, libs/skills/catalog/create-tool/references/output-schema.md, libs/skills/catalog/frontmcp-development/references/create-plugin-hooks.md
Tool and resource answers can be fitted to configured byte budgets. Fitting records omissions and checks output-schema constraints before replacing a tool answer. The flows run answer processing after finalization.
Client profiles and platform-aware output
libs/sdk/src/answer/client-profile.ts, libs/sdk/src/tool/flows/tools-list.flow.ts, libs/sdk/src/resource/flows/resources-list.flow.ts, libs/sdk/src/skill/skill-instructions.helper.ts, libs/sdk/src/transport/*, libs/sdk/src/notification/notification.service.ts, libs/auth/src/common/session.types.ts, libs/uipack/src/adapters/*, libs/skills/catalog/frontmcp-config/references/configure-client-profiles.md, libs/skills/catalog/frontmcp-config/references/configure-transport.md
Client profiles shape tool answers, tool listings, resource annotations, and injected instructions. Client names are passed through instruction-composition paths. Platform detection and UI serving recognize Claude Code and Codex.
Answer metering and integrated verification
libs/sdk/src/answer/answer-meter.ts, libs/sdk/src/answer/__tests__/answer-meter.spec.ts, libs/skills/catalog/frontmcp-observability/references/metrics-endpoint.md, apps/e2e/demo-e2e-answer-shaping/*, libs/testing/src/server/port-registry.ts
Tool-answer byte, estimated-token, and duplicated-byte counters are recorded when metrics are enabled. The E2E demo and tests cover budgets, profiles, metrics, and TypeScript consumer declarations.

Forwarded request headers

Layer / File(s) Summary
Forward-header rules and validation
libs/sdk/src/common/types/options/http/*, libs/sdk/src/common/utils/forward-headers.utils.ts, libs/sdk/src/common/types/options/__tests__/answer-shaping.options.spec.ts, libs/sdk/src/common/utils/__tests__/forward-headers.utils.spec.ts, libs/skills/catalog/frontmcp-config/references/configure-http.md, libs/skills/catalog/frontmcp-development/references/decorators-guide.md
HTTP options accept header names and globs. Validation rejects patterns that match protected headers. The forwarder matches case-insensitively and rejects names longer than 256 characters.
Header filtering in request contexts
libs/sdk/src/context/metadata.utils.ts, libs/sdk/src/context/frontmcp-context-storage.ts, libs/sdk/src/scope/scope.instance.ts, libs/sdk/src/flows/flow.registry.ts, libs/sdk/src/context/__tests__/forwarded-headers.spec.ts, libs/skills/catalog/frontmcp-config/references/configure-http.md, libs/skills/catalog/create-tool/references/execution-context.md
Request metadata and in-process contexts retain configured headers. Context storage passes the matcher to metadata extraction, while fetch forwarding remains limited to configured origins.

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

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant CallToolFlow
  participant AnswerBudget
  participant ClientProfile
  participant AnswerMeter
  MCPClient->>CallToolFlow: call tool
  CallToolFlow->>AnswerBudget: fit answer to configured budget
  CallToolFlow->>ClientProfile: shape answer for resolved profile
  CallToolFlow->>AnswerMeter: record answer cost when enabled
  CallToolFlow-->>MCPClient: return processed answer
Loading

Suggested reviewers: alexmercerpo

Merge Risk: Low · previous review ff2bd

These issues affect opt-in header forwarding, unusually long budget notes, and testing support for the new platforms. The PR is mergeable with owner awareness, but those paths should be corrected before relying on them.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly summarizes the main changes: answer budgets, client profiles, answer-cost metrics, and forwarded headers.
Description check Passed The description includes the required “What and why,” “User-facing change,” and “Tests” sections. It explains the changes, user impact, affected documentation, and reported validation in detail.
Docstring Coverage Passed Docstring coverage is 65.00% which is sufficient. The required threshold is 65.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
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: Docstring Coverage

Explanation

Docstring coverage is 65.00% which is sufficient. 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 40 functions across 50 files. (33 skipped: 17 unsupported, 16 over the file limit.)

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

@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 13:44
@frontegg-david
frontegg-david added this pull request to stack #997 November 2, 2026 13:44
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@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-02T14:30:45.659Z
Commit: 375b269f

@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/sdk/src/answer/answer-budget.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.

@frontegg-david
frontegg-david marked this pull request as draft November 2, 2026 14:17
…orwardHeaders

A content-* glob kept the inbound content-length, which fetch() then sent to
an allow-listed origin with a body of another length. content-length is now
never kept. A forwardHeaders name or glob inside a refused family
(x-forwarded-port, proxy-t*) passed validation and was then silently dropped;
it now fails startup like the other refused headers.

Also add the codex and claude-code platform values to the platform helpers
of @frontmcp/testing, which mirror the SDK's platform types.
@frontegg-david
frontegg-david marked this pull request as ready for review November 2, 2026 14:17
@frontegg-david

Copy link
Copy Markdown
Contributor Author

Merged feat/m04-aliases-remedy (2354300, 6a96200) into this branch along with the fixes above (a826eaa). Local runs on a826eaa: nx test sdk 586 suites / 7635 tests, nx test testing 403 passed, nx test skills 249/249, nx test demo-e2e-answer-shaping 10/10.

@alexmercerpo
alexmercerpo merged commit fb68a9d into main Nov 2, 2026
46 checks passed
@alexmercerpo
alexmercerpo deleted the feat/m05-answer-shaping 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