> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Audit Tracker Update Code Review

> Explains code changes that enable a FinOps agent to evaluate deployment labels, enforce tagging rules, restrict tools, create one tracker ticket per non compliant resource, and list issues.

Welcome back. This lesson walks through the small but important code changes that enable our FinOps agent to interact with the tracker MCP so it can create and list issues. The update enforces a structured prompt, restricts the agent tool surface, and wires in a tracker MCP endpoint so the agent produces one ticket per non-compliant resource and then lists issues.

Start by opening the repository at the `08` folder, then the `agents` folder and inspect `tracker_auditor.py`:

![A screenshot of a GitHub repository page showing the "sections" folder and a list of subfolders (00-course-introduction, 01-cluster-foundation, 02-airline-app-deployment, etc.) with last commit messages and dates. The browser window and repository file tree are visible on the left.](https://mintcdn.com/kodekloud-c4ac6d9a/rjGeTDj8cr6D5YbW/images/Building-Kubernetes-FinOps-AI-Agent/From-Findings-to-Actionable-Tickets/Demo-Audit-Tracker-Update-Code-Review/github-repo-sections-file-tree.jpg?fit=max\&auto=format\&n=rjGeTDj8cr6D5YbW\&q=85\&s=3b56bc3ba5cf3291e5c3eac7999e4743)

Open `tracker_auditor.py`. This file contains the agent prompt, the allowed tools set, the tagging rules loader, and a small async entrypoint that calls `run_agent` from `mcp_client.py`. Below is a cleaned, complete, and corrected excerpt of `tracker_auditor.py` showing the enforced prompt structure and how tagging rules are loaded and forwarded:

```python theme={null}
# tracker_auditor.py
"""Unified FinOps agent: audit cluster labels via K8s MCP and post tickets via tracker MCP."""
import asyncio
from pathlib import Path

from mcp_client import run_agent

# Keep the tool surface small so the agent spends steps on create_issue, not discovery.
ALLOWED_TOOLS = {"kubectl_get", "create_issue", "list_issues"}

PROMPT = """
You are a FinOps agent with Kubernetes kubectl_get and issue tracker tools (create_issue, list_issues).

Execute this exact plan — do not invent extra discovery steps:

Step 1 — Collect (1 kubectl_get call only):
 - kubectl_get resourceType=deployments namespace=payment output=json

Step 2 — Evaluate labels on each Deployment against the tagging rules.
A required tag is missing when NONE of its label_mappings keys appear on the resource.

Step 3 — For EVERY non-compliant Deployment, call create_issue (one call per resource).
Required fields:
 - title: "[FinOps] {namespace}/{resource_name} - {CATEGORY}"
 - summary, body, namespace, resource_name, resource_kind
 - category: missing-owner | unallocated | orphaned | missing-labels
 - priority: critical | high | medium | low
 - suggested_owner, suggested_cost_center, reasoning
 - source: "mcp-llm-agent"

Step 4 — Call list_issues once and stop.

Hard rules:
 - Follow Step 1's scope exactly — do not invent extra kubectl_get calls.
 - Do not fetch configmaps, PVCs, or individual resources by name.
 - Do not write a plain-English audit report instead of creating tickets.
 - Your run is a failure if you end without at least one create_issue call when
   non-compliant Deployments exist in the scoped result set.
"""

TAGGING_RULES_PATH = (
    Path(__file__).parents[2] / "07-llm-structured-agent" / "config" / "tagging-rules.yaml"
)

def load_tagging_rules() -> str:
    if TAGGING_RULES_PATH.exists():
        return TAGGING_RULES_PATH.read_text()
    return ""

async def main() -> None:
    result = await run_agent(
        PROMPT,
        tagging_rules=load_tagging_rules(),
        max_tokens=4096,
        allowed_tools=ALLOWED_TOOLS,
    )

    print(result)
    print("\n--- Done. Verify tickets at http://localhost:8085 ---")

if __name__ == "__main__":
    asyncio.run(main())
```

Key behaviors implemented in this file

* ALLOWED\_TOOLS intentionally limits the agent to `kubectl_get`, `create_issue`, and `list_issues`. This focuses agent steps on creating tickets rather than exploring other resources.
* The PROMPT is prescriptive and scoped to exactly one `kubectl_get` call: `deployments` in the `payment` namespace. The prompt enforces the tagging evaluation logic and requires one `create_issue` call per non-compliant Deployment.
* Tagging rules are read from `tagging-rules.yaml` and passed into the agent as a system-level policy message (see later), ensuring consistent evaluation.

Next, the tracker MCP wiring is implemented in `mcp_client.py`. The snippet below shows environment loading, default MCP URLs, and the `MCP_SERVERS` mapping that now includes a `tracker` entry using SSE transport:

```python theme={null}
# mcp_client.py (excerpt)
from dotenv import load_dotenv
import os
from pathlib import Path

load_dotenv(Path(__file__).parents[3] / ".env")

K8S_MCP_URL = os.getenv("K8S_MCP_URL", "http://localhost:8000/mcp")
TRACKER_MCP_URL = os.getenv("TRACKER_MCP_URL", "http://localhost:8086/sse")

MCP_SERVERS = {
    "k8s": {
        "url": K8S_MCP_URL,
        "transport": "streamable_http",
    },
    "tracker": {
        "url": TRACKER_MCP_URL,
        "transport": "sse",
    },
}
```

Why this matters

* Adding the tracker entry enables the client to route create/list issue operations to the tracker MCP at `TRACKER_MCP_URL`. The SSE transport supports streaming updates from the tracker if needed.
* Environment variables allow overriding defaults for local and CI environments (`K8S_MCP_URL`, `TRACKER_MCP_URL`).

One important implementation detail: the tagging rules YAML is delivered to the LLM as a SystemMessage so the model treats it like policy. The code below (excerpted and corrected) shows how tools are filtered by `allowed_tools`, how tagging rules are injected as system-level content, and how the agent is invoked:

```python theme={null}
# mcp_client.py (continued, excerpt)
import os
from typing import Optional
from langchain.chat_models import ChatOpenAI
from langchain.schema import SystemMessage, HumanMessage

# Helper to construct the Chat LLM client
def build_llm(max_tokens: Optional[int] = None) -> ChatOpenAI:
    return ChatOpenAI(
        openai_api_key=os.environ["OPENAI_API_KEY"],
        temperature=float(os.getenv("OPENAI_TEMPERATURE", "0.2")),
        max_tokens=max_tokens or int(os.getenv("OPENAI_MAX_TOKENS", "512")),
    )

async def run_agent(
    prompt: str,
    max_tokens: Optional[int] = None,
    tagging_rules: Optional[str] = None,
    allowed_tools: Optional[set[str]] = None,
) -> str:
    tools, cleanup = await get_mcp_tools()
    try:
        if allowed_tools is not None:
            tools = [tool for tool in tools if tool.name in allowed_tools]
            missing = allowed_tools - {tool.name for tool in tools}
            if missing:
                raise RuntimeError(f"Required MCP tools not found: {sorted(missing)}")

        agent = create_agent(build_llm(max_tokens=max_tokens), tools)
        messages = []

        if tagging_rules:
            # Tagging rules are passed as a system message, so the agent treats them as
            # authoritative policy when evaluating resources.
            messages.append(
                SystemMessage(
                    content=(
                        "Apply the tagging rules below when evaluating cluster resources. "
                        "Treat label_mappings as the only accepted keys for each required tag. "
                        "Do not invent extra policy, exclusions, or judgments beyond this YAML.\n\n"
                        f"Tagging rules (YAML):\n{tagging_rules}"
                    )
                )
            )

        messages.append(HumanMessage(content=prompt))

        # Audit + many create_issue calls can require a higher recursion/step budget.
        result = await agent.ainvoke(
            {"messages": messages},
            config={"recursion_limit": 100},
        )

        # Return the last assistant message content
        return result["messages"][-1].content
    finally:
        await cleanup()
```

The tagging rules YAML is embedded as a system message so the LLM evaluates labels strictly according to that YAML. This avoids the model inventing extra policy or exclusions.

Quick reference — files and responsibilities

| File | Responsibility | Example / Note |
| - | - | - |
| `agents/tracker_auditor.py` | Defines the structured prompt, allowed tools, loads tagging rules, and starts the run. | Run locally: `python tracker_auditor.py` |
| `mcp_client.py` | Loads environment, configures MCP servers (k8s + tracker), constructs LLM client, filters tools and forwards tagging rules as a system message. | Ensure `OPENAI_API_KEY` is set in environment |
| `07-llm-structured-agent/config/tagging-rules.yaml` | The YAML policy used by the agent to evaluate labels. | Loaded and passed as a system message |

Run and verify

1. Ensure your environment variables are set (for example in `.env`): `OPENAI_API_KEY`, optionally `K8S_MCP_URL`, `TRACKER_MCP_URL`.
2. Start your MCP services (k8s MCP and tracker MCP).
3. Run the auditor: `python tracker_auditor.py`.
4. The script prints a verification hint: "Done. Verify tickets at [http://localhost:8085](http://localhost:8085)". Confirm the tracker UI shows created tickets.
5. The agent must issue one `create_issue` call per non-compliant Deployment in the result set and call `list_issues` exactly once before stopping.

Important: The agent is intentionally restricted. If MCP tools required by `ALLOWED_TOOLS` are not available, `run_agent` will raise an error. Make sure the tracker MCP service is reachable at `TRACKER_MCP_URL`.

Why these changes improve the system

* Structured prompt: Forces a predictable plan (collect → evaluate → create tickets → list issues), which reduces hallucination and ensures consistent ticket creation.
* Small tool surface: By restricting tools, the agent focuses on remediation (creating issues) rather than additional discovery.
* System-level tagging rules: Passing policy as a SystemMessage ensures the LLM evaluates resources according to authoritative YAML, preventing ad-hoc judgments.
* Tracker MCP integration: Enables end-to-end ticket creation and verification against a dedicated tracker service.

References

* LangChain Chat Models: [https://python.langchain.com/en/latest/modules/agents/how\_to/custom\_agent.html](https://python.langchain.com/en/latest/modules/agents/how_to/custom_agent.html)
* dotenv Python package: [https://pypi.org/project/python-dotenv/](https://pypi.org/project/python-dotenv/)
* Kubernetes concepts: [https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/](https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/)

That covers the code changes and how they work together to produce actionable tickets from non-compliant Deployments. See you in the next lesson.

> **[Watch Video](https://learn.kodekloud.com/user/courses/building-kubernetes-finops-ai-agent/module/bbcc27a3-b731-4d76-9ba0-b3df1a8b3afb/lesson/4a100dfa-745f-46f8-af14-4840b0142a2a)**


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.