mcp-mesh 0.8.1__py3-none-any.whl → 0.9.0b2__py3-none-any.whl
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.
- _mcp_mesh/__init__.py +1 -1
- _mcp_mesh/engine/mesh_llm_agent.py +17 -10
- _mcp_mesh/engine/mesh_llm_agent_injector.py +27 -10
- _mcp_mesh/engine/provider_handlers/__init__.py +2 -0
- _mcp_mesh/engine/provider_handlers/base_provider_handler.py +177 -0
- _mcp_mesh/engine/provider_handlers/claude_handler.py +129 -102
- _mcp_mesh/engine/provider_handlers/gemini_handler.py +218 -48
- _mcp_mesh/engine/provider_handlers/generic_handler.py +31 -0
- _mcp_mesh/engine/provider_handlers/openai_handler.py +4 -1
- _mcp_mesh/pipeline/mcp_heartbeat/rust_heartbeat.py +18 -0
- _mcp_mesh/tracing/trace_context_helper.py +5 -3
- {mcp_mesh-0.8.1.dist-info → mcp_mesh-0.9.0b2.dist-info}/METADATA +2 -2
- {mcp_mesh-0.8.1.dist-info → mcp_mesh-0.9.0b2.dist-info}/RECORD +16 -16
- mesh/helpers.py +39 -46
- {mcp_mesh-0.8.1.dist-info → mcp_mesh-0.9.0b2.dist-info}/WHEEL +0 -0
- {mcp_mesh-0.8.1.dist-info → mcp_mesh-0.9.0b2.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,15 +4,17 @@ Claude/Anthropic provider handler.
|
|
|
4
4
|
Optimized for Claude API (Claude 3.x, Sonnet, Opus, Haiku)
|
|
5
5
|
using Anthropic's best practices for tool calling and JSON responses.
|
|
6
6
|
|
|
7
|
-
Supports
|
|
8
|
-
-
|
|
9
|
-
- hint: Use prompt-based JSON instructions (medium speed, ~95% reliable)
|
|
7
|
+
Supports two output modes:
|
|
8
|
+
- hint: Use prompt-based JSON instructions with DECISION GUIDE (~95% reliable)
|
|
10
9
|
- text: Plain text output for str return types (fastest)
|
|
11
10
|
|
|
11
|
+
Native response_format (strict mode) is NOT used due to cross-runtime
|
|
12
|
+
incompatibilities when tools are present, and grammar compilation overhead.
|
|
13
|
+
|
|
12
14
|
Features:
|
|
13
15
|
- Automatic prompt caching for system messages (up to 90% cost reduction)
|
|
14
16
|
- Anti-XML tool calling instructions
|
|
15
|
-
-
|
|
17
|
+
- DECISION GUIDE for tool vs. direct JSON response decisions
|
|
16
18
|
"""
|
|
17
19
|
|
|
18
20
|
import json
|
|
@@ -23,15 +25,16 @@ from pydantic import BaseModel
|
|
|
23
25
|
|
|
24
26
|
from .base_provider_handler import (
|
|
25
27
|
BASE_TOOL_INSTRUCTIONS,
|
|
26
|
-
BaseProviderHandler,
|
|
27
28
|
CLAUDE_ANTI_XML_INSTRUCTION,
|
|
28
|
-
|
|
29
|
+
BaseProviderHandler,
|
|
29
30
|
)
|
|
30
31
|
|
|
31
32
|
logger = logging.getLogger(__name__)
|
|
32
33
|
|
|
33
34
|
# Output mode constants
|
|
34
|
-
OUTPUT_MODE_STRICT =
|
|
35
|
+
OUTPUT_MODE_STRICT = (
|
|
36
|
+
"strict" # Unused for Claude (kept for override_mode compatibility)
|
|
37
|
+
)
|
|
35
38
|
OUTPUT_MODE_HINT = "hint"
|
|
36
39
|
OUTPUT_MODE_TEXT = "text"
|
|
37
40
|
|
|
@@ -42,19 +45,20 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
42
45
|
|
|
43
46
|
Claude Characteristics:
|
|
44
47
|
- Excellent at following detailed instructions
|
|
45
|
-
- Native structured output via response_format (requires strict schema)
|
|
46
48
|
- Native tool calling (via Anthropic messages API)
|
|
47
49
|
- Performs best with anti-XML tool calling instructions
|
|
48
50
|
- Automatic prompt caching for cost optimization
|
|
49
51
|
|
|
50
|
-
Output Modes:
|
|
51
|
-
-
|
|
52
|
-
- hint: JSON schema in prompt (medium speed, usually valid JSON)
|
|
52
|
+
Output Modes (TEXT + HINT only):
|
|
53
|
+
- hint: JSON schema in prompt with DECISION GUIDE (~95% reliable)
|
|
53
54
|
- text: Plain text output for str return types (fastest)
|
|
54
55
|
|
|
56
|
+
Native response_format (strict mode) is not used. HINT mode with
|
|
57
|
+
detailed prompt instructions provides sufficient reliability (~95%)
|
|
58
|
+
without the cross-runtime incompatibilities and grammar compilation
|
|
59
|
+
overhead of native structured output.
|
|
60
|
+
|
|
55
61
|
Best Practices (from Anthropic docs):
|
|
56
|
-
- Use response_format for guaranteed JSON schema compliance
|
|
57
|
-
- Schema must have additionalProperties: false on all objects
|
|
58
62
|
- Add anti-XML instructions to prevent <invoke> style tool calls
|
|
59
63
|
- Use one tool call at a time for better reliability
|
|
60
64
|
- Use cache_control for system prompts to reduce costs
|
|
@@ -64,62 +68,18 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
64
68
|
"""Initialize Claude handler."""
|
|
65
69
|
super().__init__(vendor="anthropic")
|
|
66
70
|
|
|
67
|
-
def _is_simple_schema(self, model_class: type[BaseModel]) -> bool:
|
|
68
|
-
"""
|
|
69
|
-
Check if a Pydantic model has a simple schema.
|
|
70
|
-
|
|
71
|
-
Simple schema criteria:
|
|
72
|
-
- Less than 5 fields
|
|
73
|
-
- All fields are basic types (str, int, float, bool, list, Optional)
|
|
74
|
-
- No nested Pydantic models
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
model_class: Pydantic model class
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
True if schema is simple, False otherwise
|
|
81
|
-
"""
|
|
82
|
-
try:
|
|
83
|
-
schema = model_class.model_json_schema()
|
|
84
|
-
properties = schema.get("properties", {})
|
|
85
|
-
|
|
86
|
-
# Check field count
|
|
87
|
-
if len(properties) >= 5:
|
|
88
|
-
return False
|
|
89
|
-
|
|
90
|
-
# Check for nested objects or complex types
|
|
91
|
-
for field_name, field_schema in properties.items():
|
|
92
|
-
field_type = field_schema.get("type")
|
|
93
|
-
|
|
94
|
-
# Check for nested objects (indicates nested Pydantic model)
|
|
95
|
-
if field_type == "object" and "properties" in field_schema:
|
|
96
|
-
return False
|
|
97
|
-
|
|
98
|
-
# Check for $ref (nested model reference)
|
|
99
|
-
if "$ref" in field_schema:
|
|
100
|
-
return False
|
|
101
|
-
|
|
102
|
-
# Check array items for complex types
|
|
103
|
-
if field_type == "array":
|
|
104
|
-
items = field_schema.get("items", {})
|
|
105
|
-
if items.get("type") == "object" or "$ref" in items:
|
|
106
|
-
return False
|
|
107
|
-
|
|
108
|
-
return True
|
|
109
|
-
except Exception:
|
|
110
|
-
return False
|
|
111
|
-
|
|
112
71
|
def determine_output_mode(
|
|
113
72
|
self, output_type: type, override_mode: Optional[str] = None
|
|
114
73
|
) -> str:
|
|
115
74
|
"""
|
|
116
75
|
Determine the output mode based on return type.
|
|
117
76
|
|
|
77
|
+
Strategy: TEXT + HINT only. No STRICT mode for Claude.
|
|
78
|
+
|
|
118
79
|
Logic:
|
|
119
80
|
- If override_mode specified, use it
|
|
120
81
|
- If return type is str, use "text" mode
|
|
121
|
-
-
|
|
122
|
-
- Otherwise, use "strict" mode
|
|
82
|
+
- All schema types use "hint" mode (prompt-based JSON instructions)
|
|
123
83
|
|
|
124
84
|
Args:
|
|
125
85
|
output_type: Return type (str or BaseModel subclass)
|
|
@@ -136,15 +96,11 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
136
96
|
if output_type is str:
|
|
137
97
|
return OUTPUT_MODE_TEXT
|
|
138
98
|
|
|
139
|
-
#
|
|
99
|
+
# All schema types use HINT mode -- no STRICT for Claude
|
|
140
100
|
if isinstance(output_type, type) and issubclass(output_type, BaseModel):
|
|
141
|
-
|
|
142
|
-
return OUTPUT_MODE_HINT
|
|
143
|
-
else:
|
|
144
|
-
return OUTPUT_MODE_STRICT
|
|
101
|
+
return OUTPUT_MODE_HINT
|
|
145
102
|
|
|
146
|
-
|
|
147
|
-
return OUTPUT_MODE_STRICT
|
|
103
|
+
return OUTPUT_MODE_HINT
|
|
148
104
|
|
|
149
105
|
def _apply_prompt_caching(
|
|
150
106
|
self, messages: list[dict[str, Any]]
|
|
@@ -224,9 +180,8 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
224
180
|
"""
|
|
225
181
|
Prepare request parameters for Claude API with output mode support.
|
|
226
182
|
|
|
227
|
-
Output Mode Strategy:
|
|
228
|
-
-
|
|
229
|
-
- hint: No response_format, rely on prompt instructions (medium speed)
|
|
183
|
+
Output Mode Strategy (TEXT + HINT only):
|
|
184
|
+
- hint: No response_format, rely on prompt instructions (~95% reliable)
|
|
230
185
|
- text: No response_format, plain text output (fastest)
|
|
231
186
|
|
|
232
187
|
Args:
|
|
@@ -238,9 +193,8 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
238
193
|
Returns:
|
|
239
194
|
Dictionary of parameters for litellm.completion()
|
|
240
195
|
"""
|
|
241
|
-
# Extract output_mode from kwargs
|
|
242
|
-
|
|
243
|
-
determined_mode = self.determine_output_mode(output_type, output_mode)
|
|
196
|
+
# Extract output_mode from kwargs to prevent it leaking into request params
|
|
197
|
+
kwargs.pop("output_mode", None)
|
|
244
198
|
|
|
245
199
|
# Remove response_format from kwargs - we control this based on output mode
|
|
246
200
|
# The decorator's response_format="json" is just a hint for parsing, not API param
|
|
@@ -259,22 +213,6 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
259
213
|
if tools:
|
|
260
214
|
request_params["tools"] = tools
|
|
261
215
|
|
|
262
|
-
# Only add response_format in "strict" mode
|
|
263
|
-
if determined_mode == OUTPUT_MODE_STRICT:
|
|
264
|
-
# Claude requires additionalProperties: false on all object types
|
|
265
|
-
# Unlike OpenAI/Gemini, Claude doesn't require all properties in 'required'
|
|
266
|
-
if isinstance(output_type, type) and issubclass(output_type, BaseModel):
|
|
267
|
-
schema = output_type.model_json_schema()
|
|
268
|
-
strict_schema = make_schema_strict(schema, add_all_required=False)
|
|
269
|
-
request_params["response_format"] = {
|
|
270
|
-
"type": "json_schema",
|
|
271
|
-
"json_schema": {
|
|
272
|
-
"name": output_type.__name__,
|
|
273
|
-
"schema": strict_schema,
|
|
274
|
-
"strict": False, # Allow optional fields with defaults
|
|
275
|
-
},
|
|
276
|
-
}
|
|
277
|
-
|
|
278
216
|
return request_params
|
|
279
217
|
|
|
280
218
|
def format_system_prompt(
|
|
@@ -287,9 +225,8 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
287
225
|
"""
|
|
288
226
|
Format system prompt for Claude with output mode support.
|
|
289
227
|
|
|
290
|
-
Output Mode Strategy:
|
|
291
|
-
-
|
|
292
|
-
- hint: Add detailed JSON schema instructions in prompt
|
|
228
|
+
Output Mode Strategy (TEXT + HINT only):
|
|
229
|
+
- hint: Add detailed JSON schema instructions with DECISION GUIDE in prompt
|
|
293
230
|
- text: No JSON instructions (plain text output)
|
|
294
231
|
|
|
295
232
|
Args:
|
|
@@ -319,13 +256,8 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
319
256
|
# Text mode: No JSON instructions
|
|
320
257
|
pass
|
|
321
258
|
|
|
322
|
-
elif determined_mode == OUTPUT_MODE_STRICT:
|
|
323
|
-
# Strict mode: Minimal instructions (response_format handles schema)
|
|
324
|
-
if isinstance(output_type, type) and issubclass(output_type, BaseModel):
|
|
325
|
-
system_content += f"\n\nYour final response will be structured as JSON matching the {output_type.__name__} format."
|
|
326
|
-
|
|
327
259
|
elif determined_mode == OUTPUT_MODE_HINT:
|
|
328
|
-
# Hint mode: Add detailed JSON schema instructions
|
|
260
|
+
# Hint mode: Add detailed JSON schema instructions with DECISION GUIDE
|
|
329
261
|
if isinstance(output_type, type) and issubclass(output_type, BaseModel):
|
|
330
262
|
schema = output_type.model_json_schema()
|
|
331
263
|
properties = schema.get("properties", {})
|
|
@@ -344,8 +276,19 @@ class ClaudeHandler(BaseProviderHandler):
|
|
|
344
276
|
)
|
|
345
277
|
|
|
346
278
|
fields_text = "\n".join(field_descriptions)
|
|
347
|
-
system_content += f"""
|
|
348
279
|
|
|
280
|
+
# Add DECISION GUIDE when tools are present
|
|
281
|
+
decision_guide = ""
|
|
282
|
+
if tool_schemas:
|
|
283
|
+
decision_guide = """
|
|
284
|
+
DECISION GUIDE:
|
|
285
|
+
- If your answer requires real-time data (weather, calculations, etc.), call the appropriate tool FIRST, then format your response as JSON.
|
|
286
|
+
- If your answer is general knowledge (like facts, explanations, definitions), directly return your response as JSON WITHOUT calling tools.
|
|
287
|
+
- After calling a tool and receiving results, STOP calling tools and return your final JSON response.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
system_content += f"""
|
|
291
|
+
{decision_guide}
|
|
349
292
|
RESPONSE FORMAT:
|
|
350
293
|
You MUST respond with valid JSON matching this schema:
|
|
351
294
|
{{
|
|
@@ -355,7 +298,10 @@ You MUST respond with valid JSON matching this schema:
|
|
|
355
298
|
Example format:
|
|
356
299
|
{json.dumps({k: f"<{v.get('type', 'value')}>" for k, v in properties.items()}, indent=2)}
|
|
357
300
|
|
|
358
|
-
|
|
301
|
+
CRITICAL: Your response must be ONLY the raw JSON object.
|
|
302
|
+
- DO NOT wrap in markdown code fences (```json or ```)
|
|
303
|
+
- DO NOT include any text before or after the JSON
|
|
304
|
+
- Start directly with {{ and end with }}"""
|
|
359
305
|
|
|
360
306
|
return system_content
|
|
361
307
|
|
|
@@ -368,9 +314,90 @@ IMPORTANT: Respond ONLY with valid JSON. No markdown code fences, no preamble te
|
|
|
368
314
|
"""
|
|
369
315
|
return {
|
|
370
316
|
"native_tool_calling": True, # Claude has native function calling
|
|
371
|
-
"structured_output":
|
|
317
|
+
"structured_output": False, # Uses HINT mode (prompt-based), not native response_format
|
|
372
318
|
"streaming": True, # Supports streaming
|
|
373
319
|
"vision": True, # Claude 3+ supports vision
|
|
374
|
-
"json_mode":
|
|
320
|
+
"json_mode": False, # No native JSON mode used
|
|
375
321
|
"prompt_caching": True, # Automatic system prompt caching for cost savings
|
|
376
322
|
}
|
|
323
|
+
|
|
324
|
+
def apply_structured_output(
|
|
325
|
+
self,
|
|
326
|
+
output_schema: dict[str, Any],
|
|
327
|
+
output_type_name: Optional[str],
|
|
328
|
+
model_params: dict[str, Any],
|
|
329
|
+
) -> dict[str, Any]:
|
|
330
|
+
"""
|
|
331
|
+
Apply Claude-specific structured output for mesh delegation using HINT mode.
|
|
332
|
+
|
|
333
|
+
Instead of using response_format (strict mode), injects detailed JSON schema
|
|
334
|
+
instructions into the system message. This is consistent with the TEXT + HINT
|
|
335
|
+
only strategy and avoids cross-runtime incompatibilities.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
output_schema: JSON schema dict from consumer
|
|
339
|
+
output_type_name: Name of the output type (e.g., "AnalysisResult")
|
|
340
|
+
model_params: Current model parameters dict (will be modified)
|
|
341
|
+
|
|
342
|
+
Returns:
|
|
343
|
+
Modified model_params with HINT-mode instructions in system prompt
|
|
344
|
+
"""
|
|
345
|
+
# Build HINT mode instructions from the schema
|
|
346
|
+
properties = output_schema.get("properties", {})
|
|
347
|
+
required = output_schema.get("required", [])
|
|
348
|
+
|
|
349
|
+
field_descriptions = []
|
|
350
|
+
for field_name, field_schema in properties.items():
|
|
351
|
+
field_type = field_schema.get("type", "any")
|
|
352
|
+
is_required = field_name in required
|
|
353
|
+
req_marker = " (required)" if is_required else " (optional)"
|
|
354
|
+
desc = field_schema.get("description", "")
|
|
355
|
+
desc_text = f" - {desc}" if desc else ""
|
|
356
|
+
field_descriptions.append(
|
|
357
|
+
f" - {field_name}: {field_type}{req_marker}{desc_text}"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
fields_text = "\n".join(field_descriptions)
|
|
361
|
+
type_name = output_type_name or "Response"
|
|
362
|
+
|
|
363
|
+
hint_instructions = f"""
|
|
364
|
+
|
|
365
|
+
DECISION GUIDE:
|
|
366
|
+
- If your answer requires real-time data (weather, calculations, etc.), call the appropriate tool FIRST, then format your response as JSON.
|
|
367
|
+
- If your answer is general knowledge, directly return your response as JSON WITHOUT calling tools.
|
|
368
|
+
- After calling a tool and receiving results, STOP calling tools and return your final JSON response.
|
|
369
|
+
|
|
370
|
+
RESPONSE FORMAT:
|
|
371
|
+
You MUST respond with valid JSON matching this schema:
|
|
372
|
+
{{
|
|
373
|
+
{fields_text}
|
|
374
|
+
}}
|
|
375
|
+
|
|
376
|
+
Example format:
|
|
377
|
+
{json.dumps({k: f"<{v.get('type', 'value')}>" for k, v in properties.items()}, indent=2)}
|
|
378
|
+
|
|
379
|
+
CRITICAL: Your response must be ONLY the raw JSON object.
|
|
380
|
+
- DO NOT wrap in markdown code fences (```json or ```)
|
|
381
|
+
- DO NOT include any text before or after the JSON
|
|
382
|
+
- Start directly with {{ and end with }}"""
|
|
383
|
+
|
|
384
|
+
# Inject into system message
|
|
385
|
+
messages = model_params.get("messages", [])
|
|
386
|
+
for msg in messages:
|
|
387
|
+
if msg.get("role") == "system":
|
|
388
|
+
content = msg.get("content", "")
|
|
389
|
+
if isinstance(content, str):
|
|
390
|
+
msg["content"] = content + hint_instructions
|
|
391
|
+
elif isinstance(content, list):
|
|
392
|
+
# Content block format -- append to last text block
|
|
393
|
+
for block in reversed(content):
|
|
394
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
395
|
+
block["text"] = block["text"] + hint_instructions
|
|
396
|
+
break
|
|
397
|
+
break
|
|
398
|
+
|
|
399
|
+
logger.info(
|
|
400
|
+
f"Claude hint mode for '{type_name}' "
|
|
401
|
+
f"(mesh delegation, schema in prompt)"
|
|
402
|
+
)
|
|
403
|
+
return model_params
|