@adcp/sdk 7.1.0 → 7.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/bin/adcp-config.js +10 -1
- package/bin/adcp.js +376 -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/server/idempotency/store.d.ts +6 -5
- package/dist/lib/server/idempotency/store.d.ts.map +1 -1
- package/dist/lib/server/idempotency/store.js +11 -14
- package/dist/lib/server/idempotency/store.js.map +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 +8 -0
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +73 -2
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts +27 -3
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +21 -31
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/index.d.ts +1 -1
- package/dist/lib/testing/compliance/index.d.ts.map +1 -1
- package/dist/lib/testing/compliance/index.js +2 -1
- package/dist/lib/testing/compliance/index.js.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.js +11 -1
- package/dist/lib/testing/scenarios/error-compliance.js.map +1 -1
- package/dist/lib/testing/storyboard/parallel-dispatch.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/parallel-dispatch.js +6 -4
- package/dist/lib/testing/storyboard/parallel-dispatch.js.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.js +18 -7
- package/dist/lib/testing/storyboard/validations.js.map +1 -1
- package/dist/lib/types/index.d.ts +2 -0
- package/dist/lib/types/index.d.ts.map +1 -1
- package/dist/lib/types/index.js +4 -0
- package/dist/lib/types/index.js.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/docs/llms.txt +10 -2
- package/package.json +2 -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,
|
|
@@ -3236,7 +3455,7 @@ function aggregateStrictSummaries(summaries) {
|
|
|
3236
3455
|
}
|
|
3237
3456
|
|
|
3238
3457
|
async function handleMultiInstanceStoryboardRun(args, opts, urls) {
|
|
3239
|
-
const { authToken, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath } = opts;
|
|
3458
|
+
const { authToken, authScheme, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath } = opts;
|
|
3240
3459
|
|
|
3241
3460
|
if (urls.length < 2) {
|
|
3242
3461
|
console.error('ERROR: Multi-instance mode requires 2+ --url flags. Drop --url for single-instance.');
|
|
@@ -3445,7 +3664,7 @@ async function handleMultiInstanceStoryboardRun(args, opts, urls) {
|
|
|
3445
3664
|
|
|
3446
3665
|
const runOptions = {
|
|
3447
3666
|
protocol,
|
|
3448
|
-
...(
|
|
3667
|
+
...buildResolvedAuthOption({ resolvedAuth: authToken, resolvedAuthScheme: authScheme || 'bearer' }),
|
|
3449
3668
|
...(opts.allowHttp && { allow_http: true }),
|
|
3450
3669
|
multi_instance_strategy: strategy,
|
|
3451
3670
|
...(webhookReceiverOpts ?? {}),
|
|
@@ -3556,7 +3775,7 @@ async function handleMultiInstanceStoryboardRun(args, opts, urls) {
|
|
|
3556
3775
|
* Storyboard ID, bundle ID, or `--file` is required.
|
|
3557
3776
|
*/
|
|
3558
3777
|
async function handleAgentsRoutedStoryboardRun(args, opts, routing) {
|
|
3559
|
-
const { authToken, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath, format } = opts;
|
|
3778
|
+
const { authToken, authScheme, protocolFlag, jsonOutput, dryRun, positionalArgs, file: filePath, format } = opts;
|
|
3560
3779
|
|
|
3561
3780
|
const webhookAutoTunnel = args.includes('--webhook-receiver-auto-tunnel');
|
|
3562
3781
|
const webhookReceiverBase = extractWebhookReceiverOptions(args);
|
|
@@ -3703,7 +3922,7 @@ async function handleAgentsRoutedStoryboardRun(args, opts, routing) {
|
|
|
3703
3922
|
|
|
3704
3923
|
const runOptions = {
|
|
3705
3924
|
protocol,
|
|
3706
|
-
...(
|
|
3925
|
+
...buildResolvedAuthOption({ resolvedAuth: authToken, resolvedAuthScheme: authScheme || 'bearer' }),
|
|
3707
3926
|
...(opts.allowHttp && { allow_http: true }),
|
|
3708
3927
|
agents: routing.agents,
|
|
3709
3928
|
...(routing.default_agent ? { default_agent: routing.default_agent } : {}),
|
|
@@ -3831,11 +4050,12 @@ async function runFullAssessment(agentArg, rawArgs, parsedOpts) {
|
|
|
3831
4050
|
agentUrl,
|
|
3832
4051
|
protocol,
|
|
3833
4052
|
authToken: finalAuthToken,
|
|
4053
|
+
authScheme: finalAuthScheme,
|
|
3834
4054
|
oauthTokens,
|
|
3835
4055
|
oauthClient,
|
|
3836
4056
|
oauthClientCredentials,
|
|
3837
4057
|
savedHeaders,
|
|
3838
|
-
} = await resolveAgent(agentArg, opts.authToken, opts.protocolFlag, opts.jsonOutput);
|
|
4058
|
+
} = await resolveAgent(agentArg, opts.authToken, opts.protocolFlag, opts.jsonOutput, opts.authScheme);
|
|
3839
4059
|
|
|
3840
4060
|
const mergedAssessmentHeaders = mergeHeaders(savedHeaders, opts.customHeaders);
|
|
3841
4061
|
|
|
@@ -3888,6 +4108,7 @@ async function runFullAssessment(agentArg, rawArgs, parsedOpts) {
|
|
|
3888
4108
|
|
|
3889
4109
|
const { auth: authOption } = buildResolvedAuthOption({
|
|
3890
4110
|
resolvedAuth: finalAuthToken,
|
|
4111
|
+
resolvedAuthScheme: finalAuthScheme,
|
|
3891
4112
|
resolvedOauthTokens: oauthTokens,
|
|
3892
4113
|
resolvedOauthClient: oauthClient,
|
|
3893
4114
|
resolvedOauthClientCredentials: oauthClientCredentials,
|
|
@@ -4047,7 +4268,7 @@ async function runFullAssessment(agentArg, rawArgs, parsedOpts) {
|
|
|
4047
4268
|
|
|
4048
4269
|
async function handleStoryboardStepCmd(args) {
|
|
4049
4270
|
const { getComplianceStoryboardById, runStoryboardStep } = await import('../dist/lib/testing/storyboard/index.js');
|
|
4050
|
-
const { authToken, protocolFlag, jsonOutput, debug, positionalArgs } = parseAgentOptions(args);
|
|
4271
|
+
const { authToken, authScheme, protocolFlag, jsonOutput, debug, positionalArgs } = parseAgentOptions(args);
|
|
4051
4272
|
|
|
4052
4273
|
enforceStrictFlags(args, warnRemovedFlags(args));
|
|
4053
4274
|
|
|
@@ -4072,10 +4293,11 @@ async function handleStoryboardStepCmd(args) {
|
|
|
4072
4293
|
agentUrl,
|
|
4073
4294
|
protocol,
|
|
4074
4295
|
authToken: resolvedAuth,
|
|
4296
|
+
authScheme: resolvedAuthScheme,
|
|
4075
4297
|
oauthTokens: resolvedOauthTokens,
|
|
4076
4298
|
oauthClient: resolvedOauthClient,
|
|
4077
4299
|
oauthClientCredentials: resolvedOauthClientCredentials,
|
|
4078
|
-
} = await resolveAgent(agentArg, authToken, protocolFlag, jsonOutput);
|
|
4300
|
+
} = await resolveAgent(agentArg, authToken, protocolFlag, jsonOutput, authScheme);
|
|
4079
4301
|
|
|
4080
4302
|
// Parse --context and --request flags (supports inline JSON or @file.json)
|
|
4081
4303
|
let context = {};
|
|
@@ -4095,6 +4317,7 @@ async function handleStoryboardStepCmd(args) {
|
|
|
4095
4317
|
request,
|
|
4096
4318
|
...buildResolvedAuthOption({
|
|
4097
4319
|
resolvedAuth,
|
|
4320
|
+
resolvedAuthScheme,
|
|
4098
4321
|
resolvedOauthTokens,
|
|
4099
4322
|
resolvedOauthClient,
|
|
4100
4323
|
resolvedOauthClientCredentials,
|
|
@@ -4632,10 +4855,17 @@ Save an agent URL under an alias in ~/.adcp/config.json so future commands
|
|
|
4632
4855
|
can use the alias in place of the URL.
|
|
4633
4856
|
|
|
4634
4857
|
AUTH METHODS (pick one):
|
|
4635
|
-
Static bearer token:
|
|
4858
|
+
Static bearer token (default):
|
|
4636
4859
|
--auth <token> Pre-issued bearer token
|
|
4637
4860
|
--no-auth Agent requires no auth
|
|
4638
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
|
+
|
|
4639
4869
|
HEADERS (compose with any auth method):
|
|
4640
4870
|
-H, --header K=V Extra HTTP header on every request to this agent.
|
|
4641
4871
|
Repeatable. Persists in ~/.adcp/config.json.
|
|
@@ -4666,6 +4896,9 @@ EXAMPLES:
|
|
|
4666
4896
|
# Static bearer (local dev)
|
|
4667
4897
|
adcp --save-auth mine https://agent.example.com/mcp --auth AB12
|
|
4668
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
|
+
|
|
4669
4902
|
# Browser OAuth
|
|
4670
4903
|
adcp --save-auth mine https://agent.example.com/mcp --oauth
|
|
4671
4904
|
|
|
@@ -4704,6 +4937,7 @@ credential material — never sync or commit.
|
|
|
4704
4937
|
// consume the next token; boolean flags consume only themselves.
|
|
4705
4938
|
const valueFlags = new Set([
|
|
4706
4939
|
'--auth',
|
|
4940
|
+
'--auth-scheme',
|
|
4707
4941
|
'--oauth-token-url',
|
|
4708
4942
|
'--client-id',
|
|
4709
4943
|
'--client-id-env',
|
|
@@ -4739,13 +4973,40 @@ credential material — never sync or commit.
|
|
|
4739
4973
|
} else if (booleanFlags.has(tok)) {
|
|
4740
4974
|
parsedFlags[tok] = true;
|
|
4741
4975
|
} else {
|
|
4742
|
-
|
|
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
|
+
}
|
|
4743
4986
|
}
|
|
4744
4987
|
}
|
|
4745
4988
|
}
|
|
4746
4989
|
const savedHeaders = savedHeadersFromFlags.customHeaders;
|
|
4747
4990
|
|
|
4748
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
|
+
}
|
|
4749
5010
|
const noAuthFlag = parsedFlags['--no-auth'] === true;
|
|
4750
5011
|
const oauthFlag = parsedFlags['--oauth'] === true;
|
|
4751
5012
|
const dryRunFlag = parsedFlags['--dry-run'] === true;
|
|
@@ -5169,7 +5430,16 @@ credential material — never sync or commit.
|
|
|
5169
5430
|
const hasAuthDecision = providedAuthToken !== null || noAuthFlag;
|
|
5170
5431
|
const nonInteractive = url && hasAuthDecision;
|
|
5171
5432
|
|
|
5172
|
-
await interactiveSetup(
|
|
5433
|
+
await interactiveSetup(
|
|
5434
|
+
alias,
|
|
5435
|
+
url,
|
|
5436
|
+
protocol,
|
|
5437
|
+
providedAuthToken,
|
|
5438
|
+
nonInteractive,
|
|
5439
|
+
noAuthFlag,
|
|
5440
|
+
savedHeaders,
|
|
5441
|
+
providedAuthScheme
|
|
5442
|
+
);
|
|
5173
5443
|
process.exit(0);
|
|
5174
5444
|
}
|
|
5175
5445
|
|
|
@@ -5192,7 +5462,17 @@ credential material — never sync or commit.
|
|
|
5192
5462
|
console.log(` Protocol: ${agent.protocol}`);
|
|
5193
5463
|
}
|
|
5194
5464
|
if (agent.auth_token) {
|
|
5195
|
-
|
|
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
|
+
}
|
|
5196
5476
|
}
|
|
5197
5477
|
if (agent.oauth_client_credentials) {
|
|
5198
5478
|
// Intentionally minimal: show only that CC is configured and the
|
|
@@ -5283,6 +5563,10 @@ credential material — never sync or commit.
|
|
|
5283
5563
|
// Parse options first
|
|
5284
5564
|
const authIndex = args.indexOf('--auth');
|
|
5285
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);
|
|
5286
5570
|
// adcp-client#1612: accept `--transport` as an alias for `--protocol`.
|
|
5287
5571
|
// Hard-fail when the flag is present without a value, matching sites 1
|
|
5288
5572
|
// and 2 — security review of #1619 flagged the silent-fallthrough as a
|
|
@@ -5323,6 +5607,7 @@ credential material — never sync or commit.
|
|
|
5323
5607
|
arg =>
|
|
5324
5608
|
!arg.startsWith('--') &&
|
|
5325
5609
|
arg !== authToken && // Don't include the auth token value
|
|
5610
|
+
arg !== authScheme && // Don't include the auth-scheme value
|
|
5326
5611
|
arg !== protocolFlag && // Don't include the protocol value
|
|
5327
5612
|
arg !== (timeoutIndex !== -1 ? args[timeoutIndex + 1] : null) && // Don't include timeout value
|
|
5328
5613
|
!headerTokens.has(arg) // Drop -H and its KEY=VALUE payload
|
|
@@ -5379,6 +5664,12 @@ credential material — never sync or commit.
|
|
|
5379
5664
|
if (!authToken) {
|
|
5380
5665
|
authToken = getEffectiveAuthToken(savedAgent);
|
|
5381
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
|
+
}
|
|
5382
5673
|
|
|
5383
5674
|
if (debug) {
|
|
5384
5675
|
console.error(`DEBUG: Using saved agent '${firstArg}'`);
|
|
@@ -5445,7 +5736,14 @@ credential material — never sync or commit.
|
|
|
5445
5736
|
console.error(` Protocol: ${protocol}`);
|
|
5446
5737
|
console.error(` Agent URL: ${agentUrl}`);
|
|
5447
5738
|
console.error(` Tool: ${toolName || '(list tools)'}`);
|
|
5448
|
-
|
|
5739
|
+
const authLabel = authToken
|
|
5740
|
+
? authScheme === 'basic'
|
|
5741
|
+
? 'basic (user:pass)'
|
|
5742
|
+
: 'bearer'
|
|
5743
|
+
: useOAuth
|
|
5744
|
+
? 'oauth'
|
|
5745
|
+
: 'none';
|
|
5746
|
+
console.error(` Auth: ${authLabel}`);
|
|
5449
5747
|
console.error(` Payload: ${JSON.stringify(payload, null, 2)}`);
|
|
5450
5748
|
console.error('');
|
|
5451
5749
|
}
|
|
@@ -5480,9 +5778,47 @@ credential material — never sync or commit.
|
|
|
5480
5778
|
}
|
|
5481
5779
|
}
|
|
5482
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
|
+
|
|
5483
5796
|
// Merge saved-alias headers (per-agent routing context, e.g. x-adcp-tenant)
|
|
5484
5797
|
// with `-H KEY=VALUE` flags from the current invocation. CLI wins on conflict.
|
|
5485
|
-
|
|
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');
|
|
5486
5822
|
|
|
5487
5823
|
// Create agent config
|
|
5488
5824
|
const agentConfig = {
|
|
@@ -5490,7 +5826,11 @@ credential material — never sync or commit.
|
|
|
5490
5826
|
name: 'CLI Agent',
|
|
5491
5827
|
agent_uri: agentUrl,
|
|
5492
5828
|
protocol: protocol,
|
|
5493
|
-
...(authToken &&
|
|
5829
|
+
...(authToken &&
|
|
5830
|
+
!useOAuth &&
|
|
5831
|
+
!agentOAuthClientCredentials &&
|
|
5832
|
+
!useBasicAuth && { auth_token: authToken, requiresAuth: true }),
|
|
5833
|
+
...(useBasicAuth && { requiresAuth: true }),
|
|
5494
5834
|
...(agentOAuthTokens && { oauth_tokens: agentOAuthTokens }),
|
|
5495
5835
|
...(agentOAuthClient && { oauth_client: agentOAuthClient }),
|
|
5496
5836
|
...(agentOAuthClientCredentials && { oauth_client_credentials: agentOAuthClientCredentials }),
|
|
@@ -6011,14 +6351,28 @@ credential material — never sync or commit.
|
|
|
6011
6351
|
// `NeedsAuthorizationError` is the richer form (already extended from
|
|
6012
6352
|
// AuthenticationRequiredError); the string-match branches cover older
|
|
6013
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.
|
|
6014
6358
|
const isUnauthorized =
|
|
6015
6359
|
error instanceof NeedsAuthorizationError ||
|
|
6016
6360
|
error.name === 'UnauthorizedError' ||
|
|
6361
|
+
error.name === 'AuthenticationRequiredError' ||
|
|
6017
6362
|
error.message?.toLowerCase().includes('unauthorized') ||
|
|
6363
|
+
error.message?.toLowerCase().includes('authentication required') ||
|
|
6018
6364
|
error.message?.includes('401');
|
|
6019
6365
|
|
|
6020
6366
|
if (isUnauthorized && protocol === 'mcp' && !useOAuth && !authToken) {
|
|
6021
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
|
+
);
|
|
6022
6376
|
console.log('Starting OAuth authentication...\n');
|
|
6023
6377
|
|
|
6024
6378
|
// Run OAuth flow automatically
|