Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 148 additions & 102 deletions src/mcp_agent/cli/cloud/commands/deploy/main.py

Large diffs are not rendered by default.

93 changes: 91 additions & 2 deletions src/mcp_agent/cli/cloud/commands/deploy/wrangler_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
import tempfile
import textwrap
from pathlib import Path
import json

from rich.progress import Progress, SpinnerColumn, TextColumn

from mcp_agent.cli.config import settings
from mcp_agent.cli.core.constants import MCP_SECRETS_FILENAME
from mcp_agent.cli.utils.ux import console, print_error, print_warning
from mcp_agent.cli.utils.ux import console, print_error, print_warning, print_info
from mcp_agent.cli.utils.git_utils import (
get_git_metadata,
compute_directory_fingerprint,
utc_iso_now,
)

from .constants import (
CLOUDFLARE_ACCOUNT_ID,
Expand Down Expand Up @@ -195,13 +201,96 @@ def ignore_patterns(_path, names):
# Rename in place
file_path.rename(py_path)

# Create temporary wrangler.toml
# Collect deployment metadata (git if available, else workspace hash)
git_meta = get_git_metadata(project_dir)
deploy_source = "git" if git_meta else "workspace"
meta_vars = {
"MCP_DEPLOY_SOURCE": deploy_source,
"MCP_DEPLOY_TIME_UTC": utc_iso_now(),
}
if git_meta:
meta_vars.update(
{
"MCP_DEPLOY_GIT_COMMIT": git_meta.commit_sha,
"MCP_DEPLOY_GIT_SHORT": git_meta.short_sha,
"MCP_DEPLOY_GIT_BRANCH": git_meta.branch or "",
"MCP_DEPLOY_GIT_DIRTY": "true" if git_meta.dirty else "false",
}
)
# Friendly console hint
dirty_mark = "*" if git_meta.dirty else ""
print_info(
f"Deploying from git commit {git_meta.short_sha}{dirty_mark} on branch {git_meta.branch or '?'}"
)
else:
# Compute a cheap fingerprint (metadata-based) of the prepared project
bundle_hash = compute_directory_fingerprint(
temp_project_dir,
ignore_names={
".git",
"logs",
"__pycache__",
"node_modules",
"venv",
MCP_SECRETS_FILENAME,
},
)
meta_vars.update({"MCP_DEPLOY_WORKSPACE_HASH": bundle_hash})
print_info(f"Deploying from non-git workspace (hash {bundle_hash[:12]}…)")

# Write a breadcrumb file into the project so it ships with the bundle.
# Use a Python file for guaranteed inclusion without renaming.
breadcrumb = {
"version": 1,
"app_id": app_id,
"deploy_time_utc": meta_vars["MCP_DEPLOY_TIME_UTC"],
"source": meta_vars["MCP_DEPLOY_SOURCE"],
}
if git_meta:
breadcrumb.update(
{
"git": {
"commit": git_meta.commit_sha,
"short": git_meta.short_sha,
"branch": git_meta.branch,
"dirty": git_meta.dirty,
"tag": git_meta.tag,
"message": git_meta.commit_message,
}
}
)
else:
breadcrumb.update(
{"workspace_fingerprint": meta_vars["MCP_DEPLOY_WORKSPACE_HASH"]}
)

breadcrumb_py = textwrap.dedent(
"""
# Auto-generated by mcp-agent deploy. Do not edit.
# Contains deployment metadata for traceability.
import json as _json
BREADCRUMB = %s
BREADCRUMB_JSON = _json.dumps(BREADCRUMB, separators=(",", ":"))
__all__ = ["BREADCRUMB", "BREADCRUMB_JSON"]
"""
).strip() % (json.dumps(breadcrumb, indent=2))

(temp_project_dir / "mcp_deploy_breadcrumb.py").write_text(breadcrumb_py)

# Create temporary wrangler.toml with [vars] carrying deploy metadata
# Use TOML strings and keep values simple/escaped; also include a compact JSON blob
meta_json = json.dumps(meta_vars, separators=(",", ":"))
vars_lines = ["[vars]"] + [f'{k} = "{v}"' for k, v in meta_vars.items()]
vars_lines.append(f'MCP_DEPLOY_META = """{meta_json}"""')

wrangler_toml_content = textwrap.dedent(
f"""
name = "{app_id}"
main = "{main_py}"
compatibility_flags = ["python_workers"]
compatibility_date = "2025-06-26"

{os.linesep.join(vars_lines)}
"""
).strip()

Expand Down
6 changes: 4 additions & 2 deletions src/mcp_agent/cli/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ def run_async(coro):
raise


def load_user_app(script_path: Path | None, settings_override: Optional[Settings] = None) -> MCPApp:
def load_user_app(
script_path: Path | None, settings_override: Optional[Settings] = None
) -> MCPApp:
"""Import a user script and return an MCPApp instance.

Resolution order within module globals:
1) variable named 'app' that is MCPApp
2) callable 'create_app' or 'get_app' that returns MCPApp
3) first MCPApp instance found in globals

Args:
script_path: Path to the Python script containing the MCPApp
settings_override: Optional settings to override the app's configuration
Expand Down
8 changes: 5 additions & 3 deletions src/mcp_agent/cli/mcp_app/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ async def get_app_id_by_name(self, name: str) -> Optional[str]:
async def deploy_app(
self,
app_id: str,
deployment_metadata: Optional[Dict[str, Any]] = None,
) -> MCPApp:
"""Deploy an MCP App via the API.

Expand All @@ -326,9 +327,10 @@ async def deploy_app(
if not app_id or not is_valid_app_id_format(app_id):
raise ValueError(f"Invalid app ID format: {app_id}")

payload = {
"appId": app_id,
}
payload: Dict[str, Any] = {"appId": app_id}
if deployment_metadata:
# Tentative field; include only when requested
payload["deploymentMetadata"] = deployment_metadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to wait for www side to land and get deployed before landing this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting that the server-side changes have been deployed so this is good to go


# Use a longer timeout for deployments
deploy_timeout = 300.0
Expand Down
Loading
Loading