mcp: сервер memo_search/memo_status/memo_reindex
This commit is contained in:
+36
-42
@@ -1,68 +1,62 @@
|
||||
Проект: /root/WORK/memo (Kotlin/JVM). Заказ по MCP-серверу.
|
||||
Проект: /root/WORK/memo (Kotlin/JVM). Заказ по MCP-серверу. Контракт уже в docs/orders/07-mcp.md —
|
||||
читать его как исходное ТЗ, а здесь перечислены обязательные требования и известные грабли.
|
||||
|
||||
Создать:
|
||||
memo-mcp/build.gradle.kts (kotlin("jvm") + application + зависимости на :memo-core и kotlinx-serialization-json)
|
||||
memo-mcp/build.gradle.kts
|
||||
memo-mcp/src/main/kotlin/memo/mcp/McpServer.kt
|
||||
memo-mcp/src/test/kotlin/memo/mcp/McpProtocolTest.kt
|
||||
|
||||
## Зависимости модуля memo-mcp
|
||||
|
||||
plugins { kotlin("jvm") application; kotlin("plugin.serialization") version "2.4.10" }
|
||||
plugins { kotlin("jvm"); application; kotlin("plugin.serialization") version "2.4.10" }
|
||||
application { mainClass.set("memo.mcp.McpServerKt") }
|
||||
dependencies {
|
||||
implementation(project(":memo-core"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
||||
}
|
||||
ВАЖНО: в kotlin("plugin.serialization") версию указать обязательно 2.4.10, иначе сборка упадёт на несовместимости версий плагина и Kotlin.
|
||||
Если сериализация начнёт мешать — НЕ добавляй её, собери JSON строками вручную (ручной JSON-рендер допустим и проще).
|
||||
|
||||
## McpServer.kt — протокол
|
||||
## McpServer.kt
|
||||
|
||||
fun main(args: Array<String>) — читает stdin построчно, отвечает в stdout ОДНОЙ строкой JSON-RPC 2.0 на запрос.
|
||||
НИЧЕГО кроме JSON-RPC в stdout не печатать (диагностика — только в stderr).
|
||||
Поддерживаемые методы:
|
||||
- `initialize` → result: {"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"memo","version":"0.1.0"}}
|
||||
- `notifications/initialized` → уведомление, ответ не отправлять
|
||||
- `tools/list` → result: {"tools":[ ... ]} с тремя инструментами:
|
||||
* memo_search: {"path":string,"query":string,"k":integer(по умолч. 8),"mode":string("hybrid"|"lex"|"vec")}
|
||||
обязательные: path, query
|
||||
* memo_status: {"path":string} — обязательный: path
|
||||
* memo_reindex: {"path":string} — обязательный: path
|
||||
- `tools/call` с params {"name":..., "arguments":{...}} →
|
||||
result: {"content":[{"type":"text","text":"<строка>"}],"isError":<bool>}
|
||||
Ошибка (нет обязательного аргумента, неизвестный инструмент, исключение) → result с "isError":true
|
||||
и текстом ошибки, а НЕ JSON-RPC error.
|
||||
fun main(args: Array<String>) — читает stdin построчно, на каждый запрос печатает в stdout РОВНО одну строку
|
||||
JSON-RPC 2.0. В stdout НИЧЕГО кроме JSON (включая баннеры/логи) — диагностика только в stderr.
|
||||
|
||||
Методы:
|
||||
- initialize → {"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"memo","version":"0.1.0"}}
|
||||
- notifications/initialized → ответ не отправлять (вернуть null)
|
||||
- tools/list → 3 инструмента: memo_search {path, query, k=8, mode="hybrid"}, memo_status {path}, memo_reindex {path}
|
||||
- tools/call {"name":..., "arguments":{...}} → {"content":[{"type":"text","text":"..."}],"isError":false}
|
||||
Ошибки (нет обязательного аргумента, неизвестный инструмент, исключение) → isError:true + текст, НЕ JSON-RPC error.
|
||||
- неизвестный метод → {"jsonrpc":"2.0","id":<id>,"error":{"code":-32601,"message":"Method not found"}}
|
||||
- уведомление (нет "id" в запросе) → ответ не отправлять
|
||||
|
||||
Реализацию инструментов вынести в функции, тестируемые без процесса:
|
||||
fun toolSearch(path: String, query: String, k: Int, mode: SearchMode): String
|
||||
fun toolStatus(path: String): String
|
||||
fun toolReindex(path: String): String
|
||||
fun handleRequest(line: String): String? // null для уведомлений
|
||||
Тестируемые без процесса функции: handleRequest(line: String): String?, toolSearch(path, query, k, mode): String,
|
||||
toolStatus(path): String, toolReindex(path): String.
|
||||
|
||||
Поведение:
|
||||
- Модель: MEMO_MODEL_DIR, иначе /root/WORK/memo/models/siglip2. Пути к БД — как в CLI
|
||||
(<коллекция>/.memo/index.db); определение коллекций: каталог с *.md на глубине до 2.
|
||||
- toolSearch: поиск через Searcher с refresh-хуком (переиндексация перед поиском).
|
||||
Текст ответа — человекочитаемый список: `<path>:<line> <heading>\n<text>\n` для каждого хита.
|
||||
Пустой результат → строка "ничего не найдено".
|
||||
- toolStatus: по коллекции — `файлов <N>, чанков <M>, индекс <ISO-дата>`.
|
||||
- toolReindex: полная переиндексация: удалить каталог `<коллекция>/.memo` и создать заново, затем
|
||||
indexTree. Вернуть `переиндексировано файлов: <N>`.
|
||||
- memo_reindex через tools/call должен РАБОТАТЬ (не isError).
|
||||
- Неизвестный инструмент → isError true с текстом "unknown tool: <name>".
|
||||
Поведение инструментов:
|
||||
- Коллекции определять ТАК ЖЕ, как CLI: см. memo-cli/src/main/kotlin/memo/cli/Main.kt — переиспользуй ту
|
||||
логику определения корня и коллекций (если это private — сделай в memo-core отдельный файл
|
||||
memo-core/src/main/kotlin/memo/core/Collections.kt с функциями
|
||||
`fun resolveCollection(path: java.io.File): java.io.File` и `fun findCollections(root: java.io.File): List<java.io.File>`,
|
||||
и переключи CLI и MCP на них; CLI при этом должен остаться с зелёными тестами).
|
||||
- toolSearch: Searcher с refresh-хуком (indexTree перед поиском), текст ответа — по строке на хит:
|
||||
`<path>:<line> <heading>` и следом текст чанка; пусто → "ничего не найдено".
|
||||
- toolStatus: `<коллекция>: файлов <N>, чанков <M>, индекс <ISO>`.
|
||||
- toolReindex: удалить `<коллекция>/.memo` целиком и переиндексировать; вернуть `переиндексировано файлов: <N>`.
|
||||
|
||||
## McpProtocolTest.kt — ровно 5 тестов, имена ровно такие
|
||||
|
||||
1. `initializeHandshake` — handleRequest(строка JSON с initialize) → ответ содержит "2024-11-05" и "memo".
|
||||
2. `toolsListHasThreeTools` — ответ на tools/list содержит имена memo_search, memo_status, memo_reindex.
|
||||
1. `initializeHandshake` — ответ содержит "2024-11-05" и "memo".
|
||||
2. `toolsListHasThreeTools` — ответ содержит memo_search, memo_status, memo_reindex.
|
||||
3. `unknownMethodReturns32601` — ответ содержит "-32601".
|
||||
4. `searchWithoutPathIsError` — tools/call memo_search с пустыми arguments → в ответе "isError":true.
|
||||
5. `notificationProducesNoResponse` — handleRequest("{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}") == null.
|
||||
4. `searchWithoutPathIsError` — tools/call memo_search с arguments {} → содержит "isError":true.
|
||||
5. `notificationProducesNoResponse` — handleRequest(уведомление notifications/initialized) == null.
|
||||
|
||||
После: ./gradlew :memo-mcp:test --rerun-tasks — зелёные; ./gradlew :memo-mcp:installDist — собирается;
|
||||
бинарь: memo-mcp/build/install/memo-mcp/bin/memo-mcp
|
||||
После: ./gradlew :memo-mcp:test --rerun-tasks и ./gradlew :memo-mcp:installDist — зелёные.
|
||||
Коммит: git add -A && git commit -m "mcp: сервер memo_search/memo_status/memo_reindex"
|
||||
|
||||
СТРОГИЕ ЗАПРЕТЫ:
|
||||
- Не выводить план текстом; сразу создавай файлы.
|
||||
- Не трогать memo-core, memo-cli, memo-watch.
|
||||
- Ничего не печатать в stdout, кроме строк JSON-RPC.
|
||||
- Не трогать логику memo-core, кроме добавления Collections.kt и переключения CLI на него.
|
||||
- Ничего в stdout, кроме JSON-RPC строк.
|
||||
|
||||
Reference in New Issue
Block a user