From 391af68e9e01ca81afef3f0c2a20d5d2f0f8a080 Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 16 Jul 2026 09:26:19 +0700 Subject: [PATCH] docs(mcp): explain response budget guardrails --- README.md | 10 ++++++++++ gitnexus/test/unit/server.test.ts | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/README.md b/README.md index 907bc29843..813c34c2c9 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,15 @@ codex plugin marketplace add abhigyanpatwari/GitNexus +
+MCP response budgets + +The `query`, `context`, and `impact` tools accept an optional positive-integer `maxTokens` argument. It bounds the complete formatted MCP response, including hints and error text, using a deterministic four-UTF-8-bytes-per-token estimate. When truncation is required, the response ends with `…` and remains valid UTF-8. + +Set `GITNEXUS_MCP_DEFAULT_MAX_TOKENS` to apply the same guardrail when callers do not send `maxTokens`. An explicit tool argument takes precedence. Leaving both unset preserves the existing response byte-for-byte; this is a transport guardrail, not semantic pagination or an exact model-specific tokenizer limit. + +
+ ## CLI Reference Everyday commands: @@ -450,6 +459,7 @@ Most `analyze` knobs are also CLI flags (`--workers`, `--worker-timeout`, `--max | `GITNEXUS_CHUNK_BYTE_BUDGET` | `2097152` (2 MB) | Chunk boundary used for cache-key composition and dispatch. Smaller = finer-grained cache hits but more dispatch overhead. | Tuning incremental-analyze cache behavior on monorepos. | | `GITNEXUS_NO_GITIGNORE` | unset | When set, skips `.gitignore` parsing. `.gitnexusignore` is still honored. | Indexing a repo whose `.gitignore` excludes files you actually want indexed (e.g., generated code committed for cross-repo lookup). | | `GITNEXUS_SKIP_OPTIONAL_GRAMMARS` | unset | When `=1` strictly, skips the vendored grammar materialize for `tree-sitter-dart`, `tree-sitter-proto`, `tree-sitter-swift`, and `tree-sitter-kotlin` at install time (and the Dart/Proto source builds). Those four won't be parsed; the install still succeeds. | Installing on a host without a C++ toolchain or where the vendored prebuilds don't match; willing to skip Dart/Proto/Swift/Kotlin parsing. | +| `GITNEXUS_MCP_DEFAULT_MAX_TOKENS` | unset | Default positive-integer response budget for MCP `query`, `context`, and `impact`, estimated at four UTF-8 bytes per token. Explicit `maxTokens` wins. | Long MCP responses consume too much model context and callers cannot reliably add a per-request budget. | diff --git a/gitnexus/test/unit/server.test.ts b/gitnexus/test/unit/server.test.ts index 84850ffa41..8160f56266 100644 --- a/gitnexus/test/unit/server.test.ts +++ b/gitnexus/test/unit/server.test.ts @@ -212,6 +212,23 @@ describe('MCP output budgets', () => { expect(backend.callTool).not.toHaveBeenCalled(); }); + it('rejects an invalid environment default before backend execution', async () => { + const previous = process.env.GITNEXUS_MCP_DEFAULT_MAX_TOKENS; + process.env.GITNEXUS_MCP_DEFAULT_MAX_TOKENS = 'invalid'; + try { + const backend = createMockBackend(); + const { text, isError } = await callToolThroughServer(backend, 'query', { + search_query: 'auth', + }); + expect(isError).toBe(true); + expect(text).toMatch(/GITNEXUS_MCP_DEFAULT_MAX_TOKENS.*positive integer/i); + expect(backend.callTool).not.toHaveBeenCalled(); + } finally { + if (previous === undefined) delete process.env.GITNEXUS_MCP_DEFAULT_MAX_TOKENS; + else process.env.GITNEXUS_MCP_DEFAULT_MAX_TOKENS = previous; + } + }); + it('applies a valid budget to backend error text', async () => { const backend = createMockBackend({ callTool: vi.fn().mockRejectedValue(new Error('😀'.repeat(100))),