.vscode — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited .vscode (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A Model Context Protocol (MCP) server for DuploCloud. Dynamically discovers duploctl commands and exposes them as MCP tools so AI agents and compatible clients can query infrastructure state and perform auditable actions.
Built on FastMCP and the duplocloud-client Python package. Installs as a duploctl plugin — no separate command needed.
duploctl resource via entry points; run with duploctl mcpduploctl @Command methods are registered as MCP tools at startupconfig tool and GET /config route showing live server state@custom_tool and @custom_route decorators with context injectionDUPLO_HOST, DUPLO_TOKEN)DUPLO_TENANT)pip install duplocloud-mcpFor pinned version installs and alternative methods (GitHub release artifact, git tag), see the release notes.
For development:
git clone https://github.com/duplocloud/mcp.git
cd mcp
pip install -e ".[test]"Set your DuploCloud credentials and start the server:
export DUPLO_HOST="https://your-portal.duplocloud.net"
export DUPLO_TOKEN="your-token"
export DUPLO_TENANT="your-tenant"
duploctl mcpBy default the server starts in stdio transport with compact mode (5 tools). To use HTTP:
duploctl mcp --transport httpVerify it's running:
curl http://localhost:8000/health
# {"status":"healthy","service":"duplocloud-mcp"}
curl http://localhost:8000/config
# Full server configuration including registered toolsEvery setting can be provided as a CLI argument or an environment variable. CLI arguments take precedence.
| Flag | Env Variable | Default | Description |
|---|---|---|---|
-H | DUPLO_HOST | -- | DuploCloud portal URL (required) |
-t | DUPLO_TOKEN | -- | Authentication token (required) |
-T | DUPLO_TENANT | -- | Tenant name (optional but recommended) |
-tp, --transport | DUPLO_MCP_TRANSPORT | stdio | Transport protocol (stdio or http) |
-mp, --port | DUPLO_MCP_PORT | 8000 | Port for HTTP transport |
--resource-filter | DUPLO_MCP_RESOURCE_FILTER | .* | Regex filter for resource names |
--command-filter | DUPLO_MCP_COMMAND_FILTER | .* | Regex filter for command names |
--tool-mode | DUPLO_MCP_TOOL_MODE | compact | Tool registration mode (compact or expanded) |
All duploctl global arguments (-H, -t, -T, etc.) are also accepted and passed through to the DuploCloud client. The output flag (-o) is ignored as it does not apply in the MCP context. The query flag (-q) is available per tool call via the execute tool's query parameter.
The --tool-mode flag controls how duploctl commands are exposed as MCP tools.
Registers one tool per resource+command combination, plus the config tool.
duploctl mcp --tool-mode expandedProduces tools like tenant_create, tenant_find, service_list, etc. Each tool has its own input schema -- commands with Pydantic models (e.g. tenant_create) get full field-level schemas so the LLM sees every field name, type, and constraint.
Use --resource-filter and --command-filter with expanded mode to keep the tool count manageable:
duploctl mcp --tool-mode expanded --resource-filter "tenant|service" --command-filter "list|find|create"Default. Registers five tools total, inspired by the duploctl bitbucket pipe.
duploctl mcp --tool-mode compact| Tool | Purpose |
|---|---|
resources | List available resources (filtered) |
explain_resource | List commands available on a resource |
explain_command | Show arguments and body model schema for a specific command |
execute | Run any duploctl command |
config | Display current MCP server configuration |
The intended LLM workflow:
The execute tool accepts name, args, body, query, and wait parameters. It dispatches through the same DuploClient path as the CLI, so model validation, filtering, and formatting all work the same way.
Filters use Python regex with fullmatch semantics -- the entire name must match the pattern.
Expose only specific resource types:
# Only tenant tools
duploctl mcp --resource-filter "tenant"
# Tenant and service tools
duploctl mcp --resource-filter "tenant|service"
# Everything related to batch
duploctl mcp --resource-filter "batch_.*"
# All resources (default)
duploctl mcp --resource-filter ".*"Via environment variable:
export DUPLO_MCP_RESOURCE_FILTER="tenant|service|s3"
duploctl mcpExpose only specific operations across all resources:
# Read-only -- only list and find commands
duploctl mcp --command-filter "list|find"
# Only create and delete
duploctl mcp --command-filter "create|delete"Filters compose as an intersection. This exposes only list and find for tenant and service:
duploctl mcp \
--resource-filter "tenant|service" \
--command-filter "list|find"Result: tenant_list, tenant_find, service_list, service_find.
For most MCP clients (Claude Code, VS Code, etc.), use stdio transport. Add to your .mcp.json (project root) or .vscode/mcp.json:
{
"mcpServers": {
"duploctl": {
"command": "duploctl",
"args": ["mcp"],
"env": {
"DUPLO_HOST": "https://your-portal.duplocloud.net",
"DUPLO_TOKEN": "your-token",
"DUPLO_TENANT": "your-tenant"
}
}
}
}For clients that connect over HTTP (persistent server):
duploctl mcp --transport http{
"mcpServers": {
"duploctl": {
"url": "http://localhost:8000/mcp",
"type": "http"
}
}
}{
"mcpServers": {
"duploctl": {
"command": "duploctl",
"args": ["mcp", "--resource-filter", "tenant|service"],
"env": {
"DUPLO_HOST": "https://your-portal.duplocloud.net",
"DUPLO_TOKEN": "your-token",
"DUPLO_TENANT": "your-tenant"
}
}
}
}The @custom_tool and @custom_route decorators let you add ad-hoc tools and HTTP routes that receive a Ctx object with the DuploCloud client and server config injected.
from duplocloud.mcp.ctx import Ctx, custom_tool, custom_route
from starlette.requests import Request
from starlette.responses import JSONResponse
# A plain function that does the work
def get_status(ctx: Ctx) -> dict:
tenants = ctx.duplo.load("tenant").list()
return {
"tenant_count": len(tenants),
"tools": ctx.tools,
}
# Expose as an MCP tool
@custom_tool(name="status", description="Get environment status.")
def status_tool(ctx: Ctx) -> dict:
return get_status(ctx)
# Expose the same logic as an HTTP route
@custom_route("/status", methods=["GET"])
async def status_route(ctx: Ctx, request: Request):
return JSONResponse(get_status(ctx))The ctx parameter is injected automatically and hidden from the tool's input schema -- MCP clients never see it.
Decorators accept an optional mode parameter to conditionally register based on server mode:
@custom_tool(name="debug_info", mode="debug")
def debug_info(ctx: Ctx) -> dict:
"""Only registered when the server runs in debug mode."""
return {"config": ctx.config}| Endpoint | Method | Description |
|---|---|---|
/mcp | POST | MCP protocol endpoint (StreamableHTTP) |
/health | GET | Health check for load balancers |
/config | GET | Live server configuration and registered tools |
The Docker image uses duploctl mcp as its entrypoint with --transport http as the default CMD. Pass arguments at runtime to override:
# Default (http transport, compact mode)
docker run -e DUPLO_HOST=... -e DUPLO_TOKEN=... -e DUPLO_TENANT=... duplocloud-mcp
# stdio transport
docker run -e DUPLO_HOST=... -e DUPLO_TOKEN=... -e DUPLO_TENANT=... duplocloud-mcp --transport stdio
# Expanded mode with filters
docker run -e DUPLO_HOST=... -e DUPLO_TOKEN=... -e DUPLO_TENANT=... duplocloud-mcp \
--tool-mode expanded --resource-filter "tenant|service"duplocloud/mcp/
__main__.py # Legacy entrypoint (use duploctl mcp instead)
app.py # FastMCP instance and health route
compact_tools.py # Compact mode tools (execute, explain, resources)
config_display.py # Built-in config tool and route
ctx.py # Ctx dataclass, @custom_tool, @custom_route
server.py # DuploCloudMCP resource plugin and lifecycle coordinator
tools.py # ToolRegistrar (duploctl -> MCP tool conversion)
utils.py # Docstring template resolution
tests/
conftest.py # Shared fixtures
test_ctx.py # Ctx, decorators, drain functions
test_custom.py # Context injection, register_custom, build_config
test_filters.py # Regex filter matching behavior
test_modes.py # Expanded and compact mode tests
test_server.py # DuploCloudMCP init, filter application, self-exclusion
test_tools.py # ToolRegistrar param building and wrapper constructionpip install -e ".[test]"
pytest~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.