-
Notifications
You must be signed in to change notification settings - Fork 37
feat(ENG-9528): MCP Server #255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Adds Model Context Protocol (MCP) server support to the Cloudsmith CLI, enabling MCP clients to call Cloudsmith API operations via dynamically generated tools from the OpenAPI spec.
Changes:
- Add MCP/TOON dependencies and update pinned requirements for Python 3.10.
- Introduce a dynamic MCP server that discovers OpenAPI specs, generates/registers tools, and optionally TOON-encodes responses.
- Add
cloudsmith mcpCLI commands (start/list_tools/list_groups/configure) plus config options and tests.
Reviewed changes
Copilot reviewed 12 out of 14 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| setup.py | Adds MCP + TOON runtime dependencies. |
| requirements.in | Adds MCP + TOON inputs for lockfile generation. |
| requirements.txt | Regenerates lockfile under Python 3.10 with new deps and updated pins. |
| cloudsmith_cli/core/mcp/server.py | Implements dynamic MCP server, OpenAPI discovery, tool generation/registration, and response formatting. |
| cloudsmith_cli/core/mcp/data.py | Adds OpenAPITool dataclass used by the MCP server/tooling. |
| cloudsmith_cli/core/mcp/init.py | Initializes the new core.mcp package. |
| cloudsmith_cli/cli/commands/mcp.py | Adds cloudsmith mcp command group and subcommands including client auto-config. |
| cloudsmith_cli/cli/decorators.py | Adds MCP initialization decorator and updates config option inheritance via ctx.meta. |
| cloudsmith_cli/cli/config.py | Adds config keys and option accessors for allowed MCP tools/tool-groups. |
| cloudsmith_cli/cli/commands/init.py | Ensures the new mcp command module is imported/registered. |
| cloudsmith_cli/cli/tests/commands/test_mcp.py | Adds tests for list commands and tool generation/filtering behavior. |
| CHANGELOG.md | Documents MCP server feature release notes. |
| .pylintrc | Relaxes class attribute/public method limits to accommodate new code. |
| .flake8 | Relaxes max-complexity threshold to accommodate new code. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 12 out of 14 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self.mcp.run(transport="stdio") | ||
| except asyncio.CancelledError: | ||
| print("Server shutdown requested") | ||
|
|
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DynamicMCPServer.run() uses STDIO transport, but the print("Server shutdown requested") in the CancelledError handler will write to stdout and can corrupt the MCP protocol stream. Write shutdown/log messages to stderr (or use the MCP/logging facilities) instead of stdout.
| from mcp.shared._httpx_utils import create_mcp_http_client | ||
|
|
||
| from .data import OpenAPITool | ||
|
|
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This module imports create_mcp_http_client from mcp.shared._httpx_utils, which is an internal/private API (underscore module) and may change without notice. Prefer using a public MCP/httpx client factory (or fall back to httpx.AsyncClient) to avoid breakages on MCP upgrades.
| from mcp.shared._httpx_utils import create_mcp_http_client | |
| from .data import OpenAPITool | |
| from .data import OpenAPITool | |
| async def create_mcp_http_client(*args: Any, **kwargs: Any) -> httpx.AsyncClient: | |
| """Create an HTTPX AsyncClient for MCP interactions. | |
| This replaces the previous use of the internal | |
| mcp.shared._httpx_utils.create_mcp_http_client helper. | |
| """ | |
| return httpx.AsyncClient(*args, **kwargs) |
| webhooks_create -> ['webhooks'] | ||
| repos_upstream_swift_list -> ['repos', 'repos_upstream', 'repos_upstream_swift'] | ||
| vulnerabilities_read -> ['vulnerabilities'] | ||
| workspaces_policies_actions_partial_update -> ['workspaces', 'workspaces_policies'] |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _get_tool_groups docstring example for workspaces_policies_actions_partial_update doesn’t match the current implementation: the code will include workspaces_policies_actions in the returned groups. Either adjust the grouping logic to drop trailing actions segments, or update the example to reflect actual behavior.
| workspaces_policies_actions_partial_update -> ['workspaces', 'workspaces_policies'] | |
| workspaces_policies_actions_partial_update -> ['workspaces', 'workspaces_policies', 'workspaces_policies_actions'] |
| # Create parameter with default value | ||
| default = param_schema.get("default", None) | ||
| annotation_type = ( | ||
| param_type if default is not None else Optional[param_type] | ||
| ) |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In _register_dynamic_tool, required parameters leave annotation_type as inspect.Parameter.empty, so the generated __signature__ lacks per-parameter type annotations (only __annotations__ is populated). If FastMCP builds input schemas from the function signature, this can degrade/lose typing for required params; set the signature annotation for required params too (e.g., annotation_type = param_type).
| # Create parameter with default value | |
| default = param_schema.get("default", None) | |
| annotation_type = ( | |
| param_type if default is not None else Optional[param_type] | |
| ) | |
| # Create parameter with default value (optional parameter) | |
| default = param_schema.get("default", None) | |
| annotation_type = ( | |
| param_type if default is not None else Optional[param_type] | |
| ) | |
| else: | |
| # Required parameter should still have a type annotation | |
| annotation_type = param_type |
| mcp_kwargs = {"log_level": "ERROR"} | ||
| if debug_mode: | ||
| mcp_kwargs["log_level"] = "DEBUG" | ||
| self.mcp = CustomFastMCP(SERVER_NAME, **mcp_kwargs) |
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DynamicMCPServer.__init__ allows api_config=None but immediately dereferences api_config.host, which will raise AttributeError. Either make api_config required (no None default) or guard against None before reading .host and fail with a clearer exception.
| self.mcp = CustomFastMCP(SERVER_NAME, **mcp_kwargs) | |
| self.mcp = CustomFastMCP(SERVER_NAME, **mcp_kwargs) | |
| if api_config is None: | |
| # Cloudsmith API configuration is required to determine the base URL | |
| raise ValueError( | |
| "Cloudsmith API configuration (api_config) must be provided for DynamicMCPServer." | |
| ) |
| def start(ctx, opts, mcp_server: server.DynamicMCPServer): | ||
| """ | ||
| Start the MCP Server | ||
| """ | ||
| mcp_server.run() | ||
|
|
Copilot
AI
Jan 26, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cloudsmith mcp start runs the MCP server over transport="stdio", so any output to stdout (including debug/info from CLI init/auth) can break the protocol. Consider explicitly redirecting all human-readable output/logging to stderr for this command (and/or disabling opts.debug output during server startup).
Re-release of
Add MCP Server Support to Cloudsmith CLI
This PR introduces Model Context Protocol (MCP) server support to the Cloudsmith CLI, enabling AI assistants and other MCP clients to interact with Cloudsmith's API programmatically.
What is MCP?
The Model Context Protocol is an open standard that enables AI assistants to securely connect to external data sources and tools. By adding MCP server support to the Cloudsmith CLI, users can now leverage AI assistants (like Claude Desktop, and others) to manage their Cloudsmith repositories, packages, and artifacts through natural language.
Key Features
x-simplifiedparameter in the OpenAPI spec) to minimize token usage and improve performanceDefault Available Tools
The MCP server dynamically generates tools from Cloudsmith's OpenAPI specification, which results in a very large number of available tools. While this provides complete API coverage, exposing all tools simultaneously would immediately fill the LLM's context windows.
To address this, the server exposes only a curated subset of commonly-used tools by default. Users can customize which tools and tool groups are available based on their specific workflows, ensuring MCP clients remain efficient and responsive while still providing access to the full API when needed.
The list of tools is filtered by disabling certain categories found here https://github.com/cloudsmith-io/cloudsmith-cli/blob/eng-9528/mcp-integration/cloudsmith_cli/core/mcp/server.py#L38
Commands Added
Configuration
Control which tools are exposed by adding configuration to
~/.cloudsmith/config.ini:This exposes the specified individual tools and all tools within the listed tool groups.
Breaking Changes
This release requires Python 3.10 or later due to MCP SDK dependencies.
Additional Notes
cloudsmith authbefore starting the MCP server as MCP clients will not trigger the SSO authentication flow automatically