{"slug": "the-agent-protocols-stopped-at-the-client-let-s-talk-about-the-server-half", "title": "The Agent Protocols Stopped at the Client — Let's Talk About the Server Half", "summary": "A developer has released Franca, an open-source Java 21 library published to Maven Central that implements the server side of agent wire protocols, terminating OpenAI Responses and Anthropic Messages dialects inside a developer's own service. The library, hosted via Spring Boot 3, uses pluggable adapters and drivers to let applications own the server half of the agent stack rather than relying on vendor-hosted implementations or client-side prompt bundles.", "body_md": "Every few weeks another \"skills\" announcement lands. Prompt bundles, client-side toolkits, SDK-level orchestration patterns. I read them the way you probably do — genuinely interested, quietly uneasy. The industry's channel for developer agency in the agent stack has ended up almost entirely on the client side. Which is strange, if you think about where capabilities actually have to run: against your data, your environment, your governance. Server-side.\n\nThis post is about a gap that sits precisely there, and an open-source Java library I shipped to Maven Central to fill it.\n\nOpenAI Responses and Anthropic Messages are becoming the wire protocols for agents. Look at what they specify on the client's half and it's genuinely impressive: conversation shape, tool declarations, reasoning knobs, structured events, streaming semantics. A client SDK that speaks one of these can talk to anything that speaks it back.\n\nNow look for the server's half. Which function actually answers a declared tool type? Where does it run? Should there be a retrieval pass before the answer? What can the server inject, observe, or veto?\n\nThe protocol is silent on all of it. And silence has a consequence: **whoever implements the server owns your application layer.**\n\nToday the server half lives in one of three places:\n\n`/v1/responses` too — but as a raw text-in/text-out endpoint. They say the words of the dialect without the agent semantics behind them.\nSo the division of labor we all claim to want — model capability belongs to model vendors, application capability belongs to application developers, the wire protocol is the border — only got implemented on one side. On the other side, your options are: rent the vendor's server half, route through a translator, or accept a very smart autocomplete.\n\nThe industry's answer to the remaining developer agency is skills. Client-side prompt bundles. That's what you get when the server belongs to someone else.\n\nFranca is my attempt at the missing piece: a Java 21 library that terminates both dialects inside your own service, and turns the server half of the protocol into an ordinary development surface.\n\nThe bottom boundary is any inference backend — currently an OpenAI-compatible chat driver, more via pluggable drivers. The top boundary is your `/v1/responses` and `/v1/messages` endpoints. The middle is yours.\n\nDependencies (Spring Boot 3 host):\n\n```\n<dependencyManagement>\n  <dependencies>\n    <dependency>\n      <groupId>io.github.franca-protocol</groupId>\n      <artifactId>franca-bom</artifactId>\n      <version>0.18.0</version>\n      <type>pom</type>\n      <scope>import</scope>\n    </dependency>\n  </dependencies>\n</dependencyManagement>\n\n<dependencies>\n  <dependency>\n    <groupId>io.github.franca-protocol</groupId>\n    <artifactId>franca-spring-boot</artifactId>\n  </dependency>\n  <dependency>\n    <groupId>io.github.franca-protocol</groupId>\n    <artifactId>franca-spring-tools-local</artifactId>\n  </dependency>\n</dependencies>\n```\n\nConfiguration is three blocks — adapters decide which dialects you terminate, drivers decide how to speak to backends, routes map aliases to targets:\n\n```\nfranca:\n  adapters:\n    - name: responses\n      type: io.franca.adapter.responses.ResponsesAdapter\n  drivers:\n    - name: openai-chat\n      type: io.franca.driver.chat.OpenAiChatDriver\n  routes:\n    - alias: gpt-5.6\n      target:\n        driver: openai-chat\n        address: https://your-openai-compatible-backend\n        model: your-model\n        metadata:\n          api-key: ${BACKEND_API_KEY}\n```\n\nWith that, your service *is* a standard OpenAI-compatible server. Clients point `base_url` at you and change nothing else.\n\nBoth boundaries are pluggable, deliberately. The two dialect adapters and the chat driver are what ship today, but neither boundary is sealed: a new dialect is a new adapter implementation, a backend that isn't plain OpenAI-compatible chat is a new driver — ordinary SPI implementations, not forks. That matters because backends churn faster than protocols; you should be able to follow the backend without touching your capability code.\n\nThe hosted-tools asymmetry is my favorite part. The client declares a capability; the server decides what serves it.\n\nA server-side function is one bean:\n\n```\n@Component\npublic class HashToolHandler implements HostedToolHandler {\n\n    @Override\n    public String toolType() {\n        return \"hash\";\n    }\n\n    @Override\n    public Mono<String> execute(String callId, String arguments) {\n        // arguments: the JSON the model produced for this call.\n        // Return a JSON string; it is fed back to the model for the next turn.\n    }\n}\n```\n\nThe client declares only the type — no function body, no schema:\n\n```\ncurl -X POST http://localhost:8080/v1/responses \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"gpt-5.6\",\n    \"tools\": [{\"type\": \"hash\"}],\n    \"input\": \"Compute the SHA-256 digest of 'hello franca'.\"\n  }'\n```\n\nWhat actually answers `type: \"hash\"` is entirely your call: that local bean, any MCP endpoint (namespaced, with model-visible aliases), or a fleet of cloud-vendor-hosted functions — Alibaba Cloud Function Compute, AWS Lambda, and friends. The model never learns where the code runs. The orchestration in between — call, execute, feed back, loop — is collapsed by the server into one continuous Responses event stream.\n\nOne SPI detail worth flagging, because it's where per-request context enters. The minimal form above is all a stateless tool needs, but the interface carries more when you want it: a default overload hands your handler the request's `metadata` field (session ids, trace ids, billing tags — whatever the caller sent), and a streaming form lets a tool emit progress events while it runs. The metadata channel is the interesting one for this argument: it's exactly how per-user context reaches server-side tool execution — from the request to your handler, inside your process, never through a third party.\n\nNotice what the client did *not* do. It installed nothing. No environment to set up, no dependencies, no credentials granted — even in the case where the tool is calling services that already belong to you. That's the part of the skills model I could never reconcile: installing a skill means installing its world — dependencies, environment, authorization — on the client, per client, even when the capabilities live on your side of the wall. A hosted tool keeps the capability where your data, your credentials, and your permissions already live; the client's entire footprint is one declared type. In that mode you can add a platform function without changing a line of code — existing clients just start declaring it.\n\nFor private deployments this is the only shape that works: internal data sources can't be shipped to a vendor's hosted `web_search`, and you don't want credentials distributed to every client that might call you.\n\nAnd note what bring-your-own-tools via the platform actually costs: to let the platform's runtime call your MCP endpoint you either hand over your credentials or open your network to it — and the first time your tool needs per-user authorization, your users' auth tokens are passing through a third party. The privacy question isn't only where the data lives. It's who holds the keys while the model works.\n\nThe declaration syntax stays on the wire untouched. The *implementation right* comes back to the server. For deployments that are heavy server-side and deliberately thin client-side, that's the whole game.\n\nThe one place I've deliberately stuck my neck out.\n\nIn the protocol, `reasoning` is a knob for how deeply the model thinks. Franca reinterprets it as a slot for server-side thinking strategies (a `ThinkingPhase` SPI). Request `reasoning.effort: \"research\"`, and the server runs a retrieval pass through hosted tools first — streamed back as reasoning events so the client watches it look things up — then hands the findings to the answering pass. The original request context stays **byte-identical**: retrieval never pollutes your conversation history, which is the part of deep-research implementations that quietly rots.\n\nThere's a second motivation that took me longer to articulate. Planned, fixed-shape chains — the LangChain-style workflows everyone keeps rebuilding as client-side orchestration — are a genuinely strong need that is architecturally homeless. A chain isn't conversation and isn't a tool; it's *how the server should think through the task*. The thinking region is where it belongs: a phase can perceive (call hosted tools, gather signals) and guide the answering pass, and none of it lands in the visible context.\n\nAnd because a phase is an ordinary artifact, the ecosystem story inverts. Skills distribute prompt bundles to the client; thinking phases distribute behavior to the server. Write one once, publish it, and another deployment enables it by importing the artifact and filling in a key in the reasoning parameter.\n\nReinterpreting `reasoning.effort` as a server behavior switch is not something the spec says. There is a \"controversial decisions\" doc in the repository defending it, and I'd genuinely like pushback on it.\n\nThe boring-but-necessary sibling: backends spell \"thinking\" differently (`reasoning-effort`, `thinking-disabled`, `enable-thinking`, …). Franca normalizes this per route via `thinking-style` and `effort-value-map`, so heterogeneous backends get one consistent semantic.\n\nBecause gateways answer a different question. A gateway answers \"many upstreams, one entry point, unified billing\" — traffic in the front door, out the back. Franca answers \"where does the protocol terminate, and who gets to extend it\":\n\nA gateway is a new member of your architecture. A library is a part of your service. They solve access and capability respectively, and there is room for both — but only one of them hands you back the server half of the protocol.\n\nThe last option to close out is doing it yourself — the wire formats are public, after all. The honest tally of \"yourself\" is: extensible platform functions, two dialects' event streams, the tool call/execute/feed-back loop, protocol compatibility and reasoning normalization across heterogeneous backends, and the plumbing that keeps all of it streaming. None of it is your product. It's the server half of the protocol, rebuilt from scratch — which is the work this library packages, once.\n\n`io.github.franca-protocol:franca-bom:0.18.0`\n`franca-example` ships with both dialects, MCP tools, and the retrieval phase enabled\nModel capability belongs to the vendors. Application capability should belong to you. The protocol was supposed to be the border — it just stopped halfway. Franca is a attempt to finish drawing it.\n\nIf you think the server-side gap looks different from where you sit, or that skills and client-side agency are enough — I'd like to hear the argument.", "url": "https://wpnews.pro/news/the-agent-protocols-stopped-at-the-client-let-s-talk-about-the-server-half", "canonical_source": "https://dev.to/sorenvale/the-agent-protocols-stopped-at-the-client-lets-talk-about-the-server-half-48ja", "published_at": "2026-09-15 11:24:25+00:00", "updated_at": "2026-09-15 11:43:27.527833+00:00", "lang": "en", "topics": ["ai-agents", "ai-infrastructure", "developer-tools", "ai-tools", "large-language-models"], "entities": ["Franca", "Maven Central", "OpenAI", "Anthropic", "Spring Boot", "Java"], "alternates": {"html": "https://wpnews.pro/news/the-agent-protocols-stopped-at-the-client-let-s-talk-about-the-server-half", "markdown": "https://wpnews.pro/news/the-agent-protocols-stopped-at-the-client-let-s-talk-about-the-server-half.md", "text": "https://wpnews.pro/news/the-agent-protocols-stopped-at-the-client-let-s-talk-about-the-server-half.txt", "jsonld": "https://wpnews.pro/news/the-agent-protocols-stopped-at-the-client-let-s-talk-about-the-server-half.jsonld"}}