sdcvalidator 4.2.0__tar.gz → 4.2.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.
- {sdcvalidator-4.2.0/src/sdcvalidator.egg-info → sdcvalidator-4.2.1}/PKG-INFO +1 -1
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/pyproject.toml +2 -1
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator/__init__.py +1 -1
- sdcvalidator-4.2.1/src/sdcvalidator/mcp_server.py +295 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1/src/sdcvalidator.egg-info}/PKG-INFO +1 -1
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator.egg-info/SOURCES.txt +2 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator.egg-info/entry_points.txt +1 -0
- sdcvalidator-4.2.1/tests/test_mcp_server.py +139 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/LICENSE +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/README.md +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/setup.cfg +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator/cli.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator/constants.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator/converters.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator/error_classifier.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator/schema_checker.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator/validator.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator.egg-info/dependency_links.txt +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator.egg-info/requires.txt +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/src/sdcvalidator.egg-info/top_level.txt +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/tests/test_converters.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/tests/test_error_classifier.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/tests/test_schema_checker.py +0 -0
- {sdcvalidator-4.2.0 → sdcvalidator-4.2.1}/tests/test_validator.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sdcvalidator"
|
|
7
|
-
version = "4.2.
|
|
7
|
+
version = "4.2.1"
|
|
8
8
|
description = "SDC4 structural validator — thin wrapper over xmlschema with error classification"
|
|
9
9
|
license = "Apache-2.0"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -30,6 +30,7 @@ dev = ["pytest>=7.0", "pytest-cov"]
|
|
|
30
30
|
sdcvalidate = "sdcvalidator.cli:validate_main"
|
|
31
31
|
sdcvalidator-xml2json = "sdcvalidator.cli:xml2json_main"
|
|
32
32
|
sdcvalidator-json2xml = "sdcvalidator.cli:json2xml_main"
|
|
33
|
+
sdcvalidator-mcp = "sdcvalidator.mcp_server:main"
|
|
33
34
|
|
|
34
35
|
[tool.setuptools.packages.find]
|
|
35
36
|
where = ["src"]
|
|
@@ -13,7 +13,7 @@ sdcvalidator — SDC4 structural validator.
|
|
|
13
13
|
Thin wrapper over xmlschema with two-tier error classification.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
__version__ = "4.1
|
|
16
|
+
__version__ = "4.2.1"
|
|
17
17
|
|
|
18
18
|
from .validator import SDC4Validator, ValidationResult
|
|
19
19
|
from .error_classifier import ErrorClassifier
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Semantic Data Charter Foundation
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
"""
|
|
11
|
+
MCP server exposing sdcvalidator tools to any agent framework.
|
|
12
|
+
|
|
13
|
+
Implements the MCP (Model Context Protocol) as a raw JSON-RPC 2.0
|
|
14
|
+
stdio server. No external MCP SDK dependency.
|
|
15
|
+
|
|
16
|
+
Start the server::
|
|
17
|
+
|
|
18
|
+
sdcvalidator serve --mcp
|
|
19
|
+
|
|
20
|
+
MCP Tools exposed:
|
|
21
|
+
- validate_instance: validate an XML instance against its XSD schema
|
|
22
|
+
- validate_and_report: detailed validation report with error classification
|
|
23
|
+
- check_schema_compliance: verify schema follows SDC4 principles
|
|
24
|
+
|
|
25
|
+
Protocol: JSON-RPC 2.0 over stdio (one JSON object per line).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import sys
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from sdcvalidator.validator import SDC4Validator
|
|
33
|
+
from sdcvalidator.schema_checker import validate_sdc4_schema_compliance
|
|
34
|
+
|
|
35
|
+
# Protocol constants
|
|
36
|
+
JSONRPC_VERSION = "2.0"
|
|
37
|
+
MCP_PROTOCOL_VERSION = "2024-11-05"
|
|
38
|
+
SERVER_NAME = "sdcvalidator"
|
|
39
|
+
SERVER_VERSION = "4.2.1"
|
|
40
|
+
|
|
41
|
+
# Validator cache: schema_path -> SDC4Validator
|
|
42
|
+
_validators: dict[str, SDC4Validator] = {}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _get_validator(schema_path: str, check_compliance: bool = True) -> SDC4Validator:
|
|
46
|
+
"""Get or create a validator for the given schema."""
|
|
47
|
+
key = f"{schema_path}:{check_compliance}"
|
|
48
|
+
if key not in _validators:
|
|
49
|
+
_validators[key] = SDC4Validator(
|
|
50
|
+
schema_path,
|
|
51
|
+
check_sdc4_compliance=check_compliance,
|
|
52
|
+
)
|
|
53
|
+
return _validators[key]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# --- Tool definitions ---
|
|
57
|
+
|
|
58
|
+
TOOLS = [
|
|
59
|
+
{
|
|
60
|
+
"name": "validate_instance",
|
|
61
|
+
"description": (
|
|
62
|
+
"Validate an XML instance against its SDC4 XSD schema. "
|
|
63
|
+
"Returns pass/fail with error count and classified errors."
|
|
64
|
+
),
|
|
65
|
+
"inputSchema": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"schema_path": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "Path to the SDC4 data model XSD file.",
|
|
71
|
+
},
|
|
72
|
+
"instance_path": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "Path to the XML instance file to validate.",
|
|
75
|
+
},
|
|
76
|
+
"check_compliance": {
|
|
77
|
+
"type": "boolean",
|
|
78
|
+
"description": "Check schema follows SDC4 principles (no xsd:extension). Default true.",
|
|
79
|
+
"default": True,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
"required": ["schema_path", "instance_path"],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "validate_and_report",
|
|
87
|
+
"description": (
|
|
88
|
+
"Validate an XML instance and return a detailed report with "
|
|
89
|
+
"two-tier error classification (structural vs semantic)."
|
|
90
|
+
),
|
|
91
|
+
"inputSchema": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"properties": {
|
|
94
|
+
"schema_path": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "Path to the SDC4 data model XSD file.",
|
|
97
|
+
},
|
|
98
|
+
"instance_path": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Path to the XML instance file to validate.",
|
|
101
|
+
},
|
|
102
|
+
"check_compliance": {
|
|
103
|
+
"type": "boolean",
|
|
104
|
+
"description": "Check schema follows SDC4 principles. Default true.",
|
|
105
|
+
"default": True,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
"required": ["schema_path", "instance_path"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "check_schema_compliance",
|
|
113
|
+
"description": (
|
|
114
|
+
"Check if an XSD schema follows SDC4 principles "
|
|
115
|
+
"(no xsd:extension, restriction only). Does not validate instances."
|
|
116
|
+
),
|
|
117
|
+
"inputSchema": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"schema_path": {
|
|
121
|
+
"type": "string",
|
|
122
|
+
"description": "Path to the XSD schema file to check.",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
"required": ["schema_path"],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# --- Tool handlers ---
|
|
132
|
+
|
|
133
|
+
def _handle_validate_instance(args: dict[str, Any]) -> Any:
|
|
134
|
+
schema_path = args["schema_path"]
|
|
135
|
+
instance_path = args["instance_path"]
|
|
136
|
+
check_compliance = args.get("check_compliance", True)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
validator = _get_validator(schema_path, check_compliance)
|
|
140
|
+
except Exception as exc:
|
|
141
|
+
return {
|
|
142
|
+
"valid": False,
|
|
143
|
+
"error": f"Schema error: {exc}",
|
|
144
|
+
"error_count": 0,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
result = validator.validate(instance_path)
|
|
148
|
+
return {
|
|
149
|
+
"valid": result.is_valid,
|
|
150
|
+
"error_count": result.error_count,
|
|
151
|
+
"structural_error_count": len(result.structural_errors),
|
|
152
|
+
"semantic_error_count": len(result.semantic_errors),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _handle_validate_and_report(args: dict[str, Any]) -> Any:
|
|
157
|
+
schema_path = args["schema_path"]
|
|
158
|
+
instance_path = args["instance_path"]
|
|
159
|
+
check_compliance = args.get("check_compliance", True)
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
validator = _get_validator(schema_path, check_compliance)
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
return {
|
|
165
|
+
"valid": False,
|
|
166
|
+
"error": f"Schema error: {exc}",
|
|
167
|
+
"error_count": 0,
|
|
168
|
+
"structural_errors": [],
|
|
169
|
+
"semantic_errors": [],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return validator.validate_and_report(instance_path)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _handle_check_schema_compliance(args: dict[str, Any]) -> Any:
|
|
176
|
+
schema_path = args["schema_path"]
|
|
177
|
+
is_valid, errors = validate_sdc4_schema_compliance(schema_path)
|
|
178
|
+
return {
|
|
179
|
+
"compliant": is_valid,
|
|
180
|
+
"errors": errors,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
TOOL_HANDLERS = {
|
|
185
|
+
"validate_instance": _handle_validate_instance,
|
|
186
|
+
"validate_and_report": _handle_validate_and_report,
|
|
187
|
+
"check_schema_compliance": _handle_check_schema_compliance,
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# --- JSON-RPC 2.0 stdio server ---
|
|
192
|
+
|
|
193
|
+
def _jsonrpc_response(id: Any, result: Any) -> dict:
|
|
194
|
+
return {"jsonrpc": JSONRPC_VERSION, "id": id, "result": result}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _jsonrpc_error(id: Any, code: int, message: str, data: Any = None) -> dict:
|
|
198
|
+
error = {"code": code, "message": message}
|
|
199
|
+
if data is not None:
|
|
200
|
+
error["data"] = data
|
|
201
|
+
return {"jsonrpc": JSONRPC_VERSION, "id": id, "error": error}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _handle_request(request: dict) -> dict | None:
|
|
205
|
+
method = request.get("method", "")
|
|
206
|
+
params = request.get("params", {})
|
|
207
|
+
req_id = request.get("id")
|
|
208
|
+
|
|
209
|
+
if method == "initialize":
|
|
210
|
+
result = {
|
|
211
|
+
"protocolVersion": MCP_PROTOCOL_VERSION,
|
|
212
|
+
"capabilities": {"tools": {"listChanged": False}},
|
|
213
|
+
"serverInfo": {"name": SERVER_NAME, "version": SERVER_VERSION},
|
|
214
|
+
"instructions": (
|
|
215
|
+
"SDC4 structural validator. Validates XML instances against "
|
|
216
|
+
"XSD schemas with two-tier error classification (structural vs semantic). "
|
|
217
|
+
"Independent from sdcgovernance - agents call each library separately."
|
|
218
|
+
),
|
|
219
|
+
}
|
|
220
|
+
return _jsonrpc_response(req_id, result)
|
|
221
|
+
|
|
222
|
+
elif method == "notifications/initialized":
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
elif method == "tools/list":
|
|
226
|
+
return _jsonrpc_response(req_id, {"tools": TOOLS})
|
|
227
|
+
|
|
228
|
+
elif method == "tools/call":
|
|
229
|
+
tool_name = params.get("name", "")
|
|
230
|
+
tool_args = params.get("arguments", {})
|
|
231
|
+
|
|
232
|
+
handler = TOOL_HANDLERS.get(tool_name)
|
|
233
|
+
if handler is None:
|
|
234
|
+
return _jsonrpc_error(req_id, -32601, f"Unknown tool: {tool_name}")
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
result = handler(tool_args)
|
|
238
|
+
return _jsonrpc_response(req_id, {
|
|
239
|
+
"content": [{"type": "text", "text": json.dumps(result, default=str)}],
|
|
240
|
+
})
|
|
241
|
+
except Exception as exc:
|
|
242
|
+
return _jsonrpc_error(req_id, -32000, f"Tool execution error: {exc}")
|
|
243
|
+
|
|
244
|
+
elif method == "ping":
|
|
245
|
+
return _jsonrpc_response(req_id, {})
|
|
246
|
+
|
|
247
|
+
else:
|
|
248
|
+
if req_id is not None:
|
|
249
|
+
return _jsonrpc_error(req_id, -32601, f"Method not found: {method}")
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def run_stdio() -> None:
|
|
254
|
+
"""Run the MCP server on stdio."""
|
|
255
|
+
for line in sys.stdin:
|
|
256
|
+
line = line.strip()
|
|
257
|
+
if not line:
|
|
258
|
+
continue
|
|
259
|
+
try:
|
|
260
|
+
request = json.loads(line)
|
|
261
|
+
except json.JSONDecodeError as exc:
|
|
262
|
+
response = _jsonrpc_error(None, -32700, f"Parse error: {exc}")
|
|
263
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
264
|
+
sys.stdout.flush()
|
|
265
|
+
continue
|
|
266
|
+
|
|
267
|
+
response = _handle_request(request)
|
|
268
|
+
if response is not None:
|
|
269
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
270
|
+
sys.stdout.flush()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def main() -> None:
|
|
274
|
+
"""Entry point for the sdcvalidator MCP server."""
|
|
275
|
+
import argparse
|
|
276
|
+
|
|
277
|
+
parser = argparse.ArgumentParser(
|
|
278
|
+
prog="sdcvalidator-mcp",
|
|
279
|
+
description="SDC4 structural validator - MCP server",
|
|
280
|
+
)
|
|
281
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
282
|
+
|
|
283
|
+
serve_parser = subparsers.add_parser("serve", help="Start the validation server")
|
|
284
|
+
serve_parser.add_argument("--mcp", action="store_true", help="Run as MCP stdio server")
|
|
285
|
+
|
|
286
|
+
args = parser.parse_args()
|
|
287
|
+
|
|
288
|
+
if args.command == "serve" and args.mcp:
|
|
289
|
+
run_stdio()
|
|
290
|
+
else:
|
|
291
|
+
parser.print_help()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
if __name__ == "__main__":
|
|
295
|
+
main()
|
|
@@ -6,6 +6,7 @@ src/sdcvalidator/cli.py
|
|
|
6
6
|
src/sdcvalidator/constants.py
|
|
7
7
|
src/sdcvalidator/converters.py
|
|
8
8
|
src/sdcvalidator/error_classifier.py
|
|
9
|
+
src/sdcvalidator/mcp_server.py
|
|
9
10
|
src/sdcvalidator/schema_checker.py
|
|
10
11
|
src/sdcvalidator/validator.py
|
|
11
12
|
src/sdcvalidator.egg-info/PKG-INFO
|
|
@@ -16,5 +17,6 @@ src/sdcvalidator.egg-info/requires.txt
|
|
|
16
17
|
src/sdcvalidator.egg-info/top_level.txt
|
|
17
18
|
tests/test_converters.py
|
|
18
19
|
tests/test_error_classifier.py
|
|
20
|
+
tests/test_mcp_server.py
|
|
19
21
|
tests/test_schema_checker.py
|
|
20
22
|
tests/test_validator.py
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 Semantic Data Charter Foundation
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
"""
|
|
6
|
+
Tests for MCP server - JSON-RPC 2.0 protocol and tool definitions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import pytest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from sdcvalidator.mcp_server import _handle_request, TOOLS, TOOL_HANDLERS
|
|
13
|
+
|
|
14
|
+
TEST_DATA_DIR = Path(__file__).parent / "test_data"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def call_tool(name: str, arguments: dict) -> dict:
|
|
18
|
+
"""Helper: call an MCP tool via JSON-RPC and return parsed result."""
|
|
19
|
+
request = {
|
|
20
|
+
"jsonrpc": "2.0",
|
|
21
|
+
"id": 1,
|
|
22
|
+
"method": "tools/call",
|
|
23
|
+
"params": {"name": name, "arguments": arguments},
|
|
24
|
+
}
|
|
25
|
+
response = _handle_request(request)
|
|
26
|
+
assert "result" in response, f"Expected result, got: {response}"
|
|
27
|
+
content = response["result"]["content"]
|
|
28
|
+
assert len(content) == 1
|
|
29
|
+
assert content[0]["type"] == "text"
|
|
30
|
+
return json.loads(content[0]["text"])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestMcpProtocol:
|
|
34
|
+
"""JSON-RPC 2.0 MCP protocol handling."""
|
|
35
|
+
|
|
36
|
+
def test_initialize(self):
|
|
37
|
+
request = {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}
|
|
38
|
+
response = _handle_request(request)
|
|
39
|
+
assert response["id"] == 1
|
|
40
|
+
result = response["result"]
|
|
41
|
+
assert result["serverInfo"]["name"] == "sdcvalidator"
|
|
42
|
+
assert "tools" in result["capabilities"]
|
|
43
|
+
|
|
44
|
+
def test_initialized_notification(self):
|
|
45
|
+
request = {"jsonrpc": "2.0", "method": "notifications/initialized"}
|
|
46
|
+
response = _handle_request(request)
|
|
47
|
+
assert response is None
|
|
48
|
+
|
|
49
|
+
def test_tools_list(self):
|
|
50
|
+
request = {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
|
|
51
|
+
response = _handle_request(request)
|
|
52
|
+
tools = response["result"]["tools"]
|
|
53
|
+
assert len(tools) == 3
|
|
54
|
+
names = {t["name"] for t in tools}
|
|
55
|
+
assert names == {"validate_instance", "validate_and_report", "check_schema_compliance"}
|
|
56
|
+
|
|
57
|
+
def test_tools_have_input_schemas(self):
|
|
58
|
+
request = {"jsonrpc": "2.0", "id": 3, "method": "tools/list", "params": {}}
|
|
59
|
+
response = _handle_request(request)
|
|
60
|
+
for tool in response["result"]["tools"]:
|
|
61
|
+
assert "inputSchema" in tool
|
|
62
|
+
assert tool["inputSchema"]["type"] == "object"
|
|
63
|
+
|
|
64
|
+
def test_ping(self):
|
|
65
|
+
request = {"jsonrpc": "2.0", "id": 4, "method": "ping", "params": {}}
|
|
66
|
+
response = _handle_request(request)
|
|
67
|
+
assert response["id"] == 4
|
|
68
|
+
assert response["result"] == {}
|
|
69
|
+
|
|
70
|
+
def test_unknown_method(self):
|
|
71
|
+
request = {"jsonrpc": "2.0", "id": 5, "method": "nonexistent", "params": {}}
|
|
72
|
+
response = _handle_request(request)
|
|
73
|
+
assert "error" in response
|
|
74
|
+
assert response["error"]["code"] == -32601
|
|
75
|
+
|
|
76
|
+
def test_unknown_tool(self):
|
|
77
|
+
request = {
|
|
78
|
+
"jsonrpc": "2.0", "id": 6,
|
|
79
|
+
"method": "tools/call",
|
|
80
|
+
"params": {"name": "nonexistent_tool", "arguments": {}},
|
|
81
|
+
}
|
|
82
|
+
response = _handle_request(request)
|
|
83
|
+
assert "error" in response
|
|
84
|
+
assert "Unknown tool" in response["error"]["message"]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TestCheckSchemaCompliance:
|
|
88
|
+
"""check_schema_compliance MCP tool."""
|
|
89
|
+
|
|
90
|
+
def test_valid_schema(self):
|
|
91
|
+
data = call_tool("check_schema_compliance", {
|
|
92
|
+
"schema_path": str(TEST_DATA_DIR / "valid_sdc4_schema.xsd"),
|
|
93
|
+
})
|
|
94
|
+
assert data["compliant"] is True
|
|
95
|
+
assert data["errors"] == []
|
|
96
|
+
|
|
97
|
+
def test_invalid_schema_with_extension(self):
|
|
98
|
+
data = call_tool("check_schema_compliance", {
|
|
99
|
+
"schema_path": str(TEST_DATA_DIR / "invalid_sdc4_schema_with_extension.xsd"),
|
|
100
|
+
})
|
|
101
|
+
assert data["compliant"] is False
|
|
102
|
+
assert len(data["errors"]) > 0
|
|
103
|
+
assert any("extension" in e.lower() for e in data["errors"])
|
|
104
|
+
|
|
105
|
+
def test_non_sdc4_schema_passes(self):
|
|
106
|
+
data = call_tool("check_schema_compliance", {
|
|
107
|
+
"schema_path": str(TEST_DATA_DIR / "non_sdc4_schema_with_extension.xsd"),
|
|
108
|
+
})
|
|
109
|
+
assert data["compliant"] is True
|
|
110
|
+
|
|
111
|
+
def test_nonexistent_file(self):
|
|
112
|
+
data = call_tool("check_schema_compliance", {
|
|
113
|
+
"schema_path": "/nonexistent/path/schema.xsd",
|
|
114
|
+
})
|
|
115
|
+
assert data["compliant"] is False
|
|
116
|
+
assert any("not found" in e.lower() for e in data["errors"])
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class TestConsistentSerialization:
|
|
120
|
+
"""Verify all tools return consistent JSON-RPC format."""
|
|
121
|
+
|
|
122
|
+
def test_all_tools_return_single_text_content(self):
|
|
123
|
+
"""Every tool returns exactly one content block with type=text."""
|
|
124
|
+
test_calls = [
|
|
125
|
+
("check_schema_compliance", {"schema_path": str(TEST_DATA_DIR / "valid_sdc4_schema.xsd")}),
|
|
126
|
+
]
|
|
127
|
+
for tool_name, args in test_calls:
|
|
128
|
+
request = {
|
|
129
|
+
"jsonrpc": "2.0", "id": 1,
|
|
130
|
+
"method": "tools/call",
|
|
131
|
+
"params": {"name": tool_name, "arguments": args},
|
|
132
|
+
}
|
|
133
|
+
response = _handle_request(request)
|
|
134
|
+
assert "result" in response, f"{tool_name} returned error: {response}"
|
|
135
|
+
content = response["result"]["content"]
|
|
136
|
+
assert len(content) == 1, f"{tool_name} returned {len(content)} content blocks"
|
|
137
|
+
assert content[0]["type"] == "text"
|
|
138
|
+
parsed = json.loads(content[0]["text"])
|
|
139
|
+
assert parsed is not None
|
|
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
|