Skip to content

feat(sdks): MCP tools for uploads, an x-mcp-name override, and files - #757

Merged
field123 merged 6 commits into
mainfrom
feat/mcp-tools-uploads-files
Oct 19, 2026
Merged

field123 merged 6 commits into
mainfrom
feat/mcp-tools-uploads-files

Conversation

@field123

@field123 field123 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #750. Part of #735.

Summary

The plugin now generates tools for uploads and takes an x-mcp-name override. The files package ships ./mcp with 4 tools.

 operation body
   application/json          → tool
+  multipart/form-data       → tool; each binary field is a data: URI string (format: uri, x-mcp-file: { maxSize })
+                              plus an optional <field>_name string for the file's name
   other (form-encoded, …)   → no tool

 handler(args)
+  if upload: decodeDataUris(args.body, fields, x-mcp-max-file-bytes)
+    not a data: URI / malformed / over the limit → isError, nothing sent
+    never downloads a URL
   call the SDK function → toToolResult

 tool name
-  <api>_<snake_case_function>
+  x-mcp-name ?? <api>_<snake_case_function>   (operationIds unchanged)
  • Follows the latest MCP File Uploads drafts. SEP-2356 and its follow-up SEP-2631 were closed unmerged. The working group's extension repo has no spec yet. Their latest text asks for: a data: URI in a format: uri string, marked x-mcp-file with maxSize; the filename in a separate string argument, not a name= parameter; any other scheme rejected and never fetched; and failures returned as isError results naming the field. The <field>_name argument wins; name= is still accepted for the earlier draft. No in-house prior art existed: epcc-api-mcp handles no uploads, and the TypeScript SDK prototype adds only types.
  • x-mcp-max-file-bytes is required on any operation that uploads a file. The plugin throws without it, and the field's description states the limit. The rollout notes didn't say where the limit comes from, and the APIs document different ones (files 8MB, pim 50MB, inventories and subscriptions none), so each operation states its own.
  • files createAFile has a limit of 8,388,608 bytes, from the spec's "8MB by default". A store with a raised limit still gets 8 MiB from this tool.
  • files gets a new files-standalone@v1 bundle and generates from it, so its overrides live in the spec. The bundle alone left sdk.gen.ts, types.gen.ts and zod.gen.ts byte-identical.
  • The support file copied into catalogs, rule-promotions and cart-checkout-order gains the two upload helpers. Their tools are unchanged, and they get patch changesets.

One deliberate change to types.gen.ts

The fix also belongs in the service: files.svc !687 types the two parameters as integers in its spec. The service parses them with strconv.Atoi. Files is the only Elastic Path spec that declares them as string with format: int64. Once that change reaches the published spec, the correction file here can go.

The spec types page[limit] and page[offset] on getAllFiles as string with format: int64. The zod schemas parse them as numbers, so the generated handler didn't type-check (rollout note 2). files_service_corrections.yaml types them as integers, which changes 4 lines of types.gen.ts:

-export type PageOffset = string
-export type PageLimit = string
+export type PageOffset = number
+export type PageLimit = number

sdk.gen.ts and zod.gen.ts are unchanged, and the request on the wire is the same. A TypeScript caller passing "10" now gets a type error, so the changeset says so. Confirmed in review: keep the correction here and fix the service spec.

Files package choices

  • Risk: no overrides. GET is read, POST is create, DELETE is delete. No POST only reads, and none erases data.
  • Titles: none changed. The summaries ("Create a File") are accurate.
  • Credentials: no files tool returns one.
  • Tool list size: 4,388 characters in total. files_get_all_files is 37.6% and files_create_a_file is 26.5%, both over a quarter. With 4 tools that's expected and isn't the shared-schema problem: get_all_files is large because of its filter description, create_a_file because of the upload field.

Evidence

  • Before: mcp-plugin.test.ts with the new fixture failed: getWidgetHistoryEntryRevisionAttachmentByIdentifiers gives the invalid tool name …, and the multipart operation had no tool.
    After: 45 plugin tests pass. They cover a multipart part carrying the decoded bytes, name and type; percent-encoded data; a size at and over the limit; a malformed URI, invalid base64 and a URL, each sending nothing; an optional body; a missing or invalid x-mcp-max-file-bytes; an x-mcp-name that's valid, too long or has a space; the x-mcp-file marker; and the <field>_name argument, including its precedence over name= and a body that already has such a field.
  • Suites: files 48, catalogs 120, rule-promotions 56, cart-checkout-order 128, all passing with no type errors. The generation tests show regenerating changes nothing else.
  • Live, read-only (approved store): all 4 tools registered on a real McpServer. Through a real Client, files_get_all_files with page[limit]: 2 returned 2 files, and files_get_a_file returned the first one.
  • Live upload, run by Robert (approved store, through a real Client):
    • An oversized file, a URL and invalid base64 each came back as an isError result, and no request was sent.
    • files_create_a_file uploaded a 70-byte PNG. The API kept the name, type image/png and size 70.
    • files_get_a_file read it back, and the file link returned the same bytes.
    • files_delete_a_file removed it, and a read afterwards returned 404.
    • Run twice: first with the name as a name= parameter, then with the new file_name field. Both times the API stored the name sent.

Merge Danger

Door: two-way

Blast Radius: small

New ./mcp subpath and an optional peer, so the main entry is untouched apart from the getAllFiles paging types above. The plugin change affects only packages that adopt it; the three that already have are regenerated byte-identically apart from the support copy.

…p-name

An operation with a multipart/form-data body is now a tool. Each top-level
binary field takes a data: URI string (RFC 2397) with format: uri. The handler
decodes it into a File with the type and name from the URI and checks its size
against the operation's x-mcp-max-file-bytes override, which the plugin
requires. A malformed URI, a URL, or a file over the limit returns an isError
result and sends nothing; the tool never downloads a URL. Other non-JSON
bodies, such as form-encoded ones, still get no tool.

An x-mcp-name override replaces the generated tool name, for names over 64
characters. Operation IDs, and so the SDK function names, stay as they are.
The plugin still throws on an invalid name, and now suggests x-mcp-name when
the generated one is too long.

The support file copied into catalogs, rule-promotions and
cart-checkout-order gains the two upload helpers; their tools are unchanged.
The files package ships a ./mcp export with 4 tools, generated from a new
files-standalone@v1 bundle that carries a written description for each.
files_create_a_file uploads a data: URI of at most 8,388,608 bytes, the
API's default 8MB limit.

page[limit] and page[offset] on getAllFiles were typed string in the spec
while the zod schemas parse them as numbers, so the generated handler did not
type-check. A spec correction types them as integers, which changes those two
fields in types.gen.ts from string to number. sdk.gen.ts and zod.gen.ts are
unchanged.
@vercel

vercel Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

6 Skipped Deployments
Project Deployment Actions Updated
commerce-essentials Ignored Ignored Preview Oct 19, 2026 10:43am UTC
composable-frontend-algolia Ignored Ignored Oct 19, 2026 10:43am UTC
composable-frontend-core Ignored Ignored Preview Oct 19, 2026 10:43am UTC
composable-frontend-docs Ignored Ignored Preview Oct 19, 2026 10:43am UTC
composable-frontend-simple Ignored Ignored Preview Oct 19, 2026 10:43am UTC
composable-frontend-subscriptions Ignored Ignored Preview Oct 19, 2026 10:43am UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 65f947c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@epcc-sdk/sdks-cart-checkout-order Patch
@epcc-sdk/sdks-catalogs Patch
@epcc-sdk/sdks-files Minor
@epcc-sdk/rule-promotions Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Each data: URI file field now carries x-mcp-file with its maxSize, the
file-input marker in the MCP File Uploads Working Group's drafts (SEP-2356,
SEP-2631), so a host that recognises it can offer a file picker. The limit is
the operation's x-mcp-max-file-bytes.
The latest SEP-2356 draft drops the name= parameter from data: URIs and asks
servers that need the file name to declare a separate string argument. Each
upload field now has an optional <field>_name next to it, such as file_name.
The handler names the File from it, else from a name= parameter (still
accepted for the earlier draft), else after the field, and does not send it
to the API. The plugin throws if the body already has a <field>_name.
A File uploads section in the SDKs README names the sources: the rollout
notes on #735, SEP-2356 and SEP-2631 from the MCP File Uploads Working Group
(both closed unmerged, work continuing in experimental-ext-file-uploads),
RFC 2397 and SEP-1303. It compares what the latest drafts ask for with what
the tool does, lists where it departs, and says how it was verified,
including the live upload against a store. The files README links to it.
dataUriFields returned the body schema's own type, so <field>_name existed at
runtime but not in TypeScript: a direct handler call with file_name was an
excess-property error, and a wrapping handler could not read it. The schema
type now includes each <field>_name as an optional string, and the decoded
body type drops it before the SDK function is called. A files type test
covers both.
@field123
field123 merged commit 7cd1177 into main Oct 19, 2026
7 checks passed
@field123
field123 deleted the feat/mcp-tools-uploads-files branch October 19, 2026 11:22
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.

MCP tools: file uploads, tool-name override, and files

1 participant