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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,19 @@ The MCP server provides the following tools for interacting with the OpenShift A
* **Create a cluster**: "Create a new cluster named 'my-cluster' with OpenShift 4.14 and base domain 'example.com'"
* **Check cluster events**: "What events happened on cluster abc123?"
* **Install a cluster**: "Start the installation for cluster abc123"

## Prometheus Metrics

The MCP server exposes Prometheus metrics to monitor tool usage and performance. The metrics are available at `http://localhost:8000/metrics` when the server is running.

### Available Metrics

* **assisted_service_mcp_tool_request_count** - Number of tool requests.
* **assisted_service_mcp_tool_request_duration_sum** - Total time to run the tool, in seconds.
* **assisted_service_mcp_tool_request_duration_count** - Total number of tool requests measured.
* **assisted_service_mcp_tool_request_duration_bucket** - Number of tool requests organized in buckets.

### Metric Labels

All metrics include the following label:
* **tool** - The name of the tool, for example `cluster_info`, `list_clusters`, etc.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies = [
"requests>=2.32.3",
"retry>=0.9.2",
"types-requests>=2.32.4.20250611",
"prometheus_client>=0.22.1",
]

[dependency-groups]
Expand Down
25 changes: 22 additions & 3 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@
import json
import os


import requests
from mcp.server.fastmcp import FastMCP
import uvicorn
from assisted_service_client import models
from mcp.server.fastmcp import FastMCP

from service_client import InventoryClient

from service_client import InventoryClient, metrics, track_tool_usage
from service_client.logger import log


mcp = FastMCP("AssistedService", host="0.0.0.0")


Expand Down Expand Up @@ -115,6 +119,7 @@ def get_access_token() -> str:


@mcp.tool()
@track_tool_usage()
async def cluster_info(cluster_id: str) -> str:
"""
Get comprehensive information about a specific assisted installer cluster.
Expand All @@ -141,6 +146,7 @@ async def cluster_info(cluster_id: str) -> str:


@mcp.tool()
@track_tool_usage()
async def list_clusters() -> str:
"""
List all assisted installer clusters for the current user.
Expand Down Expand Up @@ -174,6 +180,7 @@ async def list_clusters() -> str:


@mcp.tool()
@track_tool_usage()
async def cluster_events(cluster_id: str) -> str:
"""
Get the events related to a cluster with the given cluster id.
Expand All @@ -197,6 +204,7 @@ async def cluster_events(cluster_id: str) -> str:


@mcp.tool()
@track_tool_usage()
async def host_events(cluster_id: str, host_id: str) -> str:
"""
Get events specific to a particular host within a cluster.
Expand All @@ -222,6 +230,7 @@ async def host_events(cluster_id: str, host_id: str) -> str:


@mcp.tool()
@track_tool_usage()
async def cluster_iso_download_url(cluster_id: str) -> str:
"""
Get ISO download URL(s) for a cluster.
Expand Down Expand Up @@ -278,6 +287,7 @@ async def cluster_iso_download_url(cluster_id: str) -> str:


@mcp.tool()
@track_tool_usage()
async def create_cluster(
name: str, version: str, base_domain: str, single_node: bool
) -> str:
Expand Down Expand Up @@ -328,6 +338,7 @@ async def create_cluster(


@mcp.tool()
@track_tool_usage()
async def set_cluster_vips(cluster_id: str, api_vip: str, ingress_vip: str) -> str:
"""
Configure the virtual IP addresses (VIPs) for cluster API and ingress traffic.
Expand Down Expand Up @@ -362,6 +373,7 @@ async def set_cluster_vips(cluster_id: str, api_vip: str, ingress_vip: str) -> s


@mcp.tool()
@track_tool_usage()
async def install_cluster(cluster_id: str) -> str:
"""
Trigger the installation process for a prepared cluster.
Expand Down Expand Up @@ -391,6 +403,7 @@ async def install_cluster(cluster_id: str) -> str:


@mcp.tool()
@track_tool_usage()
async def list_versions() -> str:
"""
List all available OpenShift versions for installation.
Expand All @@ -411,6 +424,7 @@ async def list_versions() -> str:


@mcp.tool()
@track_tool_usage()
async def list_operator_bundles() -> str:
"""
List available operator bundles for cluster installation.
Expand All @@ -431,6 +445,7 @@ async def list_operator_bundles() -> str:


@mcp.tool()
@track_tool_usage()
async def add_operator_bundle_to_cluster(cluster_id: str, bundle_name: str) -> str:
"""
Add an operator bundle to be installed with the cluster.
Expand Down Expand Up @@ -458,6 +473,7 @@ async def add_operator_bundle_to_cluster(cluster_id: str, bundle_name: str) -> s


@mcp.tool()
@track_tool_usage()
async def cluster_credentials_download_url(cluster_id: str, file_name: str) -> str:
"""
Get presigned download URL for cluster credential files.
Expand Down Expand Up @@ -501,6 +517,7 @@ async def cluster_credentials_download_url(cluster_id: str, file_name: str) -> s


@mcp.tool()
@track_tool_usage()
async def set_host_role(host_id: str, infraenv_id: str, role: str) -> str:
"""
Assign a specific role to a discovered host in the cluster.
Expand Down Expand Up @@ -528,4 +545,6 @@ async def set_host_role(host_id: str, infraenv_id: str, role: str) -> str:


if __name__ == "__main__":
mcp.run(transport="sse")
app = mcp.sse_app()
app.add_route("/metrics", metrics)
uvicorn.run(app, host="0.0.0.0")
3 changes: 2 additions & 1 deletion service_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@

from .assisted_service_api import InventoryClient
from .logger import log
from .metrics import metrics, track_tool_usage

__all__ = ["InventoryClient", "log"]
__all__ = ["InventoryClient", "log", "metrics", "track_tool_usage"]
56 changes: 56 additions & 0 deletions service_client/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
Metrics for the MCP server.

This module provides metrics for the MCP server.
"""

from typing import Callable, Any
from functools import wraps

from prometheus_client import (
CONTENT_TYPE_LATEST,
Counter,
Histogram,
generate_latest,
)
from starlette.requests import Request
from starlette.responses import PlainTextResponse


# Define counter for request count
REQUEST_COUNT = Counter(
"assisted_service_mcp_tool_request_count",
"Request count",
["tool"],
)

# Define histogram for request latency
REQUEST_LATENCY = Histogram(
"assisted_service_mcp_tool_request_duration",
"Request latency",
["tool"],
buckets=(0.1, 1.0, 10.0, 30.0, float("inf")),
)


def track_tool_usage() -> Callable:
"""Decorate MCP tools with this decorator to track tool usage metrics."""

def decorator(func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
tool_name = func.__name__
REQUEST_COUNT.labels(tool=tool_name).inc()
Comment thread
maorfr marked this conversation as resolved.
with REQUEST_LATENCY.labels(tool=tool_name).time():
response = await func(*args, **kwargs)
return response

return wrapper

return decorator


# Metrics route
async def metrics(_request: Request) -> PlainTextResponse:
"""Metrics endpoint."""
return PlainTextResponse(generate_latest(), media_type=CONTENT_TYPE_LATEST)
Loading