Skip to content

Conversation

@Kigstn
Copy link
Contributor

@Kigstn Kigstn commented Oct 25, 2025

This PR adds support for the server to output files.
This is done via Embedded Ressources

image

This allows this server to do cool stuff like generate matplotlib graphs :)

@Kigstn
Copy link
Contributor Author

Kigstn commented Oct 27, 2025

The pyright action is failing - but tbh I did not touch most of the code. Can happily fix the issues, but wanted to ask before :)

@DouweM
Copy link

DouweM commented Oct 27, 2025

@Kigstn Thanks Daniel! Can you pull the verbose changes that cause the linter errors into a separate PR, so we can just focus on output files here?

@DouweM DouweM mentioned this pull request Oct 27, 2025
@DouweM DouweM self-assigned this Oct 27, 2025
@DouweM
Copy link

DouweM commented Oct 27, 2025

Note that we'll want to think about how this interacts with #9

@Kigstn
Copy link
Contributor Author

Kigstn commented Oct 28, 2025

@DouweM of course, done!
One thing I am not 100% happy with is the mounting of folder into pyodide. This may led to problems when multiple calls are made at the same time - however changing this would have been quite complex due to how pyodide is passed currently. Will happily do it if wanted tho :)

Gernally - Happy to adapt things, just give me some feedback :)
Thanks!


I made a 2nd tiny PR for the verbose changes

@agahkarakuzu
Copy link

This would be an excellent addition, thank you so much for working on this feature!

@Kigstn I tried to install this locally (pip install -e .), as I was eager to see file output working (output_files). Yet I get the following error:

    raise RuntimeError(f'`deno run ...` returned a non-zero exit code {p.returncode}: {"".join(stdout)}')
RuntimeError: `deno run ...` returned a non-zero exit code 1: Initialize @types/node@22.12.0Initialize zod@3.25.76Initialize pyodide@0.28.2Initialize mime-types@3.0.1Initialize @modelcontextprotocol/sdk@1.17.5Initialize undici-types@6.20.0Initialize ws@8.18.3Initialize mime-db@1.54.0Initialize express@5.1.0Initialize eventsource-parser@3.0.6Initialize content-type@1.0.5Initialize cors@2.8.5Initialize express-rate-limit@7.5.1Initialize pkce-challenge@5.0.0Initialize ajv@6.12.6Initialize eventsource@3.0.7Initialize raw-body@3.0.0Initialize zod-to-json-schema@3.24.6Initialize cross-spawn@7.0.6Initialize router@2.2.0Initialize statuses@2.0.2Initialize qs@6.14.0Initialize on-finished@2.4.1Initialize escape-html@1.0.3Initialize fresh@2.0.0Initialize range-parser@1.2.1Initialize debug@4.4.1Initialize etag@1.8.1Initialize cookie-signature@1.2.2Initialize once@1.4.0Initialize parseurl@1.3.3Initialize vary@1.1.2Initialize http-errors@2.0.0Initialize finalhandler@2.1.0Initialize type-is@2.0.1Initialize accepts@2.0.0Initialize content-disposition@1.0.0Initialize send@1.2.0Initialize encodeurl@2.0.0Initialize cookie@0.7.2Initialize body-parser@2.2.0Initialize merge-descriptors@2.0.0Initialize serve-static@2.2.0Initialize proxy-addr@2.0.7Initialize object-assign@4.1.1Initialize uri-js@4.4.1Initialize json-schema-traverse@0.4.1Initialize fast-json-stable-stringify@2.1.0Initialize fast-deep-equal@3.1.3Initialize iconv-lite@0.6.3Initialize bytes@3.1.2Initialize unpipe@1.0.0Initialize path-key@3.1.1Initialize shebang-command@2.0.0Initialize which@2.0.2Initialize is-promise@4.0.0Initialize path-to-regexp@8.3.0Initialize depd@2.0.0Initialize side-channel@1.1.0Initialize ee-first@1.1.1Initialize ms@2.1.3Initialize wrappy@1.0.2Initialize statuses@2.0.1Initialize inherits@2.0.4Initialize toidentifier@1.0.1Initialize setprototypeof@1.2.0Initialize media-typer@1.1.0Initialize negotiator@1.0.0Initialize safe-buffer@5.2.1Initialize forwarded@0.2.0Initialize ipaddr.js@1.9.1Initialize punycode@2.3.1Initialize safer-buffer@2.1.2Initialize shebang-regex@3.0.0Initialize isexe@2.0.0Initialize object-inspect@1.13.4Initialize side-channel-list@1.0.0Initialize side-channel-weakmap@1.0.2Initialize es-errors@1.3.0Initialize side-channel-map@1.0.1Initialize get-intrinsic@1.3.0Initialize call-bound@1.0.4Initialize math-intrinsics@1.1.0Initialize get-proto@1.0.1Initialize has-symbols@1.1.0Initialize es-object-atoms@1.1.1Initialize es-define-property@1.0.1Initialize hasown@2.0.2Initialize gopd@1.2.0Initialize call-bind-apply-helpers@1.0.2Initialize function-bind@1.1.2Initialize dunder-proto@1.0.1error: Module not found "file:///private/var/folders/qc/6xvjmcj12gj80tn60__2648c0000gn/T/tmpb1b5xz2c/mcp-run-python/src/prepareEnvCode.ts".at file:///private/var/folders/qc/6xvjmcj12gj80tn60__2648c0000gn/T/tmpb1b5xz2c/mcp-run-python/src/runCode.ts:3:35

Am I missing something?

@DouweM
Copy link

DouweM commented Oct 30, 2025

One thing I am not 100% happy with is the mounting of folder into pyodide. This may led to problems when multiple calls are made at the same time - however changing this would have been quite complex due to how pyodide is passed currently. Will happily do it if wanted tho :)

@Kigstn Yeah we'll need to support parallel runs. Could we use a virtual FS that's only mounted into a specific run, instead of a real directory on disk?

I also think this should be an opt-in feature, so we don't change people's existing usage of the mcp server if they just want text output, no files.

@Kigstn
Copy link
Contributor Author

Kigstn commented Nov 3, 2025

This would be an excellent addition, thank you so much for working on this feature!

@Kigstn I tried to install this locally (pip install -e .), as I was eager to see file output working (output_files). Yet I get the following error:

    raise RuntimeError(f'`deno run ...` returned a non-zero exit code {p.returncode}: {"".join(stdout)}')
RuntimeError: `deno run ...` returned a non-zero exit code 1: Initialize @types/node@22.12.0Initialize zod@3.25.76Initialize pyodide@0.28.2Initialize mime-types@3.0.1Initialize @modelcontextprotocol/sdk@1.17.5Initialize undici-types@6.20.0Initialize ws@8.18.3Initialize mime-db@1.54.0Initialize express@5.1.0Initialize eventsource-parser@3.0.6Initialize content-type@1.0.5Initialize cors@2.8.5Initialize express-rate-limit@7.5.1Initialize pkce-challenge@5.0.0Initialize ajv@6.12.6Initialize eventsource@3.0.7Initialize raw-body@3.0.0Initialize zod-to-json-schema@3.24.6Initialize cross-spawn@7.0.6Initialize router@2.2.0Initialize statuses@2.0.2Initialize qs@6.14.0Initialize on-finished@2.4.1Initialize escape-html@1.0.3Initialize fresh@2.0.0Initialize range-parser@1.2.1Initialize debug@4.4.1Initialize etag@1.8.1Initialize cookie-signature@1.2.2Initialize once@1.4.0Initialize parseurl@1.3.3Initialize vary@1.1.2Initialize http-errors@2.0.0Initialize finalhandler@2.1.0Initialize type-is@2.0.1Initialize accepts@2.0.0Initialize content-disposition@1.0.0Initialize send@1.2.0Initialize encodeurl@2.0.0Initialize cookie@0.7.2Initialize body-parser@2.2.0Initialize merge-descriptors@2.0.0Initialize serve-static@2.2.0Initialize proxy-addr@2.0.7Initialize object-assign@4.1.1Initialize uri-js@4.4.1Initialize json-schema-traverse@0.4.1Initialize fast-json-stable-stringify@2.1.0Initialize fast-deep-equal@3.1.3Initialize iconv-lite@0.6.3Initialize bytes@3.1.2Initialize unpipe@1.0.0Initialize path-key@3.1.1Initialize shebang-command@2.0.0Initialize which@2.0.2Initialize is-promise@4.0.0Initialize path-to-regexp@8.3.0Initialize depd@2.0.0Initialize side-channel@1.1.0Initialize ee-first@1.1.1Initialize ms@2.1.3Initialize wrappy@1.0.2Initialize statuses@2.0.1Initialize inherits@2.0.4Initialize toidentifier@1.0.1Initialize setprototypeof@1.2.0Initialize media-typer@1.1.0Initialize negotiator@1.0.0Initialize safe-buffer@5.2.1Initialize forwarded@0.2.0Initialize ipaddr.js@1.9.1Initialize punycode@2.3.1Initialize safer-buffer@2.1.2Initialize shebang-regex@3.0.0Initialize isexe@2.0.0Initialize object-inspect@1.13.4Initialize side-channel-list@1.0.0Initialize side-channel-weakmap@1.0.2Initialize es-errors@1.3.0Initialize side-channel-map@1.0.1Initialize get-intrinsic@1.3.0Initialize call-bound@1.0.4Initialize math-intrinsics@1.1.0Initialize get-proto@1.0.1Initialize has-symbols@1.1.0Initialize es-object-atoms@1.1.1Initialize es-define-property@1.0.1Initialize hasown@2.0.2Initialize gopd@1.2.0Initialize call-bind-apply-helpers@1.0.2Initialize function-bind@1.1.2Initialize dunder-proto@1.0.1error: Module not found "file:///private/var/folders/qc/6xvjmcj12gj80tn60__2648c0000gn/T/tmpb1b5xz2c/mcp-run-python/src/prepareEnvCode.ts".at file:///private/var/folders/qc/6xvjmcj12gj80tn60__2648c0000gn/T/tmpb1b5xz2c/mcp-run-python/src/runCode.ts:3:35

Am I missing something?

Yes, you need to run make build first to install some extra things @agahkarakuzu :)

@agahkarakuzu
Copy link

I totally missed that, thank you @Kigstn!

@Kigstn
Copy link
Contributor Author

Kigstn commented Nov 3, 2025

One thing I am not 100% happy with is the mounting of folder into pyodide. This may led to problems when multiple calls are made at the same time - however changing this would have been quite complex due to how pyodide is passed currently. Will happily do it if wanted tho :)

@Kigstn Yeah we'll need to support parallel runs. Could we use a virtual FS that's only mounted into a specific run, instead of a real directory on disk?

I will investigate this. From a quick look a virtual FS will not solve the problem, as you simple can't re-mount the same directory. I am going to look at either:

  • spawning multiple instances of pyodide
  • using semaphores to limit code execution to one
  • using web workers to put code execution into threads

Any preferences @DouweM ? :)

@Kigstn
Copy link
Contributor Author

Kigstn commented Nov 3, 2025

And for opt-in, that makes sense & is very reasonable :)

@DouweM
Copy link

DouweM commented Nov 4, 2025

@Kigstn Ah the issue is that we're using the same Pyodide instance, right. Then in this scenario I think multiple instances makes sense.

@Kigstn
Copy link
Contributor Author

Kigstn commented Nov 7, 2025

Alright @DouweM - I think it's now ready.

As you can see, getting this to run was a bit more complicated & required more changes. So have fun with the review 😅


So what changed:

  • Changed pyodide to be seperate instances. 10 by default, this can be configured via CLI
    • There also is a timeout when waiting for an instance. Can also be configured via CLI
  • Added timeout functionality to the code run function. TBH this should have been a different PR, but I was chaning the same references so many times I just added it :D
    Now the code is only allowed to run for 60s, then it gets interrupted. Honestly a very important feature that was missing, and also configurable via CLI. Default again 60s
  • Added tests for parallelism. Pyodide needs 1-2s for a worker to be ready, but this only effects the first batch of code in my testing. Seems to work well - feel free to suggest any tests that are missing :)
  • Made the file upload feature opt-in, again CLI :)

@Kigstn
Copy link
Contributor Author

Kigstn commented Nov 8, 2025

Also have a question regarding the failing test: it's a performance test I added to check that worker creation (which takes 2-3s) is not repeated constantly. So I fire 500 super simple code snippets against it and test the time. Locally that takes 15s to run, on CI 25ish. The problem however is that due to the CI worker running the test for all python versions, this is super slow & inconsistent.

Personally, I would move these tests out of Ci and maybe into their own Ci instance where it is only run once. Otherwise the benchmarking will always be inconsistent...

Or these tests can be removed / only exist locally

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.

3 participants