From e6cd895acd0ed196992f9e81bc2fda97ee712059 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Fri, 13 Mar 2026 11:19:32 -0400 Subject: [PATCH 1/2] fix: correct managed OAuth credential name lookup for gateway MCP clients The managed OAuth credential is created with the suffix '-oauth' (e.g. 'my-gateway-oauth') but was being looked up with '-agent-oauth' in schema-mapper.ts and displayed with '-agent-oauth' in AddGatewayScreen. This mismatch caused the credential lookup to fail silently, resulting in an empty provider_name in the generated @requires_access_token decorator. The agent runtime then crashed with: ParamValidationError: Invalid length for parameter resourceCredentialProviderName, value: 0, valid min length: 1 --- .../generate/__tests__/schema-mapper.test.ts | 20 +++++++++++++++++++ .../agent/generate/schema-mapper.ts | 2 +- src/cli/tui/screens/mcp/AddGatewayScreen.tsx | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts b/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts index 593c9520..b7c04c6d 100644 --- a/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts +++ b/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts @@ -190,3 +190,23 @@ describe('mapGenerateConfigToRenderConfig', () => { expect(result.memoryProviders[0]!.strategies).toEqual(['SEMANTIC', 'USER_PREFERENCE', 'SUMMARIZATION']); }); }); + +describe('gateway credential provider name mapping', () => { + it('uses the correct credential name suffix (-oauth) matching GatewayPrimitive creation', async () => { + // Regression test: credential is created as `${gatewayName}-oauth` by GatewayPrimitive, + // so the lookup in mapGatewaysToGatewayProviders must use the same suffix. + // Previously used '-agent-oauth' which caused provider_name="" in generated code. + // + // We verify this by checking the source code directly since ConfigIO requires + // a full build environment to import. + + const fs = await import('node:fs'); + const path = await import('node:path'); + const schemaMapperPath = path.resolve(import.meta.dirname ?? __dirname, '../schema-mapper.ts'); + const source = fs.readFileSync(schemaMapperPath, 'utf-8'); + + // The credential lookup must use `${gateway.name}-oauth` (not `-agent-oauth`) + expect(source).toContain('`${gateway.name}-oauth`'); + expect(source).not.toContain('-agent-oauth'); + }); +}); diff --git a/src/cli/operations/agent/generate/schema-mapper.ts b/src/cli/operations/agent/generate/schema-mapper.ts index 9cda6000..e33b0525 100644 --- a/src/cli/operations/agent/generate/schema-mapper.ts +++ b/src/cli/operations/agent/generate/schema-mapper.ts @@ -199,7 +199,7 @@ async function mapGatewaysToGatewayProviders(): Promise c.name === credName); if (credential) { diff --git a/src/cli/tui/screens/mcp/AddGatewayScreen.tsx b/src/cli/tui/screens/mcp/AddGatewayScreen.tsx index 4560214e..52b2465f 100644 --- a/src/cli/tui/screens/mcp/AddGatewayScreen.tsx +++ b/src/cli/tui/screens/mcp/AddGatewayScreen.tsx @@ -272,7 +272,7 @@ export function AddGatewayScreen({ onComplete, onExit, existingGateways, unassig ? [{ label: 'Allowed Scopes', value: wizard.config.jwtConfig.allowedScopes.join(', ') }] : []), ...(wizard.config.jwtConfig.agentClientId - ? [{ label: 'Agent Credential', value: `${wizard.config.name}-agent-oauth` }] + ? [{ label: 'Agent Credential', value: `${wizard.config.name}-oauth` }] : []), ] : []), From fe9659d968b8e09b5e07a9bf94d98f584d275ff4 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Fri, 13 Mar 2026 12:08:58 -0400 Subject: [PATCH 2/2] refactor: extract computeManagedOAuthCredentialName to shared utility Replace inline credential name construction with a shared function in credential-utils.ts. All three consumers (GatewayPrimitive, schema-mapper, AddGatewayScreen) now use the same function, preventing future naming drift. Add regression test for the shared function. --- .../generate/__tests__/schema-mapper.test.ts | 23 ++++++------------- .../agent/generate/schema-mapper.ts | 7 ++++-- src/cli/primitives/GatewayPrimitive.ts | 4 ++-- src/cli/primitives/credential-utils.ts | 9 ++++++++ src/cli/tui/screens/mcp/AddGatewayScreen.tsx | 3 ++- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts b/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts index b7c04c6d..73d162e4 100644 --- a/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts +++ b/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts @@ -1,3 +1,4 @@ +import { computeManagedOAuthCredentialName } from '../../../../primitives/credential-utils.js'; import type { GenerateConfig } from '../../../../tui/screens/generate/types.js'; import { mapGenerateConfigToAgent, @@ -192,21 +193,11 @@ describe('mapGenerateConfigToRenderConfig', () => { }); describe('gateway credential provider name mapping', () => { - it('uses the correct credential name suffix (-oauth) matching GatewayPrimitive creation', async () => { - // Regression test: credential is created as `${gatewayName}-oauth` by GatewayPrimitive, - // so the lookup in mapGatewaysToGatewayProviders must use the same suffix. - // Previously used '-agent-oauth' which caused provider_name="" in generated code. - // - // We verify this by checking the source code directly since ConfigIO requires - // a full build environment to import. - - const fs = await import('node:fs'); - const path = await import('node:path'); - const schemaMapperPath = path.resolve(import.meta.dirname ?? __dirname, '../schema-mapper.ts'); - const source = fs.readFileSync(schemaMapperPath, 'utf-8'); - - // The credential lookup must use `${gateway.name}-oauth` (not `-agent-oauth`) - expect(source).toContain('`${gateway.name}-oauth`'); - expect(source).not.toContain('-agent-oauth'); + it('computeManagedOAuthCredentialName produces the correct suffix', () => { + // Regression test: the managed credential name must use '-oauth' suffix. + // GatewayPrimitive creates it, schema-mapper looks it up, AddGatewayScreen displays it. + // All three now use computeManagedOAuthCredentialName to stay in sync. + expect(computeManagedOAuthCredentialName('my-gateway')).toBe('my-gateway-oauth'); + expect(computeManagedOAuthCredentialName('test')).toBe('test-oauth'); }); }); diff --git a/src/cli/operations/agent/generate/schema-mapper.ts b/src/cli/operations/agent/generate/schema-mapper.ts index e33b0525..0b90c71c 100644 --- a/src/cli/operations/agent/generate/schema-mapper.ts +++ b/src/cli/operations/agent/generate/schema-mapper.ts @@ -11,7 +11,10 @@ import type { } from '../../../../schema'; import { DEFAULT_STRATEGY_NAMESPACES } from '../../../../schema'; import { GatewayPrimitive } from '../../../primitives/GatewayPrimitive'; -import { computeDefaultCredentialEnvVarName } from '../../../primitives/credential-utils'; +import { + computeDefaultCredentialEnvVarName, + computeManagedOAuthCredentialName, +} from '../../../primitives/credential-utils'; import type { AgentRenderConfig, GatewayProviderRenderConfig, @@ -199,7 +202,7 @@ async function mapGatewaysToGatewayProviders(): Promise c.name === credName); if (credential) { diff --git a/src/cli/primitives/GatewayPrimitive.ts b/src/cli/primitives/GatewayPrimitive.ts index 1ef75ab0..313fa6c0 100644 --- a/src/cli/primitives/GatewayPrimitive.ts +++ b/src/cli/primitives/GatewayPrimitive.ts @@ -8,7 +8,7 @@ import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/ import type { AddGatewayConfig } from '../tui/screens/mcp/types'; import { BasePrimitive } from './BasePrimitive'; import { SOURCE_CODE_NOTE } from './constants'; -import { computeDefaultCredentialEnvVarName } from './credential-utils'; +import { computeDefaultCredentialEnvVarName, computeManagedOAuthCredentialName } from './credential-utils'; import type { AddResult, AddScreenComponent, RemovableResource } from './types'; import type { Command } from '@commander-js/extra-typings'; @@ -379,7 +379,7 @@ export class GatewayPrimitive extends BasePrimitive ): Promise { - const credentialName = `${gatewayName}-oauth`; + const credentialName = computeManagedOAuthCredentialName(gatewayName); const project = await this.readProjectSpec(); // Skip if credential already exists diff --git a/src/cli/primitives/credential-utils.ts b/src/cli/primitives/credential-utils.ts index 5b639a50..8c1df16e 100644 --- a/src/cli/primitives/credential-utils.ts +++ b/src/cli/primitives/credential-utils.ts @@ -6,3 +6,12 @@ export function computeDefaultCredentialEnvVarName(credentialName: string): string { return `AGENTCORE_CREDENTIAL_${credentialName.replace(/-/g, '_').toUpperCase()}`; } + +/** + * Compute the managed OAuth credential name for a gateway. + * Used when creating the credential (GatewayPrimitive) and when + * looking it up for code generation (schema-mapper). + */ +export function computeManagedOAuthCredentialName(gatewayName: string): string { + return `${gatewayName}-oauth`; +} diff --git a/src/cli/tui/screens/mcp/AddGatewayScreen.tsx b/src/cli/tui/screens/mcp/AddGatewayScreen.tsx index 52b2465f..b6ccf7e4 100644 --- a/src/cli/tui/screens/mcp/AddGatewayScreen.tsx +++ b/src/cli/tui/screens/mcp/AddGatewayScreen.tsx @@ -1,5 +1,6 @@ import type { GatewayAuthorizerType } from '../../../../schema'; import { GatewayNameSchema } from '../../../../schema'; +import { computeManagedOAuthCredentialName } from '../../../primitives/credential-utils'; import { ConfirmReview, Panel, @@ -272,7 +273,7 @@ export function AddGatewayScreen({ onComplete, onExit, existingGateways, unassig ? [{ label: 'Allowed Scopes', value: wizard.config.jwtConfig.allowedScopes.join(', ') }] : []), ...(wizard.config.jwtConfig.agentClientId - ? [{ label: 'Agent Credential', value: `${wizard.config.name}-oauth` }] + ? [{ label: 'Agent Credential', value: computeManagedOAuthCredentialName(wizard.config.name) }] : []), ] : []),