Skip to content

Staged uploads: itglue_request_upload + upload_id (memory / Azure Blob) - #18

Merged
selic merged 1 commit into
mainfrom
feat/staged-uploads
Sep 16, 2026
Merged

selic merged 1 commit into
mainfrom
feat/staged-uploads

Conversation

@selic

@selic selic commented Sep 16, 2026

Copy link
Copy Markdown
Member

Problem

A client cannot get a real file into itglue_create_document_image / itglue_create_attachment:

  • content_base64 travels through the model — megabytes of base64 typed token by token, usually truncated (yesterday's NotIdentifiedByImageMagickError).
  • file_path reads the server's disk, which on a hosted deployment is not the client's.
  • url needs somewhere Azure-reachable to host the file first.

Design

itglue_request_upload returns a one-time slot: upload_id, a PUT URL, required headers and a ready-to-run curl. The client PUTs the bytes from a shell; the model then passes upload_id (new fourth source in binary-input.ts). Slots live 15 minutes, are bound to the requesting principal (role|label|keyHash), and are consumed once.

Two UploadStore backends (src/uploads/), selected by UPLOAD_STAGING:

mode PUT target for
memory (default on http) this server's PUT /upload/:id, same auth headers as /mcp; PUBLIC_BASE_URL builds the URL directly reachable deployments
azure-blob 15-min single-blob SAS on Azure Blob Storage; server downloads + deletes with its own creds (AZURE_STORAGE_CONNECTION_STRING, or AZURE_STORAGE_ACCOUNT via DefaultAzureCredential → user-delegation SAS) servers behind a gateway (our case)
off (default on stdio) — file_path already works locally

Azure SDKs are lazy-imported inside createAzureBlobBackend, so stdio/memory deployments never load them. Blob names are <principal-hash>/<upload_id>; malformed ids never touch storage; an hourly-ish sweep removes orphans (plus a 1-day lifecycle rule on our container).

Verified

  • 171 tests: memory store (TTL, principal binding, one-shot, size caps), Azure store with a fake backend, the /upload route on a real listener (401/403/404/409/400), upload_id resolution errors, the tool's curl output and tiering.
  • Live against ndrmcpstore from a local http server: SAS PUT → 201, itglue_create_document_image(upload_id) uploaded logo.png to doc 24183083 with the right name, second use rejected, blob gone, test image deleted.

Also: server instructions rewritten for the image/attachment split, README section, server.json env vars, CLAUDE.md.

🤖 Generated with Claude Code

…/ Azure Blob)

Tool arguments travel through the model, so content_base64 only works for
tiny files — a screenshot is megabytes of base64 the model has to reproduce
token by token, and file_path cannot reach a client's disk from a hosted
server. Staged uploads hand the client a place to PUT the real bytes:

  itglue_request_upload → { upload_id, url, headers, curl }
  client PUTs the file → itglue_create_document_image / itglue_create_attachment
  with upload_id, and the server reads the bytes itself.

Two backends behind one UploadStore (src/uploads/), chosen by UPLOAD_STAGING:
  memory      PUT to this server's /upload/:id with the same auth as /mcp
              (needs PUBLIC_BASE_URL). Default on http.
  azure-blob  PUT to a 15-minute single-blob SAS on Azure Blob Storage; the
              server later downloads and deletes the blob with its own
              credentials (connection string, or DefaultAzureCredential with a
              user-delegation SAS). For servers behind a gateway. SDKs are
              lazy-imported so other modes never load them.
Slots are bound to the principal (role|label|keyHash), expire after 15 min
and are consumed once; blob names are namespaced by a principal hash.

Verified live against ndrmcpstore: SAS PUT 201, server-side download, IT Glue
upload with the original file name, second use rejected, blob removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@selic
selic merged commit 6f825fd into main Sep 16, 2026
2 checks passed
@selic
selic deleted the feat/staged-uploads branch September 16, 2026 13:12
@selic selic mentioned this pull request Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant