Skip to content

Commit a920ca9

Browse files
initial pass at generating mcp from all cli commands on the fly
1 parent 1a1ae59 commit a920ca9

File tree

3 files changed

+422
-0
lines changed

3 files changed

+422
-0
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ docs = [
4545
"pymdown-extensions",
4646
"mkdocs-macros-plugin"
4747
]
48+
mcp = [
49+
"mcp>=1.3.0; python_version >= '3.10'"
50+
]
4851

4952
[project.urls]
5053
Repository = "http://github.com/posit-dev/rsconnect-python"

rsconnect/main.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3032,6 +3032,117 @@ def system_caches_delete(
30323032
ce.delete_runtime_cache(language, version, image_name, dry_run)
30333033

30343034

3035+
@cli.group(
3036+
name="mcp",
3037+
no_args_is_help=True,
3038+
short_help="Run rsconnect-python as an MCP (Model Context Protocol) server.",
3039+
help="Run rsconnect-python as an MCP (Model Context Protocol) server. "
3040+
"This allows AI assistants and other MCP clients to interact with "
3041+
"Posit Connect servers through standardized tool calls. "
3042+
"All rsconnect CLI commands are automatically exposed as MCP tools."
3043+
)
3044+
def mcp():
3045+
pass
3046+
3047+
3048+
@mcp.command(
3049+
name="server",
3050+
short_help="Start the MCP server.",
3051+
help="Start the MCP server with the specified transport method."
3052+
)
3053+
@click.option(
3054+
"--stdio",
3055+
is_flag=True,
3056+
default=True,
3057+
help="Use stdio transport for communication with MCP clients."
3058+
)
3059+
@click.option(
3060+
"--include",
3061+
help="Comma-separated list of tool names to include in MCP exposure (if not specified, all tools are included)."
3062+
)
3063+
@click.option(
3064+
"--exclude",
3065+
help="Comma-separated list of tool names to exclude from MCP exposure."
3066+
)
3067+
@click.option("--verbose", "-v", count=True, help="Enable verbose output. Use -vv for very verbose (debug) output.")
3068+
@cli_exception_handler
3069+
def mcp_server(stdio: bool, include: Optional[str], exclude: Optional[str], verbose: int):
3070+
set_verbosity(verbose)
3071+
3072+
if not stdio:
3073+
raise click.UsageError("Must specify --stdio transport method")
3074+
3075+
try:
3076+
import asyncio
3077+
3078+
from .mcp import ClickToMCPConverter, RSConnectMCPServer
3079+
3080+
# Parse include/exclude lists
3081+
include_tools = []
3082+
exclude_tools = []
3083+
3084+
if include:
3085+
include_tools = [tool.strip() for tool in include.split(",")]
3086+
if exclude:
3087+
exclude_tools = [tool.strip() for tool in exclude.split(",")]
3088+
3089+
converter = ClickToMCPConverter(cli, include_tools=include_tools, exclude_tools=exclude_tools)
3090+
3091+
logger.info("Starting MCP server with stdio transport...")
3092+
logger.info("Automatically discovering CLI commands as MCP tools...")
3093+
3094+
server = RSConnectMCPServer(cli, converter)
3095+
logger.info(f"Discovered {len(server.converter.tools)} tools")
3096+
3097+
if include_tools:
3098+
logger.info(f"Included tools: {', '.join(include_tools)}")
3099+
if exclude_tools:
3100+
logger.info(f"Excluded tools: {', '.join(exclude_tools)}")
3101+
3102+
asyncio.run(server.run())
3103+
3104+
except ImportError:
3105+
click.secho(
3106+
"MCP dependencies not installed. Install with: pip install mcp",
3107+
fg="red",
3108+
err=True
3109+
)
3110+
sys.exit(1)
3111+
except KeyboardInterrupt:
3112+
logger.info("MCP server stopped by user")
3113+
except Exception as e:
3114+
logger.error(f"MCP server error: {e} \n{traceback.format_exc()}")
3115+
sys.exit(1)
3116+
3117+
3118+
@mcp.command(
3119+
name="list",
3120+
short_help="List available MCP tools.",
3121+
help="List all CLI commands that would be exposed as MCP tools."
3122+
)
3123+
@click.option("--verbose", "-v", count=True, help="Enable verbose output. Use -vv for very verbose (debug) output.")
3124+
@cli_exception_handler
3125+
def mcp_list(verbose: int):
3126+
set_verbosity(verbose)
3127+
3128+
try:
3129+
from .mcp import ClickToMCPConverter
3130+
3131+
converter = ClickToMCPConverter(cli)
3132+
3133+
click.echo(f"Discovered {len(converter.tools)} MCP tools:")
3134+
for tool in converter.tools:
3135+
click.echo(f" - {tool.name}: {tool.description}")
3136+
3137+
except ImportError:
3138+
click.secho(
3139+
"MCP dependencies not installed. Install with: pip install mcp",
3140+
fg="red",
3141+
err=True
3142+
)
3143+
sys.exit(1)
3144+
3145+
30353146
if __name__ == "__main__":
30363147
cli()
30373148
click.echo()

0 commit comments

Comments
 (0)