mobygate 0.7.1 → 0.7.2

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 CHANGED
@@ -4,6 +4,41 @@ All notable changes to mobygate are documented here. Format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version numbers are
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.7.2] — 2026-04-25
8
+
9
+ ### Fixed
10
+
11
+ - **"I can't use the tool 'grep' here because it isn't available" refusals**
12
+ in long-running tasks. Even with `allowedTools: ['mcp__mobygate__*']`
13
+ blocking everything except client-defined tools, the model retains
14
+ strong priors from training for Claude Code's built-ins (Bash, Grep,
15
+ Read, Edit, Glob, WebFetch, ToolSearch, etc.). When a task seemed to
16
+ call for one — e.g., "find all TODOs" → instinctive reach for Grep —
17
+ the model would attempt it, get blocked, refuse the task, and stop.
18
+ Instead of falling back to the available client tool (`searchFiles`,
19
+ `terminal`, etc.).
20
+
21
+ **Fix:** for any tool-enabled request, append a short system-prompt
22
+ block (~150 tokens) via the SDK's
23
+ `systemPrompt: { type: 'preset', preset: 'claude_code', append: ... }`
24
+ option. The append explicitly lists the available client tools and
25
+ states that Claude Code's built-ins are NOT in this environment.
26
+ Calibrated to be matter-of-fact ("here's the environment, work
27
+ within it") rather than over-restrictive — the model now uses
28
+ available tools or briefly says what's missing, instead of refusing
29
+ silently.
30
+
31
+ Applies to both `/v1/chat/completions` and `/v1/messages`.
32
+
33
+ ### Notes
34
+
35
+ - New helper: `buildToolUsageGuidance(tools)` in `lib/tool-bridge.js`
36
+ produces the append text from the OpenAI-shape tool array. The
37
+ Anthropic surface translates its tool defs to OpenAI shape for the
38
+ bridge already, so the helper takes one input shape across both.
39
+ - Per-request token overhead: ~150 tokens, only when `tools` is non-empty.
40
+ No effect on text-only chat or non-tool requests.
41
+
7
42
  ## [0.7.1] — 2026-04-24
8
43
 
9
44
  Fixes a meaningful token-burn issue for clients that don't pass session
@@ -218,6 +218,50 @@ export function hasToolUse(assistantMessage) {
218
218
  // Tool results (OpenAI tool messages → Anthropic tool_result content blocks)
219
219
  // ---------------------------------------------------------------------------
220
220
 
221
+ // ---------------------------------------------------------------------------
222
+ // Strict-tool guidance (system-prompt append for tool-enabled requests)
223
+ // ---------------------------------------------------------------------------
224
+ // Even with native MCP registration + a tight `allowedTools` allowlist, the
225
+ // model retains strong priors for Claude Code's built-in tools (Bash, Read,
226
+ // Edit, Grep, Glob, WebFetch, ToolSearch, etc.) from training. When a task
227
+ // seems to need one of those, the model reaches for it, gets blocked by
228
+ // `allowedTools`, says "I can't use the tool 'grep' here because it isn't
229
+ // available," and gives up — instead of falling back to the available
230
+ // client-defined tools. We saw this in production OpenClaw use.
231
+ //
232
+ // The fix: append a short, explicit guidance block to Claude Code's system
233
+ // prompt (via `systemPrompt: { type: 'preset', preset: 'claude_code', append: ... }`)
234
+ // telling the model exactly which tools are available and that built-ins
235
+ // are NOT in this environment. The positive list reinforces what the model
236
+ // already sees via MCP registration; the negative list shuts down the
237
+ // trained-in instinct to reach for built-ins.
238
+ //
239
+ // Calibration matters: too directive and the model becomes over-conservative
240
+ // and refuses legitimate work. We aim for matter-of-fact "here's the
241
+ // environment, work within it" rather than threatening prohibition.
242
+
243
+ const KNOWN_BUILTINS = 'Bash, Read, Edit, Write, Grep, Glob, NotebookEdit, WebFetch, WebSearch, Task, ToolSearch';
244
+
245
+ export function buildToolUsageGuidance(openaiTools) {
246
+ if (!Array.isArray(openaiTools) || openaiTools.length === 0) return null;
247
+ const names = [];
248
+ for (const t of openaiTools) {
249
+ if (t?.type !== 'function' || !t.function?.name) continue;
250
+ names.push(t.function.name);
251
+ }
252
+ if (names.length === 0) return null;
253
+
254
+ return [
255
+ 'Tool environment: this session is running through a proxy that exposes only the client-defined tools listed below. Claude Code\'s default built-in tools',
256
+ `(${KNOWN_BUILTINS}, etc.) are NOT available in this environment and cannot be invoked — calls to them will fail.`,
257
+ '',
258
+ 'Available tools:',
259
+ ...names.map((n) => ` - ${n}`),
260
+ '',
261
+ 'If a task seems to require a built-in tool that isn\'t in this list, accomplish what you can with the available tools and briefly note what\'s missing — do not refuse silently or claim you have no tools.',
262
+ ].join('\n');
263
+ }
264
+
221
265
  /**
222
266
  * Format OpenAI role:'tool' messages as a single user-readable text
223
267
  * block to splice into a resumed prompt.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobygate",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "OpenAI-compatible local proxy for Claude Max. The Möbius-strip gateway: OpenAI shape in, Claude Max out.",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -55,6 +55,7 @@ import { loadSessions, saveSessions, flushSessionsNow } from './lib/session-stor
55
55
  import { LOGS_DIR } from './lib/config.js';
56
56
  import {
57
57
  buildClientToolsServer,
58
+ buildToolUsageGuidance,
58
59
  extractToolUses,
59
60
  hasToolUse,
60
61
  toolMessagesToText,
@@ -402,6 +403,12 @@ async function handleStreaming(req, res, body, requestId, sessionKey) {
402
403
  // Build the in-process MCP server exposing client tools to the SDK.
403
404
  // null when toolsEnabled is false (or all tools are malformed).
404
405
  const clientToolsServer = toolsEnabled ? buildClientToolsServer(body.tools) : null;
406
+ // System-prompt append: tells the model exactly which tools are
407
+ // available and that Claude Code's built-ins (Bash, Grep, Read, etc.)
408
+ // are NOT in this environment. Without this, the model trained-in
409
+ // priors lead it to call Grep/Bash, get blocked by allowedTools, and
410
+ // refuse the task instead of falling back to client tools. ~150 tokens.
411
+ const toolsGuidance = clientToolsServer ? buildToolUsageGuidance(body.tools) : null;
405
412
  if (images.length) console.log(` [multimodal] ${images.length} image block(s)`);
406
413
  if (toolsEnabled) console.log(` [tools] ${body.tools.length} client tool(s) registered as MCP`);
407
414
 
@@ -458,6 +465,7 @@ async function handleStreaming(req, res, body, requestId, sessionKey) {
458
465
  ? {
459
466
  mcpServers: { [MCP_SERVER_NAME]: clientToolsServer },
460
467
  allowedTools: [`${MCP_TOOL_PREFIX}*`],
468
+ systemPrompt: { type: 'preset', preset: 'claude_code', append: toolsGuidance },
461
469
  }
462
470
  : toolsEnabled
463
471
  // Tools were requested but none were valid — disable all tools.
@@ -620,6 +628,7 @@ async function handleNonStreaming(res, body, requestId, sessionKey) {
620
628
  const prompt = buildQueryPrompt(promptText, images);
621
629
  const model = resolveModel(body.model);
622
630
  const clientToolsServer = toolsEnabled ? buildClientToolsServer(body.tools) : null;
631
+ const toolsGuidance = clientToolsServer ? buildToolUsageGuidance(body.tools) : null;
623
632
  if (images.length) console.log(` [multimodal] ${images.length} image block(s)`);
624
633
  if (toolsEnabled) console.log(` [tools] ${body.tools.length} client tool(s) registered as MCP`);
625
634
 
@@ -656,6 +665,7 @@ async function handleNonStreaming(res, body, requestId, sessionKey) {
656
665
  ? {
657
666
  mcpServers: { [MCP_SERVER_NAME]: clientToolsServer },
658
667
  allowedTools: [`${MCP_TOOL_PREFIX}*`],
668
+ systemPrompt: { type: 'preset', preset: 'claude_code', append: toolsGuidance },
659
669
  }
660
670
  : toolsEnabled
661
671
  ? { allowedTools: [] }
@@ -806,6 +816,7 @@ async function handleAnthropicNonStreaming(res, body, requestId, sessionKey) {
806
816
  }))
807
817
  : null;
808
818
  const clientToolsServer = toolsForBridge ? buildClientToolsServer(toolsForBridge) : null;
819
+ const toolsGuidance = clientToolsServer ? buildToolUsageGuidance(toolsForBridge) : null;
809
820
 
810
821
  if (images.length) console.log(` [multimodal] ${images.length} image block(s)`);
811
822
  if (toolsEnabled) console.log(` [tools] ${body.tools.length} client tool(s) registered as MCP`);
@@ -844,6 +855,7 @@ async function handleAnthropicNonStreaming(res, body, requestId, sessionKey) {
844
855
  ? {
845
856
  mcpServers: { [MCP_SERVER_NAME]: clientToolsServer },
846
857
  allowedTools: [`${MCP_TOOL_PREFIX}*`],
858
+ systemPrompt: { type: 'preset', preset: 'claude_code', append: toolsGuidance },
847
859
  }
848
860
  : toolsEnabled
849
861
  ? { allowedTools: [] }
@@ -948,6 +960,7 @@ async function handleAnthropicStreaming(req, res, body, requestId, sessionKey) {
948
960
  }))
949
961
  : null;
950
962
  const clientToolsServer = toolsForBridge ? buildClientToolsServer(toolsForBridge) : null;
963
+ const toolsGuidance = clientToolsServer ? buildToolUsageGuidance(toolsForBridge) : null;
951
964
 
952
965
  if (images.length) console.log(` [multimodal] ${images.length} image block(s)`);
953
966
  if (toolsEnabled) console.log(` [tools] ${body.tools.length} client tool(s) registered as MCP`);
@@ -1004,6 +1017,7 @@ async function handleAnthropicStreaming(req, res, body, requestId, sessionKey) {
1004
1017
  ? {
1005
1018
  mcpServers: { [MCP_SERVER_NAME]: clientToolsServer },
1006
1019
  allowedTools: [`${MCP_TOOL_PREFIX}*`],
1020
+ systemPrompt: { type: 'preset', preset: 'claude_code', append: toolsGuidance },
1007
1021
  }
1008
1022
  : toolsEnabled
1009
1023
  ? { allowedTools: [] }