Skip to content

Commit 3e0471b

Browse files
Merge branch 'main' into feat/tui-harness
2 parents 60db522 + 5c8d1b4 commit 3e0471b

119 files changed

Lines changed: 2855 additions & 185 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ workdir-tmp/
6060
bun.lock
6161

6262
.agentreview
63+
ProtocolTesting/

AGENTS.md

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ Note: CDK L3 constructs are in a separate package `@aws/agentcore-cdk`.
4747
- **Container**: Agent is built as a Docker container image, deployed via ECR and CodeBuild. Requires a `Dockerfile` in
4848
the agent's code directory. Supported container runtimes: Docker, Podman, Finch.
4949

50-
### Coming Soon
51-
52-
- MCP gateway and tool support (`add gateway`, `add mcp-tool`) - currently hidden
53-
5450
## Primitives Architecture
5551

5652
All resource types (agent, memory, identity, gateway, mcp-tool) are modeled as **primitives** -- self-contained classes
@@ -61,11 +57,11 @@ Each primitive extends `BasePrimitive` and implements: `add()`, `remove()`, `pre
6157

6258
Current primitives:
6359

64-
- `AgentPrimitive` -- agent creation (template + BYO), removal, credential resolution
65-
- `MemoryPrimitive` -- memory creation with strategies, removal
66-
- `CredentialPrimitive` -- credential/identity creation, .env management, removal
67-
- `GatewayPrimitive` -- MCP gateway creation/removal (hidden, coming soon)
68-
- `GatewayTargetPrimitive` -- MCP tool creation/removal with code generation (hidden, coming soon)
60+
- `AgentPrimitive` agent creation (template + BYO), removal, credential resolution
61+
- `MemoryPrimitive` memory creation with strategies, removal
62+
- `CredentialPrimitive` credential/identity creation, .env management, removal
63+
- `GatewayPrimitive` MCP gateway creation/removal
64+
- `GatewayTargetPrimitive` MCP tool creation/removal with code generation
6965

7066
Singletons are created in `registry.ts` and wired into CLI commands via `cli.ts`. See `src/cli/AGENTS.md` for details on
7167
adding new primitives.
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
/* eslint-disable security/detect-non-literal-fs-filename */
2+
import { exists, prereqs, readProjectConfig, runCLI } from '../src/test-utils/index.js';
3+
import { randomUUID } from 'node:crypto';
4+
import { mkdir, readFile, rm } from 'node:fs/promises';
5+
import { tmpdir } from 'node:os';
6+
import { join } from 'node:path';
7+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
8+
9+
describe.skipIf(!prereqs.npm || !prereqs.git)('integration: create with protocol modes', () => {
10+
let testDir: string;
11+
12+
beforeAll(async () => {
13+
testDir = join(tmpdir(), `agentcore-integ-protocols-${randomUUID()}`);
14+
await mkdir(testDir, { recursive: true });
15+
});
16+
17+
afterAll(async () => {
18+
await rm(testDir, { recursive: true, force: true });
19+
});
20+
21+
it('creates MCP standalone project (no framework, no model provider)', async () => {
22+
const name = `Mcp${Date.now().toString().slice(-6)}`;
23+
const result = await runCLI(
24+
['create', '--name', name, '--language', 'Python', '--protocol', 'MCP', '--json'],
25+
testDir
26+
);
27+
28+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
29+
const json = JSON.parse(result.stdout);
30+
expect(json.success).toBe(true);
31+
32+
const agentName = json.agentName || name;
33+
const agentDir = join(json.projectPath, 'app', agentName);
34+
35+
expect(await exists(agentDir), 'Agent directory should exist').toBe(true);
36+
expect(await exists(join(agentDir, 'main.py')), 'main.py should exist').toBe(true);
37+
expect(await exists(join(agentDir, 'pyproject.toml')), 'pyproject.toml should exist').toBe(true);
38+
39+
// Verify pyproject.toml references mcp (FastMCP)
40+
const pyproject = await readFile(join(agentDir, 'pyproject.toml'), 'utf-8');
41+
expect(pyproject.toLowerCase().includes('mcp'), 'pyproject.toml should reference mcp').toBe(true);
42+
43+
// Verify config has protocol set to MCP
44+
const config = await readProjectConfig(json.projectPath);
45+
const agents = config.agents as Record<string, unknown>[];
46+
expect(agents).toBeDefined();
47+
expect(agents.length).toBe(1);
48+
expect(agents[0]!.name).toBe(agentName);
49+
expect(agents[0]!.protocol).toBe('MCP');
50+
51+
// MCP agents should have no credentials
52+
const credentials = (config.credentials as Record<string, unknown>[]) ?? [];
53+
expect(credentials.length).toBe(0);
54+
});
55+
56+
it('creates A2A project with Strands framework', async () => {
57+
const name = `A2a${Date.now().toString().slice(-6)}`;
58+
const result = await runCLI(
59+
[
60+
'create',
61+
'--name',
62+
name,
63+
'--language',
64+
'Python',
65+
'--protocol',
66+
'A2A',
67+
'--framework',
68+
'Strands',
69+
'--model-provider',
70+
'Bedrock',
71+
'--memory',
72+
'none',
73+
'--json',
74+
],
75+
testDir
76+
);
77+
78+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
79+
const json = JSON.parse(result.stdout);
80+
expect(json.success).toBe(true);
81+
82+
const agentName = json.agentName || name;
83+
const agentDir = join(json.projectPath, 'app', agentName);
84+
85+
expect(await exists(agentDir), 'Agent directory should exist').toBe(true);
86+
expect(await exists(join(agentDir, 'main.py')), 'main.py should exist').toBe(true);
87+
88+
// Verify config has protocol set to A2A
89+
const config = await readProjectConfig(json.projectPath);
90+
const agents = config.agents as Record<string, unknown>[];
91+
expect(agents.length).toBe(1);
92+
expect(agents[0]!.protocol).toBe('A2A');
93+
});
94+
95+
it('creates HTTP project with explicit protocol HTTP', async () => {
96+
const name = `Http${Date.now().toString().slice(-6)}`;
97+
const result = await runCLI(
98+
[
99+
'create',
100+
'--name',
101+
name,
102+
'--language',
103+
'Python',
104+
'--framework',
105+
'Strands',
106+
'--model-provider',
107+
'Bedrock',
108+
'--memory',
109+
'none',
110+
'--json',
111+
],
112+
testDir
113+
);
114+
115+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
116+
const json = JSON.parse(result.stdout);
117+
expect(json.success).toBe(true);
118+
119+
// Verify config has explicit protocol: HTTP
120+
const config = await readProjectConfig(json.projectPath);
121+
const agents = config.agents as Record<string, unknown>[];
122+
expect(agents.length).toBe(1);
123+
expect(agents[0]!.protocol).toBe('HTTP');
124+
});
125+
126+
it('rejects invalid protocol', async () => {
127+
const name = `Bad${Date.now().toString().slice(-6)}`;
128+
const result = await runCLI(
129+
['create', '--name', name, '--language', 'Python', '--protocol', 'INVALID', '--json'],
130+
testDir
131+
);
132+
133+
expect(result.exitCode).not.toBe(0);
134+
});
135+
136+
it('rejects MCP with --framework flag', async () => {
137+
const name = `McpFw${Date.now().toString().slice(-6)}`;
138+
const result = await runCLI(
139+
['create', '--name', name, '--language', 'Python', '--protocol', 'MCP', '--framework', 'Strands', '--json'],
140+
testDir
141+
);
142+
143+
expect(result.exitCode).not.toBe(0);
144+
});
145+
146+
it('rejects A2A with unsupported framework (CrewAI)', async () => {
147+
const name = `A2aCrew${Date.now().toString().slice(-6)}`;
148+
const result = await runCLI(
149+
[
150+
'create',
151+
'--name',
152+
name,
153+
'--language',
154+
'Python',
155+
'--protocol',
156+
'A2A',
157+
'--framework',
158+
'CrewAI',
159+
'--model-provider',
160+
'Bedrock',
161+
'--memory',
162+
'none',
163+
'--json',
164+
],
165+
testDir
166+
);
167+
168+
expect(result.exitCode).not.toBe(0);
169+
});
170+
});
171+
172+
describe.skipIf(!prereqs.npm || !prereqs.git)('integration: add agent with protocol modes', () => {
173+
let testDir: string;
174+
let projectPath: string;
175+
176+
beforeAll(async () => {
177+
testDir = join(tmpdir(), `agentcore-integ-add-protocol-${randomUUID()}`);
178+
await mkdir(testDir, { recursive: true });
179+
180+
// Create a base project first (HTTP, no agent)
181+
const result = await runCLI(['create', '--name', 'ProtoTest', '--no-agent', '--json'], testDir);
182+
expect(result.exitCode, `setup stderr: ${result.stderr}`).toBe(0);
183+
const json = JSON.parse(result.stdout);
184+
projectPath = json.projectPath;
185+
});
186+
187+
afterAll(async () => {
188+
await rm(testDir, { recursive: true, force: true });
189+
});
190+
191+
it('adds MCP agent to existing project', async () => {
192+
const name = `McpAgent${Date.now().toString().slice(-6)}`;
193+
const result = await runCLI(
194+
['add', 'agent', '--name', name, '--protocol', 'MCP', '--language', 'Python', '--json'],
195+
projectPath
196+
);
197+
198+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
199+
const json = JSON.parse(result.stdout);
200+
expect(json.success).toBe(true);
201+
202+
const config = await readProjectConfig(projectPath);
203+
const agents = config.agents as Record<string, unknown>[];
204+
const mcpAgent = agents.find(a => a.name === name);
205+
expect(mcpAgent).toBeDefined();
206+
expect(mcpAgent!.protocol).toBe('MCP');
207+
});
208+
209+
it('adds A2A agent to existing project', async () => {
210+
const name = `A2aAgent${Date.now().toString().slice(-6)}`;
211+
const result = await runCLI(
212+
[
213+
'add',
214+
'agent',
215+
'--name',
216+
name,
217+
'--protocol',
218+
'A2A',
219+
'--framework',
220+
'Strands',
221+
'--model-provider',
222+
'Bedrock',
223+
'--memory',
224+
'none',
225+
'--language',
226+
'Python',
227+
'--json',
228+
],
229+
projectPath
230+
);
231+
232+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
233+
const json = JSON.parse(result.stdout);
234+
expect(json.success).toBe(true);
235+
236+
const config = await readProjectConfig(projectPath);
237+
const agents = config.agents as Record<string, unknown>[];
238+
const a2aAgent = agents.find(a => a.name === name);
239+
expect(a2aAgent).toBeDefined();
240+
expect(a2aAgent!.protocol).toBe('A2A');
241+
});
242+
243+
it('adds BYO agent with MCP protocol', async () => {
244+
const name = `ByoMcp${Date.now().toString().slice(-6)}`;
245+
const result = await runCLI(
246+
[
247+
'add',
248+
'agent',
249+
'--name',
250+
name,
251+
'--type',
252+
'byo',
253+
'--protocol',
254+
'MCP',
255+
'--language',
256+
'Python',
257+
'--code-location',
258+
`app/${name}/`,
259+
'--json',
260+
],
261+
projectPath
262+
);
263+
264+
expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0);
265+
const json = JSON.parse(result.stdout);
266+
expect(json.success).toBe(true);
267+
268+
const config = await readProjectConfig(projectPath);
269+
const agents = config.agents as Record<string, unknown>[];
270+
const byoAgent = agents.find(a => a.name === name);
271+
expect(byoAgent).toBeDefined();
272+
expect(byoAgent!.protocol).toBe('MCP');
273+
});
274+
});

0 commit comments

Comments
 (0)