@adcp/sdk 7.0.0 → 7.2.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/bin/adcp-config.js +10 -1
- package/bin/adcp.js +424 -22
- package/dist/lib/auth/oauth/authorization-required.d.ts +17 -0
- package/dist/lib/auth/oauth/authorization-required.d.ts.map +1 -1
- package/dist/lib/auth/oauth/authorization-required.js +20 -0
- package/dist/lib/auth/oauth/authorization-required.js.map +1 -1
- package/dist/lib/auth/oauth/index.d.ts +1 -1
- package/dist/lib/auth/oauth/index.d.ts.map +1 -1
- package/dist/lib/auth/oauth/index.js +2 -1
- package/dist/lib/auth/oauth/index.js.map +1 -1
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.js +13 -2
- package/dist/lib/core/SingleAgentClient.js.map +1 -1
- package/dist/lib/discovery/property-crawler.d.ts +13 -1
- package/dist/lib/discovery/property-crawler.d.ts.map +1 -1
- package/dist/lib/discovery/property-crawler.js +40 -14
- package/dist/lib/discovery/property-crawler.js.map +1 -1
- package/dist/lib/discovery/resolve-agent-properties.d.ts +103 -0
- package/dist/lib/discovery/resolve-agent-properties.d.ts.map +1 -0
- package/dist/lib/discovery/resolve-agent-properties.js +182 -0
- package/dist/lib/discovery/resolve-agent-properties.js.map +1 -0
- package/dist/lib/discovery/types.d.ts +41 -2
- package/dist/lib/discovery/types.d.ts.map +1 -1
- package/dist/lib/discovery/types.js +2 -1
- package/dist/lib/discovery/types.js.map +1 -1
- package/dist/lib/discovery/validate-adagents.d.ts +114 -0
- package/dist/lib/discovery/validate-adagents.d.ts.map +1 -0
- package/dist/lib/discovery/validate-adagents.js +417 -0
- package/dist/lib/discovery/validate-adagents.js.map +1 -0
- package/dist/lib/errors/index.d.ts +42 -5
- package/dist/lib/errors/index.d.ts.map +1 -1
- package/dist/lib/errors/index.js +64 -9
- package/dist/lib/errors/index.js.map +1 -1
- package/dist/lib/index.d.ts +3 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +17 -10
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/protocols/a2a.d.ts.map +1 -1
- package/dist/lib/protocols/a2a.js +70 -11
- package/dist/lib/protocols/a2a.js.map +1 -1
- package/dist/lib/protocols/mcp.d.ts.map +1 -1
- package/dist/lib/protocols/mcp.js +61 -5
- package/dist/lib/protocols/mcp.js.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/fetch-async.d.ts.map +1 -1
- package/dist/lib/signing/fetch-async.js +5 -0
- package/dist/lib/signing/fetch-async.js.map +1 -1
- package/dist/lib/signing/fetch.d.ts.map +1 -1
- package/dist/lib/signing/fetch.js +11 -0
- package/dist/lib/signing/fetch.js.map +1 -1
- package/dist/lib/testing/client.d.ts +16 -2
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +1 -0
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts +17 -2
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +38 -0
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.d.ts.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.js +1 -0
- package/dist/lib/testing/compliance/spec-conformance.js.map +1 -1
- package/dist/lib/testing/compliance/types.d.ts +11 -0
- package/dist/lib/testing/compliance/types.d.ts.map +1 -1
- package/dist/lib/testing/index.d.ts +1 -1
- package/dist/lib/testing/index.d.ts.map +1 -1
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.js +21 -2
- package/dist/lib/testing/storyboard/default-invariants.js.map +1 -1
- package/dist/lib/testing/storyboard/index.d.ts +1 -1
- package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/index.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +284 -16
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/types.d.ts +112 -1
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js +1 -0
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/utils/response-unwrapper.d.ts +33 -0
- package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +41 -3
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/version.d.ts +10 -6
- package/dist/lib/version.d.ts.map +1 -1
- package/dist/lib/version.js +11 -4
- package/dist/lib/version.js.map +1 -1
- package/docs/llms.txt +10 -2
- package/package.json +1 -1
- package/skills/call-adcp-agent/SKILL.md +1 -1
package/bin/adcp.js
CHANGED
|
@@ -266,6 +266,163 @@ function mergeHeaders(savedHeaders, cliHeaders) {
|
|
|
266
266
|
return Object.keys(filtered).length > 0 ? filtered : undefined;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Parse `--auth-scheme bearer|basic`. Defaults to `bearer` (preserving the
|
|
271
|
+
* pre-existing CLI contract where `--auth TOKEN` is always a bearer). When
|
|
272
|
+
* the flag is absent, falls back to the `ADCP_AUTH_SCHEME` env var so CI
|
|
273
|
+
* jobs can flip the scheme without rewriting their command line. Returns
|
|
274
|
+
* `null` when neither the flag nor the env var supplied a value — callers
|
|
275
|
+
* use that to defer to a saved alias's `auth_scheme`.
|
|
276
|
+
*/
|
|
277
|
+
function parseAuthSchemeFlag(args) {
|
|
278
|
+
let value = null;
|
|
279
|
+
let source = null;
|
|
280
|
+
// Long-form: --auth-scheme VALUE
|
|
281
|
+
const longIdx = args.indexOf('--auth-scheme');
|
|
282
|
+
if (longIdx !== -1) {
|
|
283
|
+
if (longIdx + 1 >= args.length || args[longIdx + 1].startsWith('--')) {
|
|
284
|
+
console.error('ERROR: --auth-scheme requires a value (bearer|basic)\n');
|
|
285
|
+
process.exit(2);
|
|
286
|
+
}
|
|
287
|
+
value = args[longIdx + 1];
|
|
288
|
+
source = 'flag';
|
|
289
|
+
}
|
|
290
|
+
// Single-token form: --auth-scheme=VALUE. Security-reviewer L3 from PR #1719
|
|
291
|
+
// flagged that the long-form path treats `--auth-scheme=basic` (no space) as
|
|
292
|
+
// an unknown arg, which silently falls through to env-var lookup. Match the
|
|
293
|
+
// other flags' equals-form by checking for the literal prefix explicitly.
|
|
294
|
+
if (value === null) {
|
|
295
|
+
const eqArg = args.find(a => a.startsWith('--auth-scheme='));
|
|
296
|
+
if (eqArg) {
|
|
297
|
+
value = eqArg.slice('--auth-scheme='.length);
|
|
298
|
+
source = 'flag';
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (value === null && process.env.ADCP_AUTH_SCHEME) {
|
|
302
|
+
value = process.env.ADCP_AUTH_SCHEME;
|
|
303
|
+
source = 'env';
|
|
304
|
+
}
|
|
305
|
+
if (value === null) return null;
|
|
306
|
+
if (value !== 'bearer' && value !== 'basic') {
|
|
307
|
+
// Name the source in the error so the operator knows whether to fix the
|
|
308
|
+
// flag invocation or the environment variable.
|
|
309
|
+
const sourceLabel = source === 'env' ? 'ADCP_AUTH_SCHEME env var' : '--auth-scheme';
|
|
310
|
+
console.error(`ERROR: ${sourceLabel} must be 'bearer' or 'basic', got: ${value}\n`);
|
|
311
|
+
process.exit(2);
|
|
312
|
+
}
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Warn when `ADCP_AUTH_SCHEME` was set in the environment but the resolved
|
|
318
|
+
* scheme didn't end up applied (because no token / no credential resolved to
|
|
319
|
+
* the request). The inverse case (token without scheme → silent bearer) is
|
|
320
|
+
* the safe direction and gets no warning.
|
|
321
|
+
*
|
|
322
|
+
* Called once per top-level invocation, after auth resolution completes.
|
|
323
|
+
* The check is purely advisory; it doesn't change behavior, just surfaces a
|
|
324
|
+
* likely misconfiguration before the user wonders why their Basic gateway
|
|
325
|
+
* keeps 401ing.
|
|
326
|
+
*/
|
|
327
|
+
function maybeWarnAuthSchemeIneffective(resolvedAuthToken, resolvedAuthScheme) {
|
|
328
|
+
const envScheme = process.env.ADCP_AUTH_SCHEME;
|
|
329
|
+
if (!envScheme) return;
|
|
330
|
+
if (envScheme !== 'bearer' && envScheme !== 'basic') return; // parse error already exited
|
|
331
|
+
// Effective: a token resolved AND the resolved scheme matches what the env
|
|
332
|
+
// asked for. If we have no token, basic is meaningless; if we have a token
|
|
333
|
+
// but the scheme resolved differently (e.g. saved alias overrode), the env
|
|
334
|
+
// is no-op-shadowed and worth flagging.
|
|
335
|
+
const effective = !!resolvedAuthToken && resolvedAuthScheme === envScheme;
|
|
336
|
+
if (!effective) {
|
|
337
|
+
console.error(
|
|
338
|
+
`Warning: ADCP_AUTH_SCHEME=${envScheme} is set but did not apply ` +
|
|
339
|
+
`(${!resolvedAuthToken ? 'no --auth / saved auth_token resolved' : `resolved scheme is '${resolvedAuthScheme}'`}). ` +
|
|
340
|
+
`Pass --auth (or an alias with credentials) to use the env-supplied scheme.`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Split a `user:pass` credential string and validate per RFC 7617:
|
|
347
|
+
* - userid must not contain `:` (a colon would decode ambiguously on the
|
|
348
|
+
* server, and undici/curl would mis-parse the cached header).
|
|
349
|
+
* - CR, LF, and non-printable ASCII are rejected — these are the same
|
|
350
|
+
* header-smuggling shapes parseHeaderFlags refuses on `-H` input.
|
|
351
|
+
*
|
|
352
|
+
* Returns `{ username, password }`. Exits 2 with a stderr message on any
|
|
353
|
+
* validation failure so the failure mode matches the rest of the CLI's
|
|
354
|
+
* argument validation (no half-encoded header reaches the wire).
|
|
355
|
+
*/
|
|
356
|
+
function decodeBasicCredentials(userpass, source = '--auth') {
|
|
357
|
+
if (typeof userpass !== 'string' || userpass.length === 0) {
|
|
358
|
+
console.error(`ERROR: ${source} value for basic auth must be 'user:pass' (got empty value)\n`);
|
|
359
|
+
process.exit(2);
|
|
360
|
+
}
|
|
361
|
+
const colon = userpass.indexOf(':');
|
|
362
|
+
if (colon === -1) {
|
|
363
|
+
// Don't echo the credential itself — even a length leak is enough to help
|
|
364
|
+
// someone reasoning about the value off-screen. The fix is the same
|
|
365
|
+
// regardless of what they passed in.
|
|
366
|
+
console.error(`ERROR: ${source} basic-auth credential must be in 'user:pass' form (no ':' found)\n`);
|
|
367
|
+
process.exit(2);
|
|
368
|
+
}
|
|
369
|
+
const username = userpass.slice(0, colon);
|
|
370
|
+
const password = userpass.slice(colon + 1);
|
|
371
|
+
if (username.length === 0) {
|
|
372
|
+
console.error(`ERROR: ${source} basic auth username must not be empty\n`);
|
|
373
|
+
process.exit(2);
|
|
374
|
+
}
|
|
375
|
+
if (username.includes(':')) {
|
|
376
|
+
// Impossible from a single-colon split, but kept as a defensive guard
|
|
377
|
+
// in case a future refactor changes the split logic.
|
|
378
|
+
console.error(`ERROR: ${source} basic auth username must not contain ':' (RFC 7617)\n`);
|
|
379
|
+
process.exit(2);
|
|
380
|
+
}
|
|
381
|
+
const badChar = /[\r\n\0]|[^\x20-\x7E]/;
|
|
382
|
+
if (badChar.test(username)) {
|
|
383
|
+
console.error(`ERROR: ${source} basic auth username contains CR, LF, NUL, or non-printable ASCII\n`);
|
|
384
|
+
process.exit(2);
|
|
385
|
+
}
|
|
386
|
+
if (badChar.test(password)) {
|
|
387
|
+
console.error(`ERROR: ${source} basic auth password contains CR, LF, NUL, or non-printable ASCII\n`);
|
|
388
|
+
process.exit(2);
|
|
389
|
+
}
|
|
390
|
+
return { username, password };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Encode RFC 7617 `Authorization: Basic <base64(user:pass)>` from a validated
|
|
395
|
+
* `{username, password}` pair. Caller MUST have run `decodeBasicCredentials`
|
|
396
|
+
* first — this helper does not re-validate.
|
|
397
|
+
*/
|
|
398
|
+
function encodeBasicAuthHeader({ username, password }) {
|
|
399
|
+
return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Inject `Authorization: Basic <base64(user:pass)>` into the header map the
|
|
404
|
+
* CLI hands to the SDK. Called AFTER `mergeHeaders()` runs so the
|
|
405
|
+
* reserved-key filter doesn't strip the injected Authorization. The protocol
|
|
406
|
+
* layer (`src/lib/protocols/mcp.ts` and `protocols/a2a.ts`) spreads
|
|
407
|
+
* `customHeaders` BEFORE SDK-supplied Authorization, so this injected value
|
|
408
|
+
* reaches the wire intact only when the caller has suppressed `auth_token`
|
|
409
|
+
* (the bearer-path overrides Authorization in that case).
|
|
410
|
+
*
|
|
411
|
+
* **Invariant for future contributors**: this MUST be called after
|
|
412
|
+
* `mergeHeaders()` and the bearer-path `auth_token` must be omitted when
|
|
413
|
+
* `useBasicAuth` is true. Moving the injection inside `mergeHeaders()` would
|
|
414
|
+
* silently drop the header (reserved-key filter is case-insensitive on
|
|
415
|
+
* `authorization`). The test at
|
|
416
|
+
* `test/lib/cli-auth-scheme.test.js:'wire test'` is the regression guard —
|
|
417
|
+
* if a refactor moves the injection wrong, that test stops sending Basic.
|
|
418
|
+
*
|
|
419
|
+
* Returns the merged header bag (always defined, even if empty in).
|
|
420
|
+
*/
|
|
421
|
+
function injectBasicAuthHeader(mergedHeaders, userpass, source = '--auth') {
|
|
422
|
+
const { username, password } = decodeBasicCredentials(userpass, source);
|
|
423
|
+
return { ...(mergedHeaders || {}), Authorization: encodeBasicAuthHeader({ username, password }) };
|
|
424
|
+
}
|
|
425
|
+
|
|
269
426
|
/**
|
|
270
427
|
* Extract human-readable protocol message from conversation
|
|
271
428
|
*/
|
|
@@ -439,6 +596,10 @@ async function handleTestCommand(args) {
|
|
|
439
596
|
}
|
|
440
597
|
authToken = args[authIndex + 1];
|
|
441
598
|
}
|
|
599
|
+
// `--auth-scheme bearer|basic` selects how `--auth` (or the saved alias's
|
|
600
|
+
// auth_token) is sent. Default null → defer to saved alias's `auth_scheme`,
|
|
601
|
+
// then fall back to `bearer`.
|
|
602
|
+
const authScheme = parseAuthSchemeFlag(args);
|
|
442
603
|
|
|
443
604
|
// adcp-client#1612: accept `--transport` as an alias for `--protocol`.
|
|
444
605
|
// The original symptom report used `--transport a2a` (per A2A SDK
|
|
@@ -476,7 +637,7 @@ async function handleTestCommand(args) {
|
|
|
476
637
|
|
|
477
638
|
// Filter out flag arguments to find positional arguments
|
|
478
639
|
const positionalArgs = args.filter(
|
|
479
|
-
arg => !arg.startsWith('--') && arg !== authToken && arg !== protocolFlag && arg !== brief
|
|
640
|
+
arg => !arg.startsWith('--') && arg !== authToken && arg !== authScheme && arg !== protocolFlag && arg !== brief
|
|
480
641
|
);
|
|
481
642
|
|
|
482
643
|
if (positionalArgs.length === 0) {
|
|
@@ -507,6 +668,7 @@ async function handleTestCommand(args) {
|
|
|
507
668
|
let agentUrl;
|
|
508
669
|
let protocol = protocolFlag;
|
|
509
670
|
let finalAuthToken = authToken;
|
|
671
|
+
let finalAuthScheme = authScheme; // null = defer to alias's saved scheme
|
|
510
672
|
let oauthTokens = null;
|
|
511
673
|
|
|
512
674
|
// Resolve agent
|
|
@@ -520,6 +682,9 @@ async function handleTestCommand(args) {
|
|
|
520
682
|
agentUrl = savedAgent.url;
|
|
521
683
|
protocol = protocol || savedAgent.protocol;
|
|
522
684
|
finalAuthToken = finalAuthToken || getEffectiveAuthToken(savedAgent);
|
|
685
|
+
if (finalAuthScheme === null && savedAgent.auth_scheme) {
|
|
686
|
+
finalAuthScheme = savedAgent.auth_scheme;
|
|
687
|
+
}
|
|
523
688
|
if (savedAgent.oauth_client_credentials) {
|
|
524
689
|
// Client credentials: tokens are refreshed inside `ProtocolClient.callTool`
|
|
525
690
|
// via `ensureClientCredentialsTokens`. Surface cached tokens so the call
|
|
@@ -583,11 +748,12 @@ async function handleTestCommand(args) {
|
|
|
583
748
|
}
|
|
584
749
|
}
|
|
585
750
|
|
|
586
|
-
// Build test options
|
|
751
|
+
// Build test options. `buildResolvedAuthOption` handles the bearer/basic
|
|
752
|
+
// split based on the resolved scheme (default: bearer).
|
|
587
753
|
const testOptions = {
|
|
588
754
|
protocol,
|
|
589
755
|
brief,
|
|
590
|
-
...(
|
|
756
|
+
...buildResolvedAuthOption({ resolvedAuth: finalAuthToken, resolvedAuthScheme: finalAuthScheme || 'bearer' }),
|
|
591
757
|
};
|
|
592
758
|
|
|
593
759
|
if (!jsonOutput) {
|
|
@@ -713,6 +879,9 @@ function parseAgentOptions(args) {
|
|
|
713
879
|
if (authIndex !== -1 && authIndex + 1 < args.length && !args[authIndex + 1].startsWith('--')) {
|
|
714
880
|
authToken = args[authIndex + 1];
|
|
715
881
|
}
|
|
882
|
+
// `--auth-scheme bearer|basic` selects the scheme used to send `--auth`.
|
|
883
|
+
// Default (null) defers to the saved alias's `auth_scheme`, then to `bearer`.
|
|
884
|
+
const authScheme = parseAuthSchemeFlag(args);
|
|
716
885
|
|
|
717
886
|
// adcp-client#1612: accept `--transport` as an alias for `--protocol`.
|
|
718
887
|
// See parseStorboardArgs for full rationale.
|
|
@@ -894,6 +1063,7 @@ function parseAgentOptions(args) {
|
|
|
894
1063
|
// so falsy-but-valid values (e.g. port "0") aren't dropped from the filter.
|
|
895
1064
|
const flagValues = [
|
|
896
1065
|
authToken,
|
|
1066
|
+
authScheme,
|
|
897
1067
|
protocolFlag,
|
|
898
1068
|
brief,
|
|
899
1069
|
contextValue,
|
|
@@ -921,6 +1091,7 @@ function parseAgentOptions(args) {
|
|
|
921
1091
|
|
|
922
1092
|
return {
|
|
923
1093
|
authToken,
|
|
1094
|
+
authScheme,
|
|
924
1095
|
protocolFlag,
|
|
925
1096
|
brief,
|
|
926
1097
|
file,
|
|
@@ -1105,6 +1276,7 @@ async function ensureOAuthTokensForAlias(alias, url, { quiet = false, allowHttp
|
|
|
1105
1276
|
*/
|
|
1106
1277
|
function buildResolvedAuthOption({
|
|
1107
1278
|
resolvedAuth,
|
|
1279
|
+
resolvedAuthScheme,
|
|
1108
1280
|
resolvedOauthTokens,
|
|
1109
1281
|
resolvedOauthClient,
|
|
1110
1282
|
resolvedOauthClientCredentials,
|
|
@@ -1128,6 +1300,21 @@ function buildResolvedAuthOption({
|
|
|
1128
1300
|
};
|
|
1129
1301
|
}
|
|
1130
1302
|
if (resolvedAuth) {
|
|
1303
|
+
if (resolvedAuthScheme === 'basic') {
|
|
1304
|
+
// `resolvedAuth` is the raw `user:pass` string (from `--auth` or the
|
|
1305
|
+
// saved alias's `auth_token`). Decode it here so both the storyboard
|
|
1306
|
+
// runner and `createTestClient` receive the `type: 'basic'` shape they
|
|
1307
|
+
// already know how to send (`src/lib/testing/storyboard/runner.ts:4176`
|
|
1308
|
+
// and `src/lib/testing/client.ts:155`). Most paths validated at
|
|
1309
|
+
// register/parse time already, so this is a second-line defense; the
|
|
1310
|
+
// source string names the alias-resolved origin so a malformed
|
|
1311
|
+
// hand-edited config surfaces with the right hint instead of "--auth".
|
|
1312
|
+
const { username, password } = decodeBasicCredentials(
|
|
1313
|
+
resolvedAuth,
|
|
1314
|
+
'resolved basic credential (saved alias or --auth)'
|
|
1315
|
+
);
|
|
1316
|
+
return { auth: { type: 'basic', username, password } };
|
|
1317
|
+
}
|
|
1131
1318
|
return { auth: { type: 'bearer', token: resolvedAuth } };
|
|
1132
1319
|
}
|
|
1133
1320
|
return {};
|
|
@@ -1149,10 +1336,11 @@ function buildResolvedAuthOption({
|
|
|
1149
1336
|
* function deliberately does NOT exchange — it just surfaces the saved
|
|
1150
1337
|
* credentials so the caller can hand them to the testing/protocol layer.
|
|
1151
1338
|
*/
|
|
1152
|
-
async function resolveAgent(agentArg, authToken, protocolFlag, jsonOutput) {
|
|
1339
|
+
async function resolveAgent(agentArg, authToken, protocolFlag, jsonOutput, authScheme = null) {
|
|
1153
1340
|
let agentUrl;
|
|
1154
1341
|
let protocol = protocolFlag;
|
|
1155
1342
|
let finalAuthToken = authToken;
|
|
1343
|
+
let finalAuthScheme = authScheme; // Explicit CLI flag wins; null = defer.
|
|
1156
1344
|
let oauthTokens;
|
|
1157
1345
|
let oauthClient;
|
|
1158
1346
|
let oauthClientCredentials;
|
|
@@ -1164,6 +1352,8 @@ async function resolveAgent(agentArg, authToken, protocolFlag, jsonOutput) {
|
|
|
1164
1352
|
agentUrl = builtIn.url;
|
|
1165
1353
|
protocol = protocol || builtIn.protocol;
|
|
1166
1354
|
finalAuthToken = finalAuthToken || builtIn.auth_token;
|
|
1355
|
+
// Built-ins are bearer-only — no basic-auth gateway in front of them.
|
|
1356
|
+
if (finalAuthScheme === null) finalAuthScheme = 'bearer';
|
|
1167
1357
|
} else if (isAlias(agentArg)) {
|
|
1168
1358
|
const savedAgent = getAgent(agentArg);
|
|
1169
1359
|
agentUrl = savedAgent.url;
|
|
@@ -1177,6 +1367,9 @@ async function resolveAgent(agentArg, authToken, protocolFlag, jsonOutput) {
|
|
|
1177
1367
|
oauthClient = savedAgent.oauth_client;
|
|
1178
1368
|
}
|
|
1179
1369
|
finalAuthToken = finalAuthToken || getEffectiveAuthToken(savedAgent);
|
|
1370
|
+
if (finalAuthScheme === null && savedAgent.auth_scheme) {
|
|
1371
|
+
finalAuthScheme = savedAgent.auth_scheme;
|
|
1372
|
+
}
|
|
1180
1373
|
if (savedAgent.headers && Object.keys(savedAgent.headers).length > 0) {
|
|
1181
1374
|
savedHeaders = { ...savedAgent.headers };
|
|
1182
1375
|
}
|
|
@@ -1205,6 +1398,11 @@ async function resolveAgent(agentArg, authToken, protocolFlag, jsonOutput) {
|
|
|
1205
1398
|
agentUrl,
|
|
1206
1399
|
protocol,
|
|
1207
1400
|
authToken: finalAuthToken,
|
|
1401
|
+
// Default to 'bearer' so callers can compare without null-checking. The
|
|
1402
|
+
// distinction between an explicit `bearer` and an absent flag is only
|
|
1403
|
+
// meaningful during alias resolution above — once we've returned, the
|
|
1404
|
+
// scheme has been resolved.
|
|
1405
|
+
authScheme: finalAuthScheme || 'bearer',
|
|
1208
1406
|
oauthTokens,
|
|
1209
1407
|
oauthClient,
|
|
1210
1408
|
oauthClientCredentials,
|
|
@@ -1387,6 +1585,7 @@ QUICK START:
|
|
|
1387
1585
|
AGENT MANAGEMENT:
|
|
1388
1586
|
--save-auth <alias> [url] Save agent with alias
|
|
1389
1587
|
Static bearer: --auth <token> | --no-auth
|
|
1588
|
+
HTTP Basic: --auth <user:pass> --auth-scheme basic
|
|
1390
1589
|
OAuth (browser): --oauth
|
|
1391
1590
|
OAuth (M2M): --client-id <id> | --client-id-env <VAR>
|
|
1392
1591
|
--client-secret <s> | --client-secret-env <VAR>
|
|
@@ -1399,7 +1598,11 @@ AGENT MANAGEMENT:
|
|
|
1399
1598
|
OPTIONS:
|
|
1400
1599
|
--protocol PROTO Force protocol: mcp or a2a (default: auto-detect)
|
|
1401
1600
|
--transport PROTO Alias for --protocol (A2A SDK convention)
|
|
1402
|
-
--auth TOKEN Authentication token
|
|
1601
|
+
--auth TOKEN Authentication token (or 'user:pass' with --auth-scheme basic)
|
|
1602
|
+
--auth-scheme bearer|basic
|
|
1603
|
+
Send --auth as Bearer (default) or RFC 7617 Basic for
|
|
1604
|
+
gateway-fronted agents. Env: ADCP_AUTH_SCHEME. Details:
|
|
1605
|
+
"adcp --save-auth --help".
|
|
1403
1606
|
-H, --header K=V Extra HTTP header on every request (repeatable). Auth wins on conflict.
|
|
1404
1607
|
Common use: -H x-adcp-tenant=<id> for tenant routing behind a reverse proxy.
|
|
1405
1608
|
--oauth OAuth authentication (MCP only, opens browser)
|
|
@@ -1519,7 +1722,11 @@ OPTIONS:
|
|
|
1519
1722
|
--context JSON Pass context from previous step (step only)
|
|
1520
1723
|
--request JSON Override sample_request for the step (step only)
|
|
1521
1724
|
--json JSON output (recommended for LLM consumption)
|
|
1522
|
-
--auth TOKEN Authentication token
|
|
1725
|
+
--auth TOKEN Authentication token (or 'user:pass' with --auth-scheme basic)
|
|
1726
|
+
--auth-scheme bearer|basic
|
|
1727
|
+
How --auth is sent (default: bearer). Use 'basic' for
|
|
1728
|
+
gateways that require RFC 7617 Authorization: Basic
|
|
1729
|
+
instead of Authorization: Bearer.
|
|
1523
1730
|
--oauth Run the browser OAuth flow inline if the saved alias
|
|
1524
1731
|
has no valid tokens yet. Requires a saved alias
|
|
1525
1732
|
(use --save-auth first for raw URLs). MCP only.
|
|
@@ -2067,7 +2274,17 @@ function enforceStrictFlags(args, removedFound) {
|
|
|
2067
2274
|
|
|
2068
2275
|
async function handleStoryboardRun(args) {
|
|
2069
2276
|
const opts = parseAgentOptions(args);
|
|
2070
|
-
const {
|
|
2277
|
+
const {
|
|
2278
|
+
authToken,
|
|
2279
|
+
authScheme,
|
|
2280
|
+
protocolFlag,
|
|
2281
|
+
jsonOutput,
|
|
2282
|
+
dryRun,
|
|
2283
|
+
positionalArgs,
|
|
2284
|
+
file: filePath,
|
|
2285
|
+
localAgent,
|
|
2286
|
+
format,
|
|
2287
|
+
} = opts;
|
|
2071
2288
|
|
|
2072
2289
|
enforceStrictFlags(args, warnRemovedFlags(args));
|
|
2073
2290
|
|
|
@@ -2152,11 +2369,12 @@ async function handleStoryboardRun(args) {
|
|
|
2152
2369
|
agentUrl,
|
|
2153
2370
|
protocol,
|
|
2154
2371
|
authToken: resolvedAuth,
|
|
2372
|
+
authScheme: resolvedAuthScheme,
|
|
2155
2373
|
oauthTokens: resolvedOauthTokens,
|
|
2156
2374
|
oauthClient: resolvedOauthClient,
|
|
2157
2375
|
oauthClientCredentials: resolvedOauthClientCredentials,
|
|
2158
2376
|
savedHeaders,
|
|
2159
|
-
} = await resolveAgent(agentArg, authToken, protocolFlag, jsonOutput);
|
|
2377
|
+
} = await resolveAgent(agentArg, authToken, protocolFlag, jsonOutput, authScheme);
|
|
2160
2378
|
|
|
2161
2379
|
const mergedRunHeaders = mergeHeaders(savedHeaders, opts.customHeaders);
|
|
2162
2380
|
|
|
@@ -2222,6 +2440,7 @@ async function handleStoryboardRun(args) {
|
|
|
2222
2440
|
protocol,
|
|
2223
2441
|
...buildResolvedAuthOption({
|
|
2224
2442
|
resolvedAuth,
|
|
2443
|
+
resolvedAuthScheme,
|
|
2225
2444
|
resolvedOauthTokens,
|
|
2226
2445
|
resolvedOauthClient,
|
|
2227
2446
|
resolvedOauthClientCredentials,
|
|
@@ -2310,6 +2529,7 @@ async function handleStoryboardRun(args) {
|
|
|
2310
2529
|
`${overallIcon} ${result.passed_count} passed, ${result.failed_count} failed, ${result.skipped_count} skipped (${result.total_duration_ms}ms)${hintTail}`
|
|
2311
2530
|
);
|
|
2312
2531
|
printStrictSummary(result.strict_validation_summary);
|
|
2532
|
+
printNotices(result.notices);
|
|
2313
2533
|
}
|
|
2314
2534
|
|
|
2315
2535
|
if (opts.softFail && !result.overall_passed) printSoftFailBlock([result.storyboard_id], jsonOutput);
|
|
@@ -3135,6 +3355,23 @@ async function handleLocalAgentStoryboardRun(modulePath, args, opts) {
|
|
|
3135
3355
|
`\n${overallIcon} ${result.passed_count} passed, ${result.failed_count} failed, ${result.skipped_count} skipped across ${result.results.length} storyboard(s)${hintTail}`
|
|
3136
3356
|
);
|
|
3137
3357
|
printStrictSummary(aggregateStrictSummaries(result.results.map(r => r.strict_validation_summary)));
|
|
3358
|
+
// Aggregate notices across all storyboards in the run, deduped by `code`
|
|
3359
|
+
// (matches ComplianceResult.notices semantics). Storyboard_ids on each
|
|
3360
|
+
// notice carry which storyboards triggered it, so the rollup stays clean.
|
|
3361
|
+
const aggregatedNotices = new Map();
|
|
3362
|
+
for (const sb of result.results || []) {
|
|
3363
|
+
for (const n of sb.notices || []) {
|
|
3364
|
+
const existing = aggregatedNotices.get(n.code);
|
|
3365
|
+
if (existing) {
|
|
3366
|
+
for (const sid of n.storyboard_ids || []) {
|
|
3367
|
+
if (!existing.storyboard_ids.includes(sid)) existing.storyboard_ids.push(sid);
|
|
3368
|
+
}
|
|
3369
|
+
} else {
|
|
3370
|
+
aggregatedNotices.set(n.code, { ...n, storyboard_ids: [...(n.storyboard_ids || [])] });
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
printNotices([...aggregatedNotices.values()]);
|
|
3138
3375
|
if (opts.softFail && !result.overall_passed) {
|
|
3139
3376
|
printSoftFailBlock(
|
|
3140
3377
|
result.results.filter(r => !r.overall_passed).map(r => r.storyboard_id),
|
|
@@ -3159,6 +3396,36 @@ function printStrictSummary(summary) {
|
|
|
3159
3396
|
console.log(`${icon} strict: ${passed}/${checked} passed${tail}`);
|
|
3160
3397
|
}
|
|
3161
3398
|
|
|
3399
|
+
/**
|
|
3400
|
+
* Render the `notices` advisory surface (adcp-client#1704) in the
|
|
3401
|
+
* default text output. Notices are decoupled from overall_passed —
|
|
3402
|
+
* they describe protocol-compliance trajectory (deprecations, upcoming
|
|
3403
|
+
* requirements) rather than pass/fail. Severity drives the icon:
|
|
3404
|
+
*
|
|
3405
|
+
* - `future_required` → ⏰ (action needed before a named version cut)
|
|
3406
|
+
* - `deprecation` → 🪦 (migrate away from the named claim/field)
|
|
3407
|
+
* - `info` → ℹ️ (purely informational)
|
|
3408
|
+
*
|
|
3409
|
+
* Without this, notices land only in `--json` output and adopters who
|
|
3410
|
+
* tail the default text output never see the signal. Stays silent when
|
|
3411
|
+
* `notices` is empty or absent.
|
|
3412
|
+
*/
|
|
3413
|
+
function printNotices(notices) {
|
|
3414
|
+
if (!Array.isArray(notices) || notices.length === 0) return;
|
|
3415
|
+
const icon = sev => {
|
|
3416
|
+
if (sev === 'future_required') return '⏰';
|
|
3417
|
+
if (sev === 'deprecation') return '🪦';
|
|
3418
|
+
return 'ℹ️ ';
|
|
3419
|
+
};
|
|
3420
|
+
for (const n of notices) {
|
|
3421
|
+
const tag = (n.severity || 'info').toUpperCase().replace('_', '-');
|
|
3422
|
+
const version = n.effective_version ? ` (effective in ${n.effective_version})` : '';
|
|
3423
|
+
console.log(`${icon(n.severity)} ${tag}: ${n.code}${version}`);
|
|
3424
|
+
if (n.message) console.log(` ${n.message}`);
|
|
3425
|
+
if (n.docs_url) console.log(` ↳ ${n.docs_url}`);
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3162
3429
|
/**
|
|
3163
3430
|
* Aggregate per-storyboard strict summaries into one. Runs are
|
|
3164
3431
|
* independent grading passes against the same SDK; summing their
|
|
@@ -3188,7 +3455,7 @@ function aggregateStrictSummaries(summaries) {
|
|
|
3188
3455
|
}
|
|
3189
3456
|
|
|
3190
3457
|
async function handleMultiInstanceStoryboardRun(args, opts, urls) {
|
|
3191
|
-
const { authToken, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath } = opts;
|
|
3458
|
+
const { authToken, authScheme, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath } = opts;
|
|
3192
3459
|
|
|
3193
3460
|
if (urls.length < 2) {
|
|
3194
3461
|
console.error('ERROR: Multi-instance mode requires 2+ --url flags. Drop --url for single-instance.');
|
|
@@ -3397,7 +3664,7 @@ async function handleMultiInstanceStoryboardRun(args, opts, urls) {
|
|
|
3397
3664
|
|
|
3398
3665
|
const runOptions = {
|
|
3399
3666
|
protocol,
|
|
3400
|
-
...(
|
|
3667
|
+
...buildResolvedAuthOption({ resolvedAuth: authToken, resolvedAuthScheme: authScheme || 'bearer' }),
|
|
3401
3668
|
...(opts.allowHttp && { allow_http: true }),
|
|
3402
3669
|
multi_instance_strategy: strategy,
|
|
3403
3670
|
...(webhookReceiverOpts ?? {}),
|
|
@@ -3508,7 +3775,7 @@ async function handleMultiInstanceStoryboardRun(args, opts, urls) {
|
|
|
3508
3775
|
* Storyboard ID, bundle ID, or `--file` is required.
|
|
3509
3776
|
*/
|
|
3510
3777
|
async function handleAgentsRoutedStoryboardRun(args, opts, routing) {
|
|
3511
|
-
const { authToken, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath, format } = opts;
|
|
3778
|
+
const { authToken, authScheme, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath, format } = opts;
|
|
3512
3779
|
|
|
3513
3780
|
const webhookAutoTunnel = args.includes('--webhook-receiver-auto-tunnel');
|
|
3514
3781
|
const webhookReceiverBase = extractWebhookReceiverOptions(args);
|
|
@@ -3655,7 +3922,7 @@ async function handleAgentsRoutedStoryboardRun(args, opts, routing) {
|
|
|
3655
3922
|
|
|
3656
3923
|
const runOptions = {
|
|
3657
3924
|
protocol,
|
|
3658
|
-
...(
|
|
3925
|
+
...buildResolvedAuthOption({ resolvedAuth: authToken, resolvedAuthScheme: authScheme || 'bearer' }),
|
|
3659
3926
|
...(opts.allowHttp && { allow_http: true }),
|
|
3660
3927
|
agents: routing.agents,
|
|
3661
3928
|
...(routing.default_agent ? { default_agent: routing.default_agent } : {}),
|
|
@@ -3783,11 +4050,12 @@ async function runFullAssessment(agentArg, rawArgs, parsedOpts) {
|
|
|
3783
4050
|
agentUrl,
|
|
3784
4051
|
protocol,
|
|
3785
4052
|
authToken: finalAuthToken,
|
|
4053
|
+
authScheme: finalAuthScheme,
|
|
3786
4054
|
oauthTokens,
|
|
3787
4055
|
oauthClient,
|
|
3788
4056
|
oauthClientCredentials,
|
|
3789
4057
|
savedHeaders,
|
|
3790
|
-
} = await resolveAgent(agentArg, opts.authToken, opts.protocolFlag, opts.jsonOutput);
|
|
4058
|
+
} = await resolveAgent(agentArg, opts.authToken, opts.protocolFlag, opts.jsonOutput, opts.authScheme);
|
|
3791
4059
|
|
|
3792
4060
|
const mergedAssessmentHeaders = mergeHeaders(savedHeaders, opts.customHeaders);
|
|
3793
4061
|
|
|
@@ -3840,6 +4108,7 @@ async function runFullAssessment(agentArg, rawArgs, parsedOpts) {
|
|
|
3840
4108
|
|
|
3841
4109
|
const { auth: authOption } = buildResolvedAuthOption({
|
|
3842
4110
|
resolvedAuth: finalAuthToken,
|
|
4111
|
+
resolvedAuthScheme: finalAuthScheme,
|
|
3843
4112
|
resolvedOauthTokens: oauthTokens,
|
|
3844
4113
|
resolvedOauthClient: oauthClient,
|
|
3845
4114
|
resolvedOauthClientCredentials: oauthClientCredentials,
|
|
@@ -3999,7 +4268,7 @@ async function runFullAssessment(agentArg, rawArgs, parsedOpts) {
|
|
|
3999
4268
|
|
|
4000
4269
|
async function handleStoryboardStepCmd(args) {
|
|
4001
4270
|
const { getComplianceStoryboardById, runStoryboardStep } = await import('../dist/lib/testing/storyboard/index.js');
|
|
4002
|
-
const { authToken, protocolFlag, jsonOutput, debug, positionalArgs } = parseAgentOptions(args);
|
|
4271
|
+
const { authToken, authScheme, protocolFlag, jsonOutput, debug, positionalArgs } = parseAgentOptions(args);
|
|
4003
4272
|
|
|
4004
4273
|
enforceStrictFlags(args, warnRemovedFlags(args));
|
|
4005
4274
|
|
|
@@ -4024,10 +4293,11 @@ async function handleStoryboardStepCmd(args) {
|
|
|
4024
4293
|
agentUrl,
|
|
4025
4294
|
protocol,
|
|
4026
4295
|
authToken: resolvedAuth,
|
|
4296
|
+
authScheme: resolvedAuthScheme,
|
|
4027
4297
|
oauthTokens: resolvedOauthTokens,
|
|
4028
4298
|
oauthClient: resolvedOauthClient,
|
|
4029
4299
|
oauthClientCredentials: resolvedOauthClientCredentials,
|
|
4030
|
-
} = await resolveAgent(agentArg, authToken, protocolFlag, jsonOutput);
|
|
4300
|
+
} = await resolveAgent(agentArg, authToken, protocolFlag, jsonOutput, authScheme);
|
|
4031
4301
|
|
|
4032
4302
|
// Parse --context and --request flags (supports inline JSON or @file.json)
|
|
4033
4303
|
let context = {};
|
|
@@ -4047,6 +4317,7 @@ async function handleStoryboardStepCmd(args) {
|
|
|
4047
4317
|
request,
|
|
4048
4318
|
...buildResolvedAuthOption({
|
|
4049
4319
|
resolvedAuth,
|
|
4320
|
+
resolvedAuthScheme,
|
|
4050
4321
|
resolvedOauthTokens,
|
|
4051
4322
|
resolvedOauthClient,
|
|
4052
4323
|
resolvedOauthClientCredentials,
|
|
@@ -4584,10 +4855,17 @@ Save an agent URL under an alias in ~/.adcp/config.json so future commands
|
|
|
4584
4855
|
can use the alias in place of the URL.
|
|
4585
4856
|
|
|
4586
4857
|
AUTH METHODS (pick one):
|
|
4587
|
-
Static bearer token:
|
|
4858
|
+
Static bearer token (default):
|
|
4588
4859
|
--auth <token> Pre-issued bearer token
|
|
4589
4860
|
--no-auth Agent requires no auth
|
|
4590
4861
|
|
|
4862
|
+
HTTP Basic auth (RFC 7617, for gateways like Apigee/Kong/AWS API GW that
|
|
4863
|
+
front the agent with a BasicAuthentication policy):
|
|
4864
|
+
--auth <user:pass> Credentials in 'user:pass' form
|
|
4865
|
+
--auth-scheme basic Required to switch from bearer to Basic. The
|
|
4866
|
+
username is checked for the RFC 7617 ban on
|
|
4867
|
+
':', and both halves are checked for CR/LF/NUL.
|
|
4868
|
+
|
|
4591
4869
|
HEADERS (compose with any auth method):
|
|
4592
4870
|
-H, --header K=V Extra HTTP header on every request to this agent.
|
|
4593
4871
|
Repeatable. Persists in ~/.adcp/config.json.
|
|
@@ -4618,6 +4896,9 @@ EXAMPLES:
|
|
|
4618
4896
|
# Static bearer (local dev)
|
|
4619
4897
|
adcp --save-auth mine https://agent.example.com/mcp --auth AB12
|
|
4620
4898
|
|
|
4899
|
+
# HTTP Basic (gateway-fronted agent — Apigee, Kong, AWS API GW)
|
|
4900
|
+
adcp --save-auth mine https://agent.example.com/mcp --auth user:pass --auth-scheme basic
|
|
4901
|
+
|
|
4621
4902
|
# Browser OAuth
|
|
4622
4903
|
adcp --save-auth mine https://agent.example.com/mcp --oauth
|
|
4623
4904
|
|
|
@@ -4656,6 +4937,7 @@ credential material — never sync or commit.
|
|
|
4656
4937
|
// consume the next token; boolean flags consume only themselves.
|
|
4657
4938
|
const valueFlags = new Set([
|
|
4658
4939
|
'--auth',
|
|
4940
|
+
'--auth-scheme',
|
|
4659
4941
|
'--oauth-token-url',
|
|
4660
4942
|
'--client-id',
|
|
4661
4943
|
'--client-id-env',
|
|
@@ -4691,13 +4973,40 @@ credential material — never sync or commit.
|
|
|
4691
4973
|
} else if (booleanFlags.has(tok)) {
|
|
4692
4974
|
parsedFlags[tok] = true;
|
|
4693
4975
|
} else {
|
|
4694
|
-
|
|
4976
|
+
// Equals-form: `--auth-scheme=basic` (no space). The long-form
|
|
4977
|
+
// valueFlags check above only matches the bare flag name; the
|
|
4978
|
+
// equals form lands here. Mirror the parsing pattern used at the
|
|
4979
|
+
// top-level parseAuthSchemeFlag so the two surfaces stay aligned.
|
|
4980
|
+
const eqMatch = /^(--[a-z-]+)=(.+)$/i.exec(tok);
|
|
4981
|
+
if (eqMatch && valueFlags.has(eqMatch[1])) {
|
|
4982
|
+
parsedFlags[eqMatch[1]] = eqMatch[2];
|
|
4983
|
+
} else {
|
|
4984
|
+
positional.push(tok);
|
|
4985
|
+
}
|
|
4695
4986
|
}
|
|
4696
4987
|
}
|
|
4697
4988
|
}
|
|
4698
4989
|
const savedHeaders = savedHeadersFromFlags.customHeaders;
|
|
4699
4990
|
|
|
4700
4991
|
const providedAuthToken = parsedFlags['--auth'] ?? null;
|
|
4992
|
+
// Honor `ADCP_AUTH_SCHEME` on the save path too — CI scripts that set it
|
|
4993
|
+
// globally shouldn't have to re-pass the flag on every `adcp --save-auth`.
|
|
4994
|
+
// The CLI flag wins on conflict (consistent with the runtime path).
|
|
4995
|
+
const providedAuthScheme = parsedFlags['--auth-scheme'] ?? process.env.ADCP_AUTH_SCHEME ?? null;
|
|
4996
|
+
if (providedAuthScheme !== null && providedAuthScheme !== 'bearer' && providedAuthScheme !== 'basic') {
|
|
4997
|
+
const sourceLabel = parsedFlags['--auth-scheme'] !== undefined ? '--auth-scheme' : 'ADCP_AUTH_SCHEME env var';
|
|
4998
|
+
console.error(`ERROR: ${sourceLabel} must be 'bearer' or 'basic', got: ${providedAuthScheme}\n`);
|
|
4999
|
+
process.exit(2);
|
|
5000
|
+
}
|
|
5001
|
+
if (providedAuthScheme === 'basic' && providedAuthToken !== null) {
|
|
5002
|
+
// Validate at register time so a typo (missing `:`) doesn't get persisted
|
|
5003
|
+
// to disk and then surface as a confusing decode error on every later call.
|
|
5004
|
+
decodeBasicCredentials(providedAuthToken, '--auth');
|
|
5005
|
+
}
|
|
5006
|
+
if (providedAuthScheme !== null && providedAuthToken === null) {
|
|
5007
|
+
console.error('ERROR: --auth-scheme requires --auth (no token to interpret)\n');
|
|
5008
|
+
process.exit(2);
|
|
5009
|
+
}
|
|
4701
5010
|
const noAuthFlag = parsedFlags['--no-auth'] === true;
|
|
4702
5011
|
const oauthFlag = parsedFlags['--oauth'] === true;
|
|
4703
5012
|
const dryRunFlag = parsedFlags['--dry-run'] === true;
|
|
@@ -5121,7 +5430,16 @@ credential material — never sync or commit.
|
|
|
5121
5430
|
const hasAuthDecision = providedAuthToken !== null || noAuthFlag;
|
|
5122
5431
|
const nonInteractive = url && hasAuthDecision;
|
|
5123
5432
|
|
|
5124
|
-
await interactiveSetup(
|
|
5433
|
+
await interactiveSetup(
|
|
5434
|
+
alias,
|
|
5435
|
+
url,
|
|
5436
|
+
protocol,
|
|
5437
|
+
providedAuthToken,
|
|
5438
|
+
nonInteractive,
|
|
5439
|
+
noAuthFlag,
|
|
5440
|
+
savedHeaders,
|
|
5441
|
+
providedAuthScheme
|
|
5442
|
+
);
|
|
5125
5443
|
process.exit(0);
|
|
5126
5444
|
}
|
|
5127
5445
|
|
|
@@ -5144,7 +5462,17 @@ credential material — never sync or commit.
|
|
|
5144
5462
|
console.log(` Protocol: ${agent.protocol}`);
|
|
5145
5463
|
}
|
|
5146
5464
|
if (agent.auth_token) {
|
|
5147
|
-
|
|
5465
|
+
if (agent.auth_scheme === 'basic') {
|
|
5466
|
+
// For basic, show the username — it's already on disk in cleartext
|
|
5467
|
+
// and seeing it makes the alias immediately recognizable (e.g. tells
|
|
5468
|
+
// you which gateway tenant this alias talks to). Password is the
|
|
5469
|
+
// sensitive half and stays hidden.
|
|
5470
|
+
const colonIdx = agent.auth_token.indexOf(':');
|
|
5471
|
+
const username = colonIdx > 0 ? agent.auth_token.slice(0, colonIdx) : '(malformed)';
|
|
5472
|
+
console.log(` Auth: HTTP Basic (user=${username})`);
|
|
5473
|
+
} else {
|
|
5474
|
+
console.log(` Auth: bearer token configured`);
|
|
5475
|
+
}
|
|
5148
5476
|
}
|
|
5149
5477
|
if (agent.oauth_client_credentials) {
|
|
5150
5478
|
// Intentionally minimal: show only that CC is configured and the
|
|
@@ -5235,6 +5563,10 @@ credential material — never sync or commit.
|
|
|
5235
5563
|
// Parse options first
|
|
5236
5564
|
const authIndex = args.indexOf('--auth');
|
|
5237
5565
|
let authToken = authIndex !== -1 ? args[authIndex + 1] : process.env.ADCP_AUTH_TOKEN;
|
|
5566
|
+
// `--auth-scheme bearer|basic`. Default null → defer to saved alias's
|
|
5567
|
+
// `auth_scheme`, then fall back to `bearer`. `--auth user:pass --auth-scheme
|
|
5568
|
+
// basic` is the gateway-Basic shape (e.g. an Apigee BasicAuthentication policy).
|
|
5569
|
+
let authScheme = parseAuthSchemeFlag(args);
|
|
5238
5570
|
// adcp-client#1612: accept `--transport` as an alias for `--protocol`.
|
|
5239
5571
|
// Hard-fail when the flag is present without a value, matching sites 1
|
|
5240
5572
|
// and 2 — security review of #1619 flagged the silent-fallthrough as a
|
|
@@ -5275,6 +5607,7 @@ credential material — never sync or commit.
|
|
|
5275
5607
|
arg =>
|
|
5276
5608
|
!arg.startsWith('--') &&
|
|
5277
5609
|
arg !== authToken && // Don't include the auth token value
|
|
5610
|
+
arg !== authScheme && // Don't include the auth-scheme value
|
|
5278
5611
|
arg !== protocolFlag && // Don't include the protocol value
|
|
5279
5612
|
arg !== (timeoutIndex !== -1 ? args[timeoutIndex + 1] : null) && // Don't include timeout value
|
|
5280
5613
|
!headerTokens.has(arg) // Drop -H and its KEY=VALUE payload
|
|
@@ -5331,6 +5664,12 @@ credential material — never sync or commit.
|
|
|
5331
5664
|
if (!authToken) {
|
|
5332
5665
|
authToken = getEffectiveAuthToken(savedAgent);
|
|
5333
5666
|
}
|
|
5667
|
+
// Honor the alias's saved scheme only when the caller didn't override
|
|
5668
|
+
// it explicitly. Lets a basic-auth alias work without `--auth-scheme basic`
|
|
5669
|
+
// on every invocation.
|
|
5670
|
+
if (authScheme === null && savedAgent.auth_scheme) {
|
|
5671
|
+
authScheme = savedAgent.auth_scheme;
|
|
5672
|
+
}
|
|
5334
5673
|
|
|
5335
5674
|
if (debug) {
|
|
5336
5675
|
console.error(`DEBUG: Using saved agent '${firstArg}'`);
|
|
@@ -5397,7 +5736,14 @@ credential material — never sync or commit.
|
|
|
5397
5736
|
console.error(` Protocol: ${protocol}`);
|
|
5398
5737
|
console.error(` Agent URL: ${agentUrl}`);
|
|
5399
5738
|
console.error(` Tool: ${toolName || '(list tools)'}`);
|
|
5400
|
-
|
|
5739
|
+
const authLabel = authToken
|
|
5740
|
+
? authScheme === 'basic'
|
|
5741
|
+
? 'basic (user:pass)'
|
|
5742
|
+
: 'bearer'
|
|
5743
|
+
: useOAuth
|
|
5744
|
+
? 'oauth'
|
|
5745
|
+
: 'none';
|
|
5746
|
+
console.error(` Auth: ${authLabel}`);
|
|
5401
5747
|
console.error(` Payload: ${JSON.stringify(payload, null, 2)}`);
|
|
5402
5748
|
console.error('');
|
|
5403
5749
|
}
|
|
@@ -5432,9 +5778,47 @@ credential material — never sync or commit.
|
|
|
5432
5778
|
}
|
|
5433
5779
|
}
|
|
5434
5780
|
|
|
5781
|
+
// Runtime mutex: `--auth-scheme basic` is incompatible with any OAuth shape
|
|
5782
|
+
// (browser flow, refresh-token alias, or client credentials). The
|
|
5783
|
+
// `--save-auth` flow catches this at register time, but a user invoking
|
|
5784
|
+
// `adcp <oauth-alias> <tool> --auth user:pass --auth-scheme basic` would
|
|
5785
|
+
// previously have the basic credential silently dropped because the gating
|
|
5786
|
+
// logic below excludes basic when any OAuth material is present. Fail
|
|
5787
|
+
// closed instead — better a clear error than a credential silently ignored.
|
|
5788
|
+
if (authScheme === 'basic' && (useOAuth || agentOAuthClientCredentials || agentOAuthTokens)) {
|
|
5789
|
+
console.error(
|
|
5790
|
+
'\n❌ ERROR: --auth-scheme basic cannot be combined with --oauth or with an alias that has OAuth tokens / client credentials.\n' +
|
|
5791
|
+
' The agent is already configured for OAuth — pick one auth method, not both.\n'
|
|
5792
|
+
);
|
|
5793
|
+
process.exit(2);
|
|
5794
|
+
}
|
|
5795
|
+
|
|
5435
5796
|
// Merge saved-alias headers (per-agent routing context, e.g. x-adcp-tenant)
|
|
5436
5797
|
// with `-H KEY=VALUE` flags from the current invocation. CLI wins on conflict.
|
|
5437
|
-
|
|
5798
|
+
let mergedHeaders = mergeHeaders(savedAgent && savedAgent.headers, cliHeaders);
|
|
5799
|
+
|
|
5800
|
+
// Basic auth path: gateways like Apigee/Kong/AWS API GW with a
|
|
5801
|
+
// BasicAuthentication policy speak RFC 7617 (`Authorization: Basic
|
|
5802
|
+
// <base64(user:pass)>`), not OAuth bearer. We inject the encoded header
|
|
5803
|
+
// AFTER `mergeHeaders` runs so the reserved-key filter doesn't strip it.
|
|
5804
|
+
// The protocol layer (`src/lib/protocols/mcp.ts:475-477`,
|
|
5805
|
+
// `src/lib/protocols/a2a.ts:283-292`) spreads `customHeaders` before the
|
|
5806
|
+
// SDK-supplied Authorization, so suppressing `auth_token` here lets our
|
|
5807
|
+
// injected Basic header reach the wire intact.
|
|
5808
|
+
const useBasicAuth = authScheme === 'basic' && authToken;
|
|
5809
|
+
if (useBasicAuth) {
|
|
5810
|
+
// Helper extraction protects the invariant: this MUST run after
|
|
5811
|
+
// mergeHeaders() so the reserved-key filter doesn't strip the injection.
|
|
5812
|
+
// See `injectBasicAuthHeader`'s docstring for the full rationale.
|
|
5813
|
+
mergedHeaders = injectBasicAuthHeader(mergedHeaders, authToken);
|
|
5814
|
+
}
|
|
5815
|
+
|
|
5816
|
+
// Advisory warning: surface ADCP_AUTH_SCHEME=basic when no token resolved
|
|
5817
|
+
// (otherwise the env var is silently no-op and adopters wonder why their
|
|
5818
|
+
// Basic gateway keeps 401ing). Only fires in the env-set-but-not-applied
|
|
5819
|
+
// case — the inverse (token-without-scheme → silent bearer) is the safe
|
|
5820
|
+
// direction.
|
|
5821
|
+
maybeWarnAuthSchemeIneffective(authToken, authScheme || 'bearer');
|
|
5438
5822
|
|
|
5439
5823
|
// Create agent config
|
|
5440
5824
|
const agentConfig = {
|
|
@@ -5442,7 +5826,11 @@ credential material — never sync or commit.
|
|
|
5442
5826
|
name: 'CLI Agent',
|
|
5443
5827
|
agent_uri: agentUrl,
|
|
5444
5828
|
protocol: protocol,
|
|
5445
|
-
...(authToken &&
|
|
5829
|
+
...(authToken &&
|
|
5830
|
+
!useOAuth &&
|
|
5831
|
+
!agentOAuthClientCredentials &&
|
|
5832
|
+
!useBasicAuth && { auth_token: authToken, requiresAuth: true }),
|
|
5833
|
+
...(useBasicAuth && { requiresAuth: true }),
|
|
5446
5834
|
...(agentOAuthTokens && { oauth_tokens: agentOAuthTokens }),
|
|
5447
5835
|
...(agentOAuthClient && { oauth_client: agentOAuthClient }),
|
|
5448
5836
|
...(agentOAuthClientCredentials && { oauth_client_credentials: agentOAuthClientCredentials }),
|
|
@@ -5963,14 +6351,28 @@ credential material — never sync or commit.
|
|
|
5963
6351
|
// `NeedsAuthorizationError` is the richer form (already extended from
|
|
5964
6352
|
// AuthenticationRequiredError); the string-match branches cover older
|
|
5965
6353
|
// error shapes from the MCP SDK and other 401 paths.
|
|
6354
|
+
// `Authentication required` matches the plain `AuthenticationRequiredError`
|
|
6355
|
+
// (thrown when the SDK got 401 but couldn't walk OAuth metadata) — that's
|
|
6356
|
+
// the exact shape a Basic-fronted gateway produces, so it's critical the
|
|
6357
|
+
// Basic-hint path catches it.
|
|
5966
6358
|
const isUnauthorized =
|
|
5967
6359
|
error instanceof NeedsAuthorizationError ||
|
|
5968
6360
|
error.name === 'UnauthorizedError' ||
|
|
6361
|
+
error.name === 'AuthenticationRequiredError' ||
|
|
5969
6362
|
error.message?.toLowerCase().includes('unauthorized') ||
|
|
6363
|
+
error.message?.toLowerCase().includes('authentication required') ||
|
|
5970
6364
|
error.message?.includes('401');
|
|
5971
6365
|
|
|
5972
6366
|
if (isUnauthorized && protocol === 'mcp' && !useOAuth && !authToken) {
|
|
5973
6367
|
console.log('\n🔐 Server requires authentication.');
|
|
6368
|
+
// Some agents sit behind an API gateway with a BasicAuthentication policy
|
|
6369
|
+
// (Apigee, Kong, AWS API GW, nginx auth_basic). OAuth won't succeed
|
|
6370
|
+
// against those gateways no matter how the browser flow goes — surface
|
|
6371
|
+
// the alternative before opening the browser so a Basic-fronted adopter
|
|
6372
|
+
// doesn't bounce through a doomed OAuth handshake first.
|
|
6373
|
+
console.log(
|
|
6374
|
+
'If your agent is fronted by an HTTP-Basic gateway, retry with: --auth <user:pass> --auth-scheme basic'
|
|
6375
|
+
);
|
|
5974
6376
|
console.log('Starting OAuth authentication...\n');
|
|
5975
6377
|
|
|
5976
6378
|
// Run OAuth flow automatically
|