From b6cb1d61af2deb77edbd31560ab95670740f3567 Mon Sep 17 00:00:00 2001 From: notgitika Date: Mon, 19 Oct 2026 15:36:55 -0400 Subject: [PATCH 1/2] fix(dev): honor custom ports for MCP and A2A --- src/assets/templates/mcp-python-fastmcp/main.py | 9 ++++++--- src/assets/templates/runtime-docs/python/AGENTS.md | 8 +++++--- src/assets/templates/runtime-docs/python/README.md | 14 +++++++++++--- src/core/dev/codezip.test.ts | 1 + src/core/dev/codezip.ts | 3 +++ src/handlers/project/project.test.ts | 1 + 6 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/assets/templates/mcp-python-fastmcp/main.py b/src/assets/templates/mcp-python-fastmcp/main.py index 8136299292..fd5cef416f 100644 --- a/src/assets/templates/mcp-python-fastmcp/main.py +++ b/src/assets/templates/mcp-python-fastmcp/main.py @@ -1,9 +1,12 @@ -{{#if needsOs}} import os -{{/if}} from mcp.server.fastmcp import FastMCP -mcp = FastMCP("{{ name }}", host="0.0.0.0", stateless_http=True) +mcp = FastMCP( + "{{ name }}", + host="0.0.0.0", + port=int(os.environ.get("FASTMCP_PORT", "8000")), + stateless_http=True, +) {{#if needsOs}} _MOUNT_PATHS = [ diff --git a/src/assets/templates/runtime-docs/python/AGENTS.md b/src/assets/templates/runtime-docs/python/AGENTS.md index ad8d7cd644..bf1542d00b 100644 --- a/src/assets/templates/runtime-docs/python/AGENTS.md +++ b/src/assets/templates/runtime-docs/python/AGENTS.md @@ -30,14 +30,16 @@ in more detail. - Accept the platform-provided `Mcp-Session-Id` header. Stay stateless unless the user needs elicitation or sampling with MCP protocol version 2025-11-25 or earlier, which requires stateful mode. -- Add `mcp` to the dependencies. Do not hardcode a port other than 8000; - `agentcore dev` sets `FASTMCP_PORT`. +- Add `mcp` to the dependencies. Pass + `port=int(os.environ.get("FASTMCP_PORT", "8000"))` to `FastMCP`, so + `agentcore dev` can move the server when port 8000 is busy. {{/if}} {{#if (eq protocol "A2A")}} - Serve A2A on `0.0.0.0` port 9000: JSON-RPC 2.0 at `POST /` (for example `message/send`), the agent card at `GET /.well-known/agent-card.json`, and `GET /ping`. Use `serve_a2a(executor)` from `bedrock_agentcore.runtime` - under `if __name__ == "__main__":`; it implements all three. + under `if __name__ == "__main__":`; it implements all three and reads the + `A2A_PORT` override that `agentcore dev` sets for local development. - Add `bedrock-agentcore[a2a] ~= 1.24.0` and `a2a-sdk` to the dependencies. {{/if}} {{#if (eq protocol "AGUI")}} diff --git a/src/assets/templates/runtime-docs/python/README.md b/src/assets/templates/runtime-docs/python/README.md index c9dd5c0f9b..b224f373cb 100644 --- a/src/assets/templates/runtime-docs/python/README.md +++ b/src/assets/templates/runtime-docs/python/README.md @@ -110,9 +110,15 @@ with MCP protocol version 2025-11-25 and earlier; from 2026-07-28 those use multi-round-trip requests, which work in stateless mode. ```python +import os from mcp.server.fastmcp import FastMCP -mcp = FastMCP("{{name}}", host="0.0.0.0", stateless_http=True) +mcp = FastMCP( + "{{name}}", + host="0.0.0.0", + port=int(os.environ.get("FASTMCP_PORT", "8000")), + stateless_http=True, +) @mcp.tool() @@ -125,8 +131,8 @@ if __name__ == "__main__": mcp.run(transport="streamable-http") ``` -Add `mcp` to your dependencies. `agentcore dev` sets `FASTMCP_PORT`, so -don't hardcode a different port. +Add `mcp` to your dependencies. Read `FASTMCP_PORT` as above so +`agentcore dev` can move the server when port 8000 is busy. Full contract: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-mcp-protocol-contract.html {{/if}} @@ -146,6 +152,8 @@ if __name__ == "__main__": Add `bedrock-agentcore[a2a] ~= 1.24.0` (the version line with `serve_a2a`) and `a2a-sdk` to your dependencies, as in the `pyproject.toml` example below. +`agentcore dev` sets `A2A_PORT` when it needs a local port other than the +deployed service-contract default of 9000; `serve_a2a` reads it automatically. Full contract: https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a-protocol-contract.html {{/if}} diff --git a/src/core/dev/codezip.test.ts b/src/core/dev/codezip.test.ts index 4be2380d5a..67609cf16f 100644 --- a/src/core/dev/codezip.test.ts +++ b/src/core/dev/codezip.test.ts @@ -189,6 +189,7 @@ describe("CodeZipDevRunner", () => { expect(calls[0]?.command).toEqual(["uv", "run", "python", "main.py"]); expect(calls[0]?.options.env?.FASTMCP_PORT).toBe(protocol === "MCP" ? "9000" : undefined); + expect(calls[0]?.options.env?.A2A_PORT).toBe(protocol === "A2A" ? "9000" : undefined); }, ); diff --git a/src/core/dev/codezip.ts b/src/core/dev/codezip.ts index c91dc77d4d..37aa590ffc 100644 --- a/src/core/dev/codezip.ts +++ b/src/core/dev/codezip.ts @@ -131,6 +131,9 @@ function commandForRuntime( if (input.runtime.protocol === "MCP") { env.FASTMCP_PORT = String(input.port); } + if (input.runtime.protocol === "A2A") { + env.A2A_PORT = String(input.port); + } if (!entrypoint.endsWith(".py")) { return { diff --git a/src/handlers/project/project.test.ts b/src/handlers/project/project.test.ts index 3e57fb3bb4..0749d9d200 100644 --- a/src/handlers/project/project.test.ts +++ b/src/handlers/project/project.test.ts @@ -422,6 +422,7 @@ describe("project create", () => { const runtimeRoot = join(projectRoot, "app", "agent"); const mainPy = await Bun.file(join(runtimeRoot, "main.py")).text(); expect(mainPy).toContain("FastMCP"); + expect(mainPy).toContain('port=int(os.environ.get("FASTMCP_PORT", "8000"))'); expect(mainPy).toContain('mcp.run(transport="streamable-http")'); expect(await Bun.file(join(runtimeRoot, "Dockerfile")).exists()).toBe(false); expect(spec.memories ?? []).toEqual([]); From 9f3d6afef14d78b8bc4753c060f827f0947d6f08 Mon Sep 17 00:00:00 2001 From: notgitika Date: Mon, 19 Oct 2026 15:40:37 -0400 Subject: [PATCH 2/2] fix(dev): reserve the A2A port variable --- src/assets/templates/shared/env.local.template | 10 +++++----- src/handlers/project/dev/environment.test.ts | 2 ++ src/handlers/project/dev/environment.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/assets/templates/shared/env.local.template b/src/assets/templates/shared/env.local.template index 97af2bb1b2..18e0a1e178 100644 --- a/src/assets/templates/shared/env.local.template +++ b/src/assets/templates/shared/env.local.template @@ -1,10 +1,10 @@ # Environment variables for local development. # `agentcore dev` loads this file into your agent's process. Values here -# override injected values except PORT, FASTMCP_PORT, and LOCAL_DEV, which the -# CLI owns. While trace collection is on (the default), the CLI also owns the -# OTEL_* and AGENT_OBSERVABILITY_ENABLED variables so traces reach its local -# collector — pass --no-traces (or set instrumentation.enableOtel to false in -# agentcore.json) to disable collection and set your own. +# override injected values except PORT, FASTMCP_PORT, A2A_PORT, and LOCAL_DEV, +# which the CLI owns. While trace collection is on (the default), the CLI also +# owns the OTEL_* and AGENT_OBSERVABILITY_ENABLED variables so traces reach its +# local collector — pass --no-traces (or set instrumentation.enableOtel to +# false in agentcore.json) to disable collection and set your own. # This file is gitignored — keep secrets out of version control. # # Example: diff --git a/src/handlers/project/dev/environment.test.ts b/src/handlers/project/dev/environment.test.ts index ee8c733540..d6a22b96a4 100644 --- a/src/handlers/project/dev/environment.test.ts +++ b/src/handlers/project/dev/environment.test.ts @@ -33,6 +33,7 @@ SHARED="local value" AWS_REGION=local-region PORT=enc:v1:ignored FASTMCP_PORT=enc:v1:ignored +A2A_PORT=enc:v1:ignored LOCAL_DEV=enc:v1:ignored MULTILINE="first second" @@ -45,6 +46,7 @@ second" { name: "SHARED", value: "runtime" }, { name: "RUNTIME_ONLY", value: "yes" }, { name: "PORT", value: "1234" }, + { name: "A2A_PORT", value: "9000" }, ]), ), ).resolves.toEqual({ diff --git a/src/handlers/project/dev/environment.ts b/src/handlers/project/dev/environment.ts index 27129c56bc..b16071ad34 100644 --- a/src/handlers/project/dev/environment.ts +++ b/src/handlers/project/dev/environment.ts @@ -2,7 +2,7 @@ import { join } from "node:path"; import type { ProjectRuntime } from "../../../projectSchemas/runtime"; import { readEnvFile } from "../../../io/envFile"; -const RESERVED_ENV_KEYS = ["PORT", "FASTMCP_PORT", "LOCAL_DEV"] as const; +const RESERVED_ENV_KEYS = ["PORT", "FASTMCP_PORT", "A2A_PORT", "LOCAL_DEV"] as const; export type DevEnvironmentInput = { projectRoot: string;