qtype 0.1.11__py3-none-any.whl → 0.1.13__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.
Files changed (263) hide show
  1. qtype/` +0 -0
  2. qtype/application/__init__.py +0 -2
  3. qtype/application/converters/tools_from_api.py +67 -57
  4. qtype/application/converters/tools_from_module.py +66 -32
  5. qtype/base/types.py +6 -1
  6. qtype/commands/convert.py +3 -6
  7. qtype/commands/generate.py +97 -10
  8. qtype/commands/mcp.py +68 -0
  9. qtype/commands/run.py +116 -44
  10. qtype/commands/validate.py +4 -4
  11. qtype/docs/.pages +8 -0
  12. qtype/docs/Concepts/mental-model-and-philosophy.md +363 -0
  13. qtype/docs/Contributing/.pages +4 -0
  14. qtype/docs/Contributing/index.md +283 -0
  15. qtype/docs/Contributing/roadmap.md +81 -0
  16. qtype/docs/Decisions/ADR-001-Chat-vs-Completion-Endpoint-Features.md +56 -0
  17. qtype/docs/Gallery/dataflow_pipelines.md +81 -0
  18. qtype/docs/Gallery/dataflow_pipelines.mermaid +45 -0
  19. qtype/docs/Gallery/research_assistant.md +97 -0
  20. qtype/docs/Gallery/research_assistant.mermaid +42 -0
  21. qtype/docs/Gallery/simple_chatbot.md +38 -0
  22. qtype/docs/Gallery/simple_chatbot.mermaid +35 -0
  23. qtype/docs/How To/Authentication/configure_aws_authentication.md +60 -0
  24. qtype/docs/How To/Authentication/use_api_key_authentication.md +40 -0
  25. qtype/docs/How To/Command Line Usage/load_multiple_inputs_from_files.md +77 -0
  26. qtype/docs/How To/Command Line Usage/pass_inputs_on_the_cli.md +52 -0
  27. qtype/docs/How To/Command Line Usage/serve_with_auto_reload.md +27 -0
  28. qtype/docs/How To/Data Processing/adjust_concurrency.md +40 -0
  29. qtype/docs/How To/Data Processing/cache_step_results.md +71 -0
  30. qtype/docs/How To/Data Processing/decode_json_xml.md +24 -0
  31. qtype/docs/How To/Data Processing/explode_collections.md +40 -0
  32. qtype/docs/How To/Data Processing/gather_results.md +68 -0
  33. qtype/docs/How To/Data Processing/invoke_other_flows.md +71 -0
  34. qtype/docs/How To/Data Processing/load_data_from_athena.md +49 -0
  35. qtype/docs/How To/Data Processing/read_data_from_files.md +61 -0
  36. qtype/docs/How To/Data Processing/read_sql_databases.md +46 -0
  37. qtype/docs/How To/Data Processing/write_data_to_file.md +39 -0
  38. qtype/docs/How To/Invoke Models/call_large_language_models.md +51 -0
  39. qtype/docs/How To/Invoke Models/create_embeddings.md +49 -0
  40. qtype/docs/How To/Invoke Models/reuse_prompts_with_templates.md +38 -0
  41. qtype/docs/How To/Language Features/include_qtype_yaml.md +45 -0
  42. qtype/docs/How To/Language Features/include_raw_text_from_other_files.md +48 -0
  43. qtype/docs/How To/Language Features/reference_entities_by_id.md +51 -0
  44. qtype/docs/How To/Language Features/use_agent_skills.md +29 -0
  45. qtype/docs/How To/Language Features/use_environment_variables.md +48 -0
  46. qtype/docs/How To/Language Features/use_optional_variables.md +42 -0
  47. qtype/docs/How To/Language Features/use_qtype_mcp.md +59 -0
  48. qtype/docs/How To/Observability & Debugging/trace_calls_with_open_telemetry.md +49 -0
  49. qtype/docs/How To/Observability & Debugging/validate_qtype_yaml.md +36 -0
  50. qtype/docs/How To/Observability & Debugging/visualize_application_architecture.md +61 -0
  51. qtype/docs/How To/Observability & Debugging/visualize_example.mermaid +35 -0
  52. qtype/docs/How To/Qtype Server/flow_as_ui.png +0 -0
  53. qtype/docs/How To/Qtype Server/serve_flows_as_apis.md +40 -0
  54. qtype/docs/How To/Qtype Server/serve_flows_as_ui.md +41 -0
  55. qtype/docs/How To/Qtype Server/use_conversational_interfaces.md +56 -0
  56. qtype/docs/How To/Qtype Server/use_variables_with_ui_hints.md +48 -0
  57. qtype/docs/How To/Tools & Integration/bind_tool_inputs_and_outputs.md +47 -0
  58. qtype/docs/How To/Tools & Integration/create_tools_from_openapi_specifications.md +85 -0
  59. qtype/docs/How To/Tools & Integration/create_tools_from_python_modules.md +87 -0
  60. qtype/docs/Reference/cli.md +336 -0
  61. qtype/docs/Reference/plugins.md +99 -0
  62. qtype/docs/Reference/semantic-validation-rules.md +184 -0
  63. qtype/docs/Tutorials/.pages +1 -0
  64. qtype/docs/Tutorials/01-first-qtype-application.md +249 -0
  65. qtype/docs/Tutorials/02-conversational-chatbot.md +327 -0
  66. qtype/docs/Tutorials/03-structured-data.md +480 -0
  67. qtype/docs/Tutorials/04-tools-and-function-calling.md +476 -0
  68. qtype/docs/Tutorials/example_chat.png +0 -0
  69. qtype/docs/Tutorials/index.md +92 -0
  70. qtype/docs/components/APIKeyAuthProvider.md +7 -0
  71. qtype/docs/components/APITool.md +10 -0
  72. qtype/docs/components/AWSAuthProvider.md +13 -0
  73. qtype/docs/components/AWSSecretManager.md +5 -0
  74. qtype/docs/components/Agent.md +6 -0
  75. qtype/docs/components/Aggregate.md +7 -0
  76. qtype/docs/components/AggregateStats.md +7 -0
  77. qtype/docs/components/Application.md +22 -0
  78. qtype/docs/components/AuthorizationProvider.md +6 -0
  79. qtype/docs/components/AuthorizationProviderList.md +5 -0
  80. qtype/docs/components/BearerTokenAuthProvider.md +6 -0
  81. qtype/docs/components/BedrockReranker.md +8 -0
  82. qtype/docs/components/ChatContent.md +7 -0
  83. qtype/docs/components/ChatMessage.md +6 -0
  84. qtype/docs/components/Collect.md +6 -0
  85. qtype/docs/components/ConstantPath.md +5 -0
  86. qtype/docs/components/Construct.md +6 -0
  87. qtype/docs/components/CustomType.md +7 -0
  88. qtype/docs/components/Decoder.md +8 -0
  89. qtype/docs/components/DecoderFormat.md +8 -0
  90. qtype/docs/components/DocToTextConverter.md +7 -0
  91. qtype/docs/components/Document.md +7 -0
  92. qtype/docs/components/DocumentEmbedder.md +6 -0
  93. qtype/docs/components/DocumentIndex.md +7 -0
  94. qtype/docs/components/DocumentSearch.md +7 -0
  95. qtype/docs/components/DocumentSource.md +12 -0
  96. qtype/docs/components/DocumentSplitter.md +9 -0
  97. qtype/docs/components/Echo.md +8 -0
  98. qtype/docs/components/Embedding.md +7 -0
  99. qtype/docs/components/EmbeddingModel.md +6 -0
  100. qtype/docs/components/Explode.md +5 -0
  101. qtype/docs/components/FieldExtractor.md +21 -0
  102. qtype/docs/components/FileSource.md +6 -0
  103. qtype/docs/components/FileWriter.md +7 -0
  104. qtype/docs/components/Flow.md +14 -0
  105. qtype/docs/components/FlowInterface.md +7 -0
  106. qtype/docs/components/Index.md +8 -0
  107. qtype/docs/components/IndexUpsert.md +6 -0
  108. qtype/docs/components/InvokeEmbedding.md +7 -0
  109. qtype/docs/components/InvokeFlow.md +8 -0
  110. qtype/docs/components/InvokeTool.md +8 -0
  111. qtype/docs/components/LLMInference.md +9 -0
  112. qtype/docs/components/ListType.md +5 -0
  113. qtype/docs/components/Memory.md +8 -0
  114. qtype/docs/components/MessageRole.md +14 -0
  115. qtype/docs/components/Model.md +10 -0
  116. qtype/docs/components/ModelList.md +5 -0
  117. qtype/docs/components/OAuth2AuthProvider.md +9 -0
  118. qtype/docs/components/PrimitiveTypeEnum.md +20 -0
  119. qtype/docs/components/PromptTemplate.md +7 -0
  120. qtype/docs/components/PythonFunctionTool.md +7 -0
  121. qtype/docs/components/RAGChunk.md +7 -0
  122. qtype/docs/components/RAGDocument.md +10 -0
  123. qtype/docs/components/RAGSearchResult.md +8 -0
  124. qtype/docs/components/Reranker.md +5 -0
  125. qtype/docs/components/SQLSource.md +8 -0
  126. qtype/docs/components/Search.md +7 -0
  127. qtype/docs/components/SearchResult.md +7 -0
  128. qtype/docs/components/SecretManager.md +7 -0
  129. qtype/docs/components/SecretReference.md +7 -0
  130. qtype/docs/components/Source.md +5 -0
  131. qtype/docs/components/Step.md +8 -0
  132. qtype/docs/components/TelemetrySink.md +9 -0
  133. qtype/docs/components/Tool.md +9 -0
  134. qtype/docs/components/ToolList.md +5 -0
  135. qtype/docs/components/TypeList.md +5 -0
  136. qtype/docs/components/Variable.md +8 -0
  137. qtype/docs/components/VariableList.md +5 -0
  138. qtype/docs/components/VectorIndex.md +7 -0
  139. qtype/docs/components/VectorSearch.md +6 -0
  140. qtype/docs/components/VertexAuthProvider.md +9 -0
  141. qtype/docs/components/Writer.md +5 -0
  142. qtype/docs/example_ui.png +0 -0
  143. qtype/docs/index.md +81 -0
  144. qtype/docs/legacy_how_tos/.pages +6 -0
  145. qtype/docs/legacy_how_tos/Configuration/modular-yaml.md +366 -0
  146. qtype/docs/legacy_how_tos/Configuration/phoenix_projects.png +0 -0
  147. qtype/docs/legacy_how_tos/Configuration/phoenix_traces.png +0 -0
  148. qtype/docs/legacy_how_tos/Configuration/reference-by-id.md +251 -0
  149. qtype/docs/legacy_how_tos/Configuration/telemetry-setup.md +259 -0
  150. qtype/docs/legacy_how_tos/Data Types/custom-types.md +52 -0
  151. qtype/docs/legacy_how_tos/Data Types/domain-types.md +113 -0
  152. qtype/docs/legacy_how_tos/Debugging/visualize-apps.md +147 -0
  153. qtype/docs/legacy_how_tos/Tools/api-tools.md +29 -0
  154. qtype/docs/legacy_how_tos/Tools/python-tools.md +299 -0
  155. qtype/docs/skills/architect/SKILL.md +188 -0
  156. qtype/docs/skills/architect/references/cheatsheet.md +198 -0
  157. qtype/docs/skills/architect/references/patterns.md +29 -0
  158. qtype/docs/stylesheets/extra.css +27 -0
  159. qtype/dsl/custom_types.py +2 -1
  160. qtype/dsl/linker.py +23 -7
  161. qtype/dsl/loader.py +3 -3
  162. qtype/dsl/model.py +181 -67
  163. qtype/examples/authentication/aws_authentication.qtype.yaml +63 -0
  164. qtype/examples/conversational_ai/hello_world_chat.qtype.yaml +43 -0
  165. qtype/examples/conversational_ai/simple_chatbot.qtype.yaml +40 -0
  166. qtype/examples/data_processing/athena_query.qtype.yaml +56 -0
  167. qtype/examples/data_processing/batch_inputs.csv +5 -0
  168. qtype/examples/data_processing/batch_processing.qtype.yaml +54 -0
  169. qtype/examples/data_processing/cache_step_results.qtype.yaml +78 -0
  170. qtype/examples/data_processing/collect_results.qtype.yaml +55 -0
  171. qtype/examples/data_processing/create_sample_db.py +129 -0
  172. qtype/examples/data_processing/dataflow_pipelines.qtype.yaml +108 -0
  173. qtype/examples/data_processing/decode_json.qtype.yaml +23 -0
  174. qtype/examples/data_processing/explode_items.qtype.yaml +25 -0
  175. qtype/examples/data_processing/invoke_other_flows.qtype.yaml +98 -0
  176. qtype/examples/data_processing/read_file.qtype.yaml +60 -0
  177. qtype/examples/data_processing/reviews.db +0 -0
  178. qtype/examples/data_processing/sample_article.txt +1 -0
  179. qtype/examples/data_processing/sample_documents.jsonl +5 -0
  180. qtype/examples/invoke_models/create_embeddings.qtype.yaml +28 -0
  181. qtype/examples/invoke_models/simple_llm_call.qtype.yaml +32 -0
  182. qtype/examples/language_features/include_raw.qtype.yaml +27 -0
  183. qtype/examples/language_features/optional_variables.qtype.yaml +32 -0
  184. qtype/examples/language_features/story_prompt.txt +6 -0
  185. qtype/examples/language_features/ui_hints.qtype.yaml +52 -0
  186. qtype/examples/legacy/bedrock/data_analysis_with_telemetry.qtype.yaml +169 -0
  187. qtype/examples/legacy/bedrock/hello_world.qtype.yaml +39 -0
  188. qtype/examples/legacy/bedrock/hello_world_chat.qtype.yaml +37 -0
  189. qtype/examples/legacy/bedrock/hello_world_chat_with_telemetry.qtype.yaml +40 -0
  190. qtype/examples/legacy/bedrock/hello_world_chat_with_thinking.qtype.yaml +40 -0
  191. qtype/examples/legacy/bedrock/hello_world_completion.qtype.yaml +41 -0
  192. qtype/examples/legacy/bedrock/hello_world_completion_with_auth.qtype.yaml +44 -0
  193. qtype/examples/legacy/bedrock/simple_agent_chat.qtype.yaml +46 -0
  194. qtype/examples/legacy/chat_with_langfuse.qtype.yaml +50 -0
  195. qtype/examples/legacy/data/customers.csv +6 -0
  196. qtype/examples/legacy/data_processor.qtype.yaml +48 -0
  197. qtype/examples/legacy/echo/debug_example.qtype.yaml +59 -0
  198. qtype/examples/legacy/echo/prompt.qtype.yaml +22 -0
  199. qtype/examples/legacy/echo/readme.md +29 -0
  200. qtype/examples/legacy/echo/test.qtype.yaml +26 -0
  201. qtype/examples/legacy/echo/video.qtype.yaml +20 -0
  202. qtype/examples/legacy/field_extractor_example.qtype.yaml +137 -0
  203. qtype/examples/legacy/multi_flow_example.qtype.yaml +125 -0
  204. qtype/examples/legacy/openai/hello_world_chat.qtype.yaml +43 -0
  205. qtype/examples/legacy/openai/hello_world_chat_with_telemetry.qtype.yaml +46 -0
  206. qtype/examples/legacy/qtype_plugin_example.py +51 -0
  207. qtype/examples/legacy/rag.qtype.yaml +207 -0
  208. qtype/examples/legacy/sample_data.txt +43 -0
  209. qtype/examples/legacy/time_utilities.qtype.yaml +64 -0
  210. qtype/examples/legacy/vertex/README.md +11 -0
  211. qtype/examples/legacy/vertex/hello_world_chat.qtype.yaml +36 -0
  212. qtype/examples/legacy/vertex/hello_world_completion.qtype.yaml +40 -0
  213. qtype/examples/legacy/vertex/hello_world_completion_with_auth.qtype.yaml +45 -0
  214. qtype/examples/observability_debugging/trace_with_opentelemetry.qtype.yaml +40 -0
  215. qtype/examples/research_assistant/research_assistant.qtype.yaml +94 -0
  216. qtype/examples/research_assistant/tavily.oas.yaml +722 -0
  217. qtype/examples/research_assistant/tavily.qtype.yaml +216 -0
  218. qtype/examples/tutorials/01_hello_world.qtype.yaml +48 -0
  219. qtype/examples/tutorials/02_conversational_chat.qtype.yaml +37 -0
  220. qtype/examples/tutorials/03_structured_data.qtype.yaml +130 -0
  221. qtype/examples/tutorials/04_tools_and_function_calling.qtype.yaml +89 -0
  222. qtype/interpreter/api.py +4 -1
  223. qtype/interpreter/base/base_step_executor.py +3 -1
  224. qtype/interpreter/base/stream_emitter.py +19 -13
  225. qtype/interpreter/conversions.py +7 -3
  226. qtype/interpreter/converters.py +142 -26
  227. qtype/interpreter/executors/agent_executor.py +2 -3
  228. qtype/interpreter/executors/aggregate_executor.py +3 -4
  229. qtype/interpreter/executors/construct_executor.py +15 -15
  230. qtype/interpreter/executors/doc_to_text_executor.py +1 -3
  231. qtype/interpreter/executors/field_extractor_executor.py +13 -12
  232. qtype/interpreter/executors/file_source_executor.py +21 -34
  233. qtype/interpreter/executors/file_writer_executor.py +4 -4
  234. qtype/interpreter/executors/index_upsert_executor.py +1 -1
  235. qtype/interpreter/executors/invoke_embedding_executor.py +1 -4
  236. qtype/interpreter/executors/invoke_flow_executor.py +2 -2
  237. qtype/interpreter/executors/invoke_tool_executor.py +19 -18
  238. qtype/interpreter/executors/llm_inference_executor.py +16 -18
  239. qtype/interpreter/executors/prompt_template_executor.py +1 -3
  240. qtype/interpreter/executors/sql_source_executor.py +1 -1
  241. qtype/interpreter/resource_cache.py +3 -1
  242. qtype/interpreter/rich_progress.py +6 -3
  243. qtype/interpreter/stream/chat/converter.py +25 -17
  244. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +2 -2
  245. qtype/interpreter/tools/function_tool_helper.py +11 -10
  246. qtype/interpreter/types.py +89 -4
  247. qtype/interpreter/typing.py +35 -38
  248. qtype/mcp/__init__.py +0 -0
  249. qtype/mcp/server.py +722 -0
  250. qtype/schema/qtype.schema.json +4016 -0
  251. qtype/semantic/checker.py +20 -1
  252. qtype/semantic/generate.py +6 -9
  253. qtype/semantic/model.py +26 -33
  254. qtype/semantic/resolver.py +7 -0
  255. qtype/semantic/visualize.py +45 -53
  256. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/METADATA +65 -44
  257. qtype-0.1.13.dist-info/RECORD +352 -0
  258. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/WHEEL +1 -2
  259. qtype/application/facade.py +0 -177
  260. qtype-0.1.11.dist-info/RECORD +0 -142
  261. qtype-0.1.11.dist-info/top_level.txt +0 -1
  262. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/entry_points.txt +0 -0
  263. {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/licenses/LICENSE +0 -0
qtype/mcp/server.py ADDED
@@ -0,0 +1,722 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ import tempfile
6
+ from functools import lru_cache
7
+ from importlib.resources import files
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import tantivy
12
+ from mcp.server.fastmcp import FastMCP
13
+ from pydantic import BaseModel
14
+
15
+ from qtype.commands.convert import convert_to_yaml
16
+
17
+ # Initialize FastMCP server
18
+ mcp = FastMCP("qtype", host="0.0.0.0")
19
+
20
+ # Regex for pymdownx snippets: --8<-- "path/to/file"
21
+ SNIPPET_REGEX = re.compile(r'--8<--\s+"([^"]+)"')
22
+
23
+
24
+ # ============================================================================
25
+ # Resource Abstraction Layer
26
+ # ============================================================================
27
+
28
+
29
+ class ResourceDirectory:
30
+ """Abstraction for accessing resource directories (docs, examples, etc.)."""
31
+
32
+ def __init__(
33
+ self, name: str, file_extension: str, resolve_snippets: bool = False
34
+ ):
35
+ """Initialize a resource directory.
36
+
37
+ Args:
38
+ name: Directory name (e.g., "docs", "examples")
39
+ file_extension: File extension to search for (e.g., ".md", ".yaml")
40
+ resolve_snippets: Whether to resolve MkDocs snippets in file content
41
+ """
42
+ self.name = name
43
+ self.file_extension = file_extension
44
+ self.resolve_snippets = resolve_snippets
45
+ self._path_cache: Path | None = None
46
+
47
+ def get_path(self) -> Path:
48
+ """Get the path to this resource directory.
49
+
50
+ Returns:
51
+ Path to the resource directory, trying installed package first,
52
+ then falling back to development path.
53
+ """
54
+ if self._path_cache is not None:
55
+ return self._path_cache
56
+
57
+ try:
58
+ # Try to get from installed package
59
+ resource_root = files("qtype") / self.name
60
+ # Check if it exists by trying to iterate
61
+ list(resource_root.iterdir())
62
+ self._path_cache = Path(str(resource_root))
63
+ except (FileNotFoundError, AttributeError, TypeError):
64
+ # Fall back to development path
65
+ self._path_cache = Path(__file__).parent.parent.parent / self.name
66
+
67
+ return self._path_cache
68
+
69
+ def get_file(self, file_path: str) -> str:
70
+ """Get the content of a specific file.
71
+
72
+ Args:
73
+ file_path: Relative path to the file from the resource root.
74
+
75
+ Returns:
76
+ The full content of the file.
77
+
78
+ Raises:
79
+ FileNotFoundError: If the specified file doesn't exist.
80
+ ValueError: If the path tries to access files outside the directory.
81
+ """
82
+ resource_path = self.get_path()
83
+
84
+ # Resolve the requested file path
85
+ requested_file = (resource_path / file_path).resolve()
86
+
87
+ # Security check: ensure the resolved path is within resource directory
88
+ try:
89
+ requested_file.relative_to(resource_path.resolve())
90
+ except ValueError:
91
+ raise ValueError(
92
+ f"Invalid path: '{file_path}' is outside {self.name} directory"
93
+ )
94
+
95
+ if not requested_file.exists():
96
+ raise FileNotFoundError(
97
+ f"{self.name.capitalize()} file not found: '{file_path}'. "
98
+ f"Use list_{self.name} to see available files."
99
+ )
100
+
101
+ if not requested_file.is_file():
102
+ raise ValueError(f"Path is not a file: '{file_path}'")
103
+
104
+ content = requested_file.read_text(encoding="utf-8")
105
+
106
+ # Apply snippet resolution if enabled
107
+ if self.resolve_snippets:
108
+ content = _resolve_snippets(content, requested_file)
109
+
110
+ return content
111
+
112
+ def list_files(self) -> list[str]:
113
+ """List all files in this resource directory.
114
+
115
+ Returns:
116
+ Sorted list of relative paths to all files with the configured extension.
117
+
118
+ Raises:
119
+ FileNotFoundError: If the resource directory doesn't exist.
120
+ """
121
+ resource_path = self.get_path()
122
+
123
+ if not resource_path.exists():
124
+ raise FileNotFoundError(
125
+ f"{self.name.capitalize()} directory not found: {resource_path}"
126
+ )
127
+
128
+ # Find all files with the configured extension
129
+ pattern = f"*{self.file_extension}"
130
+ files_list = []
131
+ for file in resource_path.rglob(pattern):
132
+ # Get relative path from resource root
133
+ rel_path = file.relative_to(resource_path)
134
+ files_list.append(str(rel_path))
135
+
136
+ return sorted(files_list)
137
+
138
+
139
+ # Initialize resource directories
140
+ _docs_resource = ResourceDirectory("docs", ".md", resolve_snippets=True)
141
+ _examples_resource = ResourceDirectory("examples", ".yaml")
142
+
143
+
144
+ # ============================================================================
145
+ # Helper Functions
146
+ # ============================================================================
147
+
148
+
149
+ @lru_cache(maxsize=1)
150
+ def _load_schema() -> dict[str, Any]:
151
+ """Load the QType schema JSON file once and cache it.
152
+
153
+ Returns:
154
+ The complete schema as a dictionary.
155
+
156
+ Raises:
157
+ FileNotFoundError: If schema file doesn't exist.
158
+ json.JSONDecodeError: If schema file is invalid JSON.
159
+ """
160
+ # Try to load from installed package data first
161
+ try:
162
+ schema_file = files("qtype") / "schema" / "qtype.schema.json"
163
+ schema_text = schema_file.read_text(encoding="utf-8")
164
+ return json.loads(schema_text)
165
+ except (FileNotFoundError, AttributeError):
166
+ # Fall back to development path
167
+ schema_path = (
168
+ Path(__file__).parent.parent.parent / "schema/qtype.schema.json"
169
+ )
170
+ with open(schema_path, encoding="utf-8") as f:
171
+ return json.load(f)
172
+
173
+
174
+ def _resolve_snippets(content: str, base_path: Path) -> str:
175
+ """
176
+ Recursively finds and replaces MkDocs snippets in markdown content.
177
+ Mimics the behavior of pymdownx.snippets.
178
+
179
+ Args:
180
+ content: The markdown content to process
181
+ base_path: Path to the file being processed (used to resolve relative paths)
182
+ """
183
+ docs_root = _docs_resource.get_path()
184
+ project_root = docs_root.parent
185
+
186
+ def replace_match(match):
187
+ snippet_path = match.group(1)
188
+
189
+ # pymdownx logic: try relative to current file, then relative to docs, then project root
190
+ candidates = [
191
+ base_path.parent / snippet_path, # Relative to the doc file
192
+ docs_root / snippet_path, # Relative to docs root
193
+ project_root / snippet_path, # Relative to project root
194
+ ]
195
+
196
+ for candidate in candidates:
197
+ if candidate.exists() and candidate.is_file():
198
+ # Recursively resolve snippets inside the included file
199
+ return _resolve_snippets(
200
+ candidate.read_text(encoding="utf-8"), candidate
201
+ )
202
+
203
+ return f"> [!WARNING] Could not resolve snippet: {snippet_path}"
204
+
205
+ return SNIPPET_REGEX.sub(replace_match, content)
206
+
207
+
208
+ @lru_cache(maxsize=1)
209
+ def _build_search_index() -> tantivy.Index:
210
+ """Build and cache a Tantivy search index for docs and examples.
211
+
212
+ Returns:
213
+ A Tantivy Index containing all documentation markdown files
214
+ and example YAML files.
215
+
216
+ Raises:
217
+ Exception: If index building fails.
218
+ """
219
+ docs_path = _docs_resource.get_path()
220
+ examples_path = _examples_resource.get_path()
221
+
222
+ # Create schema with fields for title, path, and content
223
+ schema_builder = tantivy.SchemaBuilder()
224
+ schema_builder.add_text_field("title", stored=True)
225
+ schema_builder.add_text_field("path", stored=True)
226
+ schema_builder.add_text_field("content", stored=True)
227
+ schema_builder.add_text_field("type", stored=True)
228
+ schema = schema_builder.build()
229
+
230
+ # Create index in temporary directory
231
+ index = tantivy.Index(schema, path=tempfile.mkdtemp())
232
+ writer = index.writer()
233
+
234
+ # Helper to index files
235
+ def index_files(
236
+ root_path: Path,
237
+ pattern: str,
238
+ type_label: str,
239
+ path_prefix: str,
240
+ process_content=None,
241
+ extract_title=None,
242
+ ):
243
+ for file_path in root_path.rglob(pattern):
244
+ content = file_path.read_text(encoding="utf-8")
245
+ if process_content:
246
+ content = process_content(content, file_path)
247
+
248
+ rel_path = str(file_path.relative_to(root_path))
249
+ title = (
250
+ extract_title(content, file_path)
251
+ if extract_title
252
+ else file_path.stem
253
+ )
254
+
255
+ writer.add_document(
256
+ tantivy.Document(
257
+ title=title,
258
+ path=f"{path_prefix}/{rel_path}",
259
+ content=content,
260
+ type=type_label,
261
+ )
262
+ )
263
+
264
+ # Extract title from markdown first heading
265
+ def extract_md_title(content: str, file_path: Path) -> str:
266
+ for line in content.split("\n"):
267
+ if line.startswith("# "):
268
+ return line[2:].strip()
269
+ return file_path.stem
270
+
271
+ # Index documentation and examples
272
+ index_files(
273
+ docs_path,
274
+ "*.md",
275
+ "documentation",
276
+ "docs",
277
+ process_content=_resolve_snippets,
278
+ extract_title=extract_md_title,
279
+ )
280
+ index_files(examples_path, "*.yaml", "example", "examples")
281
+
282
+ writer.commit()
283
+ return index
284
+
285
+
286
+ # ============================================================================
287
+ # Tool Functions
288
+ # ============================================================================
289
+
290
+
291
+ class MermaidDiagramPreviewInput(BaseModel):
292
+ """Arguments for VS Code's `mermaid-diagram-preview` tool."""
293
+
294
+ code: str
295
+
296
+
297
+ class MermaidVisualizationResult(BaseModel):
298
+ """Structured output for Mermaid visualization."""
299
+
300
+ mermaid_code: str
301
+ mermaid_markdown: str
302
+ suggested_next_tool: str
303
+ mermaid_diagram_preview_input: MermaidDiagramPreviewInput
304
+ preview_instructions: str
305
+
306
+
307
+ # Rebuild model after nested dependency is defined
308
+ MermaidVisualizationResult.model_rebuild()
309
+
310
+
311
+ @mcp.tool(
312
+ title="Convert API Specification to QType Tools",
313
+ description=(
314
+ "Converts an OpenAPI specification (URL or file path) to QType tool definitions. "
315
+ "Returns YAML code containing the generated tools, custom types, and authentication "
316
+ "providers that can be used in QType applications."
317
+ ),
318
+ )
319
+ async def convert_api_to_tools(api_spec: str) -> str:
320
+ """Convert API specification to QType YAML format.
321
+
322
+ Args:
323
+ api_spec: URL or file path to an OpenAPI/Swagger specification.
324
+ Examples: "https://api.example.com/openapi.json",
325
+ "/path/to/openapi.yaml"
326
+
327
+ Returns:
328
+ YAML string containing the generated QType tools, types, and
329
+ authentication providers.
330
+
331
+ Raises:
332
+ Exception: If conversion fails or no tools are found.
333
+ """
334
+ from qtype.application.converters.tools_from_api import tools_from_api
335
+ from qtype.dsl.model import Application, ToolList
336
+
337
+ try:
338
+ api_name, auths, tools, types = tools_from_api(api_spec)
339
+ if not tools:
340
+ raise ValueError(
341
+ f"No tools found from the API specification: {api_spec}"
342
+ )
343
+
344
+ # Create document with or without Application wrapper
345
+ if not auths and not types:
346
+ doc = ToolList(root=list(tools))
347
+ else:
348
+ doc = Application(
349
+ id=api_name,
350
+ description=(
351
+ f"Tools created from API specification {api_spec}"
352
+ ),
353
+ tools=list(tools),
354
+ types=types,
355
+ auths=auths,
356
+ )
357
+
358
+ return convert_to_yaml(doc)
359
+
360
+ except Exception as e:
361
+ raise Exception(f"API conversion failed: {str(e)}")
362
+
363
+
364
+ @mcp.tool(
365
+ title="Convert Python Module to QType Tools",
366
+ description=(
367
+ "Converts Python functions from a module to QType tool definitions. "
368
+ "Analyzes function signatures, docstrings, and type hints to generate "
369
+ "YAML tool definitions that can be used in QType applications."
370
+ ),
371
+ )
372
+ async def convert_python_to_tools(module_path: str) -> str:
373
+ """Convert Python module to QType YAML format.
374
+
375
+ Args:
376
+ module_path: Path to the Python module to convert.
377
+ Example: "my_package.my_module" or "/path/to/module.py"
378
+
379
+ Returns:
380
+ YAML string containing the generated QType tools and custom types.
381
+
382
+ Raises:
383
+ Exception: If conversion fails or no tools are found.
384
+ """
385
+ from qtype.application.converters.tools_from_module import (
386
+ tools_from_module,
387
+ )
388
+ from qtype.dsl.model import Application, ToolList
389
+
390
+ try:
391
+ tools, types = tools_from_module(module_path)
392
+ if not tools:
393
+ raise ValueError(f"No tools found in the module: {module_path}")
394
+
395
+ # Create document with or without Application wrapper
396
+ if types:
397
+ doc = Application(
398
+ id=module_path,
399
+ description=(
400
+ f"Tools created from Python module {module_path}"
401
+ ),
402
+ tools=list(tools),
403
+ types=types,
404
+ )
405
+ else:
406
+ doc = ToolList(root=list(tools))
407
+
408
+ return convert_to_yaml(doc)
409
+
410
+ except Exception as e:
411
+ raise Exception(f"Python module conversion failed: {str(e)}")
412
+
413
+
414
+ @mcp.tool(
415
+ title="Get QType Component Schema",
416
+ description=(
417
+ "Returns the JSON Schema definition for a specific QType component. "
418
+ "Use this to understand the structure, required fields, and allowed values "
419
+ "when building QType YAML specifications. "
420
+ "Common components include: Flow, Agent, LLMInference, DocumentSource, "
421
+ "Application, Model, Variable, CustomType. "
422
+ "Component names are case-sensitive and must match exactly."
423
+ ),
424
+ structured_output=True,
425
+ )
426
+ def get_component_schema(component_name: str) -> dict[str, Any]:
427
+ """Get the JSON Schema definition for a QType component.
428
+
429
+ Args:
430
+ component_name: The exact name of the QType component (case-sensitive).
431
+ Examples: "DocumentSource", "Flow", "Agent", "LLMInference",
432
+ "Application", "Model", "Variable", "CustomType".
433
+
434
+ Returns:
435
+ JSON Schema definition for the component including its properties,
436
+ required fields, types, and descriptions.
437
+
438
+ Raises:
439
+ ValueError: If component_name is not found. The error message will
440
+ include a list of all available component names.
441
+ """
442
+ schema = _load_schema()
443
+
444
+ # Look up the component in $defs
445
+ if "$defs" not in schema:
446
+ raise ValueError("Schema file does not contain $defs section")
447
+
448
+ if component_name not in schema["$defs"]:
449
+ available = sorted(schema["$defs"].keys())
450
+ raise ValueError(
451
+ f"Component '{component_name}' not found in schema. "
452
+ f"Available components: {', '.join(available)}"
453
+ )
454
+
455
+ return schema["$defs"][component_name]
456
+
457
+
458
+ @mcp.tool(
459
+ title="Get QType Documentation",
460
+ description=(
461
+ "Returns the content of a specific documentation file. "
462
+ "Use list_documentation first to see available files. "
463
+ "Provide the relative path (e.g., 'components/Flow.md', 'index.md', "
464
+ "'Tutorials/getting_started.md')."
465
+ ),
466
+ )
467
+ def get_documentation(file_path: str) -> str:
468
+ """Get the content of a specific documentation file.
469
+
470
+ Args:
471
+ file_path: Relative path to the documentation file from the docs root.
472
+ Example: "components/Flow.md", "index.md", "Tutorials/getting_started.md".
473
+ Use list_documentation to see all available files.
474
+
475
+ Returns:
476
+ The full markdown content of the documentation file with snippets resolved.
477
+
478
+ Raises:
479
+ FileNotFoundError: If the specified file doesn't exist.
480
+ ValueError: If the path tries to access files outside the docs directory.
481
+ """
482
+ return _docs_resource.get_file(file_path)
483
+
484
+
485
+ @mcp.tool(
486
+ title="List QType Components",
487
+ description=(
488
+ "Returns a list of all available QType component types that can be used "
489
+ "in YAML specifications. Use this to discover what components exist before "
490
+ "requesting their detailed schemas with get_component_schema."
491
+ ),
492
+ structured_output=True,
493
+ )
494
+ def list_components() -> list[str]:
495
+ """List all available QType component types.
496
+
497
+ Returns:
498
+ Sorted list of all component names available in the QType schema.
499
+ Each name can be used with get_component_schema to retrieve its
500
+ full JSON Schema definition.
501
+ """
502
+ schema = _load_schema()
503
+
504
+ if "$defs" not in schema:
505
+ raise ValueError("Schema file does not contain $defs section")
506
+
507
+ return sorted(schema["$defs"].keys())
508
+
509
+
510
+ @mcp.tool(
511
+ title="List QType Documentation",
512
+ description=(
513
+ "Returns a list of all available documentation files. "
514
+ "Use this to discover what documentation exists, then retrieve "
515
+ "specific files with get_documentation. Files are organized by category: "
516
+ "components/ (component reference), Concepts/ (conceptual guides), "
517
+ "Tutorials/ (step-by-step tutorials), How To/ (task guides), etc."
518
+ ),
519
+ structured_output=True,
520
+ )
521
+ def list_documentation() -> list[str]:
522
+ """List all available documentation markdown files.
523
+
524
+ Returns:
525
+ Sorted list of relative paths to all .md documentation files.
526
+ Paths are relative to the docs root (e.g., "components/Flow.md",
527
+ "Tutorials/getting_started.md").
528
+ """
529
+ return _docs_resource.list_files()
530
+
531
+
532
+ @mcp.tool(
533
+ title="Get QType Example",
534
+ description=(
535
+ "Returns the content of a specific example YAML file. "
536
+ "Use list_examples first to see available files. "
537
+ "Provide the relative path (e.g., 'conversational_ai/simple_chatbot.qtype.yaml', "
538
+ "'data_processing/csv_processor.qtype.yaml')."
539
+ ),
540
+ )
541
+ def get_example(file_path: str) -> str:
542
+ """Get the content of a specific example file.
543
+
544
+ Args:
545
+ file_path: Relative path to the example file from the examples root.
546
+ Example: "conversational_ai/simple_chatbot.qtype.yaml",
547
+ "data_processing/csv_processor.qtype.yaml".
548
+ Use list_examples to see all available files.
549
+
550
+ Returns:
551
+ The full YAML content of the example file.
552
+
553
+ Raises:
554
+ FileNotFoundError: If the specified file doesn't exist.
555
+ ValueError: If the path tries to access files outside the examples directory.
556
+ """
557
+ return _examples_resource.get_file(file_path)
558
+
559
+
560
+ @mcp.tool(
561
+ title="List QType Examples",
562
+ description=(
563
+ "Returns a list of all available example YAML files. "
564
+ "Use this to discover what examples exist, then retrieve "
565
+ "specific files with get_example. Examples are organized by category: "
566
+ "conversational_ai/ (chatbots and agents), data_processing/ (ETL and transformations), "
567
+ "invoke_models/ (LLM usage), language_features/ (QType syntax examples), etc."
568
+ ),
569
+ structured_output=True,
570
+ )
571
+ def list_examples() -> list[str]:
572
+ """List all available example YAML files.
573
+
574
+ Returns:
575
+ Sorted list of relative paths to all .yaml example files.
576
+ Paths are relative to the examples root (e.g.,
577
+ "conversational_ai/simple_chatbot.qtype.yaml",
578
+ "data_processing/csv_processor.qtype.yaml").
579
+ """
580
+ return _examples_resource.list_files()
581
+
582
+
583
+ @mcp.tool(
584
+ title="Search QType Library",
585
+ description=(
586
+ "Full-text search across all QType documentation and examples. "
587
+ "Returns matching documents and example YAML files ranked by relevance. "
588
+ "Use this to find documentation about specific topics, features, or components, "
589
+ "or to discover example implementations. Doc paths can be used with get_documentation."
590
+ ),
591
+ structured_output=True,
592
+ )
593
+ def search_library(query: str, limit: int = 10) -> list[dict[str, Any]]:
594
+ """Search library using full-text search.
595
+
596
+ Args:
597
+ query: Search query string. Can include multiple words, phrases,
598
+ or boolean operators (AND, OR, NOT).
599
+ limit: Maximum number of results to return (default: 10, max: 50).
600
+
601
+ Returns:
602
+ List of matching items with:
603
+ - title: Item title
604
+ - path: Relative path (docs/ or examples/ prefix)
605
+ - type: Either "documentation" or "example"
606
+ - score: Relevance score (higher is more relevant)
607
+
608
+ Examples:
609
+ search_library("flow execution") # Find docs/examples about flows
610
+ search_library("DocumentSource") # Find component docs
611
+ search_library("authentication AND API") # Boolean search
612
+ """
613
+ # Clamp limit to reasonable range
614
+ limit = max(1, min(limit, 50))
615
+
616
+ index = _build_search_index()
617
+ index.reload()
618
+ searcher = index.searcher()
619
+ tantivy_query = index.parse_query(query, ["title", "content"])
620
+
621
+ search_results = searcher.search(tantivy_query, limit)
622
+
623
+ results = []
624
+ for score, doc_address in search_results.hits:
625
+ doc = searcher.doc(doc_address)
626
+ results.append(
627
+ {
628
+ "title": doc["title"][0],
629
+ "path": doc["path"][0],
630
+ "type": doc["type"][0],
631
+ "score": score,
632
+ }
633
+ )
634
+
635
+ return results
636
+
637
+
638
+ @mcp.tool(
639
+ title="Validate QType YAML",
640
+ description=(
641
+ "Validates QType YAML for syntax and semantic correctness. "
642
+ "Returns a human-readable status string: either a success message or "
643
+ "a validation error with details."
644
+ ),
645
+ )
646
+ async def validate_qtype_yaml(yaml_content: str) -> str:
647
+ """Validate QType YAML for syntax and semantic errors.
648
+
649
+ Args:
650
+ yaml_content: The QType YAML content to validate.
651
+
652
+ Returns:
653
+ A human-readable status string.
654
+ """
655
+ from qtype.semantic.loader import load_from_string
656
+
657
+ try:
658
+ document, _ = load_from_string(yaml_content)
659
+ return "✅ Valid QType Code"
660
+
661
+ except Exception as e:
662
+ # Return the error message so the LLM can fix it
663
+ return f"❌ Validation Error: {str(e)}"
664
+
665
+
666
+ @mcp.tool(
667
+ title="Visualize QType Architecture",
668
+ description=(
669
+ "Generates a Mermaid flowchart diagram from QType YAML code. "
670
+ "Returns a structured result containing raw Mermaid code plus preview guidance. "
671
+ "After calling this tool, call the mermaid-diagram-preview tool using the "
672
+ "returned mermaid_diagram_preview_input."
673
+ ),
674
+ structured_output=True,
675
+ )
676
+ async def visualize_qtype_architecture(
677
+ yaml_content: str,
678
+ ) -> MermaidVisualizationResult:
679
+ """Generate Mermaid diagram from QType YAML.
680
+
681
+ Args:
682
+ yaml_content: The complete QType YAML specification to visualize.
683
+ Must be a valid Application definition.
684
+
685
+ Returns:
686
+ A structured result with:
687
+ - mermaid_code: Raw Mermaid diagram code (no backticks/fences)
688
+ - suggested_next_tool: "mermaid-diagram-preview"
689
+ - preview_instructions: How to preview in VS Code
690
+
691
+ Raises:
692
+ Exception: If YAML is invalid or visualization fails.
693
+ """
694
+ from qtype.semantic.loader import load_from_string
695
+ from qtype.semantic.model import Application
696
+ from qtype.semantic.visualize import visualize_application
697
+
698
+ try:
699
+ document, _ = load_from_string(yaml_content)
700
+ if not isinstance(document, Application):
701
+ raise ValueError(
702
+ "YAML must contain an Application to visualize. "
703
+ f"Got {type(document).__name__} instead."
704
+ )
705
+ mermaid_content = visualize_application(document)
706
+
707
+ return MermaidVisualizationResult(
708
+ mermaid_code=mermaid_content,
709
+ mermaid_markdown=f"```mermaid\n{mermaid_content}\n```\n",
710
+ suggested_next_tool="mermaid-diagram-preview",
711
+ mermaid_diagram_preview_input=MermaidDiagramPreviewInput(
712
+ code=mermaid_content
713
+ ),
714
+ preview_instructions=(
715
+ "Call mermaid-diagram-preview with mermaid_diagram_preview_input. "
716
+ "Alternatively, save mermaid_code in a .md file fenced with "
717
+ "```mermaid ...``` and open the Markdown preview."
718
+ ),
719
+ )
720
+
721
+ except Exception as e:
722
+ raise RuntimeError(f"Visualization failed: {e}") from e