From 041310ad90e004232a1c6fc5984fde9ce9b255c3 Mon Sep 17 00:00:00 2001 From: hsteude Date: Tue, 7 Jul 2026 23:56:31 +0200 Subject: [PATCH 1/4] Add MCP ToolHive examples --- README.md | 2 + agentops/mcp-toolhive/README.md | 76 +++++++++++++++++ .../mcp-toolhive/custom-fastmcp-server.yaml | 45 ++++++++++ .../mcp-toolhive/runbook-server/Dockerfile | 17 ++++ .../runbook-server/requirements.txt | 1 + .../mcp-toolhive/runbook-server/server.py | 84 +++++++++++++++++++ agentops/mcp-toolhive/time-server.yaml | 16 ++++ 7 files changed, 241 insertions(+) create mode 100644 agentops/mcp-toolhive/README.md create mode 100644 agentops/mcp-toolhive/custom-fastmcp-server.yaml create mode 100644 agentops/mcp-toolhive/runbook-server/Dockerfile create mode 100644 agentops/mcp-toolhive/runbook-server/requirements.txt create mode 100644 agentops/mcp-toolhive/runbook-server/server.py create mode 100644 agentops/mcp-toolhive/time-server.yaml diff --git a/README.md b/README.md index 05c3307..9d9352a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ For full platform documentation, see [docs.prokube.ai](https://docs.prokube.ai/) ```py . ├── .github # workflows to build images +├── agentops # AgentOps examples (MCP servers, ToolHive) ├── hparam-tuning # hyperparameter tuning examples (Katib) ├── images # custom container images used by examples ├── mlflow # MLflow experiment tracking examples @@ -41,6 +42,7 @@ Some examples require a minimum prokube platform version. If an example is not l | Example | Min platform version | Notes | |---|---|---| +| `agentops/mcp-toolhive` | TBD | Requires MCP servers / ToolHive support | | `serving/minimal-s3-model` | v1.7.0 | Requires s3creds secret with KServe support | ## Contributing diff --git a/agentops/mcp-toolhive/README.md b/agentops/mcp-toolhive/README.md new file mode 100644 index 0000000..8b8d227 --- /dev/null +++ b/agentops/mcp-toolhive/README.md @@ -0,0 +1,76 @@ +# MCP servers with ToolHive + +This example shows two ways to run MCP servers on prokube with ToolHive: + +- deploy an existing upstream MCP server image with a ToolHive `MCPServer` resource; +- build and deploy a small custom MCP server image for workspace runbooks. + +Use the prokube **MCP** UI for the normal workflow. These manifests are useful when you want to understand the generated Kubernetes resources, test GitOps-style deployment, or build your own MCP server image. + +## Prerequisites + +- Access to a prokube workspace with MCP servers enabled. +- `kubectl` configured for the target workspace. +- Permission to create ToolHive `MCPServer` resources in the workspace namespace. +- A container registry for the custom image example. + +Set your workspace namespace: + +```bash +export NAMESPACE= +``` + +## Deploy the upstream time server + +The `time-server.yaml` manifest runs the upstream Time MCP server image from the MCP registry. + +```bash +kubectl apply -n "$NAMESPACE" -f time-server.yaml +kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" +``` + +The server exposes the `get_current_time` and `convert_time` tools. + +Note: the current upstream image requires root because of its image layout. Use this as a minimal ToolHive example, not as a production baseline. + +## Build the custom runbook server + +The `runbook-server/` directory contains a small FastMCP server for workspace-specific operational runbooks. It stores Markdown files in `/data/runbooks`, which is backed by a PVC in the example manifest. + +It exposes: + +- `list_runbooks` +- `get_runbook` +- `save_runbook` +- `search_runbooks` +- `delete_runbook` + +Build and push the image: + +```bash +export IMAGE=//workspace-runbooks-mcp:0.1.0 +docker build -t "$IMAGE" runbook-server +docker push "$IMAGE" +``` + +Update `custom-fastmcp-server.yaml` and replace `IMAGE_PLACEHOLDER` with the pushed image: + +```bash +sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" custom-fastmcp-server.yaml | kubectl apply -n "$NAMESPACE" -f - +kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" +``` + +The custom image runs as a non-root user and does not require a writable root filesystem. Runbook data is written to the mounted `/data` volume. + +## Connect a client + +After the server is running, open **MCP** in the prokube UI and copy the server URL from the deployed servers table or details page. + +For external clients, create an API key scoped to the MCP server and use the authentication format expected by the client. Existing MCP examples commonly use the `x-api-key` header. + +## Clean up + +```bash +kubectl delete -n "$NAMESPACE" -f time-server.yaml +sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" custom-fastmcp-server.yaml | kubectl delete -n "$NAMESPACE" -f - +``` diff --git a/agentops/mcp-toolhive/custom-fastmcp-server.yaml b/agentops/mcp-toolhive/custom-fastmcp-server.yaml new file mode 100644 index 0000000..07c3d94 --- /dev/null +++ b/agentops/mcp-toolhive/custom-fastmcp-server.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: workspace-runbooks-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: toolhive.stacklok.dev/v1beta1 +kind: MCPServer +metadata: + name: workspace-runbooks +spec: + image: IMAGE_PLACEHOLDER + transport: stdio + proxyPort: 8080 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + podTemplateSpec: + spec: + volumes: + - name: runbooks + persistentVolumeClaim: + claimName: workspace-runbooks-data + containers: + - name: mcp + volumeMounts: + - name: runbooks + mountPath: /data + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 10001 + capabilities: + drop: + - ALL diff --git a/agentops/mcp-toolhive/runbook-server/Dockerfile b/agentops/mcp-toolhive/runbook-server/Dockerfile new file mode 100644 index 0000000..ebbc011 --- /dev/null +++ b/agentops/mcp-toolhive/runbook-server/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + RUNBOOK_DIR=/data/runbooks + +WORKDIR /app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY server.py ./server.py + +RUN useradd --create-home --uid 10001 appuser +USER 10001 + +ENTRYPOINT ["python", "server.py"] diff --git a/agentops/mcp-toolhive/runbook-server/requirements.txt b/agentops/mcp-toolhive/runbook-server/requirements.txt new file mode 100644 index 0000000..8c471c7 --- /dev/null +++ b/agentops/mcp-toolhive/runbook-server/requirements.txt @@ -0,0 +1 @@ +fastmcp>=3.4,<4 diff --git a/agentops/mcp-toolhive/runbook-server/server.py b/agentops/mcp-toolhive/runbook-server/server.py new file mode 100644 index 0000000..f570518 --- /dev/null +++ b/agentops/mcp-toolhive/runbook-server/server.py @@ -0,0 +1,84 @@ +import os +import re +from pathlib import Path + +from fastmcp import FastMCP + + +RUNBOOK_DIR = Path(os.environ.get("RUNBOOK_DIR", "/data/runbooks")) +RUNBOOK_DIR.mkdir(parents=True, exist_ok=True) + +mcp = FastMCP("workspace-runbooks") + + +def _safe_name(name: str) -> str: + slug = name.removesuffix(".md").strip().lower() + slug = re.sub(r"[^a-z0-9._-]+", "-", slug).strip("-._") + if not slug: + raise ValueError("Runbook name must contain at least one letter or number") + return f"{slug}.md" + + +def _runbook_path(name: str) -> Path: + path = (RUNBOOK_DIR / _safe_name(name)).resolve() + if RUNBOOK_DIR.resolve() not in path.parents: + raise ValueError("Invalid runbook path") + return path + + +@mcp.tool +def list_runbooks() -> list[str]: + """List available runbook names.""" + return sorted(path.stem for path in RUNBOOK_DIR.glob("*.md")) + + +@mcp.tool +def get_runbook(name: str) -> str: + """Return the Markdown content of a runbook.""" + path = _runbook_path(name) + if not path.exists(): + raise ValueError(f"Runbook not found: {name}") + return path.read_text(encoding="utf-8") + + +@mcp.tool +def save_runbook(name: str, content: str) -> str: + """Create or replace a runbook with Markdown content.""" + path = _runbook_path(name) + path.write_text(content.rstrip() + "\n", encoding="utf-8") + return path.stem + + +@mcp.tool +def search_runbooks(query: str) -> list[dict[str, str]]: + """Search runbook names and content with simple case-insensitive text matching.""" + needle = query.strip().lower() + if not needle: + raise ValueError("Query must not be empty") + + matches: list[dict[str, str]] = [] + for path in sorted(RUNBOOK_DIR.glob("*.md")): + content = path.read_text(encoding="utf-8") + haystack = f"{path.stem}\n{content}".lower() + if needle not in haystack: + continue + lines = [line.strip() for line in content.splitlines() if needle in line.lower()] + matches.append({ + "name": path.stem, + "snippet": lines[0] if lines else content[:160], + }) + return matches + + +@mcp.tool +def delete_runbook(name: str) -> bool: + """Delete a runbook. Returns true when a file was deleted.""" + path = _runbook_path(name) + if not path.exists(): + return False + path.unlink() + return True + + +if __name__ == "__main__": + mcp.run() diff --git a/agentops/mcp-toolhive/time-server.yaml b/agentops/mcp-toolhive/time-server.yaml new file mode 100644 index 0000000..dc8592e --- /dev/null +++ b/agentops/mcp-toolhive/time-server.yaml @@ -0,0 +1,16 @@ +apiVersion: toolhive.stacklok.dev/v1beta1 +kind: MCPServer +metadata: + name: time +spec: + image: docker.io/mcp/time:latest@sha256:9c46a918633fb474bf8035e3ee90ebac6bcf2b18ccb00679ac4c179cba0ebfcf + transport: stdio + proxyPort: 8080 + podTemplateSpec: + spec: + containers: + - name: mcp + securityContext: + runAsUser: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false From af6f44928ae159693562ac676fc64e79d95fbea3 Mon Sep 17 00:00:00 2001 From: hsteude Date: Wed, 8 Jul 2026 00:06:51 +0200 Subject: [PATCH 2/4] Restructure MCP server examples --- README.md | 4 +- agentops/mcp-toolhive/README.md | 76 ------------------- mcp-servers/README.md | 22 ++++++ mcp-servers/build-custom-mcp-server/README.md | 57 ++++++++++++++ .../runbook-server/Dockerfile | 4 +- .../runbook-server/requirements.txt | 0 .../seed-runbooks/model-endpoint-503.md | 17 +++++ .../seed-runbooks/pipeline-quota.md | 17 +++++ .../runbook-server/server.py | 11 +++ .../workspace-runbooks.yaml | 0 .../deploy-upstream-mcp-server/README.md | 18 +++++ .../time-server.yaml | 0 12 files changed, 147 insertions(+), 79 deletions(-) delete mode 100644 agentops/mcp-toolhive/README.md create mode 100644 mcp-servers/README.md create mode 100644 mcp-servers/build-custom-mcp-server/README.md rename {agentops/mcp-toolhive => mcp-servers/build-custom-mcp-server}/runbook-server/Dockerfile (73%) rename {agentops/mcp-toolhive => mcp-servers/build-custom-mcp-server}/runbook-server/requirements.txt (100%) create mode 100644 mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/model-endpoint-503.md create mode 100644 mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/pipeline-quota.md rename {agentops/mcp-toolhive => mcp-servers/build-custom-mcp-server}/runbook-server/server.py (87%) rename agentops/mcp-toolhive/custom-fastmcp-server.yaml => mcp-servers/build-custom-mcp-server/workspace-runbooks.yaml (100%) create mode 100644 mcp-servers/deploy-upstream-mcp-server/README.md rename {agentops/mcp-toolhive => mcp-servers/deploy-upstream-mcp-server}/time-server.yaml (100%) diff --git a/README.md b/README.md index 9d9352a..f672530 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ For full platform documentation, see [docs.prokube.ai](https://docs.prokube.ai/) ```py . ├── .github # workflows to build images -├── agentops # AgentOps examples (MCP servers, ToolHive) ├── hparam-tuning # hyperparameter tuning examples (Katib) ├── images # custom container images used by examples ├── mlflow # MLflow experiment tracking examples +├── mcp-servers # MCP server examples with ToolHive ├── notebooks # Jupyter notebook examples (Dask, MNIST VAE, etc.) ├── pipelines # Kubeflow Pipelines examples ├── rstudio # RStudio examples @@ -42,7 +42,7 @@ Some examples require a minimum prokube platform version. If an example is not l | Example | Min platform version | Notes | |---|---|---| -| `agentops/mcp-toolhive` | TBD | Requires MCP servers / ToolHive support | +| `mcp-servers` | TBD | Requires MCP servers / ToolHive support | | `serving/minimal-s3-model` | v1.7.0 | Requires s3creds secret with KServe support | ## Contributing diff --git a/agentops/mcp-toolhive/README.md b/agentops/mcp-toolhive/README.md deleted file mode 100644 index 8b8d227..0000000 --- a/agentops/mcp-toolhive/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# MCP servers with ToolHive - -This example shows two ways to run MCP servers on prokube with ToolHive: - -- deploy an existing upstream MCP server image with a ToolHive `MCPServer` resource; -- build and deploy a small custom MCP server image for workspace runbooks. - -Use the prokube **MCP** UI for the normal workflow. These manifests are useful when you want to understand the generated Kubernetes resources, test GitOps-style deployment, or build your own MCP server image. - -## Prerequisites - -- Access to a prokube workspace with MCP servers enabled. -- `kubectl` configured for the target workspace. -- Permission to create ToolHive `MCPServer` resources in the workspace namespace. -- A container registry for the custom image example. - -Set your workspace namespace: - -```bash -export NAMESPACE= -``` - -## Deploy the upstream time server - -The `time-server.yaml` manifest runs the upstream Time MCP server image from the MCP registry. - -```bash -kubectl apply -n "$NAMESPACE" -f time-server.yaml -kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" -``` - -The server exposes the `get_current_time` and `convert_time` tools. - -Note: the current upstream image requires root because of its image layout. Use this as a minimal ToolHive example, not as a production baseline. - -## Build the custom runbook server - -The `runbook-server/` directory contains a small FastMCP server for workspace-specific operational runbooks. It stores Markdown files in `/data/runbooks`, which is backed by a PVC in the example manifest. - -It exposes: - -- `list_runbooks` -- `get_runbook` -- `save_runbook` -- `search_runbooks` -- `delete_runbook` - -Build and push the image: - -```bash -export IMAGE=//workspace-runbooks-mcp:0.1.0 -docker build -t "$IMAGE" runbook-server -docker push "$IMAGE" -``` - -Update `custom-fastmcp-server.yaml` and replace `IMAGE_PLACEHOLDER` with the pushed image: - -```bash -sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" custom-fastmcp-server.yaml | kubectl apply -n "$NAMESPACE" -f - -kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" -``` - -The custom image runs as a non-root user and does not require a writable root filesystem. Runbook data is written to the mounted `/data` volume. - -## Connect a client - -After the server is running, open **MCP** in the prokube UI and copy the server URL from the deployed servers table or details page. - -For external clients, create an API key scoped to the MCP server and use the authentication format expected by the client. Existing MCP examples commonly use the `x-api-key` header. - -## Clean up - -```bash -kubectl delete -n "$NAMESPACE" -f time-server.yaml -sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" custom-fastmcp-server.yaml | kubectl delete -n "$NAMESPACE" -f - -``` diff --git a/mcp-servers/README.md b/mcp-servers/README.md new file mode 100644 index 0000000..bec434f --- /dev/null +++ b/mcp-servers/README.md @@ -0,0 +1,22 @@ +# MCP server examples + +These examples show how to run MCP servers on prokube with ToolHive. + +- [`deploy-upstream-mcp-server`](./deploy-upstream-mcp-server/): deploy an existing upstream MCP server image with a ToolHive `MCPServer` resource. +- [`build-custom-mcp-server`](./build-custom-mcp-server/): build and deploy a small custom FastMCP server image. + +Use the prokube **MCP** UI for the normal workflow. These examples are useful when you want to understand the underlying Kubernetes resources, test GitOps-style deployment, or build your own MCP server image. + +## Prerequisites + +- Access to a prokube workspace with MCP servers enabled. +- `kubectl` configured for the target workspace. +- Permission to create ToolHive `MCPServer` resources in the workspace namespace. + +Set your workspace namespace: + +```bash +export NAMESPACE= +``` + +For external MCP clients, create an API key scoped to the deployed MCP server and use the authentication format expected by the client. Existing MCP examples commonly use the `x-api-key` header. diff --git a/mcp-servers/build-custom-mcp-server/README.md b/mcp-servers/build-custom-mcp-server/README.md new file mode 100644 index 0000000..7849cd2 --- /dev/null +++ b/mcp-servers/build-custom-mcp-server/README.md @@ -0,0 +1,57 @@ +# Build and deploy a custom MCP server + +This example builds a small FastMCP server for workspace-specific operational runbooks. + +The server stores Markdown files in `/data/runbooks`, which is backed by a PVC in `workspace-runbooks.yaml`. It does not use RAG, embeddings, or a vector database. Search is simple case-insensitive text matching over Markdown files. + +The example demonstrates: + +- a readable custom MCP server implementation; +- a non-root container image; +- read-only root filesystem compatibility; +- persistent workspace data; +- seeding initial Markdown files into the PVC on first start. + +## Tools + +The server exposes: + +- `list_runbooks` +- `get_runbook` +- `save_runbook` +- `search_runbooks` +- `delete_runbook` + +## How runbooks get into the server + +There are two paths: + +- Seed files in `runbook-server/seed-runbooks/` are copied into `/data/runbooks` when the server starts and the PVC is empty. +- MCP clients can create or update runbooks later with the `save_runbook` tool. + +Because `/data` is backed by a PVC, runbooks created through MCP tools survive pod restarts. + +## Build and push the image + +```bash +export IMAGE=//workspace-runbooks-mcp:0.1.0 +docker build -t "$IMAGE" runbook-server +docker push "$IMAGE" +``` + +## Deploy + +Replace `IMAGE_PLACEHOLDER` with the pushed image and apply the manifest: + +```bash +sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" workspace-runbooks.yaml | kubectl apply -n "$NAMESPACE" -f - +kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" +``` + +After the server is running, open **MCP** in the prokube UI and copy the server URL from the deployed servers table or details page. + +## Clean up + +```bash +sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" workspace-runbooks.yaml | kubectl delete -n "$NAMESPACE" -f - +``` diff --git a/agentops/mcp-toolhive/runbook-server/Dockerfile b/mcp-servers/build-custom-mcp-server/runbook-server/Dockerfile similarity index 73% rename from agentops/mcp-toolhive/runbook-server/Dockerfile rename to mcp-servers/build-custom-mcp-server/runbook-server/Dockerfile index ebbc011..d7e1655 100644 --- a/agentops/mcp-toolhive/runbook-server/Dockerfile +++ b/mcp-servers/build-custom-mcp-server/runbook-server/Dockerfile @@ -2,7 +2,8 @@ FROM python:3.12-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ - RUNBOOK_DIR=/data/runbooks + RUNBOOK_DIR=/data/runbooks \ + SEED_RUNBOOK_DIR=/app/seed-runbooks WORKDIR /app @@ -10,6 +11,7 @@ COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY server.py ./server.py +COPY seed-runbooks ./seed-runbooks RUN useradd --create-home --uid 10001 appuser USER 10001 diff --git a/agentops/mcp-toolhive/runbook-server/requirements.txt b/mcp-servers/build-custom-mcp-server/runbook-server/requirements.txt similarity index 100% rename from agentops/mcp-toolhive/runbook-server/requirements.txt rename to mcp-servers/build-custom-mcp-server/runbook-server/requirements.txt diff --git a/mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/model-endpoint-503.md b/mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/model-endpoint-503.md new file mode 100644 index 0000000..e9e512e --- /dev/null +++ b/mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/model-endpoint-503.md @@ -0,0 +1,17 @@ +# Model Endpoint 503 + +## Symptoms + +- Client requests to a model endpoint return `503`. +- The endpoint is listed in the UI, but predictions fail. + +## Checks + +1. Check whether the backing pod is running. +2. Check recent pod events for image pull, quota, or readiness errors. +3. Check the model server logs. +4. Confirm that the endpoint is not scaling from zero and still warming up. + +## Escalate + +Escalate to the platform owner if pods cannot be scheduled because of quota or node capacity. diff --git a/mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/pipeline-quota.md b/mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/pipeline-quota.md new file mode 100644 index 0000000..fe0db42 --- /dev/null +++ b/mcp-servers/build-custom-mcp-server/runbook-server/seed-runbooks/pipeline-quota.md @@ -0,0 +1,17 @@ +# Pipeline Quota + +## Symptoms + +- Pipeline steps stay pending. +- New pods are not created in the workspace namespace. + +## Checks + +1. Check workspace pod quota in the prokube UI. +2. Delete completed pods if the workspace is at its pod limit. +3. Check whether other workloads are consuming CPU or memory requests. +4. Retry the run after capacity is available. + +## Escalate + +Escalate if active workloads are expected and the workspace needs a quota increase. diff --git a/agentops/mcp-toolhive/runbook-server/server.py b/mcp-servers/build-custom-mcp-server/runbook-server/server.py similarity index 87% rename from agentops/mcp-toolhive/runbook-server/server.py rename to mcp-servers/build-custom-mcp-server/runbook-server/server.py index f570518..b2004c6 100644 --- a/agentops/mcp-toolhive/runbook-server/server.py +++ b/mcp-servers/build-custom-mcp-server/runbook-server/server.py @@ -1,16 +1,26 @@ import os import re +import shutil from pathlib import Path from fastmcp import FastMCP RUNBOOK_DIR = Path(os.environ.get("RUNBOOK_DIR", "/data/runbooks")) +SEED_RUNBOOK_DIR = Path(os.environ.get("SEED_RUNBOOK_DIR", "/app/seed-runbooks")) + RUNBOOK_DIR.mkdir(parents=True, exist_ok=True) mcp = FastMCP("workspace-runbooks") +def _seed_runbooks() -> None: + if any(RUNBOOK_DIR.glob("*.md")) or not SEED_RUNBOOK_DIR.exists(): + return + for source in SEED_RUNBOOK_DIR.glob("*.md"): + shutil.copyfile(source, RUNBOOK_DIR / source.name) + + def _safe_name(name: str) -> str: slug = name.removesuffix(".md").strip().lower() slug = re.sub(r"[^a-z0-9._-]+", "-", slug).strip("-._") @@ -81,4 +91,5 @@ def delete_runbook(name: str) -> bool: if __name__ == "__main__": + _seed_runbooks() mcp.run() diff --git a/agentops/mcp-toolhive/custom-fastmcp-server.yaml b/mcp-servers/build-custom-mcp-server/workspace-runbooks.yaml similarity index 100% rename from agentops/mcp-toolhive/custom-fastmcp-server.yaml rename to mcp-servers/build-custom-mcp-server/workspace-runbooks.yaml diff --git a/mcp-servers/deploy-upstream-mcp-server/README.md b/mcp-servers/deploy-upstream-mcp-server/README.md new file mode 100644 index 0000000..b7188f7 --- /dev/null +++ b/mcp-servers/deploy-upstream-mcp-server/README.md @@ -0,0 +1,18 @@ +# Deploy an upstream MCP server + +This example deploys the upstream Time MCP server image with a ToolHive `MCPServer` resource. + +```bash +kubectl apply -n "$NAMESPACE" -f time-server.yaml +kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" +``` + +The server exposes the `get_current_time` and `convert_time` tools. + +The current upstream image requires root because of its image layout. Use this as a minimal ToolHive smoke test, not as a production baseline. + +## Clean up + +```bash +kubectl delete -n "$NAMESPACE" -f time-server.yaml +``` diff --git a/agentops/mcp-toolhive/time-server.yaml b/mcp-servers/deploy-upstream-mcp-server/time-server.yaml similarity index 100% rename from agentops/mcp-toolhive/time-server.yaml rename to mcp-servers/deploy-upstream-mcp-server/time-server.yaml From d785b332810e379a9cc9415f6a1654dfe225f7f1 Mon Sep 17 00:00:00 2001 From: hsteude Date: Wed, 8 Jul 2026 22:03:09 +0200 Subject: [PATCH 3/4] Simplify MCP example namespace usage --- mcp-servers/README.md | 11 ++--------- mcp-servers/build-custom-mcp-server/README.md | 6 +++--- mcp-servers/deploy-upstream-mcp-server/README.md | 6 +++--- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/mcp-servers/README.md b/mcp-servers/README.md index bec434f..62b4fc8 100644 --- a/mcp-servers/README.md +++ b/mcp-servers/README.md @@ -10,13 +10,6 @@ Use the prokube **MCP** UI for the normal workflow. These examples are useful wh ## Prerequisites - Access to a prokube workspace with MCP servers enabled. -- `kubectl` configured for the target workspace. +- A prokube Lab or notebook running in the target workspace. +- `kubectl` configured for the current workspace namespace. - Permission to create ToolHive `MCPServer` resources in the workspace namespace. - -Set your workspace namespace: - -```bash -export NAMESPACE= -``` - -For external MCP clients, create an API key scoped to the deployed MCP server and use the authentication format expected by the client. Existing MCP examples commonly use the `x-api-key` header. diff --git a/mcp-servers/build-custom-mcp-server/README.md b/mcp-servers/build-custom-mcp-server/README.md index 7849cd2..232a616 100644 --- a/mcp-servers/build-custom-mcp-server/README.md +++ b/mcp-servers/build-custom-mcp-server/README.md @@ -44,8 +44,8 @@ docker push "$IMAGE" Replace `IMAGE_PLACEHOLDER` with the pushed image and apply the manifest: ```bash -sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" workspace-runbooks.yaml | kubectl apply -n "$NAMESPACE" -f - -kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" +sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" workspace-runbooks.yaml | kubectl apply -f - +kubectl get mcpservers.toolhive.stacklok.dev ``` After the server is running, open **MCP** in the prokube UI and copy the server URL from the deployed servers table or details page. @@ -53,5 +53,5 @@ After the server is running, open **MCP** in the prokube UI and copy the server ## Clean up ```bash -sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" workspace-runbooks.yaml | kubectl delete -n "$NAMESPACE" -f - +sed "s|IMAGE_PLACEHOLDER|$IMAGE|g" workspace-runbooks.yaml | kubectl delete -f - ``` diff --git a/mcp-servers/deploy-upstream-mcp-server/README.md b/mcp-servers/deploy-upstream-mcp-server/README.md index b7188f7..3ab4133 100644 --- a/mcp-servers/deploy-upstream-mcp-server/README.md +++ b/mcp-servers/deploy-upstream-mcp-server/README.md @@ -3,8 +3,8 @@ This example deploys the upstream Time MCP server image with a ToolHive `MCPServer` resource. ```bash -kubectl apply -n "$NAMESPACE" -f time-server.yaml -kubectl get mcpservers.toolhive.stacklok.dev -n "$NAMESPACE" +kubectl apply -f time-server.yaml +kubectl get mcpservers.toolhive.stacklok.dev ``` The server exposes the `get_current_time` and `convert_time` tools. @@ -14,5 +14,5 @@ The current upstream image requires root because of its image layout. Use this a ## Clean up ```bash -kubectl delete -n "$NAMESPACE" -f time-server.yaml +kubectl delete -f time-server.yaml ``` From d3e76f8bf2a4e8e2a18cf96ece24949aeae5d650 Mon Sep 17 00:00:00 2001 From: hsteude Date: Wed, 8 Jul 2026 22:04:45 +0200 Subject: [PATCH 4/4] Clarify upstream MCP namespace usage --- mcp-servers/deploy-upstream-mcp-server/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mcp-servers/deploy-upstream-mcp-server/README.md b/mcp-servers/deploy-upstream-mcp-server/README.md index 3ab4133..3be0d89 100644 --- a/mcp-servers/deploy-upstream-mcp-server/README.md +++ b/mcp-servers/deploy-upstream-mcp-server/README.md @@ -7,6 +7,8 @@ kubectl apply -f time-server.yaml kubectl get mcpservers.toolhive.stacklok.dev ``` +If your kubeconfig can access multiple namespaces, set the target workspace namespace explicitly, for example with `kubectl --namespace apply -f time-server.yaml`. + The server exposes the `get_current_time` and `convert_time` tools. The current upstream image requires root because of its image layout. Use this as a minimal ToolHive smoke test, not as a production baseline.