Skip to content

Commit b367ef8

Browse files
committed
feat: add get_mcp_server_detail tool
1 parent 9530a50 commit b367ef8

7 files changed

Lines changed: 201 additions & 18 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Empowers AI agents and chatbots with direct access to [ModelScope](https://model
1414

1515
- 🎨 **AI Image Generation** - Generate images from prompts (text-to-image) or transform existing images (image-to-image) using AIGC models
1616
- 🔍 **Resource Discovery** - Search and discover ModelScope resources including models, datasets, studios (AI apps), research papers, and MCP servers with advanced filtering options
17-
- 📋 **Resource Details** _(Coming Soon)_ - Get comprehensive details for specific resources
17+
- 📋 **Resource Details** - Get comprehensive details for specific resources
1818
- 📖 **Documentation Search** _(Coming Soon)_ - Semantic search for ModelScope documentation and articles
1919
- 🚀 **Gradio API Integration** _(Coming Soon)_ - Invoke Gradio APIs exposed by any pre-configured ModelScope studios
2020
- 🔐 **Context Information** - Access current operational context including authenticated user information and environment details

README_zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
- 🎨 **AI 图像生成** - 借助 AIGC 模型,轻松实现文生图(根据描述生成图像)或图生图(转换现有图像)
1616
- 🔍 **资源发现** - 快速搜索和发现 ModelScope 平台上的模型、数据集、创空间(AI 应用)、研究论文和 MCP 服务,支持多种高级筛选
17-
- 📋 **资源详情** _(即将推出)_ - 深入了解特定资源的详细信息
17+
- 📋 **资源详情** - 深入了解特定资源的详细信息
1818
- 📖 **文档搜索** _(即将推出)_ - 智能语义搜索 ModelScope 文档和文章内容
1919
- 🚀 **Gradio API 集成** _(即将推出)_ - 调用任意预配置的 ModelScope 创空间暴露的 Gradio API
2020
- 🔐 **上下文信息** - 实时获取当前操作环境信息,包括用户认证状态和运行环境详情

demo.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,15 +199,51 @@ async def demo_search_mcp_servers(client: Client) -> None:
199199
summaries = []
200200
for server in data:
201201
name = server.get("name", "N/A")
202-
publisher = server.get("publisher", "N/A")
203202
view_count = server.get("view_count", 0)
204-
summaries.append(f"{name} by {publisher} (Views {view_count})")
203+
summaries.append(f"{name} (Views {view_count})")
205204
print(f" • Result: Found {len(data)} items - {' | '.join(summaries)}")
206205
else:
207206
print(" • Result: No servers found")
208207
print()
209208

210209

210+
async def demo_get_mcp_server_detail(client: Client) -> None:
211+
"""Demo getting MCP server detail."""
212+
tool_name = "get_mcp_server_detail"
213+
server_id = "pengqun/modelscope-mcp-server"
214+
print_step_title(tool_name, f"🔍 Get MCP server detail for '{server_id}'")
215+
216+
result = await client.call_tool(
217+
tool_name,
218+
{
219+
"server_id": server_id,
220+
},
221+
)
222+
data = parse_tool_response(result)
223+
224+
if data:
225+
name = data.get("name", "N/A")
226+
author = data.get("author", "N/A")
227+
description = data.get("description", "N/A")
228+
is_hosted = data.get("is_hosted", False)
229+
is_verified = data.get("is_verified", False)
230+
view_count = data.get("view_count", 0)
231+
github_stars = data.get("github_stars", 0)
232+
tags = ", ".join(data.get("tags", []))
233+
modelscope_url = data.get("modelscope_url", "N/A")
234+
235+
print(f" • Name: {name}")
236+
print(f" • Author: {author}")
237+
print(f" • Description: {description[:80]}{'...' if len(description) > 80 else ''}")
238+
print(f" • Status: {'Hosted' if is_hosted else 'Not Hosted'}, {'Verified' if is_verified else 'Unverified'}")
239+
print(f" • Metrics: {view_count:,} views, {github_stars:,} GitHub stars")
240+
print(f" • Tags: {tags}")
241+
print(f" • ModelScope URL: {modelscope_url}")
242+
else:
243+
print(" • Result: Server detail not found")
244+
print()
245+
246+
211247
async def demo_generate_image(client: Client) -> None:
212248
"""Demo image generation."""
213249
tool_name = "generate_image"
@@ -270,6 +306,7 @@ async def main() -> None:
270306
await demo_search_studios(client)
271307
await demo_search_papers(client)
272308
await demo_search_mcp_servers(client)
309+
await demo_get_mcp_server_detail(client)
273310

274311
if args.full:
275312
await demo_generate_image(client)

src/modelscope_mcp_server/tools/mcp.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from ..client import default_client
1313
from ..settings import settings
14-
from ..types import McpServer
14+
from ..types import McpServer, McpServerDetail
1515

1616
logger = logging.get_logger(__name__)
1717

@@ -90,14 +90,55 @@ async def search_mcp_servers(
9090

9191
server = McpServer(
9292
id=id,
93-
modelscope_url=modelscope_url,
9493
name=server_data.get("name", ""),
95-
chinese_name=server_data.get("chinese_name", ""),
9694
description=server_data.get("description", ""),
97-
publisher=server_data.get("publisher", ""),
9895
tags=server_data.get("tags", []),
96+
logo_url=server_data.get("logo_url"),
97+
modelscope_url=modelscope_url,
9998
view_count=server_data.get("view_count", 0),
10099
)
101100
servers.append(server)
102101

103102
return servers
103+
104+
@mcp.tool(
105+
annotations={
106+
"title": "Get MCP Server Detail",
107+
}
108+
)
109+
async def get_mcp_server_detail(
110+
server_id: Annotated[
111+
str,
112+
Field(description="MCP Server ID in format 'author/name', e.g., 'pengqun/modelscope-mcp-server'"),
113+
],
114+
) -> McpServerDetail:
115+
"""Get detailed information about a specific MCP server."""
116+
url = f"{settings.main_domain}/openapi/v1/mcp/servers/{server_id}"
117+
118+
response = default_client.get(url)
119+
server_data = response.get("data", {})
120+
121+
id = server_data.get("id", "")
122+
modelscope_url = f"{settings.main_domain}/mcp/servers/{id}"
123+
124+
server_detail = McpServerDetail(
125+
# McpServer fields
126+
id=id,
127+
name=server_data.get("name", ""),
128+
description=server_data.get("description", ""),
129+
tags=server_data.get("tags", []),
130+
logo_url=server_data.get("logo_url"),
131+
modelscope_url=modelscope_url,
132+
view_count=server_data.get("view_count", 0),
133+
# Additional fields
134+
author=server_data.get("author", ""),
135+
server_config=server_data.get("server_config", []),
136+
env_schema=server_data.get("env_schema", ""),
137+
is_hosted=server_data.get("is_hosted", False),
138+
is_verified=server_data.get("is_verified", False),
139+
source_url=server_data.get("source_url", ""),
140+
readme=server_data.get("readme", ""),
141+
github_stars=server_data.get("github_stars", 0),
142+
)
143+
144+
return server_detail

src/modelscope_mcp_server/types.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,18 +140,41 @@ class McpServer(BaseModel):
140140
# Basic information
141141
id: Annotated[str, Field(description="MCP Server ID")]
142142
name: Annotated[str, Field(description="MCP Server name")]
143-
chinese_name: Annotated[str, Field(description="Chinese name")]
144143
description: Annotated[str, Field(description="Description")]
145-
publisher: Annotated[str, Field(description="Publisher")]
146144
tags: Annotated[list[str], Field(description="Tags")] = []
147145

148146
# Links
147+
logo_url: Annotated[str | None, Field(description="Logo image URL")] = None
149148
modelscope_url: Annotated[str, Field(description="Detail page URL on ModelScope")]
150149

151150
# Metrics
152151
view_count: Annotated[int, Field(description="View count")] = 0
153152

154153

154+
class McpServerDetail(McpServer):
155+
"""Detailed MCP Server information extending basic MCP Server info."""
156+
157+
# Basic information
158+
author: Annotated[str, Field(description="Author")]
159+
160+
# Configuration
161+
server_config: Annotated[list[dict], Field(description="Server configuration")] = []
162+
env_schema: Annotated[str, Field(description="JSON schema for environment variables")]
163+
164+
# Status flags
165+
is_hosted: Annotated[bool, Field(description="Whether the server supports hosted mode")]
166+
is_verified: Annotated[bool, Field(description="Whether the server's hosted mode is verified")]
167+
168+
# Additional links
169+
source_url: Annotated[str, Field(description="Source code URL")]
170+
171+
# Additional metrics
172+
github_stars: Annotated[int, Field(description="GitHub stars count")] = 0
173+
174+
# Documentation
175+
readme: Annotated[str, Field(description="README content")]
176+
177+
155178
class ImageGenerationResult(BaseModel):
156179
"""Image generation result."""
157180

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import pytest
2+
from fastmcp import Client
3+
4+
5+
# Helper functions
6+
async def get_mcp_server_detail_helper(client, server_id):
7+
"""Helper function to get MCP server detail and validate basic response structure."""
8+
result = await client.call_tool("get_mcp_server_detail", {"server_id": server_id})
9+
assert hasattr(result, "data"), "Result should have data attribute"
10+
server_detail = result.data
11+
return server_detail
12+
13+
14+
def print_server_detail_info(server_detail):
15+
"""Print server detail information."""
16+
print(f"ID: {server_detail.id}")
17+
print(f"Name: {server_detail.name}")
18+
print(f"Author: {server_detail.author}")
19+
print(f"Description: {server_detail.description}")
20+
print(f"Is Hosted: {server_detail.is_hosted}")
21+
print(f"Is Verified: {server_detail.is_verified}")
22+
print(f"View Count: {server_detail.view_count}")
23+
print(f"GitHub Stars: {server_detail.github_stars}")
24+
print(f"Tags: {server_detail.tags}")
25+
print(f"Source URL: {server_detail.source_url}")
26+
print(f"Logo URL: {server_detail.logo_url}")
27+
print(f"ModelScope URL: {server_detail.modelscope_url}")
28+
print(f"Server Config: {server_detail.server_config}")
29+
print(f"Env Schema: {server_detail.env_schema}")
30+
print(f"Readme: {server_detail.readme}")
31+
32+
33+
def validate_server_detail_fields(server_detail):
34+
"""Validate that server detail has all required fields."""
35+
required_fields = [
36+
"id",
37+
"name",
38+
"description",
39+
"author",
40+
"tags",
41+
"env_schema",
42+
"server_config",
43+
"is_hosted",
44+
"is_verified",
45+
"modelscope_url",
46+
"source_url",
47+
"logo_url",
48+
"readme",
49+
"view_count",
50+
"github_stars",
51+
]
52+
53+
for field in required_fields:
54+
assert hasattr(server_detail, field), f"Server detail should have '{field}' attribute"
55+
56+
# Validate field types
57+
assert isinstance(server_detail.id, str), "ID should be a string"
58+
assert isinstance(server_detail.name, str), "Name should be a string"
59+
assert isinstance(server_detail.tags, list), "Tags should be a list"
60+
assert isinstance(server_detail.is_hosted, bool), "is_hosted should be a boolean"
61+
assert isinstance(server_detail.is_verified, bool), "is_verified should be a boolean"
62+
assert isinstance(server_detail.view_count, int), "View count should be an integer"
63+
assert isinstance(server_detail.github_stars, int), "GitHub stars should be an integer"
64+
assert isinstance(server_detail.server_config, list), "Server config should be a list"
65+
assert isinstance(server_detail.env_schema, str), "Env schema should be a string"
66+
assert isinstance(server_detail.readme, str), "Readme should be a string"
67+
assert isinstance(server_detail.logo_url, str), "Logo URL should be a string"
68+
69+
70+
@pytest.mark.integration
71+
async def test_get_mcp_server_detail(mcp_server):
72+
"""Test get_mcp_server_detail with a known server ID."""
73+
async with Client(mcp_server) as client:
74+
server_id = "pengqun/modelscope-mcp-server"
75+
server_detail = await get_mcp_server_detail_helper(client, server_id)
76+
77+
# Validate response structure
78+
validate_server_detail_fields(server_detail)
79+
80+
# Print detailed information
81+
print(f"\n✅ Received MCP server detail for '{server_id}':")
82+
print_server_detail_info(server_detail)
83+
84+
# Validate specific fields for known server
85+
assert server_detail.id == server_id, f"Server ID should be {server_id}"
86+
assert server_detail.author == "pengqun", "Author should be pengqun"
87+
assert server_detail.name != "", "Name should not be empty"

tests/tools/test_search_mcp_servers.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@ async def search_mcp_servers_helper(client, params):
1414

1515
def print_server_info(server, extra_fields=None):
1616
"""Print server information with optional extra fields."""
17-
base_info = (
18-
f"id: {server.get('id', '')} | "
19-
f"name: {server.get('name', '')} | "
20-
f"chinese_name: {server.get('chinese_name', '')} | "
21-
f"publisher: {server.get('publisher', '')}"
22-
)
17+
base_info = f"id: {server.get('id', '')} | name: {server.get('name', '')}"
2318

2419
if extra_fields:
2520
for field in extra_fields:
@@ -40,10 +35,10 @@ def validate_server_fields(server):
4035
required_fields = [
4136
"id",
4237
"name",
43-
"chinese_name",
4438
"description",
45-
"publisher",
4639
"tags",
40+
"logo_url",
41+
"modelscope_url",
4742
"view_count",
4843
]
4944

0 commit comments

Comments
 (0)