@kealthas-dev/opencode-mcp-spring-lsp
v1.1.0
Published
MCP server exposing spring-boot-language-server (Spring-aware LSP) code-intelligence tools - see README.md for design and the config.json-driven config
Downloads
198
Readme
spring-lsp mcp server
An MCP server exposing Spring-aware code-intelligence tools — spring_hover, spring_completion, spring_document_symbols, spring_workspace_symbols, spring_diagnostics, spring_boot_structure — by spawning and driving a real spring-boot-language-server process (VMware/Spring's own LSP implementation, the same one their VS Code "Spring Boot Tools" extension uses) over its native LSP stdio protocol. Written in TypeScript (src/server.ts + src/lsp-client.ts + src/types/config.ts, compiled to dist/ — see Run below). Same hand-rolled-against-@modelcontextprotocol/sdk pattern and type: "remote" deployment shape as mcp-servers/oracle//mcp-servers/loki//mcp-servers/java-lsp/ — see mcp-servers/java-lsp/README.md for the shared design notes (persistent singleton LSP session, stateless-per-request MCP/HTTP layer, lsp-client.ts duplicated between the two packages rather than shared as a dependency).
This exists because generic Java tooling (jdtls, and opencode's own built-in jdtls LSP integration) has no notion of Spring's dependency-injection/annotation-driven wiring — @Autowired interface fields resolve to the interface, not the actual injected bean; @EventListener/@Scheduled/@RequestMapping handler methods show zero references even though the framework calls them via reflection; application.properties/.yml are invisible to it entirely. spring-boot-language-server is Spring's own answer to that gap.
Vendoring
spring-boot-language-server isn't published to Maven Central or any other package registry — confirmed against its own project's FAQ. The only distribution channel is VMware's "Spring Boot Tools" VS Code extension, whose .vsix bundles it. vendor/spring-boot-language-server-*.tar.gz (committed, ~81MB) is that jar plus the ~170-jar lib/ directory its MANIFEST.MF's Class-Path requires alongside it — this is a full embedded Spring Boot 4 application (Tomcat, the real Eclipse JDT core, OpenRewrite, jgit, ...), not a single-file fat jar, so both have to ship together. src/server.ts extracts it automatically into a sibling directory on first run (gitignored — see the root .gitignore); nothing to do manually beyond npm install.
This is, by a wide margin, the largest binary in this repository (existing precedent — the plugins/*/*.tgz tarballs — are ~4KB each; this is ~20,000x that). It permanently adds ~81MB to every future clone, and removing it later would not shrink git history without a rewrite. See fetch-spring-boot-language-server.sh to reproduce or refresh it.
JDK version
spring-boot-language-server itself needs a JDK 21+ runtime — confirmed directly from the vendored 2.5.0-SNAPSHOT build's own MANIFEST.MF (Java-Version: 21), not from older STS4 docs (which say 11+ for older releases — this build has moved past that). Set JAVA_EXECUTABLE if the java on PATH isn't 21+; this is separate from whatever JDK your actual Spring Boot project targets.
Configuration
Config is file-based, not env-var-based — same two-file split as mcp-servers/oracle/ (see its README's Configuration section for the fullest writeup of the pattern):
$HOME/.config/kealthas-dev/opencode-mcp-spring-lsp/server.json— the port to listen on.SPRING_LSP_MCP_PORTenv var overrides it, for running more than one instance (one per project, say). Otherwise optional: if missing, defaults to8093; if present, must be valid JSON or the server refuses to start. Shape (seeserver.example.json):{ "SPRING_LSP_MCP_PORT": 8093 }- A config file, read once at startup (unlike
mcp-servers/oracle/mcp-servers/loki, not re-read per call - the LSP session is stateful and tied to one workspace, so switching config means restarting the process). The location it's read from is never user-supplied — only a short environment/project name is, viaSPRING_LSP_CONFIG_ENV; seemcp-servers/oracle/README.md's Configuration section for why. With noSPRING_LSP_CONFIG_ENVset, it's read fromconfig.json; withSPRING_LSP_CONFIG_ENV=my-project, fromconfig-my-project.jsoninstead. Required (one file or the other must exist) — the server prints a sample and exits if the resolved file doesn't exist orSPRING_LSP_WORKSPACE_ROOTis missing from it. Shape (seeconfig.example.json):{ "SPRING_LSP_WORKSPACE_ROOT": "/path/to/your/spring-boot/project", "JAVA_EXECUTABLE": "/path/to/jdk21/bin/java" }SPRING_LSP_WORKSPACE_ROOT— absolute path to the Spring Boot project to analyze.JAVA_EXECUTABLE— optional, see "JDK version" above; defaults to whateverjavaresolves to onPATH.
Run
See docs/java-lsp-spring-lsp-quickstart.zh.md for a bare-minimum copy-paste version of the local-dev path below.
Published as @kealthas-dev/opencode-mcp-spring-lsp (including the vendored tarball above — a global install is fully self-contained) — on a real deployment, install it globally and run the resulting binary:
npm install -g @kealthas-dev/opencode-mcp-spring-lsp
mkdir -p ~/.config/kealthas-dev/opencode-mcp-spring-lsp
# real config at ~/.config/kealthas-dev/opencode-mcp-spring-lsp/config-my-project.json (see config.example.json for the shape)
SPRING_LSP_CONFIG_ENV=my-project opencode-mcp-spring-lspFor local dev/testing against this repo's own checkout (this directory, not the published package), same idea — drop a real config file at the default location, or a named config-<name>.json (see config.example.json for the shape):
npm install
npm run build
npm start # reads ~/.config/kealthas-dev/opencode-mcp-spring-lsp/config.jsonnpm run dev runs src/server.ts directly via tsx watch instead, for a compile-on-save loop.
Either way, point opencode at it with a type: "remote" entry (see deploy/opencode.json.example).
Status
Protocol plumbing verified end-to-end against the real, vendored spring-boot-language-server 2.5.0-SNAPSHOT — both manually and by spring-lsp.test.ts: the initialize handshake succeeds; the auto-extraction of the vendored tarball works; .java/.properties files can be opened and synced; spring_boot_structure's real sts/spring-boot/structure custom command round-trips cleanly; spring_diagnostics/spring_completion on a .properties file return cleanly without crashing the server. The fuller client-capabilities object in src/lsp-client.ts is required for this server; keep the defaultClientCapabilities() comment if editing it.
Not verified: actual Spring-aware semantic richness. Every tool call in testing was run against a bare loose .java file + application.properties with no real Maven/Gradle project and no resolved spring-boot-starter-* dependencies — against that fixture, every one of this server's own richer results (spring_hover/spring_completion finding real config properties, spring_boot_structure finding real beans, even plain spring_document_symbols) comes back an empty array, not an error. Two known reasons:
- No real Spring Boot dependencies on the classpath —
spring_hover/spring_completion's config-property awareness comes from the project's own resolvedspring-configuration-metadata.json(inside its actualspring-boot-starter-*jars). A fixture project with no such dependencies has none to offer. - This server expects a paired jdtls providing classpath/project info via a "classpath listener" mechanism, which VS Code's Java extension pack wires up between its
redhat.java(jdtls) andvmware.vscode-spring-bootextensions. Standalone,SpringSymbolIndex/JdtLsProjectCachetime out waiting for that listener (visible directly in this server's own stderr logs:TimeoutException ... at SpringSymbolIndex.getDocumentSymbolsFromMetamodelIndex) and degrade to empty results rather than erroring. This pairing is not implemented in this package —mcp-servers/java-lsp's separate jdtls process and this one currently run fully independently, each unaware of the other. Wiring them together (sospring_*tools get real classpath-aware results) is real follow-up work, not attempted here — seemcp-servers/TODO.md.
Not yet wired into tests/run-in-container.sh / the docker/ sandbox — same reason as mcp-servers/java-lsp (no JDK in the sandbox's base image; see that package's README). Run spring-lsp.test.ts directly on a machine with a JDK 21+ java for now.
To actually see this server's Spring-specific value, point SPRING_LSP_WORKSPACE_ROOT at a real Maven/Gradle Spring Boot project with its dependencies already resolved (mvn dependency:resolve / a completed Gradle sync) — not attempted here, and the classpath-listener gap above may still limit results even then.
