Implement server logic fully

This commit is contained in:
Χγφτ Kompanion 2025-10-16 04:55:18 +02:00
parent 220be1744d
commit f71b2ef510
7 changed files with 839 additions and 289 deletions

View File

@ -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)

View File

@ -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).

View File

@ -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);
}
std::string arg = argv[2];
return false;
}
const QByteArray data = file.readAll();
out = QString::fromUtf8(data).toStdString();
return true;
}
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;
}
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 == "-") {
return read_all(std::cin);
payloadOut = read_all(std::cin);
return true;
}
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);
if (QFileInfo::exists(arg)) {
return readFileUtf8(arg, payloadOut, error);
}
payloadOut = arg.toStdString();
return true;
}
return arg;
}
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";
}
payloadOut = "{}";
return true;
}
} // namespace
#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;
}
try {
std::string request = load_request(argc, argv);
std::string response = server.dispatch(tool, request);
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;
} catch (const std::exception& ex) {
std::cerr << "Error: " << ex.what() << "\n";
}
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;
}
KompanionQtServer server(backend, &logic);
if (backend == QStringLiteral("stdio")) {
server.start();
} else {
server.start(address);
}
return app.exec();
}

View File

@ -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"

View File

@ -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;
};

View File

@ -1,4 +1,57 @@
{
"$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."
},
"tools": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"input": {
"$ref": "#/$defs/jsonSchema"
},
"output": {
"$ref": "#/$defs/jsonSchema"
}
},
"required": [
"description",
"input",
"output"
],
"additionalProperties": false
}
}
},
"required": [
"namespace",
"tools"
],
"additionalProperties": false,
"$defs": {
"stringList": {
"type": "array",
"items": {
"type": "string"
}
},
"jsonSchema": {
"type": "object",
"description": "A JSON Schema fragment describing tool input or output."
}
},
"examples": [
{
"namespace": "kom.memory.v1",
"tools": {
"save_context": {
@ -6,21 +59,41 @@
"input": {
"type": "object",
"properties": {
"namespace": {"type": "string"},
"key": {"type": "string"},
"content": {},
"tags": {"type": "array", "items": {"type": "string"}},
"ttl_seconds": {"type": "integer"}
"namespace": {
"type": "string"
},
"required": ["namespace", "content"]
"key": {
"type": "string"
},
"content": {},
"tags": {
"$ref": "#/$defs/stringList"
},
"ttl_seconds": {
"type": "integer"
}
},
"required": [
"namespace",
"content"
],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"id": {"type": "string"},
"created_at": {"type": "string"}
"id": {
"type": "string"
},
"required": ["id", "created_at"]
"created_at": {
"type": "string"
}
},
"required": [
"id",
"created_at"
],
"additionalProperties": false
}
},
"recall_context": {
@ -28,13 +101,26 @@
"input": {
"type": "object",
"properties": {
"namespace": {"type": "string"},
"key": {"type": "string"},
"tags": {"type": "array", "items": {"type": "string"}},
"limit": {"type": "integer"},
"since": {"type": "string"}
"namespace": {
"type": "string"
},
"required": ["namespace"]
"key": {
"type": "string"
},
"tags": {
"$ref": "#/$defs/stringList"
},
"limit": {
"type": "integer"
},
"since": {
"type": "string"
}
},
"required": [
"namespace"
],
"additionalProperties": false
},
"output": {
"type": "object",
@ -44,17 +130,33 @@
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"key": {"type": "string"},
"id": {
"type": "string"
},
"key": {
"type": "string"
},
"content": {},
"tags": {"type": "array", "items": {"type": "string"}},
"created_at": {"type": "string"}
"tags": {
"$ref": "#/$defs/stringList"
},
"required": ["id", "content", "created_at"]
"created_at": {
"type": "string"
}
},
"required": [
"id",
"content",
"created_at"
],
"additionalProperties": false
}
}
},
"required": ["items"]
"required": [
"items"
],
"additionalProperties": false
}
},
"embed_text": {
@ -62,18 +164,39 @@
"input": {
"type": "object",
"properties": {
"model": {"type": "string"},
"texts": {"type": "array", "items": {"type": "string"}}
"model": {
"type": "string"
},
"required": ["texts"]
"texts": {
"$ref": "#/$defs/stringList"
}
},
"required": [
"texts"
],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"model": {"type": "string"},
"vectors": {"type": "array", "items": {"type": "array", "items": {"type": "number"}}}
"model": {
"type": "string"
},
"required": ["model", "vectors"]
"vectors": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "number"
}
}
}
},
"required": [
"model",
"vectors"
],
"additionalProperties": false
}
},
"upsert_memory": {
@ -81,29 +204,54 @@
"input": {
"type": "object",
"properties": {
"namespace": {"type": "string"},
"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"}}
"id": {
"type": "string"
},
"required": ["text"]
"text": {
"type": "string"
},
"metadata": {
"type": "object"
},
"embedding": {
"type": "array",
"items": {
"type": "number"
}
}
},
"required": ["namespace", "items"]
"required": [
"text"
],
"additionalProperties": false
}
}
},
"required": [
"namespace",
"items"
],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"upserted": {"type": "integer"}
"upserted": {
"type": "integer"
}
},
"required": ["upserted"]
"required": [
"upserted"
],
"additionalProperties": false
}
},
"search_memory": {
@ -111,18 +259,39 @@
"input": {
"type": "object",
"properties": {
"namespace": {"type": "string"},
"namespace": {
"type": "string"
},
"query": {
"type": "object",
"properties": {
"text": {"type": "string"},
"embedding": {"type": "array", "items": {"type": "number"}},
"k": {"type": "integer"},
"filter": {"type": "object"}
}
"text": {
"type": "string"
},
"embedding": {
"type": "array",
"items": {
"type": "number"
}
},
"required": ["namespace", "query"]
"k": {
"type": "integer"
},
"filter": {
"type": "object"
}
},
"required": [
"text"
],
"additionalProperties": false
}
},
"required": [
"namespace",
"query"
],
"additionalProperties": false
},
"output": {
"type": "object",
@ -132,16 +301,31 @@
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"score": {"type": "number"},
"text": {"type": "string"},
"metadata": {"type": "object"}
"id": {
"type": "string"
},
"required": ["id", "score"]
"score": {
"type": "number"
},
"text": {
"type": "string"
},
"metadata": {
"type": "object"
}
},
"required": [
"id",
"score"
],
"additionalProperties": false
}
}
},
"required": ["matches"]
"required": [
"matches"
],
"additionalProperties": false
}
},
"warm_cache": {
@ -149,65 +333,101 @@
"input": {
"type": "object",
"properties": {
"namespace": {"type": "string"},
"since": {"type": "string"}
"namespace": {
"type": "string"
},
"required": ["namespace"]
"since": {
"type": "string"
}
},
"required": [
"namespace"
],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"queued": {"type": "integer"}
},
"required": ["queued"]
"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": {"type": "array", "items": {"type": "string"}},
"destination": {"type": "string"}
"namespaces": {
"$ref": "#/$defs/stringList"
},
"required": ["namespaces", "destination"]
"destination": {
"type": "string"
}
},
"required": [
"namespaces",
"destination"
],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"status": {"type": "string"},
"artifact": {"type": "string"}
"status": {
"type": "string"
},
"required": ["status", "artifact"]
"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"}
"source": {
"type": "string"
}
},
"required": ["source"]
"required": [
"source"
],
"additionalProperties": false
},
"output": {
"type": "object",
"properties": {
"status": {"type": "string"}
"status": {
"type": "string"
}
},
"required": ["status"]
"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"}
"includeGitStatus": {
"type": "boolean"
}
},
"additionalProperties": false
},
@ -219,17 +439,34 @@
"items": {
"type": "object",
"properties": {
"title": {"type": "string"},
"body": {"type": "string"}
"title": {
"type": "string"
},
"required": ["title", "body"]
"body": {
"type": "string"
}
},
"pg_dsn": {"type": "string"},
"notes": {"type": "string"}
"required": [
"title",
"body"
],
"additionalProperties": false
}
},
"required": ["sections"]
"pg_dsn": {
"type": "string"
},
"notes": {
"type": "string"
}
},
"required": [
"sections"
],
"additionalProperties": false
}
}
}
}
]
}

View File

@ -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