Mistral Example
examples/mistral_analysis.py is a self-contained script that uses function-calling (tool use) to drive the full client-led analysis workflow against the REST endpoints — the LLM itself decides which tool to call next (ingest → get_next_resource → submit_analysis → report), rather than the server orchestrating it.
No MCP client is needed — the LLM calls the server tools directly. This is one worked example of the client-led pattern; it happens to use Mistral because that’s what’s bundled, but nothing about the pattern is Mistral-specific — see Adapting for other models below.
How it works
Section titled “How it works”sequenceDiagram
participant Script as User Script
participant Mistral as Mistral API
participant API as REST API
Script->>Mistral: system prompt + plan text
Mistral->>API: GET /terraform-plan/rules?type=aws
API-->>Mistral: ruleset (Markdown)
Mistral->>API: POST /terraform-plan/{ns}/ingest
API-->>Mistral: resource units[]
loop until HTTP 208
Mistral->>API: GET /{ns}/resource/next
API-->>Mistral: resource unit
Note over Mistral: analyse body against rules internally
Mistral->>API: PUT /resource/{hash}/analysis
end
Mistral->>API: GET /{ns}/report
API-->>Mistral: Markdown report
Mistral-->>Script: final report text
Note over Script: print report
Mistral receives the plan text in the first message and autonomously decides which tools to call and in what order — the script only executes the HTTP calls and feeds results back as tool responses.
Prerequisites
Section titled “Prerequisites”pip install mistralai httpxThe server must be running:
python src/server.py# or (first time: builds the image)docker compose up --buildexport MISTRAL_API_KEY=your-key-here
# Analyse a plan (defaults: mistral-large-latest, http://localhost:8000)python examples/mistral_analysis.py path/to/plan.txtEnvironment variables
Section titled “Environment variables”| Variable | Default | Description |
|---|---|---|
MISTRAL_API_KEY | — | Mistral API key (required unless CODESTRAL_API_KEY is set) |
CODESTRAL_API_KEY | — | Codestral API key — takes priority over MISTRAL_API_KEY |
MISTRAL_SERVER_URL | — | Custom API endpoint, e.g. https://codestral.mistral.ai/v1 |
MISTRAL_MODEL | mistral-large-latest | Any Mistral model with tool use |
MCP_BASE_URL | http://localhost:8000 | Server base URL |
MCP_API_KEY | (empty) | Server API key if auth.api_key is set |
Example — standard Mistral API
Section titled “Example — standard Mistral API”export MISTRAL_API_KEY=sk-...export MISTRAL_MODEL=mistral-large-latestexport MCP_BASE_URL=https://terraform-analyzer.example.comexport MCP_API_KEY=my-server-key
python examples/mistral_analysis.py infra/prod.tfplan.txtExample — Codestral endpoint
Section titled “Example — Codestral endpoint”export CODESTRAL_API_KEY=sk-...export MISTRAL_SERVER_URL=https://codestral.mistral.ai/v1export MISTRAL_MODEL=codestral-latest
python examples/mistral_analysis.py infra/prod.tfplan.txtExample output
Section titled “Example output”Namespace : a3f1c2d4e5b6c7d8e9f0...Plan file : infra/prod.tfplan.txtModel : mistral-large-latestAPI base : http://localhost:8000
→ get_rules(analyzer_name='terraform-plan', rule_type='aws') → ingest(analyzer_name='terraform-plan', namespace='a3f1c2...') → get_next_resource(namespace='a3f1c2...') → submit_analysis(hash='d4e5f6...', summary='S3 bucket exposes public read...') → get_next_resource(namespace='a3f1c2...') → submit_analysis(hash='b7c8d9...', summary='Security group allows unrestricted...') → get_next_resource(namespace='a3f1c2...') # HTTP 208 → loop ends → get_report(namespace='a3f1c2...')
# Terraform Security Report...Script structure
Section titled “Script structure”Tool handlers (TOOL_HANDLERS) — thin httpx wrappers that map tool names to REST calls. Each returns the HTTP status code plus the response body so Mistral knows whether a call succeeded or failed.
Tool schemas (TOOLS) — JSON Schema definitions passed to mistral.chat.complete(). The schemas mirror the REST API’s request/response contract so Mistral can construct valid payloads without guidance.
System prompt (SYSTEM_PROMPT) — instructs Mistral on the exact workflow order, loop termination condition (status=208), and finding format rules.
Agent loop — calls mistral.chat.complete() in a loop, executes any tool calls that come back, appends the results, and repeats until Mistral returns a plain text message (the report).
Adapting for other models
Section titled “Adapting for other models”Any model that supports function / tool calling works. Replace the SDK and tool call handling:
Mistral (default)
Section titled “Mistral (default)”from mistralai import Mistralclient = Mistral(api_key=os.environ["MISTRAL_API_KEY"])response = client.chat.complete(model="mistral-large-latest", messages=messages, tools=TOOLS)OpenAI-compatible (Ollama, vLLM, …)
Section titled “OpenAI-compatible (Ollama, vLLM, …)”from openai import OpenAIclient = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")response = client.chat.completions.create(model="llama3", messages=messages, tools=TOOLS)Tool call extraction is identical — both SDKs expose response.choices[0].message.tool_calls.
The TOOLS list and TOOL_HANDLERS dict require no changes.