Skip to content

Commit 994b7ca

Browse files
committed
feat: improve tool response representation in Claude Desktop
1 parent 9dba297 commit 994b7ca

9 files changed

Lines changed: 159 additions & 408 deletions

File tree

src/deepset_mcp/tool_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,4 +363,4 @@ def register_tools(
363363
object_store,
364364
)
365365

366-
mcp_server_instance.add_tool(enhanced_tool, name=tool_name, structured_output=False)
366+
mcp_server_instance.add_tool(enhanced_tool, name=tool_name)

src/deepset_mcp/tools/tokonomics/__init__.py

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,12 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5-
"""
6-
Tokonomics: Explorable and Referenceable Tools for LLM Agents.
7-
8-
=============================================================
9-
10-
A library that provides token-efficient object exploration and reference
11-
passing capabilities for LLM agents.
12-
13-
Key Features:
14-
- TTL-based object storage for temporary results
15-
- Rich object exploration with multiple rendering modes
16-
- Reference-based parameter passing (@obj_id.path.to.value)
17-
- Type-safe decorators that preserve function signatures
18-
- Configurable preview truncation and custom rendering callbacks
19-
20-
Usage:
21-
------
22-
23-
Basic explorable tool:
24-
25-
>>> from deepset_mcp.tools.tokonomics import explorable
26-
>>>
27-
>>> @explorable
28-
>>> def get_data():
29-
... return {"users": [{"name": "Alice", "age": 30}]}
30-
>>>
31-
>>> result = get_data()
32-
>>> print(result) # Shows rich preview
33-
>>> result.obj_id # "obj_123"
34-
>>> result.value # Original data
35-
36-
Referenceable tool that accepts references:
37-
38-
>>> from deepset_mcp.tools.tokonomics import referenceable
39-
>>>
40-
>>> @referenceable
41-
>>> def process_users(users: list) -> str:
42-
... return f"Processed {len(users)} users"
43-
>>>
44-
>>> # Use with direct data
45-
>>> process_users([{"name": "Bob"}])
46-
>>>
47-
>>> # Use with reference
48-
>>> process_users("@obj_123.users")
49-
50-
Exploration utilities:
51-
52-
>>> from deepset_mcp.tools.tokonomics import explore, search
53-
>>>
54-
>>> # Explore object structure
55-
>>> explore("obj_123", mode="tree")
56-
>>>
57-
>>> # Search within objects
58-
>>> search("obj_123", "Alice")
59-
"""
60-
615
from .decorators import explorable, explorable_and_referenceable, referenceable
626
from .explorer import RichExplorer
63-
from .object_store import Explorable, InMemoryBackend, ObjectStore
7+
from .object_store import InMemoryBackend, ObjectStore
648

659
__all__ = [
6610
# Core classes
67-
"Explorable",
6811
"InMemoryBackend",
6912
"ObjectStore",
7013
"RichExplorer",
@@ -73,5 +16,3 @@
7316
"referenceable",
7417
"explorable_and_referenceable",
7518
]
76-
77-
__version__ = "0.1.0"

src/deepset_mcp/tools/tokonomics/decorators.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from glom import GlomError, glom
2121

2222
from deepset_mcp.tools.tokonomics.explorer import RichExplorer
23-
from deepset_mcp.tools.tokonomics.object_store import Explorable, ObjectStore
23+
from deepset_mcp.tools.tokonomics.object_store import ObjectStore
2424

2525
F = TypeVar("F", bound=Callable[..., Any])
2626

@@ -148,26 +148,29 @@ def decorator(func: F) -> F:
148148
if inspect.iscoroutinefunction(func):
149149

150150
@wraps(func)
151-
async def async_wrapper(*args: Any, **kwargs: Any) -> Explorable[Any]:
151+
async def async_wrapper(*args: Any, **kwargs: Any) -> str:
152152
result = await func(*args, **kwargs)
153153
obj_id = object_store.put(result)
154154
preview = explorer.explore(obj_id)
155-
return Explorable(obj_id, result, preview)
155+
156+
return preview
156157

157158
# Enhance docstring
158159
async_wrapper.__doc__ = _enhance_docstring_for_explorable(func.__doc__ or "", func.__name__)
160+
async_wrapper.__annotations__["return"] = str
159161
return async_wrapper # type: ignore[return-value]
160162
else:
161163

162164
@wraps(func)
163-
def sync_wrapper(*args: Any, **kwargs: Any) -> Explorable[Any]:
165+
def sync_wrapper(*args: Any, **kwargs: Any) -> str:
164166
result = func(*args, **kwargs)
165167
obj_id = object_store.put(result)
166168
preview = explorer.explore(obj_id)
167-
return Explorable(obj_id, result, preview)
169+
return preview
168170

169171
# Enhance docstring
170172
sync_wrapper.__doc__ = _enhance_docstring_for_explorable(func.__doc__ or "", func.__name__)
173+
sync_wrapper.__annotations__["return"] = str
171174
return sync_wrapper # type: ignore[return-value]
172175

173176
return decorator

src/deepset_mcp/tools/tokonomics/explorer.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def explore(self, obj_id: str, path: str = "") -> str:
9191
else:
9292
body = self._get_pretty_repr(obj)
9393

94-
return f"{header}\n\n{body}"
94+
return f"{header}\n\n" + body
9595

9696
def search(self, obj_id: str, pattern: str, path: str = "", case_sensitive: bool = False) -> str:
9797
"""Search for a pattern within a string object.
@@ -162,21 +162,21 @@ def slice(self, obj_id: str, start: int = 0, end: int | None = None, path: str =
162162

163163
# Handle string slicing
164164
if isinstance(obj, str):
165-
sliced: str | list[Any] | tuple[Any] = obj[start:end]
165+
sliced_str: str = obj[start:end]
166166
actual_end = end if end is not None else len(obj)
167-
body = f"String slice [{start}:{actual_end}] of length {len(sliced)}:\n\n{sliced}"
168-
return f"{header}\n\n{body}"
167+
body = f"String slice [{start}:{actual_end}] of length {len(sliced_str)}:\n\n{sliced_str}"
168+
return f"{header}\n\n" + body
169169

170170
# Handle list/tuple slicing
171171
elif isinstance(obj, list | tuple):
172-
sliced = obj[start:end]
172+
sliced_list = obj[start:end]
173173
actual_end = end if end is not None else len(obj)
174174

175175
# Use Pretty to render the sliced list with current settings
176176
with self.console.capture() as cap:
177177
self.console.print(
178178
Pretty(
179-
sliced,
179+
sliced_list,
180180
max_depth=self.max_depth,
181181
max_length=None, # Show all items in the slice
182182
max_string=self.max_string_length,
@@ -187,10 +187,10 @@ def slice(self, obj_id: str, start: int = 0, end: int | None = None, path: str =
187187
type_name = type(obj).__name__
188188
body = (
189189
f"{type_name.capitalize()} slice [{start}:{actual_end}] "
190-
f"(showing {len(sliced)} of {len(obj)} items):\n\n"
190+
f"(showing {len(sliced_list)} of {len(obj)} items):\n\n"
191191
f"{cap.get().rstrip()}"
192192
)
193-
return f"{header}\n\n{body}"
193+
return f"{header}\n\n" + body
194194

195195
else:
196196
return f"{header}\n\nObject of type {type(obj).__name__} does not support slicing"

src/deepset_mcp/tools/tokonomics/object_store.py

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,86 +2,19 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5-
"""
6-
Tokonomics: Explorable and Referenceable Tools.
7-
8-
===============================================
9-
10-
A library that equips LLM‑agents with:
11-
12-
* TTL‑based object storage
13-
* Rich object exploration & reference passing
14-
* Smart decorators for explorable outputs and referenceable inputs
15-
* Strict typing / signature preservation (incl. *async* callables)
16-
* ReST‑style docstring enhancement
17-
* Configurable preview truncation (`max_bytes`) and a user‑supplied
18-
`preview_callback` for custom renderings (e.g. pandas.DataFrame)
19-
"""
20-
215
from __future__ import annotations
226

237
import logging
248
import time
259
import uuid
2610
from typing import (
2711
Any,
28-
Generic,
2912
Protocol,
30-
TypeVar,
3113
)
3214

3315
import orjson
3416

3517
logger = logging.getLogger(__name__)
36-
# =============================================================================
37-
# 1 · Generic return‑value wrapper
38-
# =============================================================================
39-
40-
_T = TypeVar("_T")
41-
42-
43-
class Explorable(Generic[_T]):
44-
"""
45-
Wrapper returned by ``@explorable`` decorated tools.
46-
47-
Attributes
48-
----------
49-
obj_id :
50-
The internal identifier of the stored object.
51-
value :
52-
The original, *unmodified* object returned by the wrapped tool.
53-
preview :
54-
Rich‑rendered string generated by the explorer.
55-
"""
56-
57-
__slots__ = ("obj_id", "value", "_preview")
58-
59-
def __init__(self, obj_id: str, value: _T, preview: str) -> None:
60-
"""Initialize Explorable wrapper with object ID, value, and preview."""
61-
self.obj_id = obj_id
62-
self.value: _T = value
63-
self._preview = preview
64-
65-
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
66-
# Representations
67-
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
68-
69-
def __str__(self) -> str:
70-
"""Return the preview string representation."""
71-
return self._preview
72-
73-
__repr__ = __str__
74-
75-
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
76-
# Convenience for notebooks / REPLs
77-
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
78-
79-
_ipython_display_ = __str__ # Jupyter friendly
80-
81-
82-
# =============================================================================
83-
# 2 · ObjectStore Backend Protocol
84-
# =============================================================================
8518

8619

8720
class ObjectStoreBackend(Protocol):
@@ -178,11 +111,6 @@ def delete(self, key: str) -> bool:
178111
return bool(self._client.delete(key))
179112

180113

181-
# =============================================================================
182-
# 3 · Time‑to‑live based object store
183-
# =============================================================================
184-
185-
186114
class ObjectStore:
187115
"""JSON-based object store with pluggable backends."""
188116

0 commit comments

Comments
 (0)