java-spring-mcp
Provides Maven Central artifact metadata via Sonatype's search API, including newest matching versions, version lists, central.sonatype.com page links, and javadoc.io links for Java dependencies.
Provides access to the official Spring Boot reference documentation, allowing AI agents to resolve and search Spring Boot sections (such as using, features, actuator, security, testing, web, data, io, messaging, packaging, and index) and retrieve the relevant documentation as clean markdown.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@java-spring-mcplook up the javadoc for java.net.http.HttpClient"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
java-spring-mcp
An MCP (Model Context Protocol) server that gives AI coding agents live JDK javadoc, Spring Boot reference documentation and Maven Central metadata instead of whatever the model happens to remember. Pages are scraped from the official sites at request time, cached locally, and returned as clean markdown — so generated Java and Spring code targets real, current APIs rather than deprecated or invented ones.
JDK classes — scraped live from docs.oracle.com (all 41 modules:
java.base,java.sql,java.desktop,java.net.http, …), ~4,700 classes with their module recorded, so a bareHttpClientresolves tojava.net.httpand not to a guessSpring Boot documentation — a first-class layer over the official Spring Boot reference (all 11 sections:
using,features,actuator,security,testing,web,data,io,messaging,packaging,index), the Getting Started guides, Spring Initializr and the release notes. It searches section titles and the headings inside each page, which is what a topic query such asdata jpaactually needs.Maven Central — artifact metadata from the official search API plus a javadoc.io link
Local search index over every JDK class plus the Spring sections, rebuilt at most every 7 days, with a stale fallback when the network fails
SQLite TTL cache so repeated lookups are instant
Politeness layer — robots.txt (RFC 9309), per-host throttle incl.
Crawl-delay,Retry-After, conditional GET and request budgets (details)
The eleven tools
Java tools:
Tool | What it does |
| Resolve one identifier ( |
| Ranked name search over the local index of all JDK classes and Spring Boot reference sections. |
| Maven Central artifact metadata: newest matching version, the version list, the central.sonatype.com page and a javadoc.io link. |
| Real health check: index size/age, cache stats, live probes of docs.oracle.com, docs.spring.io and search.maven.org, and politeness counters. |
| Server liveness and version. |
Spring tools (added for the Spring documentation layer — see Replacing @enokdev/springdocs-mcp):
Tool | What it does |
| Ranked hits over the reference manual's nav titles and in-page headings, each hit with a quoted excerpt. Answers |
| One reference section or subsection as clean markdown, with |
| List the spring.io guides catalogue, or fetch one guide as markdown. |
| Spring Initializr metadata: the Boot and Java versions it offers, project types, and the dependency catalogue (filterable). |
| A need ("jpa", "oauth2 client") → the starter coordinates Spring Initializr itself generates, with paste-ready Maven and Gradle blocks. |
| Release / migration notes for a Spring project, grouped into breaking changes, new features, deprecations and the rest. |
Every tool returns a plain dict. Failures come back as {"error": …, "suggestion": …} — a bad lookup never raises into the MCP layer, and no tool delegates to another tool.
Related MCP server: linked-docs
Requirements
Python 3.10 or newer (developed and tested on 3.12)
uvfor the one-command install below (uvxships with it)The MCP Python SDK 1.x is pinned (
mcp>=1.2.0,<2.0); this package does not support the mcp 2.x rename ofFastMCP
Install & run
One command — no checkout, no token
uvx --from git+https://github.com/KEEPEE/java-spring-mcp.git java-spring-docsThis builds the package in an isolated environment and starts the stdio MCP server. It prints nothing on purpose: stdout is the protocol channel. Stop it with Ctrl-C.
After the repository is updated, force uv to re-resolve the commit:
uvx --refresh --from git+https://github.com/KEEPEE/java-spring-mcp.git java-spring-docsLocal checkout — fastest startup, editable while developing
git clone https://github.com/KEEPEE/java-spring-mcp.git
cd java-spring-mcp
python3 -m venv .venv
.venv/bin/pip install -e .
.venv/bin/java-spring-docs # stdio MCP serverMCP client configuration
Generic stdio client (Claude Desktop, Cursor, Cline, …)
{
"mcpServers": {
"java-spring": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/KEEPEE/java-spring-mcp.git",
"java-spring-docs"
]
}
}
}With an explicit cache directory (any env you set is passed straight through to the server):
{
"mcpServers": {
"java-spring": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/KEEPEE/java-spring-mcp.git",
"java-spring-docs"
],
"env": {
"JAVA_SPRING_MCP_CACHE_DIR": "/tmp/java-spring-cache"
}
}
}
}DeepSeek Harness (cordis-style plugin list)
- id: mcp-java-spring
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: java-spring
transport: stdio
command: uvx
args:
[
'--from',
'git+https://github.com/KEEPEE/java-spring-mcp.git',
'java-spring-docs'
]To skip the build at client start, point command at the console script of a checkout instead — command: /path/to/java-spring-mcp/.venv/bin/java-spring-docs with args: [].
Tools reference
java_docs(identifier, topic=None, max_tokens=8000)
Resolves an identifier to one documentation page. Identifier forms, tried in this order:
Form | Meaning |
| Spring Boot reference section ( |
| fully-qualified class; the module is read from the search index, falling back to |
| exact-name lookup in the local index (a class beats an interface on ambiguity), then fetch |
topic keeps only the section whose heading contains it (methods, method summary, fields, constructors, …) plus the page title and description; if nothing matches, the full page is returned with a note listing the available headings. max_tokens is a rough budget (1 token ≈ 4 characters): the markdown is cut at a line boundary and truncated is set.
Returns {"type", "identifier", "url", "title", "content", "truncated", "cached"}, plus an optional note. type is jdk_class or spring_section.
// java_docs({ "identifier": "java.net.http.HttpClient", "max_tokens": 250 })
// a bare name would also work: "HttpClient" resolves to this same module
{
"type": "jdk_class",
"identifier": "java.net.http.HttpClient",
"url": "https://docs.oracle.com/en/java/javase/26/docs/api/java.net.http/java/net/http/HttpClient.html",
"content": "# Class HttpClient\n\n```java\npublic abstract class HttpClient extends Object implements AutoCloseable\n```\n\nAn HTTP Client.\n\nAn `HttpClient` can be used to send requests and retrieve their responses … [truncated: showing ~243 of ~9883 estimated tokens]",
"truncated": true,
"cached": false,
"title": "Class HttpClient"
}// java_docs({ "identifier": "spring:using", "max_tokens": 250 })
{
"type": "spring_section",
"identifier": "spring:using",
"url": "https://docs.spring.io/spring-boot/reference/using/index.html",
"content": "# Developing with Spring Boot\n\nThis section goes into more detail about how you should use Spring Boot. … ## Contents\n\n- [Build Systems](…)\n- [Structuring Your Code](…) … [truncated: showing ~226 of ~429 estimated tokens]",
"truncated": true,
"cached": false,
"title": "Developing with Spring Boot"
}A topic that matches nothing tells you what the page does have:
// java_docs({ "identifier": "java.util.List", "topic": "nonexistent-section", "max_tokens": 120 })
{
"type": "jdk_class",
"identifier": "java.util.List",
"url": "https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/List.html",
"content": "# Interface List<E>\n\n```java\npublic interface List<E> extends SequencedCollection<E>\n``` … [truncated: showing ~113 of ~1040 estimated tokens]",
"truncated": true,
"cached": true,
"title": "Interface List<E>",
"note": "no section matching 'nonexistent-section'; available headings: Interface List<E>, Unmodifiable Lists, Method Summary, Methods declared in interface Collection, Methods declared in interface Iterable, …"
}A name that exists nowhere is reported honestly — Oracle answers a wrong class with a redirect, not a 404, so the request budget is what stops the search (see budgets):
// java_docs({ "identifier": "com.example.NopeXYZ" })
{
"error": "could not fetch javadoc for 'com.example.NopeXYZ': request budget exhausted for scope 'tool:java_docs'",
"suggestion": "the per-call request budget (6) ran out while guessing modules; try java_search('com.example.NopeXYZ') to find the right package/module"
}java_search(query, limit=8)
Ranks the local index by name similarity (exact > prefix > substring > fuzzy). Returns {"query", "count", "results": [{name, package, module, kind, url, score}], "index_stale"}. kind is class, interface, enum, record, annotation or spring_section.
// java_search({ "query": "http client", "limit": 3 })
{
"query": "http client",
"count": 3,
"results": [
{ "name": "HttpClient", "package": "java.net.http", "module": "java.net.http", "kind": "class", "url": "https://docs.oracle.com/en/java/javase/26/docs/api/java.net.http/java/net/http/HttpClient.html", "score": 14.0 },
{ "name": "HttpClient.Builder", "package": "java.net.http", "module": "java.net.http", "kind": "interface", "url": "https://docs.oracle.com/en/java/javase/26/docs/api/java.net.http/java/net/http/HttpClient.Builder.html", "score": 14.0 },
{ "name": "HttpClient.Redirect","package": "java.net.http", "module": "java.net.http", "kind": "enum", "url": "https://docs.oracle.com/en/java/javase/26/docs/api/java.net.http/java/net/http/HttpClient.Redirect.html", "score": 14.0 }
],
"index_stale": false
}Spring sections are ranked in the same list, so one search covers both halves of the index:
// java_search({ "query": "spring using", "limit": 3 })
{
"query": "spring using",
"count": 3,
"results": [
{ "name": "Spring Boot: Using", "package": "using", "module": "spring-boot", "kind": "spring_section", "url": "https://docs.spring.io/spring-boot/reference/using/index.html", "score": 14.0 },
{ "name": "Spring", "package": "javax.swing","module": "java.desktop","kind": "class", "url": "https://docs.oracle.com/en/java/javase/26/docs/api/java.desktop/javax/swing/Spring.html", "score": 12.909 },
{ "name": "SpringLayout", "package": "javax.swing","module": "java.desktop","kind": "class", "url": "https://docs.oracle.com/en/java/javase/26/docs/api/java.desktop/javax/swing/SpringLayout.html", "score": 9.882 }
],
"index_stale": false
}maven_package(artifact_id, group_id=None, version=None, max_tokens=6000)
Maven Central metadata. artifact_id is the artifact to look up; group_id pins the group when several projects share an artifact name; version selects one release from the result list. Returns {"group_id", "artifact_id", "version", "versions", "url", "javadoc_url", "cached"} — versions is the newest 20 coordinates, url the central.sonatype.com page and javadoc_url the javadoc.io page. max_tokens is reserved for future javadoc content fetching and currently has no effect.
// maven_package({ "artifact_id": "guava", "group_id": "com.google.guava" })
{
"group_id": "com.google.guava",
"artifact_id": "guava",
"version": "33.4.8-jre",
"versions": ["33.4.8-jre", "33.4.8-android", "33.4.7-jre", "33.4.7-android", "33.4.6-jre", "… 20 entries in total"],
"url": "https://central.sonatype.com/artifact/com.google.guava/guava",
"javadoc_url": "https://www.javadoc.io/doc/com.google.guava/guava",
"cached": false
}Without group_id the search API ranks by relevance, so a bare spring-boot legitimately resolves to whichever project Maven Central scores highest — pass the group when you mean a specific one:
// maven_package({ "artifact_id": "spring-boot" })
{ "group_id": "org.apache.camel.springboot", "artifact_id": "spring-boot", "version": "4.20.0", "…": "…" }An unknown coordinate is an error dict, not an empty result:
// maven_package({ "artifact_id": "nope-not-a-real-artifact-xyz" })
{
"error": "maven lookup failed: artifact not found on Maven Central",
"suggestion": "No results for 'nope-not-a-real-artifact-xyz'. Verify the coordinates, or retry without a group id: maven_package('nope-not-a-real-artifact-xyz')"
}java_status()
Probes docs.oracle.com, docs.spring.io and search.maven.org with a light GET (10 s timeout) and reports the index and cache state. overall is ok, degraded or error. The politeness block is diagnostics only and never changes overall.
// java_status()
{
"server": "java-spring-mcp",
"version": "0.2.0",
"checks": {
"search_index": { "status": "ok", "entries": 4716, "built_at": "2026-10-07T06:52:16.422162+00:00", "stale": false },
"cache": { "status": "ok", "entries": 9, "expired": 0 },
"docs_oracle_com": { "status": "ok", "http_status": 200 },
"docs_spring_io": { "status": "ok", "http_status": 200 },
"search_maven_org":{ "status": "ok", "http_status": 200 }
},
"overall": "ok",
"politeness": {
"status": "ok", "disabled": false,
"requests": 15, "robots_requests": 3, "robots_rows": 3, "robots_fetches": 3, "robots_cache_hits": 13,
"blocked_by_robots": 0, "throttle_waits": 9, "throttle_sleep_s": 3.338,
"host_delays": { "docs.oracle.com": 0.0, "docs.spring.io": 0.0, "search.maven.org": 0.0 },
"budgets": { "tool:java_docs": [6, 6], "tool:maven_package": [1, 6] },
"budget_denied": 1, "conditional": 0, "conditional_skipped": 0, "revalidated_304": 0,
"redirect_hops": 3, "retries_429": 0, "retries_transport": 0, "stalls": 0, "errors": 0
}
}health_check()
{"status": "ok", "server": "java-spring-mcp", "version": "0.2.0"} — no network, no cache; safe as a liveness probe.
Spring tools reference
These six tools are the Spring documentation layer. They read four hosts — docs.spring.io (reference manual), spring.io (guides), start.spring.io (Initializr) and api.github.com (release notes) — and every one of them is budgeted, throttled and cached like the Java tools.
spring_search_concepts(query, version=None, limit=8, max_tokens=4000)
The question a Spring agent actually asks is by topic, not by page: "how do profiles work?", "what does the actuator give me?", "JPA with Spring Boot?". This tool ranks the reference manual's navigation titles (296 pages, read from the section index) and the headings inside the pages themselves, and quotes the text under each hit.
Ranking is the same ladder as java_search (exact word > prefix > substring, every query word must match), weighted by field (title > slug > section > parent) with a bonus for reference pages over how-to pages. When the titles cannot answer the query, the tool fetches up to HEADING_PAGE_LIMIT (4) candidate pages, harvests their h2–h4 headings plus a short excerpt for each, and adds those as hits. Harvested headings are cached, so the same query a second time costs zero requests.
Returns {"query", "hit_count", "hits", "index", "version", "docs_base", "pages_read", "headings_harvested", "truncated"}. Each hit is {title, url, section, version, score, matched_on, excerpt, excerpt_source, nav_depth, is_reference_page} (+ heading_level and page_title for a heading hit). matched_on is title, path or heading.
// spring_search_concepts({ "query": "auto-configuration", "max_tokens": 400 }) // live, cold: 3 requests
{
"query": "auto-configuration",
"hit_count": 5,
"hits": [
{ "title": "Auto-configuration", "url": "https://docs.spring.io/spring-boot/reference/using/auto-configuration.html", "section": "using", "version": "4.1.1", "score": 6.9, "matched_on": "title", "excerpt": "Auto-configuration is non-invasive. At any point, you can start to define your own configuration to replace specific parts of the auto-configuration. For example, if you add your own DataSource bean, the default embedded database support…", "excerpt_source": "page", "nav_depth": 3, "is_reference_page": true },
{ "title": "Creating Your Own Auto-configuration", "url": "https://docs.spring.io/spring-boot/reference/features/developing-auto-configuration.html", "section": "features", "version": "4.1.1", "score": 6.9, "matched_on": "title", "excerpt": "Classes that implement auto-configuration are annotated with @AutoConfiguration . This annotation itself is meta-annotated with @Configuration …", "excerpt_source": "page", "nav_depth": 3, "is_reference_page": true },
{ "title": "Auto-configuration Packages", "url": "https://docs.spring.io/spring-boot/reference/using/auto-configuration.html#using.auto-configuration.packages", "section": "using", "version": "4.1.1", "score": 6.9, "matched_on": "heading", "excerpt": "Auto-configuration packages are the packages that various auto-configured features look in by default when scanning for things such as entities and Spring Data repositories. …", "excerpt_source": "page", "nav_depth": 3, "is_reference_page": true, "heading_level": "h2", "page_title": "Auto-configuration" }
],
"index": { "cached": false },
"version": "4.1.1",
"docs_base": "https://docs.spring.io/spring-boot/reference",
"pages_read": 2,
"headings_harvested": 21,
"truncated": false
}Measured live for the six topics that the third-party Spring MCP server could not answer: auto-configuration 5 hits (2 pages read, 21 headings harvested), profiles 4, actuator 5, web 5, data jpa 5, security 5 — and the identical second call of each returns the same hits with pages_read: 0 and no requests at all (0.01 s).
data jpa is the case that proves the heading pass is needed: no page is called "data jpa", so the answer comes from a heading — JPA and Spring Data JPA at reference/data/sql.html#data.sql.jpa-and-spring-data.
spring_reference(section, subsection=None, version=None, max_tokens=8000)
One reference page as clean markdown, with the same topic filtering as java_docs. section is a reference section (web, data, using, actuator, …); subsection is a page inside it. Hyphens and spaces are the same thing, so ("web", "graceful shutdown") resolves to reference/web/graceful-shutdown.html.
version selects the docs tree: None → https://docs.spring.io/spring-boot/reference, "4.1" or "4.1.1" → https://docs.spring.io/spring-boot/4.1.1/reference. A value that is not major.minor[.patch] is rejected rather than guessed at.
Returns the same payload as java_docs (type: "spring-reference") plus version, headings (the page's own outline) and alternatives when several pages matched.
// spring_reference({ "section": "data", "subsection": "sql", "max_tokens": 200 }) // live
{
"type": "spring-reference",
"identifier": "data:sql",
"url": "https://docs.spring.io/spring-boot/reference/data/sql.html",
"title": "SQL Databases",
"content": "# SQL Databases\n\nThe [Spring Framework](https://spring.io/projects/spring-framework) provides extensive support for work… [truncated: showing ~127 of ~11614 estimated tokens]",
"truncated": true,
"cached": false,
"version": "4.1.1",
"headings": ["h2 Configure a DataSource", "h3 Embedded Database Support", "h3 Connection to a Production Database", "h2 JPA and Spring Data JPA", "…"]
}When the subsection names a heading rather than a page, the tool says so instead of pretending:
// spring_reference({ "section": "web", "subsection": "profiles" }) // live
{
"…": "…",
"url": "https://docs.spring.io/spring-boot/reference/features/profiles.html",
"note": "'profiles' is not a page under 'web'; it lives at /spring-boot/reference/features/profiles.html (matched by page name)"
}An unknown section lists what does exist instead of returning nothing:
// spring_reference({ "section": "nope" }) // live
{
"error": "unknown Spring documentation section: no Spring Boot documentation section named 'nope'",
"suggestion": "spring_search_concepts('<topic>') lists the sections that exist",
"candidates": [ { "title": "Developing with Spring Boot", "url": "https://docs.spring.io/spring-boot/reference/using/index.html" }, "… 12 section landing pages …" ]
}spring_guides(guide=None, topic=None, max_tokens=8000)
No arguments (or topic=) lists the guides catalogue; guide= fetches one guide as markdown. The catalogue is read from https://spring.io/page-data/guides/page-data.json, because https://spring.io/guides itself is a client-rendered page: fetched over HTTP it answers 308 → /guides/ and then contains zero links. If page-data ever stops working, the tool falls back to https://spring.io/sitemap-index.xml (allowed by spring.io's robots.txt) and says so in a note, because a sitemap gives URLs only and titles have to be derived from the slug. guide_count is the number of guides actually returned, so it drops with max_tokens (68 at 8000, 58 at 4000, 22 at 1500).
// spring_guides({}) // live: 68 guides, 1 request (cold); 0 requests warm
{
"type": "spring-guide-catalogue",
"source": "page-data",
"url": "https://spring.io/page-data/guides/page-data.json",
"guide_count": 68,
"guides": [
{ "title": "Messaging with Redis", "url": "https://spring.io/guides/gs/messaging-redis/", "slug": "messaging-redis", "type": "getting-started", "category": ["Spring Data", "Redis"], "description": "Learn how to use Redis as a message broker." },
"…"
],
"truncated": true,
"cached": false
}guide= accepts a slug (spring-boot), a kind+slug (gs/spring-boot, topicals/spring-boot-3), a path (/guides/gs/spring-boot/) or a full URL — all resolve to the same canonical guide URL. The markdown keeps the guide body and drops the site chrome (nav, footer, social links, "report an issue").
spring_initializr(section="options", query=None, max_tokens=6000)
Spring Initializr metadata, read from https://start.spring.io/ with Accept: application/vnd.initializr.v2.1+json. This is the only place that says which Boot versions a project can actually be built with today.
// spring_initializr({ "section": "options" }) // live
{
"type": "initializr-options",
"source": "start.spring.io",
"url": "https://start.spring.io/",
"boot_version_default": "4.1.1.RELEASE",
"boot_version_latest_released": "4.1.1.RELEASE",
"boot_versions": {
"default": "4.1.1.RELEASE",
"latest_released": "4.1.1.RELEASE",
"values": [
{ "id": "4.2.0.BUILD-SNAPSHOT", "name": "4.2.0 (SNAPSHOT)" },
{ "id": "4.2.0.M2", "name": "4.2.0 (M2)" },
{ "id": "4.1.2.BUILD-SNAPSHOT", "name": "4.1.2 (SNAPSHOT)" },
{ "id": "4.1.1.RELEASE", "name": "4.1.1" },
"…"
]
},
"java_versions": { "default": "17", "values": [ { "id": "27", "name": "27" }, { "id": "25", "name": "25" }, { "id": "21", "name": "21" }, { "id": "17", "name": "17" } ] },
"project_types": { "default": "gradle-project", "values": [ { "id": "gradle-project", "name": "Gradle - Groovy" }, "…" ] },
"languages": { "default": "java", "values": [ { "id": "java", "name": "Java" }, { "id": "kotlin", "name": "Kotlin" }, "…" ] },
"packagings": { "default": "jar", "values": [ { "id": "jar", "name": "Jar" }, { "id": "war", "name": "War" } ] },
"defaults": { "groupId": "com.example", "artifactId": "demo", "version": "0.0.1-SNAPSHOT", "packageName": "com.example.demo" },
"dependency_groups": [ { "name": "Developer Tools", "count": 7 }, { "name": "Web", "count": 18 }, { "name": "SQL", "count": 19 }, "…" ],
"dependency_count": 204,
"cached": false
}section="dependencies" returns the catalogue (204 entries), optionally filtered: spring_initializr("dependencies", query="jpa") → data-jpa / Spring Data JPA / group SQL, with the reference_url pointing at the exact docs anchor for it.
spring_dependency(need, build="both", boot_version=None, max_tokens=4000)
Turns a need into coordinates. It never invents an artifact: the ids it recommends come from the Initializr catalogue, and the coordinates it prints are the ones Initializr's own build endpoints generate for those ids (https://start.spring.io/pom.xml?type=maven-build&dependencies=…&bootVersion=… and the /build.gradle twin).
That distinction matters. On Boot 4.1 the catalogue id web generates spring-boot-starter-webmvc — not the spring-boot-starter-web that a model would write from memory. Asking for spring-boot-starter-web because the need was "web" would be a plausible, wrong dependency.
// spring_dependency({ "need": "jpa" }) // live: 2 requests (pom + gradle)
{
"type": "spring-dependency",
"need": "jpa",
"boot_version": "4.1.1",
"matches": [ { "id": "data-jpa", "name": "Spring Data JPA", "group": "SQL", "score": 3.0 } ],
"coordinates": [
{ "group_id": "org.springframework.boot", "artifact_id": "spring-boot-starter-data-jpa", "scope": "compile" },
{ "group_id": "org.springframework.boot", "artifact_id": "spring-boot-starter-data-jpa-test", "scope": "test" }
],
"source": { "pom_url": "https://start.spring.io/pom.xml?type=maven-build&dependencies=data-jpa&bootVersion=4.1.1", "gradle_url": "…", "cached": false },
"maven_snippet": "<dependencies>\n <dependency>\n <groupId>org.springframework.boot</groupId>\n <artifactId>spring-boot-starter-data-jpa</artifactId>\n </dependency>\n …\n</dependencies>",
"gradle_snippet": "dependencies {\n implementation 'org.springframework.boot:spring-boot-starter-data-jpa'\n testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'\n testRuntimeOnly 'org.junit.platform:junit-platform-launcher'\n}",
"truncated": false,
"note": "coordinates are the ones Spring Initializr itself generates for these dependency ids, not names derived from the ids"
}boot_version is normalized before it reaches Initializr: 4.1.1.RELEASE becomes 4.1.1, because bootVersion=4.1.1.RELEASE makes /build.gradle fail with Bom '…spring-boot-dependencies:4.1.1.RELEASE' could not be resolved (measured: HTTP 500). A need that matches nothing returns the catalogue's groups, not a guess:
// spring_dependency({ "need": "flux capacitor" }) // live
{
"error": "no Spring Initializr dependency matches 'flux capacitor'",
"suggestion": "spring_initializr('dependencies') lists every id in the catalogue; this tool only recommends artifacts that catalogue offers",
"dependency_groups": [ { "name": "Web", "count": 12 }, "…" ]
}spring_versions(project="spring-boot", version=None, focus="all", max_tokens=6000)
Release notes from the GitHub releases API (api.github.com), parsed into sections and classified: new-features, breaking-changes, deprecations, bug-fixes, dependency-upgrades, documentation, other. GitHub spells its section icons as ## :warning: Attention Required, so the classifier strips the emoji markers before deciding — without that step every section lands in other. focus= returns only the requested class (deprecations is a keyword view across all sections). version accepts 4.1, 4.1.1 or v4.1.1; the patch is optional and the nearest match in the release list is used.
// spring_versions({}) // live
{
"type": "spring-release-notes",
"project": "spring-boot",
"repo": "spring-projects/spring-boot",
"tag": "v4.1.1",
"prerelease": false,
"published_at": "2026-08-20T20:02:36Z",
"latest_stable": "v4.1.1", // newest non-prerelease in the list
"latest_listed": "v4.2.0-M2", // newest entry, prereleases included
"focus": "all",
"counts": { "new-features": 0, "breaking-changes": 1, "deprecations": 4, "bug-fixes": 33, "dependency-upgrades": 57, "documentation": 21, "other": 0 },
"section_headings": [ ":warning: Attention Required", ":lady_beetle: Bug Fixes", ":notebook_with_decorative_cover: Documentation", ":hammer: Dependency Upgrades", ":heart: Contributors" ],
"content": "# v4.1.1\n\n## Breaking Changes\n- Spring Boot's Gradle plugin no longer automatically configures gRPC when the Protobuf plugin is applied. … [#50822](…)\n\n## Bug Fixes\n- Kafka consumer-specific security protocol is not taken into account [#51369](…) …",
"truncated": true,
"cached": false,
"url": "https://github.com/spring-projects/spring-boot/releases/tag/v4.1.1"
}A version that does not exist lists the tags that do:
// spring_versions({ "version": "9.9" }) // live
{
"error": "no spring-projects/spring-boot release matches version '9.9'",
"suggestion": "spring_versions() with no version lists the newest one",
"available_tags": [ "v4.2.0-M2", "v4.2.0-M1", "v4.1.1", "v4.0.8", "v3.5.16", "v4.1.0", "…" ]
}project= covers spring-boot, spring-framework, spring-security, spring-data-jpa, spring-batch, spring-ai (live: spring_versions(project="spring-framework") → v7.0.9, published 2026-08-20).
Which Spring Boot version is current (and why two tools disagree)
The Spring layer had to answer "what is the latest Spring Boot?" from live data instead of from memory, and the two sources disagree:
Source | Says | Why |
|
| it is the version list the project generator will actually build with |
GitHub releases ( | newest stable v4.1.1 (published 2026-08-20), newest listed | it is the release ledger |
| newest | the Maven Central search index is stale: |
So "3.5.3" is not a Spring fact, it is a lagging search index: the same query re-fetched minutes apart returns byte-identical 3.5.x results while the repository itself already carries 4.1.1. maven_package is left as it is (it reports what its upstream API reports, and its versions list is honest about that), but no Spring tool derives a version from it — spring_dependency takes its Boot version from Initializr, spring_versions from GitHub, spring_reference from the docs tree it just read. A Spring agent that asks maven_package("spring-boot") for "the latest Boot" will get Camel's org.apache.camel.springboot:spring-boot:4.20.0 anyway, because relevance ranking beats the group you meant.
Replacing @enokdev/springdocs-mcp
The third-party @enokdev/springdocs-mcp was configured alongside this server to cover Spring. Its search_spring_concepts does not work — measured against the running server, auto-configuration, profiles and actuator all answered Concept not found in documentation. — and its other tools duplicate what this package now does against the same upstream pages. The mapping:
| This package |
|
|
|
|
|
|
|
|
|
|
|
|
Recommendation: drop @enokdev/springdocs-mcp from the MCP configuration. It is a second scraper of the same four hosts, with its own cache and no politeness layer, and the one capability it had that this package did not (topic search over the reference manual) is now the strongest tool here. Nothing in this package calls it or depends on it.
Caching & politeness
Cache
Everything lives in one directory: ~/.cache/java-spring-mcp by default, overridable with JAVA_SPRING_MCP_CACHE_DIR.
File | Contents | TTL |
| fetched pages (parsed result + raw body + | JDK docs 7 days, Spring reference pages 7 days, Spring concept index 7 days, Spring guides catalogue 7 days, Initializr metadata 1 day, GitHub releases 1 day, index 7 days, Maven 1 day |
| the politeness layer's robots.txt cache | 7 days per host |
Delete the files to force a full refresh. A cache problem is never a tool failure: if the directory is unwritable the server simply runs without a cache and says so in java_status().checks.cache.
Politeness
Every outbound request — fetch_*, the index build and the java_status probes — goes through one small internal module, src/java_spring_mcp/politeness.py (stdlib + httpx, no extra dependency):
robots.txt is read and obeyed. Rules are matched per RFC 9309 (
*, trailing$,%2A/%24literals, mergedUser-agentgroups, most-specific match wins,AllowbeatsDisallowon a tie).urllib.robotparseris deliberately not used: on Python < 3.14 it ignores wildcards and would report rules such asDisallow: /*/sp_common/*.htmas allowed.docs.oracle.compublishes over 5,700 rules in a 158 KB file, and among them the older JDK lines —/en/java/javase/12/…/16/and/18/…/20/, plus/javase/6/,/7/,/9/,/10/,/1.5.0/— are disallowed. This server fetchesjavase/26; if an older URL ever reached the layer the answer is{"ok": false, "error": "blocked by robots.txt for docs.oracle.com — Disallow: /en/java/javase/12/ matches …"}with no request made, not a silent fetch. The javadoc line is a single constant,fetchers.JDK_API_BASE, pinned tojavase/26; the disallow list stops at/20/, sojavase/17and every line from/21/up are open to crawlers — repointing that constant at the Java 21 LTS javadoc is the one change that stays robots-clean. Files are cached 7 days per host, including negative results (404 / 403 / 5xx), so a host costs one robots request per week.Per-host throttle, including
Crawl-delay. Requests to one host are spaced 0.35–0.9 s apart, or by the site's ownCrawl-delaywhen it declares one —docs.spring.iodeclarescrawl-delay: 1and therefore gets at least one second between requests. A cold run touches three hosts and pays one robots fetch plus one page fetch each, which is exactly where that delay is felt — that is the point (measured: 3 robots fetches + 3 page fetches, 1.56 s spent waiting,crawl_delay_applied: 1).429/503/504are handled.Retry-Afteris honoured to the second; without it the per-host delay is escalated and the request is retried once. A response that stalls past the read timeout escalates the delay instead of being retried blindly.Conditional GET.
ETag/Last-Modifiedand the raw body are stored next to the cached result, so refreshing an expired entry costs a304instead of a full download. Oracle's javadoc pages send anETag(noLast-Modified), so revalidation works there.search.maven.orgsends neitherETagnorLast-Modified— verified against live response headers — so a conditional GET is not possible for Maven lookups: the fetcher stores no validators for that host and the layer counts the attempt underconditional_skippedrather than sending a request that could never yield a304. The Spring hosts split the same way and were measured the same way:docs.spring.iopages sendLast-Modifiedand noETag, whilespring.io'spage-data,start.spring.ioandapi.github.comall sendETag— either validator is enough, so every Spring fetch stores one.Request budgets. One tool call may make at most 6 requests in total, and one budget unit is one request on the wire: the robots fetch, every retry and every redirect hop all pay. That matters here because Oracle answers a wrong class name with a
302to its landing page rather than a404, so nothing in the status line stops the module-guessing loop — the budget does. Measured live: a cold legitimatejava_docscosts 2–3 units, a cold typo spends all 6 and stops with the error dict above. A standalone index build gets its ownindex:docs.oracle.combudget of 6 and, if it runs out, returns a partial index instead of raising.Host allowlist. Only the hosts this package documents can be contacted —
docs.oracle.com,docs.spring.io,search.maven.org,spring.io,start.spring.ioandapi.github.com— so a malformed identifier or a surprising redirect cannot turn a docs lookup into a request to somebody else's site. What each of them actually publishes (re-measured live, 2026-10-07):docs.spring.iodeclaresUser-agent: *withcrawl-delay: 1and a second group that disallows/autorepo/;spring.ioallows everything and publishesSitemap: https://spring.io/sitemap-index.xml(the guides catalogue is read frompage-data, and the sitemap is the fallback source);start.spring.ioanswersrobots.txtwith 404 — no rules, so everything under it is fair game and the negative result is cached for a week;api.github.comlikewise 404srobots.txtand its rate-limit header reported{"limit": 60, "remaining": 57}, which is whyspring_versionsfetches one page of 100 releases per project and caches it for a day.One transport, no
curlfallback. Earlier versions shelled out tocurlwhenhttpxfailed. That second transport is gone and stays gone: it bypassed robots.txt, the throttle, the budget and the stored validators, so a fallback request was an unpolite request; it doubled the worst case to roughly two minutes (--max-time 90on top of a 20 shttpxtimeout); and the premise it was written for — that some edges throttle Python's TLS fingerprint — did not reproduce under measurement. The layer's own transport retry, backoff and 10 s stall detection cover the real failure mode in about a fifth of the time, inside the same budget and robots accounting.
The layer never raises and never changes a tool's return shape. java_status reports its counters under the top-level politeness key.
Opt-out
export JAVA_SPRING_MCP_POLITENESS_DISABLED=1This single switch turns off robots.txt, the throttle, retries, conditional GET and the host allowlist at once. Use it at your own risk: you take over responsibility for respecting each site's crawling rules, and you are far more likely to be rate-limited or blocked. There is no partial opt-out.
Development
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest -q # 257 offline tests; fixtures in tests/fixtures/
.venv/bin/python scripts/politeness_smoke.py /tmp/java-spring-smoke-cache
.venv/bin/python scripts/live_spring_smoke.py /tmp/java-spring-live-cache
.venv/bin/python scripts/e2e_mcp_test.pypytestis fully offline: HTTP is simulated withhttpx.MockTransportand time/jitter are injected, so the suite is deterministic and runs in seconds.tests/fixtures/holds real captured pages (the JDKListjavadoc, the Springusingsection, Maven search JSON, the full ~2 MB all-classes index).The Spring tools are tested the same way:
tests/test_spring.pycovers the parsers and rankers over fixtures trimmed from live responses (the reference nav, 14 reference pages, a guide page, Initializr's v2.1 metadata, GitHub's release list, Initializr's own generatedpom.xml/build.gradle), andtests/test_spring_tools.pydrives the six tools through a fixture-backed transport — including the six topic queries (auto-configuration,profiles,actuator,web,data jpa,security), a cache-hit assertion per tool, a request-budget assertion, and the assertion thatspring_dependencyreturnsspring-boot-starter-webmvcfor the needwebbecause that is what Initializr generates. Note thatspring.pyimports_http_getdirectly, so a test transport has to replace the binding in bothfetchersandspring.scripts/politeness_smoke.py <cache-dir>andscripts/live_spring_smoke.py <cache-dir>are live measurements, not tests: they drive the real tools against the real docs sites and print what the politeness layer did (requests, robots, throttle, conditional GET, budgets).scripts/e2e_mcp_test.pyspawns the installedjava-spring-docsconsole script and speaks newline-delimited JSON-RPC to it (initialize→tools/list→ every tool → a negative case →health_check). Exit code 0 means every check passed. SetJAVA_SPRING_MCP_E2E_CMDto run a different server command.
Troubleshooting
The first java_docs call takes half a minute. That is the cold search-index build: the Oracle all-classes page plus a robots fetch, spaced by the per-host throttle, followed by the page you actually asked for. It happens once every 7 days; later calls hit the cached index. Delete cache.db and you pay for it again.
request budget exhausted for scope 'tool:java_docs'. One tool call hit its cap of 6 requests. Oracle redirects a wrong class name to its landing page instead of returning 404, so a name that is not in the index burns the budget on module guesses before the tool gives up. java_search first — it is offline and free — and pass the fully-qualified name. java_status().politeness.budgets shows how much each scope spent.
"partial": true, or fewer search results than expected. An index build ran out of its budget and stopped early, returning a partial index with a partial_reason. A partial index is not cached, so the next run rebuilds it. java_search still works — it just knows fewer classes — and java_status().checks.search_index.entries tells you how many it has (a complete JDK 26 index is ~4,700 entries).
"stale": true / index_stale: true. The rebuild failed (offline, DNS failure, 5xx) and the server fell back to the older cached index instead of failing the lookup.
Nothing is cached and overall is degraded. Read java_status().checks.cache.error — an unwritable JAVA_SPRING_MCP_CACHE_DIR (read-only mount, missing permission) is the usual cause. Tools keep working without a cache; they are just slower and noisier on the network.
A lookup returns blocked by robots.txt. The site's rules disallow that path for this user agent, and the request was not sent. For Oracle this normally means an older JDK javadoc line: /en/java/javase/12/–/16/ and /18/–/20/ are closed to crawlers. Fetch the page yourself, or accept the consequences of the opt-out switch above.
maven_package returns a group you did not expect. Without group_id the Maven search API ranks by relevance, and a common artifact name can belong to several projects. Pass group_id to pin the coordinates.
Support development
java-spring-mcp is built and maintained by Michal in his spare time. If it saves you time or makes your team's docs easier to work with, a coffee (or more) would mean a lot — every contribution helps keep the project moving. 🙏
Pay via Revolut: revolut.me/michal4zvc
License & attribution
MIT — see LICENSE. Copyright (c) 2026 Michal Gaspierik.
The design of the politeness layer was inspired by Crawl4AI (© Unclecode, Apache-2.0): a TTL-cached robots store, the wildcard rule translation, and a per-domain rate limiter with escalating backoff. The implementation is a clean-room rewrite in this project's own synchronous stdlib-plus-httpx style — no line was transcribed, translated or mechanically adapted from Crawl4AI, and Crawl4AI is not a dependency of this package. Three defects of the original design are fixed (robots fetched_at refresh, negative-result caching, Crawl-delay support — the last one matters concretely here, because docs.spring.io declares crawl-delay: 1). The GPL-3.0 part of Crawl4AI — its vendored html2text fork — is deliberately excluded: no code, data or dependency from that tree is used or shipped here. The full statement is in NOTICE.
Same clean-room architecture as flutter-mcp: fetchers → pure parsers → TTL cache → search index → tools, with no circular delegation, a pinned 1.x MCP SDK, error-dict failures and an offline test suite.
Part of the keepee self-hosted family. Also see Wiki4AI — a self-hosted collaborative wiki for teams and AI agents, with hybrid semantic search and an MCP server of its own — and the full project index.
Available Tools
11 toolshealth_checkA
Check that the server is up and report its version.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It implicitly conveys a safe, idempotent read and discloses the return payload (version), which is useful. It does not state whether auth is required, whether it can fail/timeout, or what 'up' means operationally, but for a trivial probe the disclosure is reasonable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single tight sentence with zero waste, front-loading the primary action (check server is up) before the secondary outcome (report version).
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description steps in to say it reports the version, which is the key return detail. For a zero-param probe with no annotations this is nearly complete; a note on failure mode or the distinction from flutter_status would close the remaining gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters (schema coverage 100%), so there is nothing to document and the baseline of 4 applies. The description correctly signals a no-argument call.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: checks server availability and reports its version. The action is unambiguous. However, it offers no differentiation from the similarly-named sibling flutter_status, which an agent could plausibly confuse with a server health probe.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use guidance, no prerequisites, and no mention of when this should be preferred over flutter_status. Usage is only vaguely implied by the verb 'check'.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
java_docsA
Fetch JDK class javadoc or a Spring Boot reference section as markdown.
identifier resolution order:
"spring" or "spring:" (e.g. "spring:using") → Spring Boot reference section;
dotted names like "java.util.List" → fully-qualified class; the module is resolved from the search index when possible, otherwise java.base is tried first and then a fixed list of fallback modules;
plain names like "HttpClient" → exact-name lookup in the search index (class/interface preferred when the name is ambiguous, with a "note" listing alternates).
topic (e.g. "methods", "fields", "constructors", "nested") keeps only
that heading section plus title/description; if no such section exists the
full content is returned with a "note" listing the available headings.
max_tokens truncates the final markdown to roughly that many tokens
(estimated as len(text)//4), cut at a line boundary, and sets
"truncated": true when applied.
On failure returns {"error", "suggestion"} — try java_search() to find the right name or package.
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | ||
| identifier | Yes | ||
| max_tokens | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations present, the description carries the full burden and does so richly: module resolution with a fixed fallback list, ambiguous-name handling that returns a 'note' with alternates, topic-section filtering and its full-content fallback, truncation clipped at line boundaries with a 'truncated' flag, and an error shape of {error, suggestion}. This is behavior well beyond what the schema exposes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded one-line purpose followed by an ordered resolution list and two behavior paragraphs; every sentence conveys a rule an agent needs. It is dense rather than padded, though slightly longer than strictly necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema and no annotations exist, yet the description covers the return format (markdown), the 'truncated' and 'note' fields, and the error contract, so an agent knows what to expect. Minor gap: the success payload's overall shape is only loosely implied.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does: it fully specifies the accepted 'identifier' forms (spring / spring:<section>, dotted FQN, plain name), the 'topic' heading filter with its no-section fallback, and exactly how 'max_tokens' is estimated (len//4) and applied. Every parameter is given meaning the schema lacks.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Fetch') and two concrete resources ('JDK class javadoc' or 'a Spring Boot reference section') with the output format ('as markdown'). An agent can distinguish this document-fetch tool from java_search without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The numbered identifier resolution order tells the agent exactly which input form maps to which source, and the failure clause explicitly routes to java_search(). It does not, however, distinguish this tool from the sibling spring_reference / spring_guides tools, which overlap with the 'spring:<section>' behavior.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
java_searchA
Search JDK classes and Spring Boot reference sections by name.
Ranks entries of the cached search index (exact > startswith > substring > fuzzy per token; every token must match). Spring sections are included as pseudo-entries, so e.g. 'spring using' finds the "Using" section. Returns {"query", "count", "results": [{name, package, module, kind, url, score}], "index_stale"}.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the ranking precedence (exact > startswith > substring > fuzzy), the all-tokens-must-match constraint, that Spring sections appear as pseudo-entries, and the return shape including an index_stale flag. It omits any auth, rate-limit, or index-refresh behavior, but the non-obvious search semantics are covered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose, then ranking rules, then the return contract — every sentence adds information rather than restating the name. Slightly dense, but nothing is padding and the ordering follows how an agent would consume it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no annotations and no output schema, the description supplies the return shape and the ranking/matching behavior an agent needs to interpret results. The remaining gap is the undocumented 'limit' parameter; otherwise it is sufficient for a two-parameter read-only search.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and the schema only labels the fields 'Query' and 'Limit'. The description meaningfully explains query semantics (per-token matching, fuzzy fallback, name-based lookup), which compensates for the required parameter, but the 'limit' parameter is never mentioned or bounded, leaving one of two parameters undocumented everywhere.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Search JDK classes and Spring Boot reference sections by name.' This clearly separates it from spring_search_concepts or spring_reference, though it never names a sibling explicitly, so an agent must infer the boundary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied rather than stated: the 'by name' framing and pseudo-entry note suggest this is the lexical/name lookup tool versus conceptual search. There is no explicit when-to-use, when-not-to-use, or alternative tool named, so the agent has to reason about routing itself.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
java_statusA
Real health check: search index, local cache and upstream endpoints.
The cache check gains read_only: true plus read_only_reason
when the database cannot be written (P5). It stays an "ok" check on
purpose: a cache that only reads is a degraded optimisation, not a sick
server, so overall does not change.
Probes docs.oracle.com, docs.spring.io and search.maven.org with a light GET (10s timeout each). Never raises; returns {"server", "version", "checks": {...}, "overall": "ok"|"degraded"|"error"} where overall is "error" when the search index is unavailable, "degraded" when any check fails, and "ok" otherwise.
Also returns a top-level politeness block with the politeness layer's
counters: robots cache rows/fetches/hits, requests blocked by robots.txt,
throttle waits and per-host delays, conditional GETs / 304 revalidations,
429 / transport retries, stalls, request budgets and whether the layer is
disabled (JAVA_SPRING_MCP_POLITENESS_DISABLED). It sits outside
checks on purpose: it is diagnostics and must never change
overall.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does so richly: it discloses the 10s per-host GET timeout, that it never raises, the exact mapping of overall values (error when the search index is unavailable, degraded on any check failure, ok otherwise), the env var that disables the politeness layer, and the deliberate decision that a read-only cache stays 'ok'. This is exactly the kind of non-obvious behavior an agent cannot get from a zero-parameter schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the one-line purpose, then logically grouped paragraphs for the cache edge case, the probe/return contract, and the politeness block. It is longer than most zero-param definitions, but nearly every sentence carries operational detail that has nowhere else to live given the absent output schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and no annotations, the description fully specifies the return shape (server, version, checks, overall) plus the top-level politeness counters and the environment flag. An agent has everything needed to call the tool and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing to document and the baseline of 4 applies. The description correctly spends no effort on parameter syntax and instead describes the return payload and semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: 'Real health check: search index, local cache and upstream endpoints.' The word 'Real' hints at contrast with the sibling health_check, and the enumerated components make the scope concrete. It stops short of explicitly routing the agent to or away from health_check, so it falls short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No explicit when-to-use, when-not-to-use, or alternative guidance is given, despite a sibling named health_check that an agent must distinguish this from. The 'Real health check' phrasing and the 'never raises' note imply it is a safe diagnostic call, but the agent is left to infer the selection rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
maven_packageA
Look up an artifact on Maven Central (metadata only).
Returns the newest matching version, the top 20 versions and project /
javadoc links. When version is given and not found, the error carries
a suggestion listing available versions. Metadata is cached for 1 day
keyed by the search-API URL; failures are never cached.
Note: max_tokens is reserved for future javadoc content fetching and
currently has no effect — only metadata is returned.
| Name | Required | Description | Default |
|---|---|---|---|
| version | No | ||
| group_id | No | ||
| max_tokens | No | ||
| artifact_id | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and largely meets it: it discloses the return shape (newest matching version, top 20 versions, project/javadoc links), a 1-day cache keyed by search-API URL, that failures are never cached, and that a missing version yields an error suggesting available versions. It does not state permission/auth requirements or rate limits, and never explicitly says the operation is non-mutating, so it falls short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core purpose is front-loaded in the first sentence, followed by return shape, error behavior and caching. The max_tokens disclaimer is a slight detour but earns its place by preventing an agent from supplying a parameter that does nothing; overall it is tight and waste-free.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only lookup tool with no annotations and no output schema, the description covers the essentials: what is returned, what happens on a miss, and caching behavior. It is only short on group_id resolution rules, auth/prerequisite requirements and pagination or rate-limit limits, which are secondary for this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate, and it only covers two of four parameters: version lookup behavior (not-found error with suggestions) and the max_tokens no-op. The semantics of artifact_id (required) and the optional group_id - especially how the search resolves when group_id is omitted - receive no explanation, leaving a meaningful gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Look up an artifact on Maven Central') plus an explicit scope qualifier ('metadata only'). This clearly separates it from the java_docs, java_search and spring_* siblings, which fetch source/docs or Spring-specific content rather than Maven artifact metadata. An agent can route correctly without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by 'metadata only' and the caching/error notes, but there is no explicit when-to-use statement and no named alternative among the ten sibling tools. It never says, for example, to prefer java_docs when full javadoc content is wanted (despite the max_tokens note hinting at that future capability). Context is inferable but not stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
spring_dependencyA
Turn a need ("jpa", "web", "oauth2 client") into real starter coordinates.
No artifact name is invented. The need is matched against Spring
Initializr's own dependency catalogue, then Initializr is asked to generate
the Maven pom and the Gradle build file for those ids, and those are
returned — parsed coordinates plus ready-to-paste snippets. That matters:
the catalogue id "web" generates spring-boot-starter-webmvc on Spring
Boot 4.1, not the spring-boot-starter-web that string-building
spring-boot-starter-<id> would produce.
build is "both" (default), "maven" or "gradle". boot_version pins
the line (plain form, e.g. "4.1.1"); omitted uses Initializr's newest
released default. max_tokens truncates the snippets.
On failure returns {"error", "suggestion"} — an unmatched need returns an error rather than a guess.
| Name | Required | Description | Default |
|---|---|---|---|
| need | Yes | ||
| build | No | both | |
| max_tokens | No | ||
| boot_version | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden well: it discloses that no artifact name is invented, that the catalogue is the source of truth, that the returned coordinates come from actually generated pom/build files rather than string concatenation, and that an unmatched need produces an error with a suggestion rather than a guess. Auth, rate limits, and network-dependence are unstated.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and then layered with justification and parameter semantics; every paragraph adds real information. Slightly longer than necessary, with the bolded emphasis and parenthetical example costing some tightness but earning their place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 4-parameter tool with no annotations and no output schema, the description covers purpose, parameter semantics, failure shape ({"error","suggestion"}), and the nature of the return payload (parsed coordinates plus ready-to-paste snippets). The internal structure of the parsed coordinates is left unspecified, a minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% and titles are bare, so the description does the full job: it supplies the allowed values for `build` ("both"/"maven"/"gradle"), the exact format and default semantics for `boot_version` (plain form like "4.1.1", newest released default when omitted), what `max_tokens` truncates, and example values for `need`.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific transformation (a need string into real starter coordinates) and explains the mechanism: matching against Spring Initializr's catalogue and generating the Maven/Gradle file. The concrete example ("web" yielding spring-boot-starter-webmvc on Boot 4.1) makes it clearly distinguishable from spring_initializr and maven_package.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage context is clear: call it when you have a human-style need ("jpa", "web", "oauth2 client") and want verified coordinates. It does not explicitly name sibling alternatives (e.g. spring_initializr for whole projects) or state when not to use it, so it stops short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
spring_guidesA
List the spring.io guides, or read one guide as markdown.
No guide → the catalogue (title, url, type, category, description) from
spring.io's own guide data: the HTML index is rendered client-side, so
scraping it yields no guides at all, and the sitemap fallback yields URLs
without titles. topic filters the catalogue by title, slug, category or
description.
guide → that guide as markdown. Guide pages are server-rendered even
though the index is not, so "gs/spring-boot", "accessing-data-jpa" or a full
https://spring.io/guides/… URL all work. topic then keeps only the
matching heading section, as java_docs(topic=...) does. max_tokens
truncates (estimated as len(text)//4).
On failure returns {"error", "suggestion"}.
| Name | Required | Description | Default |
|---|---|---|---|
| guide | No | ||
| topic | No | ||
| max_tokens | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so well: it explains why the HTML index cannot be scraped (client-side rendering), that the sitemap fallback lacks titles, that guide pages are server-rendered and therefore retrievable, how `max_tokens` truncation is estimated (len(text)//4), and the exact failure payload shape {"error", "suggestion"}. This is unusually rich disclosure of internal behavior and failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The two-mode structure is front-loaded and the description is scannable, with each paragraph mapping to one branch of the tool. The aside about client-side rendering versus server-rendered guide pages is longer than strictly necessary, though it does justify result quality, so it is not pure waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a three-parameter, no-output-schema tool with zero annotation coverage, the description supplies both modes' return shapes (catalogue fields title/url/type/category/description; markdown for a single guide) plus the error contract. An agent has everything needed to call it correctly and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does for all three parameters: `guide` accepts slugs, short forms like "gs/spring-boot", or full spring.io URLs; `topic` filters the catalogue by title/slug/category/description and, in guide mode, narrows to a matching heading section; `max_tokens` truncates with a stated estimation formula.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a concrete verb and resource ('List the spring.io guides, or read one guide as markdown') and immediately splits the tool into two clearly delineated modes keyed on the `guide` parameter. It also anchors itself against a sibling by noting the `topic` behavior matches `java_docs(topic=...)`, so an agent can place it in the toolset without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit routing rules: omit `guide` for the catalogue, supply `guide` for markdown, and describes what `topic` does in each mode. What's missing is guidance on when to prefer this over other documentation siblings such as `spring_reference` or `spring_search_concepts`, so the alternative-selection story is incomplete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
spring_initializrA
Read Spring Initializr's own metadata: project options or dependency catalogue.
section="options" → the choices start.spring.io offers (Spring Boot
versions with the newest released one called out, Java versions, project
types, packaging, and the defaults for groupId/artifactId/version). No
version is hardcoded anywhere: it is whatever Initializr is serving today.
section="dependencies" → the dependency catalogue (id, name,
description, group, reference doc link), filtered by query over those
fields. Without query the whole catalogue is returned, truncated to
max_tokens.
On failure returns {"error", "suggestion"}.
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | ||
| section | No | options | |
| max_tokens | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and does reasonably well: it discloses that no version is hardcoded and content is whatever Initializr serves today, that output is truncated to `max_tokens`, and that failures return {"error", "suggestion"}. It omits auth/rate-limit considerations, but the disclosed truncation and error-shape behavior are genuinely useful.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core purpose in one line, then uses parallel `section=` blocks that are easy to scan. Slightly more verbiage than strictly necessary, but each section earns its place by describing a distinct mode.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only metadata tool with no output schema, no annotations, and zero schema coverage, the description covers both return shapes (options payload and truncated catalogue) and the error contract, which is enough for an agent to call it correctly. Only external concerns like auth or pagination beyond `max_tokens` are absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate and largely does: it defines both enumerable `section` values with their contents, specifies that `query` matches over id/name/description/group/reference-doc-link, and explains `max_tokens` as the truncation limit. This adds real meaning beyond the bare schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (read Spring Initializr's own metadata) and clearly enumerates the two operational modes via the `section` parameter, mapping each to distinct content (options vs dependency catalogue). It does not, however, differentiate itself from siblings like spring_versions or spring_dependency, which an agent could easily confuse with this tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains what each `section` value returns and that `query` filters the catalogue, which implies when each mode is appropriate. But it gives no explicit when-to-use guidance against the many related siblings (spring_dependency, spring_search_concepts, spring_versions), leaving the routing decision to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
spring_referenceA
Read a Spring Boot reference section or subsection as clean markdown.
section / subsection are resolved against the documentation's own
navigation tree, so "web", "data"+"sql", "using"+"auto-configuration",
"how-to"+"actuator" and "installing" all work, and a name that does not
exist comes back with the sections that do. Resolution is offline when the
index is cached, so no request is wasted guessing a URL and getting a 404.
When subsection is not a page of its own, the section page is returned
with only the matching heading section kept (plus title and description) —
the same behaviour as java_docs(topic=...) — and a "note" lists the
available headings when nothing matches.
version (e.g. "4.1") reads that docs line; omitted reads the latest.
max_tokens truncates the markdown to roughly that many tokens
(estimated as len(text)//4) at a line boundary and sets "truncated".
On failure returns {"error", "suggestion", "candidates"}.
| Name | Required | Description | Default |
|---|---|---|---|
| section | Yes | ||
| version | No | ||
| max_tokens | No | ||
| subsection | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so: it discloses offline resolution when the index is cached, that max_tokens truncates at a line boundary and sets a 'truncated' flag, that narrowing keeps only the matching heading section plus title/description, and that failure returns {"error", "suggestion", "candidates"}. These are behavioral traits well beyond a bare read.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and organized by parameter, with each paragraph earning its place. It is somewhat long and mildly discursive ('no request is wasted guessing a URL and getting a 404'), but the length is justified by the amount of non-obvious behavior being documented.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema and no annotations, so the description must cover everything, and it does: purpose, parameter semantics, resolution fallback, truncation, and failure payload. An agent has enough to call the tool correctly and interpret both success and error results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate, and it does for every parameter: section/subsection resolution semantics, version ('e.g. "4.1"', omitted reads latest), and max_tokens including the estimation formula (len(text)//4) and the resulting 'truncated' flag. This adds far more meaning than the bare schema types.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read a Spring Boot reference section or subsection as clean markdown') and explains exactly what 'section'/'subsection' resolve against. It also positions itself relative to java_docs by saying the narrowing behavior is 'the same behaviour as java_docs(topic=...)', letting an agent distinguish it from siblings without opening both schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete usage context: valid resolution examples ('web', 'data'+'sql', 'how-to'+'actuator'), behavior when subsection is not its own page, and the note fallback when nothing matches. However, it never states when to reach for this tool over spring_guides, spring_search_concepts, or spring_search_concepts for discovering section names, so explicit alternatives/exclusions are absent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
spring_search_conceptsA
Find where a Spring Boot topic is documented: section titles and headings.
Matches the documentation's own titles ("Auto-configuration", "Profiles", "Web", "Security") and the h2/h3/h4 headings inside pages, so "data jpa" finds reference/data/sql.html#jpa-and-spring-data even though no page is titled "JPA". Each hit carries title, url, section, version, score, matched_on ("title", "path" or "heading") and a short excerpt.
version (e.g. "4.1") searches that docs line; omitted searches the
latest one and the result reports which version that was. limit caps
hits (1-25). max_tokens bounds the excerpt text across the hits
(estimated as len(text)//4): whole hits past the budget are dropped and
"truncated" is set.
The index is a single page — the reference navigation tree (296 entries on the measured 4.1.1 docs) — cached for a week. Headings are harvested only for queries the titles cannot answer, at most 4 pages per call, and are persisted under their own cache key, so a repeat search costs zero requests.
On failure returns {"error", "suggestion"}.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| version | No | ||
| max_tokens | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and discharges it well: it discloses the week-long single-page index cache, that headings are only harvested for queries titles cannot answer and at most 4 pages per call, that heading results are cached under their own key so repeats cost zero requests, and the exact max_tokens truncation contract (len(text)//4 budget, whole hits dropped past it, 'truncated' flag set). It also documents the failure shape {"error", "suggestion"}.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in the first line and each subsequent sentence is about a distinct concern (matching surface, result fields, parameter behavior, caching, error shape). It runs long and the caching paragraph, while useful, is the least essential, but there is little outright waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, yet the description enumerates the returned fields (title, url, section, version, score, matched_on, excerpt) and the error payload, so an agent knows both what it gets back and how failures surface. For a 4-parameter, annotation-free tool this is complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate and it does for all four parameters: version (docs line, omitted = latest with the resolved version reported back), limit (caps hits, 1-25), and max_tokens (budget semantics and estimation formula), with query self-evident. This adds substantive meaning beyond the bare schema titles.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource ('Find where a Spring Boot topic is documented: section titles and headings') and explains the matching surface (page titles plus h2/h3/h4 headings), which is far more than a restatement of the name. It stops short of distinguishing itself from close siblings such as spring_reference or spring_guides, which an agent could plausibly confuse with a documentation-lookup tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Use is implied clearly by the opening line and the worked 'data jpa' example, and the version-omitted default is spelled out. However, there is no explicit when-to-use-this-vs-alternative guidance and no exclusions, so selection against siblings like java_search or spring_reference is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
spring_versionsA
Release and migration notes for a Spring project, from its GitHub releases.
project is "spring-boot" (default), "spring-framework",
"spring-security", "spring-data-jpa", "spring-batch" or "spring-ai".
version picks a release ("4.1.1", or "4.1" for its newest patch);
omitted picks the newest released version — never a milestone or
snapshot, and never a hardcoded number: it is whatever GitHub lists today.
focus is "all", "breaking-changes" (the release's Attention Required /
Noteworthy Changes sections), "new-features" (New Features) or
"deprecations". Deprecations are found by keyword across every section
because no spring-boot release body has a deprecation heading (measured over
100 releases). Migration-guide and release-notes wiki links found in the
body are returned under wiki_links.
max_tokens truncates the rendered notes (estimated as len(text)//4).
Releases are cached for a day: unauthenticated GitHub allows 60 requests per
hour per IP, and one call spends at most one of them.
On failure returns {"error", "suggestion"}.
| Name | Required | Description | Default |
|---|---|---|---|
| focus | No | all | |
| project | No | spring-boot | |
| version | No | ||
| max_tokens | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the 60 requests/hour unauthenticated GitHub limit, one-call-at-most-one-request accounting, a one-day cache, max_tokens truncation estimated as len(text)//4, and the {"error", "suggestion"} failure shape. It stops short of describing the returned notes payload structure beyond wiki_links, and the read-only nature is only implied.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose, then parameter semantics, then operational caveats; nearly every sentence earns its place. The measured-over-100-releases parenthetical and the request-budget detail are slightly more than strictly needed, pushing length past the tightest possible form.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given zero schema descriptions, zero annotations, and no output schema, the description supplies the equivalent of all three: full enum semantics, mutation-free read behavior and rate limits, plus concrete return details (rendered notes and wiki_links) and an error contract. Nothing essential for a correct call is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate and it does: it enumerates every valid project value, explains that version accepts either a full release or a major.minor that resolves to the newest patch, defines all four focus modes including the substring matches for breaking-changes and new-features, and gives the truncation formula for max_tokens.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: release and migration notes for a Spring project sourced from GitHub releases. The project list (spring-boot, spring-framework, spring-security, etc.) and the 'wiki_links' mention make it clearly distinct from siblings like spring_reference, spring_guides, and spring_search_concepts.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives rich in-tool guidance (which focus values map to which release sections, why deprecations are keyword-searched, and that version pinning resolves to the newest patch), but never says when to reach for this tool rather than spring_reference or spring_guides for migration information. Usage is implied rather than routed against alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
11 tool updates
v0.2.0- First observed
health_check - First observed
java_docs - First observed
java_search - First observed
java_status - First observed
maven_package - First observed
spring_dependency - First observed
spring_guides - First observed
spring_initializr - First observed
spring_reference - First observed
spring_search_concepts - First observed
spring_versions
TDQS
Scored across 11 tools
Several tools have genuinely overlapping purposes: java_docs can fetch Spring Boot reference sections (via 'spring' / 'spring:section'), which duplicates spring_reference, and java_search covers both JDK classes and Spring Boot reference sections, overlapping with spring_search_concepts. health_check vs java_status are also both health checks, though their descriptions clearly differentiate depth. Descriptions help, but boundaries between the doc/search tools require careful reading.
Names are uniformly snake_case with a domain prefix (spring_, java_, maven_, health_), which is a readable, predictable convention. The prefixes vary by domain rather than being a single verb_noun pattern, and health_check is a slight outlier, but nothing is chaotic or camelCase-mixed.
11 tools is well within a healthy range and each tool maps to a distinct capability (health, dependency resolution, docs, search, Maven lookup, guides, Initializr metadata, versions). No redundant or filler tools inflate the count.
The surface covers discovery, search, documentation reading, dependency coordinates, Initializr metadata and release/migration notes, which is broad lifecycle coverage for a Java/Spring dev assistant. Minor gaps remain (e.g. no actual project scaffolding/generation, and maven_package is metadata-only), but agents can work around them.
Maintenance
Related MCP Connectors
Search GitHub, npm, PyPI, StackOverflow, ArXiv from one MCP — built for coding agents.
An MCP server that gives your AI access to the source code and docs of all public github repos
Search MCP servers, agent skills and open-source AI projects, plus AI tools. Read-only.
Search your AI chat history (ChatGPT, Claude, Codex) from any MCP client. Remote, private, read-only
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides semantic search over markdown documentation using RAG, allowing natural language queries and integration with MCP clients.1MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to intelligently search and reference documentation using hybrid semantic + keyword search via MCP protocol.-
- FlicenseAqualityDmaintenanceEnables searching documentation from GitHub repositories and web pages via MCP tools, with in-memory indexing and caching for fast retrieval.3-
- AlicenseAqualityBmaintenanceEnables searching and retrieving documentation from crawled documentation sites as an MCP server, allowing coding agents to query real docs instead of relying on training data.4MIT