Skip to content

Commit 1138452

Browse files
committed
chore: add docker icon
1 parent f9316b0 commit 1138452

File tree

4 files changed

+108
-24
lines changed

4 files changed

+108
-24
lines changed
Lines changed: 12 additions & 0 deletions
Loading

pages/registry/index.html

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,26 @@
88
<meta name="viewport" content="width=device-width, initial-scale=1.0">
99
<title>mcpm.sh - Server Registry</title>
1010
{% include favicon.html %}
11-
11+
1212
<!-- Primary Meta Tags -->
1313
<meta name="title" content="mcpm.sh - Server Registry">
1414
<meta name="description" content="The single open source MCP registry we all need. Discover and explore MCP servers for your projects.">
1515
<meta name="keywords" content="MCP, server registry, AI, machine learning, open source">
16-
16+
1717
<!-- Open Graph / Facebook -->
1818
<meta property="og:type" content="website">
1919
<meta property="og:url" content="https://mcpm.sh/registry/">
2020
<meta property="og:title" content="mcpm.sh - Server Registry">
2121
<meta property="og:description" content="The single open source MCP registry we all need. Discover and explore MCP servers for your projects.">
2222
<meta property="og:image" content="https://mcpm.sh/assets/images/mcpm-social.png">
23-
23+
2424
<!-- Twitter -->
2525
<meta property="twitter:card" content="summary_large_image">
2626
<meta property="twitter:url" content="https://mcpm.sh/registry/">
2727
<meta property="twitter:title" content="mcpm.sh - Server Registry">
2828
<meta property="twitter:description" content="The single open source MCP registry we all need. Discover and explore MCP servers for your projects.">
2929
<meta property="twitter:image" content="https://mcpm.sh/assets/images/mcpm-social.png">
30-
30+
3131
<link rel="preconnect" href="https://fonts.googleapis.com">
3232
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
3333
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
@@ -41,7 +41,7 @@
4141
<style>
4242
{% include colors.css %}
4343
{% include common.css %}
44-
44+
4545
/* Override colors with blue and pink theme */
4646
:root {
4747
--accent-color: #3b82f6;
@@ -372,7 +372,7 @@
372372
align-items: center;
373373
margin: 0;
374374
}
375-
375+
376376
.server-card .category:hover {
377377
background-color: var(--accent-color);
378378
color: white;
@@ -1216,12 +1216,13 @@
12161216
}
12171217

12181218
.server-card .meta .category {
1219-
background-color: var(--accent-light);
1220-
color: var(--accent-color);
1219+
background-color: var(--accent-color);
1220+
color: white;
12211221
padding: 0.1rem 0.5rem;
12221222
border-radius: 3px;
12231223
font-size: 0.7rem;
12241224
margin-left: 0.5rem;
1225+
font-weight: 600;
12251226
}
12261227

12271228
.official-badge {
@@ -1237,6 +1238,21 @@
12371238
text-transform: uppercase;
12381239
letter-spacing: 0.5px;
12391240
}
1241+
1242+
.docker-icon {
1243+
display: inline-block;
1244+
margin-left: 8px;
1245+
vertical-align: middle;
1246+
color: #2496ed;
1247+
}
1248+
1249+
.docker-icon svg {
1250+
vertical-align: middle;
1251+
}
1252+
1253+
.docker-icon:hover {
1254+
opacity: 0.8;
1255+
}
12401256
</style>
12411257
</head>
12421258

@@ -1635,7 +1651,7 @@ <h3>Raw Manifest:</h3>
16351651
const summary = document.createElement('summary');
16361652
summary.textContent = key;
16371653
details.appendChild(summary);
1638-
1654+
16391655
const subUl = document.createElement('ul');
16401656
Object.entries(value).forEach(([subKey, subValue]) => {
16411657
const subLi = document.createElement('li');
@@ -1660,7 +1676,7 @@ <h3>Raw Manifest:</h3>
16601676
const summary = document.createElement('summary');
16611677
summary.textContent = tool.name || `Tool ${index + 1}`;
16621678
details.appendChild(summary);
1663-
1679+
16641680
const subUl = document.createElement('ul');
16651681
Object.entries(tool).forEach(([key, value]) => {
16661682
const subLi = document.createElement('li');
@@ -1685,7 +1701,7 @@ <h3>Raw Manifest:</h3>
16851701
const summary = document.createElement('summary');
16861702
summary.textContent = resource.name || `Resource ${index + 1}`;
16871703
details.appendChild(summary);
1688-
1704+
16891705
const subUl = document.createElement('ul');
16901706
Object.entries(resource).forEach(([key, value]) => {
16911707
const subLi = document.createElement('li');
@@ -1710,7 +1726,7 @@ <h3>Raw Manifest:</h3>
17101726
const summary = document.createElement('summary');
17111727
summary.textContent = prompt.name || `Prompt ${index + 1}`;
17121728
details.appendChild(summary);
1713-
1729+
17141730
const subUl = document.createElement('ul');
17151731
Object.entries(prompt).forEach(([key, value]) => {
17161732
const subLi = document.createElement('li');
@@ -1863,15 +1879,32 @@ <h3>Raw Manifest:</h3>
18631879

18641880
const heading = document.createElement('h3');
18651881
heading.textContent = server.display_name || server.name;
1866-
1882+
1883+
if (server.docker_url) {
1884+
const dockerIcon = document.createElement('span');
1885+
dockerIcon.className = 'docker-icon';
1886+
dockerIcon.innerHTML = '<img src="/assets/images/docker-mark-blue.svg" alt="Docker" width="16" height="16">';
1887+
dockerIcon.style.cursor = 'pointer';
1888+
dockerIcon.title = 'Docker';
1889+
1890+
// Open Docker Hub link in new tab
1891+
dockerIcon.addEventListener('click', (event) => {
1892+
event.stopPropagation();
1893+
window.open(server.docker_url, '_blank');
1894+
});
1895+
1896+
// Add Docker icon to heading
1897+
heading.appendChild(dockerIcon);
1898+
}
1899+
18671900
// Add official badge if the server is marked as official
18681901
if (server.is_official) {
18691902
const officialBadge = document.createElement('span');
18701903
officialBadge.className = 'official-badge';
18711904
officialBadge.textContent = 'Official';
18721905
heading.appendChild(officialBadge);
18731906
}
1874-
1907+
18751908
serverHeader.appendChild(heading);
18761909

18771910
// Add placeholder for GitHub stars in the header
@@ -1887,11 +1920,11 @@ <h3>Raw Manifest:</h3>
18871920
description.className = 'description';
18881921
description.textContent = server.description;
18891922
serverContent.appendChild(description);
1890-
1923+
18911924
// Create tags container
18921925
const tagsContainer = document.createElement('div');
18931926
tagsContainer.className = 'tags';
1894-
1927+
18951928
// Add regular tags (if any)
18961929
if (server.tags && server.tags.length > 0) {
18971930
server.tags.slice(0, 4).forEach(tag => {
@@ -1901,28 +1934,28 @@ <h3>Raw Manifest:</h3>
19011934
tagsContainer.appendChild(tagSpan);
19021935
});
19031936
}
1904-
1937+
19051938
// Append tags container to server content
19061939
serverContent.appendChild(tagsContainer);
1907-
1940+
19081941
const meta = document.createElement('div');
19091942
meta.className = 'meta';
1910-
1943+
19111944
// Add placeholder for author that will be populated later
19121945
const authorMeta = document.createElement('span');
19131946
authorMeta.className = 'author author-placeholder';
19141947
authorMeta.textContent = 'Loading author...';
19151948
authorMeta.style.opacity = '0';
19161949
meta.appendChild(authorMeta);
1917-
1950+
19181951
// Add categories if they exist
19191952
if (server.categories && server.categories.length > 0) {
19201953
const categorySpan = document.createElement('span');
19211954
categorySpan.className = 'category';
19221955
categorySpan.textContent = server.categories[0]; // Display first category
19231956
meta.appendChild(categorySpan);
19241957
}
1925-
1958+
19261959
// Update author information directly from server data (no need to fetch)
19271960
if (server.author && server.author.name) {
19281961
// Add user icon before author name
@@ -2188,4 +2221,4 @@ <h3>Raw Manifest:</h3>
21882221
</script>
21892222
</body>
21902223

2191-
</html>
2224+
</html>

scripts/get_manifest.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
import requests
1212
from categorization import CategorizationAgent
1313
from openai import OpenAI
14-
from utils import McpClient, validate_arguments_in_installation
14+
from utils import McpClient, inspect_docker_repo, validate_arguments_in_installation
1515

1616
dotenv.load_dotenv()
1717
logging.basicConfig(level=logging.INFO,
1818
format="%(asctime)s - %(levelname)s - %(message)s")
1919
logger = logging.getLogger(__name__)
2020

2121
_OUTPUT_DIR = "mcp-registry/servers/"
22+
DOCKER_MCP_REPO_URL = "https://hub.docker.com/r"
2223

2324

2425
class ManifestGenerator:
@@ -781,6 +782,13 @@ def generate_manifest(self, repo_url: str, server_name: Optional[str] = None) ->
781782
except Exception as e:
782783
logger.error(f"Failed to extract capabilities: {e}")
783784

785+
# docker url if docker command in installation
786+
installations = manifest.get("installations", {})
787+
if "docker" in installations:
788+
docker_repo_name = inspect_docker_repo(installations["docker"])
789+
if docker_repo_name:
790+
manifest["docker_url"] = f"{DOCKER_MCP_REPO_URL}/{docker_repo_name}"
791+
784792
return manifest
785793

786794
except Exception as e:

scripts/utils.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import re
22
from contextlib import AsyncExitStack
33
from datetime import timedelta
4-
from typing import Any
4+
from typing import Any, Optional
55

6+
import requests
67
from mcp import ClientSession, StdioServerParameters
78
from mcp.client.stdio import stdio_client
89
from mcp.shared.exceptions import McpError
910
from mcp.types import ListPromptsResult, ListResourcesResult, ListToolsResult
1011

12+
DOCKER_HUB_REPO_URL = "https://hub.docker.com/v2/repositories/"
1113

1214
class McpClient:
1315
session: ClientSession
@@ -148,3 +150,32 @@ def validate_arguments_in_installation(
148150
installation["env"] = env
149151

150152
return installation, replacement
153+
154+
155+
def validate_docker_url(docker_url: str) -> bool:
156+
try:
157+
response = requests.get(docker_url)
158+
# if success with status code 200, the repo should be a valid and registered one
159+
return response.status_code == 200
160+
except Exception:
161+
return False
162+
163+
164+
def inspect_docker_repo(installation: dict[str, Any]) -> Optional[str]:
165+
""" inspect the docker url from docker installation args, the args should pattern as {namespace}/{repo_name} where namespace=mcp
166+
Example
167+
if args = ["run", "-i", "--rm", "-e", "PERPLEXITY_API_KEY", "mcp/perplexity-ask"]
168+
return "mcp/perplexity-ask"
169+
"""
170+
repo_name = None
171+
if "args" in installation and installation["args"]:
172+
args = installation["args"]
173+
for arg in args:
174+
# namespace/repo(:tag)
175+
repo_match = re.match(r"^(mcp/[\w-]+)(?::[\w.\-]+)?$", arg)
176+
if repo_match:
177+
repo_name = repo_match.group(1)
178+
if validate_docker_url(DOCKER_HUB_REPO_URL + repo_name):
179+
return repo_name # namespace/repo without tag
180+
181+
return None

0 commit comments

Comments
 (0)