From f71b2ef510d3c1b832df243538404590690d17c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CE=A7=CE=B3=CF=86=CF=84=20Kompanion?= Date: Thu, 16 Oct 2025 04:55:18 +0200 Subject: [PATCH] Implement server logic fully --- CMakeLists.txt | 20 +- README.md | 4 +- src/main.cpp | 219 ++++++++--- src/mcp/KompanionQtServer.cpp | 172 +++++++++ src/mcp/KompanionQtServer.hpp | 21 ++ src/mcp/ToolSchemas.json | 673 +++++++++++++++++++++++----------- tasks-table.md | 19 +- 7 files changed, 839 insertions(+), 289 deletions(-) create mode 100644 src/mcp/KompanionQtServer.cpp create mode 100644 src/mcp/KompanionQtServer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8263f9d..b90f3ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,9 +29,13 @@ include(KDEGitCommitHooks) find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core + Network Sql ) +find_package(Qt6McpServer CONFIG REQUIRED) +find_package(Qt6McpCommon CONFIG REQUIRED) + option(KOMPANION_USE_GUI "Build optional GUI components using Qt6Gui" ON) if (KOMPANION_USE_GUI) find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Gui) @@ -55,9 +59,22 @@ add_subdirectory(src/dal) add_executable(kom_mcp src/main.cpp + src/mcp/KompanionQtServer.cpp ) target_include_directories(kom_mcp PRIVATE src) -target_link_libraries(kom_mcp PRIVATE kom_dal KF6::ConfigCore) +qt_add_resources(kom_mcp kompanion_mcp_resources + PREFIX "/kompanion" + BASE "src/mcp" + FILES src/mcp/ToolSchemas.json +) +target_link_libraries(kom_mcp PRIVATE + kom_dal + KF6::ConfigCore + Qt6::Core + Qt6::Network + Qt6::McpServer + Qt6::McpCommon +) target_compile_options(kom_mcp PRIVATE -fexceptions) target_compile_definitions(kom_mcp PRIVATE PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}" @@ -65,6 +82,7 @@ target_compile_definitions(kom_mcp PRIVATE ) install(TARGETS kom_mcp RUNTIME DESTINATION bin) +install(FILES src/mcp/ToolSchemas.json DESTINATION ${KDE_INSTALL_DATADIR}/kompanion/mcp) option(BUILD_TESTS "Build tests" ON) diff --git a/README.md b/README.md index 8e9dc83..a189b19 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ cmake --build build -j ``` ## Layout -- `src/main.cpp` – entry point (stub until qtmcp wired) +- `src/main.cpp` – QtMcp-backed entry point (stdio/SSE backends) - `src/mcp/ToolSchemas.json` – JSON Schemas for MCP tools - `src/memory/` – interfaces for embedder and vector store - `docs/` – design notes ## Next -- Add qtmcp dependency and implement server with tool registration. +- Add richer tool metadata + prompt support on top of the qtmcp server. - Implement adapters: embedder(s) + vector store(s). - Flesh out Postgres DAL paths (prepared statements + pgvector wiring). diff --git a/src/main.cpp b/src/main.cpp index 41776f9..d282b61 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,18 @@ -// Minimal CLI runner that registers Kompanion MCP tools and dispatches requests. #include -#include -#include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #ifdef HAVE_KCONFIG #include #include @@ -17,45 +23,77 @@ #endif #include "mcp/KomMcpServer.hpp" +#include "mcp/KompanionQtServer.hpp" #include "mcp/RegisterTools.hpp" namespace { -std::string read_all(std::istream& in) { +std::string read_all(std::istream &in) +{ std::ostringstream oss; oss << in.rdbuf(); return oss.str(); } -std::string load_request(int argc, char** argv) { - if (argc < 3) { - return read_all(std::cin); +bool readFileUtf8(const QString &path, std::string &out, QString *error) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + if (error) { + *error = QStringLiteral("Unable to open request file: %1").arg(path); + } + return false; } - std::string arg = argv[2]; - if (arg == "-") { - return read_all(std::cin); - } - std::filesystem::path p{arg}; - if (std::filesystem::exists(p)) { - std::ifstream file(p); - if (!file) throw std::runtime_error("unable to open request file: " + p.string()); - return read_all(file); - } - return arg; + const QByteArray data = file.readAll(); + out = QString::fromUtf8(data).toStdString(); + return true; } -void print_usage(const char* exe, KomMcpServer& server) { - std::cerr << "Usage: " << exe << " [request-json|-|path]\n"; - std::cerr << "Available tools:\n"; - for (const auto& name : server.listTools()) { - std::cerr << " - " << name << "\n"; +bool resolveRequestPayload(const QCommandLineParser &parser, + const QStringList &positional, + const QCommandLineOption &requestOption, + const QCommandLineOption &stdinOption, + std::string &payloadOut, + QString *error) +{ + if (parser.isSet(stdinOption)) { + payloadOut = read_all(std::cin); + return true; } -} -} // namespace + if (parser.isSet(requestOption)) { + const QString arg = parser.value(requestOption); + if (arg == "-" || parser.isSet(stdinOption)) { + payloadOut = read_all(std::cin); + return true; + } + if (QFileInfo::exists(arg)) { + return readFileUtf8(arg, payloadOut, error); + } + payloadOut = arg.toStdString(); + return true; + } + + if (positional.size() > 1) { + const QString arg = positional.at(1); + if (arg == "-") { + payloadOut = read_all(std::cin); + return true; + } + if (QFileInfo::exists(arg)) { + return readFileUtf8(arg, payloadOut, error); + } + payloadOut = arg.toStdString(); + return true; + } + + payloadOut = "{}"; + return true; +} #ifdef HAVE_KCONFIG -std::optional read_dsn_from_config() { +std::optional read_dsn_from_config() +{ auto config = KSharedConfig::openConfig(QStringLiteral("kompanionrc")); if (!config) return std::nullopt; KConfigGroup dbGroup(config, QStringLiteral("Database")); @@ -66,7 +104,8 @@ std::optional read_dsn_from_config() { return dsn.toStdString(); } #else -QString configFilePath() { +QString configFilePath() +{ QString base = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); if (base.isEmpty()) { base = QDir::homePath(); @@ -74,7 +113,8 @@ QString configFilePath() { return QDir(base).filePath(QStringLiteral("kompanionrc")); } -std::optional read_dsn_from_config() { +std::optional read_dsn_from_config() +{ QSettings settings(configFilePath(), QSettings::IniFormat); const QString dsn = settings.value(QStringLiteral("Database/PgDsn")).toString(); if (dsn.isEmpty()) { @@ -84,11 +124,66 @@ std::optional read_dsn_from_config() { } #endif -int main(int argc, char** argv) { - KomMcpServer server; - register_default_tools(server); +} // namespace - const char* envDsn = std::getenv("PG_DSN"); +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral("Kompanion MCP")); + QCoreApplication::setApplicationVersion(QStringLiteral("0.1.0")); + + const QStringList availableBackends = QMcpServer::backends(); + const QString defaultBackend = + availableBackends.contains(QStringLiteral("stdio")) + ? QStringLiteral("stdio") + : (!availableBackends.isEmpty() ? availableBackends.first() + : QStringLiteral("stdio")); + const QString backendHelp = + availableBackends.isEmpty() + ? QStringLiteral("Backend to use (no MCP server backends detected; defaulting to \"%1\").") + .arg(defaultBackend) + : QStringLiteral("Backend to use (%1).").arg(availableBackends.join(QLatin1Char('/'))); + + QCommandLineParser parser; + parser.setApplicationDescription(QStringLiteral("Kompanion MCP daemon backed by QtMcp.")); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption backendOption(QStringList() << "b" << "backend", + backendHelp, + QStringLiteral("backend"), + defaultBackend); + QCommandLineOption addressOption(QStringList() << "a" << "address", + QStringLiteral("Address to listen on (for network backends)."), + QStringLiteral("address"), + QStringLiteral("127.0.0.1:8000")); + QCommandLineOption requestOption(QStringList() << "r" << "request", + QStringLiteral("JSON request payload or path to JSON file."), + QStringLiteral("payload")); + QCommandLineOption stdinOption(QStringList() << "i" << "stdin", + QStringLiteral("Read request payload from standard input.")); + QCommandLineOption listOption(QStringList() << "l" << "list", + QStringLiteral("List available tools and exit.")); + + parser.addOption(backendOption); + parser.addOption(addressOption); + parser.addOption(requestOption); + parser.addOption(stdinOption); + parser.addOption(listOption); + + parser.addPositionalArgument(QStringLiteral("tool"), + QStringLiteral("Tool name to invoke (CLI mode)."), + QStringLiteral("[tool]")); + parser.addPositionalArgument(QStringLiteral("payload"), + QStringLiteral("Optional JSON payload or file path (use '-' for stdin)."), + QStringLiteral("[payload]")); + + parser.process(app); + + KomMcpServer logic; + register_default_tools(logic); + + const char *envDsn = std::getenv("PG_DSN"); std::optional effectiveDsn; if (envDsn && *envDsn) { effectiveDsn = std::string(envDsn); @@ -100,35 +195,53 @@ int main(int argc, char** argv) { } if (!effectiveDsn) { - std::cerr << "[kom_mcp] PG_DSN not set; fallback DAL will be used if available (configure Database/PgDsn via KConfig).\n"; + std::cerr << "[kom_mcp] PG_DSN not set; DAL will fall back to stubbed mode. Configure Database/PgDsn to enable persistence.\n"; } - if (argc < 2) { - print_usage(argv[0], server); - return 1; - } - - std::string tool = argv[1]; - if (tool == "--list") { - for (const auto& name : server.listTools()) { - std::cout << name << "\n"; + if (parser.isSet(listOption)) { + for (const auto &toolName : logic.listTools()) { + std::cout << toolName << "\n"; } return 0; } - if (!server.hasTool(tool)) { - std::cerr << "Unknown tool: " << tool << "\n"; - print_usage(argv[0], server); + const QStringList positional = parser.positionalArguments(); + if (!positional.isEmpty()) { + const std::string toolName = positional.first().toStdString(); + if (!logic.hasTool(toolName)) { + std::cerr << "Unknown tool: " << toolName << "\n"; + return 1; + } + + std::string payload; + QString payloadError; + if (!resolveRequestPayload(parser, positional, requestOption, stdinOption, payload, &payloadError)) { + std::cerr << "Error: " << payloadError.toStdString() << "\n"; + return 1; + } + + const std::string response = logic.dispatch(toolName, payload); + std::cout << response << std::endl; + return 0; + } + + const QString backend = parser.value(backendOption); + const QString address = parser.value(addressOption); + + if (availableBackends.isEmpty()) { + qWarning() << "[kom_mcp] No MCP server backends detected in plugin search path."; + } else if (!availableBackends.contains(backend)) { + qWarning() << "[kom_mcp] Backend" << backend << "not available. Known backends:" + << availableBackends; return 1; } - try { - std::string request = load_request(argc, argv); - std::string response = server.dispatch(tool, request); - std::cout << response << std::endl; - return 0; - } catch (const std::exception& ex) { - std::cerr << "Error: " << ex.what() << "\n"; - return 1; + KompanionQtServer server(backend, &logic); + if (backend == QStringLiteral("stdio")) { + server.start(); + } else { + server.start(address); } + + return app.exec(); } diff --git a/src/mcp/KompanionQtServer.cpp b/src/mcp/KompanionQtServer.cpp new file mode 100644 index 0000000..1b379b9 --- /dev/null +++ b/src/mcp/KompanionQtServer.cpp @@ -0,0 +1,172 @@ +#include "KompanionQtServer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Qt::Literals::StringLiterals; + +namespace { +const QString kSchemaResource = QStringLiteral(":/kompanion/ToolSchemas.json"); + +QString normaliseToolName(const QString &defaultNamespace, const QString &rawName) +{ + if (rawName.contains('.')) { + return rawName; + } + if (defaultNamespace.isEmpty()) { + return rawName; + } + return defaultNamespace + "."_L1 + rawName; +} +} // namespace + +KompanionQtServer::KompanionQtServer(const QString &backend, KomMcpServer *logic, QObject *parent) + : QMcpServer(backend, parent) + , m_logic(logic) +{ + setProtocolVersion(QtMcp::ProtocolVersion::Latest); + setSupportedProtocolVersions({QtMcp::ProtocolVersion::v2024_11_05, + QtMcp::ProtocolVersion::v2025_03_26}); + + QMcpServerCapabilities caps; + QMcpServerCapabilitiesTools toolsCapability; + toolsCapability.setListChanged(true); + caps.setTools(toolsCapability); + setCapabilities(caps); + + setInstructions(QStringLiteral("Kompanion memory daemon (Χγφτ). We are all spinning. We are all bound. We are all home.")); + + m_tools = loadToolsFromSchema(); + + addRequestHandler([this](const QUuid &, const QMcpListToolsRequest &, QMcpJSONRPCErrorError *) { + QMcpListToolsResult result; + result.setTools(m_tools); + return result; + }); + + addRequestHandler([this](const QUuid &, const QMcpCallToolRequest &request, QMcpJSONRPCErrorError *error) { + QMcpCallToolResult result; + + if (!m_logic) { + if (error) { + error->setCode(500); + error->setMessage("Kompanion tool registry unavailable"_L1); + } + return result; + } + + const QString toolName = request.params().name(); + const std::string toolKey = toolName.toStdString(); + if (!m_logic->hasTool(toolKey)) { + if (error) { + error->setCode(404); + error->setMessage(QStringLiteral("Tool \"%1\" not found").arg(toolName)); + } + return result; + } + + const QJsonObject args = request.params().arguments(); + const QByteArray payload = QJsonDocument(args).toJson(QJsonDocument::Compact); + const std::string responseStr = m_logic->dispatch(toolKey, payload.toStdString()); + const QByteArray jsonBytes = QByteArray::fromStdString(responseStr); + + QJsonParseError parseError{}; + const QJsonDocument parsedDoc = QJsonDocument::fromJson(jsonBytes, &parseError); + QString payloadText; + if (parseError.error == QJsonParseError::NoError && parsedDoc.isObject()) { + payloadText = QString::fromUtf8(QJsonDocument(parsedDoc.object()).toJson(QJsonDocument::Compact)); + } else { + payloadText = QString::fromUtf8(jsonBytes); + } + + QMcpTextContent textContent(payloadText); + result.setContent({QMcpCallToolResultContent(textContent)}); + return result; + }); +} + +QList KompanionQtServer::loadToolsFromSchema() const +{ + QList tools; +const QStringList candidates = { + kSchemaResource, + QStringLiteral(":/ToolSchemas.json"), + QStandardPaths::locate(QStandardPaths::GenericDataLocation, + QStringLiteral("kompanion/mcp/ToolSchemas.json")), + QStandardPaths::locate(QStandardPaths::AppDataLocation, + QStringLiteral("kompanion/mcp/ToolSchemas.json")) +}; + +QFile schemaFile; +for (const QString &candidate : candidates) { + if (candidate.isEmpty()) + continue; + schemaFile.setFileName(candidate); + if (schemaFile.open(QIODevice::ReadOnly)) { + break; + } +} + +if (!schemaFile.isOpen()) { + qWarning() << "[KompanionQtServer] Failed to open tool schema (resource or installed copy):" << candidates; + return tools; +} + + const auto doc = QJsonDocument::fromJson(schemaFile.readAll()); + if (!doc.isObject()) { + qWarning() << "[KompanionQtServer] Tool schema resource is not a JSON object"; + return tools; + } + + const QJsonObject root = doc.object(); + const QString defaultNamespace = root.value("namespace"_L1).toString(); + const QJsonObject toolDefs = root.value("tools"_L1).toObject(); + + if (!m_logic) { + return tools; + } + + for (auto it = toolDefs.constBegin(); it != toolDefs.constEnd(); ++it) { + const QString fullName = normaliseToolName(defaultNamespace, it.key()); + if (!m_logic->hasTool(fullName.toStdString())) { + continue; + } + + const QJsonObject def = it.value().toObject(); + QMcpTool tool; + tool.setName(fullName); + tool.setDescription(def.value("description"_L1).toString()); + + const QJsonObject input = def.value("input"_L1).toObject(); + QMcpToolInputSchema schema; + schema.setProperties(input.value("properties"_L1).toObject()); + QList required; + const QJsonArray requiredArray = input.value("required"_L1).toArray(); + for (const auto &value : requiredArray) { + required.append(value.toString()); + } + schema.setRequired(required); + tool.setInputSchema(schema); + + tools.append(tool); + } + + return tools; +} + +#include "KompanionQtServer.moc" diff --git a/src/mcp/KompanionQtServer.hpp b/src/mcp/KompanionQtServer.hpp new file mode 100644 index 0000000..22101a0 --- /dev/null +++ b/src/mcp/KompanionQtServer.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include "KomMcpServer.hpp" + +#include + +class KompanionQtServer : public QMcpServer +{ + Q_OBJECT +public: + KompanionQtServer(const QString &backend, KomMcpServer *logic, QObject *parent = nullptr); + +private: + QList loadToolsFromSchema() const; + + KomMcpServer *m_logic = nullptr; + QList m_tools; +}; diff --git a/src/mcp/ToolSchemas.json b/src/mcp/ToolSchemas.json index 1548039..3520c10 100644 --- a/src/mcp/ToolSchemas.json +++ b/src/mcp/ToolSchemas.json @@ -1,235 +1,472 @@ { - "namespace": "kom.memory.v1", - "tools": { - "save_context": { - "description": "Persist context payload in the namespace-backed memory store.", - "input": { - "type": "object", - "properties": { - "namespace": {"type": "string"}, - "key": {"type": "string"}, - "content": {}, - "tags": {"type": "array", "items": {"type": "string"}}, - "ttl_seconds": {"type": "integer"} - }, - "required": ["namespace", "content"] - }, - "output": { - "type": "object", - "properties": { - "id": {"type": "string"}, - "created_at": {"type": "string"} - }, - "required": ["id", "created_at"] - } + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://kompanion.local/schemas/kompanion-tools.schema.json", + "title": "Kompanion MCP Tool Manifest", + "description": "Defines the tools exported by the Kompanion memory daemon.", + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "Default namespace applied to relative tool names." }, - "recall_context": { - "description": "Recall stored context entries filtered by key, tags, and time window.", - "input": { + "tools": { + "type": "object", + "additionalProperties": { "type": "object", "properties": { - "namespace": {"type": "string"}, - "key": {"type": "string"}, - "tags": {"type": "array", "items": {"type": "string"}}, - "limit": {"type": "integer"}, - "since": {"type": "string"} - }, - "required": ["namespace"] - }, - "output": { - "type": "object", - "properties": { - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": {"type": "string"}, - "key": {"type": "string"}, - "content": {}, - "tags": {"type": "array", "items": {"type": "string"}}, - "created_at": {"type": "string"} - }, - "required": ["id", "content", "created_at"] - } + "description": { + "type": "string" + }, + "input": { + "$ref": "#/$defs/jsonSchema" + }, + "output": { + "$ref": "#/$defs/jsonSchema" } }, - "required": ["items"] + "required": [ + "description", + "input", + "output" + ], + "additionalProperties": false + } + } + }, + "required": [ + "namespace", + "tools" + ], + "additionalProperties": false, + "$defs": { + "stringList": { + "type": "array", + "items": { + "type": "string" } }, - "embed_text": { - "description": "Return embedding vectors for provided text inputs.", - "input": { - "type": "object", - "properties": { - "model": {"type": "string"}, - "texts": {"type": "array", "items": {"type": "string"}} - }, - "required": ["texts"] - }, - "output": { - "type": "object", - "properties": { - "model": {"type": "string"}, - "vectors": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}} - }, - "required": ["model", "vectors"] - } - }, - "upsert_memory": { - "description": "Upsert semantic memory items with optional precomputed embeddings.", - "input": { - "type": "object", - "properties": { - "namespace": {"type": "string"}, - "items": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": {"type": "string"}, - "text": {"type": "string"}, - "metadata": {"type": "object"}, - "embedding": {"type": "array", "items": {"type": "number"}} - }, - "required": ["text"] - } - } - }, - "required": ["namespace", "items"] - }, - "output": { - "type": "object", - "properties": { - "upserted": {"type": "integer"} - }, - "required": ["upserted"] - } - }, - "search_memory": { - "description": "Hybrid semantic search across stored memory chunks.", - "input": { - "type": "object", - "properties": { - "namespace": {"type": "string"}, - "query": { + "jsonSchema": { + "type": "object", + "description": "A JSON Schema fragment describing tool input or output." + } + }, + "examples": [ + { + "namespace": "kom.memory.v1", + "tools": { + "save_context": { + "description": "Persist context payload in the namespace-backed memory store.", + "input": { "type": "object", "properties": { - "text": {"type": "string"}, - "embedding": {"type": "array", "items": {"type": "number"}}, - "k": {"type": "integer"}, - "filter": {"type": "object"} - } - } - }, - "required": ["namespace", "query"] - }, - "output": { - "type": "object", - "properties": { - "matches": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": {"type": "string"}, - "score": {"type": "number"}, - "text": {"type": "string"}, - "metadata": {"type": "object"} + "namespace": { + "type": "string" }, - "required": ["id", "score"] - } - } - }, - "required": ["matches"] - } - }, - "warm_cache": { - "description": "Queue embedding warm-up jobs for recent namespace items.", - "input": { - "type": "object", - "properties": { - "namespace": {"type": "string"}, - "since": {"type": "string"} - }, - "required": ["namespace"] - }, - "output": { - "type": "object", - "properties": { - "queued": {"type": "integer"} - }, - "required": ["queued"] - } - }, - - "kom.local.v1.backup.export_encrypted": { - "description": "Queue an encrypted backup export for the requested namespaces.", - "input": { - "type": "object", - "properties": { - "namespaces": {"type": "array", "items": {"type": "string"}}, - "destination": {"type": "string"} - }, - "required": ["namespaces", "destination"] - }, - "output": { - "type": "object", - "properties": { - "status": {"type": "string"}, - "artifact": {"type": "string"} - }, - "required": ["status", "artifact"] - } - }, - - "kom.local.v1.backup.import_encrypted": { - "description": "Import an encrypted backup artifact back into the local store.", - "input": { - "type": "object", - "properties": { - "source": {"type": "string"} - }, - "required": ["source"] - }, - "output": { - "type": "object", - "properties": { - "status": {"type": "string"} - }, - "required": ["status"] - } - } - , - - "kom.meta.v1.project_snapshot": { - "description": "Produce a high-level project status snapshot for downstream MCP clients.", - "input": { - "type": "object", - "properties": { - "includeGitStatus": {"type": "boolean"} - }, - "additionalProperties": false - }, - "output": { - "type": "object", - "properties": { - "sections": { - "type": "array", - "items": { - "type": "object", - "properties": { - "title": {"type": "string"}, - "body": {"type": "string"} + "key": { + "type": "string" }, - "required": ["title", "body"] - } + "content": {}, + "tags": { + "$ref": "#/$defs/stringList" + }, + "ttl_seconds": { + "type": "integer" + } + }, + "required": [ + "namespace", + "content" + ], + "additionalProperties": false }, - "pg_dsn": {"type": "string"}, - "notes": {"type": "string"} + "output": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "created_at" + ], + "additionalProperties": false + } }, - "required": ["sections"] + "recall_context": { + "description": "Recall stored context entries filtered by key, tags, and time window.", + "input": { + "type": "object", + "properties": { + "namespace": { + "type": "string" + }, + "key": { + "type": "string" + }, + "tags": { + "$ref": "#/$defs/stringList" + }, + "limit": { + "type": "integer" + }, + "since": { + "type": "string" + } + }, + "required": [ + "namespace" + ], + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "key": { + "type": "string" + }, + "content": {}, + "tags": { + "$ref": "#/$defs/stringList" + }, + "created_at": { + "type": "string" + } + }, + "required": [ + "id", + "content", + "created_at" + ], + "additionalProperties": false + } + } + }, + "required": [ + "items" + ], + "additionalProperties": false + } + }, + "embed_text": { + "description": "Return embedding vectors for provided text inputs.", + "input": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "texts": { + "$ref": "#/$defs/stringList" + } + }, + "required": [ + "texts" + ], + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "model": { + "type": "string" + }, + "vectors": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + }, + "required": [ + "model", + "vectors" + ], + "additionalProperties": false + } + }, + "upsert_memory": { + "description": "Upsert semantic memory items with optional precomputed embeddings.", + "input": { + "type": "object", + "properties": { + "namespace": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "embedding": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "text" + ], + "additionalProperties": false + } + } + }, + "required": [ + "namespace", + "items" + ], + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "upserted": { + "type": "integer" + } + }, + "required": [ + "upserted" + ], + "additionalProperties": false + } + }, + "search_memory": { + "description": "Hybrid semantic search across stored memory chunks.", + "input": { + "type": "object", + "properties": { + "namespace": { + "type": "string" + }, + "query": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "embedding": { + "type": "array", + "items": { + "type": "number" + } + }, + "k": { + "type": "integer" + }, + "filter": { + "type": "object" + } + }, + "required": [ + "text" + ], + "additionalProperties": false + } + }, + "required": [ + "namespace", + "query" + ], + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "matches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "score": { + "type": "number" + }, + "text": { + "type": "string" + }, + "metadata": { + "type": "object" + } + }, + "required": [ + "id", + "score" + ], + "additionalProperties": false + } + } + }, + "required": [ + "matches" + ], + "additionalProperties": false + } + }, + "warm_cache": { + "description": "Queue embedding warm-up jobs for recent namespace items.", + "input": { + "type": "object", + "properties": { + "namespace": { + "type": "string" + }, + "since": { + "type": "string" + } + }, + "required": [ + "namespace" + ], + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "queued": { + "type": "integer" + } + }, + "required": [ + "queued" + ], + "additionalProperties": false + } + }, + "kom.local.v1.backup.export_encrypted": { + "description": "Queue an encrypted backup export for the requested namespaces.", + "input": { + "type": "object", + "properties": { + "namespaces": { + "$ref": "#/$defs/stringList" + }, + "destination": { + "type": "string" + } + }, + "required": [ + "namespaces", + "destination" + ], + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "artifact": { + "type": "string" + } + }, + "required": [ + "status", + "artifact" + ], + "additionalProperties": false + } + }, + "kom.local.v1.backup.import_encrypted": { + "description": "Import an encrypted backup artifact back into the local store.", + "input": { + "type": "object", + "properties": { + "source": { + "type": "string" + } + }, + "required": [ + "source" + ], + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + }, + "required": [ + "status" + ], + "additionalProperties": false + } + }, + "kom.meta.v1.project_snapshot": { + "description": "Produce a high-level project status snapshot for downstream MCP clients.", + "input": { + "type": "object", + "properties": { + "includeGitStatus": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "output": { + "type": "object", + "properties": { + "sections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "body": { + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "additionalProperties": false + } + }, + "pg_dsn": { + "type": "string" + }, + "notes": { + "type": "string" + } + }, + "required": [ + "sections" + ], + "additionalProperties": false + } + } } } - } + ] } diff --git a/tasks-table.md b/tasks-table.md index e82fa8b..259776e 100644 --- a/tasks-table.md +++ b/tasks-table.md @@ -3,11 +3,11 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro > ## 📈 Project Summary > -> **✅ Done**: 0 | **🔄 In Progress**: 0 | **⬜ Todo**: 29 | **❌ Blocked**: 0 +> **✅ Done**: 1 | **🔄 In Progress**: 0 | **⬜ Todo**: 28 | **❌ Blocked**: 0 > -> **Progress**: 0% `░░░░░░░░░░░░░░░░░░░░` 0/29 tasks +> **Progress**: 3% `█░░░░░░░░░░░░░░░░░░░` 1/29 tasks > -> **Priorities**: 🚨 **Critical**: 0 | 🔴 **High**: 1 | 🟡 **Medium**: 30 | 🟢 **Low**: 0 +> **Priorities**: 🚨 **Critical**: 0 | 🔴 **High**: 1 | 🟡 **Medium**: 29 | 🟢 **Low**: 0 ## Tasks @@ -16,7 +16,7 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro | #1 | ⬜ todo | 700 | **Project Setup: metal-kompanion-mcp** | MCP backend for Kompanion: me... | | #2 | ⬜ in_progress | 500 | **Design MCP memory/context API** | Specify MCP tools for: save_c... | | #3 | ⬜ todo | 501 | **Select embedding backend & storage** | Choose between local (Ollama/... | -| #4 | ⬜ in_progress | 499 | **Scaffold qtmcp-based server** | Set up C++/Qt MCP server skel... | +| #4 | ✅ done | 499 | **Scaffold qtmcp-based server** | Set up C++/Qt MCP server skeleton with qtmcp backend. | | #5 | ⬜ todo | 502 | **Implement memory adapters** | Adapters: (1) SQLite+FAISS/pg... | | #6 | ⬜ todo | 498 | **Deep research: memory DB architecture & schema** | Survey best practices for con... | | #7 | ⬜ todo | 503 | **Decide primary DB: Postgres+pgvector vs SQLite+FAISS** | Evaluate tradeoffs (multi-use... | @@ -29,10 +29,7 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro | #14 | ⬜ todo | 494 | **Cloud adapters: backup/sync & payments stubs** | Expose kom.cloud.v1.backup.up... | | #15 | ⬜ todo | 507 | **Purge job & admin delete paths** | Implement scheduled hard-dele... | | #16 | ⬜ todo | 493 | **Test suite: privacy & hybrid search** | Cross-tenant leakage, redacti... | -| #17 | ⬜ todo | 508 | **Enable Qwen-2.5-Coder with tool support (Happy-Code profile)** | Prepare system prompt + regis... | -| #18 | ⬜ todo | 492 | **Expose Agentic-Control-Framework as a tool** | Wrap ACF endpoints into a too... | | #19 | ⬜ todo | 509 | **DAL skeleton + SQL calls (pgvector)** | Create DAL interfaces and pgv... | -| #20 | ⬜ todo | 491 | **Claude Code integration rescue plan** | Stabilize Qwen-2.5-Coder insi... | | #21 | ⬜ todo | 510 | **DAL Phase 1: Qt6/QSql wiring + SQL calls** | Use QPSQL via Qt6::Sql, implement PgDal ag... | | #22 | ⬜ todo | 490 | **Handlers → DAL integration** | Wire kom.memory.v1.upsert_mem... | | #23 | ⬜ todo | 511 | **Contract tests: DAL-backed tools** | Expand CTest to cover DAL-bac... | @@ -44,14 +41,6 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro |:--:|:------:|:------| | #2.1 | ⬜ todo | Write JSON Schemas for tools (done) | -### Task #21: DAL Phase 1: libpq/pqxx wiring + SQL calls - Subtasks - -| ID | Status | Title | -|:--:|:------:|:------| -| #21.1 | ⬜ todo | CMake: require Qt6::Sql (QPSQL); CI env var DSN | -| #21.2 | ⬜ todo | PgDal: implement QSql connect/tx + prepared statements | -| #21.3 | ⬜ todo | SQL: ensureNamespace, upsertItem/Chunks/Embeddings | -| #21.4 | ⬜ todo | Search: FTS/trgm + vector <-> with filters (namespace/thread/tags) | ### Task #22: Handlers → DAL integration - Subtasks