# Architect MCP — Python host (reference implementation)

A minimal Python agent loop that drives the standalone Architect MCP
bundle via the official Python MCP SDK. Reference implementation for
integrating the Architect MCP server with a Python-side LLM host —
the agent loop is ~150 lines, the provider abstraction another ~80,
and every tool call is printed to stdout so the mechanics are visible.

The default LLM provider is **GitHub Models** (GPT-4o-mini by default)
via the OpenAI SDK pointed at the GitHub Models inference endpoint.
Free during preview, single API key, Microsoft-aligned. The wire
format is the same Azure-AI-Inference shape that real Azure OpenAI
uses, so this also previews the migration path to a corporate Azure
OpenAI deployment.

## Why this exists

It pairs with the standalone MCP bundle
(`architect-language-tools/mcp/dist/architect-mcp-standalone.js`) to
demonstrate end-to-end MCP integration in a form readable in one
sitting. The MCP server is the durable artefact and any compliant
MCP client can drive it; this Python host is the reference Python
client we ship alongside it.

The `providers/` directory is one file (`github_provider.py`); the seam
is preserved so adding another LLM (Azure OpenAI, Anthropic, etc.)
later is a ~30-line addition rather than a refactor.

## Setup

From the repo root:

```sh
cd architect-mcp-python-host
python -m venv .venv
# Windows PowerShell
.venv\Scripts\Activate.ps1
# Windows cmd / Git Bash
# .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
# edit .env — fill in GITHUB_TOKEN
```

Get the GitHub PAT at https://github.com/settings/tokens (classic, no
scopes needed during preview).

Build the standalone bundle once (it lives in a `dist/` that's
gitignored, so it must be regenerated after a fresh checkout):

```sh
cd ../architect-language-tools
ARCH_LANG_BUILD=standalone node mcp/build.js
# PowerShell:
# $env:ARCH_LANG_BUILD = "standalone"; node mcp\build.js
```

## Run

```sh
python agent.py \
    --workspace path/to/Reports \
    --question "What functions are declared in Reports/COMMON_HELPERS.TXT, and which other files reference them?"
```

A multi-tool composition question is the most illuminating demo:

> *"What functions in Reports/COMMON_HELPERS.TXT are referenced from
> elsewhere in the workspace?"*

That requires `list_symbols` followed by `find_references` per
function — the model has to plan the sequence itself.

### Optional flags

- `--model <name>` — override the default `gpt-4o-mini`. Other tool-capable
  options on GitHub Models include `gpt-4o`, `Phi-3.5-MoE-instruct`,
  `Mistral-large`. Heavier models plan multi-step questions better but
  are rate-limited more aggressively on the free tier.
- `--system "..."` — override the default system prompt.
- `--bundle <path>` — point at a different MCP bundle (useful when
  testing local builds).
- `--log [PATH]` — write a JSONL transcript of every provider request,
  every provider response, every MCP tool call, and every MCP tool
  result. With no argument, writes to `./logs/run-YYYYMMDD-HHMMSS.jsonl`.
  See *Logs* below.

## What the console output shows

Each turn prints:

- the model name being called,
- any text the model produced,
- every `[tool_use] name(args)` it requested,
- the `[tool_result]` body returned by the MCP server (truncated at
  400 chars in the console — the full text is what the model actually
  receives, and the full text is captured by `--log`).

When the model stops asking for tools, the loop exits and the final
text is the answer.

## Logs

Run with `--log` to capture full I/O as JSONL — one event per line,
UTC-timestamped.

Event kinds:

| `kind`              | When                | Captures                                                       |
|---------------------|---------------------|----------------------------------------------------------------|
| `session_start`     | once at startup     | provider, model, workspace, question, system prompt, MCP tools |
| `provider_request`  | each turn           | full `messages` list sent to the LLM                           |
| `provider_response` | each turn           | text + tool_calls + raw assistant message                      |
| `mcp_tool_call`     | per tool dispatch   | tool name, full arguments                                      |
| `mcp_tool_result`   | per tool dispatch   | full content (no truncation), is_error flag                    |
| `session_end`       | once at exit        | reason (`done` / `max_turns`), turn count, final text          |

API keys never reach the log layer (they live inside the OpenAI client),
so log files are safe to share for diagnostics. Workspace source code
*can* end up in logs if you pass it via `--question`; standard
input/output logging hygiene applies.

## Notable design points

- **Provider abstraction is a single seam.** `providers/__init__.py`
  defines the contract (`translate_tools`, `chat`, `append_tool_results`)
  and a tiny `ToolCall` dataclass. Anything provider-specific lives
  inside `providers/<name>_provider.py`. Adding Azure OpenAI later is
  ~30 lines.
- **Tool catalogue comes from the server, not the client.** Whatever
  the bundle advertises via `tools/list` is what the model sees. No
  duplication, no schema drift. This is the whole point of MCP.
- **Tool dispatch is one line.** `await session.call_tool(name, args)`.
  The Python MCP SDK handles framing, request IDs, and response
  parsing. The agent loop only deals with provider-shaped messages.
- **No persistent state, no memory, no streaming.** Each invocation
  is a single question. Adding any of those belongs in the host layer
  (here), not in the MCP server — the separation is the point.

## Notable shortcuts

- The OpenAI Chat Completions API rejects top-level
  `oneOf`/`anyOf`/`allOf`/`enum` on a function's `parameters` schema,
  which two of our MCP tools use to express "exactly one of `symbol`
  or `file`". `translate_tools` strips those constraints; the agent
  tools' runtime checks handle the validation that the schema would
  otherwise enforce.
- Tool dispatch is sequential within a turn. A model that requests
  three tool calls in one response will see them executed in order,
  not in parallel. Adequate for a demo.
- Errors from the MCP server surface as `is_error=true` tool_result
  entries, which the model is expected to read and recover from.
  No retries, no backoff.

## When to retire this host

When the agent loop graduates into a real framework — most likely
Semantic Kernel once the stack pivots to Azure OpenAI + Azure AI
Search. The MCP bundle moves over unchanged; this reference host
can step aside at that point.
