Skip to main content

Integration

ScalaSemantic is an MCP stdio server — the MCP client spawns it as a process and owns its lifecycle. Integrating means two things: make the target project emit SemanticDB, and register a launch command for that project root.

The server speaks newline-delimited JSON-RPC 2.0 on stdout. Diagnostic logging is off by default and, when enabled, goes to a file — never to stdout. See Logging.

Prerequisite: SemanticDB on the target project

The server reads SemanticDB; it does not generate it. The project must be compiled with SemanticDB enabled:

sbt (also done automatically by the auto-download script's setup):

// build.sbt
semanticdbEnabled := true

The setup script also creates scala-semantic.sbt with small scalaSemanticWriteClasspath and scalaSemanticWriteModules tasks. They read build modules and Compile / fullClasspath, write .scala-semantic/classpath-sbt.json plus .scala-semantic/modules-sbt.json, and should be run after dependency or module configuration changes:

sbt scalaSemanticWriteClasspath

Mill — Mill's def semanticDbEnabled = true only feeds the on-demand semanticDbData target; a plain mill __.compile emits no *.semanticdb, so that flag alone leaves ScalaSemantic with an empty index. Instead make the normal compile emit it via the compiler flag, in each ScalaModule (-sourceroot must be the build root, not the module dir, so multi-module source paths stay unique):

// build.mill / build.sc
def scalacOptions = super.scalacOptions() ++
Seq("-Xsemanticdb", "-sourceroot", build.moduleDir.toString) // build.sc: build.millSourcePath

(Alternatively, keep def semanticDbEnabled = true and run mill __.semanticDbData instead of mill __.compile — the Mill-native target that materializes the files under out/.)

For live-buffer typechecking, write .scala-semantic/classpath-mill.json and .scala-semantic/modules-mill.json from the build. This repo ships compact Mill examples as root tasks scalaSemanticWriteClasspath and scalaSemanticWriteModules; run the classpath task after dependency or module configuration changes:

./mill scalaSemanticWriteClasspath

Gradle — no native flag; pass the compiler option directly via the Scala plugin's compile task. Scala 3:

tasks.withType(ScalaCompile) {
scalaCompileOptions.additionalParameters = ["-Ysemanticdb", "-sourceroot", projectDir.toString()]
}

Scala 2.13 needs the semanticdb-scalac compiler plugin jar instead of a native flag:

scalaCompilerPlugins "org.scalameta:semanticdb-scalac_2.13.16:4.13.9"
tasks.withType(ScalaCompile) {
scalaCompileOptions.additionalParameters = ["-Yrangepos", "-P:semanticdb:sourceroot:${projectDir}"]
}

Plain scalac — Scala 3:

scalac -Ysemanticdb -sourceroot . <sources...>

Scala 2.13 (resolve the plugin jar with coursier first):

scalac -Xplugin:/path/to/semanticdb-scalac_2.13.16-4.13.9.jar -Yrangepos -P:semanticdb:sourceroot:. <sources...>

Whatever the build tool, the only machine requirement to run the server is a JVM (java on PATH).

Each release publishes both a self-contained fat jar attached to the GitHub Release and the same server as regular Maven Central artifacts (io.github.mercurievv::scalasemantic-mcp and friends). Options A and C run the fat jar; option B resolves the Maven Central artifact directly via scala-cli/coursier.

Three ways to launch

A — Scala CLI remote scriptB — auto-download scriptC — plain java -jar
Get the jarscala-cli/coursier resolve + cache the published artifactscript downloads + cachesyou download once
Write client configscala-cli ... setupby handby hand
Enable SemanticDBscript creates sbt config if missingyou add one lineyou add one line
Stays up to dateyes (latest.release)yesmanual
Works withany build tool with Scala CLI installedany build toolany build tool

Option A — Scala CLI remote script

If scala-cli is already installed, one command can configure a project without installing a separate launcher:

scala-cli https://raw.githubusercontent.com/MercurieVV/ScalaSemantic/master/scripts/scalasemantic-mcp.scala -- setup --client claude

Use --client codex, gemini, cline, roo, continue, antigravity, generic-json, or all. The script writes/merges the MCP client config, creates SCALA_SEMANTIC_RULES.md, creates a small scala-semantic.sbt file with semanticdbEnabled := true when it finds an sbt project that does not already configure SemanticDB, and registers this command:

scala-cli run --dependency "io.github.mercurievv::scalasemantic-mcp:latest.release" \
--main-class com.github.mercurievv.scalasemantic.mcpServer \
-- .

The MCP client runs that command over stdio. Note this does not invoke the setup script itself — the generated config skips it entirely so that every server launch (which happens far more often than setup, e.g. on every editor restart) is just a coursier-cached jar load: no network fetch, no recompile. The dependency version is latest.release, a coursier magic version that always re-resolves to the newest published release.

To pin a specific version instead of latest.release, edit ServerDependency in a local copy of the script and re-run setup.

Worktrees and cwd changes

Generated configs use . as the server root so a newly spawned MCP server indexes the directory it was launched from, not the directory where setup originally ran. The server also discovers .scala-semantic/classpath-*.json from that active root, follows .scala-semantic/modules-*.json to child source and output directories, and falls back to visible submodule scanning when no direct or module-guided metadata exists. Some stdio MCP clients keep the same server process alive when the agent later changes cwd or enters a git worktree, and do not reliably send root-change notifications. After such a cwd change, call set_workspace_root with the new absolute path before other ScalaSemantic tools; use get_workspace_root to confirm the current state and discovered classpath metadata.

Option B — auto-download launcher

curl -fsSL https://raw.githubusercontent.com/MercurieVV/ScalaSemantic/master/scripts/install.sh | sh

Installs the launcher to ~/.local/bin/scalasemantic-mcp. It downloads and caches the fat jar from GitHub Releases (uses coursier if available). Pin a version with SCALASEMANTIC_VERSION=vX.Y.Z.

Then register manually in your client config:

{
"mcpServers": {
"scala-semantic": {
"command": "~/.local/bin/scalasemantic-mcp",
"args": [
"serve",
"."
]
}
}
}

Option C — plain java -jar

Download scalasemantic-mcp.jar from the latest release:

{
"mcpServers": {
"scala-semantic": {
"command": "java",
"args": ["-jar", "/abs/path/to/scalasemantic-mcp.jar", "."]
}
}
}

Do not use runMain (sbt or Mill) — it writes build logs to stdout and corrupts the JSON-RPC stream. To build the jar locally: ./mill mcp.assembly.

Logging

Silent by default. Enable with flags appended to args (after the project root), or matching env vars:

FlagEnv varWhat it logs
--logSCALASEMANTIC_LOG=1startup line + one line per tool call
--log-outputSCALASEMANTIC_LOG_OUTPUT=1also logs each JSON-RPC response

Log file defaults to <root>/scala-semantic-mcp.log. Override with SCALASEMANTIC_LOG_FILE. Lines are timestamped and flushed — tail -f shows them live.

Manual stdio check

printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"find_symbol","arguments":{"query":"Animal"}}}' \
'{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"class_hierarchy","arguments":{"symbol":"com/github/mercurievv/scalasemantic/fixtures/Animal#"}}}' \
| java -jar scalasemantic-mcp.jar .

Expect four JSON-RPC responses on stdout. The initialize response carries an instructions field; find_symbol turns "Animal" into the symbol string that class_hierarchy then uses.

Next, use the Tool reference for the full tool list and Tool Examples for worked requests.

Classpath Metadata & Migration from Flat Classpath

In previous versions, a single flat, colon-separated classpath file was passed to the server. The server now discovers module-aware JSON classpath metadata by default from .scala-semantic/classpath-sbt.json, .scala-semantic/classpath-mill.json, or .scala-semantic/classpath-scala-cli.json under the active workspace root. It also follows .scala-semantic/modules-sbt.json, .scala-semantic/modules-mill.json, .scala-semantic/modules-scala-cli.json, or .scala-semantic/modules.json to discover child module metadata in source and output directories. If no direct or module-guided metadata exists, it scans non-hidden subdirectories for submodule metadata, including <submoduleOutDir>/.scala-semantic/classpath.json in visible build output directories. You can still pass an explicit classpath file as the optional second serve argument, or set SCALASEMANTIC_CLASSPATH, to override discovery.

Automatic Migration

The setup command (via option A/B) automatically detects the build tool and generates the correct .scala-semantic/classpath-<tool>.json file, plus .scala-semantic/modules-<tool>.json when the build integration can expose module topology. It also configures the build tool (e.g., creating scala-semantic.sbt for sbt) to maintain metadata freshness automatically. Generated MCP client configs no longer pass this file path; the server finds it from the current workspace root.

Troubleshooting Classpath Freshness

If you import your project and live-buffer typechecking is not working (e.g., you see unresolved types or imports for new code):

  1. For sbt projects: Starting sbt or reloading the build will automatically trigger classpath generation via the onLoad hook. You can also run the task manually:
    sbt scalaSemanticWriteClasspath
  2. For Mill projects: Compiling the project (mill __.compile or via BSP/IDE build import) automatically updates each module's classpath metadata. You can also run the command manually:
    ./mill scalaSemanticWriteClasspath
  3. For Scala CLI projects: Re-run the setup command to regenerate the classpath metadata:
    scala-cli https://raw.githubusercontent.com/MercurieVV/ScalaSemantic/master/scripts/scalasemantic-mcp.scala -- setup --client <your-client>