initial commit

This commit is contained in:
2026-06-11 22:55:35 +01:00
parent eabebcf89b
commit 391828d892
14 changed files with 694 additions and 8 deletions
+137
View File
@@ -0,0 +1,137 @@
# k8s manifests for `captcha-solver-mcp`
These manifests follow the conventions used by the other MCP servers in the
home cluster's `agentgateway-system` namespace (compare
`home-cluster/.../base/mcp-servers/time/` and `.../mcp-servers/web-search/`).
## Resources
| File | Resource |
|------|----------|
| `deployment.yaml` | `Deployment/mcp-captcha-solver` |
| `service.yaml` | `Service/mcp-captcha-solver` (ClusterIP, port 8085) |
| `agentgateway-backend.yaml` | `AgentgatewayBackend/mcp-captcha-solver` (per-server, hand-written) |
| `external-secret.yaml` | `ExternalSecret/1p-mcp-captcha-solver` — **optional**, only if you point at a public LLM endpoint |
## Install
The Deployment talks to the LLM through the in-cluster **agentgateway-proxy**
(ClusterIP `10.43.198.35`, port 80), which fronts the `minimax` AgentgatewayBackend
and holds the upstream `api.minimax.io` auth in the `agentgateway-minimax-auth`
secret. From the pod's perspective the endpoint is **keyless** — no
`CAPTCHA_LLM_API_KEY` is set, and no `ExternalSecret` is needed.
### 1. Add the manifests to `home-cluster`
There are two equally good layouts. Pick **layout B** (chart-driven) for
consistency with the existing per-server MCP backends unless you need a custom
`AgentgatewayPolicy`.
#### Layout A — hand-written, fully self-contained
```sh
mkdir -p ~/Documents/projects/home-cluster/kubernetes/namespaces/agentgateway-system/base/mcp-servers/captcha-solver
cp deploy/k8s/{deployment,service,agentgateway-backend}.yaml \
~/Documents/projects/home-cluster/kubernetes/namespaces/agentgateway-system/base/mcp-servers/captcha-solver/
```
Then edit `home-cluster/.../base/mcp-servers/kustomization.yaml`:
```yaml
- ./captcha-solver/deployment.yaml
- ./captcha-solver/service.yaml
- ./captcha-solver/agentgateway-backend.yaml
```
#### Layout B — chart-driven (recommended, matches existing per-server MCPs)
Copy `deployment.yaml` and `service.yaml` exactly as in Layout A, **but skip
`agentgateway-backend.yaml`** — the `agentgw-cfg` chart will generate it from
`mcpServers` in values.
Edit `home-cluster/.../agentgw-cfg/values.yaml`:
```yaml
mcpServers:
- name: captcha-solver
host: mcp-captcha-solver.agentgateway-system.svc.cluster.local
port: 8085
# path: /mcp (default)
# protocol: StreamableHTTP (default)
```
This produces:
- `AgentgatewayBackend/mcp-captcha-solver` (per-server, StreamableHTTP,
Stateless — matches the other MCP backends)
- `HTTPRoute/mcp-per-server` with a rule on
`/mcp/captcha-solver` → that backend (per-server alias)
The same `values.yaml` entry also adds this server to the umbrella
`mcp-servers` backend that the catch-all `/mcp` HTTPRoute uses.
### 2. (Only if you switch the BASE_URL) Add the ExternalSecret
Skip this section if you keep the default
`http://agentgateway-proxy.agentgateway-system.svc.cluster.local/v1`. The
in-cluster gateway is keyless for cluster callers.
If you change `CAPTCHA_LLM_BASE_URL` to a public endpoint (e.g.
`https://llmgw.h.raczylo.com/v1` from outside the cluster, or
`https://api.openai.com/v1`), append `external-secret.yaml` to
`home-cluster/.../base/mcp-servers/eso.yaml` and re-add the
`valueFrom: secretKeyRef` block in the Deployment for `CAPTCHA_LLM_API_KEY`.
### 3. Build & push the image
The manifests reference `ghcr.io/lukaszraczylo/mcp-captchasolver:latest`. Build
and push from this repo:
```sh
docker buildx build --builder multi-arch \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/lukaszraczylo/mcp-captchasolver:latest \
-t ghcr.io/lukaszraczylo/mcp-captchasolver:$(git rev-parse --short HEAD) \
--push .
```
(The `multi-registry-secret` patch in
`home-cluster/.../base/mcp-servers/kustomization.yaml` already grants the
service account permission to pull `ghcr.io/lukaszraczylo/mcp-*` images.)
### 4. Verify
```sh
kubectl --context admin@home -n agentgateway-system \
rollout status deploy/mcp-captcha-solver
kubectl --context admin@home -n agentgateway-system \
get agentgatewaybackend mcp-captcha-solver
curl -s http://10.0.2.101/mcp/captcha-solver \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
```
The last call should respond with a `serverInfo` block naming
`captcha-solver-mcp` v0.1.0 (or whatever `CAPTCHA_VERSION` was baked in at
build time). From inside a debug pod:
```sh
kubectl --context admin@home -n agentgateway-system exec -it deploy/mcp-captcha-solver -- \
wget -qO- http://agentgateway-proxy.agentgateway-system.svc.cluster.local/v1/models | head
```
should list `minimax/MiniMax-M3` (proving the in-cluster path works).
## Port choice
`8085` is the next free port in the 80xx range used by the other MCP servers
(8081, 8083, 8084, 8087, 8088, 8089, 8092 are taken). Bump the `containerPort`,
`Service.port`, `Deployment` env `CAPTCHA_HTTP_ADDR`, and the `mcpServers`
`port:` if you want a different one.
## Resource sizing
Vision models can be heavier than text-only ones, so the limits (`1 CPU`,
`512Mi`) are a step above `mcp-time` (`200m`, `128Mi`) and on par with
`mcp-web-search`. Tune after observing real load.
+41
View File
@@ -0,0 +1,41 @@
# AgentgatewayBackend for captcha-solver-mcp.
#
# Two options for getting this into the cluster — pick one:
#
# A) Hand-written: drop this file into
# home-cluster/kubernetes/namespaces/agentgateway-system/base/mcp-servers/captcha-solver/agentgateway-backend.yaml
# and add it to the kustomization.yaml alongside deployment.yaml/service.yaml.
#
# B) Generated by the agentgw-cfg chart: add an entry to the `mcpServers` list
# in home-cluster/kubernetes/namespaces/agentgateway-system/agentgw-cfg/values.yaml:
#
# mcpServers:
# - name: captcha-solver
# host: mcp-captcha-solver.agentgateway-system.svc.cluster.local
# port: 8085
# # path: /mcp (default)
# # protocol: StreamableHTTP (default)
#
# This produces BOTH a per-server AgentgatewayBackend (matching the file
# below) AND a per-server HTTPRoute at /mcp/captcha-solver. The chart
# template lives at:
# home-cluster/kubernetes/namespaces/agentgateway-system/agentgw-cfg/templates/mcp.yaml
#
# Option B is preferred (matches the existing per-server backends) unless you
# also need to attach AgentgatewayPolicy/auth that the chart doesn't expose.
---
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayBackend
metadata:
name: mcp-captcha-solver
namespace: agentgateway-system
spec:
mcp:
sessionRouting: Stateless
targets:
- name: captcha-solver
static:
host: mcp-captcha-solver.agentgateway-system.svc.cluster.local
path: /mcp
port: 8085
protocol: StreamableHTTP
+55
View File
@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-captcha-solver
namespace: agentgateway-system
labels:
app.kubernetes.io/name: mcp-captcha-solver
app.kubernetes.io/part-of: mcp-servers
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: mcp-captcha-solver
template:
metadata:
labels:
app.kubernetes.io/name: mcp-captcha-solver
app.kubernetes.io/part-of: mcp-servers
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: mcp
image: ghcr.io/lukaszraczylo/mcp-captchasolver:latest
imagePullPolicy: IfNotPresent
ports:
- { name: http, containerPort: 8085 }
env:
- { name: CAPTCHA_TRANSPORT, value: "http" }
- { name: CAPTCHA_HTTP_ADDR, value: ":8085" }
- { name: CAPTCHA_HTTP_PATH, value: "/mcp" }
- { name: CAPTCHA_LLM_PROVIDER, value: "openai-compatible" }
- { name: CAPTCHA_LLM_BASE_URL, value: "http://agentgateway-proxy.agentgateway-system.svc.cluster.local/v1" }
- { name: CAPTCHA_LLM_MODEL, value: "minimax/MiniMax-M3" }
- { name: CAPTCHA_LLM_TIMEOUT, value: "120s" }
- { name: CAPTCHA_LLM_MAX_TOKENS, value: "2048" }
readinessProbe:
httpGet: { path: /healthz, port: http }
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet: { path: /healthz, port: http }
initialDelaySeconds: 15
periodSeconds: 15
resources:
requests: { cpu: 50m, memory: 64Mi }
limits: { cpu: 1, memory: 512Mi }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities: { drop: [ALL] }
seccompProfile: { type: RuntimeDefault }
+44
View File
@@ -0,0 +1,44 @@
# Optional: ExternalSecret for the captcha-solver API key.
#
# The default Deployment points at the keyless in-cluster LLM gateway
# (agentgateway-proxy → api.minimax.io via the `minimax` AgentgatewayBackend,
# which holds the upstream auth in `agentgateway-minimax-auth`). No client
# API key is needed in that case, and this file is **not** required.
#
# Only apply this if you change `CAPTCHA_LLM_BASE_URL` to a public endpoint
# that needs an API key (e.g. https://llmgw.h.raczylo.com/v1 from outside the
# cluster, or https://api.openai.com/v1). 1Password layout (matches the
# convention in home-cluster/.../base/mcp-servers/eso.yaml):
#
# vault: <whatever stores ns_agentgateway>
# item: ns_agentgateway/mcp_captcha_solver_llm_api_key
# field: key (1Password item key)
#
# Append to home-cluster/kubernetes/namespaces/agentgateway-system/base/mcp-servers/eso.yaml.
# The Secret is consumed by the Deployment as `mcp-captcha-solver` / `CAPTCHA_LLM_API_KEY`,
# and the Deployment's `env:` block must be amended to re-add the
# `valueFrom: secretKeyRef` lookup.
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: 1p-mcp-captcha-solver
namespace: agentgateway-system
spec:
refreshInterval: 24h
secretStoreRef:
name: 1password-homecluster
kind: ClusterSecretStore
target:
name: mcp-captcha-solver
creationPolicy: Owner
deletionPolicy: Retain
template:
engineVersion: v2
type: Opaque
data:
CAPTCHA_LLM_API_KEY: "{{ .key }}"
data:
- secretKey: key
remoteRef:
key: ns_agentgateway/mcp_captcha_solver_llm_api_key
+13
View File
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: mcp-captcha-solver
namespace: agentgateway-system
labels:
app.kubernetes.io/name: mcp-captcha-solver
app.kubernetes.io/part-of: mcp-servers
spec:
selector:
app.kubernetes.io/name: mcp-captcha-solver
ports:
- { name: http, port: 8085, targetPort: http }