Skip to content

Add real-time WebSocket tools - #6

Merged
arjunjain merged 1 commit into
mainfrom
feature/websocket
Aug 26, 2026
Merged

arjunjain merged 1 commit into
mainfrom
feature/websocket

Conversation

@arjunjain

@arjunjain arjunjain commented Aug 26, 2026 •

Copy link
Copy Markdown
Collaborator

Syncs this client with python-client 0.3.0 (PR #27), which added the real-time WebSocket service.

What's here

Query management — three REST endpoints, verified against the OpenAPI 3.1 spec:

Endpoint Method
websocket/register POST (query params, no body)
websocket/fetch GET
websocket/delete DELETE

Register once, stream many times. A registered query has no date or paging filters — it matches news as it is published. Registering identical filters twice answers 409 with the existing registration_id.

Failure model

Per the spec, the handshake is always accepted; a refused connection is then closed with code 1008 carrying one of three reasons — invalid credentials or registration not found, api limit reached, or device limit reached (>5 devices on one registration_id). Those raise the new typed auth error and are never retried. Every other close code, including 1013 (send timeout), is transient.

⚠️ Each delivered article consumes 1 API credit per connected device.

MCP specifics — why stream_news is bounded

MCP tools are request/response, so an open-ended stream cannot be a tool. stream_news collects until either max_articles arrive or wait_seconds elapses, whichever comes first, and always returns within wait_seconds. Every reply states why it stopped:

stopped_because Meaning
max_articles Hit the cap — there may be more
timeout Window elapsed; 0 means the feed was quiet
connection_closed Server ended the feed

A test caught the tool reporting timeout when the server had actually closed the feed — which would tell the model "nothing arrived" when the truth was "the feed ended". Fixed.

Tool annotations

register_realtime_query is annotated as creating, delete_realtime_query as destructive; the rest stay read-only. Previously every tool shared one read-only constant.

Cost guidance

registration_id is constrained to the spec's 32 characters so the model cannot invent one, and the stream_news docstring warns that each delivered article costs 1 API credit per connected device (max 5 devices per registration) — an LLM left to itself would happily burn credits on a broad query.

Also regenerated uv.lock for the new websockets dep; CI runs uv sync --frozen and would have failed without it.

184 tests pass, ruff clean.

Sync with python-client 0.3.0. Adds four tools: register_realtime_query,
list_realtime_queries, delete_realtime_query, and stream_news.

MCP tools are request/response, so stream_news is deliberately bounded —
it collects until max_articles arrive or wait_seconds elapses, whichever
comes first, and always returns within wait_seconds. Every reply states
stopped_because (max_articles | timeout | connection_closed) so the model
can tell a hit cap from a quiet feed. Call it again to keep listening.

register and delete are annotated as mutating (the latter destructive)
since they create and remove server-side state; the rest stay read-only.

fetch() now carries an HTTP method. registration_id is constrained to the
spec's 32 characters, and the stream_news docstring warns that each
delivered article costs 1 API credit per connected device, with at most
5 devices per registration.
@arjunjain
arjunjain merged commit 8505791 into main Aug 26, 2026
1 check passed
@arjunjain
arjunjain deleted the feature/websocket branch August 26, 2026 14: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.

1 participant