From bbfcdc4b7754065fec60c6bd92da3577cfa34643 Mon Sep 17 00:00:00 2001 From: Aidan Daly <99039782+aidandaly24@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:50:18 -0500 Subject: [PATCH 1/3] ci: add feat/gateway-integration branch to workflow triggers (#376) --- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/codeql.yml | 6 +++--- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 4 ++-- .github/workflows/pr-size.yml | 2 +- .github/workflows/pr-title.yml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 474ec6c3..5a99f3a8 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,9 +2,9 @@ name: Build and Test on: push: - branches: ['main'] + branches: ['main', 'feat/gateway-integration'] pull_request: - branches: ['main'] + branches: ['main', 'feat/gateway-integration'] permissions: contents: read diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5eee5e90..a64f5bb3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,11 +2,11 @@ name: CodeQL on: push: - branches: ['main'] + branches: ['main', 'feat/gateway-integration'] pull_request: - branches: ['main'] + branches: ['main', 'feat/gateway-integration'] pull_request_target: - branches: ['main'] + branches: ['main', 'feat/gateway-integration'] # Cancel in-progress runs when a new commit is pushed concurrency: diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index edde6245..557f787b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -6,7 +6,7 @@ on: description: 'AWS region for deployment' default: 'us-east-1' pull_request_target: - branches: [main] + branches: [main, feat/gateway-integration] permissions: id-token: write # OIDC — lets GitHub assume an AWS IAM role via short-lived token (no stored keys) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 377464de..b88258fe 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,9 +2,9 @@ name: Quality and Safety Checks on: push: - branches: ['main'] + branches: ['main', 'feat/gateway-integration'] pull_request: - branches: ['main'] + branches: ['main', 'feat/gateway-integration'] permissions: contents: read diff --git a/.github/workflows/pr-size.yml b/.github/workflows/pr-size.yml index 1982c05f..e172e99f 100644 --- a/.github/workflows/pr-size.yml +++ b/.github/workflows/pr-size.yml @@ -4,7 +4,7 @@ name: PR Size Check and Label # Safe because this workflow only reads PR metadata — it never checks out untrusted code. on: pull_request_target: - branches: [main] + branches: [main, feat/gateway-integration] jobs: label-size: diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 46fd69bf..02ed5fa7 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -2,7 +2,7 @@ name: Validate PR Title on: pull_request_target: - branches: [main] + branches: [main, feat/gateway-integration] types: [opened, edited, synchronize, reopened] jobs: From 941178a419b6bd3432a62b82a27dced0314d68c8 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 23 Feb 2026 00:15:57 +0000 Subject: [PATCH 2/3] feat: mcp gateway schema types (auth, outbound auth, OAuth credentials) --- src/cli/operations/mcp/create-mcp.ts | 4 +- src/cli/operations/remove/remove-mcp-tool.ts | 4 +- .../tui/screens/mcp/useAddGatewayWizard.ts | 1 + src/schema/schemas/__tests__/mcp.test.ts | 28 ++++++++++- src/schema/schemas/agentcore-project.ts | 29 +++++++++-- src/schema/schemas/mcp.ts | 48 +++++++++++++++++-- 6 files changed, 101 insertions(+), 13 deletions(-) diff --git a/src/cli/operations/mcp/create-mcp.ts b/src/cli/operations/mcp/create-mcp.ts index a03543e5..5f4447e8 100644 --- a/src/cli/operations/mcp/create-mcp.ts +++ b/src/cli/operations/mcp/create-mcp.ts @@ -126,7 +126,7 @@ export async function getExistingToolNames(): Promise { // Gateway targets for (const gateway of mcpSpec.agentCoreGateways) { for (const target of gateway.targets) { - for (const toolDef of target.toolDefinitions) { + for (const toolDef of target.toolDefinitions ?? []) { toolNames.push(toolDef.name); } } @@ -256,7 +256,7 @@ export async function createToolFromWizard(config: AddMcpToolConfig): Promise t.name === toolDef.name)) { + if ((existingTarget.toolDefinitions ?? []).some(t => t.name === toolDef.name)) { throw new Error(`Tool "${toolDef.name}" already exists in gateway "${gateway.name}".`); } } diff --git a/src/cli/operations/remove/remove-mcp-tool.ts b/src/cli/operations/remove/remove-mcp-tool.ts index 9b02be84..dd4dab28 100644 --- a/src/cli/operations/remove/remove-mcp-tool.ts +++ b/src/cli/operations/remove/remove-mcp-tool.ts @@ -109,7 +109,7 @@ export async function previewRemoveMcpTool(tool: RemovableMcpTool): Promise g.name === tool.gatewayName); const target = gateway?.targets.find(t => t.name === tool.name); if (target) { - for (const toolDef of target.toolDefinitions) { + for (const toolDef of target.toolDefinitions ?? []) { toolNamesToRemove.push(toolDef.name); } } diff --git a/src/cli/tui/screens/mcp/useAddGatewayWizard.ts b/src/cli/tui/screens/mcp/useAddGatewayWizard.ts index 48c1f0b4..136c8899 100644 --- a/src/cli/tui/screens/mcp/useAddGatewayWizard.ts +++ b/src/cli/tui/screens/mcp/useAddGatewayWizard.ts @@ -5,6 +5,7 @@ import { useCallback, useMemo, useState } from 'react'; /** Maps authorizer type to the next step after authorizer selection */ const AUTHORIZER_NEXT_STEP: Record = { NONE: 'agents', + AWS_IAM: 'agents', CUSTOM_JWT: 'jwt-config', }; diff --git a/src/schema/schemas/__tests__/mcp.test.ts b/src/schema/schemas/__tests__/mcp.test.ts index cd437fd2..2414406b 100644 --- a/src/schema/schemas/__tests__/mcp.test.ts +++ b/src/schema/schemas/__tests__/mcp.test.ts @@ -261,6 +261,11 @@ describe('AgentCoreGatewayTargetSchema', () => { name: 'myTarget', targetType: 'lambda', toolDefinitions: [validToolDef], + compute: { + host: 'Lambda', + implementation: { language: 'Python', path: 'tools', handler: 'h' }, + pythonVersion: 'PYTHON_3_12', + }, }); expect(result.success).toBe(true); }); @@ -270,6 +275,11 @@ describe('AgentCoreGatewayTargetSchema', () => { name: 'myTarget', targetType: 'lambda', toolDefinitions: [], + compute: { + host: 'Lambda', + implementation: { language: 'Python', path: 'tools', handler: 'h' }, + pythonVersion: 'PYTHON_3_12', + }, }); expect(result.success).toBe(false); }); @@ -303,6 +313,11 @@ describe('AgentCoreGatewaySchema', () => { name: 'target1', targetType: 'lambda', toolDefinitions: [validToolDef], + compute: { + host: 'Lambda', + implementation: { language: 'Python', path: 'tools', handler: 'h' }, + pythonVersion: 'PYTHON_3_12', + }, }, ], }; @@ -387,7 +402,18 @@ describe('AgentCoreMcpSpecSchema', () => { agentCoreGateways: [ { name: 'gw1', - targets: [{ name: 't1', targetType: 'lambda', toolDefinitions: [validToolDef] }], + targets: [ + { + name: 't1', + targetType: 'lambda', + toolDefinitions: [validToolDef], + compute: { + host: 'Lambda', + implementation: { language: 'Python', path: 'tools', handler: 'h' }, + pythonVersion: 'PYTHON_3_12', + }, + }, + ], }, ], }); diff --git a/src/schema/schemas/agentcore-project.ts b/src/schema/schemas/agentcore-project.ts index 49a7f576..7f7e1c6c 100644 --- a/src/schema/schemas/agentcore-project.ts +++ b/src/schema/schemas/agentcore-project.ts @@ -71,9 +71,6 @@ export type Memory = z.infer; // Credential Schema // ============================================================================ -export const CredentialTypeSchema = z.literal('ApiKeyCredentialProvider'); -export type CredentialType = z.infer; - export const CredentialNameSchema = z .string() .min(3, 'Credential name must be at least 3 characters') @@ -83,11 +80,33 @@ export const CredentialNameSchema = z 'Must contain only alphanumeric characters, underscores, dots, and hyphens (3-255 chars)' ); -export const CredentialSchema = z.object({ - type: CredentialTypeSchema, +export const CredentialTypeSchema = z.enum(['ApiKeyCredentialProvider', 'OAuthCredentialProvider']); +export type CredentialType = z.infer; + +export const ApiKeyCredentialSchema = z.object({ + type: z.literal('ApiKeyCredentialProvider'), + name: CredentialNameSchema, +}); + +export type ApiKeyCredential = z.infer; + +export const OAuthCredentialSchema = z.object({ + type: z.literal('OAuthCredentialProvider'), name: CredentialNameSchema, + /** OIDC discovery URL for the OAuth provider */ + discoveryUrl: z.string().url(), + /** Scopes this credential provider supports */ + scopes: z.array(z.string()).default([]), + /** Credential provider vendor type */ + vendor: z.string().default('CustomOauth2'), + /** Whether this credential was auto-created by the CLI (e.g., for CUSTOM_JWT inbound auth) */ + managed: z.boolean().optional(), }); +export type OAuthCredential = z.infer; + +export const CredentialSchema = z.discriminatedUnion('type', [ApiKeyCredentialSchema, OAuthCredentialSchema]); + export type Credential = z.infer; // ============================================================================ diff --git a/src/schema/schemas/mcp.ts b/src/schema/schemas/mcp.ts index e3890df1..eb6e9553 100644 --- a/src/schema/schemas/mcp.ts +++ b/src/schema/schemas/mcp.ts @@ -15,7 +15,7 @@ export type GatewayTargetType = z.infer; // Gateway Authorization Schemas // ============================================================================ -export const GatewayAuthorizerTypeSchema = z.enum(['NONE', 'CUSTOM_JWT']); +export const GatewayAuthorizerTypeSchema = z.enum(['NONE', 'AWS_IAM', 'CUSTOM_JWT']); export type GatewayAuthorizerType = z.infer; /** OIDC well-known configuration endpoint suffix (per OpenID Connect Discovery 1.0 spec) */ @@ -44,6 +44,7 @@ export const CustomJwtAuthorizerConfigSchema = z.object({ allowedAudience: z.array(z.string().min(1)), /** List of allowed client IDs */ allowedClients: z.array(z.string().min(1)).min(1), + allowedScopes: z.array(z.string().min(1)).optional(), }); export type CustomJwtAuthorizerConfig = z.infer; @@ -57,6 +58,19 @@ export const GatewayAuthorizerConfigSchema = z.object({ export type GatewayAuthorizerConfig = z.infer; +export const OutboundAuthTypeSchema = z.enum(['OAUTH', 'API_KEY', 'NONE']); +export type OutboundAuthType = z.infer; + +export const OutboundAuthSchema = z + .object({ + type: OutboundAuthTypeSchema.default('NONE'), + credentialName: z.string().min(1).optional(), + scopes: z.array(z.string()).optional(), + }) + .strict(); + +export type OutboundAuth = z.infer; + export const McpImplLanguageSchema = z.enum(['TypeScript', 'Python']); export type McpImplementationLanguage = z.infer; @@ -262,10 +276,38 @@ export const AgentCoreGatewayTargetSchema = z .object({ name: z.string().min(1), targetType: GatewayTargetTypeSchema, - toolDefinitions: z.array(ToolDefinitionSchema).min(1), + /** Tool definitions. Required for Lambda targets. Optional for MCP Server (discovered via tools/list). */ + toolDefinitions: z.array(ToolDefinitionSchema).optional(), + /** Compute configuration. Required for Lambda/Runtime scaffold targets. */ compute: ToolComputeConfigSchema.optional(), + /** MCP Server endpoint URL. Required for external MCP Server targets. */ + endpoint: z.string().url().optional(), + /** Outbound auth configuration for the target. */ + outboundAuth: OutboundAuthSchema.optional(), }) - .strict(); + .strict() + .refine( + data => { + // External MCP Server: needs endpoint, no compute + if (data.targetType === 'mcpServer' && !data.compute && !data.endpoint) { + return false; + } + // Lambda target: needs compute and tool definitions + if (data.targetType === 'lambda') { + if (!data.compute) return false; + if (!data.toolDefinitions || data.toolDefinitions.length === 0) return false; + } + // Outbound auth with credential needs a credential name + if (data.outboundAuth && data.outboundAuth.type !== 'NONE' && !data.outboundAuth.credentialName) { + return false; + } + return true; + }, + { + message: + 'Invalid target configuration. MCP Server targets need an endpoint or compute. Lambda targets need compute and tool definitions. OAuth/API_KEY auth needs a credential name.', + } + ); export type AgentCoreGatewayTarget = z.infer; From df42f7ebfad446c789c79da5c54b3000075055c4 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 23 Feb 2026 11:17:31 -0500 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20address=20CR=20feedback=20=E2=80=94?= =?UTF-8?q?=20scopes=20optional,=20specific=20validation=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/schema/schemas/agentcore-project.ts | 2 +- src/schema/schemas/mcp.ts | 49 ++++++++++++++----------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/schema/schemas/agentcore-project.ts b/src/schema/schemas/agentcore-project.ts index 7f7e1c6c..13f8241f 100644 --- a/src/schema/schemas/agentcore-project.ts +++ b/src/schema/schemas/agentcore-project.ts @@ -96,7 +96,7 @@ export const OAuthCredentialSchema = z.object({ /** OIDC discovery URL for the OAuth provider */ discoveryUrl: z.string().url(), /** Scopes this credential provider supports */ - scopes: z.array(z.string()).default([]), + scopes: z.array(z.string()).optional(), /** Credential provider vendor type */ vendor: z.string().default('CustomOauth2'), /** Whether this credential was auto-created by the CLI (e.g., for CUSTOM_JWT inbound auth) */ diff --git a/src/schema/schemas/mcp.ts b/src/schema/schemas/mcp.ts index eb6e9553..f529da4a 100644 --- a/src/schema/schemas/mcp.ts +++ b/src/schema/schemas/mcp.ts @@ -286,28 +286,35 @@ export const AgentCoreGatewayTargetSchema = z outboundAuth: OutboundAuthSchema.optional(), }) .strict() - .refine( - data => { - // External MCP Server: needs endpoint, no compute - if (data.targetType === 'mcpServer' && !data.compute && !data.endpoint) { - return false; - } - // Lambda target: needs compute and tool definitions - if (data.targetType === 'lambda') { - if (!data.compute) return false; - if (!data.toolDefinitions || data.toolDefinitions.length === 0) return false; - } - // Outbound auth with credential needs a credential name - if (data.outboundAuth && data.outboundAuth.type !== 'NONE' && !data.outboundAuth.credentialName) { - return false; - } - return true; - }, - { - message: - 'Invalid target configuration. MCP Server targets need an endpoint or compute. Lambda targets need compute and tool definitions. OAuth/API_KEY auth needs a credential name.', + .superRefine((data, ctx) => { + if (data.targetType === 'mcpServer' && !data.compute && !data.endpoint) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'MCP Server targets require either an endpoint URL or compute configuration.', + }); } - ); + if (data.targetType === 'lambda' && !data.compute) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Lambda targets require compute configuration.', + path: ['compute'], + }); + } + if (data.targetType === 'lambda' && (!data.toolDefinitions || data.toolDefinitions.length === 0)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Lambda targets require at least one tool definition.', + path: ['toolDefinitions'], + }); + } + if (data.outboundAuth && data.outboundAuth.type !== 'NONE' && !data.outboundAuth.credentialName) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `${data.outboundAuth.type} outbound auth requires a credentialName.`, + path: ['outboundAuth', 'credentialName'], + }); + } + }); export type AgentCoreGatewayTarget = z.infer;