Skip to content

fix(sdk): CLI mode validates jobs and the top-level sqlite as the server does - #1036

Merged
alexmercerpo merged 4 commits into
perf/f03-browser-sizefrom
fix/g02-cli-jobs-config
Nov 3, 2026
Merged

alexmercerpo merged 4 commits into
perf/f03-browser-sizefrom
fix/g02-cli-jobs-config

Conversation

@frontegg-david

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

Copy link
Copy Markdown
Contributor

Stacked on #1030

What and why

Fixes #1032.

CLI mode reads the server config with parseFrontMcpConfigLite. That parser skips validating the subsystems the CLI host doesn't run, to keep cold start cheap. It also skipped two subsystems the host does run: jobs and the top-level sqlite were z.any(). #1028 fixed tasks the same way.

The lite parser runs in two places:

  • createForCli(), which the generated CLI and connect(config, { mode: 'cli' }) use;
  • createForManifest(), which frontmcp build boots to read a server.

A server declared with @FrontMcp was already checked by the decorator, which runs the full parser. A config passed as an object was not: an entry that exports one, or connect(config, { mode: 'cli' }). Before this PR, such a config did the following with a wrong value:

  • jobs.store. A value that is neither false nor an object ('memory', true, null) was taken for unset. The runs went to the top-level redis (unless it is Vercel KV), else to the top-level sqlite. A store naming both redis and sqlite used the SQLite file.
  • Other jobs fields. A wrong jobs.runner, jobs.defaultTimeoutMs or jobs.admission reached the job subsystem unchecked, and so did a jobs block without enabled.
  • Top-level sqlite. An empty sqlite.path was taken for no path, so the file went to ~/.{info.name}/sessions.sqlite. A missing sqlite.encryption.secret, for example from an unset environment variable, kept the task store unencrypted. The job stores already refused it, because they open the file through createStorage.

The change

The lite schema now takes jobs and sqlite from frontMcpBaseSchema.shape, as it does for tasks. These are the server's schema objects, so the lite parser gives the same messages and fails at the same point.

A valid config behaves as before. Both schemas apply defaults, but every CLI-mode consumer already falls back to the same values:

  • SQLite: walMode: true and ttlCleanupIntervalMs: 60000, at the top level and in jobs.store.sqlite. The SQLite stores use walMode !== false and ?? 60000.
  • jobs.store.redis: it is normalised like the server's redis option: a url becomes host, port, password, db and tls, and port, db, tls, keyPrefix and defaultTtlMs get their defaults. The job stores connect with exactly those connection fields. Their keys go under jobs.store.keyPrefix ?? 'mcp:jobs:', never under redis.keyPrefix.

createForManifest() still drops the backends and jobs.store after parsing, as before.

The other keys the lite parser doesn't validate

I checked every key that the lite parser passes through as z.any(), or drops, while the full parser validates it. Only jobs and sqlite are both the bug this issue reports (an option the CLI host uses to pick a store, silently taken for unset) and free of any behaviour change for a valid config. The rest are follow-ups:

Key Does CLI mode read it? Why it isn't fixed here
redis (top level) Yes: tasks and jobs take their store from it The server's schema turns redis: { url } into { provider: 'redis', host, port, … }. CLI mode passes the raw { url }, and createTaskStore doesn't recognise it because it looks for host. So in the generated CLI, tasks on a URL-form redis fall to createStorage's auto-detection (REDIS_URL, else memory), while the server keeps them in that Redis. Validating the key moves those tasks to Redis, which is a behaviour change. Worth its own issue.
fetch Yes: this.fetch() in tools The schema reduces forwardCallerTokenTo and forwardCustomHeadersTo entries to their origin (https://api.example.com/v1 becomes https://api.example.com). Validating would change which hosts get the caller's token in CLI mode, so it needs its own security-reviewed PR.
transport Yes: distributedMode, providerCaching, protocol Today CLI mode gets the raw object, or the defaults when the key is unset. The schema would fill in the defaults of a partial transport as well.
http Partly: entryPath, forwardHeaders, and port for the local auth issuer The schema's defaults (port, entryPath from FRONTMCP_HTTP_ENTRY_PATH, bodyLimit) would start to apply.
authorities Yes: authorization checks Same schema and no defaults, but it strips undeclared keys. #366 made this key pass through on purpose, and the spec "does not validate the inner shape (pass-through)" pins that. Changing it needs a decision.
pagination, elicitation, metrics One or two fields each: pagination.tools in tools/list, elicitation.enabled in tool contexts, and metrics.enabled / include for answer metering Same schema, and the defaults match the code's, so no valid config would change. They aren't a store fallback, though, so they can be one cheap follow-up.
clients, observability, channels, registry, session No. These keys aren't in the lite schema, so CLI mode strips them Adding them would turn features on in CLI mode: client profiles, the ObservabilityPlugin from config, and channels. That's new behaviour. registry serves HTTP routes, which CLI mode doesn't serve. session is deprecated and only warns.
pubsub, extApps, ui, health, throttle No, or only on paths CLI mode skips: UI, HTTP, rate limiting Passed through on purpose.

User-facing change

  • What changed: CLI mode validates jobs and the top-level sqlite with the server's schemas. That covers the generated CLI (frontmcp build --target cli), connect(config, { mode: 'cli' }), FrontMcpInstance.createForCli(), and the server read that frontmcp build does. An invalid value fails at startup with the server's error, which carries messages such as:

    • jobs.store must be false (runs kept in memory) or an object with redis or sqlite
    • jobs.store takes redis or sqlite, not both
    • jobs.runner must be 'inline', 'worker' or 'detached'
    • jobs.defaultTimeoutMs must be at most 2147483647 ms (about 24.8 days)

    Zod's own messages cover jobs.enabled, jobs.admission.*, jobs.store.sqlite.path, sqlite.path and sqlite.encryption.secret. The generated CLI prints the error as Error: … and exits with code 1.

  • Before → after (for a config given as an object, not through @FrontMcp):

    • jobs.store: 'memory', true or null: the CLI ran the jobs and kept the runs in the top-level redis or sqlite → it fails at startup and opens no store.
    • jobs.store naming both redis and sqlite: the runs went to the SQLite file → it fails at startup.
    • A wrong jobs.runner, jobs.defaultTimeoutMs or jobs.admission, or jobs without enabled (such as jobs: { store: false }): accepted → it fails at startup.
    • sqlite.path: '': the file went to ~/.{info.name}/sessions.sqlite → it fails at startup.
    • sqlite.encryption.secret missing or empty: tasks were stored unencrypted → it fails at startup.
    • frontmcp build --target cli or --target mcpb of such a config: it built → the build fails with the same error. The other targets still build but print frontmcp.manifest.json not written: …, and --strict-lint fails them. Their bundles, like the MCPB server, use the full parser, which already refused the config at boot.
    • A valid config: unchanged. The defaults the schemas now apply in CLI mode match the ones the stores fell back to before.

    The server's parser, and so @FrontMcp, already refused every one of these values, jobs without enabled included. The build reads the config in its own environment, as it already did for a @FrontMcp server. So a value read from an environment variable that the build doesn't set, such as sqlite.encryption.secret or jobs.store.redis.url, now fails those builds of a config object too.

  • frontmcp.dev pages affected (if known): the CLI target (build for CLI, server configuration), jobs configuration (jobs.store), SQLite setup (the generated CLI).

  • Skills catalog entries updated:

    • frontmcp-deployment/references/build-for-cli.md: the generated CLI validates jobs and sqlite, and what it did up to 1.10.
    • frontmcp-development/references/create-job.md: the generated CLI and connect(..., { mode: 'cli' }) validate jobs, and what they did up to 1.10. Its jobs snippets now set enabled: true, which a jobs block needs. One example and its comment said it was implied, but the decorator refused it.
    • frontmcp-setup/references/setup-sqlite.md: the generated CLI validates the top-level sqlite, and what it did up to 1.10.

Bundle size

The lite schema reaches browser bundles. It now references two existing schema objects in place of two z.any() calls. I measured with nx run sdk:build --skip-nx-cache and then nx test demo-e2e-bundle-size:

Bundle Base This PR Δ Budget
create() + one tool 1,294,971 1,294,963 −8 1,295,507 (unchanged)
create() + WebMcpPlugin + one tool 1,302,155 1,302,147 −8 1,302,379 (unchanged)
Vite initial chunk 761,771 761,771 0 761,903 (unchanged)

Tests

All new tests were written first and run against the unchanged code. The failing ones are marked "red first".

  • Unit, libs/sdk/src/common/metadata/__tests__/parse-lite.spec.ts:
    • Valid jobs (store: false, runner, defaultTimeoutMs, admission) and a valid sqlite survive the lite parser.
    • The lite parser refuses, with the full parser's messages:
      • jobs.store: 'memory';
      • a store naming both redis and sqlite;
      • jobs.runner: 'thread';
      • a jobs.defaultTimeoutMs over the timer limit.
    • It refuses these at their issue paths: jobs without enabled, a jobs.store.sqlite with an empty path, jobs.admission.concurrency: 0, sqlite.path: '', and sqlite.encryption: { secret: undefined }.
    • Red first: every refusal case failed; the round-trip tests passed before and after the fix.
  • Unit, libs/sdk/src/scope/__tests__/scope-store-off.spec.ts (CLI mode, createForCli, top-level sqlite):
    • An invalid jobs.store fails at startup before any SQLite store opens. Red first.
    • jobs.store: false keeps the runs off the SQLite file, where jobs: { enabled: true } puts them in it.
    • The CLI starter is hoisted next to startGraph and shared with the existing tasks.store: false CLI test.
  • E2E, apps/e2e/demo-e2e-cli-exec/e2e/cli-build-jobs-config.e2e.spec.ts: builds a scratch server, a config object with a job, the top-level sqlite, and jobs read from the environment, with frontmcp build --target cli --js. Then it runs the generated CLI's job run echo:
    • With jobs.store: 'memory' it exits 1, prints the server's message, and never creates the SQLite file. Red first: it exited 0 and ran the job.
    • With jobs: { enabled: true } the run's mcp:jobs: keys land in the SQLite file. With jobs.store: false the file holds none.
    • A new runCliBundle(bundle, args, env) helper in e2e/helpers/exec-cli.ts runs any generated CLI bundle, and runCli now calls it. The file's keys are read with this app's own e2e/helpers/sqlite-keys.ts. Importing the demo-e2e-jobs copy would make demo-e2e-cli-exec depend on that app in the Nx graph, so its tests would build it first.
    • The app's test target now depends on cli:build, the CLI binary its specs drive, as well as ^build.
  • Ran:
    • nx test sdk: 665 suites, 9,312 tests.
    • nx test skills: 249 tests.
    • nx test demo-e2e-cli-exec: 37 suites, 211 tests.
    • nx test demo-e2e-bundle-size: 42 tests.
    • nx run-many -t lint -p sdk skills demo-e2e-cli-exec.
    • node scripts/fix-unused-imports.mjs a388ff236, and prettier --check on the changed files.
    • A --noEmit typecheck of the changed SDK sources and specs, plus tsc --noEmit -p tsconfig.e2e.json in demo-e2e-cli-exec. That app's three errors are pre-existing, in other specs.
    • By hand: frontmcp build --target cli or --target mcpb of a config with jobs.store: 'memory' exits 1 with the server's message, and --target node builds with the "manifest not written" warning. A @FrontMcp server whose sqlite.encryption.secret reads an unset environment variable already failed --target cli the same way.

Summary by CodeRabbit

  • Bug Fixes

    • Generated CLI applications now validate job and SQLite settings at startup, with errors consistent with server validation. Invalid job-store settings are rejected rather than silently falling back to another store.
    • Job configuration blocks now require enabled to be explicitly set. Setting the job store to false keeps job runs in memory.
  • Documentation

    • Updated CLI deployment and setup guidance to clarify task and job storage options, startup validation, and task runner behavior.

…ver does

The lite config parser of CLI mode passed jobs and sqlite through as z.any(), so the generated CLI took an
invalid jobs.store for unset and kept the runs in the top-level redis or sqlite, ignored a wrong runner,
defaultTimeoutMs or admission, took an empty sqlite.path for the default path and kept its tasks unencrypted on
a missing sqlite.encryption.secret. Both keys now use the server's own schemas, so the same configs fail at
startup with the same messages.

Fixes #1032
…xec, and build the CLI its tests drive

Importing the demo-e2e-jobs helper made demo-e2e-cli-exec depend on that app in the Nx graph, so its tests built it first.
…e CLI refuse without

One create-job example and its comment said enabled was implied, but the decorator refused the block.
@frontegg-david
frontegg-david marked this pull request as ready for review November 3, 2026 10:09
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Walkthrough

The CLI lite schema now validates jobs and top-level sqlite configuration with the server schemas. Tests cover parser errors, CLI startup, and job-store behavior. Documentation describes the configuration requirements and CLI validation behavior.

Changes: CLI Configuration Validation

Layer / File(s) Summary
Lite schema and configuration contract
libs/sdk/src/common/metadata/front-mcp.metadata.ts, libs/sdk/src/common/metadata/__tests__/parse-lite.spec.ts, libs/skills/catalog/frontmcp-development/references/create-job.md, libs/skills/catalog/frontmcp-setup/references/setup-sqlite.md
The CLI lite schema now validates jobs and sqlite with the server schemas. Parser tests cover accepted values and validation errors. The job and SQLite guides describe the configuration requirements.
CLI startup and store behavior checks
libs/sdk/src/scope/__tests__/scope-store-off.spec.ts, apps/e2e/demo-e2e-cli-exec/e2e/cli-build-jobs-config.e2e.spec.ts, apps/e2e/demo-e2e-cli-exec/e2e/helpers/exec-cli.ts, apps/e2e/demo-e2e-cli-exec/e2e/helpers/sqlite-keys.ts, apps/e2e/demo-e2e-cli-exec/project.json, libs/skills/catalog/frontmcp-deployment/references/build-for-cli.md
Scope and end-to-end tests check startup validation and job-store behavior. The CLI test target builds its prerequisites, and its helpers run bundles and inspect SQLite keys. The deployment guide documents CLI validation and store behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: alexmercerpo

Merge Risk: Minimal · up to 2376e

CLI configuration gains server-equivalent validation, with no actionable merge-blocking risk identified.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title "fix(sdk): CLI mode validates jobs and the top-level sqlite as the server does" matches the main change. The diff makes the CLI lite schema take jobs and top-level sqlite from the server…
Description check Passed The description follows the repository template. It has the 'What and why' section, which names the issue (#1032), the cause, and the change. It has the 'User-facing change' section, which covers what…
Linked Issues check Passed [#1032] requires CLI mode to validate jobs with the server's jobs schema. Invalid jobs.store, jobs.runner, jobs.defaultTimeoutMs, and jobs.admission values must fail at startup with the serv…
Out of Scope Changes check Passed The top-level sqlite change goes beyond the wording of [#1032]. It is still connected to the issue. [#1032] states that invalid jobs values cause jobs to fall back to the top-level redis or `sql…
Docstring Coverage Passed Docstring coverage is 100.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. Documentat…
Full details: Title check

Explanation

The title "fix(sdk): CLI mode validates jobs and the top-level sqlite as the server does" matches the main change. The diff makes the CLI lite schema take jobs and top-level sqlite from the server's schema shapes instead of z.any(), so invalid values fail at startup with the server's messages. The title is specific, single-sentence, and follows conventional-commit form. It does not cover the E2E test, helper, or docs changes, which is expected.

Full details: Description check

Explanation

The description follows the repository template. It has the 'What and why' section, which names the issue (#1032), the cause, and the change. It has the 'User-facing change' section, which covers what changed, before and after behaviour, affected frontmcp.dev pages, and skills catalog entries. It has the 'Tests' section, which lists the unit, E2E, and manual checks, along with the commands run and their results. The 'Bundle size' section and the table of out-of-scope keys go beyond the template, but they add useful context and do not replace any required section.

Full details: Linked Issues check

Explanation

[#1032] requires CLI mode to validate jobs with the server's jobs schema. Invalid jobs.store, jobs.runner, jobs.defaultTimeoutMs, and jobs.admission values must fail at startup with the server's messages. At the reviewed head, frontMcpLiteSchema in libs/sdk/src/common/metadata/front-mcp.metadata.ts sets jobs: frontMcpBaseSchema.shape.jobs in place of z.any().optional(). The lite parser is the parser used by createForCli() and the CLI build read. parse-lite.spec.ts (present at head) checks that the lite parser refuses jobs.store: 'memory', a store naming both redis and sqlite, jobs.runner: 'thread', and an over-limit jobs.defaultTimeoutMs, using the full parser's messages. It also checks refusals for jobs without enabled and for jobs.admission.concurrency: 0, and that valid jobs values are kept. The issue's runStdio() path was not inspected separately. The lite parser is shared, but that path is not independently verified. The issue's listed acceptance criteria are met by the evidence above.

Full details: Out of Scope Changes check

Explanation

The top-level sqlite change goes beyond the wording of [#1032]. It is still connected to the issue. [#1032] states that invalid jobs values cause jobs to fall back to the top-level redis or sqlite, so validating the top-level sqlite is part of making that fallback fail at startup. The same lite-schema edit sets sqlite: frontMcpBaseSchema.shape.sqlite. The other changed files support the behavior change: parse-lite.spec.ts (tests), scope-store-off.spec.ts (CLI-mode tests for jobs.store and sqlite), the new e2e spec and its helpers (run the generated CLI), the project.json test dependency on the CLI build, and the skills catalog pages that document the new validation. The head diff of front-mcp.metadata.ts does not modify the other lite-parser keys (redis, fetch, transport, http, authorities, and so on). Those keys remain as the PR describes. The assessment of the other changed files rests on the PR summary and file listing. Their full contents were not read, and no unrelated change was found.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.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 1 functions across 6 files. (4 skipped: 4 unsupported.)

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

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between ca36629 and 2376eba.

Files selected for processing: 10
  • apps/e2e/demo-e2e-cli-exec/e2e/cli-build-jobs-config.e2e.spec.ts
  • apps/e2e/demo-e2e-cli-exec/e2e/helpers/exec-cli.ts
  • apps/e2e/demo-e2e-cli-exec/e2e/helpers/sqlite-keys.ts
  • apps/e2e/demo-e2e-cli-exec/project.json
  • libs/sdk/src/common/metadata/__tests__/parse-lite.spec.ts
  • libs/sdk/src/common/metadata/front-mcp.metadata.ts
  • libs/sdk/src/scope/__tests__/scope-store-off.spec.ts
  • libs/skills/catalog/frontmcp-deployment/references/build-for-cli.md
  • libs/skills/catalog/frontmcp-development/references/create-job.md
  • libs/skills/catalog/frontmcp-setup/references/setup-sqlite.md

Included review availability: This review used your included allowance. Your plan provides up to 5 included reviews per hour; 0 remain after this review.

@github-actions

github-actions Bot commented Nov 3, 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-03T10:24:26.076Z
Commit: b47062aa

@alexmercerpo
alexmercerpo merged commit 9ce442f into main Nov 3, 2026
47 checks passed
@alexmercerpo
alexmercerpo deleted the fix/g02-cli-jobs-config branch November 3, 2026 12:14
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.

CLI mode doesn't validate the jobs config

2 participants