@cyanmycelium/mcp-broker 1.8.0 → 1.9.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.
@@ -12,6 +12,7 @@ import { WebSocket, WebSocketServer } from 'ws';
12
12
  import { fileURLToPath } from 'url';
13
13
  import { McpAuthError, McpGrammar, McpBehavior, McpAdapterBase, McpToolResults, LoopbackTransport, McpServerBuilder, scopesOf as scopesOf$1, buildProtectedResourceMetadata, PROTECTED_RESOURCE_METADATA_PREFIX, bearerToken, buildChallengeHeader, createEventEmitter } from '@cyanmycelium/mcp-core';
14
14
  import { createRemoteJWKSet, jwtVerify, errors } from 'jose';
15
+ import { BlockList, isIP } from 'net';
15
16
  import { createRequire } from 'module';
16
17
  import { AsyncLocalStorage } from 'async_hooks';
17
18
  import { StdioTransport, StreamableHttpEndpoint } from '@cyanmycelium/mcp-core/node';
@@ -4521,6 +4522,7 @@ function buildResourceMetadata(params) {
4521
4522
  }
4522
4523
  var DEV_ISSUER = "urn:mcp-broker:dev";
4523
4524
  var DEV_TOKEN_MIN_LENGTH = 16;
4525
+ var DEV_LAN_NETWORKS = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "169.254.0.0/16", "fc00::/7", "fe80::/10"];
4524
4526
  var DEV_SUBJECT_MAPPING = { userClaim: "sub", groupClaims: ["groups"], serviceClaims: ["service"], clientClaim: "client_id" };
4525
4527
  var CALLER_KEYS = /* @__PURE__ */ new Set(["id", "tokenEnv", "user", "groups", "service", "client", "scopes"]);
4526
4528
  var digest = (token) => createHash("sha256").update(token, "utf8").digest("hex");
@@ -4528,7 +4530,7 @@ function resolveDevCallers(dev, env) {
4528
4530
  const problems = [];
4529
4531
  const callers = [];
4530
4532
  if (typeof dev !== "object" || dev === null || Array.isArray(dev)) return { callers, problems: ['"auth.dev" must be an object: { "callers": [...] }'] };
4531
- for (const key of Object.keys(dev)) if (key !== "callers") problems.push(`auth.dev: unknown key "${key}"`);
4533
+ for (const key of Object.keys(dev)) if (key !== "callers" && key !== "networks") problems.push(`auth.dev: unknown key "${key}"`);
4532
4534
  const list = dev.callers;
4533
4535
  if (!Array.isArray(list) || list.length === 0) {
4534
4536
  problems.push('"auth.dev.callers" must be a non-empty array');
@@ -4586,6 +4588,55 @@ function resolveDevCallers(dev, env) {
4586
4588
  }
4587
4589
  return { callers, problems };
4588
4590
  }
4591
+ function parseNetwork(entry) {
4592
+ const [address, prefixText, extra] = entry.trim().split("/");
4593
+ const version = isIP(address);
4594
+ if (version === 0 || extra !== void 0) return `"${entry}" is not "lan", an IPv4 or IPv6 address, or a CIDR network`;
4595
+ const max = version === 4 ? 32 : 128;
4596
+ const prefix = prefixText === void 0 ? max : /^[0-9]{1,3}$/.test(prefixText) ? Number(prefixText) : NaN;
4597
+ if (!Number.isInteger(prefix) || prefix > max) return `"${entry}": the prefix length must be an integer from 1 to ${max}`;
4598
+ if (prefix === 0) return `"${entry}" would accept every client, the internet included; write "lan" for the bench's network, or use OAuth`;
4599
+ return { address, prefix, family: version === 4 ? "ipv4" : "ipv6" };
4600
+ }
4601
+ function resolveDevNetworks(networks) {
4602
+ if (networks === void 0) return { networks: [], problems: [] };
4603
+ if (!Array.isArray(networks)) return { networks: [], problems: ['"auth.dev.networks" must be an array, e.g. ["lan"] or ["192.168.4.0/24"]'] };
4604
+ const out = [];
4605
+ const problems = [];
4606
+ for (const [index, entry] of networks.entries()) {
4607
+ if (typeof entry !== "string" || entry.trim() === "") {
4608
+ problems.push(`auth.dev.networks[${index}] must be a non-empty string`);
4609
+ continue;
4610
+ }
4611
+ for (const one of entry.trim().toLowerCase() === "lan" ? DEV_LAN_NETWORKS : [entry]) {
4612
+ const parsed = parseNetwork(one);
4613
+ if (typeof parsed === "string") problems.push(`auth.dev.networks[${index}]: ${parsed}`);
4614
+ else if (!out.includes(`${parsed.address}/${parsed.prefix}`)) out.push(`${parsed.address}/${parsed.prefix}`);
4615
+ }
4616
+ }
4617
+ return { networks: out, problems };
4618
+ }
4619
+ var DevClientFilter = class {
4620
+ _list = new BlockList();
4621
+ networks;
4622
+ constructor(networks = []) {
4623
+ const resolved = resolveDevNetworks(networks);
4624
+ if (resolved.problems.length > 0) throw new Error(resolved.problems.join("; "));
4625
+ for (const n of resolved.networks) {
4626
+ const parsed = parseNetwork(n);
4627
+ this._list.addSubnet(parsed.address, parsed.prefix, parsed.family);
4628
+ }
4629
+ this.networks = resolved.networks;
4630
+ }
4631
+ allows(req) {
4632
+ if (isLoopbackRequest(req)) return true;
4633
+ const address = req.socket?.remoteAddress;
4634
+ if (!address || this.networks.length === 0) return false;
4635
+ const plain = address.startsWith("::ffff:") ? address.slice(7) : address;
4636
+ const version = isIP(plain);
4637
+ return version !== 0 && this._list.check(plain, version === 4 ? "ipv4" : "ipv6");
4638
+ }
4639
+ };
4589
4640
  function isLoopbackRequest(req) {
4590
4641
  const address = req.socket?.remoteAddress;
4591
4642
  if (!address) return false;
@@ -4620,6 +4671,8 @@ function buildDevAuth(options) {
4620
4671
  validator: new DevTokenValidator(options.callers),
4621
4672
  loopbackOnly: true
4622
4673
  };
4674
+ const networks = new DevClientFilter(options.networks).networks;
4675
+ if (networks.length > 0) resolved.clientNetworks = networks;
4623
4676
  if (options.scopesSupported) resolved.scopesSupported = options.scopesSupported;
4624
4677
  if (options.requiredScopes) resolved.requiredScopes = options.requiredScopes;
4625
4678
  if (options.perSlotScopes) resolved.perSlotScopes = options.perSlotScopes;
@@ -4645,8 +4698,11 @@ var HttpAuthGuard = class {
4645
4698
  _auth;
4646
4699
  /** The `/mcp` suffix without a leading slash, e.g. `"mcp"`. */
4647
4700
  _mcpSuffix;
4701
+ /** The clients a `loopbackOnly` auth accepts: loopback and its `clientNetworks`. */
4702
+ _clients;
4648
4703
  constructor(auth, mcpSuffix) {
4649
4704
  this._auth = auth;
4705
+ this._clients = auth.loopbackOnly ? new DevClientFilter(auth.clientNetworks) : null;
4650
4706
  this._mcpSuffix = mcpSuffix.replace(/^\//, "");
4651
4707
  }
4652
4708
  /** Canonical resource identifier for a slot (RFC 8707 §2). */
@@ -4687,8 +4743,8 @@ var HttpAuthGuard = class {
4687
4743
  * `403` insufficient scope) otherwise.
4688
4744
  */
4689
4745
  async authorize(req, slot) {
4690
- if (this._auth.loopbackOnly && !isLoopbackRequest(req)) {
4691
- throw new AuthError(401, "invalid_token", "Development tokens are accepted from loopback clients only");
4746
+ if (this._clients && !this._clients.allows(req)) {
4747
+ throw new AuthError(401, "invalid_token", "Development tokens are not accepted from this client's network");
4692
4748
  }
4693
4749
  const token = bearerToken(req.headers["authorization"]);
4694
4750
  if (!token) {
@@ -9105,6 +9161,6 @@ var WsTunnelBuilder = class {
9105
9161
  }
9106
9162
  };
9107
9163
 
9108
- export { AuthError, BROKER_AGGREGATE_NAME, BROKER_AUDIT_RESULT_METHOD, BROKER_AUTHORIZE_METHOD, BROKER_DECLARE_METHOD, BROKER_GUIDES, BROKER_GUIDE_MIME_TYPE, BROKER_GUIDE_TOPICS, BROKER_GUIDE_URI_PREFIX, BROKER_GUIDE_URI_TEMPLATE, BROKER_METHOD_PREFIX, BROKER_PROVIDER_NAME, BROKER_RESERVED_SLOTS, BrokerAuthority, BrokerDiagnoseAdapter, BrokerDiagnoseBehavior, BrokerGuideAdapter, BrokerGuideBehavior, BrokerInfoBehavior, BrokerProvidersBehavior, CALLER_META_KEY, CORRELATION_ID_PATTERN, ConfigPolicyEngine, ConfiguredCapabilityClassifier, DEFAULT_AUTHORIZE_BATCH_LIMIT, DEFAULT_RESOURCE_SUBSCRIPTION_LIMITS, DEV_ISSUER, DEV_TOKEN_MIN_LENGTH, DefaultSlotResourceResolver, DevTokenValidator, HttpAuthGuard, JwtSubjectMapper, JwtTokenValidator, LimitController, OtlpHttpTraceExporter, PACKAGE_NAME, ProviderTableAuthenticator, ProviderTelemetryDispatcher, RemoteUpstream, ResourcePath, ResourcePathPattern, ResourceSubscriptionRegistry, SUBSCRIPTION_LIMIT_ERROR_CODE, SharedSecretProviderAuthenticator, StdioUpstream, SubjectMappingError, TELEMETRY_NOTIFICATION_METHOD, TRACEPARENT_META_KEY, VERSION, WsTunnel, WsTunnelBuilder, authorizationWithEngine, brokerGrammarKey, brokerGuide, brokerGuideIndex, brokerGuideTopicFromUri, brokerGuideUri, buildDevAuth, buildJwtAuth, buildResourceMetadata, compileAuthorizationPolicy, compileProviderAllowedResources, compileRegex, compileResourceLimits, createTraceparent, diagnoseBroker, ensureTraceparent, formatTraceparent, hasAuthorizationPolicies, isLoopbackRequest, isReservedBrokerSlot, iterAvailableBrokerGrammars, iterBrokerGrammarsFrom, loadBrokerGrammar, normalizeProviderAuthentication, parseTraceparent, providerMayPublish, providerPublishDecision, resolveDevCallers, scopesOf, startBrokerServer, validateCapability, validateLimitsConfig };
9109
- //# sourceMappingURL=chunk-ACF2R4O3.js.map
9110
- //# sourceMappingURL=chunk-ACF2R4O3.js.map
9164
+ export { AuthError, BROKER_AGGREGATE_NAME, BROKER_AUDIT_RESULT_METHOD, BROKER_AUTHORIZE_METHOD, BROKER_DECLARE_METHOD, BROKER_GUIDES, BROKER_GUIDE_MIME_TYPE, BROKER_GUIDE_TOPICS, BROKER_GUIDE_URI_PREFIX, BROKER_GUIDE_URI_TEMPLATE, BROKER_METHOD_PREFIX, BROKER_PROVIDER_NAME, BROKER_RESERVED_SLOTS, BrokerAuthority, BrokerDiagnoseAdapter, BrokerDiagnoseBehavior, BrokerGuideAdapter, BrokerGuideBehavior, BrokerInfoBehavior, BrokerProvidersBehavior, CALLER_META_KEY, CORRELATION_ID_PATTERN, ConfigPolicyEngine, ConfiguredCapabilityClassifier, DEFAULT_AUTHORIZE_BATCH_LIMIT, DEFAULT_RESOURCE_SUBSCRIPTION_LIMITS, DEV_ISSUER, DEV_LAN_NETWORKS, DEV_TOKEN_MIN_LENGTH, DefaultSlotResourceResolver, DevClientFilter, DevTokenValidator, HttpAuthGuard, JwtSubjectMapper, JwtTokenValidator, LimitController, OtlpHttpTraceExporter, PACKAGE_NAME, ProviderTableAuthenticator, ProviderTelemetryDispatcher, RemoteUpstream, ResourcePath, ResourcePathPattern, ResourceSubscriptionRegistry, SUBSCRIPTION_LIMIT_ERROR_CODE, SharedSecretProviderAuthenticator, StdioUpstream, SubjectMappingError, TELEMETRY_NOTIFICATION_METHOD, TRACEPARENT_META_KEY, VERSION, WsTunnel, WsTunnelBuilder, authorizationWithEngine, brokerGrammarKey, brokerGuide, brokerGuideIndex, brokerGuideTopicFromUri, brokerGuideUri, buildDevAuth, buildJwtAuth, buildResourceMetadata, compileAuthorizationPolicy, compileProviderAllowedResources, compileRegex, compileResourceLimits, createTraceparent, diagnoseBroker, ensureTraceparent, formatTraceparent, hasAuthorizationPolicies, isLoopbackRequest, isReservedBrokerSlot, iterAvailableBrokerGrammars, iterBrokerGrammarsFrom, loadBrokerGrammar, normalizeProviderAuthentication, parseTraceparent, providerMayPublish, providerPublishDecision, resolveDevCallers, resolveDevNetworks, scopesOf, startBrokerServer, validateCapability, validateLimitsConfig };
9165
+ //# sourceMappingURL=chunk-WEUCJH6C.js.map
9166
+ //# sourceMappingURL=chunk-WEUCJH6C.js.map