From 65635f791f512bc2fc24520b0442d1685967ef6b Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Mon, 23 Mar 2026 10:38:09 -0500 Subject: [PATCH 1/4] Example deployment descriptors for weather service and tool Signed-off-by: Ed Snible --- a2a/weather_service/.dockerignore | 5 +- a2a/weather_service/deployment/k8s.yaml | 152 ++++++++++++++++++++++++ mcp/weather_tool/.dockerignore | 5 +- mcp/weather_tool/deployment/k8s.yaml | 124 +++++++++++++++++++ 4 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 a2a/weather_service/deployment/k8s.yaml create mode 100644 mcp/weather_tool/deployment/k8s.yaml diff --git a/a2a/weather_service/.dockerignore b/a2a/weather_service/.dockerignore index b694934f..139a824f 100644 --- a/a2a/weather_service/.dockerignore +++ b/a2a/weather_service/.dockerignore @@ -1 +1,4 @@ -.venv \ No newline at end of file +.venv + +deployment + diff --git a/a2a/weather_service/deployment/k8s.yaml b/a2a/weather_service/deployment/k8s.yaml new file mode 100644 index 00000000..09e4ae1c --- /dev/null +++ b/a2a/weather_service/deployment/k8s.yaml @@ -0,0 +1,152 @@ +# Best practice to create a ServiceAccount for each agent to allow for more granular permissions if needed. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: weather-service + namespace: team1 +--- +# Expose the A2A endpoint and Agent Card. +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: weather-service + # kagenti.io/type=agent is required for the Agent to be discovered by Kagenti and show up in the UI + kagenti.io/type: agent + protocol.kagenti.io/a2a: "" + name: weather-service + namespace: team1 +spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: 8000 + selector: + app.kubernetes.io/name: weather-service + kagenti.io/type: agent + type: ClusterIP +--- +# Deploy a test build of the Agent +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: agent + app.kubernetes.io/name: weather-service + kagenti.io/framework: LangGraph + kagenti.io/type: agent + kagenti.io/workload-type: deployment + protocol.kagenti.io/a2a: "" + name: weather-service + namespace: team1 +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/name: weather-service + kagenti.io/type: agent + template: + metadata: + labels: + app.kubernetes.io/name: weather-service + kagenti.io/framework: LangGraph + kagenti.io/type: agent + protocol.kagenti.io/a2a: "" + spec: + containers: + - env: + # Port to bind A2A server + - name: PORT + value: "8000" + # Host to bind A2A server (0.0.0.0 means bind to all interfaces) + - name: HOST + value: 0.0.0.0 + # AGENT_ENDPOINT is the endpoint that appears on the Agent Card + - name: AGENT_ENDPOINT + value: http://weather-service.team1.svc.cluster.local:8080/ + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://otel-collector.kagenti-system.svc.cluster.local:8335 + - name: KEYCLOAK_URL + value: http://keycloak.keycloak.svc.cluster.local:8080 + - name: UV_CACHE_DIR + value: /app/.cache/uv + # Make direct call to tool. + - name: MCP_URL + value: http://weather-tool-mcp.team1.svc.cluster.local:8000/mcp + # Use this value to make call through MCP Gateway + # value: http://mcp-gateway-istio.gateway-system.svc.cluster.local:8080/mcp + # This is the LLM_API_BASE for ollama running on the host machine. In production, this would point to a real LLM API. + - name: LLM_API_BASE + value: http://host.docker.internal:11434/v1 + # This is a dummy key; if using a real LLM API use a real key + - name: LLM_API_KEY + value: dummy + # For production, use a Secret: + # - name: OPENAI_API_KEY + # valueFrom: + # secretKeyRef: + # name: llm-credentials + # key: openai-api-key + # This is a dummy key; if using OpenAI use a real key + - name: OPENAI_API_KEY + value: dummy + # For production, use a Secret: + # - name: OPENAI_API_KEY + # valueFrom: + # secretKeyRef: + # name: llm-credentials + # key: openai-api-key + - name: LLM_MODEL + value: llama3.2:3b-instruct-fp16 + - name: GITHUB_SECRET_NAME + value: github-token-secret + # Pin to a specific version + image: ghcr.io/kagenti/agent-examples/weather_service:v0.1.0-alpha.1 + # Or use the latest version + # image: ghcr.io/kagenti/agent-examples/weather_service:latest + imagePullPolicy: Always + name: agent + ports: + - containerPort: 8000 + name: http + protocol: TCP + readinessProbe: + failureThreshold: 12 + initialDelaySeconds: 5 + periodSeconds: 5 + successThreshold: 1 + tcpSocket: + port: 8000 + timeoutSeconds: 1 + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + # This is for agents that use uv and its dependency cache + - mountPath: /app/.cache + name: cache + # Something like this is needed if the Agent has a Marvin dependency + # - mountPath: /.marvin + # name: marvin + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + serviceAccount: weather-service + serviceAccountName: weather-service + terminationGracePeriodSeconds: 30 + # This is for agents that use uv and its dependency cache + volumes: + - emptyDir: {} + name: cache + # Something like this is needed if the Agent has a Marvin dependency + # - emptyDir: {} + # name: marvin diff --git a/mcp/weather_tool/.dockerignore b/mcp/weather_tool/.dockerignore index b694934f..139a824f 100644 --- a/mcp/weather_tool/.dockerignore +++ b/mcp/weather_tool/.dockerignore @@ -1 +1,4 @@ -.venv \ No newline at end of file +.venv + +deployment + diff --git a/mcp/weather_tool/deployment/k8s.yaml b/mcp/weather_tool/deployment/k8s.yaml new file mode 100644 index 00000000..7570655d --- /dev/null +++ b/mcp/weather_tool/deployment/k8s.yaml @@ -0,0 +1,124 @@ +# Best practice to create a ServiceAccount for each agent to allow for more granular permissions if needed. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: weather-tool + namespace: team1 +--- +# Expose the MCP endpoint +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: weather-tool + # kagenti.io/type=agent is required for the Agent to be discovered by Kagenti and show up in the UI + kagenti.io/type: tool + protocol.kagenti.io/mcp: "" + name: weather-tool-mcp + namespace: team1 +spec: + ports: + - name: http + port: 8000 + protocol: TCP + targetPort: 8000 + selector: + app.kubernetes.io/name: weather-tool + kagenti.io/type: tool + type: ClusterIP +--- +# Deploy a test build of the Agent +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: weather-tool + kagenti.io/framework: Python + # Set inject to true for [AuthBridge](https://github.com/kagenti/kagenti-extensions/tree/main/AuthBridge) + kagenti.io/inject: disabled + kagenti.io/type: tool + kagenti.io/workload-type: deployment + protocol.kagenti.io/mcp: "" + name: weather-tool + namespace: team1 +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/name: weather-tool + kagenti.io/type: tool + template: + metadata: + labels: + app.kubernetes.io/name: weather-tool + kagenti.io/framework: Python + # Set inject to true for [AuthBridge](https://github.com/kagenti/kagenti-extensions/tree/main/AuthBridge) + kagenti.io/inject: disabled + kagenti.io/transport: streamable_http + kagenti.io/type: tool + protocol.kagenti.io/mcp: "" + spec: + containers: + - env: + # Port to bind MCP server + - name: PORT + value: "8000" + # Host to bind MCP server (0.0.0.0 means bind to all interfaces) + - name: HOST + value: 0.0.0.0 + # AGENT_ENDPOINT is the endpoint that appears on the Agent Card + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: http://otel-collector.kagenti-system.svc.cluster.local:8335 + - name: KEYCLOAK_URL + value: http://keycloak.keycloak.svc.cluster.local:8080 + - name: UV_CACHE_DIR + value: /app/.cache/uv + # Pin to a specific version + image: ghcr.io/kagenti/agent-examples/weather_tool:v0.1.0-alpha.1 + # Or use the latest version + # image: ghcr.io/kagenti/agent-examples/weather_tool:latest + imagePullPolicy: Always + name: mcp + ports: + - containerPort: 8000 + name: http + protocol: TCP + resources: + limits: + cpu: 500m + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsUser: 1000 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + # This is for agents that use uv and its dependency cache + - mountPath: /app/.cache + name: cache + - mountPath: /tmp + name: tmp + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + serviceAccount: weather-tool + serviceAccountName: weather-tool + terminationGracePeriodSeconds: 30 + volumes: + # This is for agents that use uv and its dependency cache + - emptyDir: {} + name: cache + - emptyDir: {} + name: tmp From 39faa91a0ac76ad7f9f707362af1984a5efd8438 Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Mon, 23 Mar 2026 10:58:02 -0500 Subject: [PATCH 2/4] Documentation for weather service and weather tool Signed-off-by: Ed Snible --- a2a/weather_service/README.md | 32 ++++++++++++++++++++++++++++++- mcp/weather_tool/README.md | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 mcp/weather_tool/README.md diff --git a/a2a/weather_service/README.md b/a2a/weather_service/README.md index e96b95c4..a8d9350b 100644 --- a/a2a/weather_service/README.md +++ b/a2a/weather_service/README.md @@ -1,3 +1,33 @@ # Agents -Weather Service Agent \ No newline at end of file +The Weather Service Agent is an example of an [A2A](https://a2a-protocol.org/latest/) agent. + +This agent depends on the Kagenti [Weather Tool](https://github.com/kagenti/agent-examples/tree/main/mcp/weather_tool). The weather tool should be running before chatting with the weather service agent. + +## Run the agent on Kubernetes with Kagenti + +You may deploy using Kagenti's UI or through a Kubernetes manifest. + +### Deploy using Kagenti's UI + +Kagenti's UI is aware of this example agent. To deploy through the UI + +- Browse to http://kagenti-ui.localtest.me:8080/agents/ +- Build from source +- Weather service agent +- Expand Environment Variables + - Import from File/URL, URL, https://raw.githubusercontent.com/kagenti/agent-examples/refs/heads/main/a2a/weather_service/.env.ollama + - If using [Ollama](https://ollama.com/), instead of the default use https://raw.githubusercontent.com/kagenti/agent-examples/refs/heads/main/a2a/weather_service/.env.ollama + - Fetch and parse + - Import +- Build and deploy agent +- Chat + - `What is the weather in New York?` + +### Deploy using a Kubernetes deployment manifest + +Deploy the sample manifest: + +```bash +kubectl apply -f deployment/k8s.yaml +``` diff --git a/mcp/weather_tool/README.md b/mcp/weather_tool/README.md new file mode 100644 index 00000000..157b78f0 --- /dev/null +++ b/mcp/weather_tool/README.md @@ -0,0 +1,36 @@ +# MCP Weather tool + +This tool demonstrates a small MCP server. The server implements a `get_weather` tool that returns the current weather for a city using https://open-meteo.com/en/docs/geocoding-api . + +## Test the MCP server locally + +Run locally + +```bash +cd mcp/weather_tool +uv run --no-sync weather_tool.py +``` + +## Deploy the MCP server to Kagenti + +### Deploy using the Kagenti UI + +- Browse to http://kagenti-ui.localtest.me:8080/tools +- Import Tool +- Deploy from Source + - Select weather tool + +### Deploy using a Kubernetes deployment descriptor + +Alternately, you can deploy a pre-built image using Kubernetes + +- `kubectl apply -f mcp/weather_tool/deployment/k8s.yaml` + +## Test the MCP server using Kagenti + +- Visit http://kagenti-ui.localtest.me:8080/tools/team1/weather-tool +- Click "Connect & list tools" +- Expand "get_weather" +- Click "invoke tool" +- Enter the name of a city +- Click "invoke" From 263f158465c1206571ec759cd03b5e1c9dadc92b Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Mon, 23 Mar 2026 11:52:14 -0500 Subject: [PATCH 3/4] Address Kagenti pr-review comments Signed-off-by: Ed Snible --- a2a/weather_service/README.md | 2 +- a2a/weather_service/deployment/k8s.yaml | 11 +++++++++++ mcp/weather_tool/deployment/k8s.yaml | 9 +++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/a2a/weather_service/README.md b/a2a/weather_service/README.md index a8d9350b..35b67090 100644 --- a/a2a/weather_service/README.md +++ b/a2a/weather_service/README.md @@ -16,7 +16,7 @@ Kagenti's UI is aware of this example agent. To deploy through the UI - Build from source - Weather service agent - Expand Environment Variables - - Import from File/URL, URL, https://raw.githubusercontent.com/kagenti/agent-examples/refs/heads/main/a2a/weather_service/.env.ollama + - Import from File/URL, URL, https://raw.githubusercontent.com/kagenti/agent-examples/refs/heads/main/a2a/weather_service/.env.openai - If using [Ollama](https://ollama.com/), instead of the default use https://raw.githubusercontent.com/kagenti/agent-examples/refs/heads/main/a2a/weather_service/.env.ollama - Fetch and parse - Import diff --git a/a2a/weather_service/deployment/k8s.yaml b/a2a/weather_service/deployment/k8s.yaml index 09e4ae1c..4dedcefb 100644 --- a/a2a/weather_service/deployment/k8s.yaml +++ b/a2a/weather_service/deployment/k8s.yaml @@ -128,6 +128,13 @@ spec: requests: cpu: 100m memory: 256Mi + # Note that Kagenti's UI doesn't create security contexts for agents, but it's a good practice to set them for defense in depth. This is a fairly restrictive security context that should work for most agents, but you may need to adjust it based on your Agent's needs. + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + runAsUser: 1000 terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: @@ -140,6 +147,10 @@ spec: dnsPolicy: ClusterFirst restartPolicy: Always schedulerName: default-scheduler + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault serviceAccount: weather-service serviceAccountName: weather-service terminationGracePeriodSeconds: 30 diff --git a/mcp/weather_tool/deployment/k8s.yaml b/mcp/weather_tool/deployment/k8s.yaml index 7570655d..4b0cba57 100644 --- a/mcp/weather_tool/deployment/k8s.yaml +++ b/mcp/weather_tool/deployment/k8s.yaml @@ -11,7 +11,7 @@ kind: Service metadata: labels: app.kubernetes.io/name: weather-tool - # kagenti.io/type=agent is required for the Agent to be discovered by Kagenti and show up in the UI + # kagenti.io/type=tool is required for the MCP Server that provides tool to be discovered by Kagenti and show up in the UI kagenti.io/type: tool protocol.kagenti.io/mcp: "" name: weather-tool-mcp @@ -27,7 +27,7 @@ spec: kagenti.io/type: tool type: ClusterIP --- -# Deploy a test build of the Agent +# Deploy a test build of the Tool apiVersion: apps/v1 kind: Deployment metadata: @@ -68,7 +68,6 @@ spec: # Host to bind MCP server (0.0.0.0 means bind to all interfaces) - name: HOST value: 0.0.0.0 - # AGENT_ENDPOINT is the endpoint that appears on the Agent Card - name: OTEL_EXPORTER_OTLP_ENDPOINT value: http://otel-collector.kagenti-system.svc.cluster.local:8335 - name: KEYCLOAK_URL @@ -85,6 +84,8 @@ spec: - containerPort: 8000 name: http protocol: TCP + # Note that a readinessProbe is recommended. Kagenti doesn't generate one for UI + # deployments, and we follow that pattern in this example, but you may want to add one for your own tools. resources: limits: cpu: 500m @@ -117,7 +118,7 @@ spec: serviceAccountName: weather-tool terminationGracePeriodSeconds: 30 volumes: - # This is for agents that use uv and its dependency cache + # This is for tools that use uv and its dependency cache - emptyDir: {} name: cache - emptyDir: {} From 6a1c3d2f6da1337dfbd2481a4863ac18d303bf88 Mon Sep 17 00:00:00 2001 From: Ed Snible Date: Mon, 23 Mar 2026 12:16:02 -0500 Subject: [PATCH 4/4] Incorporate further code review comments Signed-off-by: Ed Snible --- a2a/a2a_currency_converter/deployment/k8s.yaml | 2 +- a2a/weather_service/deployment/k8s.yaml | 8 ++++---- mcp/weather_tool/deployment/k8s.yaml | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/a2a/a2a_currency_converter/deployment/k8s.yaml b/a2a/a2a_currency_converter/deployment/k8s.yaml index 4eda6c0f..5eb0557b 100644 --- a/a2a/a2a_currency_converter/deployment/k8s.yaml +++ b/a2a/a2a_currency_converter/deployment/k8s.yaml @@ -83,7 +83,7 @@ spec: - name: LLM_API_KEY value: dummy # For production, use a Secret: - # - name: OPENAI_API_KEY + # - name: LLM_API_KEY # valueFrom: # secretKeyRef: # name: llm-credentials diff --git a/a2a/weather_service/deployment/k8s.yaml b/a2a/weather_service/deployment/k8s.yaml index 4dedcefb..89905506 100644 --- a/a2a/weather_service/deployment/k8s.yaml +++ b/a2a/weather_service/deployment/k8s.yaml @@ -85,7 +85,7 @@ spec: - name: LLM_API_KEY value: dummy # For production, use a Secret: - # - name: OPENAI_API_KEY + # - name: LLM_API_KEY # valueFrom: # secretKeyRef: # name: llm-credentials @@ -101,8 +101,9 @@ spec: # key: openai-api-key - name: LLM_MODEL value: llama3.2:3b-instruct-fp16 - - name: GITHUB_SECRET_NAME - value: github-token-secret + # Kagenti will supply a GITHUB_SECRET_NAME, but the weather service does not need it. + # - name: GITHUB_SECRET_NAME + # value: github-token-secret # Pin to a specific version image: ghcr.io/kagenti/agent-examples/weather_service:v0.1.0-alpha.1 # Or use the latest version @@ -151,7 +152,6 @@ spec: runAsNonRoot: true seccompProfile: type: RuntimeDefault - serviceAccount: weather-service serviceAccountName: weather-service terminationGracePeriodSeconds: 30 # This is for agents that use uv and its dependency cache diff --git a/mcp/weather_tool/deployment/k8s.yaml b/mcp/weather_tool/deployment/k8s.yaml index 4b0cba57..82bdfea8 100644 --- a/mcp/weather_tool/deployment/k8s.yaml +++ b/mcp/weather_tool/deployment/k8s.yaml @@ -114,7 +114,6 @@ spec: runAsNonRoot: true seccompProfile: type: RuntimeDefault - serviceAccount: weather-tool serviceAccountName: weather-tool terminationGracePeriodSeconds: 30 volumes: