@cyanmycelium/mcp-broker 1.3.0 → 1.3.2

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 CHANGED
@@ -232,12 +232,22 @@ Each provider entry from `providers_list` / `provider_status`:
232
232
  "name": "weather",
233
233
  "transport": "ws",
234
234
  "connected": true,
235
+ "aggregate": true,
236
+ "connectedSince": "2026-09-14T08:12:03.417Z",
237
+ "connectedForMs": 184211,
235
238
  "clientCount": 0,
236
239
  "sessionCount": 1,
237
240
  "pendingCount": 0
238
241
  }
239
242
  ```
240
243
 
244
+ - The first six fields are about the **provider**: whether something serves
245
+ the slot, over what, whether it is in `_all`, and since when. A
246
+ `connectedSince` that is always a few seconds old is a provider that keeps
247
+ reconnecting.
248
+ - The three counts are about the slot's **callers**. A connected provider that
249
+ nobody is calling right now reads `clientCount: 0, sessionCount: 0,
250
+ pendingCount: 0`; that is not a fault.
241
251
  - `transport` is `ws` (dedicated socket), `ws-multiplex` (shared socket),
242
252
  `stdio` (**any** configured upstream: a child process from `stdioUpstreams`,
243
253
  a `.mcpb` bundle, *or* a remote URL from `mcpServers`), `loopback`
@@ -720,6 +730,30 @@ npm start # node dist/bin.js
720
730
 
721
731
  Requires Node 20.11+.
722
732
 
733
+ ### Running the broker of this checkout
734
+
735
+ `npm start` runs `dist/bin.js`, the code of the working tree once `npm run
736
+ build` has been run (from `node/`, which builds the provider package first).
737
+ It reads `.mcp-broker/config.json` next to `package.json` when there is one;
738
+ that folder is gitignored, so each checkout keeps its own. A config that makes
739
+ the broker reachable from a device on the LAN and serves this package's `web/`
740
+ (the broker explorer at `/demos/broker-explorer/`) from the same port:
741
+
742
+ ```json
743
+ {
744
+ "port": 3000,
745
+ "host": "0.0.0.0",
746
+ "www": { "open": false, "mounts": [{ "urlPrefix": "/", "dir": "../web" }] },
747
+ "allowedOrigins": ["http://localhost:3000", "http://127.0.0.1:3000"]
748
+ }
749
+ ```
750
+
751
+ Paths in the file resolve against the file's own directory, hence `../web`.
752
+ Keep the terminal open: the process lives as long as it does. Without the
753
+ file the defaults apply, `0.0.0.0:3000` and no static mount. Every C sample,
754
+ the roundtrip and the soak in [`c/`](https://github.com/pandaGaume/mcp-broker/tree/main/c)
755
+ target this broker, not an installed one.
756
+
723
757
  ## Releasing
724
758
 
725
759
  The package is published to npm by [`.github/workflows/release-node.yml`](https://github.com/pandaGaume/mcp-broker/blob/main/.github/workflows/release-node.yml), triggered by tags of the form `node-v*`.
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { PACKAGE_NAME, VERSION, loadBrokerConfig, WsTunnelBuilder, loadMcpbBundle, BROKER_AGGREGATE_NAME, BROKER_PROVIDER_NAME, resolveOpenTarget } from './chunk-BZUZYXVA.js';
2
+ import { PACKAGE_NAME, VERSION, loadBrokerConfig, WsTunnelBuilder, loadMcpbBundle, BROKER_AGGREGATE_NAME, BROKER_PROVIDER_NAME, resolveOpenTarget } from './chunk-J5TN5RYU.js';
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
5
  import open from 'open';
@@ -3002,6 +3002,15 @@ var AggregateServer = class _AggregateServer {
3002
3002
  this.removeProvider(name);
3003
3003
  }
3004
3004
  }
3005
+ /**
3006
+ * Slot names currently contributing to the aggregate, in registration
3007
+ * order. A provider whose handshake failed is not in it: `addProvider`
3008
+ * removed it. This is what `broker_diagnose` reads through
3009
+ * `IBrokerContext.getAggregateInfo()`.
3010
+ */
3011
+ get providerNames() {
3012
+ return [...this._sessions.keys()];
3013
+ }
3005
3014
  /** Removes a provider from the aggregate. */
3006
3015
  removeProvider(name) {
3007
3016
  const session = this._sessions.get(name);
@@ -3700,6 +3709,22 @@ var WsTunnel = class {
3700
3709
  getStdioBridgeTarget() {
3701
3710
  return this._options.stdioClient?.providerName ?? null;
3702
3711
  }
3712
+ /**
3713
+ * Membership of the `_all` aggregate, for `broker_diagnose`.
3714
+ *
3715
+ * Before `start()` the aggregate server does not exist yet, so an enabled
3716
+ * aggregate reports no provider rather than `undefined`: "cannot be read"
3717
+ * is for a host that has no aggregate at all, not for one that has not
3718
+ * started. Without this accessor the CLI's own broker skipped the
3719
+ * `aggregate-empty` check and told the operator to call tools/list on
3720
+ * `_all` by hand.
3721
+ */
3722
+ getAggregateInfo() {
3723
+ return {
3724
+ enabled: this._options.enableAggregateProvider !== false,
3725
+ providers: this._aggregateServer?.providerNames ?? []
3726
+ };
3727
+ }
3703
3728
  getProvidersInfo() {
3704
3729
  const out = [];
3705
3730
  for (const [name, state] of this._providers) {
@@ -3728,10 +3753,14 @@ var WsTunnel = class {
3728
3753
  transport = "none";
3729
3754
  connected = false;
3730
3755
  }
3756
+ const since = connected ? state.connectedSinceMs : null;
3731
3757
  return {
3732
3758
  name,
3733
3759
  transport,
3734
3760
  connected,
3761
+ aggregate: this._aggregateServer?.providerNames.includes(name) ?? false,
3762
+ connectedSince: since !== null ? new Date(since).toISOString() : null,
3763
+ connectedForMs: since !== null ? Math.max(0, Date.now() - since) : null,
3735
3764
  clientCount: state.wsClients.size,
3736
3765
  sessionCount: state.sseSessions.size + state.httpSessions.size,
3737
3766
  pendingCount: state.pending.size
@@ -3756,6 +3785,7 @@ var WsTunnel = class {
3756
3785
  }
3757
3786
  const state = this._getOrCreateProviderState(name);
3758
3787
  this._loopbackProviders.set(name, transport);
3788
+ state.connectedSinceMs = Date.now();
3759
3789
  transport.onMessage = (data) => this._routeFromProvider(state, name, data);
3760
3790
  transport.onClose = () => {
3761
3791
  this._loopbackProviders.delete(name);
@@ -3890,9 +3920,10 @@ var WsTunnel = class {
3890
3920
  const state = this._providers.get(cfg.name);
3891
3921
  if (state) this._failProviderDisconnected(state, cfg.name);
3892
3922
  };
3893
- if (cfg.aggregate) {
3894
- upstream.onOpen = () => void this._aggregateServer?.addProvider(cfg.name);
3895
- }
3923
+ upstream.onOpen = () => {
3924
+ this._getOrCreateProviderState(cfg.name).connectedSinceMs = Date.now();
3925
+ if (cfg.aggregate) void this._aggregateServer?.addProvider(cfg.name);
3926
+ };
3896
3927
  this._upstreams.set(cfg.name, upstream);
3897
3928
  upstream.connect();
3898
3929
  };
@@ -4880,6 +4911,7 @@ data: ${data}
4880
4911
  }
4881
4912
  const state = this._getOrCreateProviderState(name);
4882
4913
  state.ws = ws;
4914
+ state.connectedSinceMs = Date.now();
4883
4915
  this._watchProviderSocket(ws);
4884
4916
  if (providerPrincipal) {
4885
4917
  this._logProviderRegistration(providerPrincipal, name, this._slotResourceResolver.resolve(name), true);
@@ -5088,6 +5120,7 @@ data: ${data}
5088
5120
  providerNames.add(name);
5089
5121
  const state2 = this._getOrCreateProviderState(name);
5090
5122
  state2.ws = ws;
5123
+ state2.connectedSinceMs = Date.now();
5091
5124
  if (providerPrincipal) {
5092
5125
  this._logProviderRegistration(providerPrincipal, name, this._slotResourceResolver.resolve(name), true);
5093
5126
  }
@@ -5267,6 +5300,7 @@ data: ${data}
5267
5300
  * close handlers (dedicated WS, multiplexed WS, loopback).
5268
5301
  */
5269
5302
  _failProviderDisconnected(state, name) {
5303
+ state.connectedSinceMs = null;
5270
5304
  for (const entry of state.pending.values()) {
5271
5305
  const error = JSON.stringify({
5272
5306
  jsonrpc: "2.0",
@@ -5297,6 +5331,7 @@ data: ${data}
5297
5331
  if (!state) {
5298
5332
  state = {
5299
5333
  ws: null,
5334
+ connectedSinceMs: null,
5300
5335
  pending: /* @__PURE__ */ new Map(),
5301
5336
  sseSessions: /* @__PURE__ */ new Map(),
5302
5337
  httpSessions: /* @__PURE__ */ new Map(),
@@ -5951,5 +5986,5 @@ function resolveOpenTarget(raw, baseUrl) {
5951
5986
  }
5952
5987
 
5953
5988
  export { AuthError, BROKER_AGGREGATE_NAME, BROKER_GUIDES, BROKER_GUIDE_MIME_TYPE, BROKER_GUIDE_TOPICS, BROKER_GUIDE_URI_PREFIX, BROKER_GUIDE_URI_TEMPLATE, BROKER_PROVIDER_NAME, BROKER_RESERVED_SLOTS, BrokerDiagnoseAdapter, BrokerDiagnoseBehavior, BrokerGuideAdapter, BrokerGuideBehavior, BrokerInfoBehavior, BrokerProvidersBehavior, ConfigPolicyEngine, ConfiguredCapabilityClassifier, DEFAULT_CONFIG_FILENAME, DefaultSlotResourceResolver, HttpAuthGuard, JwtSubjectMapper, JwtTokenValidator, PACKAGE_NAME, RemoteUpstream, ResourcePath, ResourcePathPattern, SharedSecretProviderAuthenticator, StdioUpstream, SubjectMappingError, VERSION, WsTunnel, WsTunnelBuilder, authorizationWithEngine, brokerGrammarKey, brokerGuide, brokerGuideIndex, brokerGuideTopicFromUri, brokerGuideUri, buildJwtAuth, buildResourceMetadata, compileAuthorizationPolicy, compileProviderAllowedResources, diagnoseBroker, hasAuthorizationPolicies, isReservedBrokerSlot, iterAvailableBrokerGrammars, iterBrokerGrammarsFrom, loadBrokerConfig, loadBrokerGrammar, loadMcpbBundle, normalizeProviderAuthentication, providerMayPublish, providerPublishDecision, resolveOpenTarget, scopesOf, startBrokerServer, unzipMcpb, validateCapability };
5954
- //# sourceMappingURL=chunk-BZUZYXVA.js.map
5955
- //# sourceMappingURL=chunk-BZUZYXVA.js.map
5989
+ //# sourceMappingURL=chunk-J5TN5RYU.js.map
5990
+ //# sourceMappingURL=chunk-J5TN5RYU.js.map