drupal-mcp-connector 2.5.0 → 2.6.1

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.
@@ -343,16 +343,39 @@ export function assertConfigWriteAllowed(secConfig) {
343
343
  }
344
344
 
345
345
  /**
346
- * Whether the site's OAuth token carries a given scope. When the site declares
347
- * no OAuth scopes (no agent channel configured), scope gating is a no-op and
348
- * this returns true so preset-only (non-OAuth) setups are unaffected.
346
+ * Whether the site's OAuth token carries a given scope. A governed setup that
347
+ * names no scopes carries none of them (#180) an unnamed grant is not a
348
+ * wildcard. A preset-only, non-OAuth site keeps the permissive behaviour.
349
349
  * @param {object} site Resolved site config.
350
350
  * @param {string} scope OAuth scope machine id (e.g. "mcp_config").
351
351
  * @returns {boolean} True if the scope is present, or no scopes are configured.
352
352
  */
353
353
  export function hasScope(site, scope) {
354
354
  const scopes = site?.oauth?.scopes ?? [];
355
- return scopes.length === 0 || scopes.includes(scope);
355
+ if (scopes.length > 0) return scopes.includes(scope);
356
+ // Empty or absent scopes. For a GOVERNED product setup — a site that claims
357
+ // source governance, or configures OAuth at all — that is not "every scope";
358
+ // it is an unnamed grant, and treating it as a wildcard was a bypass of the
359
+ // very gate the scopes exist to be (#180). Deny, so the operator has to name
360
+ // the scopes the token actually carries.
361
+ //
362
+ // An ungoverned install (a plain apiToken or anonymous site, no OAuth block)
363
+ // has no scope vocabulary to name and keeps the historical permissive
364
+ // behaviour: preset semantics alone decide there.
365
+ return !isGovernedSetup(site);
366
+ }
367
+
368
+ /**
369
+ * Whether a site is part of the governed product path.
370
+ *
371
+ * Either it declares the source-governance requirement, or it authenticates
372
+ * with OAuth — both mean the server is deciding on scopes, so the connector
373
+ * must not invent one.
374
+ * @param {object} site Resolved site config.
375
+ * @returns {boolean}
376
+ */
377
+ export function isGovernedSetup(site) {
378
+ return site?.requireGovernance === true || Boolean(site?.oauth);
356
379
  }
357
380
 
358
381
  /**