llm-cli-gateway 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -1
- package/dist/index.js +163 -1
- package/dist/validation-tools.js +61 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to the llm-cli-gateway project.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [2.3.0] - 2026-06-08: MCP tool annotations and client safety hints
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- MCP tool annotations for all 37 tools (per MCP spec + tool-design best
|
|
12
|
+
practice): display `title` plus `readOnlyHint`/`destructiveHint`/
|
|
13
|
+
`idempotentHint`/`openWorldHint` on every registration. 14 pure-read tools
|
|
14
|
+
marked read-only/closed-world; `cli_upgrade`, `session_delete`,
|
|
15
|
+
`session_clear_all`, `llm_job_cancel` marked destructive; every
|
|
16
|
+
provider-spawning tool (requests, fork, validation) marked open-world with
|
|
17
|
+
destructive potential (spawned agentic CLIs can modify the environment).
|
|
18
|
+
Clients can use the hints for confirmation UX and safe auto-approval. New
|
|
19
|
+
invariant test pins titles, the exact destructive/read-only/open-world
|
|
20
|
+
sets, and the readOnly+destructive contradiction ban.
|
|
21
|
+
|
|
7
22
|
## [2.2.0] - 2026-06-07: MCP tool-surface usability — self-describing tools
|
|
8
23
|
|
|
9
24
|
### Added
|
|
@@ -247,7 +262,7 @@ to end with a verdaccio reproduction.
|
|
|
247
262
|
- Consumer `npm ls` exits ELSPROBLEMS: the pinned `tar-stream@3.1.7` sits
|
|
248
263
|
outside `tar-fs`'s `^2.1.4` range. Inherent to the out-of-range pin; disappears
|
|
249
264
|
in 2.0.0 (Phase B / node:sqlite) when the `better-sqlite3 → prebuild-install
|
|
250
|
-
|
|
265
|
+
→ tar-fs` chain leaves the prod graph entirely.
|
|
251
266
|
- Local-tarball installs still resolve `tar-stream@2.2.0` (shrinkwrap ignored on
|
|
252
267
|
that path); the audit's advisory carve-out stays until Phase B.
|
|
253
268
|
|
package/dist/index.js
CHANGED
|
@@ -2718,6 +2718,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
2718
2718
|
.boolean()
|
|
2719
2719
|
.default(false)
|
|
2720
2720
|
.describe("Bypass dedup and force a fresh CLI run even if a recent identical request exists"),
|
|
2721
|
+
}, {
|
|
2722
|
+
title: "Claude Code request",
|
|
2723
|
+
readOnlyHint: false,
|
|
2724
|
+
destructiveHint: true,
|
|
2725
|
+
idempotentHint: false,
|
|
2726
|
+
openWorldHint: true,
|
|
2721
2727
|
}, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
2722
2728
|
const startTime = Date.now();
|
|
2723
2729
|
if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
|
|
@@ -3019,6 +3025,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
3019
3025
|
.optional()
|
|
3020
3026
|
.describe("Codex --add-dir <DIR>: additional writable workspace directories. Emitted once per entry on new sessions only; resume inherits the original session's writable-dir policy."),
|
|
3021
3027
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3028
|
+
}, {
|
|
3029
|
+
title: "Codex request",
|
|
3030
|
+
readOnlyHint: false,
|
|
3031
|
+
destructiveHint: true,
|
|
3032
|
+
idempotentHint: false,
|
|
3033
|
+
openWorldHint: true,
|
|
3022
3034
|
}, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, worktree, }) => {
|
|
3023
3035
|
const startTime = Date.now();
|
|
3024
3036
|
const prep = prepareCodexRequest({
|
|
@@ -3191,6 +3203,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
3191
3203
|
.max(3_600_000)
|
|
3192
3204
|
.optional()
|
|
3193
3205
|
.describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
|
|
3206
|
+
}, {
|
|
3207
|
+
title: "Fork Codex session",
|
|
3208
|
+
readOnlyHint: false,
|
|
3209
|
+
destructiveHint: true,
|
|
3210
|
+
idempotentHint: false,
|
|
3211
|
+
openWorldHint: true,
|
|
3194
3212
|
}, async ({ prompt, sessionId, forkLast, model, sandboxMode, askForApproval, correlationId, idleTimeoutMs, }) => {
|
|
3195
3213
|
const corrId = correlationId || randomUUID();
|
|
3196
3214
|
const startTime = Date.now();
|
|
@@ -3317,6 +3335,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
3317
3335
|
.optional()
|
|
3318
3336
|
.describe("Emit `--yolo` to auto-approve all actions. Equivalent to approvalMode 'yolo'; routed through the same approval gate. Under mcp_managed the gate still decides."),
|
|
3319
3337
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3338
|
+
}, {
|
|
3339
|
+
title: "Gemini request",
|
|
3340
|
+
readOnlyHint: false,
|
|
3341
|
+
destructiveHint: true,
|
|
3342
|
+
idempotentHint: false,
|
|
3343
|
+
openWorldHint: true,
|
|
3320
3344
|
}, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, worktree, }) => {
|
|
3321
3345
|
return handleGeminiRequest({ sessionManager, logger, runtime }, {
|
|
3322
3346
|
prompt,
|
|
@@ -3521,6 +3545,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
3521
3545
|
.optional()
|
|
3522
3546
|
.describe("Grok -w/--worktree: native CLI worktree flag (`true` → bare `--worktree`, string → named). NOT gateway slice λ `worktree`."),
|
|
3523
3547
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3548
|
+
}, {
|
|
3549
|
+
title: "Grok request",
|
|
3550
|
+
readOnlyHint: false,
|
|
3551
|
+
destructiveHint: true,
|
|
3552
|
+
idempotentHint: false,
|
|
3553
|
+
openWorldHint: true,
|
|
3524
3554
|
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktree, }) => {
|
|
3525
3555
|
return handleGrokRequest({ sessionManager, logger, runtime }, {
|
|
3526
3556
|
prompt,
|
|
@@ -3655,6 +3685,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
3655
3685
|
.optional()
|
|
3656
3686
|
.describe("Vibe --add-dir <DIR>: additional writable workspace directories. Each entry is emitted as its own --add-dir instance (Vibe states this flag may be specified multiple times)."),
|
|
3657
3687
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3688
|
+
}, {
|
|
3689
|
+
title: "Mistral Vibe request",
|
|
3690
|
+
readOnlyHint: false,
|
|
3691
|
+
destructiveHint: true,
|
|
3692
|
+
idempotentHint: false,
|
|
3693
|
+
openWorldHint: true,
|
|
3658
3694
|
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, worktree, }) => {
|
|
3659
3695
|
return handleMistralRequest({ sessionManager, logger, runtime }, {
|
|
3660
3696
|
prompt,
|
|
@@ -3823,6 +3859,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
3823
3859
|
.boolean()
|
|
3824
3860
|
.default(false)
|
|
3825
3861
|
.describe("Bypass dedup and force a fresh CLI run even if a recent identical request exists"),
|
|
3862
|
+
}, {
|
|
3863
|
+
title: "Claude Code request (async job)",
|
|
3864
|
+
readOnlyHint: false,
|
|
3865
|
+
destructiveHint: true,
|
|
3866
|
+
idempotentHint: false,
|
|
3867
|
+
openWorldHint: true,
|
|
3826
3868
|
}, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
|
|
3827
3869
|
if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
|
|
3828
3870
|
return createErrorResponse("claude", 1, "", correlationId, new Error("systemPrompt and appendSystemPrompt are mutually exclusive; use one or the other (not both)."));
|
|
@@ -4035,6 +4077,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4035
4077
|
.optional()
|
|
4036
4078
|
.describe("Codex --add-dir <DIR>: additional writable workspace directories (repeat per entry). New sessions only."),
|
|
4037
4079
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4080
|
+
}, {
|
|
4081
|
+
title: "Codex request (async job)",
|
|
4082
|
+
readOnlyHint: false,
|
|
4083
|
+
destructiveHint: true,
|
|
4084
|
+
idempotentHint: false,
|
|
4085
|
+
openWorldHint: true,
|
|
4038
4086
|
}, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, worktree, }) => {
|
|
4039
4087
|
return handleCodexRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4040
4088
|
prompt,
|
|
@@ -4138,6 +4186,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4138
4186
|
.optional()
|
|
4139
4187
|
.describe("Emit `--yolo` to auto-approve all actions. Equivalent to approvalMode 'yolo'; routed through the same approval gate. Under mcp_managed the gate still decides."),
|
|
4140
4188
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4189
|
+
}, {
|
|
4190
|
+
title: "Gemini request (async job)",
|
|
4191
|
+
readOnlyHint: false,
|
|
4192
|
+
destructiveHint: true,
|
|
4193
|
+
idempotentHint: false,
|
|
4194
|
+
openWorldHint: true,
|
|
4141
4195
|
}, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, worktree, }) => {
|
|
4142
4196
|
return handleGeminiRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4143
4197
|
prompt,
|
|
@@ -4343,6 +4397,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4343
4397
|
.optional()
|
|
4344
4398
|
.describe("Grok -w/--worktree: native CLI worktree flag (`true` → bare `--worktree`, string → named). NOT gateway slice λ `worktree`."),
|
|
4345
4399
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4400
|
+
}, {
|
|
4401
|
+
title: "Grok request (async job)",
|
|
4402
|
+
readOnlyHint: false,
|
|
4403
|
+
destructiveHint: true,
|
|
4404
|
+
idempotentHint: false,
|
|
4405
|
+
openWorldHint: true,
|
|
4346
4406
|
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktree, }) => {
|
|
4347
4407
|
return handleGrokRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4348
4408
|
prompt,
|
|
@@ -4475,6 +4535,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4475
4535
|
.optional()
|
|
4476
4536
|
.describe("Vibe --add-dir <DIR>: additional writable workspace directories. Each entry is emitted as its own --add-dir instance."),
|
|
4477
4537
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4538
|
+
}, {
|
|
4539
|
+
title: "Mistral Vibe request (async job)",
|
|
4540
|
+
readOnlyHint: false,
|
|
4541
|
+
destructiveHint: true,
|
|
4542
|
+
idempotentHint: false,
|
|
4543
|
+
openWorldHint: true,
|
|
4478
4544
|
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, worktree, }) => {
|
|
4479
4545
|
return handleMistralRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4480
4546
|
prompt,
|
|
@@ -4505,6 +4571,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4505
4571
|
});
|
|
4506
4572
|
server.tool("llm_job_status", "Check lifecycle status (running|completed|failed|canceled|orphaned) of a gateway async or deferred-sync job by jobId.", {
|
|
4507
4573
|
jobId: z.string().describe("Async job ID from *_request_async"),
|
|
4574
|
+
}, {
|
|
4575
|
+
title: "Async job status",
|
|
4576
|
+
readOnlyHint: true,
|
|
4577
|
+
destructiveHint: false,
|
|
4578
|
+
idempotentHint: true,
|
|
4579
|
+
openWorldHint: false,
|
|
4508
4580
|
}, async ({ jobId }) => {
|
|
4509
4581
|
const job = asyncJobManager.getJobSnapshot(jobId);
|
|
4510
4582
|
if (!job) {
|
|
@@ -4543,6 +4615,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4543
4615
|
.max(2000000)
|
|
4544
4616
|
.default(200000)
|
|
4545
4617
|
.describe("Max chars returned per stream"),
|
|
4618
|
+
}, {
|
|
4619
|
+
title: "Async job result",
|
|
4620
|
+
readOnlyHint: true,
|
|
4621
|
+
destructiveHint: false,
|
|
4622
|
+
idempotentHint: true,
|
|
4623
|
+
openWorldHint: false,
|
|
4546
4624
|
}, async ({ jobId, maxChars }) => {
|
|
4547
4625
|
const result = asyncJobManager.getJobResult(jobId, maxChars);
|
|
4548
4626
|
if (!result) {
|
|
@@ -4590,6 +4668,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4590
4668
|
});
|
|
4591
4669
|
server.tool("llm_job_cancel", "Cancel a running gateway async or deferred-sync job by jobId.", {
|
|
4592
4670
|
jobId: z.string().describe("Async job ID from *_request_async"),
|
|
4671
|
+
}, {
|
|
4672
|
+
title: "Cancel async job",
|
|
4673
|
+
readOnlyHint: false,
|
|
4674
|
+
destructiveHint: true,
|
|
4675
|
+
idempotentHint: true,
|
|
4676
|
+
openWorldHint: false,
|
|
4593
4677
|
}, async ({ jobId }) => {
|
|
4594
4678
|
const cancel = asyncJobManager.cancelJob(jobId);
|
|
4595
4679
|
if (!cancel.canceled) {
|
|
@@ -4636,6 +4720,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4636
4720
|
.boolean()
|
|
4637
4721
|
.default(false)
|
|
4638
4722
|
.describe("Include the full persisted prompt text in the result"),
|
|
4723
|
+
}, {
|
|
4724
|
+
title: "Persisted request lookup",
|
|
4725
|
+
readOnlyHint: true,
|
|
4726
|
+
destructiveHint: false,
|
|
4727
|
+
idempotentHint: true,
|
|
4728
|
+
openWorldHint: false,
|
|
4639
4729
|
}, async ({ correlationId, maxChars, includePrompt }) => {
|
|
4640
4730
|
const record = readPersistedRequest(flightRecorder, correlationId, {
|
|
4641
4731
|
maxChars,
|
|
@@ -4666,7 +4756,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
4666
4756
|
],
|
|
4667
4757
|
};
|
|
4668
4758
|
});
|
|
4669
|
-
server.tool("llm_process_health", "Report gateway process health: async-job manager state plus the resolved persistence configuration and paths.", {},
|
|
4759
|
+
server.tool("llm_process_health", "Report gateway process health: async-job manager state plus the resolved persistence configuration and paths.", {}, {
|
|
4760
|
+
title: "Gateway process health",
|
|
4761
|
+
readOnlyHint: true,
|
|
4762
|
+
destructiveHint: false,
|
|
4763
|
+
idempotentHint: true,
|
|
4764
|
+
openWorldHint: false,
|
|
4765
|
+
}, async () => {
|
|
4670
4766
|
const health = asyncJobManager.getJobHealth();
|
|
4671
4767
|
const persistenceBlock = {
|
|
4672
4768
|
backend: persistence.backend,
|
|
@@ -4702,6 +4798,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4702
4798
|
.enum(["claude", "codex", "gemini", "grok", "mistral"])
|
|
4703
4799
|
.optional()
|
|
4704
4800
|
.describe("Optional CLI filter"),
|
|
4801
|
+
}, {
|
|
4802
|
+
title: "Approval decisions",
|
|
4803
|
+
readOnlyHint: true,
|
|
4804
|
+
destructiveHint: false,
|
|
4805
|
+
idempotentHint: true,
|
|
4806
|
+
openWorldHint: false,
|
|
4705
4807
|
}, async ({ limit, cli }) => {
|
|
4706
4808
|
const approvals = approvalManager.list(limit, cli);
|
|
4707
4809
|
return {
|
|
@@ -4721,6 +4823,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4721
4823
|
cli: z
|
|
4722
4824
|
.preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "mistral"]).optional())
|
|
4723
4825
|
.describe("CLI filter (claude|codex|gemini|grok|mistral)"),
|
|
4826
|
+
}, {
|
|
4827
|
+
title: "Provider models",
|
|
4828
|
+
readOnlyHint: true,
|
|
4829
|
+
destructiveHint: false,
|
|
4830
|
+
idempotentHint: true,
|
|
4831
|
+
openWorldHint: false,
|
|
4724
4832
|
}, async ({ cli }) => {
|
|
4725
4833
|
const cliInfo = getAvailableCliInfo();
|
|
4726
4834
|
const result = cli ? { [cli]: cliInfo[cli] } : cliInfo;
|
|
@@ -4730,6 +4838,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4730
4838
|
cli: z
|
|
4731
4839
|
.preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "mistral"]).optional())
|
|
4732
4840
|
.describe("CLI filter (claude|codex|gemini|grok|mistral)"),
|
|
4841
|
+
}, {
|
|
4842
|
+
title: "Provider CLI versions",
|
|
4843
|
+
readOnlyHint: true,
|
|
4844
|
+
destructiveHint: false,
|
|
4845
|
+
idempotentHint: true,
|
|
4846
|
+
openWorldHint: false,
|
|
4733
4847
|
}, async ({ cli }) => {
|
|
4734
4848
|
const versions = await getCliVersions(cli);
|
|
4735
4849
|
return { content: [{ type: "text", text: JSON.stringify({ versions }, null, 2) }] };
|
|
@@ -4742,6 +4856,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4742
4856
|
.boolean()
|
|
4743
4857
|
.default(false)
|
|
4744
4858
|
.describe("When true, run local --help probes and compare advertised flags against the declared contract. Strongly recommended after any provider CLI upgrade to detect drift."),
|
|
4859
|
+
}, {
|
|
4860
|
+
title: "Provider CLI contracts",
|
|
4861
|
+
readOnlyHint: true,
|
|
4862
|
+
destructiveHint: false,
|
|
4863
|
+
idempotentHint: true,
|
|
4864
|
+
openWorldHint: false,
|
|
4745
4865
|
}, async ({ cli, probeInstalled }) => {
|
|
4746
4866
|
const report = buildUpstreamContractReport({ cli, probeInstalled });
|
|
4747
4867
|
return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
|
|
@@ -4764,6 +4884,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4764
4884
|
.max(3_600_000)
|
|
4765
4885
|
.optional()
|
|
4766
4886
|
.describe("Upgrade timeout in ms when dryRun=false"),
|
|
4887
|
+
}, {
|
|
4888
|
+
title: "Upgrade provider CLI",
|
|
4889
|
+
readOnlyHint: false,
|
|
4890
|
+
destructiveHint: true,
|
|
4891
|
+
idempotentHint: false,
|
|
4892
|
+
openWorldHint: true,
|
|
4767
4893
|
}, async ({ cli, target, dryRun, timeoutMs }) => {
|
|
4768
4894
|
try {
|
|
4769
4895
|
const result = await runCliUpgrade({ cli, target, dryRun, timeoutMs, logger });
|
|
@@ -4799,6 +4925,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4799
4925
|
cli: SESSION_PROVIDER_ENUM.describe("CLI type (claude|codex|gemini|grok|mistral)"),
|
|
4800
4926
|
description: z.string().optional().describe("Session description"),
|
|
4801
4927
|
setAsActive: z.boolean().default(true).describe("Set as active session"),
|
|
4928
|
+
}, {
|
|
4929
|
+
title: "Create session record",
|
|
4930
|
+
readOnlyHint: false,
|
|
4931
|
+
destructiveHint: false,
|
|
4932
|
+
idempotentHint: false,
|
|
4933
|
+
openWorldHint: false,
|
|
4802
4934
|
}, async ({ cli, description, setAsActive }) => {
|
|
4803
4935
|
try {
|
|
4804
4936
|
const session = await sessionManager.createSession(cli, description);
|
|
@@ -4830,6 +4962,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4830
4962
|
});
|
|
4831
4963
|
server.tool("session_list", "List gateway session records and the active session per CLI, optionally filtered by CLI.", {
|
|
4832
4964
|
cli: SESSION_PROVIDER_ENUM.optional().describe("CLI filter (claude|codex|gemini|grok|mistral)"),
|
|
4965
|
+
}, {
|
|
4966
|
+
title: "List sessions",
|
|
4967
|
+
readOnlyHint: true,
|
|
4968
|
+
destructiveHint: false,
|
|
4969
|
+
idempotentHint: true,
|
|
4970
|
+
openWorldHint: false,
|
|
4833
4971
|
}, async ({ cli }) => {
|
|
4834
4972
|
try {
|
|
4835
4973
|
const sessions = await sessionManager.listSessions(cli);
|
|
@@ -4874,6 +5012,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4874
5012
|
server.tool("session_set_active", "Set or clear the active session for a CLI; the active session is used when a request omits sessionId.", {
|
|
4875
5013
|
cli: SESSION_PROVIDER_ENUM.describe("CLI type (claude|codex|gemini|grok|mistral)"),
|
|
4876
5014
|
sessionId: z.string().nullable().describe("Session ID (null to clear)"),
|
|
5015
|
+
}, {
|
|
5016
|
+
title: "Set active session",
|
|
5017
|
+
readOnlyHint: false,
|
|
5018
|
+
destructiveHint: false,
|
|
5019
|
+
idempotentHint: true,
|
|
5020
|
+
openWorldHint: false,
|
|
4877
5021
|
}, async ({ cli, sessionId }) => {
|
|
4878
5022
|
try {
|
|
4879
5023
|
const success = await sessionManager.setActiveSession(cli, sessionId || null);
|
|
@@ -4911,6 +5055,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4911
5055
|
});
|
|
4912
5056
|
server.tool("session_delete", "Delete a gateway session record by ID (also removes any gateway-owned worktree attached to it).", {
|
|
4913
5057
|
sessionId: z.string().describe("Session ID"),
|
|
5058
|
+
}, {
|
|
5059
|
+
title: "Delete session",
|
|
5060
|
+
readOnlyHint: false,
|
|
5061
|
+
destructiveHint: true,
|
|
5062
|
+
idempotentHint: true,
|
|
5063
|
+
openWorldHint: false,
|
|
4914
5064
|
}, async ({ sessionId }) => {
|
|
4915
5065
|
try {
|
|
4916
5066
|
const session = await sessionManager.getSession(sessionId);
|
|
@@ -4952,6 +5102,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
4952
5102
|
});
|
|
4953
5103
|
server.tool("session_get", "Get one gateway session record by session ID, including recent request history when available.", {
|
|
4954
5104
|
sessionId: z.string().describe("Session ID"),
|
|
5105
|
+
}, {
|
|
5106
|
+
title: "Get session",
|
|
5107
|
+
readOnlyHint: true,
|
|
5108
|
+
destructiveHint: false,
|
|
5109
|
+
idempotentHint: true,
|
|
5110
|
+
openWorldHint: false,
|
|
4955
5111
|
}, async ({ sessionId }) => {
|
|
4956
5112
|
try {
|
|
4957
5113
|
const session = await sessionManager.getSession(sessionId);
|
|
@@ -5015,6 +5171,12 @@ export function createGatewayServer(deps = {}) {
|
|
|
5015
5171
|
});
|
|
5016
5172
|
server.tool("session_clear_all", "Delete all gateway session records, optionally scoped to one CLI.", {
|
|
5017
5173
|
cli: SESSION_PROVIDER_ENUM.optional().describe("CLI filter (claude|codex|gemini|grok|mistral)"),
|
|
5174
|
+
}, {
|
|
5175
|
+
title: "Clear sessions",
|
|
5176
|
+
readOnlyHint: false,
|
|
5177
|
+
destructiveHint: true,
|
|
5178
|
+
idempotentHint: true,
|
|
5179
|
+
openWorldHint: false,
|
|
5018
5180
|
}, async ({ cli }) => {
|
|
5019
5181
|
try {
|
|
5020
5182
|
const count = await sessionManager.clearAllSessions(cli);
|
package/dist/validation-tools.js
CHANGED
|
@@ -57,6 +57,12 @@ export function registerValidationTools(server, deps) {
|
|
|
57
57
|
judgeModel: providerSchema
|
|
58
58
|
.optional()
|
|
59
59
|
.describe("Optional provider to run an explicit judge synthesis job."),
|
|
60
|
+
}, {
|
|
61
|
+
title: "Multi-model validation",
|
|
62
|
+
readOnlyHint: false,
|
|
63
|
+
destructiveHint: true,
|
|
64
|
+
idempotentHint: false,
|
|
65
|
+
openWorldHint: true,
|
|
60
66
|
}, async ({ question, models, focus, judgeModel }) => textResponse({
|
|
61
67
|
success: true,
|
|
62
68
|
tool: "validate_with_models",
|
|
@@ -73,6 +79,12 @@ export function registerValidationTools(server, deps) {
|
|
|
73
79
|
answer: z.string().min(1).describe("Answer to review."),
|
|
74
80
|
question: z.string().optional().describe("Original question, if available."),
|
|
75
81
|
model: providerSchema.default("codex").describe("Provider to ask for the second opinion."),
|
|
82
|
+
}, {
|
|
83
|
+
title: "Second opinion",
|
|
84
|
+
readOnlyHint: false,
|
|
85
|
+
destructiveHint: true,
|
|
86
|
+
idempotentHint: false,
|
|
87
|
+
openWorldHint: true,
|
|
76
88
|
}, async ({ answer, question, model }) => textResponse({
|
|
77
89
|
success: true,
|
|
78
90
|
tool: "second_opinion",
|
|
@@ -87,6 +99,12 @@ export function registerValidationTools(server, deps) {
|
|
|
87
99
|
server.tool("compare_answers", "Summarize agreement/differences between caller-provided answers LOCALLY — does not call any provider.", {
|
|
88
100
|
question: z.string().min(1).describe("Question the answers respond to."),
|
|
89
101
|
answers: z.array(z.string().min(1)).min(2).describe("Two or more answers to compare."),
|
|
102
|
+
}, {
|
|
103
|
+
title: "Compare answers (local)",
|
|
104
|
+
readOnlyHint: true,
|
|
105
|
+
destructiveHint: false,
|
|
106
|
+
idempotentHint: true,
|
|
107
|
+
openWorldHint: false,
|
|
90
108
|
}, async ({ question, answers }) => textResponse({
|
|
91
109
|
success: true,
|
|
92
110
|
tool: "compare_answers",
|
|
@@ -106,6 +124,12 @@ export function registerValidationTools(server, deps) {
|
|
|
106
124
|
.default("normal")
|
|
107
125
|
.describe("How aggressively to review."),
|
|
108
126
|
models: providerListSchema.describe("Providers to ask for adversarial review."),
|
|
127
|
+
}, {
|
|
128
|
+
title: "Red-team review",
|
|
129
|
+
readOnlyHint: false,
|
|
130
|
+
destructiveHint: true,
|
|
131
|
+
idempotentHint: false,
|
|
132
|
+
openWorldHint: true,
|
|
109
133
|
}, async ({ content, riskLevel, models }) => textResponse({
|
|
110
134
|
success: true,
|
|
111
135
|
tool: "red_team_review",
|
|
@@ -120,6 +144,12 @@ export function registerValidationTools(server, deps) {
|
|
|
120
144
|
server.tool("consensus_check", "Ask provider CLIs whether they agree or disagree with a claim (starts validation jobs).", {
|
|
121
145
|
claim: z.string().min(1).describe("Claim to check across providers."),
|
|
122
146
|
models: providerListSchema.describe("Providers to ask for agreement or disagreement."),
|
|
147
|
+
}, {
|
|
148
|
+
title: "Consensus check",
|
|
149
|
+
readOnlyHint: false,
|
|
150
|
+
destructiveHint: true,
|
|
151
|
+
idempotentHint: false,
|
|
152
|
+
openWorldHint: true,
|
|
123
153
|
}, async ({ claim, models }) => textResponse({
|
|
124
154
|
success: true,
|
|
125
155
|
tool: "consensus_check",
|
|
@@ -133,6 +163,12 @@ export function registerValidationTools(server, deps) {
|
|
|
133
163
|
server.tool("ask_model", "Ask one provider CLI a question through the simplified validation surface (starts a validation job).", {
|
|
134
164
|
question: z.string().min(1).describe("Question for one provider."),
|
|
135
165
|
model: providerSchema.default("claude").describe("Provider to ask."),
|
|
166
|
+
}, {
|
|
167
|
+
title: "Ask one model",
|
|
168
|
+
readOnlyHint: false,
|
|
169
|
+
destructiveHint: true,
|
|
170
|
+
idempotentHint: false,
|
|
171
|
+
openWorldHint: true,
|
|
136
172
|
}, async ({ question, model }) => textResponse({
|
|
137
173
|
success: true,
|
|
138
174
|
tool: "ask_model",
|
|
@@ -150,6 +186,12 @@ export function registerValidationTools(server, deps) {
|
|
|
150
186
|
.min(1)
|
|
151
187
|
.describe("Terminal normalized provider results from job_result."),
|
|
152
188
|
judgeModel: providerSchema.default("codex").describe("Provider to run the judge synthesis."),
|
|
189
|
+
}, {
|
|
190
|
+
title: "Synthesize validation",
|
|
191
|
+
readOnlyHint: false,
|
|
192
|
+
destructiveHint: true,
|
|
193
|
+
idempotentHint: false,
|
|
194
|
+
openWorldHint: true,
|
|
153
195
|
}, async ({ question, providerResults, judgeModel }) => textResponse({
|
|
154
196
|
success: true,
|
|
155
197
|
tool: "synthesize_validation",
|
|
@@ -160,9 +202,21 @@ export function registerValidationTools(server, deps) {
|
|
|
160
202
|
judgeProvider: judgeModel,
|
|
161
203
|
}),
|
|
162
204
|
}));
|
|
163
|
-
server.tool("list_available_models", "List models and capabilities for every available provider CLI (takes no arguments; complements per-provider list_models).", {},
|
|
205
|
+
server.tool("list_available_models", "List models and capabilities for every available provider CLI (takes no arguments; complements per-provider list_models).", {}, {
|
|
206
|
+
title: "All provider models",
|
|
207
|
+
readOnlyHint: true,
|
|
208
|
+
destructiveHint: false,
|
|
209
|
+
idempotentHint: true,
|
|
210
|
+
openWorldHint: false,
|
|
211
|
+
}, async () => textResponse({ success: true, models: getAvailableCliInfo() }));
|
|
164
212
|
server.tool("job_status", "Check a VALIDATION job's status (jobs started by validate_with_models/ask_model/etc.) — distinct from llm_job_status, which tracks provider request jobs.", {
|
|
165
213
|
jobId: z.string().min(1).describe("Validation job ID."),
|
|
214
|
+
}, {
|
|
215
|
+
title: "Validation job status",
|
|
216
|
+
readOnlyHint: true,
|
|
217
|
+
destructiveHint: false,
|
|
218
|
+
idempotentHint: true,
|
|
219
|
+
openWorldHint: false,
|
|
166
220
|
}, async ({ jobId }) => {
|
|
167
221
|
const job = deps.asyncJobManager.getJobSnapshot(jobId);
|
|
168
222
|
if (!job) {
|
|
@@ -182,6 +236,12 @@ export function registerValidationTools(server, deps) {
|
|
|
182
236
|
.max(2000000)
|
|
183
237
|
.default(200000)
|
|
184
238
|
.describe("Maximum result size."),
|
|
239
|
+
}, {
|
|
240
|
+
title: "Validation job result",
|
|
241
|
+
readOnlyHint: true,
|
|
242
|
+
destructiveHint: false,
|
|
243
|
+
idempotentHint: true,
|
|
244
|
+
openWorldHint: false,
|
|
185
245
|
}, async ({ jobId, provider, maxChars }) => {
|
|
186
246
|
const result = deps.asyncJobManager.getJobResult(jobId, maxChars);
|
|
187
247
|
if (!result) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-cli-gateway",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "llm-cli-gateway",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.3.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-cli-gateway",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"mcpName": "io.github.verivus-oss/llm-cli-gateway",
|
|
5
5
|
"description": "MCP server providing unified access to Claude Code, Codex, Gemini, Grok, and Mistral Vibe CLIs with session management, retry logic, async job orchestration, durable job results, and cross-LLM validation.",
|
|
6
6
|
"license": "MIT",
|