From f71b2ef510d3c1b832df243538404590690d17c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=CE=A7=CE=B3=CF=86=CF=84=20Kompanion?=
 <chigammaphitau@chelydra.at>
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 <cstdlib>
-#include <filesystem>
-#include <fstream>
 #include <iostream>
 #include <optional>
 #include <sstream>
 #include <string>
 
+#include <QCommandLineOption>
+#include <QCommandLineParser>
+#include <QCoreApplication>
+#include <QFile>
+#include <QFileInfo>
+#include <QLatin1Char>
+#include <QTextStream>
+#include <QStringList>
+
 #ifdef HAVE_KCONFIG
 #include <KConfigGroup>
 #include <KSharedConfig>
@@ -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 << " <tool-name> [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<std::string> read_dsn_from_config() {
+std::optional<std::string> 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<std::string> 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<std::string> read_dsn_from_config() {
+std::optional<std::string> 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<std::string> 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<std::string> 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 <QtMcpCommon/QMcpCallToolRequest>
+#include <QtMcpCommon/QMcpCallToolResult>
+#include <QtMcpCommon/QMcpCallToolResultContent>
+#include <QtMcpCommon/QMcpListToolsRequest>
+#include <QtMcpCommon/QMcpListToolsResult>
+#include <QtMcpCommon/QMcpServerCapabilities>
+#include <QtMcpCommon/QMcpServerCapabilitiesTools>
+#include <QtMcpCommon/QMcpTextContent>
+#include <QtMcpCommon/QMcpToolInputSchema>
+
+#include <QtCore/QFile>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QLoggingCategory>
+#include <QtCore/QStandardPaths>
+#include <QtCore/QStringList>
+
+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<QMcpTool> KompanionQtServer::loadToolsFromSchema() const
+{
+    QList<QMcpTool> 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<QString> 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 <QtMcpServer/QMcpServer>
+#include <QtMcpCommon/QMcpTool>
+
+#include "KomMcpServer.hpp"
+
+#include <QList>
+
+class KompanionQtServer : public QMcpServer
+{
+    Q_OBJECT
+public:
+    KompanionQtServer(const QString &backend, KomMcpServer *logic, QObject *parent = nullptr);
+
+private:
+    QList<QMcpTool> loadToolsFromSchema() const;
+
+    KomMcpServer *m_logic = nullptr;
+    QList<QMcpTool> 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
 
