blob: 5a93ca65efa104d757348f0cb80288ca4f7c9611 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""Standalone stdio fixture for bounded JSON Schema validation."""
from __future__ import annotations
import asyncio
import json
import logging
from typing import Any
from mcp.server.stdio import stdio_server
from mcp.types import GetPromptResult, Prompt, Resource, Tool
from doris_mcp_server import __version__
from doris_mcp_server.protocol import create_doris_mcp_server
from doris_mcp_server.schema_validation import JSON_SCHEMA_2020_12
SCHEMA_GUARD_INPUT = {
"$schema": JSON_SCHEMA_2020_12,
"type": "object",
"$defs": {
"selector": {
"oneOf": [
{
"type": "object",
"properties": {"id": {"type": "integer"}},
"required": ["id"],
"additionalProperties": False,
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 1,
}
},
"required": ["name"],
"additionalProperties": False,
},
]
}
},
"properties": {
"selector": {"$ref": "#/$defs/selector"},
},
"required": ["selector"],
"additionalProperties": False,
}
BOOLEAN_OUTPUT = {
"$schema": JSON_SCHEMA_2020_12,
"type": "object",
"properties": {"accepted": {"type": "boolean"}},
"required": ["accepted"],
"additionalProperties": False,
}
class EmptyResourcesManager:
async def list_resources(self) -> list[Resource]:
return []
async def read_resource(self, uri: str) -> str:
return json.dumps({"uri": uri})
class SchemaToolsManager:
async def list_tools(self) -> list[Tool]:
return [
Tool(
name="schema_guard",
description="Exercise full JSON Schema 2020-12 input validation.",
input_schema=SCHEMA_GUARD_INPUT,
output_schema=BOOLEAN_OUTPUT,
),
Tool(
name="bad_output",
description="Exercise server output-schema enforcement.",
input_schema={
"type": "object",
"additionalProperties": False,
},
output_schema=BOOLEAN_OUTPUT,
),
Tool(
name="array_output",
description="Exercise non-object 2026-07-28 structured content.",
input_schema={
"type": "object",
"additionalProperties": False,
},
output_schema={
"$schema": JSON_SCHEMA_2020_12,
"type": "array",
"items": {"type": "integer"},
},
),
Tool(
name="echo",
description="Verify process recovery after schema errors.",
input_schema={
"type": "object",
"properties": {"value": {"type": "string"}},
"required": ["value"],
"additionalProperties": False,
},
),
]
async def call_tool(self, name: str, arguments: dict[str, Any]) -> str:
if name == "schema_guard":
return json.dumps({"accepted": True})
if name == "bad_output":
return json.dumps({"accepted": "not-a-boolean"})
if name == "array_output":
return json.dumps([1, 2, 3])
return json.dumps({"value": arguments["value"]})
class EmptyPromptsManager:
async def list_prompts(self) -> list[Prompt]:
return []
async def get_prompt(
self,
name: str,
arguments: dict[str, Any],
) -> GetPromptResult:
del name, arguments
raise AssertionError("schema fixture has no prompts")
def create_schema_validation_server():
return create_doris_mcp_server(
resources_manager=EmptyResourcesManager(),
tools_manager=SchemaToolsManager(),
prompts_manager=EmptyPromptsManager(),
name="doris-mcp-schema-validation-test",
version=__version__,
logger=logging.getLogger(__name__),
)
async def main() -> None:
server = create_schema_validation_server()
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
asyncio.run(main())