@cyanmycelium/mcp-broker-provider 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -0
- package/dist/index.d.ts +297 -3
- package/dist/index.js +257 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/broker.client.ts +407 -0
- package/src/direct.transport.ts +45 -2
- package/src/index.ts +30 -0
- package/src/multiplex.transport.ts +72 -13
- package/src/transport.support.ts +35 -0
package/README.md
CHANGED
|
@@ -95,6 +95,106 @@ Either form sends the registration notification with `params: { aggregate: true
|
|
|
95
95
|
|
|
96
96
|
**Wire the message handler before you connect.** The broker runs `initialize` against a newly aggregated provider immediately, and a provider that does not answer is dropped from `_all` silently. Handing the transport to an MCP server does this for you, since the server assigns `onMessage` before calling `connect()`. Assigning it yourself, after connecting, loses the handshake and the provider never appears in the aggregate.
|
|
97
97
|
|
|
98
|
+
## Letting the broker decide (broker 1.5.0)
|
|
99
|
+
|
|
100
|
+
A provider that serves its own kind of resource can declare an authorization
|
|
101
|
+
domain and ask the broker for decisions, instead of carrying a policy of its
|
|
102
|
+
own. Both transports expose the broker's methods as `transport.broker`:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { DirectTransport, callerReferenceOf } from "@cyanmycelium/mcp-broker-provider";
|
|
106
|
+
|
|
107
|
+
const transport = new DirectTransport("ws://broker:3000/provider/scada");
|
|
108
|
+
// ... start the MCP server on it, then:
|
|
109
|
+
await transport.broker.declare({
|
|
110
|
+
version: "2026-10-01.1",
|
|
111
|
+
domain: "scada",
|
|
112
|
+
namespace: { resource: "/production/site1" },
|
|
113
|
+
capabilities: ["scada.observe", "scada.control"],
|
|
114
|
+
resources: [
|
|
115
|
+
{
|
|
116
|
+
resource: "uns://production/site1/line1/motor01/speed_sp",
|
|
117
|
+
resourcePath: "/production/site1/line1/motor01/speed_sp",
|
|
118
|
+
limits: { minValue: 0, maxValue: 1500 },
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
resultsRequired: ["scada.control"],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// In a tool handler (mcp-core 1.4.0 hands the adapter the request's _meta):
|
|
125
|
+
const caller = callerReferenceOf(request?.meta);
|
|
126
|
+
const { decisions } = await transport.broker.authorize({
|
|
127
|
+
principal: { type: "caller-ref", ref: caller!.ref },
|
|
128
|
+
checks: [{ capability: "scada.control", resource: "uns://production/site1/line1/motor01/speed_sp", resourcePath: "/production/site1/line1/motor01/speed_sp" }],
|
|
129
|
+
});
|
|
130
|
+
// decisions[0]: { effect: "allow-with-constraints", allowed: false, obligations: { constraints: { minValue: 0, maxValue: 1500 } }, ... }
|
|
131
|
+
|
|
132
|
+
// Once the write is done, or refused by a constraint:
|
|
133
|
+
transport.broker.reportResult({ decisionId: decisions[0].decisionId, result: "success", nativeStatus: "Good" });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `effect` is `allow`, `deny` or `allow-with-constraints`. The last one comes
|
|
137
|
+
with the resource's declared limits in `obligations.constraints`; apply them
|
|
138
|
+
right before executing. `allowed` is `true` only for a plain `allow`, so code
|
|
139
|
+
that reads `allowed` alone refuses a constrained allow instead of ignoring
|
|
140
|
+
its limits.
|
|
141
|
+
- `reportResult()` is a notification: nothing comes back. The broker writes the
|
|
142
|
+
outcome next to the decision in its audit, and `broker_diagnose` reports
|
|
143
|
+
decisions of `resultsRequired` capabilities still unreported.
|
|
144
|
+
|
|
145
|
+
- The provider needs its own identity on the broker (an entry in the security
|
|
146
|
+
file's `providers` table), which means presenting a secret:
|
|
147
|
+
`new DirectTransport(url, { secret })`, sent as `X-Provider-Token`. Node 22
|
|
148
|
+
and later can; a browser cannot, so this is for Node and native providers.
|
|
149
|
+
- To test all of this without an authorization server, start the broker with
|
|
150
|
+
`startTestBroker()` from `@cyanmycelium/mcp-broker/testing`
|
|
151
|
+
([guide](../broker/docs/testing.md)).
|
|
152
|
+
- The broker's answers are taken off the socket before the MCP server sees
|
|
153
|
+
them. A refused request rejects with `BrokerRequestError` (`code`, `data`).
|
|
154
|
+
- There is no timeout by default: a broker from 1.4.1 on answers at once. Set
|
|
155
|
+
`brokerRequestTimeoutMs` only to talk to an older one, which drops methods it
|
|
156
|
+
does not know.
|
|
157
|
+
|
|
158
|
+
## Distributed traces (broker 1.5.0)
|
|
159
|
+
|
|
160
|
+
Every addressed request arriving from broker 1.5 carries W3C Trace Context in
|
|
161
|
+
`params._meta.traceparent`. Read it from the request metadata, create a SERVER
|
|
162
|
+
span, then propagate a child context when this provider calls another slot:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import {
|
|
166
|
+
childTraceparent,
|
|
167
|
+
traceparentOf,
|
|
168
|
+
withTraceparent,
|
|
169
|
+
} from "@cyanmycelium/mcp-broker-provider";
|
|
170
|
+
|
|
171
|
+
const incoming = traceparentOf(request?.meta);
|
|
172
|
+
const downstreamMeta = incoming
|
|
173
|
+
? withTraceparent(undefined, childTraceparent(incoming, clientSpanId))
|
|
174
|
+
: undefined;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Completed spans are validated before emission:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const emitted = transport.broker.span({
|
|
181
|
+
traceId,
|
|
182
|
+
spanId,
|
|
183
|
+
parentSpanId,
|
|
184
|
+
name: "modbus.read",
|
|
185
|
+
kind: 3,
|
|
186
|
+
startTimeUnixNano,
|
|
187
|
+
endTimeUnixNano,
|
|
188
|
+
attributes: { "modbus.unit_id": 1 },
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The call is a notification and never waits for an answer. The broker consumes
|
|
193
|
+
`broker/telemetry`, sends it through its bounded exporter queue, and never
|
|
194
|
+
broadcasts it to MCP clients. It returns `false` and drops the span when the
|
|
195
|
+
provider link is down, so telemetry never occupies the transport queue ahead
|
|
196
|
+
of MCP control traffic. See [the complete telemetry contract](../../../docs/telemetry.md).
|
|
197
|
+
|
|
98
198
|
## Diagnostics
|
|
99
199
|
|
|
100
200
|
This stack used to fail quietly. The transports now name what went wrong, on the console, because a browser-hosted provider has nowhere else to report:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,238 @@
|
|
|
1
1
|
export { ITunnelRegisterOptions, TUNNEL_REGISTER_METHOD, TunnelEnvelope, TunnelError, TunnelErrorCode, TunnelErrorCodes, decodeEnvelope, encodeEnvelope, encodeEnvelopeMessage, encodeErrorEnvelope, encodeRegisterEnvelope, encodeRegisterFrame, envelopeFrame, tunnelErrorOf } from './protocol/index.js';
|
|
2
2
|
import { IMessageTransport } from '@cyanmycelium/mcp-core';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Talks to the broker itself, over the provider's own socket: declaring an
|
|
6
|
+
* authorization domain, and asking for decisions.
|
|
7
|
+
*
|
|
8
|
+
* Requires broker 1.5.0 or later. An older broker does not know these methods:
|
|
9
|
+
* from 1.4.1 it refuses them at once with `-32601`; 1.4.0 and earlier drop
|
|
10
|
+
* them and never answer, which is why {@link IBrokerClientOptions.requestTimeoutMs}
|
|
11
|
+
* exists, off by default.
|
|
12
|
+
*/
|
|
13
|
+
/** The `params._meta` key under which the broker passes the caller reference with each request. */
|
|
14
|
+
declare const CALLER_META_KEY = "io.cyanmycelium/caller";
|
|
15
|
+
/** W3C Trace Context carrier used by MCP requests. */
|
|
16
|
+
declare const TRACEPARENT_META_KEY = "traceparent";
|
|
17
|
+
/** Provider-to-broker telemetry notification. */
|
|
18
|
+
declare const TELEMETRY_NOTIFICATION_METHOD = "broker/telemetry";
|
|
19
|
+
/** Provider-to-broker notification reporting what happened after a decision. */
|
|
20
|
+
declare const AUDIT_RESULT_NOTIFICATION_METHOD = "broker/audit/result";
|
|
21
|
+
interface ITraceParent {
|
|
22
|
+
readonly version: "00";
|
|
23
|
+
readonly traceId: string;
|
|
24
|
+
readonly parentId: string;
|
|
25
|
+
readonly traceFlags: string;
|
|
26
|
+
}
|
|
27
|
+
type TelemetryAttributeValue = string | number | boolean;
|
|
28
|
+
interface ITelemetryEvent {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly timeUnixNano: string;
|
|
31
|
+
readonly attributes?: Readonly<Record<string, TelemetryAttributeValue>>;
|
|
32
|
+
}
|
|
33
|
+
interface ITelemetrySpan {
|
|
34
|
+
readonly traceId: string;
|
|
35
|
+
readonly spanId: string;
|
|
36
|
+
readonly parentSpanId?: string;
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly kind?: number;
|
|
39
|
+
readonly startTimeUnixNano: string;
|
|
40
|
+
readonly endTimeUnixNano: string;
|
|
41
|
+
readonly attributes?: Readonly<Record<string, TelemetryAttributeValue>>;
|
|
42
|
+
readonly events?: readonly ITelemetryEvent[];
|
|
43
|
+
readonly status?: {
|
|
44
|
+
readonly code: number;
|
|
45
|
+
readonly message?: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Parses the W3C version 00 traceparent representation used in MCP metadata. */
|
|
49
|
+
declare function parseTraceparent(value: unknown): ITraceParent | undefined;
|
|
50
|
+
declare function formatTraceparent(context: ITraceParent): string;
|
|
51
|
+
/** Reads `params._meta.traceparent`, returning `undefined` when it is malformed. */
|
|
52
|
+
declare function traceparentOf(meta: Readonly<Record<string, unknown>> | undefined): ITraceParent | undefined;
|
|
53
|
+
/** Returns a metadata copy carrying the supplied validated trace context. */
|
|
54
|
+
declare function withTraceparent(meta: Readonly<Record<string, unknown>> | undefined, context: ITraceParent): Readonly<Record<string, unknown>>;
|
|
55
|
+
/** Continues a trace using the caller's span as the new W3C parent id. */
|
|
56
|
+
declare function childTraceparent(parent: ITraceParent, spanId: string): ITraceParent;
|
|
57
|
+
/** What the broker hands a declaring provider with each request, under {@link CALLER_META_KEY}. */
|
|
58
|
+
interface ICallerReference {
|
|
59
|
+
/** Opaque. Valid on this slot, while the request it came with is pending. */
|
|
60
|
+
readonly ref: string;
|
|
61
|
+
/** Ties the decisions and the eventual report to the client request. */
|
|
62
|
+
readonly correlationId: string;
|
|
63
|
+
/** W3C trace id carried by the MCP request. */
|
|
64
|
+
readonly traceId?: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Reads the caller reference out of a request's `params._meta`, or
|
|
68
|
+
* `undefined` when there is none (the broker only adds it once a declaration
|
|
69
|
+
* was accepted). With mcp-core 1.4.0, pass `request?.meta` from the context
|
|
70
|
+
* an adapter receives.
|
|
71
|
+
*/
|
|
72
|
+
declare function callerReferenceOf(meta: Readonly<Record<string, unknown>> | undefined): ICallerReference | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Engineering limits of one resource. They hold for every caller, and the
|
|
75
|
+
* broker returns them with each allow on that resource as
|
|
76
|
+
* `obligations.constraints`, for the provider to apply.
|
|
77
|
+
*/
|
|
78
|
+
interface IResourceLimits {
|
|
79
|
+
readonly minValue?: number;
|
|
80
|
+
readonly maxValue?: number;
|
|
81
|
+
readonly allowedValues?: readonly (string | number | boolean | null)[];
|
|
82
|
+
readonly destinations?: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
/** One resource of a declaration: the provider's own identifier, and the path the broker evaluates. */
|
|
85
|
+
interface IDeclaredResource {
|
|
86
|
+
readonly resource: string;
|
|
87
|
+
readonly resourcePath: string;
|
|
88
|
+
readonly effect?: string;
|
|
89
|
+
readonly limits?: IResourceLimits;
|
|
90
|
+
}
|
|
91
|
+
/** `broker/authorization/declare` parameters. Describes; grants nothing. */
|
|
92
|
+
interface IAuthorizationDeclaration {
|
|
93
|
+
readonly version: string;
|
|
94
|
+
readonly domain: string;
|
|
95
|
+
readonly namespace: {
|
|
96
|
+
readonly resource: string;
|
|
97
|
+
};
|
|
98
|
+
readonly capabilities: readonly string[];
|
|
99
|
+
readonly resources?: readonly IDeclaredResource[];
|
|
100
|
+
readonly protects?: readonly string[];
|
|
101
|
+
/**
|
|
102
|
+
* Declared capabilities whose allowed decisions this provider promises to
|
|
103
|
+
* report with {@link BrokerClient.reportResult}. One not reported in time
|
|
104
|
+
* shows up in `broker_diagnose`.
|
|
105
|
+
*/
|
|
106
|
+
readonly resultsRequired?: readonly string[];
|
|
107
|
+
}
|
|
108
|
+
interface IDeclarationAccepted {
|
|
109
|
+
readonly accepted: true;
|
|
110
|
+
readonly version: string;
|
|
111
|
+
readonly policyVersion: string;
|
|
112
|
+
}
|
|
113
|
+
/** One question: may (the caller) do `capability` on this resource? */
|
|
114
|
+
interface IAuthorizationCheck {
|
|
115
|
+
readonly capability: string;
|
|
116
|
+
readonly resource: string;
|
|
117
|
+
readonly resourcePath: string;
|
|
118
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
119
|
+
}
|
|
120
|
+
/** `broker/authorize` parameters. */
|
|
121
|
+
interface IAuthorizationQuery {
|
|
122
|
+
/** On whose behalf: the caller of a pending request, or the provider itself. Never an identity. */
|
|
123
|
+
readonly principal: {
|
|
124
|
+
readonly type: "caller-ref";
|
|
125
|
+
readonly ref: string;
|
|
126
|
+
} | {
|
|
127
|
+
readonly type: "provider";
|
|
128
|
+
};
|
|
129
|
+
/** Only for `{ type: "provider" }`; a caller reference carries its own. */
|
|
130
|
+
readonly correlationId?: string;
|
|
131
|
+
/** Optional W3C trace id for provider-initiated work. */
|
|
132
|
+
readonly traceId?: string;
|
|
133
|
+
readonly checks: readonly IAuthorizationCheck[];
|
|
134
|
+
}
|
|
135
|
+
/** What an allow comes with. */
|
|
136
|
+
interface IAuthorizationObligations {
|
|
137
|
+
/** The declared limits of the resource. Apply them right before executing. */
|
|
138
|
+
readonly constraints?: IResourceLimits;
|
|
139
|
+
}
|
|
140
|
+
interface IAuthorizationDecision {
|
|
141
|
+
readonly decisionId: string;
|
|
142
|
+
/** `allow-with-constraints`: allowed, within `obligations`. */
|
|
143
|
+
readonly effect: "allow" | "deny" | "allow-with-constraints";
|
|
144
|
+
/**
|
|
145
|
+
* `true` only for an unconditional `allow`. A provider that reads only
|
|
146
|
+
* this field therefore refuses a constrained allow rather than ignoring
|
|
147
|
+
* its constraints; read `effect` to apply them.
|
|
148
|
+
*/
|
|
149
|
+
readonly allowed: boolean;
|
|
150
|
+
readonly reason: string;
|
|
151
|
+
readonly policies?: readonly string[];
|
|
152
|
+
readonly obligations?: IAuthorizationObligations;
|
|
153
|
+
}
|
|
154
|
+
/** `broker/audit/result` parameters: the outcome of what a decision allowed or refused. */
|
|
155
|
+
interface IAuditResult {
|
|
156
|
+
/** The `decisionId` the broker returned. */
|
|
157
|
+
readonly decisionId: string;
|
|
158
|
+
readonly result: "success" | "failure" | "refused";
|
|
159
|
+
/** The protocol's own status (`Good`, an exception code, ...). */
|
|
160
|
+
readonly nativeStatus?: string;
|
|
161
|
+
/** The provider's error code, when it failed or refused. */
|
|
162
|
+
readonly errorCode?: string;
|
|
163
|
+
}
|
|
164
|
+
interface IAuthorizationAnswer {
|
|
165
|
+
readonly policyVersion: string;
|
|
166
|
+
/** One per check, in the same order. */
|
|
167
|
+
readonly decisions: readonly IAuthorizationDecision[];
|
|
168
|
+
}
|
|
169
|
+
/** The broker refused a request, or never answered it. */
|
|
170
|
+
declare class BrokerRequestError extends Error {
|
|
171
|
+
/** JSON-RPC error code; `undefined` for a timeout or a closed socket. */
|
|
172
|
+
readonly code?: number | undefined;
|
|
173
|
+
/** The error's `data`, e.g. `{ errors: [...] }` for a refused declaration. */
|
|
174
|
+
readonly data?: unknown | undefined;
|
|
175
|
+
constructor(message: string,
|
|
176
|
+
/** JSON-RPC error code; `undefined` for a timeout or a closed socket. */
|
|
177
|
+
code?: number | undefined,
|
|
178
|
+
/** The error's `data`, e.g. `{ errors: [...] }` for a refused declaration. */
|
|
179
|
+
data?: unknown | undefined);
|
|
180
|
+
}
|
|
181
|
+
interface IBrokerClientOptions {
|
|
182
|
+
/**
|
|
183
|
+
* Rejects a request the broker did not answer within this many ms. Off by
|
|
184
|
+
* default: a broker from 1.4.1 on answers every request at once, refusals
|
|
185
|
+
* included, so waiting is never the normal path. Set it only to talk to an
|
|
186
|
+
* older broker, which drops what it does not know.
|
|
187
|
+
*/
|
|
188
|
+
readonly requestTimeoutMs?: number;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The `broker/*` methods of one provider slot. Reached as `transport.broker`
|
|
192
|
+
* on {@link DirectTransport} and {@link MultiplexTransport}; the transport
|
|
193
|
+
* routes the broker's answers here before anything reaches the MCP server.
|
|
194
|
+
*/
|
|
195
|
+
declare class BrokerClient {
|
|
196
|
+
private readonly _write;
|
|
197
|
+
private readonly _writeTelemetry;
|
|
198
|
+
private readonly _timeoutMs;
|
|
199
|
+
private readonly _waiting;
|
|
200
|
+
private _next;
|
|
201
|
+
constructor(write: (frame: string) => void, options?: IBrokerClientOptions, writeTelemetry?: (frame: string) => boolean);
|
|
202
|
+
/**
|
|
203
|
+
* Declares this provider's authorization domain. Resolves when the broker
|
|
204
|
+
* accepted it; rejects with a {@link BrokerRequestError} whose `data.errors`
|
|
205
|
+
* lists every problem otherwise. Until it resolves, serve nothing that
|
|
206
|
+
* needs a decision.
|
|
207
|
+
*/
|
|
208
|
+
declare(declaration: IAuthorizationDeclaration): Promise<IDeclarationAccepted>;
|
|
209
|
+
/** Asks for one decision per check. */
|
|
210
|
+
authorize(query: IAuthorizationQuery): Promise<IAuthorizationAnswer>;
|
|
211
|
+
/**
|
|
212
|
+
* Reports what happened after a decision, so the broker's audit shows the
|
|
213
|
+
* outcome next to the decision. A notification: nothing comes back, and a
|
|
214
|
+
* report the broker cannot match is counted on its side.
|
|
215
|
+
*/
|
|
216
|
+
reportResult(report: IAuditResult): void;
|
|
217
|
+
/** Emits one complete provider span, or returns false when the link is down. */
|
|
218
|
+
span(span: ITelemetrySpan): boolean;
|
|
219
|
+
/** Number of requests still waiting for the broker. */
|
|
220
|
+
get pendingCount(): number;
|
|
221
|
+
/**
|
|
222
|
+
* Consumes a frame when it answers one of this client's requests. Returns
|
|
223
|
+
* `true` when it did, and the frame must not reach the MCP server.
|
|
224
|
+
* @internal Called by the transports.
|
|
225
|
+
*/
|
|
226
|
+
handleIncoming(frame: string): boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Fails every waiting request: the socket they went out on is gone, and a
|
|
229
|
+
* reconnected one will not carry their answers.
|
|
230
|
+
* @internal Called by the transports.
|
|
231
|
+
*/
|
|
232
|
+
rejectAll(reason: string): void;
|
|
233
|
+
private _request;
|
|
234
|
+
}
|
|
235
|
+
|
|
4
236
|
/** Options accepted by {@link DirectTransport}. */
|
|
5
237
|
interface IDirectTransportOptions {
|
|
6
238
|
/**
|
|
@@ -22,6 +254,25 @@ interface IDirectTransportOptions {
|
|
|
22
254
|
* the provider silently never appears in `_all`.
|
|
23
255
|
*/
|
|
24
256
|
aggregate?: boolean;
|
|
257
|
+
/**
|
|
258
|
+
* Rejects a `broker.declare()` / `broker.authorize()` the broker did not
|
|
259
|
+
* answer within this many ms. Off by default: a broker from 1.4.1 on answers
|
|
260
|
+
* at once. Only for an older broker, which drops methods it does not know.
|
|
261
|
+
*/
|
|
262
|
+
brokerRequestTimeoutMs?: number;
|
|
263
|
+
/**
|
|
264
|
+
* The provider secret, sent as the `X-Provider-Token` header of the
|
|
265
|
+
* WebSocket handshake. Required by a broker that authenticates providers
|
|
266
|
+
* (`providerSecret`, or the security file's `providers` table, where it is
|
|
267
|
+
* what gives this provider its own identity).
|
|
268
|
+
*
|
|
269
|
+
* **Node only.** Node's `WebSocket` (22 and later) accepts handshake
|
|
270
|
+
* headers; a browser's does not, and a browser provider cannot
|
|
271
|
+
* authenticate (terminate provider auth in a reverse proxy instead).
|
|
272
|
+
*/
|
|
273
|
+
secret?: string;
|
|
274
|
+
/** Extra handshake headers, Node only, like {@link secret}. */
|
|
275
|
+
headers?: Readonly<Record<string, string>>;
|
|
25
276
|
}
|
|
26
277
|
/**
|
|
27
278
|
* 1:1 WebSocket transport, wraps a single `WebSocket` connection to a broker
|
|
@@ -40,6 +291,7 @@ interface IDirectTransportOptions {
|
|
|
40
291
|
declare class DirectTransport implements IMessageTransport {
|
|
41
292
|
private readonly _wsUrl;
|
|
42
293
|
private readonly _aggregate;
|
|
294
|
+
private readonly _headers;
|
|
43
295
|
private readonly _pending;
|
|
44
296
|
/** Throttles the "wrote to a closed transport" line, which repeats per frame. */
|
|
45
297
|
private readonly _afterCloseNotice;
|
|
@@ -49,6 +301,12 @@ declare class DirectTransport implements IMessageTransport {
|
|
|
49
301
|
onOpen: (() => void) | null;
|
|
50
302
|
onClose: (() => void) | null;
|
|
51
303
|
onError: ((error: Error) => void) | null;
|
|
304
|
+
/**
|
|
305
|
+
* The broker's own methods for this slot: declaring an authorization
|
|
306
|
+
* domain, asking for decisions. Its answers are taken off the socket
|
|
307
|
+
* before {@link onMessage} sees anything.
|
|
308
|
+
*/
|
|
309
|
+
readonly broker: BrokerClient;
|
|
52
310
|
constructor(wsUrl: string, options?: IDirectTransportOptions);
|
|
53
311
|
get isOpen(): boolean;
|
|
54
312
|
/**
|
|
@@ -89,6 +347,9 @@ declare class MultiplexSocket {
|
|
|
89
347
|
/** Per-URL cache so all transports targeting the same tunnel share one socket. */
|
|
90
348
|
private static readonly _instances;
|
|
91
349
|
private readonly _wsUrl;
|
|
350
|
+
/** Cache key: the URL, and the handshake headers, since two secrets are two identities and need two sockets. */
|
|
351
|
+
private readonly _key;
|
|
352
|
+
private readonly _headers;
|
|
92
353
|
private readonly _transports;
|
|
93
354
|
/** Aggregate opt-in per slot, absent when the caller did not express one. */
|
|
94
355
|
private readonly _aggregates;
|
|
@@ -105,8 +366,8 @@ declare class MultiplexSocket {
|
|
|
105
366
|
/** Throttles the "wrote to a closed tunnel" line, which repeats per frame. */
|
|
106
367
|
private readonly _afterStopNotice;
|
|
107
368
|
private constructor();
|
|
108
|
-
/** Returns (or creates) the singleton socket for a given tunnel URL. */
|
|
109
|
-
static getOrCreate(wsUrl: string): MultiplexSocket;
|
|
369
|
+
/** Returns (or creates) the singleton socket for a given tunnel URL and handshake headers. */
|
|
370
|
+
static getOrCreate(wsUrl: string, headers?: Record<string, string>): MultiplexSocket;
|
|
110
371
|
get isOpen(): boolean;
|
|
111
372
|
register(name: string, transport: MultiplexTransport, aggregate?: boolean): void;
|
|
112
373
|
unregister(name: string): void;
|
|
@@ -131,6 +392,8 @@ declare class MultiplexSocket {
|
|
|
131
392
|
*/
|
|
132
393
|
private _announceProvider;
|
|
133
394
|
send(provider: string, data: string): void;
|
|
395
|
+
/** Sends low-priority telemetry only while the shared link is open. */
|
|
396
|
+
sendTelemetry(provider: string, data: string): boolean;
|
|
134
397
|
private _connect;
|
|
135
398
|
/** Writes out everything queued while the socket was down. */
|
|
136
399
|
private _flush;
|
|
@@ -159,6 +422,25 @@ interface IMultiplexTransportOptions {
|
|
|
159
422
|
* appears in `_all`.
|
|
160
423
|
*/
|
|
161
424
|
aggregate?: boolean;
|
|
425
|
+
/**
|
|
426
|
+
* Rejects a `broker.declare()` / `broker.authorize()` the broker did not
|
|
427
|
+
* answer within this many ms. Off by default: a broker from 1.4.1 on answers
|
|
428
|
+
* at once. Only for an older broker, which drops methods it does not know.
|
|
429
|
+
*/
|
|
430
|
+
brokerRequestTimeoutMs?: number;
|
|
431
|
+
/**
|
|
432
|
+
* The provider secret, sent as the `X-Provider-Token` header of the
|
|
433
|
+
* WebSocket handshake. Required by a broker that authenticates providers
|
|
434
|
+
* (`providerSecret`, or the security file's `providers` table, where it is
|
|
435
|
+
* what gives this provider its own identity).
|
|
436
|
+
*
|
|
437
|
+
* **Node only.** Node's `WebSocket` (22 and later) accepts handshake
|
|
438
|
+
* headers; a browser's does not, and a browser provider cannot
|
|
439
|
+
* authenticate (terminate provider auth in a reverse proxy instead).
|
|
440
|
+
*/
|
|
441
|
+
secret?: string;
|
|
442
|
+
/** Extra handshake headers, Node only, like {@link secret}. */
|
|
443
|
+
headers?: Readonly<Record<string, string>>;
|
|
162
444
|
}
|
|
163
445
|
/**
|
|
164
446
|
* A transport that multiplexes multiple MCP servers over a single shared
|
|
@@ -180,7 +462,19 @@ declare class MultiplexTransport implements IMessageTransport {
|
|
|
180
462
|
onOpen: (() => void) | null;
|
|
181
463
|
onClose: (() => void) | null;
|
|
182
464
|
onError: ((error: Error) => void) | null;
|
|
465
|
+
/**
|
|
466
|
+
* The broker's own methods for this slot: declaring an authorization
|
|
467
|
+
* domain, asking for decisions. Its answers are taken off the socket
|
|
468
|
+
* before {@link onMessage} sees anything.
|
|
469
|
+
*/
|
|
470
|
+
readonly broker: BrokerClient;
|
|
183
471
|
constructor(name: string, socket: MultiplexSocket, options?: IMultiplexTransportOptions);
|
|
472
|
+
/**
|
|
473
|
+
* One frame from the broker for this slot: the broker's answer to one of
|
|
474
|
+
* {@link broker}'s requests, or MCP traffic for the server.
|
|
475
|
+
* @internal Called by the shared socket.
|
|
476
|
+
*/
|
|
477
|
+
_receive(frame: string): void;
|
|
184
478
|
/**
|
|
185
479
|
* Convenience factory: creates a {@link MultiplexTransport} backed by a
|
|
186
480
|
* shared {@link MultiplexSocket} for the given tunnel URL.
|
|
@@ -211,4 +505,4 @@ declare class MultiplexTransport implements IMessageTransport {
|
|
|
211
505
|
close(): void;
|
|
212
506
|
}
|
|
213
507
|
|
|
214
|
-
export { DirectTransport, type IDirectTransportOptions, type IMultiplexTransportOptions, MultiplexTransport };
|
|
508
|
+
export { AUDIT_RESULT_NOTIFICATION_METHOD, BrokerClient, BrokerRequestError, CALLER_META_KEY, DirectTransport, type IAuditResult, type IAuthorizationAnswer, type IAuthorizationCheck, type IAuthorizationDecision, type IAuthorizationDeclaration, type IAuthorizationObligations, type IAuthorizationQuery, type IBrokerClientOptions, type ICallerReference, type IDeclarationAccepted, type IDeclaredResource, type IDirectTransportOptions, type IMultiplexTransportOptions, type IResourceLimits, type ITelemetryEvent, type ITelemetrySpan, type ITraceParent, MultiplexTransport, TELEMETRY_NOTIFICATION_METHOD, TRACEPARENT_META_KEY, type TelemetryAttributeValue, callerReferenceOf, childTraceparent, formatTraceparent, parseTraceparent, traceparentOf, withTraceparent };
|