• Stefy Lanza (nextime / spora )'s avatar
    Fix Claude CLI mode: transport, real tool calling via MCP shim, dashboard toggle; bump to 0.99.85 · 2c3e2d8e
    Stefy Lanza (nextime / spora ) authored
    CLI mode never worked. Four independent defects, any one fatal:
    
    - The stdin frame used {"type":"user_message",...}; the CLI expects the
      Anthropic envelope {"type":"user","message":{...}} and silently discards
      anything else, so requests produced no output at all. With
      --input-format stream-json the CLI also ignores a prompt passed as an argv
      positional, so it must go over stdin.
    - _handle_cli_streaming_request() took no tools parameter but was called with
      tools=..., raising TypeError on every streaming request.
    - The event parser dispatched on a top-level content_block_delta/message_stop,
      but the CLI wraps Anthropic events as {"type":"stream_event","event":{...}},
      so those branches were dead code.
    - Tool definitions were passed to --tools, which only selects built-in tools by
      name; a JSON blob there registers nothing.
    
    Tools now reach the model through an MCP stdio shim (claude_mcp_shim.py), which
    advertises the caller's definitions via tools/list. It never executes: in the
    OpenAI protocol the client runs tools, so the first tool_use ends the turn and
    is returned as tool_calls. Past calls/results are replayed as text since each
    request is a fresh session, with a system-prompt directive so the model trusts
    a replayed result instead of re-calling.
    
    The shim forces one deviation from the intended flag set: --disallowedTools
    'mcp__*' is a blanket deny that also blocks the shim, and deny beats
    --allowedTools, so it cannot be kept alongside tool calling. It is retained
    when no tools are requested; with tools, --strict-mcp-config preserves the same
    isolation by loading only our config and ignoring the host's MCP servers.
    
    Consequences of the event model (one assistant event per content block, not per
    message): break on message_delta stop_reason == 'tool_use' rather than the first
    tool_use, or parallel calls are dropped; dedupe text/arguments across the delta
    and assistant paths; and emit sequential tool_call indices, since content-block
    indices count text/thinking blocks and leave holes that break client-side
    accumulation.
    
    System messages now go to --system-prompt instead of being inlined as user text
    the model could ignore. Non-streaming reports real token usage instead of zeros.
    
    Also fix the dashboard toggle that made this unreachable: providers.py imported
    _claude_cli_mode from startup, binding a copy of False at import time, while
    detection only ever wrote app_state['_claude_cli_mode']. The template therefore
    always received False and the use_cli_mode checkbox never rendered. Inject the
    value through init() like every other route global, and drop the orphaned
    startup global.
    Co-Authored-By: 's avatarClaude Opus 4.8 (1M context) <noreply@anthropic.com>
    2c3e2d8e
main.py 25.3 KB