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.
- qtype/` +0 -0
- qtype/application/__init__.py +0 -2
- qtype/application/converters/tools_from_api.py +67 -57
- qtype/application/converters/tools_from_module.py +66 -32
- qtype/base/types.py +6 -1
- qtype/commands/convert.py +3 -6
- qtype/commands/generate.py +97 -10
- qtype/commands/mcp.py +68 -0
- qtype/commands/run.py +116 -44
- qtype/commands/validate.py +4 -4
- qtype/docs/.pages +8 -0
- qtype/docs/Concepts/mental-model-and-philosophy.md +363 -0
- qtype/docs/Contributing/.pages +4 -0
- qtype/docs/Contributing/index.md +283 -0
- qtype/docs/Contributing/roadmap.md +81 -0
- qtype/docs/Decisions/ADR-001-Chat-vs-Completion-Endpoint-Features.md +56 -0
- qtype/docs/Gallery/dataflow_pipelines.md +81 -0
- qtype/docs/Gallery/dataflow_pipelines.mermaid +45 -0
- qtype/docs/Gallery/research_assistant.md +97 -0
- qtype/docs/Gallery/research_assistant.mermaid +42 -0
- qtype/docs/Gallery/simple_chatbot.md +38 -0
- qtype/docs/Gallery/simple_chatbot.mermaid +35 -0
- qtype/docs/How To/Authentication/configure_aws_authentication.md +60 -0
- qtype/docs/How To/Authentication/use_api_key_authentication.md +40 -0
- qtype/docs/How To/Command Line Usage/load_multiple_inputs_from_files.md +77 -0
- qtype/docs/How To/Command Line Usage/pass_inputs_on_the_cli.md +52 -0
- qtype/docs/How To/Command Line Usage/serve_with_auto_reload.md +27 -0
- qtype/docs/How To/Data Processing/adjust_concurrency.md +40 -0
- qtype/docs/How To/Data Processing/cache_step_results.md +71 -0
- qtype/docs/How To/Data Processing/decode_json_xml.md +24 -0
- qtype/docs/How To/Data Processing/explode_collections.md +40 -0
- qtype/docs/How To/Data Processing/gather_results.md +68 -0
- qtype/docs/How To/Data Processing/invoke_other_flows.md +71 -0
- qtype/docs/How To/Data Processing/load_data_from_athena.md +49 -0
- qtype/docs/How To/Data Processing/read_data_from_files.md +61 -0
- qtype/docs/How To/Data Processing/read_sql_databases.md +46 -0
- qtype/docs/How To/Data Processing/write_data_to_file.md +39 -0
- qtype/docs/How To/Invoke Models/call_large_language_models.md +51 -0
- qtype/docs/How To/Invoke Models/create_embeddings.md +49 -0
- qtype/docs/How To/Invoke Models/reuse_prompts_with_templates.md +38 -0
- qtype/docs/How To/Language Features/include_qtype_yaml.md +45 -0
- qtype/docs/How To/Language Features/include_raw_text_from_other_files.md +48 -0
- qtype/docs/How To/Language Features/reference_entities_by_id.md +51 -0
- qtype/docs/How To/Language Features/use_agent_skills.md +29 -0
- qtype/docs/How To/Language Features/use_environment_variables.md +48 -0
- qtype/docs/How To/Language Features/use_optional_variables.md +42 -0
- qtype/docs/How To/Language Features/use_qtype_mcp.md +59 -0
- qtype/docs/How To/Observability & Debugging/trace_calls_with_open_telemetry.md +49 -0
- qtype/docs/How To/Observability & Debugging/validate_qtype_yaml.md +36 -0
- qtype/docs/How To/Observability & Debugging/visualize_application_architecture.md +61 -0
- qtype/docs/How To/Observability & Debugging/visualize_example.mermaid +35 -0
- qtype/docs/How To/Qtype Server/flow_as_ui.png +0 -0
- qtype/docs/How To/Qtype Server/serve_flows_as_apis.md +40 -0
- qtype/docs/How To/Qtype Server/serve_flows_as_ui.md +41 -0
- qtype/docs/How To/Qtype Server/use_conversational_interfaces.md +56 -0
- qtype/docs/How To/Qtype Server/use_variables_with_ui_hints.md +48 -0
- qtype/docs/How To/Tools & Integration/bind_tool_inputs_and_outputs.md +47 -0
- qtype/docs/How To/Tools & Integration/create_tools_from_openapi_specifications.md +85 -0
- qtype/docs/How To/Tools & Integration/create_tools_from_python_modules.md +87 -0
- qtype/docs/Reference/cli.md +336 -0
- qtype/docs/Reference/plugins.md +99 -0
- qtype/docs/Reference/semantic-validation-rules.md +184 -0
- qtype/docs/Tutorials/.pages +1 -0
- qtype/docs/Tutorials/01-first-qtype-application.md +249 -0
- qtype/docs/Tutorials/02-conversational-chatbot.md +327 -0
- qtype/docs/Tutorials/03-structured-data.md +480 -0
- qtype/docs/Tutorials/04-tools-and-function-calling.md +476 -0
- qtype/docs/Tutorials/example_chat.png +0 -0
- qtype/docs/Tutorials/index.md +92 -0
- qtype/docs/components/APIKeyAuthProvider.md +7 -0
- qtype/docs/components/APITool.md +10 -0
- qtype/docs/components/AWSAuthProvider.md +13 -0
- qtype/docs/components/AWSSecretManager.md +5 -0
- qtype/docs/components/Agent.md +6 -0
- qtype/docs/components/Aggregate.md +7 -0
- qtype/docs/components/AggregateStats.md +7 -0
- qtype/docs/components/Application.md +22 -0
- qtype/docs/components/AuthorizationProvider.md +6 -0
- qtype/docs/components/AuthorizationProviderList.md +5 -0
- qtype/docs/components/BearerTokenAuthProvider.md +6 -0
- qtype/docs/components/BedrockReranker.md +8 -0
- qtype/docs/components/ChatContent.md +7 -0
- qtype/docs/components/ChatMessage.md +6 -0
- qtype/docs/components/Collect.md +6 -0
- qtype/docs/components/ConstantPath.md +5 -0
- qtype/docs/components/Construct.md +6 -0
- qtype/docs/components/CustomType.md +7 -0
- qtype/docs/components/Decoder.md +8 -0
- qtype/docs/components/DecoderFormat.md +8 -0
- qtype/docs/components/DocToTextConverter.md +7 -0
- qtype/docs/components/Document.md +7 -0
- qtype/docs/components/DocumentEmbedder.md +6 -0
- qtype/docs/components/DocumentIndex.md +7 -0
- qtype/docs/components/DocumentSearch.md +7 -0
- qtype/docs/components/DocumentSource.md +12 -0
- qtype/docs/components/DocumentSplitter.md +9 -0
- qtype/docs/components/Echo.md +8 -0
- qtype/docs/components/Embedding.md +7 -0
- qtype/docs/components/EmbeddingModel.md +6 -0
- qtype/docs/components/Explode.md +5 -0
- qtype/docs/components/FieldExtractor.md +21 -0
- qtype/docs/components/FileSource.md +6 -0
- qtype/docs/components/FileWriter.md +7 -0
- qtype/docs/components/Flow.md +14 -0
- qtype/docs/components/FlowInterface.md +7 -0
- qtype/docs/components/Index.md +8 -0
- qtype/docs/components/IndexUpsert.md +6 -0
- qtype/docs/components/InvokeEmbedding.md +7 -0
- qtype/docs/components/InvokeFlow.md +8 -0
- qtype/docs/components/InvokeTool.md +8 -0
- qtype/docs/components/LLMInference.md +9 -0
- qtype/docs/components/ListType.md +5 -0
- qtype/docs/components/Memory.md +8 -0
- qtype/docs/components/MessageRole.md +14 -0
- qtype/docs/components/Model.md +10 -0
- qtype/docs/components/ModelList.md +5 -0
- qtype/docs/components/OAuth2AuthProvider.md +9 -0
- qtype/docs/components/PrimitiveTypeEnum.md +20 -0
- qtype/docs/components/PromptTemplate.md +7 -0
- qtype/docs/components/PythonFunctionTool.md +7 -0
- qtype/docs/components/RAGChunk.md +7 -0
- qtype/docs/components/RAGDocument.md +10 -0
- qtype/docs/components/RAGSearchResult.md +8 -0
- qtype/docs/components/Reranker.md +5 -0
- qtype/docs/components/SQLSource.md +8 -0
- qtype/docs/components/Search.md +7 -0
- qtype/docs/components/SearchResult.md +7 -0
- qtype/docs/components/SecretManager.md +7 -0
- qtype/docs/components/SecretReference.md +7 -0
- qtype/docs/components/Source.md +5 -0
- qtype/docs/components/Step.md +8 -0
- qtype/docs/components/TelemetrySink.md +9 -0
- qtype/docs/components/Tool.md +9 -0
- qtype/docs/components/ToolList.md +5 -0
- qtype/docs/components/TypeList.md +5 -0
- qtype/docs/components/Variable.md +8 -0
- qtype/docs/components/VariableList.md +5 -0
- qtype/docs/components/VectorIndex.md +7 -0
- qtype/docs/components/VectorSearch.md +6 -0
- qtype/docs/components/VertexAuthProvider.md +9 -0
- qtype/docs/components/Writer.md +5 -0
- qtype/docs/example_ui.png +0 -0
- qtype/docs/index.md +81 -0
- qtype/docs/legacy_how_tos/.pages +6 -0
- qtype/docs/legacy_how_tos/Configuration/modular-yaml.md +366 -0
- qtype/docs/legacy_how_tos/Configuration/phoenix_projects.png +0 -0
- qtype/docs/legacy_how_tos/Configuration/phoenix_traces.png +0 -0
- qtype/docs/legacy_how_tos/Configuration/reference-by-id.md +251 -0
- qtype/docs/legacy_how_tos/Configuration/telemetry-setup.md +259 -0
- qtype/docs/legacy_how_tos/Data Types/custom-types.md +52 -0
- qtype/docs/legacy_how_tos/Data Types/domain-types.md +113 -0
- qtype/docs/legacy_how_tos/Debugging/visualize-apps.md +147 -0
- qtype/docs/legacy_how_tos/Tools/api-tools.md +29 -0
- qtype/docs/legacy_how_tos/Tools/python-tools.md +299 -0
- qtype/docs/skills/architect/SKILL.md +188 -0
- qtype/docs/skills/architect/references/cheatsheet.md +198 -0
- qtype/docs/skills/architect/references/patterns.md +29 -0
- qtype/docs/stylesheets/extra.css +27 -0
- qtype/dsl/custom_types.py +2 -1
- qtype/dsl/linker.py +23 -7
- qtype/dsl/loader.py +3 -3
- qtype/dsl/model.py +181 -67
- qtype/examples/authentication/aws_authentication.qtype.yaml +63 -0
- qtype/examples/conversational_ai/hello_world_chat.qtype.yaml +43 -0
- qtype/examples/conversational_ai/simple_chatbot.qtype.yaml +40 -0
- qtype/examples/data_processing/athena_query.qtype.yaml +56 -0
- qtype/examples/data_processing/batch_inputs.csv +5 -0
- qtype/examples/data_processing/batch_processing.qtype.yaml +54 -0
- qtype/examples/data_processing/cache_step_results.qtype.yaml +78 -0
- qtype/examples/data_processing/collect_results.qtype.yaml +55 -0
- qtype/examples/data_processing/create_sample_db.py +129 -0
- qtype/examples/data_processing/dataflow_pipelines.qtype.yaml +108 -0
- qtype/examples/data_processing/decode_json.qtype.yaml +23 -0
- qtype/examples/data_processing/explode_items.qtype.yaml +25 -0
- qtype/examples/data_processing/invoke_other_flows.qtype.yaml +98 -0
- qtype/examples/data_processing/read_file.qtype.yaml +60 -0
- qtype/examples/data_processing/reviews.db +0 -0
- qtype/examples/data_processing/sample_article.txt +1 -0
- qtype/examples/data_processing/sample_documents.jsonl +5 -0
- qtype/examples/invoke_models/create_embeddings.qtype.yaml +28 -0
- qtype/examples/invoke_models/simple_llm_call.qtype.yaml +32 -0
- qtype/examples/language_features/include_raw.qtype.yaml +27 -0
- qtype/examples/language_features/optional_variables.qtype.yaml +32 -0
- qtype/examples/language_features/story_prompt.txt +6 -0
- qtype/examples/language_features/ui_hints.qtype.yaml +52 -0
- qtype/examples/legacy/bedrock/data_analysis_with_telemetry.qtype.yaml +169 -0
- qtype/examples/legacy/bedrock/hello_world.qtype.yaml +39 -0
- qtype/examples/legacy/bedrock/hello_world_chat.qtype.yaml +37 -0
- qtype/examples/legacy/bedrock/hello_world_chat_with_telemetry.qtype.yaml +40 -0
- qtype/examples/legacy/bedrock/hello_world_chat_with_thinking.qtype.yaml +40 -0
- qtype/examples/legacy/bedrock/hello_world_completion.qtype.yaml +41 -0
- qtype/examples/legacy/bedrock/hello_world_completion_with_auth.qtype.yaml +44 -0
- qtype/examples/legacy/bedrock/simple_agent_chat.qtype.yaml +46 -0
- qtype/examples/legacy/chat_with_langfuse.qtype.yaml +50 -0
- qtype/examples/legacy/data/customers.csv +6 -0
- qtype/examples/legacy/data_processor.qtype.yaml +48 -0
- qtype/examples/legacy/echo/debug_example.qtype.yaml +59 -0
- qtype/examples/legacy/echo/prompt.qtype.yaml +22 -0
- qtype/examples/legacy/echo/readme.md +29 -0
- qtype/examples/legacy/echo/test.qtype.yaml +26 -0
- qtype/examples/legacy/echo/video.qtype.yaml +20 -0
- qtype/examples/legacy/field_extractor_example.qtype.yaml +137 -0
- qtype/examples/legacy/multi_flow_example.qtype.yaml +125 -0
- qtype/examples/legacy/openai/hello_world_chat.qtype.yaml +43 -0
- qtype/examples/legacy/openai/hello_world_chat_with_telemetry.qtype.yaml +46 -0
- qtype/examples/legacy/qtype_plugin_example.py +51 -0
- qtype/examples/legacy/rag.qtype.yaml +207 -0
- qtype/examples/legacy/sample_data.txt +43 -0
- qtype/examples/legacy/time_utilities.qtype.yaml +64 -0
- qtype/examples/legacy/vertex/README.md +11 -0
- qtype/examples/legacy/vertex/hello_world_chat.qtype.yaml +36 -0
- qtype/examples/legacy/vertex/hello_world_completion.qtype.yaml +40 -0
- qtype/examples/legacy/vertex/hello_world_completion_with_auth.qtype.yaml +45 -0
- qtype/examples/observability_debugging/trace_with_opentelemetry.qtype.yaml +40 -0
- qtype/examples/research_assistant/research_assistant.qtype.yaml +94 -0
- qtype/examples/research_assistant/tavily.oas.yaml +722 -0
- qtype/examples/research_assistant/tavily.qtype.yaml +216 -0
- qtype/examples/tutorials/01_hello_world.qtype.yaml +48 -0
- qtype/examples/tutorials/02_conversational_chat.qtype.yaml +37 -0
- qtype/examples/tutorials/03_structured_data.qtype.yaml +130 -0
- qtype/examples/tutorials/04_tools_and_function_calling.qtype.yaml +89 -0
- qtype/interpreter/api.py +4 -1
- qtype/interpreter/base/base_step_executor.py +3 -1
- qtype/interpreter/base/stream_emitter.py +19 -13
- qtype/interpreter/conversions.py +7 -3
- qtype/interpreter/converters.py +142 -26
- qtype/interpreter/executors/agent_executor.py +2 -3
- qtype/interpreter/executors/aggregate_executor.py +3 -4
- qtype/interpreter/executors/construct_executor.py +15 -15
- qtype/interpreter/executors/doc_to_text_executor.py +1 -3
- qtype/interpreter/executors/field_extractor_executor.py +13 -12
- qtype/interpreter/executors/file_source_executor.py +21 -34
- qtype/interpreter/executors/file_writer_executor.py +4 -4
- qtype/interpreter/executors/index_upsert_executor.py +1 -1
- qtype/interpreter/executors/invoke_embedding_executor.py +1 -4
- qtype/interpreter/executors/invoke_flow_executor.py +2 -2
- qtype/interpreter/executors/invoke_tool_executor.py +19 -18
- qtype/interpreter/executors/llm_inference_executor.py +16 -18
- qtype/interpreter/executors/prompt_template_executor.py +1 -3
- qtype/interpreter/executors/sql_source_executor.py +1 -1
- qtype/interpreter/resource_cache.py +3 -1
- qtype/interpreter/rich_progress.py +6 -3
- qtype/interpreter/stream/chat/converter.py +25 -17
- qtype/interpreter/stream/chat/ui_request_to_domain_type.py +2 -2
- qtype/interpreter/tools/function_tool_helper.py +11 -10
- qtype/interpreter/types.py +89 -4
- qtype/interpreter/typing.py +35 -38
- qtype/mcp/__init__.py +0 -0
- qtype/mcp/server.py +722 -0
- qtype/schema/qtype.schema.json +4016 -0
- qtype/semantic/checker.py +20 -1
- qtype/semantic/generate.py +6 -9
- qtype/semantic/model.py +26 -33
- qtype/semantic/resolver.py +7 -0
- qtype/semantic/visualize.py +45 -53
- {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/METADATA +65 -44
- qtype-0.1.13.dist-info/RECORD +352 -0
- {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/WHEEL +1 -2
- qtype/application/facade.py +0 -177
- qtype-0.1.11.dist-info/RECORD +0 -142
- qtype-0.1.11.dist-info/top_level.txt +0 -1
- {qtype-0.1.11.dist-info → qtype-0.1.13.dist-info}/entry_points.txt +0 -0
- {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
|