build: build the plugin-side client that dials out to the new endpoint and keeps the connection alive #5
Loading…
Reference in a new issue
No description provided.
Delete branch "build/78726254"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
build the plugin-side client that dials out to the new endpoint and keeps the connection alive
This task is one step of my plan: replace the loopback MCP dial-out with a persistent outbound connection from the vault
Result of earlier step 1 (read: read the current connection model in connor/service.py, connor/obsidian.py, and ):
Done reading. Writing up the notes and the spec now.
Reading notes: the connection model today
The one-line diagnosis. Every live vault operation in the service ends in an outbound HTTP dial to an address the plugin minted for its own loopback, and Obsidian runs on Joel's Mac, so that dial can never succeed from the fedora box. My earlier evidence still stands: curl to port 27125 from this box is refused, and the 503 text in chat is exactly the
_connectrewrite. Nothing in the read path differs from the write path.Service side, connor/obsidian.py.
mcp_url()atconnor/obsidian.py:181-188resolves the address from the credential vault keyobsidian.mcp_url.<name>, then the single key, then the env var.set_mcp_url()at 191-214 is the owner's self-serve writer behindPOST /vault/connect.Clientat 217-286 is a streamable-HTTP MCP client. Each call is a freshinitializePOST plusnotifications/initializedplustools/call, overurllib, 20-second timeout fromMCP_TIMEOUTat line 49. Any network error becomesVaultOfflineat 243-245._connect(name, client=None)at 289-306 is the single chokepoint all five operations use. It is also the testResult of earlier step 2 (backend: build the service-side persistent-connection endpoint per the spec from the read):
Done and green: the full suite went from 919 to 930 tests, OK, with all eleven new ones red first and green after. Here are my finishing notes to you.
The design I built, and the one assumption in it
My read step's notes survived in memory but the spec text itself did not, so I built to the constraints the notes established and I'm stating the call here for you to overrule. Stdlib has no websocket server and the gate contract forbids adding one, so the link is the shape
/eventsalready proves through the edge. The plugin opensGET /vault/link?vault=<name>and holds it as a server-sent-events stream. Every tool call Connor makes becomes onecallframe down that stream, carrying an id, the tool name, and the arguments. The plugin answers each frame with a shortPOST /vault/link/replycarrying the vault, the id, and the MCP-shapedtools/callresult. Same five tools, same result shape, so the plugin's tool handlers from the PR #80 wire contract can be reused verbatim. Only the direction of the first packet changed. If you'd rather have a real websocket, the registry andLinkclass don't care how the bytes travel, and only the two route bodies in service.py would changBuilt by Connor's frontend backburner (dispatch
78726254) in an isolated clone; shipped deterministically byship.py. The full build notes were spoken in conversation and stored in memory (agent-dispatch:78726254). Review is the gate.cfae4e50b7to2747811778