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 &) {
62 }
catch (
const std::exception &) {
69 return m_schemaLoaded;
81 }
catch (
const std::exception &e) {
82 return ValidationResult(
false, QString(
"Malformed command: %1").arg(e.what()));
88 if (!m_schemaLoaded) {
93 QString commandType =
"unknown";
94 if (command.contains(
"method") && command[
"method"].is_string()) {
95 commandType = QString::fromStdString(command[
"method"].get<std::string>());
97 return ValidationResult(
false,
"Missing or invalid 'method' field",
"", commandType);
102 if (commandSchema.is_null()) {
104 QString(
"No schema found for command type: %1").arg(commandType),
109 return validateAgainstSchema(command, commandSchema, commandType,
"command:" + commandType.toStdString());
116 }
catch (
const std::exception &e) {
117 return ValidationResult(
false, QString(
"Malformed response: %1").arg(e.what()));
123 if (!m_schemaLoaded) {
128 json baseResponseSchema;
129 if (m_schema.contains(
"definitions") && m_schema[
"definitions"].contains(
"CommandResponse")) {
130 baseResponseSchema = m_schema[
"definitions"][
"CommandResponse"];
132 return ValidationResult(
false,
"CommandResponse schema not found in definitions");
135 ValidationResult baseResult = validateAgainstSchema(response, baseResponseSchema, expectedCommand,
"response:base");
138 QString(
"Base response validation failed: %1").arg(baseResult.
errorMessage),
139 "base_response", expectedCommand);
143 if (!expectedCommand.isEmpty()) {
145 if (!responseSchema.is_null()) {
146 ValidationResult specificResult = validateAgainstSchema(response, responseSchema, expectedCommand,
"response:" + expectedCommand.toStdString());
149 QString(
"Specific response validation failed: %1").arg(specificResult.
errorMessage),
150 expectedCommand +
"_response", expectedCommand);
152 return ValidationResult(
true,
"", expectedCommand +
"_response", expectedCommand);
159ValidationResult MCPValidator::validateAgainstSchema(
const json &data,
const json &schema,
const QString &commandType,
const std::string &cacheKey)
164 json_validator *validator =
nullptr;
168 const auto it = m_validatorCache.find(cacheKey);
169 if (it != m_validatorCache.end()) {
170 validator = it->second.get();
173 json fullSchema = schema;
177 if (schema.contains(
"properties") && m_schema.contains(
"definitions")) {
178 fullSchema[
"definitions"] = m_schema[
"definitions"];
179 fullSchema[
"$schema"] = m_schema[
"$schema"];
182 auto compiled = std::make_unique<json_validator>();
183 compiled->set_root_schema(fullSchema);
184 validator = m_validatorCache.emplace(cacheKey, std::move(compiled)).first->second.get();
188 validator->validate(data);
191 return ValidationResult(
true,
"",
"", commandType);
193 }
catch (
const std::exception &e) {
195 QString errorMsg = QString::fromStdString(e.what());
196 QString errorPath = extractErrorPath(errorMsg);
198 return ValidationResult(
false, errorMsg,
"", commandType, errorPath);
204 if (!m_commandSchemas.is_null() && m_commandSchemas.contains(commandName.toStdString())) {
205 json schema = m_commandSchemas[commandName.toStdString()];
208 if (schema.contains(
"$ref") && schema[
"$ref"].is_string()) {
209 QString refPath = QString::fromStdString(schema[
"$ref"].get<std::string>());
210 if (refPath.startsWith(
"#/definitions/")) {
211 QString defName = refPath.mid(14);
212 if (m_schema.contains(
"definitions") && m_schema[
"definitions"].contains(defName.toStdString())) {
213 return m_schema[
"definitions"][defName.toStdString()];
226 if (!m_responseSchemas.is_null() && m_responseSchemas.contains(commandName.toStdString())) {
227 json schema = m_responseSchemas[commandName.toStdString()];
230 if (schema.contains(
"$ref") && schema[
"$ref"].is_string()) {
231 QString refPath = QString::fromStdString(schema[
"$ref"].get<std::string>());
232 if (refPath.startsWith(
"#/definitions/")) {
233 QString defName = refPath.mid(14);
234 if (m_schema.contains(
"definitions") && m_schema[
"definitions"].contains(defName.toStdString())) {
235 return m_schema[
"definitions"][defName.toStdString()];
246QString MCPValidator::extractErrorPath(
const QString &errorMsg)
250 static const QRegularExpression pathRegex(R
"(at\s+'([^']+)')");
251 QRegularExpressionMatch match = pathRegex.match(errorMsg);
252 if (match.hasMatch()) {
253 return match.captured(1);
261 QJsonDocument doc(qjson);
262 QByteArray jsonData = doc.toJson(QJsonDocument::Compact);
264 return json::parse(jsonData.toStdString());
265 }
catch (
const json::parse_error &e) {
266 throw std::runtime_error(std::string(
"qjsonToNlohmann: JSON parse error: ") + e.what());
272 std::string jsonString = nlohmannJson.dump();
273 QJsonParseError parseError;
274 QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromStdString(jsonString), &parseError);
275 if (parseError.error != QJsonParseError::NoError || doc.isNull()) {
276 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.