Skip to content

Commit 555ced1

Browse files
authored
Merge pull request #51 from appwrite/fix/http-inline-result-payloads
Fix hosted result payload handling
2 parents e936990 + e2dd9f0 commit 555ced1

5 files changed

Lines changed: 70 additions & 3 deletions

File tree

src/mcp_server_appwrite/http_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ async def health_endpoint(request: Request) -> PlainTextResponse:
147147
def build_app() -> Starlette:
148148
telemetry.init_telemetry("http", SERVER_VERSION)
149149
tools_manager = build_catalog_tools_manager()
150-
operator = build_operator(tools_manager)
150+
operator = build_operator(tools_manager, store_results=False)
151151
server = build_mcp_server(operator, transport="http")
152152

153153
# Streamable HTTP with SSE responses (the MCP SDK/ecosystem default). Stateless,

src/mcp_server_appwrite/operator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,15 @@ def __init__(
104104
docs_search: DocsSearch | None = None,
105105
context_provider: ContextProvider | None = None,
106106
preview_threshold: int = PREVIEW_THRESHOLD,
107+
store_results: bool = True,
107108
search_limit: int = SEARCH_LIMIT,
108109
):
109110
self._tools_manager = tools_manager
110111
self._execute_tool = execute_tool
111112
self._docs_search = docs_search
112113
self._context_provider = context_provider
113114
self._preview_threshold = preview_threshold
115+
self._store_results = store_results
114116
self._search_limit = search_limit
115117
self._result_store = ResultStore()
116118
self._catalog = self._build_catalog()
@@ -474,6 +476,9 @@ def _call_hidden_tool(self, raw_arguments: dict[str, Any]) -> list[ToolContent]:
474476
def _preview_or_store_result(
475477
self, tool_name: str, content: list[ToolContent]
476478
) -> list[ToolContent]:
479+
if not self._store_results:
480+
return content
481+
477482
if all(isinstance(item, types.TextContent) for item in content):
478483
full_text = "\n".join(
479484
item.text for item in content if isinstance(item, types.TextContent)

src/mcp_server_appwrite/server.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -865,14 +865,19 @@ def _format_appwrite_error(exc: AppwriteException) -> str:
865865

866866

867867
def build_instructions(transport: str = "http") -> str:
868+
result_handling = (
869+
"Large results are stored as resources; read the URI returned by the tool."
870+
if transport == "stdio"
871+
else "Hosted HTTP returns tool results inline, including images and other binary payloads."
872+
)
868873
common = (
869874
"Appwrite workflow: use appwrite_get_context to understand the current "
870875
"connection and available project resources, then use appwrite_search_tools "
871876
"and appwrite_call_tool for specific operations. "
872877
"Mutating hidden tools require confirm_write=true. "
873878
"For questions about Appwrite concepts, products, or guides, use "
874879
"appwrite_search_docs to search the documentation when available. "
875-
"Large results are stored as resources; read the URI returned by the tool."
880+
f"{result_handling}"
876881
)
877882

878883
if transport == "stdio":
@@ -1003,7 +1008,10 @@ def _emit_initialize(server: Server) -> None:
10031008

10041009

10051010
def build_operator(
1006-
tools_manager: ToolManager, client: Client | None = None
1011+
tools_manager: ToolManager,
1012+
client: Client | None = None,
1013+
*,
1014+
store_results: bool = True,
10071015
) -> Operator:
10081016
"""Wire the operator surface to the per-request execution path. The execution
10091017
callback re-binds each call to a per-request client via `resolve_client` in
@@ -1032,6 +1040,7 @@ def build_operator(
10321040
),
10331041
context_provider=lambda arguments: _get_context_for_request(arguments, client),
10341042
docs_search=docs_search,
1043+
store_results=store_results,
10351044
)
10361045

10371046

tests/unit/test_operator.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,57 @@ def test_large_result_is_stored_as_resource(self):
277277
self.assertEqual(contents[0].mime_type, "application/json")
278278
self.assertIn('"type": "text"', contents[0].content)
279279

280+
def test_store_results_false_returns_large_result_inline(self):
281+
manager = ToolManager()
282+
manager.tools_registry = {
283+
"tables_db_list": {
284+
"definition": make_tool("tables_db_list", "List all databases."),
285+
"function": object(),
286+
"parameter_types": {},
287+
},
288+
}
289+
runtime = Operator(
290+
manager,
291+
lambda name, arguments, *_: [
292+
types.TextContent(type="text", text="x" * 1200)
293+
],
294+
store_results=False,
295+
)
296+
297+
result = runtime.execute_public_tool(
298+
"appwrite_call_tool",
299+
{"tool_name": "tables_db_list"},
300+
)
301+
302+
self.assertEqual(result[0].text, "x" * 1200)
303+
self.assertNotIn("appwrite://operator/results/", result[0].text)
304+
305+
def test_store_results_false_returns_image_inline(self):
306+
manager = ToolManager()
307+
manager.tools_registry = {
308+
"avatars_get_qr": {
309+
"definition": make_tool("avatars_get_qr", "Get a QR code."),
310+
"function": object(),
311+
"parameter_types": {},
312+
},
313+
}
314+
runtime = Operator(
315+
manager,
316+
lambda name, arguments, *_: [
317+
types.ImageContent(type="image", data="aW1hZ2U=", mimeType="image/png")
318+
],
319+
store_results=False,
320+
)
321+
322+
result = runtime.execute_public_tool(
323+
"appwrite_call_tool",
324+
{"tool_name": "avatars_get_qr"},
325+
)
326+
327+
self.assertEqual(len(result), 1)
328+
self.assertIsInstance(result[0], types.ImageContent)
329+
self.assertEqual(result[0].mimeType, "image/png")
330+
280331

281332
if __name__ == "__main__":
282333
unittest.main()

tests/unit/test_server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ def test_build_instructions_are_transport_specific(self):
103103
self.assertNotIn("Appwrite console", stdio)
104104
self.assertIn("Appwrite console", http)
105105
self.assertIn("project_id", http)
106+
self.assertIn("Large results are stored as resources", stdio)
107+
self.assertIn("returns tool results inline", http)
106108

107109
def test_coerce_input_file_from_path(self):
108110
with tempfile.NamedTemporaryFile(suffix=".txt") as handle:

0 commit comments

Comments
 (0)