Skip to content

Modernize agent + MCP server, faster deploy#4

Open
marlenezw wants to merge 4 commits intomainfrom
modernize-core
Open

Modernize agent + MCP server, faster deploy#4
marlenezw wants to merge 4 commits intomainfrom
modernize-core

Conversation

@marlenezw
Copy link
Copy Markdown
Collaborator

Pre-SQLite modernization changes that have been validated end-to-end on the Postgres flavor.

What's included

2dd8d08 Speed up deploy: uv-based Dockerfiles, resilient postprovision

  • Switch agent + MCP Dockerfiles to uv for ~3× faster dependency install
  • Make postprovision hook idempotent and resilient to partial failures

df72730 Modernize agent: lifespan init, unified MCP path, testable stream parser

  • Use FastAPI lifespan for clean startup/shutdown of MCP client and agent
  • Unify MCP tool wiring so adding new servers is a one-line change
  • Extract streaming parser into a pure function with unit-test hooks

8476cf9 Phase 3: modernize MCP server (fastmcp 2.x, simpler DSN handling)

  • Upgrade to fastmcp 2.x APIs
  • Simplify Postgres DSN handling (single source of truth, less env juggling)

Not included

SQLite-backed minimal flavor and follow-up fixes — those will land in a separate PR once the chart-rendering issue is resolved.

Validation

azd up succeeds end-to-end against Postgres; agent answers product, sales, and chart questions correctly.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Marlene Mhangami and others added 4 commits May 1, 2026 11:39
- Switch agent/ + mcp/ Dockerfiles from pip to uv (4-6x faster cold builds:
  agent 30s vs ~150s, mcp 13s vs ~60s; warm rebuilds ~1s).
- Rewrite postprovision.sh to be tolerant of local environment quirks:
  use a venv (avoids PEP-668), tolerate missing pip, auto-add the client
  IP to the Postgres firewall, and never block infra provisioning on
  seeding failures. Re-runnable via 'azd hooks run postprovision'.
- Set azure.yaml hooks.postprovision.continueOnError: true so a seeding
  hiccup doesn't fail an otherwise-successful 'azd up'.
- Drop the dead 2-3 GB download branch (data ships with the repo, ~18 MB).
- Fix .env.example: AZURE_OPENAI_EMBEDDING_DEPLOYMENT now matches the
  Bicep deployment (text-embedding-3-small, was text-embedding-ada-002).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
agent/app.py shrinks from 609 -> 240 lines by:

- Moving Azure credential, ChatOpenAI, MultiServerMCPClient, MCP tool
  fetch, and create_agent into a single Starlette lifespan. Each chat
  request now reuses the same agent and token provider instead of
  rebuilding all of them per request.
- Removing the local/production code duality. We now use
  langchain-mcp-adapters in both modes; the OpenAI Responses-API native
  MCP path ({"type": "mcp", ...}) is gone. One code path, one set of
  semantics for stream events.
- Removing the image-generation sub-agent workaround for
  langchain-ai/langchain#34136 (fixed in langchain v1). Image generation
  is feature-flagged via ENABLE_IMAGE_GENERATION (default off) so the
  default deploy is conservative.
- Dropping temperature=0.7 (gpt-5-mini is reasoning-style and ignores
  or rejects non-default temperature).
- Adding bounded-retry/backoff for MCP startup (1,2,4,8,16s) so a
  parallel start-up race in Container Apps doesn't crash-loop the
  service unnecessarily.
- Reflecting readiness in /api/health (status, ready flag, mcp_tool_count).

agent/streaming.py (new, 214 lines) extracts the streaming response
parser into pure helpers driven by a layered chunk inspection:
tool_calls / additional_kwargs first, then content_blocks (LangChain v1
normalized format), then raw content. The state-machine for
{"status": ""} clear events is preserved exactly.

agent/test_streaming.py (new, 154 lines, 17 tests, pytest, 0.02s) covers
the NDJSON event contract with hand-built chunks for: text streaming,
reasoning blocks (ignored), tool_calls via tool_calls and via
additional_kwargs, server tool start/result, code_interpreter with
image data URLs and file outputs, response_metadata image outputs,
direct image blocks (data URL + raw base64), and JSON-stringified image
results from custom MCP tools.

agent/requirements.txt bumped to a coherent LC v1 set:
langchain >=1.0, langchain-openai >=1.0, langchain-core >=1.0,
langchain-mcp-adapters >=0.2.2 (latest published).

E2E verified locally against the deployed Azure MCP + OpenAI services:
agent boots in ~3s, loads 4 MCP tools, returns the documented NDJSON
event sequence (status -> empty status -> chunks -> done message), and
correctly executes a 'count products' query via the MCP tool against
the live Postgres (424 rows).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Pin fastmcp to >=2.0,<3 (verified with 2.14.7); tools, lifespan,
  Context API, and http_app() all compatible.
- Drop hand-rolled parse_postgres_url regex; pass DSN directly to
  asyncpg.create_pool. Add _safe_dsn() helper to URL-encode userinfo
  so passwords with '#' (auto-generated by azd) parse correctly.
- Default embedding deployment to text-embedding-3-small (matches the
  Bicep that infra/main.bicep actually deploys).
- Bump api_version default to 2024-12-01-preview; configurable via
  AZURE_OPENAI_API_VERSION env var.
- Tighten SQL guard: also reject WITH-without-SELECT, MERGE, CALL, COPY;
  accept WITH-prefixed SELECTs (CTEs).
- Add unit tests (mcp/test_app.py): 25 tests covering SQL validation +
  DSN encoding edge cases.
- File trimmed 470 -> 311 lines (-34%); behaviour unchanged externally.

Tested:
- 25/25 unit tests pass
- Docker build OK with fastmcp 2.14.7
- E2E against deployed Postgres (with '#' in pwd) + live MCP tools:
  4 tools register, get_table_schemas returns 10 tables,
  execute_sales_query returns 424 products, DROP TABLE rejected.
- Replace marketing-style feature list with a 'What you'll learn' section
- Add repository layout tree
- Expand 'How it works' with real explanations of Responses API, MCP transport, Entra ID auth, and Bicep
- Convert troubleshooting tips to a table; trim filler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant