swanlab-mcp 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ from ._version import __version__
2
+ from .config import SwanLabConfig
3
+
4
+
5
+ def main() -> None:
6
+ """Serve as the main entry point for SwanLab MCP Server."""
7
+ from .cli import main as cli_main
8
+
9
+ cli_main()
10
+
11
+
12
+ __all__ = ["main", "__version__", "SwanLabConfig"]
@@ -0,0 +1,4 @@
1
+ from . import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,3 @@
1
+ """Version information for SwanLab MCP Server."""
2
+
3
+ __version__ = "0.0.1"
swanlab_mcp/cli.py ADDED
@@ -0,0 +1,59 @@
1
+ """Command line interface for SwanLab MCP Server."""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from .meta.info import get_server_name, get_server_name_with_version, get_server_version
7
+ from .server import create_mcp_server
8
+
9
+
10
+ def create_parser() -> argparse.ArgumentParser:
11
+ """Create and configure the argument parser."""
12
+ parser = argparse.ArgumentParser(
13
+ description=get_server_name_with_version(),
14
+ formatter_class=argparse.RawDescriptionHelpFormatter,
15
+ epilog="""
16
+ Examples:
17
+ %(prog)s # Run with stdio transport (default)
18
+ %(prog)s --transport stdio # Run with stdio transport
19
+ """,
20
+ )
21
+
22
+ parser.add_argument(
23
+ "--transport",
24
+ choices=["stdio"],
25
+ default="stdio",
26
+ help="Transport type (default: stdio)",
27
+ )
28
+
29
+ parser.add_argument(
30
+ "--version",
31
+ action="version",
32
+ version=f"%(prog)s {get_server_version()}",
33
+ )
34
+
35
+ return parser
36
+
37
+
38
+ def main() -> None:
39
+ """Handle CLI entry point operations."""
40
+ parser = create_parser()
41
+ args = parser.parse_args()
42
+
43
+ # Create and configure the MCP server
44
+ try:
45
+ mcp = create_mcp_server()
46
+ print(f"MCP server created successfully on transport {args.transport}")
47
+ except Exception as e:
48
+ print(f"Error creating MCP server: {e}", file=sys.stderr)
49
+ sys.exit(1)
50
+
51
+ try:
52
+ print(f"Starting MCP server on transport {args.transport}...")
53
+ mcp.run(transport=args.transport)
54
+ except KeyboardInterrupt:
55
+ print(f"\nShutting down {get_server_name()}...", file=sys.stderr)
56
+ sys.exit(0)
57
+ except Exception as e:
58
+ print(f"Error starting server: {e}", file=sys.stderr)
59
+ sys.exit(1)
swanlab_mcp/config.py ADDED
@@ -0,0 +1,59 @@
1
+ """Configuration management for SwanLab MCP Server."""
2
+
3
+ from pydantic import Field
4
+ from pydantic_settings import BaseSettings
5
+
6
+ from .constants import DEFAULT_API_TIMEOUT_SECONDS, DEFAULT_SWANLAB_API_HOST, DEFAULT_SWANLAB_HOST
7
+
8
+
9
+ class SwanLabConfig(BaseSettings):
10
+ """SwanLab MCP Server configuration settings."""
11
+
12
+ # Authentication settings
13
+ api_key: str | None = Field(
14
+ default=None,
15
+ description="SwanLab API key for authentication",
16
+ validation_alias="SWANLAB_API_KEY",
17
+ )
18
+
19
+ # Domain settings
20
+ main_domain: str = Field(
21
+ default=DEFAULT_SWANLAB_HOST,
22
+ description="SwanLab website domain",
23
+ validation_alias="SWANLAB_HOST",
24
+ )
25
+ api_domain: str = Field(
26
+ default=DEFAULT_SWANLAB_API_HOST,
27
+ description="SwanLab API domain",
28
+ validation_alias="SWANLAB_API_HOST",
29
+ )
30
+
31
+ # Request settings
32
+ timeout: int = Field(
33
+ default=DEFAULT_API_TIMEOUT_SECONDS,
34
+ description="API request timeout in seconds",
35
+ validation_alias="API_TIMEOUT",
36
+ )
37
+
38
+ class Config:
39
+ """Pydantic configuration."""
40
+
41
+ env_file = ".env"
42
+ env_file_encoding = "utf-8"
43
+ extra = "ignore"
44
+
45
+
46
+ def get_config() -> SwanLabConfig:
47
+ """
48
+ Get the SwanLab configuration.
49
+
50
+ Returns:
51
+ SwanLabConfig instance with settings loaded from environment.
52
+
53
+ Raises:
54
+ ValueError: If required settings (like api_key) are not set.
55
+ """
56
+ config = SwanLabConfig()
57
+ if not config.api_key:
58
+ raise ValueError("SWANLAB_API_KEY environment variable must be set")
59
+ return config
@@ -0,0 +1,7 @@
1
+ """Constants for SwanLab MCP Server."""
2
+
3
+ DEFAULT_SWANLAB_HOST = "https://swanlab.cn"
4
+ DEFAULT_SWANLAB_API_HOST = "https://api.swanlab.cn"
5
+
6
+
7
+ DEFAULT_API_TIMEOUT_SECONDS = 10
File without changes
@@ -0,0 +1,72 @@
1
+ """Metadata utilities for version information."""
2
+
3
+ import sys
4
+ from importlib import metadata
5
+
6
+ from .. import __version__
7
+
8
+
9
+ def get_server_name() -> str:
10
+ """Get SwanLab MCP Server name.
11
+
12
+ Returns:
13
+ str: Server name without version
14
+
15
+ """
16
+ return "SwanLab MCP Server"
17
+
18
+
19
+ def get_server_name_with_version() -> str:
20
+ """Get SwanLab MCP Server name with version.
21
+
22
+ Returns:
23
+ str: Server name with version (e.g., "SwanLab MCP Server v1.0.0")
24
+
25
+ """
26
+ return f"{get_server_name()} v{get_server_version()}"
27
+
28
+
29
+ def get_server_version() -> str:
30
+ """Get SwanLab MCP Server version.
31
+
32
+ Returns:
33
+ str: Server version string
34
+
35
+ """
36
+ return __version__
37
+
38
+
39
+ def get_fastmcp_version() -> str:
40
+ """Get FastMCP framework version.
41
+
42
+ Returns:
43
+ str: FastMCP version string, or "unknown" if not available
44
+
45
+ """
46
+ try:
47
+ return metadata.version("fastmcp")
48
+ except metadata.PackageNotFoundError:
49
+ return "unknown"
50
+
51
+
52
+ def get_mcp_protocol_version() -> str:
53
+ """Get MCP protocol version.
54
+
55
+ Returns:
56
+ str: MCP protocol version string, or "unknown" if not available
57
+
58
+ """
59
+ try:
60
+ return metadata.version("mcp")
61
+ except metadata.PackageNotFoundError:
62
+ return "unknown"
63
+
64
+
65
+ def get_python_version() -> str:
66
+ """Get Python runtime version.
67
+
68
+ Returns:
69
+ str: Python version in x.y.z format
70
+
71
+ """
72
+ return f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
swanlab_mcp/models.py ADDED
@@ -0,0 +1,26 @@
1
+ from typing import Dict
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class Experiment(BaseModel):
7
+ cuid: str # 实验CUID, 唯一标识符
8
+ name: str # 实验名
9
+ description: str = "" # 实验描述
10
+ state: str # 实验状态, 'FINISHED' 或 'RUNNING'
11
+ show: bool # 显示状态
12
+ createdAt: str # e.g., '2024-11-23T12:28:04.286Z'
13
+ finishedAt: str = "" # e.g., '2024-11-23T12:28:04.286Z'
14
+ user: Dict[str, str] # 实验创建者, 包含 'username' 与 'name'
15
+ profile: Dict # 实验相关配置
16
+
17
+
18
+ class Project(BaseModel):
19
+ cuid: str # 项目CUID, 唯一标识符
20
+ name: str # 项目名
21
+ description: str = "" # 项目描述
22
+ visibility: str # 可见性, 'PUBLIC' 或 'PRIVATE'
23
+ createdAt: str # e.g., '2024-11-23T12:28:04.286Z'
24
+ updatedAt: str # e.g., '2024-11-23T12:28:04.286Z'
25
+ group: Dict[str, str] # 工作空间信息, 包含 'type', 'username', 'name'
26
+ count: Dict[str, int] = {} # 项目的统计信息
swanlab_mcp/server.py ADDED
@@ -0,0 +1,38 @@
1
+ """SwanLab MCP Server."""
2
+
3
+ from mcp.server.fastmcp import FastMCP
4
+ from swanlab import OpenApi
5
+
6
+ from .config import get_config
7
+ from .meta.info import get_server_name_with_version
8
+ from .tools import register_experiment_tools, register_project_tools, register_workspace_tools
9
+
10
+
11
+ def create_mcp_server():
12
+ # Load configuration
13
+ config = get_config()
14
+
15
+ # Initialize SwanLab API
16
+ swanlab_api = OpenApi(api_key=config.api_key or "")
17
+
18
+ # Initialize MCP server
19
+ mcp = FastMCP(
20
+ name=get_server_name_with_version(),
21
+ instructions="""
22
+ A Model Context Protocol (MCP) server for SwanLab - a collaborative machine learning experiment tracking platform.
23
+
24
+ This server provides tools to:
25
+ - List and manage workspaces
26
+ - List, get, and delete projects
27
+ - List, get, and delete experiments
28
+ - Retrieve experiment metrics and summaries
29
+
30
+ All operations require a valid SWANLAB_API_KEY set in the environment.
31
+ """,
32
+ )
33
+
34
+ # Register all tools
35
+ register_workspace_tools(mcp, swanlab_api)
36
+ register_project_tools(mcp, swanlab_api)
37
+ register_experiment_tools(mcp, swanlab_api)
38
+ return mcp
@@ -0,0 +1,16 @@
1
+ """SwanLab MCP Tools."""
2
+
3
+ from .experiment import ExperimentTools, register_experiment_tools
4
+ from .project import ProjectTools, register_project_tools
5
+ from .workspace import WorkspaceTools, register_workspace_tools
6
+
7
+ __all__ = [
8
+ # Tool classes
9
+ "ProjectTools",
10
+ "ExperimentTools",
11
+ "WorkspaceTools",
12
+ # Registration functions
13
+ "register_workspace_tools",
14
+ "register_project_tools",
15
+ "register_experiment_tools",
16
+ ]
@@ -0,0 +1,245 @@
1
+ """Experiment related MCP tools."""
2
+
3
+ from typing import Any, Dict, List, Optional, Union
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+ from mcp.types import ToolAnnotations
7
+ from swanlab import OpenApi
8
+
9
+ from ..models import Experiment
10
+
11
+
12
+ class ExperimentTools:
13
+ """SwanLab Experiment management tools."""
14
+
15
+ def __init__(self, api: OpenApi):
16
+ self.api = api
17
+
18
+ async def list_experiments(self, project: str) -> List[Experiment]:
19
+ """
20
+ List all experiments in a project.
21
+
22
+ Args:
23
+ project: Project name or CUID
24
+
25
+ Returns:
26
+ List of Experiment objects containing:
27
+ - cuid: Unique experiment identifier
28
+ - name: Experiment name
29
+ - description: Experiment description
30
+ - state: RUNNING or FINISHED
31
+ - show: Display status
32
+ - createdAt/finishedAt: Timestamps
33
+ - user: Creator information
34
+ - profile: Experiment configuration
35
+ """
36
+ try:
37
+ response = self.api.list_experiments(project=project)
38
+ experiments = []
39
+ for item in response.data:
40
+ if hasattr(item, "model_dump"):
41
+ experiments.append(Experiment(**item.model_dump()))
42
+ elif hasattr(item, "__dict__"):
43
+ experiments.append(Experiment(**item.__dict__))
44
+ else:
45
+ experiments.append(Experiment(**item))
46
+ return experiments
47
+ except Exception as e:
48
+ raise RuntimeError(f"Failed to list experiments for project '{project}': {str(e)}") from e
49
+
50
+ async def get_experiment(self, project: str, exp_id: str) -> Experiment:
51
+ """
52
+ Get detailed information about a specific experiment.
53
+
54
+ Args:
55
+ project: Project name or CUID
56
+ exp_id: Experiment CUID
57
+
58
+ Returns:
59
+ Experiment object with detailed information including profile data
60
+ """
61
+ try:
62
+ response = self.api.get_experiment(project=project, exp_id=exp_id)
63
+ item = response.data
64
+ if hasattr(item, "model_dump"):
65
+ return Experiment(**item.model_dump())
66
+ elif hasattr(item, "__dict__"):
67
+ return Experiment(**item.__dict__)
68
+ else:
69
+ return Experiment(**item)
70
+ except Exception as e:
71
+ raise RuntimeError(f"Failed to get experiment '{exp_id}' from project '{project}': {str(e)}") from e
72
+
73
+ async def get_summary(self, project: str, exp_id: str) -> Dict[str, Any]:
74
+ """
75
+ Get summary statistics for an experiment's metrics.
76
+
77
+ Args:
78
+ project: Project name or CUID
79
+ exp_id: Experiment CUID
80
+
81
+ Returns:
82
+ Dictionary containing metric summaries with step, value, min, and max information.
83
+ Example: {"loss": {"step": 47, "value": 0.19, "min": {...}, "max": {...}}}
84
+ """
85
+ try:
86
+ response = self.api.get_summary(project=project, exp_id=exp_id)
87
+ return response.data
88
+ except Exception as e:
89
+ raise RuntimeError(f"Failed to get summary for experiment '{exp_id}': {str(e)}") from e
90
+
91
+ async def get_metrics(self, exp_id: str, keys: Union[str, List[str]]) -> Optional[Dict[str, Any]]:
92
+ """
93
+ Get metric data for an experiment.
94
+
95
+ Args:
96
+ exp_id: Experiment CUID
97
+ keys: List of metric names to retrieve (e.g., ["loss", "acc"])
98
+
99
+ Returns:
100
+ Dictionary containing metric values indexed by step.
101
+ Includes both values and timestamps for each metric.
102
+ """
103
+ try:
104
+ response = self.api.get_metrics(exp_id=exp_id, keys=keys if isinstance(keys, list) else [keys])
105
+ data = response.data
106
+ if hasattr(data, "to_dict"):
107
+ return data.to_dict()
108
+ return None
109
+ except Exception as e:
110
+ raise RuntimeError(f"Failed to get metrics for experiment '{exp_id}': {str(e)}") from e
111
+
112
+ async def delete_experiment(self, project: str, exp_id: str) -> str:
113
+ """
114
+ Delete an experiment from a project.
115
+
116
+ Args:
117
+ project: Project name or CUID
118
+ exp_id: Experiment CUID to delete
119
+
120
+ Returns:
121
+ Success message
122
+ """
123
+ try:
124
+ self.api.delete_experiment(project=project, exp_id=exp_id)
125
+ return f"Successfully deleted experiment '{exp_id}' from project '{project}'"
126
+ except Exception as e:
127
+ raise RuntimeError(f"Failed to delete experiment '{exp_id}': {str(e)}") from e
128
+
129
+
130
+ def register_experiment_tools(mcp: FastMCP, api: OpenApi) -> None:
131
+ """
132
+ Register experiment-related MCP tools.
133
+
134
+ Args:
135
+ mcp: FastMCP server instance
136
+ api: SwanLab OpenApi instance
137
+ """
138
+ experiment_tools = ExperimentTools(api)
139
+
140
+ @mcp.tool(
141
+ name="swanlab_list_experiments_in_a_project",
142
+ description="List all experiments in a project.",
143
+ annotations=ToolAnnotations(
144
+ title="List all experiments in a project.",
145
+ readOnlyHint=True,
146
+ ),
147
+ )
148
+ async def list_experiments(project: str) -> List[Dict[str, Any]]:
149
+ """
150
+ List all experiments in a project.
151
+
152
+ Args:
153
+ project: Project name or CUID
154
+
155
+ Returns:
156
+ List of experiments with their names, states, descriptions, and metadata.
157
+ """
158
+ experiments = await experiment_tools.list_experiments(project)
159
+ return [exp.model_dump() for exp in experiments]
160
+
161
+ @mcp.tool(
162
+ name="swanlab_get_a_specific_experiment",
163
+ description="Get detailed information about a specific experiment in a project.",
164
+ annotations=ToolAnnotations(
165
+ title="Get detailed information about a specific experiment in a project.",
166
+ readOnlyHint=True,
167
+ ),
168
+ )
169
+ async def get_experiment(project: str, exp_id: str) -> Dict[str, Any]:
170
+ """
171
+ Get detailed information about a specific experiment.
172
+
173
+ Args:
174
+ project: Project name or CUID
175
+ exp_id: Experiment CUID
176
+
177
+ Returns:
178
+ Experiment details including profile data, configuration, and metadata.
179
+ """
180
+ experiment = await experiment_tools.get_experiment(project, exp_id)
181
+ return experiment.model_dump()
182
+
183
+ @mcp.tool(
184
+ name="swanlab_get_summary_for_an_experiment",
185
+ description="Get summary statistics for an experiment's metrics.",
186
+ annotations=ToolAnnotations(
187
+ title="Get summary statistics for an experiment's metrics.",
188
+ readOnlyHint=True,
189
+ ),
190
+ )
191
+ async def get_experiment_summary(project: str, exp_id: str) -> Dict[str, Any]:
192
+ """
193
+ Get summary statistics for an experiment's metrics.
194
+
195
+ Args:
196
+ project: Project name or CUID
197
+ exp_id: Experiment CUID
198
+
199
+ Returns:
200
+ Dictionary containing metric summaries with step, value, min, and max information.
201
+ """
202
+ return await experiment_tools.get_summary(project, exp_id)
203
+
204
+ @mcp.tool(
205
+ name="swanlab_get_metrics_for_an_experiment",
206
+ description="Get metric data for an experiment.",
207
+ annotations=ToolAnnotations(
208
+ title="Get metric data for an experiment.",
209
+ readOnlyHint=True,
210
+ ),
211
+ )
212
+ async def get_experiment_metrics(exp_id: str, keys: List[str]) -> Optional[Dict[str, Any]]:
213
+ """
214
+ Get metric data for an experiment.
215
+
216
+ Args:
217
+ exp_id: Experiment CUID
218
+ keys: Optional list of metric names to retrieve (e.g., ["loss", "acc"]).
219
+ If not provided, all metrics will be returned.
220
+
221
+ Returns:
222
+ Dictionary containing metric values indexed by step, including timestamps.
223
+ """
224
+ return await experiment_tools.get_metrics(exp_id, keys)
225
+
226
+ @mcp.tool(
227
+ name="swanlab_delete_an_experiment",
228
+ description="Delete an experiment from a project. Warning: This action cannot be undone.",
229
+ annotations=ToolAnnotations(
230
+ title="Delete an experiment from a project. Warning: This action cannot be undone.",
231
+ readOnlyHint=False,
232
+ ),
233
+ )
234
+ async def delete_experiment(project: str, exp_id: str) -> str:
235
+ """
236
+ Delete an experiment from a project.
237
+
238
+ Args:
239
+ project: Project name or CUID
240
+ exp_id: Experiment CUID to delete
241
+
242
+ Returns:
243
+ Success message confirming deletion.
244
+ """
245
+ return await experiment_tools.delete_experiment(project, exp_id)
@@ -0,0 +1,154 @@
1
+ """Project related MCP tools."""
2
+
3
+ from typing import Any, Dict, List
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+ from mcp.types import ToolAnnotations
7
+ from swanlab import OpenApi
8
+
9
+ from ..models import Project
10
+
11
+
12
+ class ProjectTools:
13
+ """SwanLab Project management tools."""
14
+
15
+ def __init__(self, api: OpenApi):
16
+ self.api = api
17
+
18
+ async def list_projects(self) -> List[Project]:
19
+ """
20
+ List all projects in the workspace.
21
+
22
+ Returns:
23
+ List of Project objects containing project information including:
24
+ - cuid: Unique project identifier
25
+ - name: Project name
26
+ - description: Project description
27
+ - visibility: PUBLIC or PRIVATE
28
+ - createdAt/updatedAt: Timestamps
29
+ - group: Workspace information
30
+ - count: Statistics (experiments, contributors, etc.)
31
+ """
32
+ try:
33
+ response = self.api.list_projects()
34
+ # SwanLab API returns Project objects, convert to our Pydantic models
35
+ projects = []
36
+ for item in response.data:
37
+ if hasattr(item, "model_dump"):
38
+ # If it's already a Pydantic model
39
+ projects.append(Project(**item.model_dump()))
40
+ elif hasattr(item, "__dict__"):
41
+ # If it's a regular object with attributes
42
+ projects.append(Project(**item.__dict__))
43
+ else:
44
+ # If it's a dict
45
+ projects.append(Project(**item))
46
+ return projects
47
+ except Exception as e:
48
+ raise RuntimeError(f"Failed to list projects: {str(e)}") from e
49
+
50
+ async def get_project(self, project: str) -> Project:
51
+ """
52
+ Get detailed information about a specific project by filtering from list.
53
+
54
+ Args:
55
+ project: Project name or CUID
56
+
57
+ Returns:
58
+ Project object with detailed information
59
+ """
60
+ try:
61
+ # SwanLab API doesn't have get_project, so we filter from list_projects
62
+ projects = await self.list_projects()
63
+ for proj in projects:
64
+ if proj.cuid == project or proj.name == project:
65
+ return proj
66
+ raise ValueError(f"Project '{project}' not found")
67
+ except Exception as e:
68
+ raise RuntimeError(f"Failed to get project '{project}': {str(e)}") from e
69
+
70
+ async def delete_project(self, project: str) -> str:
71
+ """
72
+ Delete a project from the workspace.
73
+
74
+ Args:
75
+ project: Project name or CUID to delete
76
+
77
+ Returns:
78
+ Success message
79
+ """
80
+ try:
81
+ self.api.delete_project(project=project)
82
+ return f"Successfully deleted project '{project}'"
83
+ except Exception as e:
84
+ raise RuntimeError(f"Failed to delete project '{project}': {str(e)}") from e
85
+
86
+
87
+ def register_project_tools(mcp: FastMCP, api: OpenApi) -> None:
88
+ """
89
+ Register project-related MCP tools.
90
+
91
+ Args:
92
+ mcp: FastMCP server instance
93
+ api: SwanLab OpenApi instance
94
+ """
95
+ project_tools = ProjectTools(api)
96
+
97
+ @mcp.tool(
98
+ name="swanlab_list_projects_in_a_workspace",
99
+ description="List all projects in the workspace.",
100
+ annotations=ToolAnnotations(
101
+ title="List all projects in the workspace.",
102
+ readOnlyHint=True,
103
+ ),
104
+ )
105
+ async def list_projects() -> List[Dict[str, Any]]:
106
+ """
107
+ List all projects in the workspace.
108
+
109
+ Returns:
110
+ List of projects with details including name, description, visibility, statistics, etc.
111
+ """
112
+ projects = await project_tools.list_projects()
113
+ return [project.model_dump() for project in projects]
114
+
115
+ @mcp.tool(
116
+ name="swanlab_get_a_specific_project",
117
+ description="Get detailed information about a specific project.",
118
+ annotations=ToolAnnotations(
119
+ title="Get detailed information about a specific project.",
120
+ readOnlyHint=True,
121
+ ),
122
+ )
123
+ async def get_project(project: str) -> Dict[str, Any]:
124
+ """
125
+ Get detailed information about a specific project.
126
+
127
+ Args:
128
+ project: Project name or CUID
129
+
130
+ Returns:
131
+ Project details including metadata and statistics.
132
+ """
133
+ project_obj = await project_tools.get_project(project)
134
+ return project_obj.model_dump()
135
+
136
+ @mcp.tool(
137
+ name="swanlab_delete_a_project",
138
+ description="Delete a project from the workspace. Warning: This action cannot be undone.",
139
+ annotations=ToolAnnotations(
140
+ title="Delete a project from the workspace. Warning: This action cannot be undone.",
141
+ readOnlyHint=False,
142
+ ),
143
+ )
144
+ async def delete_project(project: str) -> str:
145
+ """
146
+ Delete a project from the workspace.
147
+
148
+ Args:
149
+ project: Project name or CUID to delete
150
+
151
+ Returns:
152
+ Success message confirming deletion.
153
+ """
154
+ return await project_tools.delete_project(project)
@@ -0,0 +1,58 @@
1
+ """Workspace related MCP tools."""
2
+
3
+ from typing import Any, Dict, List
4
+
5
+ from mcp.server.fastmcp import FastMCP
6
+ from mcp.types import ToolAnnotations
7
+ from swanlab import OpenApi
8
+
9
+
10
+ class WorkspaceTools:
11
+ """SwanLab Workspace management tools."""
12
+
13
+ def __init__(self, api: OpenApi):
14
+ self.api = api
15
+
16
+ async def list_workspaces(self) -> List[Dict[str, Any]]:
17
+ """
18
+ List all workspaces accessible to the current user.
19
+
20
+ Returns:
21
+ List of workspace dictionaries containing:
22
+ - name: Workspace name
23
+ - username: Workspace username/identifier
24
+ - role: User's role in the workspace (OWNER, MEMBER, etc.)
25
+ """
26
+ try:
27
+ response = self.api.list_workspaces()
28
+ return response.data
29
+ except Exception as e:
30
+ raise RuntimeError(f"Failed to list workspaces: {str(e)}") from e
31
+
32
+
33
+ def register_workspace_tools(mcp: FastMCP, api: OpenApi) -> None:
34
+ """
35
+ Register workspace-related MCP tools.
36
+
37
+ Args:
38
+ mcp: FastMCP server instance
39
+ api: SwanLab OpenApi instance
40
+ """
41
+ workspace_tools = WorkspaceTools(api)
42
+
43
+ @mcp.tool(
44
+ name="swanlab_list_workspaces",
45
+ description="List all workspaces accessible to the current user.",
46
+ annotations=ToolAnnotations(
47
+ title="List all workspaces accessible to the current user.",
48
+ readOnlyHint=True,
49
+ ),
50
+ )
51
+ async def list_workspaces() -> List[Dict[str, Any]]:
52
+ """
53
+ List all workspaces accessible to the current user.
54
+
55
+ Returns:
56
+ List of workspaces with their names, usernames, and user roles.
57
+ """
58
+ return await workspace_tools.list_workspaces()
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: swanlab-mcp
3
+ Version: 0.0.1
4
+ Summary: MCP (Model Context Protocol) server support for SwanLab
5
+ Author-email: CaddiesNew <nexisato0810@gmail.com>
6
+ License: MIT
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: fastmcp>=2.14.4
9
+ Requires-Dist: pydantic-settings>=2.0.0
10
+ Requires-Dist: python-dotenv>=1.2.1
11
+ Requires-Dist: swanlab
12
+ Description-Content-Type: text/markdown
13
+
14
+ # SwanLab-MCP-Server
15
+
16
+ > A Model Context Protocol (MCP) server implementation for SwanLab, combining SwanLab-OpenAPI & FastMCP.
17
+
18
+ ## ✨ Features
19
+
20
+ ### Core Features
21
+
22
+ - **Workspace Management** - List and manage user-accessible workspaces
23
+ - **Project Management** - Create, retrieve, delete projects, and list project information
24
+ - **Experiment Management** - Create, retrieve, delete experiments, and retrieve experiment metrics and summaries
25
+ - **API Integration** - Provide complete platform access through SwanLab OpenAPI
26
+
27
+ ### Tech Stack
28
+
29
+ - **Language**: Python 3.12+
30
+ - **Core Framework**: FastMCP (v2.14.4+)
31
+ - **API Client**: SwanLab SDK
32
+ - **Config Management**: Pydantic Settings
33
+
34
+ ## 🚀 Quick Start
35
+
36
+ ### ❗️Configuration
37
+
38
+ Add the following configuration to your relative mcp config list
39
+
40
+ ```json
41
+ {
42
+ "mcpServers":
43
+ ...
44
+ {
45
+ "swanlab-mcp": {
46
+ "command": "uv",
47
+ "args": ["run", "swanlab_mcp", "--transport", "stdio"],
48
+ "env": {
49
+ "SWANLAB_API_KEY": "your_api_key_here"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ For `Claude Code` Users, you can config like this:
57
+
58
+ ```bash
59
+ claude mcp add --env SWANLAB_API_KEY=<your_api_key> -- swanlab_mcp uv run swanlab_mcp --transport stdio
60
+ ```
61
+
62
+ ### Prerequisites
63
+
64
+ - Python >= 3.12
65
+ - SwanLab API Key (get it from [SwanLab](https://swanlab.cn))
66
+
67
+ ### Installation
68
+
69
+ ```bash
70
+ # Using uv (recommended)
71
+ uv sync
72
+
73
+ # Or using pip
74
+ pip install -e .
75
+ ```
76
+
77
+ ### Configuration
78
+
79
+ #### Environment Variables
80
+
81
+ Create a `.env` file and configure your API key:
82
+
83
+ ```bash
84
+ cp .env.template .env
85
+ ```
86
+
87
+ Edit the `.env` file:
88
+
89
+ ```env
90
+ SWANLAB_API_KEY=your_api_key_here
91
+ ```
92
+
93
+ ### Running
94
+
95
+ ```bash
96
+ # Using stdio transport (default)
97
+ python -m swanlab_mcp
98
+
99
+ # Or using CLI
100
+ python -m swanlab_mcp --transport stdio
101
+
102
+ # Check version
103
+ python -m swanlab_mcp --version
104
+ ```
105
+
106
+ ### Usage
107
+
108
+ After configuration, restart Claude Desktop to interact with SwanLab via the MCP protocol.
109
+
110
+ Available Tools:
111
+ - `swanlab_list_workspaces` - List workspaces
112
+ - `swanlab_create_project` - Create project
113
+ - `swanlab_list_projects` - List projects
114
+ - `swanlab_create_experiment` - Create experiment
115
+ - `swanlab_list_experiments` - List experiments
116
+ - `swanlab_get_experiment` - Get experiment details
117
+ - `swanlab_delete_experiment` - Delete experiment
118
+
119
+ ## 🛠️ Development
120
+
121
+ ### Code Formatting
122
+
123
+ ```bash
124
+ # Using Makefile
125
+ make format
126
+
127
+ # Or manually
128
+ uvx isort . --skip-gitignore
129
+ uvx ruff format . --quiet
130
+ ```
131
+
132
+ ### Lint Check
133
+
134
+ ```bash
135
+ uvx ruff check .
136
+ ```
137
+
138
+ ### Pre-commit Hooks
139
+
140
+ ```bash
141
+ bash scripts/install-hooks.sh
142
+ ```
143
+
144
+ ## 📚 References & Acknowledgements
145
+
146
+ - [SwanLab](https://github.com/SwanHubX/SwanLab)
147
+ - [Model Context Protocol](https://modelcontextprotocol.io/docs/getting-started/intro)
148
+ - [FastMCP v2](https://github.com/jlowin/fastmcp)
149
+ - [modelscope-mcp-server](https://github.com/modelscope/modelscope-mcp-server)
150
+ - [TrackIO-mcp-server](https://github.com/fcakyon/trackio-mcp)
151
+ - [Simple-Wandb-mcp-server](https://github.com/tsilva/simple-wandb-mcp-server)
152
+
153
+ ## 📄 License
154
+
155
+ MIT License
@@ -0,0 +1,18 @@
1
+ swanlab_mcp/__init__.py,sha256=BE5zf9dh3Lx_QvUr_7vI9afv-C_2wBJaWWCiGrSrlz4,261
2
+ swanlab_mcp/__main__.py,sha256=5BjNuyet8AY-POwoF5rGt722rHQ7tJ0Vf0UFUfzzi-I,58
3
+ swanlab_mcp/_version.py,sha256=V9RIhJ1LjlFvbGmWoU5Vy5wjiVD_LKdlNdrh-NQ88Lw,73
4
+ swanlab_mcp/cli.py,sha256=7tJZv8f8ByJ0ME8wDcyTZiTkp2fPYjBk0TSGFRRbZTc,1710
5
+ swanlab_mcp/config.py,sha256=MgqztPVcEfIeRDv4AUmX9tpYTW42DXbVtgY1BW6BXHU,1603
6
+ swanlab_mcp/constants.py,sha256=0aDFnrfgN_DkY8IQY6w-0WV9hDTwvNGbCJH5BD_SXuc,172
7
+ swanlab_mcp/models.py,sha256=PbcCBYjoospoXX0HjaKtcEGq6YHQWO4oMZFrUqbln7c,967
8
+ swanlab_mcp/server.py,sha256=wYWvaezWi0hrYlLuYPRp8kRS1XfnrnXNAMxF0Sz-qDo,1172
9
+ swanlab_mcp/meta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ swanlab_mcp/meta/info.py,sha256=2w6_UhXUWWtrdSSlq_gTPdn6-Bbs7TBN24R_LO2GrEw,1482
11
+ swanlab_mcp/tools/__init__.py,sha256=AgPfbSmuI4SzryUzvqfl_wvByAV-A7zfV9zf3-lnQSU,438
12
+ swanlab_mcp/tools/experiment.py,sha256=jzwmfzwKrZctz-_hN8VCVvQJT6_EzQWoNh0JSlOp7JE,8630
13
+ swanlab_mcp/tools/project.py,sha256=sA_uKbM8M5xB4llIGa7drViLcDZF4t5qWYAK4fGNp4c,5115
14
+ swanlab_mcp/tools/workspace.py,sha256=hXhH6T1EoJkBy1pkbPEMhO_ZSqNYxy_fEoRCj0SGq9w,1726
15
+ swanlab_mcp-0.0.1.dist-info/METADATA,sha256=XZpv8xHFZ5axR7geXPI8Qvx1Ujz5-7_axXzsc9MvCnA,3356
16
+ swanlab_mcp-0.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
+ swanlab_mcp-0.0.1.dist-info/entry_points.txt,sha256=zKTWAZz7UcDiZdVTNkgtxe7ESf50fvKpBacGJGJrnS0,53
18
+ swanlab_mcp-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ swanlab_mcp = swanlab_mcp.cli:main