From 11384528ec5f5ef5934f31e8fb46e231f4b9bf5b Mon Sep 17 00:00:00 2001 From: calmini Date: Fri, 25 Apr 2025 13:21:52 +0800 Subject: [PATCH] chore: add docker icon --- pages/assets/images/docker-mark-blue.svg | 12 ++++ pages/registry/index.html | 77 +++++++++++++++++------- scripts/get_manifest.py | 10 ++- scripts/utils.py | 33 +++++++++- 4 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 pages/assets/images/docker-mark-blue.svg diff --git a/pages/assets/images/docker-mark-blue.svg b/pages/assets/images/docker-mark-blue.svg new file mode 100644 index 00000000..eba6cc41 --- /dev/null +++ b/pages/assets/images/docker-mark-blue.svg @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/pages/registry/index.html b/pages/registry/index.html index 66a66295..04ef7e09 100644 --- a/pages/registry/index.html +++ b/pages/registry/index.html @@ -8,26 +8,26 @@ mcpm.sh - Server Registry {% include favicon.html %} - + - + - + - + @@ -41,7 +41,7 @@ @@ -1635,7 +1651,7 @@

Raw Manifest:

const summary = document.createElement('summary'); summary.textContent = key; details.appendChild(summary); - + const subUl = document.createElement('ul'); Object.entries(value).forEach(([subKey, subValue]) => { const subLi = document.createElement('li'); @@ -1660,7 +1676,7 @@

Raw Manifest:

const summary = document.createElement('summary'); summary.textContent = tool.name || `Tool ${index + 1}`; details.appendChild(summary); - + const subUl = document.createElement('ul'); Object.entries(tool).forEach(([key, value]) => { const subLi = document.createElement('li'); @@ -1685,7 +1701,7 @@

Raw Manifest:

const summary = document.createElement('summary'); summary.textContent = resource.name || `Resource ${index + 1}`; details.appendChild(summary); - + const subUl = document.createElement('ul'); Object.entries(resource).forEach(([key, value]) => { const subLi = document.createElement('li'); @@ -1710,7 +1726,7 @@

Raw Manifest:

const summary = document.createElement('summary'); summary.textContent = prompt.name || `Prompt ${index + 1}`; details.appendChild(summary); - + const subUl = document.createElement('ul'); Object.entries(prompt).forEach(([key, value]) => { const subLi = document.createElement('li'); @@ -1863,7 +1879,24 @@

Raw Manifest:

const heading = document.createElement('h3'); heading.textContent = server.display_name || server.name; - + + if (server.docker_url) { + const dockerIcon = document.createElement('span'); + dockerIcon.className = 'docker-icon'; + dockerIcon.innerHTML = 'Docker'; + dockerIcon.style.cursor = 'pointer'; + dockerIcon.title = 'Docker'; + + // Open Docker Hub link in new tab + dockerIcon.addEventListener('click', (event) => { + event.stopPropagation(); + window.open(server.docker_url, '_blank'); + }); + + // Add Docker icon to heading + heading.appendChild(dockerIcon); + } + // Add official badge if the server is marked as official if (server.is_official) { const officialBadge = document.createElement('span'); @@ -1871,7 +1904,7 @@

Raw Manifest:

officialBadge.textContent = 'Official'; heading.appendChild(officialBadge); } - + serverHeader.appendChild(heading); // Add placeholder for GitHub stars in the header @@ -1887,11 +1920,11 @@

Raw Manifest:

description.className = 'description'; description.textContent = server.description; serverContent.appendChild(description); - + // Create tags container const tagsContainer = document.createElement('div'); tagsContainer.className = 'tags'; - + // Add regular tags (if any) if (server.tags && server.tags.length > 0) { server.tags.slice(0, 4).forEach(tag => { @@ -1901,20 +1934,20 @@

Raw Manifest:

tagsContainer.appendChild(tagSpan); }); } - + // Append tags container to server content serverContent.appendChild(tagsContainer); - + const meta = document.createElement('div'); meta.className = 'meta'; - + // Add placeholder for author that will be populated later const authorMeta = document.createElement('span'); authorMeta.className = 'author author-placeholder'; authorMeta.textContent = 'Loading author...'; authorMeta.style.opacity = '0'; meta.appendChild(authorMeta); - + // Add categories if they exist if (server.categories && server.categories.length > 0) { const categorySpan = document.createElement('span'); @@ -1922,7 +1955,7 @@

Raw Manifest:

categorySpan.textContent = server.categories[0]; // Display first category meta.appendChild(categorySpan); } - + // Update author information directly from server data (no need to fetch) if (server.author && server.author.name) { // Add user icon before author name @@ -2188,4 +2221,4 @@

Raw Manifest:

- \ No newline at end of file + diff --git a/scripts/get_manifest.py b/scripts/get_manifest.py index 3447686a..7e845b10 100644 --- a/scripts/get_manifest.py +++ b/scripts/get_manifest.py @@ -11,7 +11,7 @@ import requests from categorization import CategorizationAgent from openai import OpenAI -from utils import McpClient, validate_arguments_in_installation +from utils import McpClient, inspect_docker_repo, validate_arguments_in_installation dotenv.load_dotenv() logging.basicConfig(level=logging.INFO, @@ -19,6 +19,7 @@ logger = logging.getLogger(__name__) _OUTPUT_DIR = "mcp-registry/servers/" +DOCKER_MCP_REPO_URL = "https://hub.docker.com/r" class ManifestGenerator: @@ -781,6 +782,13 @@ def generate_manifest(self, repo_url: str, server_name: Optional[str] = None) -> except Exception as e: logger.error(f"Failed to extract capabilities: {e}") + # docker url if docker command in installation + installations = manifest.get("installations", {}) + if "docker" in installations: + docker_repo_name = inspect_docker_repo(installations["docker"]) + if docker_repo_name: + manifest["docker_url"] = f"{DOCKER_MCP_REPO_URL}/{docker_repo_name}" + return manifest except Exception as e: diff --git a/scripts/utils.py b/scripts/utils.py index 3d60d843..630a8bdc 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -1,13 +1,15 @@ import re from contextlib import AsyncExitStack from datetime import timedelta -from typing import Any +from typing import Any, Optional +import requests from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from mcp.shared.exceptions import McpError from mcp.types import ListPromptsResult, ListResourcesResult, ListToolsResult +DOCKER_HUB_REPO_URL = "https://hub.docker.com/v2/repositories/" class McpClient: session: ClientSession @@ -148,3 +150,32 @@ def validate_arguments_in_installation( installation["env"] = env return installation, replacement + + +def validate_docker_url(docker_url: str) -> bool: + try: + response = requests.get(docker_url) + # if success with status code 200, the repo should be a valid and registered one + return response.status_code == 200 + except Exception: + return False + + +def inspect_docker_repo(installation: dict[str, Any]) -> Optional[str]: + """ inspect the docker url from docker installation args, the args should pattern as {namespace}/{repo_name} where namespace=mcp + Example + if args = ["run", "-i", "--rm", "-e", "PERPLEXITY_API_KEY", "mcp/perplexity-ask"] + return "mcp/perplexity-ask" + """ + repo_name = None + if "args" in installation and installation["args"]: + args = installation["args"] + for arg in args: + # namespace/repo(:tag) + repo_match = re.match(r"^(mcp/[\w-]+)(?::[\w.\-]+)?$", arg) + if repo_match: + repo_name = repo_match.group(1) + if validate_docker_url(DOCKER_HUB_REPO_URL + repo_name): + return repo_name # namespace/repo without tag + + return None