Feature Request: Message middleware / interceptor API for vscode-jsonrpc
Summary
Add a way to intercept or decorate outgoing and incoming JSON-RPC messages at the transport layer, so that callers can inject or extract arbitrary metadata (such as W3C Trace Context traceparent/tracestate headers) without forking the library.
Motivation
vscode-jsonrpc is used as the JS/TS transport in many language server and extension host scenarios. In our scenario, we are communicating between a VS Code extension and a .NET process. That process uses StreamJsonRpc, which offers an ActivityTracingStrategy that automatically injects W3C Trace Context into outgoing MessagePack envelopes and extracts it from incoming ones, enabling distributed tracing across the process boundary.
We would like the same on the JS side. Concretely: when a TS client sends a JSON-RPC request, we want to inject a traceparent field alongside jsonrpc, id, method, and params. StreamJsonRpc will then pick it up and parent the server-side span under the client's span, giving a cohesive cross-process trace in tools like Jaeger or the Aspire Dashboard.
Currently this is not possible because MessageWriter is constructed inside createMessageConnection() and is not accessible to callers after the fact. There is no hook to wrap or intercept messages before they are written to the stream.
Proposed API
A message middleware interface that can be passed to createMessageConnection():
export interface MessageMiddleware {
/** Called before a message is written. Return the (optionally mutated) message, or a new one. */
onSend?(message: Message): Message
/** Called after a message is read, before it is dispatched. Return the (optionally mutated) message. */
onReceive?(message: Message): Message
}
Usage:
import { createMessageConnection, MessageMiddleware } from 'vscode-jsonrpc'
const tracing: MessageMiddleware = {
onSend(msg) {
if (isRequestMessage(msg)) {
// inject W3C traceparent alongside standard JSON-RPC fields
;(msg as any).traceparent = getCurrentTraceparent()
}
return msg
},
onReceive(msg) {
if (isRequestMessage(msg)) {
const tp = (msg as any).traceparent
if (tp) setIncomingTraceparent(tp)
}
return msg
},
}
const conn = createMessageConnection(reader, writer, logger, { middleware: tracing })
Alternatives considered
- Subclassing
MessageWriter — the writer instance is not exposed after createMessageConnection() returns, so this is not possible without forking.
- Using a custom
StreamMessageWriter wrapper — requires re-implementing the full framing protocol; fragile and error-prone.
- Encoding metadata in method params — invasive; requires changes to every call site; breaks protocol contracts.
Notes
- The
traceparent field is a top-level JSON-RPC extension field (not inside params), which is valid per the JSON-RPC 2.0 spec (unknown fields are ignored). StreamJsonRpc already uses this convention.
- This request is not limited to tracing — the same API would be useful for propagating auth tokens, request deadlines, or any other cross-cutting concern.
Feature Request: Message middleware / interceptor API for
vscode-jsonrpcSummary
Add a way to intercept or decorate outgoing and incoming JSON-RPC messages at the transport layer, so that callers can inject or extract arbitrary metadata (such as W3C Trace Context
traceparent/tracestateheaders) without forking the library.Motivation
vscode-jsonrpcis used as the JS/TS transport in many language server and extension host scenarios. In our scenario, we are communicating between a VS Code extension and a .NET process. That process uses StreamJsonRpc, which offers anActivityTracingStrategythat automatically injects W3C Trace Context into outgoing MessagePack envelopes and extracts it from incoming ones, enabling distributed tracing across the process boundary.We would like the same on the JS side. Concretely: when a TS client sends a JSON-RPC request, we want to inject a
traceparentfield alongsidejsonrpc,id,method, andparams. StreamJsonRpc will then pick it up and parent the server-side span under the client's span, giving a cohesive cross-process trace in tools like Jaeger or the Aspire Dashboard.Currently this is not possible because
MessageWriteris constructed insidecreateMessageConnection()and is not accessible to callers after the fact. There is no hook to wrap or intercept messages before they are written to the stream.Proposed API
A message middleware interface that can be passed to
createMessageConnection():Usage:
Alternatives considered
MessageWriter— the writer instance is not exposed aftercreateMessageConnection()returns, so this is not possible without forking.StreamMessageWriterwrapper — requires re-implementing the full framing protocol; fragile and error-prone.Notes
traceparentfield is a top-level JSON-RPC extension field (not insideparams), which is valid per the JSON-RPC 2.0 spec (unknown fields are ignored). StreamJsonRpc already uses this convention.