Repository navigation
Description
What is the problem this feature would solve?
Over HTTP, an McpServer with McpProtocol.v2026_07_28 always advertises tools: { listChanged: true } in server/discover, so clients open a subscriptions/listen stream (Claude Code opens one on connect). A server whose tools never change has no way to say so.
On Cloudflare Workers this costs more than an idle stream:
- A Worker request can't wait on I/O that another request started, so a stateless server is built per request. A server built for one request can't deliver a list change to a subscription held by another, so the advertised capability can never be honored.
- Every connected client keeps a request open for it, with a keepalive every 15 seconds since fix: add keepalive for Mcp http subscriptions #8653.
- A subscription on a newly built server receives one
notifications/tools/list_changedright after its acknowledgement (McpServer: a subscription on a new server receivestools/list_changedfor the server's own tools #8918), so with a server per request, every subscription makes the client fetch the tool list again.
Reproduction (effect 4.0.2; main is the same):
import { Effect, Layer, Schema } from "effect"
import { McpProtocol, McpServer, Tool, Toolkit } from "effect/ai"
import { HttpRouter } from "effect/http"
const Kit = Toolkit.make(Tool.make("ping", { success: Schema.String }))
const App = McpServer.toolkit(Kit).pipe(
Layer.provide(Kit.toLayer({ ping: () => Effect.succeed("pong") })),
Layer.provide(McpServer.layerHttp({
name: "repro",
version: "1.0.0",
path: "/mcp",
protocols: [McpProtocol.v2026_07_28]
}))
)
const { handler } = HttpRouter.toWebHandler(App)POST /mcp with server/discover answers "capabilities":{"logging":{},"completions":{},"tools":{"listChanged":true}}.
What is the feature you are proposing to solve the problem?
An option on layerHttp, beside allowSessionTermination:
McpServer.layerHttp({
name: "server",
version: "1.0.0",
path: "/mcp",
protocols: [McpProtocol.v2026_07_28],
allowSubscriptions: false
})With it, discovery advertises listChanged: false and subscriptions/listen answers Method not found. Progress and log messages during a request still reach the client.
The 2026-07-28 binding already does both when installHandlers gets no sendNotification (discovery, subscriptions/listen). That binding uses sendNotification, markSubscriptionCancelled and terminateSubscription only for subscriptions, and the older bindings don't use them. Over HTTP they are always passed, because the transport supports notifications (McpServer.ts, RpcServer.ts). So the option could leave those three out. Turning off the transport's supportsNotifications instead would also drop progress and log messages, which the delivery loop sends to the requesting client (McpServer.ts).
What alternatives have you considered?
- Refusing
subscriptions/listenin a route in front of the MCP server: discovery still advertiseslistChanged: true, so the server contradicts itself. - Acknowledging the subscription and closing the stream at once: clients reconnect.
- Keeping the stream open, as 4.0.2 allows: it works, at the costs above.
Activity
lloydrichards commentedon Oct 8, 2026
allowSubscriptions: falsewould make sense for deployments that cannot support long-lived subscription streams but I’d distinguish that from servers whose tools never change, since those servers may still need resource or prompt subscriptions.MCP exposes separate capabilities for
tools.listChanged,prompts.listChanged,resources.listChanged, andresources.subscribe. That lets a server disable tool-list notifications while keeping other subscriptions available according to the MCP capabilities.For static tools, a capability override could be expressed:
Tools would remain callable, but discovery would advertise no tool-list notifications and subscriptions would exclude them. Resource and prompt subscriptions could remain enabled.
For the Worker deployment described here, the proposed global option expresses a broader restriction:
That would disable all subscription-related capability and reject
subscriptions/listen. Progress and logs would still reach clients through their originating request’s response stream, as MCP specifies. (see HTTP notification rules)Other SDKs provide precedents for both approaches in their current source branches:
tools.listChanged: false. Its subscription handler accepts only supported notification types and completes immediately if none are accepted. Source.subscriptions/listenhandler is registered. Source.Im leaning more towards the per-capability controls, then the global version but open to other opinions. Either way, discovery and subscription acceptance should use the same settings so the server only advertises notifications it can deliver.
rickyeescobar commentedon Oct 8, 2026
Hey Lloyd, thanks for your response and explanation. I think the per-capability controls are a good pick here.
lloydrichards commentedon Oct 8, 2026
effect-bot seems to have its own plans 🤷 If you find that you need more control with the per-capability version then you can reopen this issue and I'll try be faster on the PR
lloydrichards commentedon Oct 8, 2026
and if you have any other feedback or suggestions for the McpServer you can also ping me on Discord. The whole serverless-Cloudflare McpServer is pretty fresh so its good to hear about usage, successes and pain points