structurize 3.4.3__tar.gz → 3.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {structurize-3.4.3/structurize.egg-info → structurize-3.5.1}/PKG-INFO +2 -2
  2. structurize-3.5.1/avrotize/_version.py +24 -0
  3. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotojava.py +4 -3
  4. {structurize-3.4.3 → structurize-3.5.1}/avrotize/cddltostructure.py +18 -1
  5. {structurize-3.4.3 → structurize-3.5.1}/avrotize/choice_inference.py +7 -7
  6. {structurize-3.4.3 → structurize-3.5.1}/avrotize/constants.py +17 -12
  7. {structurize-3.4.3 → structurize-3.5.1}/avrotize/dependencies/typescript/node22/package.json +1 -1
  8. {structurize-3.4.3 → structurize-3.5.1}/avrotize/mcp_server.py +210 -2
  9. {structurize-3.4.3 → structurize-3.5.1}/avrotize/schema_inference.py +9 -4
  10. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretojava.py +3 -2
  11. {structurize-3.4.3 → structurize-3.5.1}/pyproject.toml +1 -1
  12. {structurize-3.4.3 → structurize-3.5.1/structurize.egg-info}/PKG-INFO +2 -2
  13. {structurize-3.4.3 → structurize-3.5.1}/structurize.egg-info/SOURCES.txt +1 -0
  14. {structurize-3.4.3 → structurize-3.5.1}/structurize.egg-info/requires.txt +1 -1
  15. structurize-3.4.3/avrotize/_version.py +0 -34
  16. {structurize-3.4.3 → structurize-3.5.1}/.gitignore +0 -0
  17. {structurize-3.4.3 → structurize-3.5.1}/LICENSE +0 -0
  18. {structurize-3.4.3 → structurize-3.5.1}/MANIFEST.in +0 -0
  19. {structurize-3.4.3 → structurize-3.5.1}/README.md +0 -0
  20. {structurize-3.4.3 → structurize-3.5.1}/avrotize/__init__.py +0 -0
  21. {structurize-3.4.3 → structurize-3.5.1}/avrotize/__main__.py +0 -0
  22. {structurize-3.4.3 → structurize-3.5.1}/avrotize/asn1toavro.py +0 -0
  23. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotize.py +0 -0
  24. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotocpp.py +0 -0
  25. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotocsharp.py +0 -0
  26. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotocsv.py +0 -0
  27. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotodatapackage.py +0 -0
  28. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotodb.py +0 -0
  29. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotogo.py +0 -0
  30. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotographql.py +0 -0
  31. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotoiceberg.py +0 -0
  32. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotojs.py +0 -0
  33. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotojsons.py +0 -0
  34. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotojstruct.py +0 -0
  35. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotokusto.py +0 -0
  36. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotomd.py +0 -0
  37. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotools.py +0 -0
  38. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotoparquet.py +0 -0
  39. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotoproto.py +0 -0
  40. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotopython.py +0 -0
  41. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotorust.py +0 -0
  42. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotots.py +0 -0
  43. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrotoxsd.py +0 -0
  44. {structurize-3.4.3 → structurize-3.5.1}/avrotize/avrovalidator.py +0 -0
  45. {structurize-3.4.3 → structurize-3.5.1}/avrotize/commands.json +0 -0
  46. {structurize-3.4.3 → structurize-3.5.1}/avrotize/common.py +0 -0
  47. {structurize-3.4.3 → structurize-3.5.1}/avrotize/csvtoavro.py +0 -0
  48. {structurize-3.4.3 → structurize-3.5.1}/avrotize/datapackagetoavro.py +0 -0
  49. {structurize-3.4.3 → structurize-3.5.1}/avrotize/dependencies/cpp/vcpkg/vcpkg.json +0 -0
  50. {structurize-3.4.3 → structurize-3.5.1}/avrotize/dependency_resolver.py +0 -0
  51. {structurize-3.4.3 → structurize-3.5.1}/avrotize/dependency_version.py +0 -0
  52. {structurize-3.4.3 → structurize-3.5.1}/avrotize/jsonstoavro.py +0 -0
  53. {structurize-3.4.3 → structurize-3.5.1}/avrotize/jsonstostructure.py +0 -0
  54. {structurize-3.4.3 → structurize-3.5.1}/avrotize/jsontoschema.py +0 -0
  55. {structurize-3.4.3 → structurize-3.5.1}/avrotize/jstructtoavro.py +0 -0
  56. {structurize-3.4.3 → structurize-3.5.1}/avrotize/kstructtoavro.py +0 -0
  57. {structurize-3.4.3 → structurize-3.5.1}/avrotize/kustotoavro.py +0 -0
  58. {structurize-3.4.3 → structurize-3.5.1}/avrotize/kustotojstruct.py +0 -0
  59. {structurize-3.4.3 → structurize-3.5.1}/avrotize/openapitostructure.py +0 -0
  60. {structurize-3.4.3 → structurize-3.5.1}/avrotize/parquettoavro.py +0 -0
  61. {structurize-3.4.3 → structurize-3.5.1}/avrotize/proto2parser.py +0 -0
  62. {structurize-3.4.3 → structurize-3.5.1}/avrotize/proto3parser.py +0 -0
  63. {structurize-3.4.3 → structurize-3.5.1}/avrotize/prototoavro.py +0 -0
  64. {structurize-3.4.3 → structurize-3.5.1}/avrotize/sqltoavro.py +0 -0
  65. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretocddl.py +0 -0
  66. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretocpp.py +0 -0
  67. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretocsharp.py +0 -0
  68. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretocsv.py +0 -0
  69. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretodatapackage.py +0 -0
  70. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretodb.py +0 -0
  71. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretogo.py +0 -0
  72. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretographql.py +0 -0
  73. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretoiceberg.py +0 -0
  74. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretojs.py +0 -0
  75. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretojsons.py +0 -0
  76. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretokusto.py +0 -0
  77. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretomd.py +0 -0
  78. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretoproto.py +0 -0
  79. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretopython.py +0 -0
  80. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretorust.py +0 -0
  81. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretots.py +0 -0
  82. {structurize-3.4.3 → structurize-3.5.1}/avrotize/structuretoxsd.py +0 -0
  83. {structurize-3.4.3 → structurize-3.5.1}/avrotize/validate.py +0 -0
  84. {structurize-3.4.3 → structurize-3.5.1}/avrotize/xmltoschema.py +0 -0
  85. {structurize-3.4.3 → structurize-3.5.1}/avrotize/xsdtoavro.py +0 -0
  86. {structurize-3.4.3 → structurize-3.5.1}/build.ps1 +0 -0
  87. {structurize-3.4.3 → structurize-3.5.1}/build.sh +0 -0
  88. {structurize-3.4.3 → structurize-3.5.1}/setup.cfg +0 -0
  89. {structurize-3.4.3 → structurize-3.5.1}/structurize.egg-info/dependency_links.txt +0 -0
  90. {structurize-3.4.3 → structurize-3.5.1}/structurize.egg-info/entry_points.txt +0 -0
  91. {structurize-3.4.3 → structurize-3.5.1}/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.4.3
3
+ Version: 3.5.1
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
@@ -36,7 +36,7 @@ Requires-Dist: pydantic>=2.8.2; extra == "dev"
36
36
  Requires-Dist: avro>=1.12.0; extra == "dev"
37
37
  Requires-Dist: testcontainers>=4.7.2; extra == "dev"
38
38
  Requires-Dist: pymysql>=1.1.1; extra == "dev"
39
- Requires-Dist: psycopg2>=2.9.9; extra == "dev"
39
+ Requires-Dist: psycopg2-binary>=2.9.9; extra == "dev"
40
40
  Requires-Dist: pyodbc>=5.1.0; extra == "dev"
41
41
  Requires-Dist: pymongo>=4.8.0; extra == "dev"
42
42
  Requires-Dist: oracledb>=2.3.0; extra == "dev"
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '3.5.1'
22
+ __version_tuple__ = version_tuple = (3, 5, 1)
23
+
24
+ __commit_id__ = commit_id = 'g231469713'
@@ -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, JACKSON_VERSION, JDK_VERSION,
8
- JUNIT_VERSION, MAVEN_COMPILER_VERSION, MAVEN_SUREFIRE_VERSION)
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>{JACKSON_VERSION}</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,
@@ -165,6 +165,17 @@ class CddlToStructureConverter:
165
165
  # Counter for auto-generated type names
166
166
  self._auto_name_counter: int = 0
167
167
 
168
+ @staticmethod
169
+ def _wrap_ref(schema: Dict[str, Any]) -> Dict[str, Any]:
170
+ """Wrap a bare $ref in a type object per JSON Structure spec Section 3.4.1.
171
+
172
+ Bare {"$ref": "..."} is only valid inside a type attribute (e.g. union arrays).
173
+ In all other positions (items, properties, values), it must be {"type": {"$ref": "..."}}.
174
+ """
175
+ if '$ref' in schema and 'type' not in schema:
176
+ return {'type': {'$ref': schema['$ref']}}
177
+ return schema
178
+
168
179
  def _create_inline_definition(
169
180
  self, type_def: Dict[str, Any], base_name: str
170
181
  ) -> Dict[str, Any]:
@@ -900,6 +911,7 @@ class CddlToStructureConverter:
900
911
  keys_type = {'type': 'string'}
901
912
 
902
913
  values_type = member_type if member_type else {'type': 'any'}
914
+ values_type = self._wrap_ref(values_type)
903
915
 
904
916
  return {
905
917
  'keys': keys_type,
@@ -915,6 +927,7 @@ class CddlToStructureConverter:
915
927
  prop_schema = member_type.copy() if isinstance(member_type, dict) else member_type
916
928
  else:
917
929
  prop_schema = {'type': 'any'}
930
+ prop_schema = self._wrap_ref(prop_schema)
918
931
 
919
932
  # Handle occurrence indicators
920
933
  if occurrence_indicator:
@@ -1073,7 +1086,7 @@ class CddlToStructureConverter:
1073
1086
  tuple_order: List[str] = []
1074
1087
  for idx, item_type in enumerate(items_types):
1075
1088
  prop_name = f"_{idx}"
1076
- properties[prop_name] = item_type
1089
+ properties[prop_name] = self._wrap_ref(item_type)
1077
1090
  tuple_order.append(prop_name)
1078
1091
  return {
1079
1092
  'type': 'tuple',
@@ -1086,6 +1099,7 @@ class CddlToStructureConverter:
1086
1099
  items_type = items_types[0]
1087
1100
  # Extract inline compound types to definitions
1088
1101
  items_type = self._create_inline_definition(items_type, f"{context_name}_item")
1102
+ items_type = self._wrap_ref(items_type)
1089
1103
  else:
1090
1104
  # Multiple types that aren't a tuple - use union for items
1091
1105
  # Extract each inline compound type to definitions
@@ -1528,6 +1542,9 @@ class CddlToStructureConverter:
1528
1542
  else:
1529
1543
  base_type = {'type': 'any'}
1530
1544
 
1545
+ # Wrap bare $ref so constraints can be added alongside
1546
+ base_type = self._wrap_ref(base_type)
1547
+
1531
1548
  # Extract controller (constraint argument)
1532
1549
  if hasattr(operator_node, 'controller'):
1533
1550
  controller_value = self._extract_controller_value(operator_node.controller)
@@ -214,17 +214,17 @@ def _detect_discriminators(
214
214
  if len(values) < 2:
215
215
  continue
216
216
 
217
- # Discriminators must be identifier-like strings (type names, enum values)
218
- # Valid: "PlayerTracking", "ball_data", "goal-event", "Event.Type"
219
- # Invalid: "1", "2024/2025", "true", "2024-01-15T10:30:00Z", UUIDs
217
+ # Discriminators must be identifier-like strings (type names, enum values, codes)
218
+ # Valid: "PlayerTracking", "ball_data", "goal-event", "1", "用户", "true"
219
+ # Invalid: "2024/2025", "2024-01-15T10:30:00Z", UUIDs
220
220
  import re
221
221
  def is_valid_discriminator(s: str) -> bool:
222
- """Check if a string looks like a type name or identifier."""
222
+ """Check if a string looks like a type name, identifier, or short code."""
223
223
  if not s or not isinstance(s, str):
224
224
  return False
225
- # Must start with a letter, contain only alphanumeric, underscore, hyphen, dot
226
- # This matches typical identifiers: PascalCase, camelCase, snake_case, kebab-case
227
- return bool(re.match(r'^[A-Za-z][A-Za-z0-9_\-\.]*$', s))
225
+ # Accept word characters (Unicode letters, digits, underscore), hyphens, dots
226
+ # Reject slashes, colons, and other URI/date characters
227
+ return bool(re.match(r'^[\w][\w\-\.]*$', s, re.UNICODE))
228
228
 
229
229
  if not all(is_valid_discriminator(v) for v in values):
230
230
  continue
@@ -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', 'net90', 'Apache.Avro')
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', 'net90', 'Newtonsoft.Json')
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', 'net90', 'System.Text.Json')
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', 'net90', 'System.Memory.Data')
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', 'net90', 'protobuf-net')
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', 'net90', 'NUnit')
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', 'net90', 'NUnit3TestAdapter')
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', 'net90', 'Microsoft.NET.Test.Sdk')
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', 'net90', 'coverlet.collector')
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', 'net90', 'MessagePack')
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', 'net90', 'Dahomey.Cbor')
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'
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "devDependencies": {
11
11
  "@types/node": "^25.0.3",
12
- "typescript": "^5.7.2",
12
+ "typescript": "^6.0.2",
13
13
  "jest": "^30.2.0",
14
14
  "@types/jest": "^30.0.0"
15
15
  }
@@ -1,14 +1,27 @@
1
- """MCP server integration for Avrotize conversions."""
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
+ """
2
8
 
3
9
  from __future__ import annotations
4
10
 
5
11
  import argparse
12
+ import json as _json
6
13
  import os
14
+ import sys
7
15
  import tempfile
8
16
  from typing import Any, Dict, List
9
17
 
10
18
  from avrotize.avrotize import dynamic_import, load_commands
11
19
 
20
+ # ---------------------------------------------------------------------------
21
+ # MCP protocol version supported by this server
22
+ # ---------------------------------------------------------------------------
23
+ _MCP_PROTOCOL_VERSION = "2025-03-26"
24
+
12
25
 
13
26
  def _command_dest(arg: Dict[str, Any]) -> str:
14
27
  name = arg["name"]
@@ -227,10 +240,192 @@ def _execute_conversion(
227
240
  os.remove(temp_output_path)
228
241
 
229
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
+
230
414
  def run_mcp_server(transport: str = "stdio"):
231
415
  """Run avrotize as a local MCP server.
232
416
 
233
- Exposes conversion tools to MCP clients.
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.
234
429
  """
235
430
  try:
236
431
  from mcp.server.fastmcp import FastMCP
@@ -288,3 +483,16 @@ def run_mcp_server(transport: str = "stdio"):
288
483
  mcp.run(transport="stdio")
289
484
  except TypeError:
290
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()
@@ -1236,11 +1236,14 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
1236
1236
 
1237
1237
  # For inline unions with selector, we need $extends pointing to a base type
1238
1238
  # Compute common fields across all variants to create a proper base type
1239
+ disc_safe = avro_name(result.discriminator_field)
1239
1240
  all_variant_props = [c.get("properties", {}) for c in choices_map.values()]
1240
1241
  if all_variant_props:
1241
1242
  common_prop_names = set(all_variant_props[0].keys())
1242
1243
  for props in all_variant_props[1:]:
1243
1244
  common_prop_names &= set(props.keys())
1245
+ # Exclude discriminator from common props - each variant has a different default
1246
+ common_prop_names.discard(disc_safe)
1244
1247
 
1245
1248
  # Build base type with common properties
1246
1249
  base_name = avro_name(f"{type_name.rsplit('.', 1)[-1]}Base")
@@ -1277,7 +1280,6 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
1277
1280
  definitions[variant_name] = variant_record
1278
1281
  new_choices_map[variant_name] = {"type": {"$ref": f"#/definitions/{variant_name}"}}
1279
1282
 
1280
- disc_safe = avro_name(result.discriminator_field)
1281
1283
  return {
1282
1284
  "type": "choice",
1283
1285
  "name": avro_name(type_name.rsplit('.', 1)[-1]),
@@ -1477,7 +1479,8 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
1477
1479
  if all_items:
1478
1480
  item_types = self.consolidated_jstruct_type_list(type_name, all_items)
1479
1481
  if len(item_types) == 1:
1480
- return [{"type": "array", "items": item_types[0]}]
1482
+ items_schema = item_types[0] if isinstance(item_types[0], dict) else {"type": item_types[0]}
1483
+ return [{"type": "array", "items": items_schema}]
1481
1484
  else:
1482
1485
  # Build choice from multiple item types
1483
1486
  choices_map: Dict[str, Any] = {}
@@ -1539,7 +1542,8 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
1539
1542
  item_types.append(item2)
1540
1543
  if item_types:
1541
1544
  if len(item_types) == 1:
1542
- list_types.append({"type": "array", "items": item_types[0]})
1545
+ items_schema = item_types[0] if isinstance(item_types[0], dict) else {"type": item_types[0]}
1546
+ list_types.append({"type": "array", "items": items_schema})
1543
1547
  else:
1544
1548
  # Build choices map from item types
1545
1549
  choices_map: Dict[str, Any] = {}
@@ -1563,7 +1567,8 @@ class JsonStructureSchemaInferrer(SchemaInferrer):
1563
1567
  value_types.append(item3)
1564
1568
  if value_types:
1565
1569
  if len(value_types) == 1:
1566
- list_types.append({"type": "map", "values": value_types[0]})
1570
+ values_schema = value_types[0] if isinstance(value_types[0], dict) else {"type": value_types[0]}
1571
+ list_types.append({"type": "map", "values": values_schema})
1567
1572
  else:
1568
1573
  list_types.append({"type": "map", "values": {"type": "choice", "choices": value_types}})
1569
1574
 
@@ -4,7 +4,7 @@
4
4
  import json
5
5
  import os
6
6
  from typing import Dict, List, Tuple, Union, Set, Optional, Any
7
- from avrotize.constants import JACKSON_VERSION
7
+ from avrotize.constants import JACKSON_VERSION, JACKSON_ANNOTATIONS_VERSION
8
8
 
9
9
  from avrotize.common import pascal, camel, process_template
10
10
 
@@ -878,7 +878,8 @@ class StructureToJava:
878
878
  "structuretojava/pom.xml.jinja",
879
879
  groupid=groupid,
880
880
  artifactid=artifactid,
881
- jackson_version=JACKSON_VERSION
881
+ jackson_version=JACKSON_VERSION,
882
+ jackson_annotations_version=JACKSON_ANNOTATIONS_VERSION
882
883
  )
883
884
  with open(pom_path, 'w', encoding='utf-8') as file:
884
885
  file.write(pom_content)
@@ -46,7 +46,7 @@ dev = [
46
46
  "avro>=1.12.0",
47
47
  "testcontainers>=4.7.2",
48
48
  "pymysql>=1.1.1",
49
- "psycopg2>=2.9.9",
49
+ "psycopg2-binary>=2.9.9",
50
50
  "pyodbc>=5.1.0",
51
51
  "pymongo>=4.8.0",
52
52
  "oracledb>=2.3.0",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structurize
3
- Version: 3.4.3
3
+ Version: 3.5.1
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
@@ -36,7 +36,7 @@ Requires-Dist: pydantic>=2.8.2; extra == "dev"
36
36
  Requires-Dist: avro>=1.12.0; extra == "dev"
37
37
  Requires-Dist: testcontainers>=4.7.2; extra == "dev"
38
38
  Requires-Dist: pymysql>=1.1.1; extra == "dev"
39
- Requires-Dist: psycopg2>=2.9.9; extra == "dev"
39
+ Requires-Dist: psycopg2-binary>=2.9.9; extra == "dev"
40
40
  Requires-Dist: pyodbc>=5.1.0; extra == "dev"
41
41
  Requires-Dist: pymongo>=4.8.0; extra == "dev"
42
42
  Requires-Dist: oracledb>=2.3.0; extra == "dev"
@@ -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
@@ -26,7 +26,7 @@ pydantic>=2.8.2
26
26
  avro>=1.12.0
27
27
  testcontainers>=4.7.2
28
28
  pymysql>=1.1.1
29
- psycopg2>=2.9.9
29
+ psycopg2-binary>=2.9.9
30
30
  pyodbc>=5.1.0
31
31
  pymongo>=4.8.0
32
32
  oracledb>=2.3.0
@@ -1,34 +0,0 @@
1
- # file generated by setuptools-scm
2
- # don't change, don't track in version control
3
-
4
- __all__ = [
5
- "__version__",
6
- "__version_tuple__",
7
- "version",
8
- "version_tuple",
9
- "__commit_id__",
10
- "commit_id",
11
- ]
12
-
13
- TYPE_CHECKING = False
14
- if TYPE_CHECKING:
15
- from typing import Tuple
16
- from typing import Union
17
-
18
- VERSION_TUPLE = Tuple[Union[int, str], ...]
19
- COMMIT_ID = Union[str, None]
20
- else:
21
- VERSION_TUPLE = object
22
- COMMIT_ID = object
23
-
24
- version: str
25
- __version__: str
26
- __version_tuple__: VERSION_TUPLE
27
- version_tuple: VERSION_TUPLE
28
- commit_id: COMMIT_ID
29
- __commit_id__: COMMIT_ID
30
-
31
- __version__ = version = '3.4.3'
32
- __version_tuple__ = version_tuple = (3, 4, 3)
33
-
34
- __commit_id__ = commit_id = 'g1ee27c8b4'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes