Skip to content

Commit ab0062d

Browse files
adding example and small tag bump
1 parent 242a8e1 commit ab0062d

File tree

2 files changed

+102
-1
lines changed

2 files changed

+102
-1
lines changed

examples/python_session_flask.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
Flask-based MCP server exposing a single `python_session` tool.
3+
4+
Install deps:
5+
6+
pip install mcp-utils flask
7+
8+
Usage:
9+
10+
python examples/python_session_flask.py
11+
12+
Notes:
13+
- Keeps a persistent Python execution context across calls (shared state).
14+
- Returns captured stdout/stderr and the value of the final expression, if any.
15+
"""
16+
17+
from __future__ import annotations
18+
19+
import ast
20+
import io
21+
import logging
22+
import sys
23+
from contextlib import redirect_stdout, redirect_stderr
24+
25+
from flask import Flask, jsonify, request
26+
import msgspec
27+
28+
from mcp_utils.core import MCPServer
29+
from mcp_utils.queue import SQLiteResponseQueue
30+
from mcp_utils.schema import CallToolResult, TextContent
31+
32+
33+
app = Flask(__name__)
34+
mcp = MCPServer("python-session", "1.0", response_queue=SQLiteResponseQueue("responses.db"))
35+
36+
logger = logging.getLogger("mcp_utils")
37+
logger.setLevel(logging.DEBUG)
38+
39+
40+
# A single, persistent execution context shared across requests
41+
_EXEC_CONTEXT: dict[str, object] = {}
42+
43+
44+
def _run_code_in_persistent_context(code: str, context: dict[str, object]) -> tuple[str, bool]:
45+
"""Execute code in a persistent context and return (output, is_error).
46+
47+
- Captures stdout and stderr
48+
- If the last statement is an expression, evaluates and appends its repr
49+
"""
50+
stdout = io.StringIO()
51+
result_text = ""
52+
is_error = False
53+
54+
try:
55+
module = ast.parse(code, mode="exec")
56+
body = module.body
57+
with redirect_stdout(stdout), redirect_stderr(stdout):
58+
if body and isinstance(body[-1], ast.Expr):
59+
# Execute all but last, then eval last expression and show its repr
60+
exec(compile(ast.Module(body=body[:-1], type_ignores=[]), "<session>", "exec"), context, context)
61+
last_expr = ast.Expression(body[-1].value)
62+
value = eval(compile(last_expr, "<session>", "eval"), context, context)
63+
if value is not None:
64+
print(repr(value))
65+
else:
66+
exec(compile(module, "<session>", "exec"), context, context)
67+
except Exception as e: # noqa: BLE001 - return error text to client
68+
is_error = True
69+
# Include exception type and message; details in stdout if any
70+
result_text = f"{e.__class__.__name__}: {e}"
71+
72+
output = stdout.getvalue()
73+
if result_text:
74+
output = (output + ("\n" if output and not output.endswith("\n") else "") + result_text).rstrip()
75+
return (output if output else ("" if not is_error else result_text)), is_error
76+
77+
78+
@mcp.tool()
79+
def execute_code(code: str) -> CallToolResult:
80+
"""Execute Python code in a session.
81+
82+
Code executes in a persistent, shared context across calls.
83+
"""
84+
output, is_error = _run_code_in_persistent_context(code, _EXEC_CONTEXT)
85+
# Always return a string content payload with error flag as appropriate
86+
return CallToolResult(content=[TextContent(text=output or "")], is_error=is_error)
87+
88+
89+
@app.route("/mcp", methods=["POST"])
90+
def mcp_route():
91+
response = mcp.handle_message(request.get_json())
92+
return jsonify(msgspec.to_builtins(response))
93+
94+
95+
if __name__ == "__main__":
96+
handler = logging.StreamHandler(sys.stdout)
97+
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(name)s: %(message)s")
98+
handler.setFormatter(formatter)
99+
logger.addHandler(handler)
100+
# Run with Flask's built-in dev server locally
101+
app.run(host="127.0.0.1", port=9005, debug=True)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mcp-utils"
3-
version = "2.0.0"
3+
version = "2.0.1"
44
description = "Synchronous utilities for Model Context Protocol (MCP) integration"
55
readme = "README.md"
66
requires-python = ">=3.10"

0 commit comments

Comments
 (0)