Implement server logic fully
This commit is contained in:
parent
220be1744d
commit
f71b2ef510
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
207
src/main.cpp
207
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
return arg;
|
||||
payloadOut = arg.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";
|
||||
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,9 +124,64 @@ std::optional<std::string> read_dsn_from_config() {
|
|||
}
|
||||
#endif
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
KomMcpServer server;
|
||||
register_default_tools(server);
|
||||
} // namespace
|
||||
|
||||
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;
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -1,3 +1,56 @@
|
|||
{
|
||||
"$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": {
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue