Skip to content

Commit 45087e8

Browse files
uprev pyodide (#13)
Co-authored-by: David Montague <35119617+dmontagu@users.noreply.github.com>
1 parent 3fb2032 commit 45087e8

File tree

6 files changed

+65
-26
lines changed

6 files changed

+65
-26
lines changed

build/prepare_env.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ async def prepare_env(dependencies: list[str] | None) -> Success | Error:
4949
return Success(dependencies=dependencies)
5050

5151

52-
def dump_json(value: Any) -> str | None:
52+
def dump_json(value: Any, always_return_json: bool) -> str | None:
5353
from pydantic_core import to_json
5454

5555
if value is None:
5656
return None
57-
if isinstance(value, str):
57+
if isinstance(value, str) and not always_return_json:
5858
return value
5959
else:
6060
return to_json(value, indent=2, fallback=_json_fallback).decode()

mcp_run_python/deno/deno.jsonc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
"@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.17.5",
1919
"@std/cli": "jsr:@std/cli@^1.0.15",
2020
"@std/path": "jsr:@std/path@^1.0.8",
21-
// do NOT upgrade above this version until there is a workaround for https://github.com/pyodide/pyodide/pull/5621
22-
"pyodide": "npm:pyodide@0.27.6",
21+
"pyodide": "npm:pyodide@0.28.2",
2322
"zod": "npm:zod@^3.24.4"
2423
},
2524
"fmt": {

mcp_run_python/deno/deno.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mcp_run_python/deno/src/main.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ function createServer(deps: string[], returnMode: string): McpServer {
7676
7777
The code may be async, and the value on the last line will be returned as the return value.
7878
79-
The code will be executed with Python 3.12.
79+
The code will be executed with Python 3.13.
8080
`
8181

8282
let setLogLevel: LoggingLevel = 'emergency'
@@ -109,6 +109,7 @@ The code will be executed with Python 3.12.
109109
},
110110
{ name: 'main.py', content: python_code },
111111
global_variables,
112+
returnMode !== 'xml',
112113
)
113114
await Promise.all(logPromises)
114115
return {

mcp_run_python/deno/src/runCode.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,8 @@ export class RunCode {
2626
log: (level: LoggingLevel, data: string) => void,
2727
file?: CodeFile,
2828
globals?: Record<string, any>,
29+
alwaysReturnJson: boolean = false,
2930
): Promise<RunSuccess | RunError> {
30-
// remove once we can upgrade to pyodide 0.27.7 and console.log is no longer used.
31-
const realConsoleLog = console.log
32-
console.log = (...args: any[]) => log('debug', args.join(' '))
33-
3431
let pyodide: PyodideInterface
3532
let sys: any
3633
let prepareStatus: PrepareSuccess | PrepareError | undefined
@@ -51,9 +48,8 @@ export class RunCode {
5148
prepareStatus = prep.prepareStatus
5249
}
5350

54-
let runResult: RunSuccess | RunError
5551
if (prepareStatus && prepareStatus.kind == 'error') {
56-
runResult = {
52+
return {
5753
status: 'install-error',
5854
output: this.takeOutput(sys),
5955
error: prepareStatus.message,
@@ -64,27 +60,25 @@ export class RunCode {
6460
globals: pyodide.toPy({ ...(globals || {}), __name__: '__main__' }),
6561
filename: file.name,
6662
})
67-
runResult = {
63+
return {
6864
status: 'success',
6965
output: this.takeOutput(sys),
70-
returnValueJson: preparePyEnv.dump_json(rawValue),
66+
returnValueJson: preparePyEnv.dump_json(rawValue, alwaysReturnJson),
7167
}
7268
} catch (err) {
73-
runResult = {
69+
return {
7470
status: 'run-error',
7571
output: this.takeOutput(sys),
7672
error: formatError(err),
7773
}
7874
}
7975
} else {
80-
runResult = {
76+
return {
8177
status: 'success',
8278
output: this.takeOutput(sys),
8379
returnValueJson: null,
8480
}
8581
}
86-
console.log = realConsoleLog
87-
return runResult
8882
}
8983

9084
async prepEnv(
@@ -107,9 +101,9 @@ export class RunCode {
107101
pyodide.loadPackage = (pkgs, options) =>
108102
origLoadPackage(pkgs, {
109103
// stop pyodide printing to stdout/stderr
110-
messageCallback: (msg: string) => log('debug', `loadPackage: ${msg}`),
104+
messageCallback: (msg: string) => log('debug', msg),
111105
errorCallback: (msg: string) => {
112-
log('error', `loadPackage: ${msg}`)
106+
log('error', msg)
113107
this.output.push(`install error: ${msg}`)
114108
},
115109
...options,
@@ -205,7 +199,7 @@ function formatError(err: any): string {
205199
errStr = errStr.replace(/^PythonError: +/, '')
206200
// remove frames from inside pyodide
207201
errStr = errStr.replace(
208-
/ {2}File "\/lib\/python\d+\.zip\/_pyodide\/.*\n {4}.*\n(?: {4,}\^+\n)?/g,
202+
/ {2}File "\/lib\/python\d+\.zip\/_pyodide\/.*\n {4}.*\n(?: {4}.*\n)*/g,
209203
'',
210204
)
211205
return errStr
@@ -221,5 +215,5 @@ interface PrepareError {
221215
}
222216
interface PreparePyEnv {
223217
prepare_env: (files: CodeFile[]) => Promise<PrepareSuccess | PrepareError>
224-
dump_json: (value: any) => string | null
218+
dump_json: (value: any, always_return_json: boolean) => string | null
225219
}

tests/test_sandbox.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,58 @@ class Foobar:
8080
),
8181
id='print-error',
8282
),
83+
pytest.param(
84+
[],
85+
"""\
86+
def foo():
87+
1 / 0
88+
89+
def bar():
90+
foo()
91+
92+
def baz():
93+
bar()
94+
95+
baz()""",
96+
{},
97+
snapshot(
98+
{
99+
'status': 'run-error',
100+
'output': [],
101+
'error': """\
102+
Traceback (most recent call last):
103+
File "main.py", line 10, in <module>
104+
baz()
105+
~~~^^
106+
File "main.py", line 8, in baz
107+
bar()
108+
~~~^^
109+
File "main.py", line 5, in bar
110+
foo()
111+
~~~^^
112+
File "main.py", line 2, in foo
113+
1 / 0
114+
~~^~~
115+
ZeroDivisionError: division by zero
116+
""",
117+
}
118+
),
119+
id='traceback',
120+
),
83121
pytest.param(
84122
['numpy'],
85123
'import numpy\nnumpy.array([1, 2, 3])',
86124
{},
87125
snapshot({'status': 'success', 'output': [], 'return_value': [1, 2, 3]}),
88126
id='return-numpy-success',
89127
),
128+
pytest.param(
129+
[],
130+
'import sys\nsys.version_info',
131+
{},
132+
snapshot({'status': 'success', 'output': [], 'return_value': [3, 13, 2, 'final', 0]}),
133+
id='python-version',
134+
),
90135
],
91136
)
92137
async def test_sandbox(deps: list[str], code: str, locals: dict[str, Any], expected: Any):
@@ -145,7 +190,7 @@ def log_handler(level: str, message: str):
145190
assert next(((level, msg) for level, msg in logs if level == 'debug'), None) == snapshot(
146191
(
147192
'debug',
148-
'loadPackage: Loading annotated-types, micropip, packaging, pydantic, pydantic_core, typing-extensions',
193+
'Loading annotated-types, micropip, pydantic, pydantic_core, typing-extensions',
149194
),
150195
)
151196
assert [(level, msg) for level, msg in logs if level == 'info'][-1] == snapshot(('info', 'hello 123'))

0 commit comments

Comments
 (0)