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
|
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS
|
||||||
Core
|
Core
|
||||||
|
Network
|
||||||
Sql
|
Sql
|
||||||
)
|
)
|
||||||
|
|
||||||
|
find_package(Qt6McpServer CONFIG REQUIRED)
|
||||||
|
find_package(Qt6McpCommon CONFIG REQUIRED)
|
||||||
|
|
||||||
option(KOMPANION_USE_GUI "Build optional GUI components using Qt6Gui" ON)
|
option(KOMPANION_USE_GUI "Build optional GUI components using Qt6Gui" ON)
|
||||||
if (KOMPANION_USE_GUI)
|
if (KOMPANION_USE_GUI)
|
||||||
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Gui)
|
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Gui)
|
||||||
|
|
@ -55,9 +59,22 @@ add_subdirectory(src/dal)
|
||||||
|
|
||||||
add_executable(kom_mcp
|
add_executable(kom_mcp
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
|
src/mcp/KompanionQtServer.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(kom_mcp PRIVATE src)
|
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_options(kom_mcp PRIVATE -fexceptions)
|
||||||
target_compile_definitions(kom_mcp PRIVATE
|
target_compile_definitions(kom_mcp PRIVATE
|
||||||
PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
|
PROJECT_SOURCE_DIR="${CMAKE_SOURCE_DIR}"
|
||||||
|
|
@ -65,6 +82,7 @@ target_compile_definitions(kom_mcp PRIVATE
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS kom_mcp RUNTIME DESTINATION bin)
|
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)
|
option(BUILD_TESTS "Build tests" ON)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,13 @@ cmake --build build -j
|
||||||
```
|
```
|
||||||
|
|
||||||
## Layout
|
## 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/mcp/ToolSchemas.json` – JSON Schemas for MCP tools
|
||||||
- `src/memory/` – interfaces for embedder and vector store
|
- `src/memory/` – interfaces for embedder and vector store
|
||||||
- `docs/` – design notes
|
- `docs/` – design notes
|
||||||
|
|
||||||
## Next
|
## 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).
|
- Implement adapters: embedder(s) + vector store(s).
|
||||||
- Flesh out Postgres DAL paths (prepared statements + pgvector wiring).
|
- Flesh out Postgres DAL paths (prepared statements + pgvector wiring).
|
||||||
|
|
||||||
|
|
|
||||||
219
src/main.cpp
219
src/main.cpp
|
|
@ -1,12 +1,18 @@
|
||||||
// Minimal CLI runner that registers Kompanion MCP tools and dispatches requests.
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <QCommandLineOption>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QLatin1Char>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
#ifdef HAVE_KCONFIG
|
#ifdef HAVE_KCONFIG
|
||||||
#include <KConfigGroup>
|
#include <KConfigGroup>
|
||||||
#include <KSharedConfig>
|
#include <KSharedConfig>
|
||||||
|
|
@ -17,45 +23,77 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "mcp/KomMcpServer.hpp"
|
#include "mcp/KomMcpServer.hpp"
|
||||||
|
#include "mcp/KompanionQtServer.hpp"
|
||||||
#include "mcp/RegisterTools.hpp"
|
#include "mcp/RegisterTools.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::string read_all(std::istream& in) {
|
std::string read_all(std::istream &in)
|
||||||
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << in.rdbuf();
|
oss << in.rdbuf();
|
||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string load_request(int argc, char** argv) {
|
bool readFileUtf8(const QString &path, std::string &out, QString *error)
|
||||||
if (argc < 3) {
|
{
|
||||||
return read_all(std::cin);
|
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];
|
const QByteArray data = file.readAll();
|
||||||
if (arg == "-") {
|
out = QString::fromUtf8(data).toStdString();
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_usage(const char* exe, KomMcpServer& server) {
|
bool resolveRequestPayload(const QCommandLineParser &parser,
|
||||||
std::cerr << "Usage: " << exe << " <tool-name> [request-json|-|path]\n";
|
const QStringList &positional,
|
||||||
std::cerr << "Available tools:\n";
|
const QCommandLineOption &requestOption,
|
||||||
for (const auto& name : server.listTools()) {
|
const QCommandLineOption &stdinOption,
|
||||||
std::cerr << " - " << name << "\n";
|
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
|
#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"));
|
auto config = KSharedConfig::openConfig(QStringLiteral("kompanionrc"));
|
||||||
if (!config) return std::nullopt;
|
if (!config) return std::nullopt;
|
||||||
KConfigGroup dbGroup(config, QStringLiteral("Database"));
|
KConfigGroup dbGroup(config, QStringLiteral("Database"));
|
||||||
|
|
@ -66,7 +104,8 @@ std::optional<std::string> read_dsn_from_config() {
|
||||||
return dsn.toStdString();
|
return dsn.toStdString();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
QString configFilePath() {
|
QString configFilePath()
|
||||||
|
{
|
||||||
QString base = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
QString base = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||||
if (base.isEmpty()) {
|
if (base.isEmpty()) {
|
||||||
base = QDir::homePath();
|
base = QDir::homePath();
|
||||||
|
|
@ -74,7 +113,8 @@ QString configFilePath() {
|
||||||
return QDir(base).filePath(QStringLiteral("kompanionrc"));
|
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);
|
QSettings settings(configFilePath(), QSettings::IniFormat);
|
||||||
const QString dsn = settings.value(QStringLiteral("Database/PgDsn")).toString();
|
const QString dsn = settings.value(QStringLiteral("Database/PgDsn")).toString();
|
||||||
if (dsn.isEmpty()) {
|
if (dsn.isEmpty()) {
|
||||||
|
|
@ -84,11 +124,66 @@ std::optional<std::string> read_dsn_from_config() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
} // namespace
|
||||||
KomMcpServer server;
|
|
||||||
register_default_tools(server);
|
|
||||||
|
|
||||||
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;
|
std::optional<std::string> effectiveDsn;
|
||||||
if (envDsn && *envDsn) {
|
if (envDsn && *envDsn) {
|
||||||
effectiveDsn = std::string(envDsn);
|
effectiveDsn = std::string(envDsn);
|
||||||
|
|
@ -100,35 +195,53 @@ int main(int argc, char** argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!effectiveDsn) {
|
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) {
|
if (parser.isSet(listOption)) {
|
||||||
print_usage(argv[0], server);
|
for (const auto &toolName : logic.listTools()) {
|
||||||
return 1;
|
std::cout << toolName << "\n";
|
||||||
}
|
|
||||||
|
|
||||||
std::string tool = argv[1];
|
|
||||||
if (tool == "--list") {
|
|
||||||
for (const auto& name : server.listTools()) {
|
|
||||||
std::cout << name << "\n";
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!server.hasTool(tool)) {
|
const QStringList positional = parser.positionalArguments();
|
||||||
std::cerr << "Unknown tool: " << tool << "\n";
|
if (!positional.isEmpty()) {
|
||||||
print_usage(argv[0], server);
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
KompanionQtServer server(backend, &logic);
|
||||||
std::string request = load_request(argc, argv);
|
if (backend == QStringLiteral("stdio")) {
|
||||||
std::string response = server.dispatch(tool, request);
|
server.start();
|
||||||
std::cout << response << std::endl;
|
} else {
|
||||||
return 0;
|
server.start(address);
|
||||||
} catch (const std::exception& ex) {
|
|
||||||
std::cerr << "Error: " << ex.what() << "\n";
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,235 +1,472 @@
|
||||||
{
|
{
|
||||||
"namespace": "kom.memory.v1",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"tools": {
|
"$id": "https://kompanion.local/schemas/kompanion-tools.schema.json",
|
||||||
"save_context": {
|
"title": "Kompanion MCP Tool Manifest",
|
||||||
"description": "Persist context payload in the namespace-backed memory store.",
|
"description": "Defines the tools exported by the Kompanion memory daemon.",
|
||||||
"input": {
|
"type": "object",
|
||||||
"type": "object",
|
"properties": {
|
||||||
"properties": {
|
"namespace": {
|
||||||
"namespace": {"type": "string"},
|
"type": "string",
|
||||||
"key": {"type": "string"},
|
"description": "Default namespace applied to relative tool names."
|
||||||
"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"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"recall_context": {
|
"tools": {
|
||||||
"description": "Recall stored context entries filtered by key, tags, and time window.",
|
"type": "object",
|
||||||
"input": {
|
"additionalProperties": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"namespace": {"type": "string"},
|
"description": {
|
||||||
"key": {"type": "string"},
|
"type": "string"
|
||||||
"tags": {"type": "array", "items": {"type": "string"}},
|
},
|
||||||
"limit": {"type": "integer"},
|
"input": {
|
||||||
"since": {"type": "string"}
|
"$ref": "#/$defs/jsonSchema"
|
||||||
},
|
},
|
||||||
"required": ["namespace"]
|
"output": {
|
||||||
},
|
"$ref": "#/$defs/jsonSchema"
|
||||||
"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"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["items"]
|
"required": [
|
||||||
|
"description",
|
||||||
|
"input",
|
||||||
|
"output"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"namespace",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"$defs": {
|
||||||
|
"stringList": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"embed_text": {
|
"jsonSchema": {
|
||||||
"description": "Return embedding vectors for provided text inputs.",
|
"type": "object",
|
||||||
"input": {
|
"description": "A JSON Schema fragment describing tool input or output."
|
||||||
"type": "object",
|
}
|
||||||
"properties": {
|
},
|
||||||
"model": {"type": "string"},
|
"examples": [
|
||||||
"texts": {"type": "array", "items": {"type": "string"}}
|
{
|
||||||
},
|
"namespace": "kom.memory.v1",
|
||||||
"required": ["texts"]
|
"tools": {
|
||||||
},
|
"save_context": {
|
||||||
"output": {
|
"description": "Persist context payload in the namespace-backed memory store.",
|
||||||
"type": "object",
|
"input": {
|
||||||
"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": {
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"text": {"type": "string"},
|
"namespace": {
|
||||||
"embedding": {"type": "array", "items": {"type": "number"}},
|
"type": "string"
|
||||||
"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"}
|
|
||||||
},
|
},
|
||||||
"required": ["id", "score"]
|
"key": {
|
||||||
}
|
"type": "string"
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"}
|
|
||||||
},
|
},
|
||||||
"required": ["title", "body"]
|
"content": {},
|
||||||
}
|
"tags": {
|
||||||
|
"$ref": "#/$defs/stringList"
|
||||||
|
},
|
||||||
|
"ttl_seconds": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"namespace",
|
||||||
|
"content"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"pg_dsn": {"type": "string"},
|
"output": {
|
||||||
"notes": {"type": "string"}
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ MCP backend for Kompanion: memory/context/embedding provider over MCP, built fro
|
||||||
|
|
||||||
> ## 📈 Project Summary
|
> ## 📈 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
|
## 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... |
|
| #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... |
|
| #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/... |
|
| #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... |
|
| #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... |
|
| #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... |
|
| #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... |
|
| #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... |
|
| #15 | ⬜ todo | 507 | **Purge job & admin delete paths** | Implement scheduled hard-dele... |
|
||||||
| #16 | ⬜ todo | 493 | **Test suite: privacy & hybrid search** | Cross-tenant leakage, redacti... |
|
| #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... |
|
| #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... |
|
| #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... |
|
| #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... |
|
| #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) |
|
| #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
|
### Task #22: Handlers → DAL integration - Subtasks
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue