Skip to content

Commit

Permalink
feat(servlets): eval-py servlet
Browse files Browse the repository at this point in the history
  • Loading branch information
G4Vi committed Jan 9, 2025
1 parent eb189d3 commit a7dad2c
Show file tree
Hide file tree
Showing 12 changed files with 567 additions and 0 deletions.
2 changes: 2 additions & 0 deletions servlets/eval-py/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
plugin.wasm
plugin/__pycache__
25 changes: 25 additions & 0 deletions servlets/eval-py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Python Eval Servlet

A simple servlet that evaluates Python code in a CPython Wasm sandbox and returns the result.

## What it does

Takes Python code as input, evaluates it using `exec()`, and returns the stdout output as a string.

## Usage

Call with:

```json
{
"arguments": {
"code": "print(2 + 2)" // Required: Python code to evaluate
}
}
```

Returns:

```
4
```
48 changes: 48 additions & 0 deletions servlets/eval-py/plugin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# THIS FILE WAS GENERATED BY `xtp-python-bindgen`. DO NOT EDIT.

from typing import Optional, List # noqa: F401
from datetime import datetime # noqa: F401
import json
import extism # pyright: ignore
import plugin


from pdk_types import (
BlobResourceContents,
CallToolRequest,
CallToolResult,
Content,
ContentType,
ListToolsResult,
Params,
Role,
TextAnnotation,
TextResourceContents,
ToolDescription,
) # noqa: F401


# Imports


# Exports
# The implementations for these functions is in `plugin.py`


# Called when the tool is invoked.
# If you support multiple tools, you must switch on the input.params.name to detect which tool is being called.
@extism.plugin_fn
def call():
input = extism.input_str()
input = CallToolRequest.from_json(input)
res = plugin.call(input)
extism.output_str(res.to_json())


# Called by mcpx to understand how and why to use this tool.
# Note: Your servlet configs will not be set when this function is called,
# so do not rely on config in this function
@extism.plugin_fn
def describe():
res = plugin.describe()
extism.output_str(res.to_json())
21 changes: 21 additions & 0 deletions servlets/eval-py/plugin/pdk_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# THIS FILE WAS GENERATED BY `xtp-python-bindgen`. DO NOT EDIT.

from typing import Optional, List # noqa: F401
from datetime import datetime # noqa: F401
import extism # noqa: F401 # pyright: ignore
import json


from pdk_types import (
BlobResourceContents,
CallToolRequest,
CallToolResult,
Content,
ContentType,
ListToolsResult,
Params,
Role,
TextAnnotation,
TextResourceContents,
ToolDescription,
) # noqa: F401
181 changes: 181 additions & 0 deletions servlets/eval-py/plugin/pdk_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# THIS FILE WAS GENERATED BY `xtp-python-bindgen`. DO NOT EDIT.

from __future__ import annotations
from enum import Enum # noqa: F401
from typing import Optional, List # noqa: F401
from datetime import datetime # noqa: F401
from dataclasses import dataclass # noqa: F401
from dataclass_wizard import JSONWizard # noqa: F401
from dataclass_wizard.type_def import JSONObject
from base64 import b64encode, b64decode


@dataclass
class BlobResourceContents(JSONWizard):
# A base64-encoded string representing the binary data of the item.
blob: str

# The URI of this resource.
uri: str

# The MIME type of this resource, if known.
mimeType: Optional[str] = None

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


@dataclass
class CallToolRequest(JSONWizard):
params: Params

method: Optional[str] = None

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


@dataclass
class CallToolResult(JSONWizard):
content: List[Content]

# Whether the tool call ended in an error.
#
# If not set, this is assumed to be false (the call was successful).
isError: Optional[bool] = None

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


@dataclass
class Content(JSONWizard):
type: ContentType

annotations: Optional[TextAnnotation] = None

# The base64-encoded image data.
data: Optional[str] = None

# The MIME type of the image. Different providers may support different image types.
mimeType: Optional[str] = None

# The text content of the message.
text: Optional[str] = None

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


class ContentType(Enum):
Text = "text"
Image = "image"
Resource = "resource"


@dataclass
class ListToolsResult(JSONWizard):
# The list of ToolDescription objects provided by this servlet.
tools: List[ToolDescription]

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


@dataclass
class Params(JSONWizard):
name: str

arguments: Optional[dict] = None

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


class Role(Enum):
Assistant = "assistant"
User = "user"


@dataclass
class TextAnnotation(JSONWizard):
# Describes who the intended customer of this object or data is.
#
# It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`).
audience: Optional[List[Role]] = None

# Describes how important this data is for operating the server.
#
# A value of 1 means "most important," and indicates that the data is
# effectively required, while 0 means "least important," and indicates that
# the data is entirely optional.
priority: Optional[float] = None

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


@dataclass
class TextResourceContents(JSONWizard):
# The text of the item. This must only be set if the item can actually be represented as text (not binary data).
text: str

# The URI of this resource.
uri: str

# The MIME type of this resource, if known.
mimeType: Optional[str] = None

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return


@dataclass
class ToolDescription(JSONWizard):
# A description of the tool
description: str

# The JSON schema describing the argument input
inputSchema: dict

# The name of the tool. It should match the plugin / binding name.
name: str

@classmethod
def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
return o

def _pre_dict(self):
return
80 changes: 80 additions & 0 deletions servlets/eval-py/plugin/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from typing import Optional, List # noqa: F401
from datetime import datetime # noqa: F401
import extism # noqa: F401 # pyright: ignore
import traceback
from io import StringIO
import sys

from pdk_types import (
BlobResourceContents,
CallToolRequest,
CallToolResult,
Content,
ContentType,
ListToolsResult,
Params,
Role,
TextAnnotation,
TextResourceContents,
ToolDescription,
) # noqa: F401


from typing import List, Optional # noqa: F401


# Called when the tool is invoked.
# If you support multiple tools, you must switch on the input.params.name to detect which tool is being called.
def call(input: CallToolRequest) -> CallToolResult:
try:
code = input.params.arguments['code']
output = StringIO()
old_stdout = sys.stdout
sys.stdout = output
exec(code)
sys.stdout = old_stdout
result = output.getvalue()
output.close()
isError = False
except Exception as e:
result = "\n".join([
"Traceback (most recent call last):",
traceback.format_tb(e.__traceback__)[0].rstrip(),
f"{type(e).__name__}: {e}"
])
isError = True
return CallToolResult(
content=[
Content(
text=result,
mimeType="text/plain",
type=ContentType.Text,
annotations=None,
data=None,
)
],
isError=isError,
)

# Called by mcpx to understand how and why to use this tool.
# Note: Your servlet configs will not be set when this function is called,
# so do not rely on config in this function
def describe() -> ListToolsResult:
return ListToolsResult(
[
ToolDescription(
name="eval-py",
description="Evaluate some Python using `exec()` in a sandbox.",
inputSchema={
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "The Python code to eval. This code gets passed into `exec()` and the stdout output is returned.",
},
},
"required": ["code"],
},
)
]
)
Loading

0 comments on commit a7dad2c

Please sign in to comment.