Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions a2a/iag-mcp-demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ Contains some configuration yaml files for the open-source AgentGateway tool. Se

A simple Web GUI app and A2A client that enables Human-user prompting and interactions. The `chatbot` communicates directly with the `orchestrator_agent` (see below).

The console header's **Architecture** link opens `/static/architecture.html`: a
self-contained diagram of every container (console, gateways, agents, MCP
servers, database), the IndyKite platform services, the IdP with its OAuth2
token-exchange service, the external parties (Salesforce, Google Drive,
Open-Meteo, the LLM) and the numbered request flow between them. It reads
`/api/config` to badge the active usecase and grey out the `drive`/`crm`/`erp`
groups when their compose profile is off.

## `orchestrator_agent`

An A2A agent that manages the interactions with the end-users, and delegates the action to further agents down the workflow chain: `query_retriever` (data/IKG), `query_weather` (weather), and `query_drive` (Google Drive, via the `analyst_agent` - enabled when `ANALYST_HOST` is set).
Expand Down
23 changes: 21 additions & 2 deletions a2a/iag-mcp-demo/chatbot/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
APP_AGENT_CREDENTIALS_TOKEN = (os.getenv("APP_AGENT_CREDENTIALS_TOKEN") or "").strip()
EXPLAIN_STAFF_QUERY_ID = (os.getenv("EXPLAIN_STAFF_QUERY_ID") or "").strip()
EXPLAIN_DIRECT_QUERY_ID = (os.getenv("EXPLAIN_DIRECT_QUERY_ID") or "").strip()
# Architecture page: active usecase + compose profiles, used only to badge the
# diagram and dim the optional (profile-gated) service groups.
USECASE = (os.getenv("USECASE") or "").strip()
COMPOSE_PROFILES = [p.strip() for p in (os.getenv("COMPOSE_PROFILES") or "").split(",") if p.strip()]
EXPLAIN_ENABLED = all(
(INDYKITE_BASE_URL, APP_AGENT_CREDENTIALS_TOKEN, EXPLAIN_STAFF_QUERY_ID, EXPLAIN_DIRECT_QUERY_ID),
)
Expand Down Expand Up @@ -169,10 +173,22 @@ def health():
)


def _id_token_display_name() -> str:
"""Best-effort display name from the session's id_token (unverified decode, cosmetic only)."""
token = session.get("id_token") or ""
try:
payload = token.split(".")[1]
claims = json.loads(base64.urlsafe_b64decode(payload + "=" * (-len(payload) % 4)))
except Exception: # opaque/absent token: no name to show
return ""
return str(claims.get("preferred_username") or claims.get("name") or claims.get("email") or claims.get("sub") or "")


@app.route("/api/auth/status", methods=["GET"])
def auth_status():
"""Return whether the user is authenticated."""
return jsonify({"logged_in": bool(session.get("access_token"))})
"""Return whether the user is authenticated (and who, for the header chip)."""
logged_in = bool(session.get("access_token"))
return jsonify({"logged_in": logged_in, "username": _id_token_display_name() if logged_in else ""})


@app.route("/api/auth/login", methods=["GET"])
Expand Down Expand Up @@ -376,6 +392,9 @@ def get_config():
# the map derives a DENY card's workflow id from its gateway name.
"explain_enabled": EXPLAIN_ENABLED,
"explain_workflow_map": EXPLAIN_WORKFLOW_MAP,
# architecture page: badge + dimming of profile-gated groups
"usecase": USECASE,
"profiles": COMPOSE_PROFILES,
},
)

Expand Down
Loading