stinger-python-utils 0.1.8rc4__tar.gz → 0.1.8rc5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/PKG-INFO +1 -1
  2. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/pyproject.toml +1 -1
  3. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/src/stinger_python_utils/mcp/plugin.py +17 -11
  4. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/src/stinger_python_utils/mcp/server.py +24 -11
  5. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/.github/workflows/python-tests.yml +0 -0
  6. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/.github/workflows/python37.yml +0 -0
  7. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/.gitignore +0 -0
  8. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/.python-version +0 -0
  9. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/.vscode/settings.json +0 -0
  10. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/LICENSE +0 -0
  11. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/README.md +0 -0
  12. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/src/stinger_python_utils/__init__.py +0 -0
  13. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/src/stinger_python_utils/mcp/__init__.py +0 -0
  14. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/src/stinger_python_utils/mcp/__main__.py +0 -0
  15. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/src/stinger_python_utils/message_creator.py +0 -0
  16. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/src/stinger_python_utils/return_codes.py +0 -0
  17. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/test/__init__.py +0 -0
  18. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/test/test_message_creator.py +0 -0
  19. {stinger_python_utils-0.1.8rc4 → stinger_python_utils-0.1.8rc5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stinger-python-utils
3
- Version: 0.1.8rc4
3
+ Version: 0.1.8rc5
4
4
  Summary: Common utilities for Stinger Python services.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stinger-python-utils"
7
- version = "0.1.8rc4"
7
+ version = "0.1.8rc5"
8
8
  description = "Common utilities for Stinger Python services."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -17,6 +17,8 @@ from abc import ABC, abstractmethod
17
17
  from dataclasses import dataclass, field
18
18
  from typing import Any
19
19
 
20
+ from pydantic import BaseModel
21
+
20
22
 
21
23
  # ------------------------------------------------------------------
22
24
  # Data models
@@ -66,19 +68,18 @@ class MethodDefinition:
66
68
  """Describes a callable method on a stinger-ipc client.
67
69
 
68
70
  Each method is exposed as an MCP **tool** whose ``inputSchema`` is
69
- *arguments_schema*.
71
+ derived from *arguments_model* via ``model_json_schema()``.
70
72
 
71
- *arguments_schema* must be a valid `JSON Schema`_ of type
72
- ``"object"``. The property names **must** match the keyword
73
- argument names accepted by the client method.
73
+ *arguments_model* must be a :class:`pydantic.BaseModel` subclass.
74
+ When the tool is invoked, the raw JSON arguments are loaded into
75
+ an instance of this model and the model is passed to
76
+ ``call_{method_name}`` on the client.
74
77
 
75
- .. _JSON Schema: https://json-schema.org/
78
+ If *arguments_model* is ``None`` the tool accepts no arguments.
76
79
  """
77
80
 
78
81
  name: str
79
- arguments_schema: dict[str, Any] = field(
80
- default_factory=lambda: {"type": "object"}
81
- )
82
+ arguments_model: type[BaseModel] | None = None
82
83
  description: str = ""
83
84
 
84
85
 
@@ -189,19 +190,24 @@ class StingerMCPPlugin(ABC):
189
190
  setattr(client, prop_name, list(arguments.values())[0])
190
191
 
191
192
  def call_method(
192
- self, client: Any, method_name: str, arguments: dict[str, Any]
193
+ self, client: Any, method_name: str, arguments: BaseModel | None
193
194
  ) -> Any:
194
195
  """Invoke *method_name* on *client* with *arguments*.
195
196
 
197
+ *arguments* is a validated :class:`pydantic.BaseModel` instance
198
+ (or ``None`` when the method takes no parameters).
199
+
196
200
  The default implementation calls::
197
201
 
198
- getattr(client, method_name)(**arguments)
202
+ getattr(client, f"call_{method_name}")(arguments)
199
203
 
200
204
  and returns whatever the method returns (typically a
201
205
  ``concurrent.futures.Future``).
202
206
  """
203
207
  method = getattr(client, f"call_{method_name}")
204
- return method(**arguments)
208
+ if arguments is None:
209
+ return method()
210
+ return method(arguments)
205
211
 
206
212
  def serialize_property(self, prop_name: str, value: Any) -> str:
207
213
  """Serialize a property *value* to a JSON string for the MCP resource.
@@ -193,7 +193,11 @@ class StingerMCPServer:
193
193
  or f"Call {mdef.name} on {state.plugin_name} "
194
194
  f"instance {state.instance_id}"
195
195
  ),
196
- inputSchema=mdef.arguments_schema,
196
+ inputSchema=(
197
+ mdef.arguments_model.model_json_schema()
198
+ if mdef.arguments_model is not None
199
+ else {"type": "object"}
200
+ ),
197
201
  )
198
202
  )
199
203
 
@@ -214,8 +218,8 @@ class StingerMCPServer:
214
218
 
215
219
  def _resolve_tool(
216
220
  self, name: str
217
- ) -> tuple[InstanceState, str, str] | None:
218
- """Map a tool *name* → ``(state, kind, item_name)``.
221
+ ) -> tuple[InstanceState, str, MethodDefinition | PropertyDefinition] | None:
222
+ """Map a tool *name* → ``(state, kind, definition)``.
219
223
 
220
224
  *kind* is ``"method"`` or ``"property"``. Methods are checked
221
225
  first so that a method named ``set_foo`` takes precedence over
@@ -235,7 +239,7 @@ class StingerMCPServer:
235
239
  # Methods take priority
236
240
  for mdef in state.plugin.get_methods():
237
241
  if _sanitize(mdef.name) == remainder:
238
- return state, "method", mdef.name
242
+ return state, "method", mdef
239
243
 
240
244
  # Property setters: set_<prop_name>
241
245
  if remainder.startswith("set_"):
@@ -245,7 +249,7 @@ class StingerMCPServer:
245
249
  _sanitize(pdef.name) == prop_token
246
250
  and not pdef.readonly
247
251
  ):
248
- return state, "property", pdef.name
252
+ return state, "property", pdef
249
253
 
250
254
  return None
251
255
 
@@ -256,23 +260,32 @@ class StingerMCPServer:
256
260
  if target is None:
257
261
  raise ValueError(f"Unknown tool: {name}")
258
262
 
259
- state, kind, item_name = target
263
+ state, kind, defn = target
260
264
 
261
265
  if kind == "property":
262
266
  try:
263
- state.plugin.write_property(state.client, item_name, arguments)
264
- text = json.dumps({"status": "ok", "property": item_name})
267
+ state.plugin.write_property(
268
+ state.client, defn.name, arguments
269
+ )
270
+ text = json.dumps({"status": "ok", "property": defn.name})
265
271
  except Exception as exc:
266
- logger.exception("Error setting property %s", item_name)
272
+ logger.exception("Error setting property %s", defn.name)
267
273
  text = json.dumps(
268
274
  {"status": "error", "error": str(exc)}, default=str
269
275
  )
270
276
  return [types.TextContent(type="text", text=text)]
271
277
 
272
278
  # kind == "method"
279
+ assert isinstance(defn, MethodDefinition)
273
280
  try:
281
+ # Load the pydantic model from the raw arguments
282
+ model = (
283
+ defn.arguments_model(**arguments)
284
+ if defn.arguments_model is not None
285
+ else None
286
+ )
274
287
  result = state.plugin.call_method(
275
- state.client, item_name, arguments
288
+ state.client, defn.name, model
276
289
  )
277
290
  if isinstance(result, Future):
278
291
  result = await _resolve_future(result)
@@ -284,7 +297,7 @@ class StingerMCPServer:
284
297
  else:
285
298
  text = json.dumps(result, default=str)
286
299
  except Exception as exc:
287
- logger.exception("Error calling method %s", item_name)
300
+ logger.exception("Error calling method %s", defn.name)
288
301
  text = json.dumps(
289
302
  {"status": "error", "error": str(exc)}, default=str
290
303
  )