Skip to content

Commit cc0fd15

Browse files
Transport logging
1 parent d19681c commit cc0fd15

File tree

2 files changed

+60
-32
lines changed

2 files changed

+60
-32
lines changed

src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,18 @@ async function main() {
8383
});
8484

8585
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
86-
logger.info('Tool request received');
86+
const requestInfoAny = (
87+
extra as unknown as { requestInfo?: { method?: string; transport?: string } }
88+
)?.requestInfo;
89+
const method = requestInfoAny?.method ?? 'N/A';
90+
const transportType = (requestInfoAny?.transport as 'http' | 'sse' | undefined) ?? 'N/A';
91+
logger.info(
92+
{
93+
method,
94+
transport: transportType,
95+
},
96+
'Tool request received',
97+
);
8798

8899
try {
89100
const localLinkedApiToken = process.env.LINKED_API_TOKEN;

src/utils/json-http-transport.ts

Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ export class JsonHTTPServerTransport implements Transport {
2929
public onerror?: (error: Error) => void;
3030
public onmessage?: (
3131
message: JSONRPCMessage,
32-
extra?: { requestInfo?: { headers: IncomingMessage['headers'] }; authInfo?: unknown },
32+
extra?: {
33+
requestInfo?: {
34+
headers: IncomingMessage['headers'];
35+
method?: string;
36+
transport?: 'http' | 'sse';
37+
};
38+
authInfo?: unknown;
39+
},
3340
) => void;
3441

3542
private started = false;
@@ -70,43 +77,45 @@ export class JsonHTTPServerTransport implements Transport {
7077
}
7178

7279
async send(message: JSONRPCMessage, options?: TransportSendOptions): Promise<void> {
73-
// If SSE is connected, stream all server -> client messages via SSE
74-
if (this.sse && !this.sse.res.writableEnded) {
75-
const line = `data: ${JSON.stringify(message)}\n\n`;
76-
this.sse.res.write(line);
77-
return;
78-
}
79-
8080
let relatedId = options?.relatedRequestId;
8181
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
8282
relatedId = message.id;
8383
}
84-
if (relatedId === undefined) {
85-
// No place to send notifications/responses without a related request in JSON-only mode
86-
return;
87-
}
88-
const connId = this.requestIdToConn.get(relatedId);
89-
if (!connId) throw new Error(`No HTTP connection for request ${String(relatedId)}`);
90-
const ctx = this.connections.get(connId);
91-
if (!ctx) throw new Error(`HTTP connection closed for request ${String(relatedId)}`);
84+
// If a related HTTP connection is pending, complete it with JSON
85+
if (relatedId !== undefined) {
86+
const connId = this.requestIdToConn.get(relatedId);
87+
if (connId) {
88+
const ctx = this.connections.get(connId);
89+
if (!ctx) throw new Error(`HTTP connection closed for request ${String(relatedId)}`);
90+
91+
ctx.responses.set(relatedId, message);
92+
const allReady = ctx.orderedIds.every((id) => ctx.responses.has(id));
93+
if (!allReady) return;
94+
95+
const body =
96+
ctx.orderedIds.length === 1
97+
? ctx.responses.get(ctx.orderedIds[0]!)
98+
: ctx.orderedIds.map((id) => ctx.responses.get(id));
9299

93-
ctx.responses.set(relatedId, message);
94-
// When all responses for this HTTP request are ready, flush JSON and end
95-
const allReady = ctx.orderedIds.every((id) => ctx.responses.has(id));
96-
if (!allReady) return;
100+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
101+
ctx.res.writeHead(200, headers);
102+
ctx.res.end(JSON.stringify(body));
97103

98-
const body =
99-
ctx.orderedIds.length === 1
100-
? ctx.responses.get(ctx.orderedIds[0]!)
101-
: ctx.orderedIds.map((id) => ctx.responses.get(id));
104+
this.connections.delete(connId);
105+
ctx.orderedIds.forEach((id) => this.requestIdToConn.delete(id));
106+
return;
107+
}
108+
}
102109

103-
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
104-
ctx.res.writeHead(200, headers);
105-
ctx.res.end(JSON.stringify(body));
110+
// Otherwise, if SSE is connected, stream server -> client messages via SSE
111+
if (this.sse && !this.sse.res.writableEnded) {
112+
const line = `data: ${JSON.stringify(message)}\n\n`;
113+
this.sse.res.write(line);
114+
return;
115+
}
106116

107-
// Cleanup
108-
this.connections.delete(connId);
109-
ctx.orderedIds.forEach((id) => this.requestIdToConn.delete(id));
117+
// No pending HTTP response and no SSE: drop notifications without a target
118+
return;
110119
}
111120

112121
// Handle HTTP requests: supports POST for JSON and GET for SSE
@@ -240,6 +249,8 @@ export class JsonHTTPServerTransport implements Transport {
240249
this.onmessage?.(msg, {
241250
requestInfo: {
242251
headers: req.headers,
252+
method: req.method,
253+
transport: 'http',
243254
},
244255
authInfo: req.auth,
245256
});
@@ -250,13 +261,17 @@ export class JsonHTTPServerTransport implements Transport {
250261

251262
const orderedIds: RequestId[] = messages.filter(isJSONRPCRequest).map((m) => m.id);
252263
const sseConnected = !!this.sse && !this.sse.res.writableEnded;
253-
if (sseConnected) {
264+
// Prefer JSON response when client explicitly accepts JSON; use SSE only when
265+
// Accept doesn't include JSON and an SSE stream is connected
266+
if (sseConnected && !acceptsJson) {
254267
// With SSE, we emit responses on the SSE stream; reply 202 to POST immediately
255268
res.writeHead(202).end();
256269
for (const msg of messages) {
257270
this.onmessage?.(msg, {
258271
requestInfo: {
259272
headers: req.headers,
273+
method: req.method,
274+
transport: 'sse',
260275
},
261276
authInfo: req.auth,
262277
});
@@ -283,6 +298,8 @@ export class JsonHTTPServerTransport implements Transport {
283298
this.onmessage?.(msg, {
284299
requestInfo: {
285300
headers: req.headers,
301+
method: req.method,
302+
transport: 'http',
286303
},
287304
authInfo: req.auth,
288305
});

0 commit comments

Comments
 (0)