6#include <QCoreApplication>
11#include <QJsonDocument>
12#include <QRegularExpression>
23bool MCPValidator::loadSchema(
const QString &schemaPath)
26 m_schemaLoaded =
false;
29 if (!schemaFile.open(QIODevice::ReadOnly)) {
33 QByteArray schemaData = schemaFile.readAll();
38 m_schema = json::parse(schemaData.toStdString());
41 m_validator = std::make_unique<json_validator>();
42 m_validator->set_root_schema(m_schema);
45 if (m_schema.contains(
"properties")) {
46 auto properties = m_schema[
"properties"];
47 if (properties.contains(
"commands") && properties[
"commands"].contains(
"properties")) {
48 m_commandSchemas = properties[
"commands"][
"properties"];
50 if (properties.contains(
"responses") && properties[
"responses"].contains(
"properties")) {
51 m_responseSchemas = properties[
"responses"][
"properties"];
55 m_schemaLoaded =
true;
58 }
catch (
const json::parse_error &) {
60 }
catch (
const json::exception &) {
69 }
catch (
const std::exception &) {
76 return m_schemaLoaded;
88 }
catch (
const std::exception &e) {
89 return ValidationResult(
false, QString(
"Malformed command: %1").arg(e.what()));
95 if (!m_schemaLoaded) {
100 QString commandType =
"unknown";
101 if (command.contains(
"method") && command[
"method"].is_string()) {
102 commandType = QString::fromStdString(command[
"method"].get<std::string>());
104 return ValidationResult(
false,
"Missing or invalid 'method' field",
"", commandType);
109 if (commandSchema.is_null()) {
111 QString(
"No schema found for command type: %1").arg(commandType),
116 return validateAgainstSchema(command, commandSchema, commandType,
"command:" + commandType.toStdString());
123 }
catch (
const std::exception &e) {
124 return ValidationResult(
false, QString(
"Malformed response: %1").arg(e.what()));
130 if (!m_schemaLoaded) {
135 json baseResponseSchema;
136 if (m_schema.contains(
"definitions") && m_schema[
"definitions"].contains(
"CommandResponse")) {
137 baseResponseSchema = m_schema[
"definitions"][
"CommandResponse"];
139 return ValidationResult(
false,
"CommandResponse schema not found in definitions");
142 ValidationResult baseResult = validateAgainstSchema(response, baseResponseSchema, expectedCommand,
"response:base");
145 QString(
"Base response validation failed: %1").arg(baseResult.
errorMessage),
146 "base_response", expectedCommand);
153 if (!expectedCommand.isEmpty()) {
155 if (!responseSchema.is_null()) {
156 ValidationResult specificResult = validateAgainstSchema(response, responseSchema, expectedCommand,
"response:" + expectedCommand.toStdString());
159 QString(
"Specific response validation failed: %1").arg(specificResult.
errorMessage),
160 expectedCommand +
"_response", expectedCommand);
162 return ValidationResult(
true,
"", expectedCommand +
"_response", expectedCommand);
169ValidationResult MCPValidator::validateAgainstSchema(
const json &data,
const json &schema,
const QString &commandType,
const std::string &cacheKey)
174 json_validator *validator =
nullptr;
178 const auto it = m_validatorCache.find(cacheKey);
179 if (it != m_validatorCache.end()) {
180 validator = it->second.get();
183 json fullSchema = schema;
192 if (m_schema.contains(
"definitions")) {
193 fullSchema[
"definitions"] = m_schema[
"definitions"];
194 fullSchema[
"$schema"] = m_schema[
"$schema"];
197 auto compiled = std::make_unique<json_validator>();
198 compiled->set_root_schema(fullSchema);
199 validator = m_validatorCache.emplace(cacheKey, std::move(compiled)).first->second.get();
203 validator->validate(data);
206 return ValidationResult(
true,
"",
"", commandType);
208 }
catch (
const std::exception &e) {
210 QString errorMsg = QString::fromStdString(e.what());
211 QString errorPath = extractErrorPath(errorMsg);
213 return ValidationResult(
false, errorMsg,
"", commandType, errorPath);
219 if (!m_commandSchemas.is_null() && m_commandSchemas.contains(commandName.toStdString())) {
220 json schema = m_commandSchemas[commandName.toStdString()];
223 if (schema.contains(
"$ref") && schema[
"$ref"].is_string()) {
224 QString refPath = QString::fromStdString(schema[
"$ref"].get<std::string>());
225 if (refPath.startsWith(
"#/definitions/")) {
226 QString defName = refPath.mid(14);
227 if (m_schema.contains(
"definitions") && m_schema[
"definitions"].contains(defName.toStdString())) {
228 return m_schema[
"definitions"][defName.toStdString()];
241 if (!m_responseSchemas.is_null() && m_responseSchemas.contains(commandName.toStdString())) {
242 json schema = m_responseSchemas[commandName.toStdString()];
245 if (schema.contains(
"$ref") && schema[
"$ref"].is_string()) {
246 QString refPath = QString::fromStdString(schema[
"$ref"].get<std::string>());
247 if (refPath.startsWith(
"#/definitions/")) {
248 QString defName = refPath.mid(14);
249 if (m_schema.contains(
"definitions") && m_schema[
"definitions"].contains(defName.toStdString())) {
250 return m_schema[
"definitions"][defName.toStdString()];
261QString MCPValidator::extractErrorPath(
const QString &errorMsg)
268 static const QRegularExpression pathRegex(R
"(^At\s(\S*)\s+of\s)");
269 QRegularExpressionMatch match = pathRegex.match(errorMsg);
270 if (match.hasMatch()) {
271 return match.captured(1);
286 QJsonDocument doc(qjson);
287 QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
289 return json::parse(jsonData.toStdString());
290 }
catch (
const json::parse_error &e) {
291 throw std::runtime_error(std::string(
"qjsonToNlohmann: JSON parse error: ") + e.what());
297 std::string jsonString = nlohmannJson.dump();
298 QJsonParseError parseError;
299 QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(jsonString), &parseError);
300 if (parseError.error != QJsonParseError::NoError || doc.isNull()) {
301 qWarning() <<
"nlohmannToQJson: failed to parse JSON:" << parseError.errorString();
static json qjsonToNlohmann(const QJsonObject &qjson)
ValidationResult validateCommand(const QJsonObject &command)
json findResponseSchema(const QString &commandName) const
MCPValidator(const QString &schemaPath)
static QJsonObject nlohmannToQJson(const json &nlohmannJson)
QString schemaPath() const
bool isSchemaLoaded() const
ValidationResult validateResponse(const QJsonObject &response, const QString &expectedCommand=QString())
json findCommandSchema(const QString &commandName) const
Result of JSON schema validation.