-
Notifications
You must be signed in to change notification settings - Fork 12
feat: upload files through the MCP server for action File fields #1815
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Scra3
merged 39 commits into
main
from
feature/prd-913-upload-files-through-the-mcp-server
Aug 17, 2026
Merged
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
dc93471
feat(datasource-toolkit): expose a shared data uri codec
5ace7c8
feat(agent-client): encode file values for action File fields
ebc69ac
refactor: reuse the shared data uri codec
d0e8682
feat(mcp-server): enable action file fields via an upload side-channel
4c585f3
fix: address the review of the file upload side-channel
8b6a0c6
fix: address the automated review findings
2c57469
refactor(mcp-server): expose the upload destination as a tool
d1b2bbc
test(mcp-server): cover the file upload configuration and destination
97660d5
fix: make a bad file value diagnosable by the caller
d7ca657
docs(_example): demonstrate action file uploads end to end
f34cfca
fix(_example): contain the local upload storage to its root
e12cdc5
fix(mcp-server): restore the scope check the route enforced, and name…
8c68ab4
fix(_example): cap the body the local upload storage accepts
bed0bbc
fix(datasource-toolkit): reject a malformed data uri readably
49d688a
fix(mcp-server): bound a storage read and the queue behind it
d46434f
feat(mcp-server): let the standalone server enable file uploads
914548c
fix: stop rejecting work the limits were never meant to reject
4242b06
feat(mcp-server): hold uploads in memory when no storage is configured
1810811
fix(mcp-server): harden the in-memory upload store against concurrenc…
5094c9b
fix(mcp-server): accept an in-memory upload that replaces one of the …
9cd8e81
test(mcp-server): upload to the url the in-memory store actually hand…
2ea1881
fix(mcp-server): name the refused upload first when an object is missing
51f1c67
refactor(mcp-server): cut what the in-memory store did not need
4cd35f3
docs(mcp-server): name the two conditions a hosted client needs to up…
b8c461f
fix(mcp-server): stop claiming a checksum header the default backend …
31fb616
refactor(mcp-server)!: rename requestFileUpload to requestActionFileU…
f6ce069
fix(mcp-server): make the in-memory upload url genuinely single-use
d724e28
feat(mcp-server)!: enable action file uploads by default
24e0547
docs(mcp-server): stop naming a settings path the user may not have
bb62c80
docs(mcp-server): drop cloud agents from the single-instance warning
63062ec
fix(agent-client)!: keep getType() on the wire form, collapse it apart
a961a95
fix(mcp-server): survive a retry, and stop trusting a broken storage …
49bd1d1
docs(mcp-server): say why the store refuses early, and what getSize m…
f720ae5
test(agent-testing): restore the list type this package always asserted
cee8109
docs(mcp-server): the Claude Desktop upload is verified, not expected
2190fd5
fix(mcp-server): keep a required file field satisfiable, and add a wa…
b6f30be
docs(mcp-server): make the sha256 pin the stated default, not an aside
cb63a04
fix(mcp-server): review the delta the reviews had not covered
d6913ab
docs(mcp-server): cowork verified too, and the filename is a label
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,35 @@ | ||
| import type { ReviewCustomizer } from '../typings'; | ||
|
|
||
| export default (collection: ReviewCustomizer) => | ||
| collection.addManyToOneRelation('store', 'store', { foreignKey: 'storeId' }); | ||
| collection | ||
| .addManyToOneRelation('store', 'store', { foreignKey: 'storeId' }) | ||
|
|
||
| .addAction('Attach a document', { | ||
| scope: 'Single', | ||
| form: [ | ||
| { label: 'Document', type: 'File', isRequired: true }, | ||
| { label: 'Extra pages', type: 'FileList' }, | ||
| { label: 'Note', type: 'String' }, | ||
| ], | ||
| execute: async (context, resultBuilder) => { | ||
| const document = context.formValues.Document as { | ||
| name: string; | ||
| mimeType: string; | ||
| buffer: Buffer; | ||
| }; | ||
| const extras = (context.formValues['Extra pages'] ?? []) as (typeof document)[]; | ||
|
|
||
| const describe = (file: typeof document) => | ||
| `${file?.name} (${file?.mimeType}, ${file?.buffer?.length} bytes)`; | ||
|
|
||
| return resultBuilder.success( | ||
| [ | ||
| `Received ${describe(document)}`, | ||
| extras.length | ||
| ? `plus ${extras.length}: ${extras.map(describe).join(', ')}` | ||
| : 'no extras', | ||
| `note: ${context.formValues.Note ?? '-'}`, | ||
| ].join(' — '), | ||
| ); | ||
| }, | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| import type { File } from '@forestadmin/datasource-toolkit'; | ||
|
|
||
| import { makeDataUri } from '@forestadmin/datasource-toolkit'; | ||
|
|
||
| function isFileType(type: string): boolean { | ||
| return type === 'File'; | ||
| } | ||
|
|
||
| function isFileListType(type: string): boolean { | ||
| return type === 'FileList'; | ||
| } | ||
|
|
||
| function isFile(value: unknown): value is File { | ||
| const candidate = value as File; | ||
|
|
||
| return ( | ||
| typeof value === 'object' && | ||
| value !== null && | ||
| Buffer.isBuffer(candidate.buffer) && | ||
| typeof candidate.mimeType === 'string' && | ||
| typeof candidate.name === 'string' | ||
| ); | ||
| } | ||
|
macroscopeapp[bot] marked this conversation as resolved.
|
||
|
|
||
| function fileError(fieldName: string, detail: string): Error { | ||
| return new Error(`Field "${fieldName}" ${detail}`); | ||
| } | ||
|
|
||
| function encodeFileValue(value: unknown, fieldName: string): unknown { | ||
| if (value === null || value === undefined) return value; | ||
|
|
||
| // Callers that address the file indirectly (mcp-server upload handles) keep their sentinel: | ||
| // validating strings here would break them, and the agent owns the final validation. | ||
| if (typeof value === 'string') return value; | ||
|
|
||
| if (isFile(value)) return makeDataUri(value); | ||
|
|
||
| throw fileError( | ||
| fieldName, | ||
| 'expects a file: pass { buffer, mimeType, name } or a string holding a data uri.', | ||
| ); | ||
| } | ||
|
|
||
| export default function encodeFileFieldValue( | ||
| type: string, | ||
| value: unknown, | ||
| fieldName: string, | ||
| ): unknown { | ||
| if (isFileListType(type)) { | ||
| if (value === null || value === undefined) return value; | ||
|
|
||
| if (!Array.isArray(value)) { | ||
| throw fileError(fieldName, 'expects a list of files: pass an array.'); | ||
| } | ||
|
|
||
| return value.map(item => encodeFileValue(item, fieldName)); | ||
| } | ||
|
|
||
| if (isFileType(type)) return encodeFileValue(value, fieldName); | ||
|
|
||
| // A file reaching a field that is not declared as one is never intentional, and it would be | ||
| // JSON-serialized into the column as {"buffer":{"type":"Buffer",...}} without any error. | ||
| if (isFile(value)) { | ||
| throw fileError(fieldName, `is a ${type} field and cannot hold a file.`); | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.