mcp-mesh 0.6.2__tar.gz → 0.6.3__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 (151) hide show
  1. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/PKG-INFO +1 -1
  2. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/__init__.py +1 -1
  3. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/llm_config.py +10 -1
  4. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/mesh_llm_agent.py +51 -33
  5. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/mesh_llm_agent_injector.py +19 -0
  6. mcp_mesh-0.6.3/_mcp_mesh/engine/provider_handlers/claude_handler.py +418 -0
  7. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/provider_handlers/openai_handler.py +65 -9
  8. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/response_parser.py +54 -15
  9. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/mesh/decorators.py +39 -2
  10. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/pyproject.toml +4 -4
  11. mcp_mesh-0.6.2/_mcp_mesh/engine/provider_handlers/claude_handler.py +0 -138
  12. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/.gitignore +0 -0
  13. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/LICENSE +0 -0
  14. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/README.md +0 -0
  15. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/__init__.py +0 -0
  16. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/async_mcp_client.py +0 -0
  17. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/base_injector.py +0 -0
  18. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/decorator_registry.py +0 -0
  19. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/dependency_injector.py +0 -0
  20. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/full_mcp_proxy.py +0 -0
  21. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/http_wrapper.py +0 -0
  22. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/llm_errors.py +0 -0
  23. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/mcp_client_proxy.py +0 -0
  24. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/provider_handlers/__init__.py +0 -0
  25. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/provider_handlers/base_provider_handler.py +0 -0
  26. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/provider_handlers/generic_handler.py +0 -0
  27. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/provider_handlers/provider_handler_registry.py +0 -0
  28. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/self_dependency_proxy.py +0 -0
  29. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/session_aware_client.py +0 -0
  30. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/session_manager.py +0 -0
  31. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/signature_analyzer.py +0 -0
  32. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/tool_executor.py +0 -0
  33. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/tool_schema_builder.py +0 -0
  34. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/engine/unified_mcp_proxy.py +0 -0
  35. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/.openapi-generator/FILES +0 -0
  36. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/.openapi-generator/VERSION +0 -0
  37. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/.openapi-generator-ignore +0 -0
  38. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/__init__.py +0 -0
  39. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/api/__init__.py +0 -0
  40. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/api/agents_api.py +0 -0
  41. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/api/health_api.py +0 -0
  42. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/api/tracing_api.py +0 -0
  43. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/api_client.py +0 -0
  44. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/api_response.py +0 -0
  45. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/configuration.py +0 -0
  46. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/exceptions.py +0 -0
  47. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/__init__.py +0 -0
  48. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_info.py +0 -0
  49. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata.py +0 -0
  50. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner.py +0 -0
  51. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_metadata_dependencies_inner_one_of.py +0 -0
  52. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration.py +0 -0
  53. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agent_registration_metadata.py +0 -0
  54. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/agents_list_response.py +0 -0
  55. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/capability_info.py +0 -0
  56. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_metadata.py +0 -0
  57. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_agent_request.py +0 -0
  58. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/decorator_info.py +0 -0
  59. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_info.py +0 -0
  60. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/dependency_resolution_info.py +0 -0
  61. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/error_response.py +0 -0
  62. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/health_response.py +0 -0
  63. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request.py +0 -0
  64. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_request_metadata.py +0 -0
  65. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/heartbeat_response.py +0 -0
  66. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/llm_provider.py +0 -0
  67. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter.py +0 -0
  68. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner.py +0 -0
  69. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_filter_filter_inner_one_of.py +0 -0
  70. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/llm_tool_info.py +0 -0
  71. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_register_metadata.py +0 -0
  72. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_agent_registration.py +0 -0
  73. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response.py +0 -0
  74. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_registration_response_dependencies_resolved_value_inner.py +0 -0
  75. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_dependency_registration.py +0 -0
  76. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_register_metadata.py +0 -0
  77. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/mesh_tool_registration.py +0 -0
  78. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/registration_response.py +0 -0
  79. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/resolved_llm_provider.py +0 -0
  80. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/rich_dependency.py +0 -0
  81. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/root_response.py +0 -0
  82. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/standardized_dependency.py +0 -0
  83. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/models/trace_event.py +0 -0
  84. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/py.typed +0 -0
  85. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/generated/mcp_mesh_registry_client/rest.py +0 -0
  86. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/__init__.py +0 -0
  87. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/__init__.py +0 -0
  88. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_dependency_resolution.py +0 -0
  89. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_fast_heartbeat_check.py +0 -0
  90. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_health_check.py +0 -0
  91. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_orchestrator.py +0 -0
  92. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_pipeline.py +0 -0
  93. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_heartbeat_send.py +0 -0
  94. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_lifespan_integration.py +0 -0
  95. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_heartbeat/api_registry_connection.py +0 -0
  96. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_startup/__init__.py +0 -0
  97. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_startup/api_pipeline.py +0 -0
  98. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_startup/api_server_setup.py +0 -0
  99. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_startup/fastapi_discovery.py +0 -0
  100. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_startup/middleware_integration.py +0 -0
  101. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_startup/route_collection.py +0 -0
  102. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/api_startup/route_integration.py +0 -0
  103. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/__init__.py +0 -0
  104. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/dependency_resolution.py +0 -0
  105. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/fast_heartbeat_check.py +0 -0
  106. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_orchestrator.py +0 -0
  107. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_pipeline.py +0 -0
  108. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/heartbeat_send.py +0 -0
  109. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/lifespan_integration.py +0 -0
  110. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/llm_tools_resolution.py +0 -0
  111. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_heartbeat/registry_connection.py +0 -0
  112. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/__init__.py +0 -0
  113. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/configuration.py +0 -0
  114. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/decorator_collection.py +0 -0
  115. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/fastapiserver_setup.py +0 -0
  116. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/fastmcpserver_discovery.py +0 -0
  117. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/heartbeat_loop.py +0 -0
  118. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/heartbeat_preparation.py +0 -0
  119. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/server_discovery.py +0 -0
  120. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/startup_orchestrator.py +0 -0
  121. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/mcp_startup/startup_pipeline.py +0 -0
  122. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/shared/__init__.py +0 -0
  123. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/shared/base_step.py +0 -0
  124. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/shared/mesh_pipeline.py +0 -0
  125. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/shared/pipeline_types.py +0 -0
  126. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/pipeline/shared/registry_connection.py +0 -0
  127. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/__init__.py +0 -0
  128. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/config_resolver.py +0 -0
  129. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/content_extractor.py +0 -0
  130. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/defaults.py +0 -0
  131. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/fast_heartbeat_status.py +0 -0
  132. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/fastapi_middleware_manager.py +0 -0
  133. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/health_check_cache.py +0 -0
  134. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/host_resolver.py +0 -0
  135. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/logging_config.py +0 -0
  136. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/registry_client_wrapper.py +0 -0
  137. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/server_discovery.py +0 -0
  138. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/simple_shutdown.py +0 -0
  139. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/sse_parser.py +0 -0
  140. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/shared/support_types.py +0 -0
  141. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/tracing/agent_context_helper.py +0 -0
  142. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/tracing/context.py +0 -0
  143. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/tracing/execution_tracer.py +0 -0
  144. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/tracing/fastapi_tracing_middleware.py +0 -0
  145. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/tracing/redis_metadata_publisher.py +0 -0
  146. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/tracing/trace_context_helper.py +0 -0
  147. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/tracing/utils.py +0 -0
  148. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/_mcp_mesh/utils/fastmcp_schema_extractor.py +0 -0
  149. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/mesh/__init__.py +0 -0
  150. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/mesh/helpers.py +0 -0
  151. {mcp_mesh-0.6.2 → mcp_mesh-0.6.3}/mesh/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-mesh
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Kubernetes-native platform for distributed MCP applications
5
5
  Project-URL: Homepage, https://github.com/dhyansraj/mcp-mesh
6
6
  Project-URL: Documentation, https://github.com/dhyansraj/mcp-mesh/tree/main/docs
@@ -31,7 +31,7 @@ from .engine.decorator_registry import (
31
31
  get_decorator_stats,
32
32
  )
33
33
 
34
- __version__ = "0.6.2"
34
+ __version__ = "0.6.3"
35
35
 
36
36
  # Store reference to runtime processor if initialized
37
37
  _runtime_processor = None
@@ -17,7 +17,7 @@ class LLMConfig:
17
17
  Supports both direct LiteLLM providers (string) and mesh delegation (dict).
18
18
  """
19
19
 
20
- provider: Union[str, Dict[str, Any]] = "claude"
20
+ provider: Union[str, dict[str, Any]] = "claude"
21
21
  """LLM provider - string for direct LiteLLM (e.g., 'claude', 'openai') or dict for mesh delegation
22
22
  Mesh delegation format: {"capability": "llm", "tags": ["claude"], "version": ">=1.0.0"}"""
23
23
 
@@ -33,6 +33,9 @@ class LLMConfig:
33
33
  system_prompt: Optional[str] = None
34
34
  """Optional system prompt to prepend to all interactions"""
35
35
 
36
+ output_mode: Optional[str] = None
37
+ """Output mode override: 'strict', 'hint', or 'text'. If None, auto-detected by handler."""
38
+
36
39
  def __post_init__(self):
37
40
  """Validate configuration after initialization."""
38
41
  if self.max_iterations < 1:
@@ -43,3 +46,9 @@ class LLMConfig:
43
46
  # Only validate model for string providers (not needed for mesh delegation)
44
47
  if isinstance(self.provider, str) and not self.model:
45
48
  raise ValueError("model cannot be empty when using string provider")
49
+
50
+ # Validate output_mode if provided
51
+ if self.output_mode and self.output_mode not in ("strict", "hint", "text"):
52
+ raise ValueError(
53
+ f"output_mode must be 'strict', 'hint', or 'text', got '{self.output_mode}'"
54
+ )
@@ -58,7 +58,7 @@ class MeshLlmAgent:
58
58
  self,
59
59
  config: LLMConfig,
60
60
  filtered_tools: list[dict[str, Any]],
61
- output_type: type[BaseModel],
61
+ output_type: type[BaseModel] | type[str],
62
62
  tool_proxies: Optional[dict[str, Any]] = None,
63
63
  template_path: Optional[str] = None,
64
64
  context_value: Optional[Any] = None,
@@ -71,7 +71,7 @@ class MeshLlmAgent:
71
71
  Args:
72
72
  config: LLM configuration (provider, model, api_key, etc.)
73
73
  filtered_tools: List of tool metadata from registry (for schema building)
74
- output_type: Pydantic BaseModel for response validation
74
+ output_type: Pydantic BaseModel for response validation, or str for plain text
75
75
  tool_proxies: Optional map of function_name -> proxy for tool execution
76
76
  template_path: Optional path to Jinja2 template file for system prompt
77
77
  context_value: Optional context for template rendering (MeshContextModel, dict, or None)
@@ -87,6 +87,7 @@ class MeshLlmAgent:
87
87
  self.max_iterations = config.max_iterations
88
88
  self.output_type = output_type
89
89
  self.system_prompt = config.system_prompt # Public attribute for tests
90
+ self.output_mode = config.output_mode # Output mode override (strict/hint/text)
90
91
  self._iteration_count = 0
91
92
 
92
93
  # Detect if using mesh delegation (provider is dict)
@@ -126,12 +127,19 @@ IMPORTANT TOOL CALLING RULES:
126
127
  - Once you have gathered all necessary information, provide your final response
127
128
  """
128
129
 
129
- schema = self.output_type.model_json_schema()
130
- schema_str = json.dumps(schema, indent=2)
131
- self._cached_json_instructions = (
132
- f"\n\nIMPORTANT: You must return your final response as valid JSON matching this schema:\n"
133
- f"{schema_str}\n\nReturn ONLY the JSON object, no additional text."
134
- )
130
+ # Only generate JSON schema for Pydantic models, not for str return type
131
+ if self.output_type is not str and hasattr(
132
+ self.output_type, "model_json_schema"
133
+ ):
134
+ schema = self.output_type.model_json_schema()
135
+ schema_str = json.dumps(schema, indent=2)
136
+ self._cached_json_instructions = (
137
+ f"\n\nIMPORTANT: You must return your final response as valid JSON matching this schema:\n"
138
+ f"{schema_str}\n\nReturn ONLY the JSON object, no additional text."
139
+ )
140
+ else:
141
+ # str return type - no JSON schema needed
142
+ self._cached_json_instructions = ""
135
143
 
136
144
  logger.debug(
137
145
  f"🤖 MeshLlmAgent initialized: provider={config.provider}, model={config.model}, "
@@ -483,22 +491,36 @@ IMPORTANT TOOL CALLING RULES:
483
491
  try:
484
492
  # Call LLM (either direct LiteLLM or mesh-delegated)
485
493
  try:
486
- if self._is_mesh_delegated:
487
- # Mesh delegation: use provider handler to prepare vendor-specific request
488
- # Phase 2: Handler prepares params including response_format for OpenAI, etc.
489
- request_params = self._provider_handler.prepare_request(
490
- messages=messages,
491
- tools=self._tool_schemas if self._tool_schemas else None,
492
- output_type=self.output_type,
493
- **kwargs,
494
- )
494
+ # Build kwargs with output_mode override if set
495
+ call_kwargs = (
496
+ {**kwargs, "output_mode": self.output_mode}
497
+ if self.output_mode
498
+ else kwargs
499
+ )
500
+
501
+ # Use provider handler to prepare vendor-specific request
502
+ request_params = self._provider_handler.prepare_request(
503
+ messages=messages,
504
+ tools=self._tool_schemas if self._tool_schemas else None,
505
+ output_type=self.output_type,
506
+ **call_kwargs,
507
+ )
495
508
 
496
- # Extract model_params to send to provider
497
- # Don't send messages/tools (already separate params) or model/api_key (provider has them)
509
+ if self._is_mesh_delegated:
510
+ # Mesh delegation: extract model_params to send to provider
511
+ # Exclude messages/tools (separate params), model/api_key (provider has them),
512
+ # and output_mode (only used locally by prepare_request)
498
513
  model_params = {
499
514
  k: v
500
515
  for k, v in request_params.items()
501
- if k not in ["messages", "tools", "model", "api_key"]
516
+ if k
517
+ not in [
518
+ "messages",
519
+ "tools",
520
+ "model",
521
+ "api_key",
522
+ "output_mode",
523
+ ]
502
524
  }
503
525
 
504
526
  logger.debug(
@@ -509,19 +531,10 @@ IMPORTANT TOOL CALLING RULES:
509
531
  response = await self._call_mesh_provider(
510
532
  messages=messages,
511
533
  tools=self._tool_schemas if self._tool_schemas else None,
512
- **model_params, # Now includes response_format!
534
+ **model_params,
513
535
  )
514
536
  else:
515
- # Direct LiteLLM call
516
- # Phase 2: Use provider handler to prepare vendor-specific request
517
- request_params = self._provider_handler.prepare_request(
518
- messages=messages,
519
- tools=self._tool_schemas if self._tool_schemas else None,
520
- output_type=self.output_type,
521
- **kwargs,
522
- )
523
-
524
- # Add model and API key (common to all vendors)
537
+ # Direct LiteLLM call: add model and API key
525
538
  request_params["model"] = self.model
526
539
  request_params["api_key"] = self.api_key
527
540
 
@@ -612,15 +625,20 @@ IMPORTANT TOOL CALLING RULES:
612
625
  """
613
626
  Parse LLM response into output type.
614
627
 
615
- Delegates to ResponseParser for actual parsing logic.
628
+ For str return type, returns content directly without parsing.
629
+ For Pydantic models, delegates to ResponseParser.
616
630
 
617
631
  Args:
618
632
  content: Response content from LLM
619
633
 
620
634
  Returns:
621
- Parsed Pydantic model instance
635
+ Raw string (if output_type is str) or parsed Pydantic model instance
622
636
 
623
637
  Raises:
624
638
  ResponseParseError: If response doesn't match output_type schema or invalid JSON
625
639
  """
640
+ # For str return type, return content directly without parsing
641
+ if self.output_type is str:
642
+ return content
643
+
626
644
  return ResponseParser.parse(content, self.output_type)
@@ -307,6 +307,22 @@ class MeshLlmAgentInjector(BaseInjector):
307
307
  llm_agent = self._create_llm_agent(function_id)
308
308
  wrapper._mesh_update_llm_agent(llm_agent)
309
309
  logger.info(f"🔄 Updated wrapper with MeshLlmAgent for '{function_id}'")
310
+
311
+ # Set factory for per-call context agent creation (template support)
312
+ # This allows the decorator's wrapper to create new agents with context per-call
313
+ config_dict = llm_metadata.config
314
+ if config_dict.get("is_template", False):
315
+ # Capture function_id by value using default argument to avoid closure issues
316
+ def create_context_agent(
317
+ context_value: Any, _func_id: str = function_id
318
+ ) -> MeshLlmAgent:
319
+ """Factory to create MeshLlmAgent with context for template rendering."""
320
+ return self._create_llm_agent(_func_id, context_value=context_value)
321
+
322
+ wrapper._mesh_create_context_agent = create_context_agent
323
+ logger.info(
324
+ f"🎯 Set context agent factory for template-based function '{function_id}'"
325
+ )
310
326
  elif wrapper:
311
327
  logger.warning(
312
328
  f"⚠️ Wrapper for '{function_id}' found but has no _mesh_update_llm_agent method"
@@ -512,6 +528,9 @@ class MeshLlmAgentInjector(BaseInjector):
512
528
  api_key=config_dict.get("api_key", ""), # Will use ENV if empty
513
529
  max_iterations=config_dict.get("max_iterations", 10),
514
530
  system_prompt=config_dict.get("system_prompt"),
531
+ output_mode=config_dict.get(
532
+ "output_mode"
533
+ ), # Pass through output_mode from decorator
515
534
  )
516
535
 
517
536
  # Phase 4: Template support - extract template metadata
@@ -0,0 +1,418 @@
1
+ """
2
+ Claude/Anthropic provider handler.
3
+
4
+ Optimized for Claude API (Claude 3.x, Sonnet, Opus, Haiku)
5
+ using Anthropic's best practices for tool calling and JSON responses.
6
+
7
+ Supports three output modes for performance/reliability tradeoffs:
8
+ - strict: Use response_format for guaranteed schema compliance (slowest, 100% reliable)
9
+ - hint: Use prompt-based JSON instructions (medium speed, ~95% reliable)
10
+ - text: Plain text output for str return types (fastest)
11
+
12
+ Features:
13
+ - Automatic prompt caching for system messages (up to 90% cost reduction)
14
+ - Anti-XML tool calling instructions
15
+ - Output mode optimization based on return type
16
+ """
17
+
18
+ import json
19
+ import logging
20
+ from typing import Any, Optional, get_args, get_origin
21
+
22
+ from pydantic import BaseModel
23
+
24
+ from .base_provider_handler import BaseProviderHandler
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # Output mode constants
29
+ OUTPUT_MODE_STRICT = "strict"
30
+ OUTPUT_MODE_HINT = "hint"
31
+ OUTPUT_MODE_TEXT = "text"
32
+
33
+
34
+ class ClaudeHandler(BaseProviderHandler):
35
+ """
36
+ Provider handler for Claude/Anthropic models.
37
+
38
+ Claude Characteristics:
39
+ - Excellent at following detailed instructions
40
+ - Native structured output via response_format (requires strict schema)
41
+ - Native tool calling (via Anthropic messages API)
42
+ - Performs best with anti-XML tool calling instructions
43
+ - Automatic prompt caching for cost optimization
44
+
45
+ Output Modes:
46
+ - strict: response_format with JSON schema (slowest, guaranteed valid JSON)
47
+ - hint: JSON schema in prompt (medium speed, usually valid JSON)
48
+ - text: Plain text output for str return types (fastest)
49
+
50
+ Best Practices (from Anthropic docs):
51
+ - Use response_format for guaranteed JSON schema compliance
52
+ - Schema must have additionalProperties: false on all objects
53
+ - Add anti-XML instructions to prevent <invoke> style tool calls
54
+ - Use one tool call at a time for better reliability
55
+ - Use cache_control for system prompts to reduce costs
56
+ """
57
+
58
+ def __init__(self):
59
+ """Initialize Claude handler."""
60
+ super().__init__(vendor="anthropic")
61
+
62
+ def _is_simple_schema(self, model_class: type[BaseModel]) -> bool:
63
+ """
64
+ Check if a Pydantic model has a simple schema.
65
+
66
+ Simple schema criteria:
67
+ - Less than 5 fields
68
+ - All fields are basic types (str, int, float, bool, list, Optional)
69
+ - No nested Pydantic models
70
+
71
+ Args:
72
+ model_class: Pydantic model class
73
+
74
+ Returns:
75
+ True if schema is simple, False otherwise
76
+ """
77
+ try:
78
+ schema = model_class.model_json_schema()
79
+ properties = schema.get("properties", {})
80
+
81
+ # Check field count
82
+ if len(properties) >= 5:
83
+ return False
84
+
85
+ # Check for nested objects or complex types
86
+ for field_name, field_schema in properties.items():
87
+ field_type = field_schema.get("type")
88
+
89
+ # Check for nested objects (indicates nested Pydantic model)
90
+ if field_type == "object" and "properties" in field_schema:
91
+ return False
92
+
93
+ # Check for $ref (nested model reference)
94
+ if "$ref" in field_schema:
95
+ return False
96
+
97
+ # Check array items for complex types
98
+ if field_type == "array":
99
+ items = field_schema.get("items", {})
100
+ if items.get("type") == "object" or "$ref" in items:
101
+ return False
102
+
103
+ return True
104
+ except Exception:
105
+ return False
106
+
107
+ def determine_output_mode(
108
+ self, output_type: type, override_mode: Optional[str] = None
109
+ ) -> str:
110
+ """
111
+ Determine the output mode based on return type.
112
+
113
+ Logic:
114
+ - If override_mode specified, use it
115
+ - If return type is str, use "text" mode
116
+ - If return type is simple schema (<5 fields, basic types), use "hint" mode
117
+ - Otherwise, use "strict" mode
118
+
119
+ Args:
120
+ output_type: Return type (str or BaseModel subclass)
121
+ override_mode: Optional override ("strict", "hint", or "text")
122
+
123
+ Returns:
124
+ Output mode string
125
+ """
126
+ # Allow explicit override
127
+ if override_mode:
128
+ return override_mode
129
+
130
+ # String return type -> text mode
131
+ if output_type is str:
132
+ return OUTPUT_MODE_TEXT
133
+
134
+ # Check if it's a Pydantic model
135
+ if isinstance(output_type, type) and issubclass(output_type, BaseModel):
136
+ if self._is_simple_schema(output_type):
137
+ return OUTPUT_MODE_HINT
138
+ else:
139
+ return OUTPUT_MODE_STRICT
140
+
141
+ # Default to strict for unknown types
142
+ return OUTPUT_MODE_STRICT
143
+
144
+ def _make_schema_strict(self, schema: dict[str, Any]) -> dict[str, Any]:
145
+ """
146
+ Make a JSON schema strict for Claude's structured output.
147
+
148
+ Claude requires additionalProperties: false on all object types.
149
+ This recursively processes the schema to add this constraint.
150
+
151
+ Args:
152
+ schema: JSON schema dict
153
+
154
+ Returns:
155
+ Schema with additionalProperties: false on all objects
156
+ """
157
+ if not isinstance(schema, dict):
158
+ return schema
159
+
160
+ result = schema.copy()
161
+
162
+ # If this is an object type, add additionalProperties: false
163
+ if result.get("type") == "object":
164
+ result["additionalProperties"] = False
165
+
166
+ # Recursively process nested schemas
167
+ if "properties" in result:
168
+ result["properties"] = {
169
+ k: self._make_schema_strict(v) for k, v in result["properties"].items()
170
+ }
171
+
172
+ # Process $defs (Pydantic uses this for nested models)
173
+ if "$defs" in result:
174
+ result["$defs"] = {
175
+ k: self._make_schema_strict(v) for k, v in result["$defs"].items()
176
+ }
177
+
178
+ # Process items for arrays
179
+ if "items" in result:
180
+ result["items"] = self._make_schema_strict(result["items"])
181
+
182
+ # Process anyOf, oneOf, allOf
183
+ for key in ["anyOf", "oneOf", "allOf"]:
184
+ if key in result:
185
+ result[key] = [self._make_schema_strict(s) for s in result[key]]
186
+
187
+ return result
188
+
189
+ def _apply_prompt_caching(
190
+ self, messages: list[dict[str, Any]]
191
+ ) -> list[dict[str, Any]]:
192
+ """
193
+ Apply prompt caching to system messages for Claude.
194
+
195
+ Claude's prompt caching feature caches the system prompt prefix,
196
+ reducing costs by up to 90% and improving latency for repeated calls.
197
+
198
+ The cache_control with type "ephemeral" tells Claude to cache
199
+ this content for the duration of the session (typically 5 minutes).
200
+
201
+ Args:
202
+ messages: List of message dicts
203
+
204
+ Returns:
205
+ Messages with cache_control applied to system messages
206
+
207
+ Reference:
208
+ https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
209
+ """
210
+ cached_messages = []
211
+
212
+ for msg in messages:
213
+ if msg.get("role") == "system":
214
+ content = msg.get("content", "")
215
+
216
+ # Convert string content to cached content block format
217
+ if isinstance(content, str):
218
+ cached_msg = {
219
+ "role": "system",
220
+ "content": [
221
+ {
222
+ "type": "text",
223
+ "text": content,
224
+ "cache_control": {"type": "ephemeral"},
225
+ }
226
+ ],
227
+ }
228
+ cached_messages.append(cached_msg)
229
+ logger.debug(
230
+ f"🗄️ Applied prompt caching to system message ({len(content)} chars)"
231
+ )
232
+ elif isinstance(content, list):
233
+ # Already in content block format - add cache_control to last block
234
+ cached_content = []
235
+ for i, block in enumerate(content):
236
+ if isinstance(block, dict):
237
+ block_copy = block.copy()
238
+ # Add cache_control to the last text block
239
+ if i == len(content) - 1 and block.get("type") == "text":
240
+ block_copy["cache_control"] = {"type": "ephemeral"}
241
+ cached_content.append(block_copy)
242
+ else:
243
+ cached_content.append(block)
244
+ cached_messages.append(
245
+ {"role": "system", "content": cached_content}
246
+ )
247
+ logger.debug("🗄️ Applied prompt caching to system content blocks")
248
+ else:
249
+ # Unknown format - pass through unchanged
250
+ cached_messages.append(msg)
251
+ else:
252
+ # Non-system messages pass through unchanged
253
+ cached_messages.append(msg)
254
+
255
+ return cached_messages
256
+
257
+ def prepare_request(
258
+ self,
259
+ messages: list[dict[str, Any]],
260
+ tools: Optional[list[dict[str, Any]]],
261
+ output_type: type,
262
+ **kwargs: Any,
263
+ ) -> dict[str, Any]:
264
+ """
265
+ Prepare request parameters for Claude API with output mode support.
266
+
267
+ Output Mode Strategy:
268
+ - strict: Use response_format for guaranteed JSON schema compliance (slowest)
269
+ - hint: No response_format, rely on prompt instructions (medium speed)
270
+ - text: No response_format, plain text output (fastest)
271
+
272
+ Args:
273
+ messages: List of message dicts
274
+ tools: Optional list of tool schemas
275
+ output_type: Return type (str or Pydantic model)
276
+ **kwargs: Additional model parameters (may include output_mode override)
277
+
278
+ Returns:
279
+ Dictionary of parameters for litellm.completion()
280
+ """
281
+ # Extract output_mode from kwargs if provided
282
+ output_mode = kwargs.pop("output_mode", None)
283
+ determined_mode = self.determine_output_mode(output_type, output_mode)
284
+
285
+ # Remove response_format from kwargs - we control this based on output mode
286
+ # The decorator's response_format="json" is just a hint for parsing, not API param
287
+ kwargs.pop("response_format", None)
288
+
289
+ # Apply prompt caching to system messages for cost optimization
290
+ cached_messages = self._apply_prompt_caching(messages)
291
+
292
+ request_params = {
293
+ "messages": cached_messages,
294
+ **kwargs, # Pass through temperature, max_tokens, etc.
295
+ }
296
+
297
+ # Add tools if provided
298
+ # LiteLLM will convert OpenAI tool format to Anthropic's format
299
+ if tools:
300
+ request_params["tools"] = tools
301
+
302
+ # Only add response_format in "strict" mode
303
+ if determined_mode == OUTPUT_MODE_STRICT:
304
+ # Claude requires additionalProperties: false on all object types
305
+ if isinstance(output_type, type) and issubclass(output_type, BaseModel):
306
+ schema = output_type.model_json_schema()
307
+ strict_schema = self._make_schema_strict(schema)
308
+ request_params["response_format"] = {
309
+ "type": "json_schema",
310
+ "json_schema": {
311
+ "name": output_type.__name__,
312
+ "schema": strict_schema,
313
+ "strict": False, # Allow optional fields with defaults
314
+ },
315
+ }
316
+
317
+ return request_params
318
+
319
+ def format_system_prompt(
320
+ self,
321
+ base_prompt: str,
322
+ tool_schemas: Optional[list[dict[str, Any]]],
323
+ output_type: type,
324
+ output_mode: Optional[str] = None,
325
+ ) -> str:
326
+ """
327
+ Format system prompt for Claude with output mode support.
328
+
329
+ Output Mode Strategy:
330
+ - strict: Minimal JSON instructions (response_format handles schema)
331
+ - hint: Add detailed JSON schema instructions in prompt
332
+ - text: No JSON instructions (plain text output)
333
+
334
+ Args:
335
+ base_prompt: Base system prompt
336
+ tool_schemas: Optional tool schemas
337
+ output_type: Expected response type
338
+ output_mode: Optional override for output mode
339
+
340
+ Returns:
341
+ Formatted system prompt optimized for Claude
342
+ """
343
+ system_content = base_prompt
344
+ determined_mode = self.determine_output_mode(output_type, output_mode)
345
+
346
+ # Add tool calling instructions if tools available
347
+ # These prevent Claude from using XML-style <invoke> syntax
348
+ if tool_schemas:
349
+ system_content += """
350
+
351
+ IMPORTANT TOOL CALLING RULES:
352
+ - You have access to tools that you can call to gather information
353
+ - Make ONE tool call at a time
354
+ - NEVER use XML-style syntax like <invoke name="tool_name"/>
355
+ - After receiving tool results, you can make additional calls if needed
356
+ - Once you have all needed information, provide your final response
357
+ """
358
+
359
+ # Add output format instructions based on mode
360
+ if determined_mode == OUTPUT_MODE_TEXT:
361
+ # Text mode: No JSON instructions
362
+ pass
363
+
364
+ elif determined_mode == OUTPUT_MODE_STRICT:
365
+ # Strict mode: Minimal instructions (response_format handles schema)
366
+ if isinstance(output_type, type) and issubclass(output_type, BaseModel):
367
+ system_content += f"\n\nYour final response will be structured as JSON matching the {output_type.__name__} format."
368
+
369
+ elif determined_mode == OUTPUT_MODE_HINT:
370
+ # Hint mode: Add detailed JSON schema instructions
371
+ if isinstance(output_type, type) and issubclass(output_type, BaseModel):
372
+ schema = output_type.model_json_schema()
373
+ properties = schema.get("properties", {})
374
+ required = schema.get("required", [])
375
+
376
+ # Build human-readable schema description
377
+ field_descriptions = []
378
+ for field_name, field_schema in properties.items():
379
+ field_type = field_schema.get("type", "any")
380
+ is_required = field_name in required
381
+ req_marker = " (required)" if is_required else " (optional)"
382
+ desc = field_schema.get("description", "")
383
+ desc_text = f" - {desc}" if desc else ""
384
+ field_descriptions.append(
385
+ f" - {field_name}: {field_type}{req_marker}{desc_text}"
386
+ )
387
+
388
+ fields_text = "\n".join(field_descriptions)
389
+ system_content += f"""
390
+
391
+ RESPONSE FORMAT:
392
+ You MUST respond with valid JSON matching this schema:
393
+ {{
394
+ {fields_text}
395
+ }}
396
+
397
+ Example format:
398
+ {json.dumps({k: f"<{v.get('type', 'value')}>" for k, v in properties.items()}, indent=2)}
399
+
400
+ IMPORTANT: Respond ONLY with valid JSON. No markdown code fences, no preamble text."""
401
+
402
+ return system_content
403
+
404
+ def get_vendor_capabilities(self) -> dict[str, bool]:
405
+ """
406
+ Return Claude-specific capabilities.
407
+
408
+ Returns:
409
+ Capability flags for Claude
410
+ """
411
+ return {
412
+ "native_tool_calling": True, # Claude has native function calling
413
+ "structured_output": True, # Native response_format support via LiteLLM
414
+ "streaming": True, # Supports streaming
415
+ "vision": True, # Claude 3+ supports vision
416
+ "json_mode": True, # Native JSON mode via response_format
417
+ "prompt_caching": True, # Automatic system prompt caching for cost savings
418
+ }