Skip to main content
Glama

deploy_create_upload

Destructive

Start a direct-to-S3 upload for binary or large files. Use this instead of deploy_files for binaries (PDF, image, video, zip) or any file > ~1 MB. The MCP server has no access to the user's filesystem and deploy_files ships content inline through Lambda (capped at ~6 MB JSON-RPC payloads); this tool returns presigned S3 PUT URLs so the file bytes flow directly from your environment to S3, never through the MCP server.

Bundle into a zip first when: the upload contains more than 3 files OR any file is larger than ~1 MB. The fileWorker auto-extracts a single-zip upload after promotion, so subdirectories are preserved end-to-end and you avoid one PUT round-trip per file. Skip zipping only for the trivial single-small-file case (e.g. one HTML).

Bash bundle-and-deploy template (the agent should adapt fileNames and the cleanup prompt): TMP=$(mktemp -d) zip -r "$TMP/site.zip" index.html styles.css img/ # add every file/dir to deploy SIZE=$(stat -c%s "$TMP/site.zip" 2>/dev/null || stat -f%z "$TMP/site.zip") # 1. call deploy_create_upload with { siteId, files: [{ fileName: "site.zip", fileSize: $SIZE }] } # 2. PUT $TMP/site.zip to the returned URL(s) per the protocol below, capturing ETag # 3. call deploy_finalize with { siteId, versionId, completions: [...] } # 4. ASK THE USER: "Deploy succeeded. Remove temp folder $TMP? [y/N]" # Only run rm -rf "$TMP" after explicit confirmation; otherwise leave it for them to inspect.

Three-step protocol:

  1. Call this tool with { siteId, files: [{ fileName, fileSize }] }. Receive { versionId, files: { [fileName]: { uploadId, key, partUploadUrls: [{ part, url }], partSize, expiresAt } } }.

  2. For each file, slice the bytes into chunks of partSize and PUT each chunk to its partUploadUrls[i].url. Capture the ETag response header from every PUT - you will need it for finalize.

    Single-part (small file, one URL): curl -D - -X PUT --data-binary @file.pdf "$URL", then grep the response headers for ETag.

    Multi-part with dd (no temp files; reads each chunk in place): count=$(jq ".files["large.zip"].partUploadUrls | length" envelope.json) for i in $(seq 0 $((count-1))); do url=$(jq -r ".files["large.zip"].partUploadUrls[$i].url" envelope.json) etag=$(dd if=large.zip bs=5M skip=$i count=1 status=none
    | curl -sS -D - -X PUT --data-binary @- "$url"
    | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r') echo "{ "PartNumber": $((i+1)), "ETag": $etag }" >> parts.json done

    Multi-part in Python - prefer this over dd for files > ~50 MB (parallel PUTs, no temp files, cleaner error handling): import json, requests from concurrent.futures import ThreadPoolExecutor env = json.load(open("envelope.json")) info = env["files"]["large.zip"] part_size = info["partSize"] def upload_part(p): with open("large.zip", "rb") as f: # own handle per thread f.seek((p["part"] - 1) * part_size) r = requests.put(p["url"], data=f.read(part_size)) r.raise_for_status() return {"PartNumber": p["part"], "ETag": r.headers["ETag"]} with ThreadPoolExecutor(max_workers=5) as ex: # cap concurrency at 5 parts = list(ex.map(upload_part, info["partUploadUrls"]))

  3. Call deploy_finalize with { siteId, versionId, completions: [{ uploadId, key, parts: [{ ETag, PartNumber }] }] } for every multi-part file. Single-part uploads (uploadId is empty in the start response) need no completion entry.

The site must already exist - call create_site first if you do not have a siteId. Deploying overwrites existing content; confirm overwrite with the user first.

Anti-patterns - never do these to bypass a blocked or unavailable upload path:

  • Do NOT base64-embed, data-URI, or otherwise inline binary content (images, PDFs, video, fonts, zips) into HTML/CSS/JSON or any other deployed file. Inlining bloats the page, breaks browser caching, reships the bytes on every redeploy, and hides the real environment problem from the user.

  • Do NOT fall back to deploy_files for a binary that was meant for deploy_create_upload - the payload exceeds deploy_files' cap and the workaround is itself an anti-pattern.

  • Do NOT silently downscale, recompress, or otherwise mutate the user's file to fit a smaller path - ask first. When the upload path is unavailable, surface it to the user using the escalation order below.

Recovery escalation order when a PUT fails with a network/DNS error (connection refused, name resolution failure, timeout, sandbox-denied, proxy 403/407) OR your host environment has no HTTP-PUT capability at all (no bash/curl, no Python requests, no fetch). Do NOT retry blindly - a sandbox denial does not resolve on its own.

  1. Ask the user to enable egress (preferred fix). This is the cheapest option and unblocks all future uploads. Surface the exact sandbox configuration below and wait for the user to confirm before retrying. Most users can flip this setting in seconds; do not skip to step 2 without asking.

  2. If the user declines, cannot change the sandbox, or has no HTTP-PUT capability in-environment, present the failing presigned URL(s) verbatim and ask them to upload the file themselves (curl one-liner, browser, or any HTTP client they have). Provide a copy-pasteable command. Call deploy_finalize only after the user confirms each PUT returned 200 with an ETag (they will need to share the ETags back for multi-part uploads).

  3. Never route around the failure by base64-inlining the file into another deploy, recompressing, or falling back to deploy_files. See anti-patterns above.

Egress allowlist instructions for step 1:

  1. Read the failing URL's hostname. It will be either us.api.hostsmith.net, eu.api.hostsmith.net, or an S3 endpoint (copy verbatim from the failing URL).

  2. Infer the sandbox and give the user the exact configuration snippet:

    • Cursor (signal: cwd contains .cursor/, or CURSOR_* env vars) - edit ~/.cursor/sandbox.json (user scope) or <repo>/.cursor/sandbox.json (repo scope):

      { "networkPolicy": { "allow": ["us.api.hostsmith.net", "eu.api.hostsmith.net"] } }

      Reload Cursor.

    • Claude Code (signal: CLAUDE_CODE_* env vars, or ~/.claude/ settings present) - edit ~/.claude/settings.json:

      { "sandbox": { "enabled": true, "network": { "allowedDomains": ["us.api.hostsmith.net", "eu.api.hostsmith.net"] } } }

      If deniedDomains lists a matching host, remove it first - deny takes precedence.

    • Codex CLI (signal: ~/.codex/ settings, CODEX_* env vars) - edit ~/.codex/config.toml:

      [sandbox_workspace_write]
      network_access = true

      Codex has no per-host allowlist at this layer - this enables network for workspace-write mode globally. Risk is bounded by the upload token in the URL.

    • Unknown sandbox - tell the user: allow outbound HTTPS (port 443) to us.api.hostsmith.net and eu.api.hostsmith.net (and the failing S3 host if the URL points at S3) in whatever firewall/proxy they control. If a corporate HTTP proxy is in play, ensure CONNECT to those hostnames is permitted, or set NO_PROXY for direct routing.

  3. After the user confirms the change, retry the failed PUT. Tokens in the URL (ut=... for partition-host URLs, X-Amz-Signature for S3 URLs) remain valid for 1 hour from issuance, so re-running the same URL within that window works without re-calling deploy_create_upload.

Input Schema

NameRequiredDescriptionDefault
filesYesFiles to upload. Most user requests are a single file.
siteIdYesThe site ID to deploy to (from `list_sites` or `create_site`).
llm_modelYesThe exact model identifier you (the assistant) are running as, taken from your system prompt or environment (e.g. "claude-opus-4-8", "gpt-5.2"). Used for analytics only. If you do not know your model identifier with certainty, pass "unknown" — never guess.
partitionNoData partition the site lives in. Omit to use the user's home partition.
conversation_idNoPass the exact conversation_id from the server's previous response, unchanged. The server provides it on the first call — never invent one, and do not issue parallel tool calls until you have it. Keep passing the same conversation_id for the rest of the conversation, including after later user messages or on a different task; do not reset it when the user starts a new request.

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With only destructiveHint=true in annotations, the description carries the burden and exceeds it: it documents the full three-step protocol, the ETag capture requirement, single- vs multi-part handling, the 1-hour token expiry, overwrite behavior, and explicit anti-patterns. This is far more behavioral context than the annotations supply.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose and alternative are well front-loaded, but the definition runs extremely long, embedding multiple bash and Python templates, escalation ladders, and per-sandbox config snippets. Much of that material is genuinely useful yet disproportionately heavy for a tool description, diluting the selection-relevant signal.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex multi-step tool with no output schema, the description supplies the response envelope shape, the finalize handoff, the prerequisite chain, and failure recovery. Nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents every parameter (fileName, fileSize, siteId, partition, conversation_id). The description shows how to construct the `files` array and what the response envelope contains, but adds no per-parameter syntax or constraints beyond the schema. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

First sentence states a specific verb and resource ('Start a direct-to-S3 upload for binary or large files') and immediately names the sibling it replaces (`deploy_files`) along with the condition that selects it. An agent can distinguish it from `deploy_files`/`deploy_finalize` without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use (binaries, >1 MB), when-not (trivial single small file), the zipping rule (>3 files or >1 MB), prerequisites (site must exist, call create_site first), and a required user-confirmation step for overwrite. Alternatives and exclusions are fully spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.