Problem
The HTTP protocol plugin currently cannot describe APIs that accept multipart/form-data requests. Setting content_type to "multipart/form-data" today would send the entire body_field as the raw body, which is incorrect — multipart requires boundary-delimited parts, each with their own Content-Disposition and optional Content-Type headers.
multipart/form-data is extremely common in real-world APIs:
- File uploads (images, documents, videos)
- Mixed payloads (file + metadata fields in one request)
- Form submissions with binary data
Current body-handling model
tool arguments
├─ URL path params → substituted into URL template
├─ body_field → single argument becomes raw request body
├─ header_fields → become HTTP headers
└─ (remaining) → query parameters
This model assumes a single, flat body. Multipart needs multiple named parts, each potentially with its own content type and encoding.
Proposed Design
Add a new optional field multipart_fields to HttpCallTemplate. When present, the request is encoded as multipart/form-data and the body_field / content_type fields are ignored.
New field
multipart_fields: Optional[Dict[str, MultipartFieldConfig]]
Where MultipartFieldConfig is:
{
"type": "file | field",
"content_type": "<mime-type>",
"filename": "<filename or {arg_name} template>"
}
type (required): "file" for binary/file parts, "field" for plain text form fields.
content_type (optional): The Content-Type of this part. Defaults to application/octet-stream for file, omitted for field.
filename (optional): The filename for the Content-Disposition header. Only meaningful for type: "file". Supports {arg_name} substitution from other tool arguments.
Any tool argument not listed in multipart_fields, header_fields, or matched by URL path params goes to query parameters (same as today).
Argument routing (updated)
tool arguments
├─ URL path params → substituted into URL template
├─ multipart_fields → become multipart form parts (NEW)
├─ header_fields → become HTTP headers
└─ (remaining) → query parameters
multipart_fields and body_field are mutually exclusive. If both are present, that is a validation error.
Examples
Image upload with metadata
{
"name": "upload_image",
"call_template_type": "http",
"url": "https://api.example.com/images/upload",
"http_method": "POST",
"multipart_fields": {
"image": {
"type": "file",
"content_type": "image/png",
"filename": "{original_filename}"
},
"description": {
"type": "field"
}
},
"auth": {
"auth_type": "api_key",
"api_key": "Bearer ${API_KEY}",
"var_name": "Authorization",
"location": "header"
}
}
Resulting HTTP request:
POST /images/upload HTTP/1.1
Authorization: Bearer sk-xxx
Content-Type: multipart/form-data; boundary=----utcp-abc123
------utcp-abc123
Content-Disposition: form-data; name="image"; filename="photo.png"
Content-Type: image/png
<binary data decoded from base64>
------utcp-abc123
Content-Disposition: form-data; name="description"
A sunset photo
------utcp-abc123--
Simple file upload (minimal config)
{
"name": "upload_document",
"call_template_type": "http",
"url": "https://api.example.com/documents",
"http_method": "POST",
"multipart_fields": {
"file": { "type": "file" }
}
}
File data encoding
Tool arguments of type: "file" carry base64-encoded content as a string. The HTTP protocol plugin decodes this before placing it in the multipart part. This is consistent with how JSON-based protocols handle binary data (e.g., OpenAPI's format: "binary" or format: "byte").
OpenAPI conversion
OpenAPI 3.x already describes multipart requests natively:
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description:
type: string
encoding:
file:
contentType: image/png
The OpenApiConverter can map this directly:
- Properties with
format: binary or format: byte → "type": "file"
- Other properties →
"type": "field"
encoding.<field>.contentType → "content_type"
Backwards compatibility
- Fully backwards compatible:
multipart_fields is optional and defaults to None
- Existing call templates are unaffected
- No changes to other protocol plugins
Open questions
- Multiple files in one field: Should
type: "file" support arrays (multiple files under one field name)? Some APIs accept files[] with multiple parts. Could be deferred to a follow-up.
- Streaming file content: For very large files, base64 in a JSON argument is impractical. Should there be a way to reference a file path instead? This is a broader concern that affects UTCP's data model beyond just multipart.
- Dynamic content type: Should
content_type support {arg_name} substitution so the caller can specify the MIME type at call time?
Problem
The HTTP protocol plugin currently cannot describe APIs that accept
multipart/form-datarequests. Settingcontent_typeto"multipart/form-data"today would send the entirebody_fieldas the raw body, which is incorrect — multipart requires boundary-delimited parts, each with their ownContent-Dispositionand optionalContent-Typeheaders.multipart/form-datais extremely common in real-world APIs:Current body-handling model
This model assumes a single, flat body. Multipart needs multiple named parts, each potentially with its own content type and encoding.
Proposed Design
Add a new optional field
multipart_fieldstoHttpCallTemplate. When present, the request is encoded asmultipart/form-dataand thebody_field/content_typefields are ignored.New field
Where
MultipartFieldConfigis:{ "type": "file | field", "content_type": "<mime-type>", "filename": "<filename or {arg_name} template>" }type(required):"file"for binary/file parts,"field"for plain text form fields.content_type(optional): TheContent-Typeof this part. Defaults toapplication/octet-streamforfile, omitted forfield.filename(optional): The filename for theContent-Dispositionheader. Only meaningful fortype: "file". Supports{arg_name}substitution from other tool arguments.Any tool argument not listed in
multipart_fields,header_fields, or matched by URL path params goes to query parameters (same as today).Argument routing (updated)
multipart_fieldsandbody_fieldare mutually exclusive. If both are present, that is a validation error.Examples
Image upload with metadata
{ "name": "upload_image", "call_template_type": "http", "url": "https://api.example.com/images/upload", "http_method": "POST", "multipart_fields": { "image": { "type": "file", "content_type": "image/png", "filename": "{original_filename}" }, "description": { "type": "field" } }, "auth": { "auth_type": "api_key", "api_key": "Bearer ${API_KEY}", "var_name": "Authorization", "location": "header" } }Resulting HTTP request:
Simple file upload (minimal config)
{ "name": "upload_document", "call_template_type": "http", "url": "https://api.example.com/documents", "http_method": "POST", "multipart_fields": { "file": { "type": "file" } } }File data encoding
Tool arguments of
type: "file"carry base64-encoded content as a string. The HTTP protocol plugin decodes this before placing it in the multipart part. This is consistent with how JSON-based protocols handle binary data (e.g., OpenAPI'sformat: "binary"orformat: "byte").OpenAPI conversion
OpenAPI 3.x already describes multipart requests natively:
The
OpenApiConvertercan map this directly:format: binaryorformat: byte→"type": "file""type": "field"encoding.<field>.contentType→"content_type"Backwards compatibility
multipart_fieldsis optional and defaults toNoneOpen questions
type: "file"support arrays (multiple files under one field name)? Some APIs acceptfiles[]with multiple parts. Could be deferred to a follow-up.content_typesupport{arg_name}substitution so the caller can specify the MIME type at call time?