structurize 3.4.2__tar.gz → 3.5.0__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.
- {structurize-3.4.2/structurize.egg-info → structurize-3.5.0}/PKG-INFO +1 -1
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/_version.py +3 -3
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotojava.py +4 -3
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/constants.py +17 -12
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/dependencies/typescript/node22/package.json +1 -1
- structurize-3.5.0/avrotize/mcp_server.py +498 -0
- {structurize-3.4.2 → structurize-3.5.0/structurize.egg-info}/PKG-INFO +1 -1
- {structurize-3.4.2 → structurize-3.5.0}/structurize.egg-info/SOURCES.txt +1 -0
- structurize-3.4.2/avrotize/mcp_server.py +0 -253
- {structurize-3.4.2 → structurize-3.5.0}/.gitignore +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/LICENSE +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/MANIFEST.in +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/README.md +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/__init__.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/__main__.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/asn1toavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotize.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotocpp.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotocsharp.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotocsv.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotodatapackage.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotodb.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotogo.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotographql.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotoiceberg.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotojs.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotojsons.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotojstruct.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotokusto.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotomd.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotools.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotoparquet.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotoproto.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotopython.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotorust.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotots.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrotoxsd.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/avrovalidator.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/cddltostructure.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/choice_inference.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/commands.json +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/common.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/csvtoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/datapackagetoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/dependency_resolver.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/dependency_version.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/jsonstoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/jsonstostructure.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/jsontoschema.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/jstructtoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/kstructtoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/kustotoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/kustotojstruct.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/openapitostructure.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/parquettoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/proto2parser.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/proto3parser.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/prototoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/schema_inference.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/sqltoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretocddl.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretocpp.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretocsharp.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretocsv.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretodatapackage.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretodb.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretogo.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretographql.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretoiceberg.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretojava.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretojs.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretojsons.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretokusto.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretomd.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretoproto.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretopython.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretorust.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretots.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/structuretoxsd.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/validate.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/xmltoschema.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/avrotize/xsdtoavro.py +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/build.ps1 +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/build.sh +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/pyproject.toml +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/setup.cfg +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/structurize.egg-info/dependency_links.txt +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/structurize.egg-info/entry_points.txt +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/structurize.egg-info/requires.txt +0 -0
- {structurize-3.4.2 → structurize-3.5.0}/structurize.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structurize
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: Tools to convert from and to JSON Structure from various other schema languages.
|
|
5
5
|
Author-email: Clemens Vasters <clemensv@microsoft.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '3.
|
|
32
|
-
__version_tuple__ = version_tuple = (3,
|
|
31
|
+
__version__ = version = '3.5.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (3, 5, 0)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g95f637e11'
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
from typing import Dict, List, Tuple, Union
|
|
7
|
-
from avrotize.constants import (AVRO_VERSION,
|
|
8
|
-
|
|
7
|
+
from avrotize.constants import (AVRO_VERSION, JACKSON_ANNOTATIONS_VERSION, JACKSON_VERSION,
|
|
8
|
+
JDK_VERSION, JUNIT_VERSION, MAVEN_COMPILER_VERSION, MAVEN_SUREFIRE_VERSION)
|
|
9
9
|
|
|
10
10
|
from avrotize.common import pascal, camel, is_generic_avro_type, inline_avro_references, build_flat_type_dict
|
|
11
11
|
|
|
@@ -42,7 +42,7 @@ POM_CONTENT = """<?xml version="1.0" encoding="UTF-8"?>
|
|
|
42
42
|
<dependency>
|
|
43
43
|
<groupId>com.fasterxml.jackson.core</groupId>
|
|
44
44
|
<artifactId>jackson-annotations</artifactId>
|
|
45
|
-
<version>{
|
|
45
|
+
<version>{JACKSON_ANNOTATIONS_VERSION}</version>
|
|
46
46
|
</dependency>
|
|
47
47
|
<dependency>
|
|
48
48
|
<groupId>org.junit.jupiter</groupId>
|
|
@@ -2160,6 +2160,7 @@ class AvroToJava:
|
|
|
2160
2160
|
artifactid=artifactid,
|
|
2161
2161
|
AVRO_VERSION=AVRO_VERSION,
|
|
2162
2162
|
JACKSON_VERSION=JACKSON_VERSION,
|
|
2163
|
+
JACKSON_ANNOTATIONS_VERSION=JACKSON_ANNOTATIONS_VERSION,
|
|
2163
2164
|
JDK_VERSION=JDK_VERSION,
|
|
2164
2165
|
JUNIT_VERSION=JUNIT_VERSION,
|
|
2165
2166
|
MAVEN_COMPILER_VERSION=MAVEN_COMPILER_VERSION,
|
|
@@ -17,61 +17,66 @@ try:
|
|
|
17
17
|
except ValueError:
|
|
18
18
|
JACKSON_VERSION = '2.18.2' # Fallback
|
|
19
19
|
|
|
20
|
+
try:
|
|
21
|
+
JACKSON_ANNOTATIONS_VERSION = get_dependency_version('java', 'jdk21', 'com.fasterxml.jackson.core:jackson-annotations')
|
|
22
|
+
except ValueError:
|
|
23
|
+
JACKSON_ANNOTATIONS_VERSION = '2.18.2' # Fallback
|
|
24
|
+
|
|
20
25
|
JDK_VERSION = '21'
|
|
21
26
|
|
|
22
27
|
# C# dependencies
|
|
23
28
|
try:
|
|
24
|
-
CSHARP_AVRO_VERSION = get_dependency_version('cs', '
|
|
29
|
+
CSHARP_AVRO_VERSION = get_dependency_version('cs', 'net100', 'Apache.Avro')
|
|
25
30
|
except ValueError:
|
|
26
31
|
CSHARP_AVRO_VERSION = '1.12.0'
|
|
27
32
|
|
|
28
33
|
try:
|
|
29
|
-
NEWTONSOFT_JSON_VERSION = get_dependency_version('cs', '
|
|
34
|
+
NEWTONSOFT_JSON_VERSION = get_dependency_version('cs', 'net100', 'Newtonsoft.Json')
|
|
30
35
|
except ValueError:
|
|
31
36
|
NEWTONSOFT_JSON_VERSION = '13.0.3'
|
|
32
37
|
|
|
33
38
|
try:
|
|
34
|
-
SYSTEM_TEXT_JSON_VERSION = get_dependency_version('cs', '
|
|
39
|
+
SYSTEM_TEXT_JSON_VERSION = get_dependency_version('cs', 'net100', 'System.Text.Json')
|
|
35
40
|
except ValueError:
|
|
36
41
|
SYSTEM_TEXT_JSON_VERSION = '9.0.3'
|
|
37
42
|
|
|
38
43
|
try:
|
|
39
|
-
SYSTEM_MEMORY_DATA_VERSION = get_dependency_version('cs', '
|
|
44
|
+
SYSTEM_MEMORY_DATA_VERSION = get_dependency_version('cs', 'net100', 'System.Memory.Data')
|
|
40
45
|
except ValueError:
|
|
41
46
|
SYSTEM_MEMORY_DATA_VERSION = '9.0.3'
|
|
42
47
|
|
|
43
48
|
try:
|
|
44
|
-
PROTOBUF_NET_VERSION = get_dependency_version('cs', '
|
|
49
|
+
PROTOBUF_NET_VERSION = get_dependency_version('cs', 'net100', 'protobuf-net')
|
|
45
50
|
except ValueError:
|
|
46
51
|
PROTOBUF_NET_VERSION = '3.2.30'
|
|
47
52
|
|
|
48
53
|
try:
|
|
49
|
-
NUNIT_VERSION = get_dependency_version('cs', '
|
|
54
|
+
NUNIT_VERSION = get_dependency_version('cs', 'net100', 'NUnit')
|
|
50
55
|
except ValueError:
|
|
51
56
|
NUNIT_VERSION = '4.3.2'
|
|
52
57
|
|
|
53
58
|
try:
|
|
54
|
-
NUNIT_ADAPTER_VERSION = get_dependency_version('cs', '
|
|
59
|
+
NUNIT_ADAPTER_VERSION = get_dependency_version('cs', 'net100', 'NUnit3TestAdapter')
|
|
55
60
|
except ValueError:
|
|
56
61
|
NUNIT_ADAPTER_VERSION = '5.0.0'
|
|
57
62
|
|
|
58
63
|
try:
|
|
59
|
-
MSTEST_SDK_VERSION = get_dependency_version('cs', '
|
|
64
|
+
MSTEST_SDK_VERSION = get_dependency_version('cs', 'net100', 'Microsoft.NET.Test.Sdk')
|
|
60
65
|
except ValueError:
|
|
61
66
|
MSTEST_SDK_VERSION = '17.13.0'
|
|
62
67
|
|
|
63
68
|
try:
|
|
64
|
-
COVERLET_VERSION = get_dependency_version('cs', '
|
|
69
|
+
COVERLET_VERSION = get_dependency_version('cs', 'net100', 'coverlet.collector')
|
|
65
70
|
except ValueError:
|
|
66
71
|
COVERLET_VERSION = '6.0.4'
|
|
67
72
|
|
|
68
73
|
try:
|
|
69
|
-
MSGPACK_VERSION = get_dependency_version('cs', '
|
|
74
|
+
MSGPACK_VERSION = get_dependency_version('cs', 'net100', 'MessagePack')
|
|
70
75
|
except ValueError:
|
|
71
76
|
MSGPACK_VERSION = '2.5.187'
|
|
72
77
|
|
|
73
78
|
try:
|
|
74
|
-
CBOR_VERSION = get_dependency_version('cs', '
|
|
79
|
+
CBOR_VERSION = get_dependency_version('cs', 'net100', 'Dahomey.Cbor')
|
|
75
80
|
except ValueError:
|
|
76
81
|
CBOR_VERSION = '1.25.1'
|
|
77
82
|
|
|
@@ -89,4 +94,4 @@ except ValueError:
|
|
|
89
94
|
try:
|
|
90
95
|
MAVEN_COMPILER_VERSION = get_dependency_version('java', 'jdk21', 'org.apache.maven.plugins:maven-compiler-plugin')
|
|
91
96
|
except ValueError:
|
|
92
|
-
MAVEN_COMPILER_VERSION = '3.13.0'
|
|
97
|
+
MAVEN_COMPILER_VERSION = '3.13.0'
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""MCP server integration for Avrotize conversions.
|
|
2
|
+
|
|
3
|
+
Implements the MCP (Model Context Protocol) stdio transport directly via
|
|
4
|
+
JSON-RPC 2.0 over stdin/stdout to avoid the heavy ``mcp`` library import
|
|
5
|
+
(~2-3 s for uvicorn, starlette, httpx, pydantic). The ``mcp`` library
|
|
6
|
+
is used only as a fallback when *explicitly* requested.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json as _json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
from typing import Any, Dict, List
|
|
17
|
+
|
|
18
|
+
from avrotize.avrotize import dynamic_import, load_commands
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# MCP protocol version supported by this server
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
_MCP_PROTOCOL_VERSION = "2025-03-26"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _command_dest(arg: Dict[str, Any]) -> str:
|
|
27
|
+
name = arg["name"]
|
|
28
|
+
if name.startswith("-"):
|
|
29
|
+
return arg.get("dest", name.lstrip("-").replace("-", "_").replace(".", "_"))
|
|
30
|
+
return name
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _coerce_value(value: Any, arg_type: str) -> Any:
|
|
34
|
+
if value is None:
|
|
35
|
+
return None
|
|
36
|
+
if arg_type == "bool":
|
|
37
|
+
if isinstance(value, bool):
|
|
38
|
+
return value
|
|
39
|
+
if isinstance(value, (int, float)):
|
|
40
|
+
return bool(value)
|
|
41
|
+
sval = str(value).strip().lower()
|
|
42
|
+
return sval in {"1", "true", "yes", "y", "on"}
|
|
43
|
+
if arg_type == "int":
|
|
44
|
+
return int(value)
|
|
45
|
+
if arg_type == "float":
|
|
46
|
+
return float(value)
|
|
47
|
+
return str(value) if arg_type == "str" else value
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_namespace(command: Dict[str, Any], options: Dict[str, Any]) -> argparse.Namespace:
|
|
51
|
+
namespace = argparse.Namespace()
|
|
52
|
+
normalized_options = {
|
|
53
|
+
key.lstrip("-").replace("-", "_"): value
|
|
54
|
+
for key, value in (options or {}).items()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for arg in command.get("args", []):
|
|
58
|
+
dest = _command_dest(arg)
|
|
59
|
+
default_value = arg.get("default", False if arg.get("type") == "bool" else None)
|
|
60
|
+
setattr(namespace, dest, default_value)
|
|
61
|
+
if dest in normalized_options:
|
|
62
|
+
raw_value = normalized_options[dest]
|
|
63
|
+
if arg.get("nargs") in {"+", "*"}:
|
|
64
|
+
values = raw_value if isinstance(raw_value, list) else [raw_value]
|
|
65
|
+
setattr(namespace, dest, [_coerce_value(v, arg["type"]) for v in values])
|
|
66
|
+
else:
|
|
67
|
+
setattr(namespace, dest, _coerce_value(raw_value, arg["type"]))
|
|
68
|
+
|
|
69
|
+
return namespace
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _resolve_input_path(command_args: argparse.Namespace, explicit_input_path: str | None) -> str | None:
|
|
73
|
+
if explicit_input_path:
|
|
74
|
+
return explicit_input_path
|
|
75
|
+
return (
|
|
76
|
+
getattr(command_args, "input", None)
|
|
77
|
+
or getattr(command_args, "avsc", None)
|
|
78
|
+
or getattr(command_args, "proto", None)
|
|
79
|
+
or getattr(command_args, "jsons", None)
|
|
80
|
+
or getattr(command_args, "xsd", None)
|
|
81
|
+
or getattr(command_args, "kusto_uri", None)
|
|
82
|
+
or getattr(command_args, "parquet", None)
|
|
83
|
+
or getattr(command_args, "asn", None)
|
|
84
|
+
or getattr(command_args, "kstruct", None)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _find_primary_input_arg(command: Dict[str, Any]) -> Dict[str, Any] | None:
|
|
89
|
+
return next(
|
|
90
|
+
(arg for arg in command.get("args", []) if isinstance(arg.get("name"), str) and not arg["name"].startswith("-")),
|
|
91
|
+
None,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _inject_input_into_namespace(command: Dict[str, Any], command_args: argparse.Namespace, input_value: str) -> None:
|
|
96
|
+
primary_input_arg = _find_primary_input_arg(command)
|
|
97
|
+
if not primary_input_arg:
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
dest = _command_dest(primary_input_arg)
|
|
101
|
+
current_value = getattr(command_args, dest, None)
|
|
102
|
+
|
|
103
|
+
if primary_input_arg.get("nargs") in {"+", "*"}:
|
|
104
|
+
if current_value in (None, "", []):
|
|
105
|
+
setattr(command_args, dest, [input_value])
|
|
106
|
+
elif isinstance(current_value, list) and input_value not in current_value:
|
|
107
|
+
setattr(command_args, dest, [input_value, *current_value])
|
|
108
|
+
elif not isinstance(current_value, list):
|
|
109
|
+
setattr(command_args, dest, [input_value, current_value])
|
|
110
|
+
else:
|
|
111
|
+
if current_value in (None, ""):
|
|
112
|
+
setattr(command_args, dest, input_value)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _find_command(command_name: str) -> Dict[str, Any] | None:
|
|
116
|
+
return next((cmd for cmd in load_commands() if cmd.get("command") == command_name), None)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _list_commands() -> List[Dict[str, Any]]:
|
|
120
|
+
commands = load_commands()
|
|
121
|
+
result: List[Dict[str, Any]] = []
|
|
122
|
+
for command in commands:
|
|
123
|
+
if command.get("command") == "mcp":
|
|
124
|
+
continue
|
|
125
|
+
result.append(
|
|
126
|
+
{
|
|
127
|
+
"command": command.get("command"),
|
|
128
|
+
"description": command.get("description"),
|
|
129
|
+
"group": command.get("group"),
|
|
130
|
+
"args": [arg.get("name") for arg in command.get("args", [])],
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _describe_capabilities() -> Dict[str, Any]:
|
|
137
|
+
commands = load_commands()
|
|
138
|
+
command_names = [cmd.get("command") for cmd in commands if cmd.get("command") != "mcp"]
|
|
139
|
+
return {
|
|
140
|
+
"server": "avrotize",
|
|
141
|
+
"purpose": "Schema conversion and schema-driven code generation",
|
|
142
|
+
"use_when": [
|
|
143
|
+
"Converting between schema formats (Avro, JSON Schema, Proto, XSD, Parquet, etc.)",
|
|
144
|
+
"Generating code from Avro/JSON Structure schemas (C#, Java, Python, TypeScript, JavaScript, Rust, Go, C++)",
|
|
145
|
+
"Inferring schemas from sample JSON/XML/CSV/parquet inputs",
|
|
146
|
+
],
|
|
147
|
+
"do_not_use_when": [
|
|
148
|
+
"Task is unrelated to schema conversion or code generation",
|
|
149
|
+
"Task requires arbitrary code execution outside avrotize command set",
|
|
150
|
+
],
|
|
151
|
+
"recommended_flow": [
|
|
152
|
+
"Call describe_capabilities for high-level routing",
|
|
153
|
+
"Call list_conversions to discover available commands",
|
|
154
|
+
"Call get_conversion(command) to inspect required args/options",
|
|
155
|
+
"Call run_conversion(...) to execute",
|
|
156
|
+
],
|
|
157
|
+
"tools": {
|
|
158
|
+
"describe_capabilities": "High-level guidance for when and how to use this server",
|
|
159
|
+
"list_conversions": "List available conversion/code generation commands",
|
|
160
|
+
"get_conversion": "Inspect metadata and args for a specific command",
|
|
161
|
+
"run_conversion": "Execute a specific conversion command",
|
|
162
|
+
},
|
|
163
|
+
"command_count": len(command_names),
|
|
164
|
+
"commands": command_names,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _execute_conversion(
|
|
169
|
+
command_name: str,
|
|
170
|
+
input_path: str | None = None,
|
|
171
|
+
input_content: str | None = None,
|
|
172
|
+
output_path: str | None = None,
|
|
173
|
+
options: Dict[str, Any] | None = None,
|
|
174
|
+
) -> Dict[str, Any]:
|
|
175
|
+
command = _find_command(command_name)
|
|
176
|
+
if not command:
|
|
177
|
+
raise ValueError(f"Unknown command '{command_name}'.")
|
|
178
|
+
|
|
179
|
+
command_args = _build_namespace(command, options or {})
|
|
180
|
+
temp_input_path = None
|
|
181
|
+
temp_output_path = None
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
resolved_input = _resolve_input_path(command_args, input_path)
|
|
185
|
+
skip_input_file_handling = command.get("skip_input_file_handling", False)
|
|
186
|
+
if input_content is not None and not resolved_input:
|
|
187
|
+
temp_input = tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8")
|
|
188
|
+
temp_input.write(input_content)
|
|
189
|
+
temp_input.flush()
|
|
190
|
+
temp_input.close()
|
|
191
|
+
temp_input_path = temp_input.name
|
|
192
|
+
resolved_input = temp_input_path
|
|
193
|
+
|
|
194
|
+
if not skip_input_file_handling and not resolved_input:
|
|
195
|
+
if input_content is None:
|
|
196
|
+
raise ValueError("This command requires input_path or input_content.")
|
|
197
|
+
|
|
198
|
+
if resolved_input:
|
|
199
|
+
_inject_input_into_namespace(command, command_args, resolved_input)
|
|
200
|
+
|
|
201
|
+
if not output_path and any(v == "output_file_path" for v in command.get("function", {}).get("args", {}).values()):
|
|
202
|
+
temp_output = tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8")
|
|
203
|
+
temp_output_path = temp_output.name
|
|
204
|
+
temp_output.close()
|
|
205
|
+
output_path = temp_output_path
|
|
206
|
+
|
|
207
|
+
module_name, func_name = command["function"]["name"].rsplit(".", 1)
|
|
208
|
+
func = dynamic_import(module_name, func_name)
|
|
209
|
+
|
|
210
|
+
func_args: Dict[str, Any] = {}
|
|
211
|
+
for arg_name, arg_value in command["function"]["args"].items():
|
|
212
|
+
if arg_value == "input_file_path":
|
|
213
|
+
func_args[arg_name] = resolved_input
|
|
214
|
+
elif arg_value == "output_file_path":
|
|
215
|
+
func_args[arg_name] = output_path
|
|
216
|
+
elif isinstance(arg_value, str) and arg_value.startswith("args."):
|
|
217
|
+
attr_name = arg_value[5:]
|
|
218
|
+
if hasattr(command_args, attr_name):
|
|
219
|
+
func_args[arg_name] = getattr(command_args, attr_name)
|
|
220
|
+
else:
|
|
221
|
+
func_args[arg_name] = arg_value
|
|
222
|
+
|
|
223
|
+
func(**func_args)
|
|
224
|
+
|
|
225
|
+
output_content = None
|
|
226
|
+
if temp_output_path and os.path.exists(temp_output_path):
|
|
227
|
+
with open(temp_output_path, "r", encoding="utf-8") as out_file:
|
|
228
|
+
output_content = out_file.read()
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
"success": True,
|
|
232
|
+
"command": command_name,
|
|
233
|
+
"output_path": output_path,
|
|
234
|
+
"output_content": output_content,
|
|
235
|
+
}
|
|
236
|
+
finally:
|
|
237
|
+
if temp_input_path and os.path.exists(temp_input_path):
|
|
238
|
+
os.remove(temp_input_path)
|
|
239
|
+
if temp_output_path and os.path.exists(temp_output_path):
|
|
240
|
+
os.remove(temp_output_path)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _tool_schemas() -> List[Dict[str, Any]]:
|
|
244
|
+
"""Return MCP tool definitions for the four exposed tools."""
|
|
245
|
+
return [
|
|
246
|
+
{
|
|
247
|
+
"name": "describe_capabilities",
|
|
248
|
+
"description": "Describe when this server should be used and how to invoke it.",
|
|
249
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"name": "list_conversions",
|
|
253
|
+
"description": "List available Avrotize conversion commands.",
|
|
254
|
+
"inputSchema": {"type": "object", "properties": {}},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"name": "get_conversion",
|
|
258
|
+
"description": "Get metadata for a specific conversion command.",
|
|
259
|
+
"inputSchema": {
|
|
260
|
+
"type": "object",
|
|
261
|
+
"properties": {
|
|
262
|
+
"command": {"type": "string", "description": "The conversion command name."},
|
|
263
|
+
},
|
|
264
|
+
"required": ["command"],
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"name": "run_conversion",
|
|
269
|
+
"description": "Run a conversion command and return conversion output information.",
|
|
270
|
+
"inputSchema": {
|
|
271
|
+
"type": "object",
|
|
272
|
+
"properties": {
|
|
273
|
+
"command": {"type": "string", "description": "The conversion command name."},
|
|
274
|
+
"input_path": {"type": "string", "description": "Path to the input file.", "default": ""},
|
|
275
|
+
"input_content": {"type": "string", "description": "Inline input content.", "default": ""},
|
|
276
|
+
"output_path": {"type": "string", "description": "Path for the output file.", "default": ""},
|
|
277
|
+
"options": {
|
|
278
|
+
"type": "object",
|
|
279
|
+
"description": "Additional command options.",
|
|
280
|
+
"additionalProperties": True,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
"required": ["command"],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _dispatch_tool(name: str, arguments: Dict[str, Any]) -> Any:
|
|
290
|
+
"""Dispatch a tool call by *name* and return a Python object."""
|
|
291
|
+
if name == "describe_capabilities":
|
|
292
|
+
return _describe_capabilities()
|
|
293
|
+
if name == "list_conversions":
|
|
294
|
+
return _list_commands()
|
|
295
|
+
if name == "get_conversion":
|
|
296
|
+
return _get_conversion_handler(arguments)
|
|
297
|
+
if name == "run_conversion":
|
|
298
|
+
return _run_conversion_handler(arguments)
|
|
299
|
+
raise ValueError(f"Unknown tool '{name}'.")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _get_conversion_handler(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
303
|
+
command_name = args.get("command", "")
|
|
304
|
+
cmd = _find_command(command_name)
|
|
305
|
+
if not cmd:
|
|
306
|
+
raise ValueError(f"Unknown command '{command_name}'.")
|
|
307
|
+
return {
|
|
308
|
+
"command": cmd.get("command"),
|
|
309
|
+
"description": cmd.get("description"),
|
|
310
|
+
"group": cmd.get("group"),
|
|
311
|
+
"args": cmd.get("args", []),
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _run_conversion_handler(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
316
|
+
return _execute_conversion(
|
|
317
|
+
command_name=args.get("command", ""),
|
|
318
|
+
input_path=args.get("input_path") or None,
|
|
319
|
+
input_content=args.get("input_content") or None,
|
|
320
|
+
output_path=args.get("output_path") or None,
|
|
321
|
+
options=args.get("options") or {},
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# ---------------------------------------------------------------------------
|
|
326
|
+
# Lightweight JSON-RPC / MCP stdio transport
|
|
327
|
+
# ---------------------------------------------------------------------------
|
|
328
|
+
|
|
329
|
+
def _jsonrpc_error(req_id: Any, code: int, message: str) -> Dict[str, Any]:
|
|
330
|
+
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": code, "message": message}}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _jsonrpc_result(req_id: Any, result: Any) -> Dict[str, Any]:
|
|
334
|
+
return {"jsonrpc": "2.0", "id": req_id, "result": result}
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _handle_message(msg: Dict[str, Any]) -> Dict[str, Any] | None:
|
|
338
|
+
"""Process one JSON-RPC message. Returns a response dict, or None for notifications."""
|
|
339
|
+
method = msg.get("method", "")
|
|
340
|
+
req_id = msg.get("id")
|
|
341
|
+
params = msg.get("params", {})
|
|
342
|
+
|
|
343
|
+
# --- Lifecycle ----------------------------------------------------------
|
|
344
|
+
if method == "initialize":
|
|
345
|
+
return _jsonrpc_result(req_id, {
|
|
346
|
+
"protocolVersion": _MCP_PROTOCOL_VERSION,
|
|
347
|
+
"capabilities": {"tools": {}},
|
|
348
|
+
"serverInfo": {"name": "avrotize", "version": _get_version()},
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
if method == "notifications/initialized":
|
|
352
|
+
return None # notification — no response
|
|
353
|
+
|
|
354
|
+
if method == "ping":
|
|
355
|
+
return _jsonrpc_result(req_id, {})
|
|
356
|
+
|
|
357
|
+
# --- Tool discovery -----------------------------------------------------
|
|
358
|
+
if method == "tools/list":
|
|
359
|
+
return _jsonrpc_result(req_id, {"tools": _tool_schemas()})
|
|
360
|
+
|
|
361
|
+
# --- Tool execution -----------------------------------------------------
|
|
362
|
+
if method == "tools/call":
|
|
363
|
+
tool_name = params.get("name", "")
|
|
364
|
+
arguments = params.get("arguments", {})
|
|
365
|
+
try:
|
|
366
|
+
result = _dispatch_tool(tool_name, arguments)
|
|
367
|
+
text = _json.dumps(result, default=str)
|
|
368
|
+
return _jsonrpc_result(req_id, {
|
|
369
|
+
"content": [{"type": "text", "text": text}],
|
|
370
|
+
})
|
|
371
|
+
except Exception as exc:
|
|
372
|
+
return _jsonrpc_result(req_id, {
|
|
373
|
+
"content": [{"type": "text", "text": str(exc)}],
|
|
374
|
+
"isError": True,
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
# Notifications we don't handle — silently ignore
|
|
378
|
+
if req_id is None:
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
return _jsonrpc_error(req_id, -32601, f"Method not found: {method}")
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _get_version() -> str:
|
|
385
|
+
try:
|
|
386
|
+
from avrotize._version import version
|
|
387
|
+
return version
|
|
388
|
+
except Exception:
|
|
389
|
+
return "dev"
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _run_stdio_loop() -> None:
|
|
393
|
+
"""Read JSON-RPC messages from stdin, write responses to stdout."""
|
|
394
|
+
reader = sys.stdin
|
|
395
|
+
writer = sys.stdout
|
|
396
|
+
for line in reader:
|
|
397
|
+
line = line.strip()
|
|
398
|
+
if not line:
|
|
399
|
+
continue
|
|
400
|
+
try:
|
|
401
|
+
msg = _json.loads(line)
|
|
402
|
+
except _json.JSONDecodeError:
|
|
403
|
+
resp = _jsonrpc_error(None, -32700, "Parse error")
|
|
404
|
+
writer.write(_json.dumps(resp) + "\n")
|
|
405
|
+
writer.flush()
|
|
406
|
+
continue
|
|
407
|
+
|
|
408
|
+
resp = _handle_message(msg)
|
|
409
|
+
if resp is not None:
|
|
410
|
+
writer.write(_json.dumps(resp) + "\n")
|
|
411
|
+
writer.flush()
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def run_mcp_server(transport: str = "stdio"):
|
|
415
|
+
"""Run avrotize as a local MCP server.
|
|
416
|
+
|
|
417
|
+
Uses a lightweight built-in JSON-RPC stdio implementation by default.
|
|
418
|
+
"""
|
|
419
|
+
if transport != "stdio":
|
|
420
|
+
raise ValueError("Only 'stdio' transport is currently supported.")
|
|
421
|
+
_run_stdio_loop()
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def run_mcp_server_fastmcp(transport: str = "stdio"):
|
|
425
|
+
"""Run avrotize as a local MCP server using the ``mcp`` library.
|
|
426
|
+
|
|
427
|
+
This is slower to start (~2-3 s) because the ``mcp`` library eagerly
|
|
428
|
+
imports uvicorn, starlette, and httpx. Kept as a compatibility fallback.
|
|
429
|
+
"""
|
|
430
|
+
try:
|
|
431
|
+
from mcp.server.fastmcp import FastMCP
|
|
432
|
+
except ImportError as exc:
|
|
433
|
+
raise RuntimeError(
|
|
434
|
+
"MCP support is not installed. Install with: pip install mcp"
|
|
435
|
+
) from exc
|
|
436
|
+
|
|
437
|
+
if transport != "stdio":
|
|
438
|
+
raise ValueError("Only 'stdio' transport is currently supported.")
|
|
439
|
+
|
|
440
|
+
mcp = FastMCP("avrotize")
|
|
441
|
+
|
|
442
|
+
@mcp.tool()
|
|
443
|
+
def describe_capabilities() -> Dict[str, Any]:
|
|
444
|
+
"""Describe when this server should be used and how to invoke it."""
|
|
445
|
+
return _describe_capabilities()
|
|
446
|
+
|
|
447
|
+
@mcp.tool()
|
|
448
|
+
def list_conversions() -> List[Dict[str, Any]]:
|
|
449
|
+
"""List available Avrotize conversion commands."""
|
|
450
|
+
return _list_commands()
|
|
451
|
+
|
|
452
|
+
@mcp.tool()
|
|
453
|
+
def get_conversion(command: str) -> Dict[str, Any]:
|
|
454
|
+
"""Get metadata for a specific conversion command."""
|
|
455
|
+
cmd = _find_command(command)
|
|
456
|
+
if not cmd:
|
|
457
|
+
raise ValueError(f"Unknown command '{command}'.")
|
|
458
|
+
return {
|
|
459
|
+
"command": cmd.get("command"),
|
|
460
|
+
"description": cmd.get("description"),
|
|
461
|
+
"group": cmd.get("group"),
|
|
462
|
+
"args": cmd.get("args", []),
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
@mcp.tool()
|
|
466
|
+
def run_conversion(
|
|
467
|
+
command: str,
|
|
468
|
+
input_path: str = "",
|
|
469
|
+
input_content: str = "",
|
|
470
|
+
output_path: str = "",
|
|
471
|
+
options: Dict[str, Any] | None = None,
|
|
472
|
+
) -> Dict[str, Any]:
|
|
473
|
+
"""Run a conversion command and return conversion output information."""
|
|
474
|
+
return _execute_conversion(
|
|
475
|
+
command_name=command,
|
|
476
|
+
input_path=input_path or None,
|
|
477
|
+
input_content=input_content or None,
|
|
478
|
+
output_path=output_path or None,
|
|
479
|
+
options=options or {},
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
try:
|
|
483
|
+
mcp.run(transport="stdio")
|
|
484
|
+
except TypeError:
|
|
485
|
+
mcp.run()
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
# ---------------------------------------------------------------------------
|
|
489
|
+
# Direct entry point — bypasses the avrotize CLI argparse overhead.
|
|
490
|
+
# ---------------------------------------------------------------------------
|
|
491
|
+
|
|
492
|
+
def main() -> None:
|
|
493
|
+
"""Entry point for the ``avrotize-mcp`` console script."""
|
|
494
|
+
run_mcp_server("stdio")
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
if __name__ == "__main__":
|
|
498
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structurize
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.5.0
|
|
4
4
|
Summary: Tools to convert from and to JSON Structure from various other schema languages.
|
|
5
5
|
Author-email: Clemens Vasters <clemensv@microsoft.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -120,6 +120,7 @@ pyproject.toml
|
|
|
120
120
|
../avrotize/avrotots/package.json.jinja
|
|
121
121
|
../avrotize/avrotots/tsconfig.json.jinja
|
|
122
122
|
../avrotize/dependencies/cpp/vcpkg/vcpkg.json
|
|
123
|
+
../avrotize/dependencies/cs/net100/dependencies.csproj
|
|
123
124
|
../avrotize/dependencies/cs/net90/dependencies.csproj
|
|
124
125
|
../avrotize/dependencies/go/go121/go.mod
|
|
125
126
|
../avrotize/dependencies/java/jdk21/pom.xml
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
"""MCP server integration for Avrotize conversions."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
import os
|
|
7
|
-
import tempfile
|
|
8
|
-
from typing import Any, Dict, List
|
|
9
|
-
|
|
10
|
-
from avrotize.avrotize import dynamic_import, load_commands
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def _command_dest(arg: Dict[str, Any]) -> str:
|
|
14
|
-
name = arg["name"]
|
|
15
|
-
if name.startswith("-"):
|
|
16
|
-
return arg.get("dest", name.lstrip("-").replace("-", "_").replace(".", "_"))
|
|
17
|
-
return name
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _coerce_value(value: Any, arg_type: str) -> Any:
|
|
21
|
-
if value is None:
|
|
22
|
-
return None
|
|
23
|
-
if arg_type == "bool":
|
|
24
|
-
if isinstance(value, bool):
|
|
25
|
-
return value
|
|
26
|
-
if isinstance(value, (int, float)):
|
|
27
|
-
return bool(value)
|
|
28
|
-
sval = str(value).strip().lower()
|
|
29
|
-
return sval in {"1", "true", "yes", "y", "on"}
|
|
30
|
-
if arg_type == "int":
|
|
31
|
-
return int(value)
|
|
32
|
-
if arg_type == "float":
|
|
33
|
-
return float(value)
|
|
34
|
-
return str(value) if arg_type == "str" else value
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _build_namespace(command: Dict[str, Any], options: Dict[str, Any]) -> argparse.Namespace:
|
|
38
|
-
namespace = argparse.Namespace()
|
|
39
|
-
normalized_options = {
|
|
40
|
-
key.lstrip("-").replace("-", "_"): value
|
|
41
|
-
for key, value in (options or {}).items()
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for arg in command.get("args", []):
|
|
45
|
-
dest = _command_dest(arg)
|
|
46
|
-
default_value = arg.get("default", False if arg.get("type") == "bool" else None)
|
|
47
|
-
setattr(namespace, dest, default_value)
|
|
48
|
-
if dest in normalized_options:
|
|
49
|
-
setattr(namespace, dest, _coerce_value(normalized_options[dest], arg["type"]))
|
|
50
|
-
|
|
51
|
-
return namespace
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def _resolve_input_path(command_args: argparse.Namespace, explicit_input_path: str | None) -> str | None:
|
|
55
|
-
if explicit_input_path:
|
|
56
|
-
return explicit_input_path
|
|
57
|
-
return (
|
|
58
|
-
getattr(command_args, "input", None)
|
|
59
|
-
or getattr(command_args, "avsc", None)
|
|
60
|
-
or getattr(command_args, "proto", None)
|
|
61
|
-
or getattr(command_args, "jsons", None)
|
|
62
|
-
or getattr(command_args, "xsd", None)
|
|
63
|
-
or getattr(command_args, "kusto_uri", None)
|
|
64
|
-
or getattr(command_args, "parquet", None)
|
|
65
|
-
or getattr(command_args, "asn", None)
|
|
66
|
-
or getattr(command_args, "kstruct", None)
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _find_command(command_name: str) -> Dict[str, Any] | None:
|
|
71
|
-
return next((cmd for cmd in load_commands() if cmd.get("command") == command_name), None)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _list_commands() -> List[Dict[str, Any]]:
|
|
75
|
-
commands = load_commands()
|
|
76
|
-
result: List[Dict[str, Any]] = []
|
|
77
|
-
for command in commands:
|
|
78
|
-
if command.get("command") == "mcp":
|
|
79
|
-
continue
|
|
80
|
-
result.append(
|
|
81
|
-
{
|
|
82
|
-
"command": command.get("command"),
|
|
83
|
-
"description": command.get("description"),
|
|
84
|
-
"group": command.get("group"),
|
|
85
|
-
"args": [arg.get("name") for arg in command.get("args", [])],
|
|
86
|
-
}
|
|
87
|
-
)
|
|
88
|
-
return result
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def _describe_capabilities() -> Dict[str, Any]:
|
|
92
|
-
commands = load_commands()
|
|
93
|
-
command_names = [cmd.get("command") for cmd in commands if cmd.get("command") != "mcp"]
|
|
94
|
-
return {
|
|
95
|
-
"server": "avrotize",
|
|
96
|
-
"purpose": "Schema conversion and schema-driven code generation",
|
|
97
|
-
"use_when": [
|
|
98
|
-
"Converting between schema formats (Avro, JSON Schema, Proto, XSD, Parquet, etc.)",
|
|
99
|
-
"Generating code from Avro/JSON Structure schemas (C#, Java, Python, TypeScript, JavaScript, Rust, Go, C++)",
|
|
100
|
-
"Inferring schemas from sample JSON/XML/CSV/parquet inputs",
|
|
101
|
-
],
|
|
102
|
-
"do_not_use_when": [
|
|
103
|
-
"Task is unrelated to schema conversion or code generation",
|
|
104
|
-
"Task requires arbitrary code execution outside avrotize command set",
|
|
105
|
-
],
|
|
106
|
-
"recommended_flow": [
|
|
107
|
-
"Call describe_capabilities for high-level routing",
|
|
108
|
-
"Call list_conversions to discover available commands",
|
|
109
|
-
"Call get_conversion(command) to inspect required args/options",
|
|
110
|
-
"Call run_conversion(...) to execute",
|
|
111
|
-
],
|
|
112
|
-
"tools": {
|
|
113
|
-
"describe_capabilities": "High-level guidance for when and how to use this server",
|
|
114
|
-
"list_conversions": "List available conversion/code generation commands",
|
|
115
|
-
"get_conversion": "Inspect metadata and args for a specific command",
|
|
116
|
-
"run_conversion": "Execute a specific conversion command",
|
|
117
|
-
},
|
|
118
|
-
"command_count": len(command_names),
|
|
119
|
-
"commands": command_names,
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _execute_conversion(
|
|
124
|
-
command_name: str,
|
|
125
|
-
input_path: str | None = None,
|
|
126
|
-
input_content: str | None = None,
|
|
127
|
-
output_path: str | None = None,
|
|
128
|
-
options: Dict[str, Any] | None = None,
|
|
129
|
-
) -> Dict[str, Any]:
|
|
130
|
-
command = _find_command(command_name)
|
|
131
|
-
if not command:
|
|
132
|
-
raise ValueError(f"Unknown command '{command_name}'.")
|
|
133
|
-
|
|
134
|
-
command_args = _build_namespace(command, options or {})
|
|
135
|
-
temp_input_path = None
|
|
136
|
-
temp_output_path = None
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
resolved_input = _resolve_input_path(command_args, input_path)
|
|
140
|
-
skip_input_file_handling = command.get("skip_input_file_handling", False)
|
|
141
|
-
if not skip_input_file_handling and not resolved_input:
|
|
142
|
-
if input_content is None:
|
|
143
|
-
raise ValueError("This command requires input_path or input_content.")
|
|
144
|
-
temp_input = tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8")
|
|
145
|
-
temp_input.write(input_content)
|
|
146
|
-
temp_input.flush()
|
|
147
|
-
temp_input.close()
|
|
148
|
-
temp_input_path = temp_input.name
|
|
149
|
-
resolved_input = temp_input_path
|
|
150
|
-
|
|
151
|
-
if not output_path and any(v == "output_file_path" for v in command.get("function", {}).get("args", {}).values()):
|
|
152
|
-
temp_output = tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8")
|
|
153
|
-
temp_output_path = temp_output.name
|
|
154
|
-
temp_output.close()
|
|
155
|
-
output_path = temp_output_path
|
|
156
|
-
|
|
157
|
-
module_name, func_name = command["function"]["name"].rsplit(".", 1)
|
|
158
|
-
func = dynamic_import(module_name, func_name)
|
|
159
|
-
|
|
160
|
-
func_args: Dict[str, Any] = {}
|
|
161
|
-
for arg_name, arg_value in command["function"]["args"].items():
|
|
162
|
-
if arg_value == "input_file_path":
|
|
163
|
-
func_args[arg_name] = resolved_input
|
|
164
|
-
elif arg_value == "output_file_path":
|
|
165
|
-
func_args[arg_name] = output_path
|
|
166
|
-
elif isinstance(arg_value, str) and arg_value.startswith("args."):
|
|
167
|
-
attr_name = arg_value[5:]
|
|
168
|
-
if hasattr(command_args, attr_name):
|
|
169
|
-
func_args[arg_name] = getattr(command_args, attr_name)
|
|
170
|
-
else:
|
|
171
|
-
func_args[arg_name] = arg_value
|
|
172
|
-
|
|
173
|
-
func(**func_args)
|
|
174
|
-
|
|
175
|
-
output_content = None
|
|
176
|
-
if temp_output_path and os.path.exists(temp_output_path):
|
|
177
|
-
with open(temp_output_path, "r", encoding="utf-8") as out_file:
|
|
178
|
-
output_content = out_file.read()
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
"success": True,
|
|
182
|
-
"command": command_name,
|
|
183
|
-
"output_path": output_path,
|
|
184
|
-
"output_content": output_content,
|
|
185
|
-
}
|
|
186
|
-
finally:
|
|
187
|
-
if temp_input_path and os.path.exists(temp_input_path):
|
|
188
|
-
os.remove(temp_input_path)
|
|
189
|
-
if temp_output_path and os.path.exists(temp_output_path):
|
|
190
|
-
os.remove(temp_output_path)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def run_mcp_server(transport: str = "stdio"):
|
|
194
|
-
"""Run avrotize as a local MCP server.
|
|
195
|
-
|
|
196
|
-
Exposes conversion tools to MCP clients.
|
|
197
|
-
"""
|
|
198
|
-
try:
|
|
199
|
-
from mcp.server.fastmcp import FastMCP
|
|
200
|
-
except ImportError as exc:
|
|
201
|
-
raise RuntimeError(
|
|
202
|
-
"MCP support is not installed. Install with: pip install mcp"
|
|
203
|
-
) from exc
|
|
204
|
-
|
|
205
|
-
if transport != "stdio":
|
|
206
|
-
raise ValueError("Only 'stdio' transport is currently supported.")
|
|
207
|
-
|
|
208
|
-
mcp = FastMCP("avrotize")
|
|
209
|
-
|
|
210
|
-
@mcp.tool()
|
|
211
|
-
def describe_capabilities() -> Dict[str, Any]:
|
|
212
|
-
"""Describe when this server should be used and how to invoke it."""
|
|
213
|
-
return _describe_capabilities()
|
|
214
|
-
|
|
215
|
-
@mcp.tool()
|
|
216
|
-
def list_conversions() -> List[Dict[str, Any]]:
|
|
217
|
-
"""List available Avrotize conversion commands."""
|
|
218
|
-
return _list_commands()
|
|
219
|
-
|
|
220
|
-
@mcp.tool()
|
|
221
|
-
def get_conversion(command: str) -> Dict[str, Any]:
|
|
222
|
-
"""Get metadata for a specific conversion command."""
|
|
223
|
-
cmd = _find_command(command)
|
|
224
|
-
if not cmd:
|
|
225
|
-
raise ValueError(f"Unknown command '{command}'.")
|
|
226
|
-
return {
|
|
227
|
-
"command": cmd.get("command"),
|
|
228
|
-
"description": cmd.get("description"),
|
|
229
|
-
"group": cmd.get("group"),
|
|
230
|
-
"args": cmd.get("args", []),
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
@mcp.tool()
|
|
234
|
-
def run_conversion(
|
|
235
|
-
command: str,
|
|
236
|
-
input_path: str = "",
|
|
237
|
-
input_content: str = "",
|
|
238
|
-
output_path: str = "",
|
|
239
|
-
options: Dict[str, Any] | None = None,
|
|
240
|
-
) -> Dict[str, Any]:
|
|
241
|
-
"""Run a conversion command and return conversion output information."""
|
|
242
|
-
return _execute_conversion(
|
|
243
|
-
command_name=command,
|
|
244
|
-
input_path=input_path or None,
|
|
245
|
-
input_content=input_content or None,
|
|
246
|
-
output_path=output_path or None,
|
|
247
|
-
options=options or {},
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
try:
|
|
251
|
-
mcp.run(transport="stdio")
|
|
252
|
-
except TypeError:
|
|
253
|
-
mcp.run()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|