release: 0.1.6 — relay notifications/progress to MCP client
Long-running upstream skills emit MCP notifications/progress on the SSE stream. Without forwarding, the gateway swallows them and the MCP client (Claude Code, Cursor) aborts the tools/call on its idle timer after ~60s — even though the HTTP stream stays warm. Gateway now passes the SDK's extra.sendNotification through to UpstreamAgent.callTool. Any non-response frame with a method but no id becomes a notification on the client side. Each tools/call carries a progressToken (client-supplied or request-id fallback) so the client can correlate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -99,14 +99,22 @@ export class UpstreamAgent {
|
||||
async callTool(
|
||||
name: string,
|
||||
arguments_: Record<string, unknown>,
|
||||
opts: {
|
||||
onNotification?: (notif: {
|
||||
method: string;
|
||||
params?: Record<string, unknown>;
|
||||
}) => void | Promise<void>;
|
||||
progressToken?: string | number;
|
||||
} = {},
|
||||
): Promise<UpstreamCallResult> {
|
||||
const meta: Record<string, unknown> = {};
|
||||
if (this.cfg.cpJwt) meta.cp_jwt = this.cfg.cpJwt;
|
||||
if (this.cfg.cpUrl) meta.cp_url = this.cfg.cpUrl;
|
||||
if (this.cfg.bucket) meta.bucket = this.cfg.bucket;
|
||||
if (opts.progressToken !== undefined) meta.progressToken = opts.progressToken;
|
||||
const params: Record<string, unknown> = { name, arguments: arguments_ };
|
||||
if (Object.keys(meta).length > 0) params._meta = meta;
|
||||
return this.streamingCallTool(params);
|
||||
return this.streamingCallTool(params, opts.onNotification);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,6 +130,10 @@ export class UpstreamAgent {
|
||||
*/
|
||||
private async streamingCallTool(
|
||||
params: Record<string, unknown>,
|
||||
onNotification?: (notif: {
|
||||
method: string;
|
||||
params?: Record<string, unknown>;
|
||||
}) => void | Promise<void>,
|
||||
): Promise<UpstreamCallResult> {
|
||||
const id = _nextId++;
|
||||
const headers: Record<string, string> = {
|
||||
@@ -188,8 +200,21 @@ export class UpstreamAgent {
|
||||
}
|
||||
return msg.result as UpstreamCallResult;
|
||||
}
|
||||
// else: server→client request (elicitation/create) or another
|
||||
// frame; ignore — elicit relaying not wired yet.
|
||||
// Otherwise it's a server→client message. Notifications
|
||||
// (no id) → forward to the MCP client so long-running
|
||||
// skills keep the client's tool-call timer alive. Requests
|
||||
// (has id, has method) — e.g. elicitation/create — are
|
||||
// not relayed yet, so they will time out upstream.
|
||||
if (msg && typeof msg.method === "string" && !("id" in msg) && onNotification) {
|
||||
try {
|
||||
await onNotification({
|
||||
method: msg.method,
|
||||
params: msg.params as Record<string, unknown> | undefined,
|
||||
});
|
||||
} catch {
|
||||
// Best-effort; never break the stream on a notify error.
|
||||
}
|
||||
}
|
||||
}
|
||||
sep = buf.indexOf("\n\n");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user