Skip to content

Conversation

@BartoszBlizniak
Copy link
Member

@BartoszBlizniak BartoszBlizniak commented Jan 23, 2026

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

  • Dynamic Tool Generation: MCP tools are automatically generated from Cloudsmith's OpenAPI specification, ensuring the server stays in sync with the API
  • Optimized API Responses: JSON responses are encoded to TOON format and use sparse fieldsets (taken from the x-simplified parameter in the OpenAPI spec) to minimize token usage and improve performance
  • Configurable Tool Exposure: Control which tools and tool groups are exposed to prevent context window overflow in MCP clients
  • Seamless Authentication: Authentication is handled by the CLI using existing Cloudsmith API keys or SSO credentials
  • Multi-Profile Support: Configure separate MCP server instances for different Cloudsmith profiles

Default 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

# Start the MCP server
$ cloudsmith mcp start

# List available tools (use -a to show all tools)
$ cloudsmith mcp list_tools

# List available tool groups (use -a to show all groups)
$ cloudsmith mcp list_groups

# Auto-configure MCP clients (use -P to configure for a specific profile)
$ cloudsmith mcp configure

Configuration

Control which tools are exposed by adding configuration to ~/.cloudsmith/config.ini:

mcp_allowed_tools=workspaces_policies_simulate_list
mcp_allowed_tool_groups=metrics

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

  • Authentication uses the existing CLI credentials (API keys or SSO)
  • For SSO authentication, the auth flow must be completed via cloudsmith auth before starting the MCP server as MCP clients will not trigger the SSO authentication flow automatically

@BartoszBlizniak BartoszBlizniak marked this pull request as ready for review January 23, 2026 18:25
@BartoszBlizniak BartoszBlizniak requested a review from a team as a code owner January 23, 2026 18:25
Copilot AI review requested due to automatic review settings January 23, 2026 18:25
Copy link
Contributor

Copilot AI left a 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 mcp CLI 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.

Copy link
Contributor

Copilot AI left a 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.

Comment on lines +768 to +771
self.mcp.run(transport="stdio")
except asyncio.CancelledError:
print("Server shutdown requested")

Copy link

Copilot AI Jan 26, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +16
from mcp.shared._httpx_utils import create_mcp_http_client

from .data import OpenAPITool

Copy link

Copilot AI Jan 26, 2026

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.

Suggested change
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)

Copilot uses AI. Check for mistakes.
webhooks_create -> ['webhooks']
repos_upstream_swift_list -> ['repos', 'repos_upstream', 'repos_upstream_swift']
vulnerabilities_read -> ['vulnerabilities']
workspaces_policies_actions_partial_update -> ['workspaces', 'workspaces_policies']
Copy link

Copilot AI Jan 26, 2026

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.

Suggested change
workspaces_policies_actions_partial_update -> ['workspaces', 'workspaces_policies']
workspaces_policies_actions_partial_update -> ['workspaces', 'workspaces_policies', 'workspaces_policies_actions']

Copilot uses AI. Check for mistakes.
Comment on lines +369 to +373
# Create parameter with default value
default = param_schema.get("default", None)
annotation_type = (
param_type if default is not None else Optional[param_type]
)
Copy link

Copilot AI Jan 26, 2026

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).

Suggested change
# 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

Copilot uses AI. Check for mistakes.
mcp_kwargs = {"log_level": "ERROR"}
if debug_mode:
mcp_kwargs["log_level"] = "DEBUG"
self.mcp = CustomFastMCP(SERVER_NAME, **mcp_kwargs)
Copy link

Copilot AI Jan 26, 2026

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.

Suggested change
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."
)

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +49
def start(ctx, opts, mcp_server: server.DynamicMCPServer):
"""
Start the MCP Server
"""
mcp_server.run()

Copy link

Copilot AI Jan 26, 2026

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).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants