Files
Patryk GenschandClaude Opus 5 393bcde6a8 Add MCP server over Streamable HTTP
Seven read-only tools over the catalog database: list_titles, search_scripts,
get_script, get_edition, list_files, find_file and collection_stats. Writing
stays in the CLI, where the effect of a command is visible.

Transport is Streamable HTTP on the JDK's com.sun.net.httpserver, so Gson is
the only new dependency. No sessions are kept: every tool is stateless, so
Mcp-Session-Id is omitted and GET returns 405 rather than opening an SSE
stream we would never write to. Requests carrying a non-loopback Origin are
rejected, since a page in a browser can POST to localhost.

Tool results are formatted text rather than JSON. The consumer is a model
reading the answer, and prose costs less context than the same data wrapped
in objects.

Required arguments are validated against each tool's inputSchema before
dispatch, so a missing parameter is an explicit tool error instead of a
result computed from a default. find_file groups by the canonical lowercase
path, otherwise an extracted directory and its ISO look like two files.

Database access is serialized on one lock because sqlite-jdbc shares a single
connection; the workload is read-only and single-client.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:25:36 +02:00

90 lines
2.9 KiB
Groovy

plugins {
id 'java'
id 'application'
}
group = 'pl.genschu.rexcatalog'
version = '0.1.0-SNAPSHOT'
ext {
gdxVersion = '1.14.2'
}
repositories {
mavenCentral()
}
java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
dependencies {
// Podstawiane przez dependencySubstitution w settings.gradle na lokalny :core.
// Wersja jest nieistotna — nigdy nie trafia do rozwiązywania z repozytorium.
implementation 'pl.genschu:core:0.1-SNAPSHOT'
// core loguje przez statyki Gdx.app, więc potrzebny jest headless backend
implementation "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion"
runtimeOnly "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
implementation 'org.xerial:sqlite-jdbc:3.46.1.3'
// JSON-RPC serwera MCP. Transport stoi na com.sun.net.httpserver z JDK,
// więc poza parserem nie dokładamy nic.
implementation 'com.google.code.gson:gson:2.11.0'
}
application {
mainClass = 'pl.genschu.rexcatalog.Main'
}
tasks.named('run', JavaExec) {
// ./gradlew run --args="ingest /Users/genszu/Reksio"
systemProperty 'file.encoding', 'UTF-8'
// wersja :core trafia do bazy (copy.core_version, detection.core_version,
// artifact.tool_version) i steruje unieważnianiem cache'u pochodnych
systemProperty 'catalog.coreVersion',
providers.gradleProperty('coreVersion').getOrElse('nieznana')
systemProperty 'catalog.data',
providers.gradleProperty('dataDir').getOrElse("${projectDir}/data")
}
/**
* Sprawdza, czy commit przypięty w submodule odpowiada tagowi z gradle.properties.
* Pomijane, gdy build celuje w zewnętrzną kopię roboczą (-PemulatorPath).
*/
tasks.register('verifyCoreVersion') {
group = 'verification'
description = 'Weryfikuje, że vendor/Rex-EMoolator stoi na tagu z coreVersion'
def expected = providers.gradleProperty('coreVersion')
def usingExternal = providers.gradleProperty('emulatorPath').isPresent()
def submodule = file('vendor/Rex-EMoolator')
doLast {
if (usingExternal) {
logger.lifecycle('verifyCoreVersion: pominięte (-PemulatorPath, kopia robocza)')
return
}
def proc = new ProcessBuilder('git', 'describe', '--tags')
.directory(submodule)
.redirectErrorStream(true)
.start()
def actual = proc.inputStream.text.trim()
proc.waitFor()
if (actual != expected.get()) {
throw new GradleException(
"Rozjazd wersji :core — submoduł stoi na '${actual}', " +
"a gradle.properties deklaruje '${expected.get()}'.\n" +
"Albo przestaw submoduł, albo zaktualizuj coreVersion.")
}
logger.lifecycle("verifyCoreVersion: :core = ${actual}")
}
}
tasks.named('check') {
dependsOn 'verifyCoreVersion'
}