#
tokens: 48537/50000 51/60 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/arvindand/maven-tools-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .gitattributes
├── .github
│   └── workflows
│       ├── ci.yml
│       └── docker.yml
├── .gitignore
├── .mvn
│   └── wrapper
│       └── maven-wrapper.properties
├── assets
│   └── demo.gif
├── build
│   ├── build-docker.cmd
│   ├── build-docker.sh
│   ├── build.cmd
│   ├── build.sh
│   └── README.md
├── CHANGELOG.md
├── CORPORATE-CERTIFICATES.md
├── docker-compose.yml
├── LICENSE
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── arvindand
    │   │           └── mcp
    │   │               └── maven
    │   │                   ├── config
    │   │                   │   ├── CacheConfig.java
    │   │                   │   ├── CacheConstants.java
    │   │                   │   ├── Context7Properties.java
    │   │                   │   ├── HttpClientConfig.java
    │   │                   │   ├── JacksonConfig.java
    │   │                   │   ├── MavenCentralProperties.java
    │   │                   │   ├── McpToolsConfig.java
    │   │                   │   └── NativeImageConfiguration.java
    │   │                   ├── MavenMcpServerApplication.java
    │   │                   ├── model
    │   │                   │   ├── BulkCheckResult.java
    │   │                   │   ├── Context7Guidance.java
    │   │                   │   ├── DependencyAge.java
    │   │                   │   ├── DependencyAgeAnalysis.java
    │   │                   │   ├── DependencyInfo.java
    │   │                   │   ├── MavenArtifact.java
    │   │                   │   ├── MavenCoordinate.java
    │   │                   │   ├── MavenMetadata.java
    │   │                   │   ├── McpError.java
    │   │                   │   ├── ProjectHealthAnalysis.java
    │   │                   │   ├── ReleasePatternAnalysis.java
    │   │                   │   ├── StabilityFilter.java
    │   │                   │   ├── ToolResponse.java
    │   │                   │   ├── VersionComparison.java
    │   │                   │   ├── VersionInfo.java
    │   │                   │   ├── VersionsByType.java
    │   │                   │   └── VersionTimelineAnalysis.java
    │   │                   ├── service
    │   │                   │   ├── MavenCentralException.java
    │   │                   │   ├── MavenCentralService.java
    │   │                   │   └── MavenDependencyTools.java
    │   │                   └── util
    │   │                       ├── MavenCoordinateParser.java
    │   │                       └── VersionComparator.java
    │   └── resources
    │       ├── application-docker.yaml
    │       ├── application-no-context7.yaml
    │       ├── application.yaml
    │       ├── logback-spring.xml
    │       └── META-INF
    │           └── additional-spring-configuration-metadata.json
    └── test
        ├── java
        │   └── com
        │       └── arvindand
        │           └── mcp
        │               └── maven
        │                   ├── config
        │                   │   └── Context7PropertiesTest.java
        │                   ├── MavenMcpServerIT.java
        │                   ├── model
        │                   │   └── Context7GuidanceTest.java
        │                   ├── service
        │                   │   ├── MavenCentralServiceRepositoryIT.java
        │                   │   ├── MavenCentralServiceUnitTest.java
        │                   │   ├── MavenDependencyToolsContext7EnabledIT.java
        │                   │   ├── MavenDependencyToolsIT.java
        │                   │   └── MavenDependencyToolsPerformanceIT.java
        │                   ├── TestHelpers.java
        │                   └── util
        │                       ├── MavenCoordinateParserTest.java
        │                       └── VersionComparatorTest.java
        └── resources
            └── application-test.yaml
```

# Files

--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------

```
1 | /mvnw text eol=lf
2 | *.cmd text eol=crlf
3 | 
```

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
 1 | # Maven Tools MCP Server - Buildpack Optimizations
 2 | # Exclude unnecessary files from Docker build context
 3 | 
 4 | # Build artifacts
 5 | target/
 6 | *.jar
 7 | 
 8 | # IDE files
 9 | .idea/
10 | .vscode/
11 | *.iml
12 | 
13 | # OS files
14 | .DS_Store
15 | Thumbs.db
16 | 
17 | # Git and documentation (not needed in runtime)
18 | .git/
19 | *.md
20 | CHANGELOG.md
21 | CLAUDE.md
22 | 
23 | # Logs and temporary files
24 | logs/
25 | *.log
26 | *.tmp
27 | *.bak
28 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | HELP.md
 2 | CLAUDE.md
 3 | demo
 4 | target/
 5 | .mvn/wrapper/maven-wrapper.jar
 6 | !**/src/main/**/target/
 7 | !**/src/test/**/target/
 8 | 
 9 | ### STS ###
10 | .apt_generated
11 | .classpath
12 | .factorypath
13 | .project
14 | .settings
15 | .springBeans
16 | .sts4-cache
17 | logs
18 | 
19 | ### IntelliJ IDEA ###
20 | .idea
21 | *.iws
22 | *.iml
23 | *.ipr
24 | 
25 | ### VS Code ###
26 | .vscode/
27 | 
28 | # Environment variables file
29 | .env
30 | 
31 | .claude
32 | 
33 | # Internal planning documents
34 | improvement.md
35 | .mcp.json
36 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Maven Tools MCP Server
  2 | 
  3 | [![Java](https://img.shields.io/badge/Java-24-orange.svg)](https://openjdk.java.net/)
  4 | [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5.6-green.svg)](https://spring.io/projects/spring-boot)
  5 | [![MCP Protocol](https://img.shields.io/badge/MCP-2024--11--05-blue.svg)](https://modelcontextprotocol.io/)
  6 | [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
  7 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/arvindand/maven-tools-mcp)](https://github.com/arvindand/maven-tools-mcp/releases)
  8 | [![Docker](https://img.shields.io/badge/Docker-Multi--Arch-blue.svg)](https://hub.docker.com/r/arvindand/maven-tools-mcp)
  9 | [![GitHub stars](https://img.shields.io/github/stars/arvindand/maven-tools-mcp?style=social)](https://github.com/arvindand/maven-tools-mcp/stargazers)
 10 | 
 11 | ## Universal Maven Central dependency intelligence for JVM build tools
 12 | 
 13 | MCP server providing AI assistants with Maven Central dependency intelligence for all JVM build tools (Maven, Gradle, SBT, Mill). Get instant, accurate dependency information by reading maven-metadata.xml files directly from Maven Central repository - faster and more reliable than web searches or search APIs. Features Context7 integration for documentation support.
 14 | 
 15 | ## 🎯 Why This Matters
 16 | 
 17 | - **Problem:** Dependency management involves time-intensive manual searches across Maven Central for version updates and compatibility analysis
 18 | - **Solution:** AI-assisted dependency intelligence with instant bulk analysis, trend insights, and risk assessment for any JVM build tool
 19 | 
 20 | ## ⚡ Quick Demo
 21 | 
 22 | ![Demo GIF](assets/demo.gif)
 23 | 
 24 | Ask your AI assistant:
 25 | 
 26 | - *"Check all dependencies in this build file for latest versions"* (paste your build.gradle, pom.xml, build.sbt)
 27 | - *"What's the latest Spring Boot version?"*
 28 | - *"Which dependencies in my project need updates?"* (any build tool)
 29 | - *"Show me only stable versions for production deployment"*
 30 | - *"How old are my dependencies and which ones need attention?"*
 31 | - *"Analyze the release patterns for my key dependencies"*
 32 | - *"Give me a health check for all my project dependencies"*
 33 | - *"How do I upgrade Spring Boot from 2.7.0 to the latest version? Show me migration guidance"*
 34 | - *"Check these dependencies for upgrades and suggest documentation searches"* (paste your pom.xml/build.gradle)
 35 | - *"I'm still using Jackson 2.12.0. Should I upgrade and how?"*
 36 | 
 37 | ## 🔧 Supported Build Tools
 38 | 
 39 | Working with **any build tool** that uses Maven Central Repository:
 40 | 
 41 | | Build Tool | Dependency Format | Example Usage |
 42 | |------------|------------------|---------------|
 43 | | **Maven** | `groupId:artifactId:version` | `org.springframework:spring-core:6.2.8` |
 44 | | **Gradle** | `implementation("group:artifact:version")` | Uses same Maven coordinates |
 45 | | **SBT** | `libraryDependencies += "group" % "artifact" % "version"` | Same groupId:artifactId format |
 46 | | **Mill** | `ivy"group:artifact:version"` | Same Maven Central lookup |
 47 | 
 48 | **All tools use standard Maven coordinates** - just provide `groupId:artifactId` and we handle the rest.
 49 | 
 50 | ## ⚡ Advantages
 51 | 
 52 | ### vs Simple Lookup Tools
 53 | 
 54 | - ✅ **Bulk Operations** - Analyze 20+ dependencies in one call
 55 | - ✅ **Version Comparison** - Understand upgrade impact (major/minor/patch)
 56 | - ✅ **Stability Filtering** - Choose stable-only or include pre-release versions
 57 | - ✅ **Enterprise Performance** - <100ms cached responses, native images
 58 | - ✅ **Analytical Intelligence** - Age analysis, release patterns, project health scoring
 59 | - ✅ **Context7 Orchestration** - Clear tool orchestration instructions with web search fallback
 60 | 
 61 | ### vs Manual Dependency Management
 62 | 
 63 | - ✅ **Risk Assessment** - Identify breaking changes before upgrading
 64 | - ✅ **Universal Support** - Works with any JVM build tool
 65 | - ✅ **Complete Analysis** - All version types with intelligent prioritization
 66 | - ✅ **Maintenance Intelligence** - Predict maintenance activity and sustainability
 67 | 
 68 | ## Setup for Claude Desktop
 69 | 
 70 | **Step 1:** Locate your Claude Desktop configuration file
 71 | 
 72 | - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
 73 | - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
 74 | - **Linux:** `~/.config/Claude/claude_desktop_config.json`
 75 | 
 76 | **Step 2:** Add this configuration (using pre-built Docker image):
 77 | 
 78 | ```json
 79 | {
 80 |   "mcpServers": {
 81 |     "maven-tools": {
 82 |       "command": "docker",
 83 |       "args": [
 84 |         "run", "-i", "--rm",
 85 |         "arvindand/maven-tools-mcp:latest"
 86 |       ]
 87 |     }
 88 |   }
 89 | }
 90 | ```
 91 | 
 92 | **Step 3:** Restart Claude Desktop
 93 | 
 94 | **Prerequisites:** Docker installed and running
 95 | 
 96 | **Note:** The Docker image supports both AMD64 (Intel/AMD) and ARM64 (Apple Silicon) architectures. Docker automatically selects the correct version for your platform.
 97 | 
 98 | **Troubleshooting:** Context7 integration is enabled by default and contacts `https://mcp.context7.com` during startup. If your network blocks this URL the server prints a Spring stack trace to `stdout`, which causes the MCP handshake to fail. Use the Context7-free native image instead: `arvindand/maven-tools-mcp:latest-noc7`. (Environment-variable toggles only work when running the JVM jar directly.)
 99 | 
100 | **Corporate Networks with SSL Inspection:** If you need Context7 integration but your network uses SSL inspection (MITM proxies), you can build a custom image with your corporate certificates. See the [Corporate Certificate Guide](CORPORATE-CERTIFICATES.md) for detailed instructions.
101 | 
102 | ## Setup for VS Code with GitHub Copilot
103 | 
104 | **Option 1: Workspace Configuration** - Create `.vscode/mcp.json`:
105 | 
106 | ```json
107 | {
108 |   "servers": {
109 |     "maven-tools": {
110 |       "type": "stdio",
111 |       "command": "docker",
112 |       "args": ["run", "-i", "--rm", "arvindand/maven-tools-mcp:latest"]
113 |     }
114 |   }
115 | }
116 | ```
117 | 
118 | **Option 2: User Settings** - Add to your VS Code settings:
119 | 
120 | ```json
121 | {
122 |   "mcp": {
123 |     "servers": {
124 |       "maven-tools": {
125 |         "type": "stdio", 
126 |         "command": "docker",
127 |         "args": ["run", "-i", "--rm", "arvindand/maven-tools-mcp:latest"]
128 |       }
129 |     }
130 |   }
131 | }
132 | ```
133 | 
134 | **Usage:** Open Chat view (Ctrl+Alt+I), select Agent mode, then use the Tools button to enable Maven tools.
135 | 
136 | ## What it does
137 | 
138 | **Core Dependency Intelligence:**
139 | 
140 | - Get latest or stable versions of Maven dependencies
141 | - Check if specific versions exist
142 | - Bulk version checking for multiple dependencies
143 | - Compare versions and get update recommendations
144 | 
145 | **Advanced Analytics:**
146 | 
147 | - Analyze dependency age and freshness (fresh/current/aging/stale)
148 | - Assess maintenance activity and release patterns
149 | - Predict next release timeframes
150 | - Comprehensive project health scoring with risk assessment
151 | 
152 | ## Available Tools
153 | 
154 | ### Core Maven Intelligence Tools (8 tools)
155 | 
156 | | Tool | Purpose | Key Features |
157 | |------|---------|--------------|
158 | | `get_latest_version` | Get newest version by type with stability preferences | preferStable parameter, all version types |
159 | | `check_version_exists` | Verify if specific version exists with type info | Works with any JVM build tool |
160 | | `check_multiple_dependencies` | Check multiple dependencies with filtering | stableOnly parameter, bulk operations |
161 | | `compare_dependency_versions` | Compare current vs latest with upgrade recommendations | includeMigrationGuidance flag |
162 | | `analyze_dependency_age` | Classify dependencies as fresh/current/aging/stale | includeModernizationGuidance flag |
163 | | `analyze_release_patterns` | Analyze maintenance activity and predict releases | monthsToAnalyze parameter, velocity trends |
164 | | `get_version_timeline` | Enhanced version timeline with temporal analysis | versionCount parameter, release gap detection |
165 | | `analyze_project_health` | Comprehensive health analysis for multiple dependencies | includeUpgradeStrategy flag |
166 | 
167 | ### Raw Context7 Documentation Tools (2 tools - Enabled by Default)
168 | 
169 | | Tool | Purpose | Key Features |
170 | |------|---------|--------------|
171 | | `resolve-library-id` | Search for library documentation | Always available (context7.enabled=true by default) |
172 | | `get-library-docs` | Get library documentation by ID | Always available (context7.enabled=true by default) |
173 | 
174 | ### Tool Parameters
175 | 
176 | **Core Parameters:**
177 | 
178 | - `preferStable` - Prioritize stable versions in analysis
179 | - `stableOnly` - Filter to production-ready versions only
180 | - `onlyStableTargets` - Only suggest upgrades to stable versions
181 | 
182 | **Analytical Parameters:**
183 | 
184 | - `maxAgeInDays` - Set acceptable age threshold for dependencies
185 | - `monthsToAnalyze` - Specify analysis period for release patterns (default: 24)
186 | - `versionCount` - Number of recent versions to analyze in timeline (default: 20)
187 | - `includeRecommendations` - Include detailed recommendations in health analysis
188 | 
189 | **Context7 Integration:**
190 | 
191 | Context7 integration is **enabled by default** (`context7.enabled=true`). Maven tools automatically include explicit orchestration instructions in response models when upgrades or modernization are needed. Additionally, the server acts as an MCP client to expose raw Context7 tools (`resolve-library-id`, `get-library-docs`) directly to your AI assistant. When disabled, responses contain only core dependency analysis without orchestration instructions or Context7 tools.
192 | 
193 | **Universal Compatibility:**
194 | All tools work with standard Maven coordinates (`groupId:artifactId`) and support any JVM build tool.
195 | 
196 | ### `get_latest_version`
197 | 
198 | Get latest version of any dependency from Maven Central (works with Maven, Gradle, SBT, Mill) with stability preferences.
199 | 
200 | **Parameters:**
201 | 
202 | - `dependency` (string, required): Maven coordinate in format `groupId:artifactId` (NO version)
203 | - `preferStable` (boolean, optional): When true, prioritizes stable version in response (default: false)
204 | 
205 | **Examples:**
206 | 
207 | ```json
208 | {
209 |   "dependency": "org.springframework:spring-core",
210 |   "preferStable": false
211 | }
212 | ```
213 | 
214 | ```json
215 | {
216 |   "dependency": "org.springframework:spring-boot",
217 |   "preferStable": true
218 | }
219 | ```
220 | 
221 | **Response:**
222 | 
223 | ```json
224 | {
225 |   "dependency": "org.springframework:spring-core",
226 |   "latest_stable": { "version": "6.2.7", "type": "stable" },
227 |   "latest_rc": { "version": "7.0.0-RC1", "type": "rc" },
228 |   "latest_beta": { "version": "7.0.0-beta1", "type": "beta" },
229 |   "latest_alpha": { "version": "7.0.0-alpha1", "type": "alpha" },
230 |   "latest_milestone": { "version": "7.0.0-M5", "type": "milestone" },
231 |   "total_versions": 100
232 | }
233 | ```
234 | 
235 | ### `check_version_exists`
236 | 
237 | Check if specific dependency version exists and identify its stability type. Works with any JVM build tool.
238 | 
239 | **Parameters:**
240 | 
241 | - `dependency` (string, required): Maven coordinate in format `groupId:artifactId` (NO version)
242 | - `version` (string, required): Version to check
243 | 
244 | **Example:**
245 | 
246 | ```json
247 | {
248 |   "dependency": "org.jetbrains.kotlin:kotlin-stdlib",
249 |   "version": "1.9.0"
250 | }
251 | ```
252 | 
253 | **Response:**
254 | 
255 | ```json
256 | {
257 |   "exists": true,
258 |   "version": "6.0.0",
259 |   "type": "stable"
260 | }
261 | ```
262 | 
263 | ### `check_multiple_dependencies`
264 | 
265 | Check latest versions for multiple dependencies with filtering options. Works with any JVM build tool.
266 | 
267 | **Parameters:**
268 | 
269 | - `dependencies` (string, required): Comma- or newline-separated list of Maven coordinates (NO versions)
270 | - `stableOnly` (boolean, optional): When true, filters to production-ready versions only (default: false)
271 | 
272 | **Examples:**
273 | 
274 | ```json
275 | {
276 |   "dependencies": "org.jetbrains.kotlin:kotlin-stdlib,com.squareup.retrofit2:retrofit,org.apache.spark:spark-core_2.13",
277 |   "stableOnly": false
278 | }
279 | ```
280 | 
281 | ```json
282 | {
283 |   "dependencies": "org.springframework:spring-boot,com.fasterxml.jackson.core:jackson-core",
284 |   "stableOnly": true
285 | }
286 | ```
287 | 
288 | **Response (array):**
289 | 
290 | ```json
291 | [
292 |   {
293 |     "dependency": "org.springframework:spring-core",
294 |     "primary_version": "6.2.7",
295 |     "primary_type": "stable",
296 |     "total_versions": 100,
297 |     "stable_versions": 82,
298 |     "latest_stable": { "version": "6.2.7", "type": "stable" },
299 |     "latest_rc": { "version": "7.0.0-RC1", "type": "rc" },
300 |     "latest_beta": null,
301 |     "latest_alpha": null,
302 |     "latest_milestone": { "version": "7.0.0-M5", "type": "milestone" }
303 |   },
304 |   // ...more results
305 | ]
306 | ```
307 | 
308 | ### `compare_dependency_versions`
309 | 
310 | Compare current dependency versions with latest available and show upgrade recommendations with safety controls.
311 | 
312 | **Parameters:**
313 | 
314 | - `currentDependencies` (string, required): Comma- or newline-separated list of Maven coordinates with versions (`groupId:artifactId:version`)
315 | - `onlyStableTargets` (boolean, optional): When true, only suggests upgrades to stable versions (default: false)
316 | 
317 | **Examples:**
318 | 
319 | ```json
320 | {
321 |   "currentDependencies": "org.jetbrains.kotlin:kotlin-stdlib:1.8.0,com.squareup.retrofit2:retrofit:2.9.0",
322 |   "onlyStableTargets": false
323 | }
324 | ```
325 | 
326 | ```json
327 | {
328 |   "currentDependencies": "org.springframework:spring-boot:2.7.0,org.hibernate:hibernate-core:5.6.0",
329 |   "onlyStableTargets": true
330 | }
331 | ```
332 | 
333 | **Response:**
334 | 
335 | ```json
336 | {
337 |   "comparison_date": "2025-06-07T22:38:47Z",
338 |   "dependencies": [
339 |     {
340 |       "dependency": "org.springframework:spring-core:6.0.0",
341 |       "current_version": "6.0.0",
342 |       "latest_version": "7.0.0-M5",
343 |       "latest_type": "milestone",
344 |       "update_type": "major",
345 |       "update_available": true,
346 |       "status": "success",
347 |       "error": null
348 |     }
349 |   ],
350 |   "update_summary": {
351 |     "major_updates": 1,
352 |     "minor_updates": 0,
353 |     "patch_updates": 0,
354 |     "no_updates": 0
355 |   }
356 | }
357 | ```
358 | 
359 | ### Raw Context7 MCP Tools
360 | 
361 | **Note:** Context7 integration is enabled by default. The following raw Context7 MCP tools are automatically available through the server's dual MCP architecture (acting as both MCP server and MCP client):
362 | 
363 | ### `resolve-library-id`
364 | 
365 | Search for library documentation using intelligent name resolution.
366 | 
367 | **Parameters:**
368 | 
369 | - `libraryName` (string, required): Search term for library lookup (e.g., "spring boot", "testcontainers")
370 | 
371 | **Example:**
372 | 
373 | ```json
374 | {
375 |   "libraryName": "testcontainers postgresql"
376 | }
377 | ```
378 | 
379 | ### `get-library-docs`
380 | 
381 | Get comprehensive documentation for a library using its Context7 ID.
382 | 
383 | **Parameters:**
384 | 
385 | - `context7CompatibleLibraryID` (string, required): Context7-compatible library ID (from resolve-library-id)
386 | - `topic` (string, optional): Topic for focused documentation (e.g., "setup", "migration", "configuration")
387 | - `tokens` (integer, optional): Maximum tokens to retrieve (default: 10000)
388 | 
389 | **Example:**
390 | 
391 | ```json
392 | {
393 |   "context7CompatibleLibraryID": "/testcontainers/testcontainers-java",
394 |   "topic": "postgresql setup",
395 |   "tokens": 5000
396 | }
397 | ```
398 | 
399 | These tools are automatically available by default through Spring AI MCP client integration. The server acts as both an MCP server (exposing Maven tools) and an MCP client (exposing Context7 tools), providing a unified interface for both dependency analysis and documentation access.
400 | 
401 | ## Usage Examples
402 | 
403 | ### Getting Started Examples
404 | 
405 | **Simple Questions:**
406 | 
407 | - "Get latest Spring Boot version but prioritize stable releases"
408 | - "Check if Kotlin 1.9.0 exists and what stability type it is"
409 | - "Show me latest stable version of Retrofit for production deployment"
410 | 
411 | **Multi-Build Tool Support:**
412 | 
413 | - "Check these Gradle dependencies: org.jetbrains.kotlin:kotlin-stdlib,com.squareup.retrofit2:retrofit"
414 | - "I need stable versions only for my SBT project dependencies"
415 | - "Compare my Maven versions but only suggest stable upgrades for production"
416 | 
417 | **Advanced Stability Controls:**
418 | 
419 | - "Check multiple dependencies but filter to stable versions only"
420 | - "Compare my current versions with onlyStableTargets=true for safety"
421 | - "Get complete analysis but prefer stable versions in results"
422 | 
423 | ## 🚀 Real-World Use Cases
424 | 
425 | ### Gradle Project Analysis
426 | 
427 | **Action:** Paste your build.gradle: *"Analyze my Gradle dependencies for outdated versions"*  
428 | **Result:** Universal dependency analysis in seconds across any build tool
429 | 
430 | ### Security Response  
431 | 
432 | **Action:** *"Show me latest stable versions for these affected dependencies"*  
433 | **Result:** Instant security patch identification with production-safe recommendations
434 | 
435 | ### Multi-Build Tool Projects
436 | 
437 | **Action:** *"What are the latest stable versions for Spring Boot, Spring Security, and Jackson for both Maven and Gradle?"*  
438 | **Result:** Universal dependency intelligence across all JVM build tools
439 | 
440 | ### Migration Planning with Risk Assessment
441 | 
442 | **Action:** *"Compare my current versions but only suggest stable upgrades for production safety"*  
443 | **Result:** Risk-assessed upgrade recommendations with stability filtering
444 | 
445 | ## 🆚 Why Not Just Web Search?
446 | 
447 | | Scenario | Web Search | Maven Tools MCP |
448 | |----------|------------|------------------|
449 | | Single dependency lookup | 3-5 seconds | <100ms (cached) |
450 | | 20 dependencies across build tools | 60+ seconds | <500ms |
451 | | Data accuracy | Variable/outdated | 100% current |
452 | | Bulk operations | Manual, error-prone | Native support |
453 | | Version classification | Manual parsing | Automatic (stable/RC/beta) |
454 | | Stability filtering | Not available | Built-in (stableOnly, preferStable) |
455 | | Build tool compatibility | Tool-specific searches | Universal JVM support |
456 | 
457 | ## ✨ Advanced Features Examples
458 | 
459 | ### Analytical Intelligence & Documentation Enrichment
460 | 
461 | ### Dependency Age Analysis
462 | 
463 | **Usage:** *"How old is my Spring Boot dependency and should I update it?"*  
464 | **Tool:** `analyze_dependency_age`
465 | 
466 | ```json
467 | {
468 |   "dependency": "org.springframework.boot:spring-boot-starter",
469 |   "age_classification": "current",
470 |   "days_since_release": 45,
471 |   "recommendation": "Actively maintained - consider updating if needed"
472 | }
473 | ```
474 | 
475 | ### Release Pattern Analysis  
476 | 
477 | **Usage:** *"What's the maintenance pattern for Jackson? When might the next release be?"*  
478 | **Tool:** `analyze_release_patterns`
479 | 
480 | ```json
481 | {
482 |   "dependency": "com.fasterxml.jackson.core:jackson-core",
483 |   "maintenance_level": "active",
484 |   "release_velocity": 1.2,
485 |   "next_release_prediction": "Expected in 3 weeks"
486 | }
487 | ```
488 | 
489 | ### Project Health Check
490 | 
491 | **Usage:** *"Give me a health assessment for all my key dependencies"*  
492 | **Tool:** `analyze_project_health`
493 | 
494 | ```json
495 | {
496 |   "overall_health": "good",
497 |   "average_health_score": 78,
498 |   "age_distribution": {"fresh": 2, "current": 8, "aging": 3, "stale": 1}
499 | }
500 | ```
501 | 
502 | ### Version Timeline Intelligence
503 | 
504 | **Usage:** *"Show me the recent release timeline for JUnit with gap analysis"*  
505 | **Tool:** `get_version_timeline`
506 | 
507 | ```json
508 | {
509 |   "insights": ["High release frequency indicates active development"],
510 |   "recent_activity": {"activity_level": "active", "releases_last_quarter": 4}
511 | }
512 | ```
513 | 
514 | ## Features
515 | 
516 | - Version lookup (latest, stable, or specific versions)
517 | - Version type classification (stable, RC, beta, alpha, milestone)
518 | - Bulk operations for multiple dependencies
519 | - Version comparison tools
520 | - **Dependency age analysis with actionable insights**
521 | - **Maintenance pattern analysis and predictions**
522 | - **Project health scoring and recommendations**
523 | - **Context7 migration guidance and upgrade strategies**
524 | - **Documentation enrichment for complex upgrades**
525 | - Caching for better performance
526 | - Works with MCP-compatible AI assistants
527 | 
528 | > **Note:** Snapshot versions are not supported. This is because the Maven Central API does not index or provide access to snapshot artifacts. Only released versions (stable, rc, beta, alpha, milestone) are available.
529 | 
530 | ## Context7 Guided Delegation Architecture
531 | 
532 | **Default Behavior:** Context7 integration is **enabled by default**. The server acts as both an MCP server (providing Maven tools) and an MCP client (exposing Context7 tools), giving your AI assistant access to both dependency intelligence and documentation guidance in a single connection. When disabled (`context7.enabled=false`), Maven tools work independently without Context7 guidance hints or raw Context7 tools.
533 | 
534 | ### Dual MCP Architecture
535 | 
536 | Maven Tools MCP uses a **dual MCP architecture** with guided delegation for Context7 integration:
537 | 
538 | 1. **MCP Server:** Provides 8 Maven dependency analysis tools with intelligent Context7 guidance hints
539 | 2. **MCP Client:** Acts as Context7 MCP client to expose raw Context7 tools (`resolve-library-id`, `get-library-docs`)
540 | 3. **Intelligent Integration:** Maven tools include smart Context7 search suggestions when upgrades/modernization are needed
541 | 4. **Direct Access:** Your AI assistant can use both Maven analysis AND Context7 documentation tools in a single connection
542 | 
543 | This dual architecture provides both dependency intelligence and documentation access through one MCP server connection, with intelligent guidance for effective Context7 tool usage.
544 | 
545 | ### Context7 Tools (Enabled by Default)
546 | 
547 | Context7 tools are automatically enabled by default. To disable Context7 integration entirely, use the `-noc7` image variant:
548 | 
549 | ```json
550 | {
551 |   "mcpServers": {
552 |     "maven-tools": {
553 |       "command": "docker",
554 |       "args": [
555 |         "run", "-i", "--rm",
556 |         "arvindand/maven-tools-mcp:latest-noc7"
557 |       ]
558 |     }
559 |   }
560 | }
561 | ```
562 | 
563 | **Note:** Environment variable toggles (`-e CONTEXT7_ENABLED=false`) only work when running the JVM jar directly, not with native images. Native images have configuration compiled in at build time. Use the `-noc7` image variants for a pure Maven tools experience without Context7 integration.
564 | 
565 | ### Image Variants
566 | 
567 | | Image Tag | Contents | When to Use |
568 | |-----------|----------|-------------|
569 | | `arvindand/maven-tools-mcp:latest` | Native image with Context7 tools enabled | Default experience with documentation integration |
570 | | `arvindand/maven-tools-mcp:latest-noc7` | Native image without Context7 client/tools | Environments without outbound access to `mcp.context7.com` or where only Maven tools are desired |
571 | | `arvindand/maven-tools-mcp:<version>` | Version-specific multi-arch image (Context7 enabled) | Pin to an exact release |
572 | | `arvindand/maven-tools-mcp:<version>-noc7` | Version-specific image without Context7 | Pin to exact release without Context7 |
573 | 
574 | ### Context7 Orchestration Instructions
575 | 
576 | **Intelligent LLM Orchestration:**
577 | 
578 | Maven Tools MCP provides explicit orchestration instructions in response models to help LLMs effectively use the raw Context7 MCP tools when documentation is needed. These clear step-by-step instructions include web search fallback for resilient documentation access.
579 | 
580 | **Context7 Orchestration Example:**
581 | 
582 | **Usage:** *"Compare my Spring Boot version and show upgrade path"*
583 | 
584 | **Tool:** `compare_dependency_versions`
585 | 
586 | ```json
587 | {
588 |   "dependencies": [{
589 |     "dependency": "org.springframework.boot:spring-boot-starter:2.7.0",
590 |     "current_version": "2.7.0",
591 |     "latest_version": "3.2.0", 
592 |     "update_type": "major",
593 |     "context7_guidance": {
594 |       "orchestration_instructions": "Use resolve-library-id tool with libraryName='spring-boot-starter' to find documentation ID. Then use get-library-docs tool with the returned Context7 ID and topic='migration guide' to get upgrade instructions. If Context7 doesn't provide sufficient information, perform a web search for 'spring-boot-starter major version upgrade guide'."
595 |     }
596 |   }]
597 | }
598 | ```
599 | 
600 | **Modernization Guidance Example:**
601 | 
602 | **Usage:** *"Analyze my aging dependencies with modernization suggestions"*
603 | 
604 | **Tool:** `analyze_dependency_age`
605 | 
606 | ```json
607 | {
608 |   "dependency": "org.hibernate:hibernate-core",
609 |   "age_classification": "AGING",
610 |   "days_since_last_release": 180,
611 |   "recommendation": "Consider upgrading - dependency is showing age",
612 |   "context7_guidance": {
613 |     "orchestration_instructions": "Use resolve-library-id tool with libraryName='hibernate-core' to find documentation ID. Then use get-library-docs tool with the returned Context7 ID and topic='modern usage and best practices' to get modernization guidance. If Context7 doesn't provide sufficient information, perform a web search for 'hibernate-core latest features best practices'."
614 |   }
615 | }
616 | ```
617 | 
618 | ## Performance Notes
619 | 
620 | - **Cache effectiveness:** ~90% of repeated requests served from cache
621 | - **Recommended batch sizes:** 10-20 dependencies for bulk operations
622 | - **First requests:** Build cache (normal), subsequent requests much faster
623 | - **Cache duration:** 24 hours
624 | 
625 | ## 🤔 Frequently Asked Questions
626 | 
627 | **Q: How is this different from Dependabot/Renovate?**  
628 | A: Those tools create automated PRs. This gives you instant, interactive dependency intelligence through your AI assistant for decision-making and planning.
629 | 
630 | **Q: How much time does this actually save?**  
631 | A: For single dependencies: from 3-5 seconds (web search) to <100ms. For 20+ dependencies: from 60+ seconds of manual searching to <500ms bulk analysis.
632 | 
633 | **Q: Why not just search Maven Central directly?**  
634 | A: This reads maven-metadata.xml files directly from Maven Central repository, providing structured, cached responses optimized for AI consumption with intelligent version classification and bulk operations - plus the time savings above.
635 | 
636 | **Q: Can this replace my IDE's dependency management?**  
637 | A: No, it complements your IDE by providing instant dependency intelligence during natural conversations with AI assistants for planning and decision-making.
638 | 
639 | **Q: What AI assistants does this work with?**  
640 | A: Any MCP-compatible assistant including Claude Desktop, GitHub Copilot, and other MCP clients. Works through natural conversation.
641 | 
642 | **Q: Does it work with private Maven repositories?**  
643 | A: Currently only Maven Central.
644 | 
645 | **Q: What about Gradle dependencies?**  
646 | A: Maven Central hosts both Maven and Gradle dependencies, so it works for Gradle projects too (using Maven coordinates).
647 | 
648 | **Q: What is Context7 and how does the guided delegation work?**  
649 | A: Context7 is an MCP server by Upstash that provides up-to-date documentation and code examples. Maven Tools MCP uses a guided delegation architecture - our tools provide explicit orchestration instructions to help your AI assistant effectively use the raw Context7 tools when documentation is needed. This includes clear step-by-step tool usage instructions with web search fallback for resilient documentation access.
650 | 
651 | ## Alternative Setup Methods
652 | 
653 | ### Using Docker Compose
654 | 
655 | **Alternative Claude Desktop configuration** (if you prefer compose):
656 | 
657 | Download `docker-compose.yml` and configure:
658 | 
659 | ```json
660 | {
661 |   "mcpServers": {
662 |     "maven-tools": {
663 |       "command": "docker",
664 |       "args": [
665 |         "compose", "-f", "/absolute/path/to/docker-compose.yml", 
666 |         "run", "--rm", "maven-tools-mcp"
667 |       ]
668 |     }
669 |   }
670 | }
671 | ```
672 | 
673 | **For development/testing only:**
674 | 
675 | ```bash
676 | docker compose up -d  # Runs server in background for testing
677 | ```
678 | 
679 | ### Build from Source (for contributors)
680 | 
681 | **Prerequisites:**
682 | 
683 | - Java 24
684 | - Maven 3.9+
685 | 
686 | ```bash
687 | # Clone the repository
688 | git clone https://github.com/arvindand/maven-tools-mcp.git
689 | cd maven-tools-mcp
690 | 
691 | # Quick build (CI-friendly - unit tests only)
692 | ./mvnw clean package -Pci
693 | 
694 | # Full build with all tests (requires network access)
695 | ./mvnw clean package -Pfull
696 | 
697 | # Run the JAR
698 | java -jar target/maven-tools-mcp-1.5.1-SNAPSHOT.jar
699 | ```
700 | 
701 | **Claude Desktop configuration for JAR:**
702 | 
703 | ```json
704 | {
705 |   "mcpServers": {
706 |     "maven-tools": {
707 |       "command": "java",
708 |       "args": [
709 |         "-jar",
710 |         "/absolute/path/to/maven-tools-mcp-1.5.1-SNAPSHOT.jar"
711 |       ]
712 |     }
713 |   }
714 | }
715 | ```
716 | 
717 | ### Build Scripts
718 | 
719 | For easier builds, use the provided scripts in the `build/` folder:
720 | 
721 | **Linux/macOS:**
722 | 
723 | ```bash
724 | cd build
725 | ./build.sh        # Complete build helper
726 | ./build-docker.sh # Docker-focused helper
727 | ```
728 | 
729 | **Windows:**
730 | 
731 | ```cmd
732 | cd build
733 | build.cmd         # Complete build helper
734 | build-docker.cmd  # Docker-focused helper
735 | ```
736 | 
737 | ## Enterprise & Custom Clients
738 | 
739 | This server implements MCP Protocol 2024-11-05 with stdio transport, making it compatible with any MCP-compliant client.
740 | 
741 | ## Configuration
742 | 
743 | The server can be configured via `application.yaml`:
744 | 
745 | ```yaml
746 | # Cache configuration
747 | spring:
748 |   cache:
749 |     type: caffeine
750 | 
751 | # Maven Central Repository settings
752 | maven:
753 |   central:
754 |     repository-base-url: https://repo1.maven.org/maven2
755 |     timeout: 10s
756 |     max-results: 100
757 | 
758 | # Logging (minimal for MCP stdio transport)
759 | logging:
760 |   level:
761 |     root: ERROR
762 | ```
763 | 
764 | ## Technical Details
765 | 
766 | - **Framework**: Spring Boot 3.5.6 with [Spring AI MCP](https://docs.spring.io/spring-ai/reference/api/mcp.html)
767 | - **MCP Protocol**: 2024-11-05
768 | - **Java Version**: 24
769 | - **Transport**: stdio
770 | - **HTTP Client**: OkHttp 5.2.1 with HTTP/2 support
771 | - **Cache**: Caffeine (24-hour TTL, 2000 entries max)
772 | - **Resilience**: Circuit breaker, retry, and rate limiter patterns
773 | - **Data Source**: Maven Central Repository (maven-metadata.xml files)
774 | 
775 | ## References & Resources
776 | 
777 | ### Model Context Protocol (MCP)
778 | 
779 | - **Official Website**: [modelcontextprotocol.io](https://modelcontextprotocol.io/)
780 | - **GitHub Repository**: [modelcontextprotocol/specification](https://github.com/modelcontextprotocol/specification)
781 | - **Protocol Documentation**: [MCP Specification](https://spec.modelcontextprotocol.io/)
782 | 
783 | ### Spring AI MCP
784 | 
785 | - **Documentation**: [Spring AI MCP Reference](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html)
786 | - **GitHub**: [spring-projects/spring-ai](https://github.com/spring-projects/spring-ai)
787 | 
788 | ### Maven Central Repository
789 | 
790 | - **Repository**: [repo1.maven.org](https://repo1.maven.org/maven2/)
791 | - **Metadata Format**: [Maven Metadata XML Reference](https://maven.apache.org/ref/3.9.6/maven-repository-metadata/)
792 | - **Search API**: [search.maven.org](https://search.maven.org/) (not used in v1.4.0+)
793 | 
794 | ### Context7 MCP Server
795 | 
796 | - **GitHub Repository**: [upstash/context7](https://github.com/upstash/context7)
797 | - **NPM Package**: [@upstash/context7-mcp](https://www.npmjs.com/package/@upstash/context7-mcp)
798 | - **Documentation**: [Upstash Context7 Blog](https://upstash.com/blog/context7-mcp)
799 | 
800 | ## 📝 Community & Discussion
801 | 
802 | **Blog Posts:**
803 | 
804 | - [How I Connected Claude to Maven Central (and Why You Should Too)](https://dev.to/arvindand/how-i-connected-claude-to-maven-central-and-why-you-should-too-2clo)
805 | - [Guided Delegation: Adding Context7 Documentation to My Maven Tools MCP Server](https://dev.to/arvindand/guided-delegation-adding-context7-documentation-to-my-maven-tools-mcp-server-572l)
806 | 
807 | ### Get Involved
808 | 
809 | - 💬 **Discuss:** Share your experiences and ask questions [on dev.to](https://dev.to/arvindand/how-i-connected-claude-to-maven-central-and-why-you-should-too-2clo)
810 | - 🐛 **Issues:** [Report bugs or request features](https://github.com/arvindand/maven-tools-mcp/issues)
811 | - ⭐ **Support:** Star this repo if it improves your workflow
812 | 
813 | ## License
814 | 
815 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
816 | 
817 | ## Author
818 | 
819 | Arvind Menon
820 | 
821 | - GitHub: [@arvindand](https://github.com/arvindand)
822 | - Version: 1.5.1
823 | 
```

--------------------------------------------------------------------------------
/src/main/resources/application-docker.yaml:
--------------------------------------------------------------------------------

```yaml
1 | # Docker profile - ensures clean JSON-RPC communication on STDIO
2 | # The banner and startup logs can interfere with JSON-RPC protocol on STDOUT
3 | spring:
4 |   main:
5 |     banner-mode: "off"  # Must be "off" not OFF - disable banner completely
6 |     log-startup-info: false  # Disable startup info logs on STDOUT
7 | 
```

--------------------------------------------------------------------------------
/src/main/resources/application-no-context7.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Profile to completely disable Context7 for native image builds
 2 | spring:
 3 |   ai:
 4 |     mcp:
 5 |       client:
 6 |         enabled: false  # Disable MCP client completely
 7 |         toolcallback:
 8 |           enabled: false  # Disable tool callbacks
 9 |   main:
10 |     banner-mode: "off"  # Disable banner
11 |     log-startup-info: false
12 | 
13 | # Disable Context7 guidance hints
14 | context7:
15 |   enabled: false
16 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/Context7Properties.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | import org.springframework.boot.context.properties.ConfigurationProperties;
 4 | 
 5 | /**
 6 |  * Configuration properties for Context7 MCP integration.
 7 |  *
 8 |  * @param enabled whether Context7 integration is enabled (exposes raw Context7 MCP tools and
 9 |  *     includes guidance hints)
10 |  * @author Arvind Menon
11 |  * @since 1.2.0
12 |  */
13 | @ConfigurationProperties(prefix = "context7")
14 | public record Context7Properties(boolean enabled) {}
15 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/CacheConstants.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | /**
 4 |  * Cache names used throughout the Maven Tools MCP application.
 5 |  *
 6 |  * @author Arvind Menon
 7 |  * @since 1.3.0
 8 |  */
 9 | public final class CacheConstants {
10 | 
11 |   // Maven Central cache names
12 |   public static final String MAVEN_VERSION_CHECKS = "maven-version-checks";
13 |   public static final String MAVEN_ALL_VERSIONS = "maven-all-versions";
14 |   public static final String MAVEN_ACCURATE_HISTORICAL_DATA = "maven-accurate-historical-data";
15 | 
16 |   private CacheConstants() {
17 |     // Utility class
18 |   }
19 | }
20 | 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # Docker Compose for Maven Tools MCP Server
 2 | # This provides an easy way to run the MCP server with pre-built images
 3 | 
 4 | services:
 5 |   maven-tools-mcp:
 6 |     image: arvindand/maven-tools-mcp:latest
 7 |     container_name: maven-tools-mcp
 8 |     
 9 |     # MCP uses stdio transport - stdin_open required, tty should be false
10 |     stdin_open: true
11 |     tty: false  # Changed: tty interferes with JSON-RPC stdio
12 |     
13 |     # Network access for Maven Central API
14 |     network_mode: "bridge"
15 |     
16 |     # Environment variables for configuration
17 |     environment:
18 |       - SPRING_PROFILES_ACTIVE=docker
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/config/Context7PropertiesTest.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | import static org.junit.jupiter.api.Assertions.assertTrue;
 4 | 
 5 | import org.junit.jupiter.api.Test;
 6 | import org.springframework.beans.factory.annotation.Autowired;
 7 | import org.springframework.boot.test.context.SpringBootTest;
 8 | import org.springframework.test.context.ActiveProfiles;
 9 | 
10 | /**
11 |  * Test to verify Context7Properties binding.
12 |  *
13 |  * @author Arvind Menon
14 |  * @since 1.2.0
15 |  */
16 | @SpringBootTest
17 | @ActiveProfiles("test")
18 | class Context7PropertiesTest {
19 | 
20 |   @Autowired private Context7Properties context7Properties;
21 | 
22 |   @Test
23 |   void testContext7PropertiesEnabledByDefault() {
24 |     System.out.println("Context7Properties.enabled = " + context7Properties.enabled());
25 |     assertTrue(context7Properties.enabled(), "Context7 should be enabled by default");
26 |   }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/src/test/resources/application-test.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | logging:
 2 |   level:
 3 |     # Your app logs at INFO level (goes to STDERR via logback config)
 4 |     "[com.arvindand.mcp.maven]": DEBUG
 5 |     # Framework logs at ERROR level to minimize noise
 6 |     "[org.springframework.ai.mcp]": ERROR
 7 |     "[org.springframework.boot]": ERROR
 8 |     "[org.springframework]": ERROR
 9 |     "[org.apache.http]": ERROR
10 |     "[ch.qos.logback]": ERROR
11 |     root: ERROR
12 | 
13 | spring:
14 |   cache:
15 |     type: caffeine
16 |   ai:
17 |     mcp:
18 |       client:
19 |         enabled: false
20 |         type: SYNC
21 |         request-timeout: 5s
22 |         streamable-http:
23 |           connections:
24 |             context7:
25 |               url: https://mcp.context7.com
26 |     
27 | # Context7 configuration - enabled by default to match production
28 | # Individual tests can override via @TestConfiguration if needed
29 | context7:
30 |   enabled: true
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/MavenCentralProperties.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | import java.time.Duration;
 4 | import org.springframework.boot.context.properties.ConfigurationProperties;
 5 | import org.springframework.boot.context.properties.bind.DefaultValue;
 6 | 
 7 | /**
 8 |  * Configuration properties for Maven Central integration. Uses direct repository metadata access
 9 |  * for accurate version information.
10 |  *
11 |  * @param repositoryBaseUrl the base URL for direct Maven repository access
12 |  * @param timeout the timeout duration for API calls
13 |  * @param maxResults the maximum number of results to retrieve per request
14 |  * @author Arvind Menon
15 |  * @since 0.1.0
16 |  */
17 | @ConfigurationProperties(prefix = "maven.central")
18 | public record MavenCentralProperties(
19 |     @DefaultValue("https://repo1.maven.org/maven2") String repositoryBaseUrl,
20 |     @DefaultValue("10s") Duration timeout,
21 |     @DefaultValue("100") int maxResults) {}
22 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/service/MavenCentralException.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.service;
 2 | 
 3 | /**
 4 |  * Exception thrown when there are issues with Maven Central API interactions. This runtime
 5 |  * exception wraps various error conditions that can occur when communicating with Maven Central's
 6 |  * search API.
 7 |  *
 8 |  * @author Arvind Menon
 9 |  * @since 0.1.0
10 |  */
11 | public class MavenCentralException extends RuntimeException {
12 | 
13 |   /**
14 |    * Constructs a new MavenCentralException with the specified detail message.
15 |    *
16 |    * @param message the detail message
17 |    */
18 |   public MavenCentralException(String message) {
19 |     super(message);
20 |   }
21 | 
22 |   /**
23 |    * Constructs a new MavenCentralException with the specified detail message and cause.
24 |    *
25 |    * @param message the detail message
26 |    * @param cause the cause of this exception
27 |    */
28 |   public MavenCentralException(String message, Throwable cause) {
29 |     super(message, cause);
30 |   }
31 | }
32 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/MavenArtifact.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 4 | import com.fasterxml.jackson.annotation.JsonProperty;
 5 | 
 6 | /**
 7 |  * Represents a Maven artifact, typically containing version and timestamp information.
 8 |  *
 9 |  * @param id the unique identifier for this artifact
10 |  * @param groupId the Maven group identifier
11 |  * @param artifactId the Maven artifact identifier
12 |  * @param version the artifact version
13 |  * @param packaging the packaging type
14 |  * @param timestamp the timestamp when this artifact was published
15 |  */
16 | @JsonIgnoreProperties(ignoreUnknown = true)
17 | public record MavenArtifact(
18 |     @JsonProperty("id") String id,
19 |     @JsonProperty("g") String groupId,
20 |     @JsonProperty("a") String artifactId,
21 |     @JsonProperty("v") String version,
22 |     @JsonProperty("p") String packaging,
23 |     @JsonProperty("timestamp") long timestamp) {}
24 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/TestHelpers.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven;
 2 | 
 3 | import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 4 | 
 5 | import com.arvindand.mcp.maven.model.ToolResponse;
 6 | 
 7 | /**
 8 |  * Shared test utility methods for integration tests.
 9 |  *
10 |  * @author Arvind Menon
11 |  * @since 1.3.0
12 |  */
13 | public final class TestHelpers {
14 | 
15 |   private TestHelpers() {
16 |     // Prevent instantiation of utility class
17 |   }
18 | 
19 |   /**
20 |    * Extracts success data from ToolResponse for test assertions.
21 |    *
22 |    * @param <T> the expected type of the success data
23 |    * @param response the ToolResponse to extract data from
24 |    * @return the success data
25 |    * @throws AssertionError if the response is not a success response
26 |    */
27 |   @SuppressWarnings("unchecked")
28 |   public static <T> T getSuccessData(ToolResponse response) {
29 |     assertInstanceOf(
30 |         ToolResponse.Success.class, response, "Expected success response but got: " + response);
31 |     return (T) ((ToolResponse.Success<?>) response).data();
32 |   }
33 | }
34 | 
```

--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------

```
 1 | # Licensed to the Apache Software Foundation (ASF) under one
 2 | # or more contributor license agreements.  See the NOTICE file
 3 | # distributed with this work for additional information
 4 | # regarding copyright ownership.  The ASF licenses this file
 5 | # to you under the Apache License, Version 2.0 (the
 6 | # "License"); you may not use this file except in compliance
 7 | # with the License.  You may obtain a copy of the License at
 8 | #
 9 | #   http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied.  See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 | wrapperVersion=3.3.2
18 | distributionType=only-script
19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
20 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/JacksonConfig.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | import com.fasterxml.jackson.databind.ObjectMapper;
 4 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 6 | import org.springframework.context.annotation.Bean;
 7 | import org.springframework.context.annotation.Configuration;
 8 | import org.springframework.context.annotation.Primary;
 9 | 
10 | /**
11 |  * Jackson configuration for handling JDK8 types like Optional and Java 8 time types.
12 |  *
13 |  * @author Arvind Menon
14 |  * @since 1.2.0
15 |  */
16 | @Configuration(proxyBeanMethods = false)
17 | public class JacksonConfig {
18 | 
19 |   /**
20 |    * Configures ObjectMapper with JDK8 and JSR310 module support for Optional and time types.
21 |    *
22 |    * @return configured ObjectMapper
23 |    */
24 |   @Bean
25 |   @Primary
26 |   public ObjectMapper objectMapper() {
27 |     ObjectMapper mapper = new ObjectMapper();
28 |     mapper.registerModule(new Jdk8Module());
29 |     mapper.registerModule(new JavaTimeModule());
30 |     return mapper;
31 |   }
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/StabilityFilter.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | /**
 4 |  * Filter for controlling version stability preferences across MCP tools.
 5 |  *
 6 |  * <p>Provides unified parameter semantics for stability filtering, replacing the previous
 7 |  * inconsistent parameters (preferStable, stableOnly, onlyStableTargets).
 8 |  *
 9 |  * @author Arvind Menon
10 |  * @since 1.5.0
11 |  */
12 | public enum StabilityFilter {
13 | 
14 |   /**
15 |    * Include all version types (stable, RC, beta, alpha, milestone, snapshot).
16 |    *
17 |    * <p>Use when you want comprehensive version information regardless of stability.
18 |    */
19 |   ALL,
20 | 
21 |   /**
22 |    * Only include production-ready stable versions (excludes RC, beta, alpha, milestone, snapshot).
23 |    *
24 |    * <p>Use when you need only proven, production-ready releases.
25 |    */
26 |   STABLE_ONLY,
27 | 
28 |   /**
29 |    * Prioritize stable versions in results, but include other types if no stable exists.
30 |    *
31 |    * <p>Use when you prefer stable but want fallback options for libraries without stable releases.
32 |    */
33 |   PREFER_STABLE
34 | }
35 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/MavenMcpServerApplication.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven;
 2 | 
 3 | import com.arvindand.mcp.maven.config.MavenCentralProperties;
 4 | import org.springframework.boot.SpringApplication;
 5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
 7 | import org.springframework.cache.annotation.EnableCaching;
 8 | 
 9 | /**
10 |  * Main Spring Boot application class for the Maven MCP Server. This application provides Model
11 |  * Context Protocol (MCP) tools for Maven dependency management, including latest version lookup and
12 |  * version existence checks with caching support.
13 |  *
14 |  * @author Arvind Menon
15 |  * @since 0.1.0
16 |  */
17 | @SpringBootApplication
18 | @EnableCaching
19 | @EnableConfigurationProperties(MavenCentralProperties.class)
20 | public class MavenMcpServerApplication {
21 | 
22 |   /**
23 |    * Main method to start the Maven MCP Server application.
24 |    *
25 |    * @param args command line arguments
26 |    */
27 |   public static void main(String[] args) {
28 |     SpringApplication.run(MavenMcpServerApplication.class, args);
29 |   }
30 | }
31 | 
```

--------------------------------------------------------------------------------
/src/main/resources/logback-spring.xml:
--------------------------------------------------------------------------------

```
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <configuration>
 3 |     <!-- Console appender that writes to STDERR to avoid interfering with MCP JSON-RPC on STDOUT -->
 4 |     <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
 5 |         <target>System.err</target>
 6 |         <encoder>
 7 |             <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %5p --- [%15.15t] %-40.40logger{39} : %m%n</pattern>
 8 |         </encoder>
 9 |     </appender>
10 | 
11 |     <!-- Root logger configuration -->
12 |     <root level="ERROR">
13 |         <appender-ref ref="STDERR" />
14 |     </root>
15 | 
16 |     <!-- Application-specific logger -->
17 |     <logger name="com.arvindand.mcp.maven" level="INFO" additivity="false">
18 |         <appender-ref ref="STDERR" />
19 |     </logger>
20 | 
21 |     <!-- Spring Framework loggers -->
22 |     <logger name="org.springframework" level="ERROR" additivity="false">
23 |         <appender-ref ref="STDERR" />
24 |     </logger>
25 | 
26 |     <!-- Spring AI MCP loggers -->
27 |     <logger name="org.springframework.ai.mcp" level="ERROR" additivity="false">
28 |         <appender-ref ref="STDERR" />
29 |     </logger>
30 | </configuration>
31 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/VersionInfo.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.annotation.JsonValue;
 4 | 
 5 | /**
 6 |  * Version information for a Maven dependency with type-safe version classification.
 7 |  *
 8 |  * @param version the version string
 9 |  * @param type the version type classification
10 |  * @author Arvind Menon
11 |  * @since 0.1.0
12 |  */
13 | public record VersionInfo(String version, VersionType type) {
14 | 
15 |   /** Type-safe enumeration of Maven version types. */
16 |   public enum VersionType {
17 |     STABLE("stable"),
18 |     RC("rc"),
19 |     BETA("beta"),
20 |     ALPHA("alpha"),
21 |     MILESTONE("milestone");
22 | 
23 |     private final String displayName;
24 | 
25 |     VersionType(String displayName) {
26 |       this.displayName = displayName;
27 |     }
28 | 
29 |     @JsonValue
30 |     public String getDisplayName() {
31 |       return displayName;
32 |     }
33 | 
34 |     /** Parse version type from string, defaulting to STABLE for unknown types. */
35 |     public static VersionType fromString(String type) {
36 |       if (type == null) return STABLE;
37 |       return switch (type.toLowerCase()) {
38 |         case "rc" -> RC;
39 |         case "beta" -> BETA;
40 |         case "alpha" -> ALPHA;
41 |         case "milestone" -> MILESTONE;
42 |         default -> STABLE;
43 |       };
44 |     }
45 |   }
46 | }
47 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/McpToolsConfig.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | import com.arvindand.mcp.maven.service.MavenDependencyTools;
 4 | import org.springframework.ai.tool.ToolCallbackProvider;
 5 | import org.springframework.ai.tool.method.MethodToolCallbackProvider;
 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
 7 | import org.springframework.context.annotation.Bean;
 8 | import org.springframework.context.annotation.Configuration;
 9 | 
10 | /**
11 |  * Configuration for Maven dependency tools. Registers MCP tools as Spring beans using the Spring AI
12 |  * MCP Server Boot Starter pattern.
13 |  *
14 |  * @author Arvind Menon
15 |  * @since 0.1.0
16 |  */
17 | @Configuration
18 | @EnableConfigurationProperties({MavenCentralProperties.class, Context7Properties.class})
19 | public class McpToolsConfig {
20 | 
21 |   /**
22 |    * Bean for Maven dependency tools registration with MCP server.
23 |    *
24 |    * @param mavenDependencyTools the Maven dependency tools service
25 |    * @return list of tool callbacks for MCP server auto-configuration
26 |    */
27 |   @Bean
28 |   public ToolCallbackProvider mavenDependencyToolsCallbackProvider(
29 |       MavenDependencyTools mavenDependencyTools) {
30 |     return MethodToolCallbackProvider.builder().toolObjects(mavenDependencyTools).build();
31 |   }
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/ToolResponse.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
 5 | 
 6 | /**
 7 |  * Base interface for all MCP tool responses. This allows type-safe tool return types while
 8 |  * supporting both success and error responses.
 9 |  *
10 |  * @author Arvind Menon
11 |  * @since 1.3.0
12 |  */
13 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
14 | public sealed interface ToolResponse permits ToolResponse.Success, ToolResponse.Error {
15 | 
16 |   /** Success response containing the actual tool data. */
17 |   @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
18 |   record Success<T>(String status, T data) implements ToolResponse {
19 |     public static <T> Success<T> of(T data) {
20 |       return new Success<>("success", data);
21 |     }
22 |   }
23 | 
24 |   /** Error response with structured error details. */
25 |   @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
26 |   record Error(String status, McpError error) implements ToolResponse {
27 |     public static Error of(McpError error) {
28 |       return new Error("error", error);
29 |     }
30 | 
31 |     public static Error of(String message) {
32 |       return new Error("error", McpError.internalError(message));
33 |     }
34 | 
35 |     public static Error notFound(String message) {
36 |       return new Error("not_found", McpError.invalidInput(message, java.util.Map.of()));
37 |     }
38 |   }
39 | }
40 | 
```

--------------------------------------------------------------------------------
/src/main/resources/META-INF/additional-spring-configuration-metadata.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "properties": [
 3 |     {
 4 |       "name": "maven.central.repository-base-url",
 5 |       "type": "java.lang.String",
 6 |       "description": "The base URL for direct Maven repository access. Used for fetching maven-metadata.xml files directly for accurate version information.",
 7 |       "defaultValue": "https://repo1.maven.org/maven2"
 8 |     },
 9 |     {
10 |       "name": "maven.central.timeout",
11 |       "type": "java.lang.String",
12 |       "description": "The timeout duration for requests to the Maven Central repository (e.g., '10s' for 10 seconds)."
13 |     },
14 |     {
15 |       "name": "maven.central.max-results",
16 |       "type": "java.lang.String",
17 |       "description": "The maximum number of results to retrieve per request from the Maven Central repository."
18 |     },
19 |     {
20 |       "name": "maven.central.connection-pool-size",
21 |       "type": "java.lang.Integer",
22 |       "description": "The maximum number of idle connections to maintain in the OkHttp connection pool for Maven Central repository requests.",
23 |       "defaultValue": 50
24 |     },
25 |     {
26 |       "name": "context7.enabled",
27 |       "type": "java.lang.Boolean",
28 |       "description": "Whether Context7 guidance hints are included in Maven tool responses. When enabled, tools include intelligent search suggestions to help LLMs effectively use Context7 tools for migration guidance and upgrade strategies.",
29 |       "defaultValue": true
30 |     }
31 |   ]
32 | }
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/CacheConfig.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | import static com.arvindand.mcp.maven.config.CacheConstants.*;
 4 | 
 5 | import com.github.benmanes.caffeine.cache.Cache;
 6 | import com.github.benmanes.caffeine.cache.Caffeine;
 7 | import java.time.Duration;
 8 | import org.springframework.cache.CacheManager;
 9 | import org.springframework.cache.annotation.EnableCaching;
10 | import org.springframework.cache.caffeine.CaffeineCacheManager;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Configuration;
13 | 
14 | /**
15 |  * Cache configuration for Maven Tools MCP.
16 |  *
17 |  * <p>Configures cache regions for Maven Central API results with long TTL (24h) for stable data.
18 |  *
19 |  * @author Arvind Menon
20 |  * @since 1.2.0
21 |  */
22 | @Configuration
23 | @EnableCaching
24 | public class CacheConfig {
25 | 
26 |   @Bean
27 |   public CacheManager cacheManager() {
28 |     CaffeineCacheManager cacheManager = new CaffeineCacheManager();
29 | 
30 |     // Maven Central caches - long TTL since data is stable
31 |     cacheManager.registerCustomCache(MAVEN_VERSION_CHECKS, mavenCentralCache());
32 |     cacheManager.registerCustomCache(MAVEN_ALL_VERSIONS, mavenCentralCache());
33 |     cacheManager.registerCustomCache(MAVEN_ACCURATE_HISTORICAL_DATA, mavenCentralCache());
34 | 
35 |     return cacheManager;
36 |   }
37 | 
38 |   private Cache<Object, Object> mavenCentralCache() {
39 |     return Caffeine.newBuilder().maximumSize(2000).expireAfterWrite(Duration.ofHours(24)).build();
40 |   }
41 | }
42 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/DependencyInfo.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
 5 | 
 6 | /**
 7 |  * Represents comprehensive dependency information including existence, version details, and
 8 |  * metadata.
 9 |  *
10 |  * @param status the status of the dependency check (success, error, not_found)
11 |  * @param groupId the Maven group ID
12 |  * @param artifactId the Maven artifact ID
13 |  * @param version the specific version checked
14 |  * @param exists whether the dependency version exists in Maven Central
15 |  * @param type the version type classification (stable, rc, beta, alpha, milestone)
16 |  * @param isStable whether this is considered a stable version
17 |  * @param timestamp the release timestamp in milliseconds since epoch (if available)
18 |  * @author Arvind Menon
19 |  * @since 1.3.0
20 |  */
21 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
22 | public record DependencyInfo(
23 |     String status,
24 |     String groupId,
25 |     String artifactId,
26 |     String version,
27 |     boolean exists,
28 |     String type,
29 |     boolean isStable,
30 |     Long timestamp) {
31 |   public static DependencyInfo success(
32 |       MavenCoordinate coordinate,
33 |       String version,
34 |       boolean exists,
35 |       String type,
36 |       boolean isStable,
37 |       Long timestamp) {
38 |     return new DependencyInfo(
39 |         "success",
40 |         coordinate.groupId(),
41 |         coordinate.artifactId(),
42 |         version,
43 |         exists,
44 |         type,
45 |         isStable,
46 |         timestamp);
47 |   }
48 | }
49 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/VersionsByType.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
 5 | import java.util.Optional;
 6 | 
 7 | /**
 8 |  * Represents versions organized by their stability type for a dependency.
 9 |  *
10 |  * @param dependency the dependency coordinate (groupId:artifactId)
11 |  * @param latestStable latest stable version, if available
12 |  * @param latestRc latest release candidate version, if available
13 |  * @param latestBeta latest beta version, if available
14 |  * @param latestAlpha latest alpha version, if available
15 |  * @param latestMilestone latest milestone version, if available
16 |  * @param totalVersions total number of versions found
17 |  * @author Arvind Menon
18 |  * @since 1.2.0
19 |  */
20 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
21 | public record VersionsByType(
22 |     String dependency,
23 |     Optional<VersionInfo> latestStable,
24 |     Optional<VersionInfo> latestRc,
25 |     Optional<VersionInfo> latestBeta,
26 |     Optional<VersionInfo> latestAlpha,
27 |     Optional<VersionInfo> latestMilestone,
28 |     int totalVersions) {
29 | 
30 |   /** Creates a VersionsByType with primary version based on preference. */
31 |   public static VersionsByType create(
32 |       String dependency,
33 |       Optional<VersionInfo> stable,
34 |       Optional<VersionInfo> rc,
35 |       Optional<VersionInfo> beta,
36 |       Optional<VersionInfo> alpha,
37 |       Optional<VersionInfo> milestone,
38 |       int totalVersions) {
39 |     return new VersionsByType(dependency, stable, rc, beta, alpha, milestone, totalVersions);
40 |   }
41 | 
42 |   /** Gets the preferred version based on stability preference. */
43 |   public Optional<VersionInfo> getPreferredVersion(boolean preferStable) {
44 |     if (preferStable && latestStable.isPresent()) {
45 |       return latestStable;
46 |     }
47 | 
48 |     // Return first available version in order of stability
49 |     return latestStable
50 |         .or(() -> latestRc)
51 |         .or(() -> latestBeta)
52 |         .or(() -> latestAlpha)
53 |         .or(() -> latestMilestone);
54 |   }
55 | }
56 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/HttpClientConfig.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.config;
 2 | 
 3 | import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
 4 | import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
 5 | import io.github.resilience4j.retry.RetryRegistry;
 6 | import java.time.Duration;
 7 | import java.util.List;
 8 | import java.util.concurrent.TimeUnit;
 9 | import okhttp3.ConnectionPool;
10 | import okhttp3.OkHttpClient;
11 | import okhttp3.Protocol;
12 | import org.springframework.beans.factory.annotation.Value;
13 | import org.springframework.context.annotation.Bean;
14 | import org.springframework.context.annotation.Configuration;
15 | import org.springframework.web.client.RestClient;
16 | 
17 | /**
18 |  * HTTP client configuration with OkHttp for improved performance and HTTP/2 support.
19 |  *
20 |  * <p>Configures connection pooling, timeouts, and protocols for Maven Central API access.
21 |  *
22 |  * @author Arvind Menon
23 |  * @since 1.5.0
24 |  */
25 | @Configuration
26 | public class HttpClientConfig {
27 | 
28 |   @Bean
29 |   OkHttpClient okHttpClient(
30 |       @Value("${maven.central.connection-pool-size:50}") int poolSize,
31 |       @Value("${maven.central.timeout:8s}") Duration timeout) {
32 | 
33 |     ConnectionPool connectionPool =
34 |         new ConnectionPool(
35 |             poolSize, // maxIdleConnections
36 |             24, // keepAliveDuration
37 |             TimeUnit.HOURS);
38 | 
39 |     return new OkHttpClient.Builder()
40 |         .connectionPool(connectionPool)
41 |         .connectTimeout(timeout)
42 |         .readTimeout(timeout.plusSeconds(2))
43 |         .writeTimeout(timeout)
44 |         .protocols(List.of(Protocol.HTTP_2, Protocol.HTTP_1_1))
45 |         .retryOnConnectionFailure(true)
46 |         .build();
47 |   }
48 | 
49 |   @Bean
50 |   RestClient mavenCentralRestClient(OkHttpClient okHttpClient) {
51 |     return RestClient.builder().build();
52 |   }
53 | 
54 |   @Bean
55 |   CircuitBreakerRegistry circuitBreakerRegistry() {
56 |     return CircuitBreakerRegistry.ofDefaults();
57 |   }
58 | 
59 |   @Bean
60 |   RetryRegistry retryRegistry() {
61 |     return RetryRegistry.ofDefaults();
62 |   }
63 | 
64 |   @Bean
65 |   RateLimiterRegistry rateLimiterRegistry() {
66 |     return RateLimiterRegistry.ofDefaults();
67 |   }
68 | }
69 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/MavenCoordinate.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 4 | 
 5 | /**
 6 |  * Represents a Maven coordinate with groupId, artifactId, version, packaging, and classifier. This
 7 |  * record provides an immutable representation of Maven artifact coordinates.
 8 |  *
 9 |  * @param groupId the Maven group identifier
10 |  * @param artifactId the Maven artifact identifier
11 |  * @param version the artifact version (optional)
12 |  * @param packaging the packaging type (optional, defaults to 'jar')
13 |  * @param classifier the artifact classifier (optional)
14 |  * @author Arvind Menon
15 |  * @since 0.1.0
16 |  */
17 | @JsonIgnoreProperties(ignoreUnknown = true)
18 | public record MavenCoordinate(
19 |     String groupId, String artifactId, String version, String packaging, String classifier) {
20 | 
21 |   /**
22 |    * Creates a MavenCoordinate with groupId and artifactId only.
23 |    *
24 |    * @param groupId the Maven group identifier
25 |    * @param artifactId the Maven artifact identifier
26 |    * @return a new MavenCoordinate instance
27 |    */
28 |   public static MavenCoordinate of(String groupId, String artifactId) {
29 |     return new MavenCoordinate(groupId, artifactId, null, null, null);
30 |   }
31 | 
32 |   /**
33 |    * Creates a MavenCoordinate with groupId, artifactId, and version.
34 |    *
35 |    * @param groupId the Maven group identifier
36 |    * @param artifactId the Maven artifact identifier
37 |    * @param version the artifact version
38 |    * @return a new MavenCoordinate instance
39 |    */
40 |   public static MavenCoordinate of(String groupId, String artifactId, String version) {
41 |     return new MavenCoordinate(groupId, artifactId, version, null, null);
42 |   }
43 | 
44 |   /**
45 |    * Creates a formatted Maven coordinate string for display.
46 |    *
47 |    * @return the formatted coordinate string in Maven standard format
48 |    */
49 |   public String toCoordinateString() {
50 |     StringBuilder sb = new StringBuilder();
51 |     sb.append(groupId).append(":").append(artifactId);
52 |     if (version != null) {
53 |       sb.append(":").append(version);
54 |     }
55 |     if (packaging != null) {
56 |       sb.append(":").append(packaging);
57 |     }
58 |     if (classifier != null) {
59 |       sb.append(":").append(classifier);
60 |     }
61 |     return sb.toString();
62 |   }
63 | }
64 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/DependencyAge.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
 5 | import java.time.Instant;
 6 | import java.util.Optional;
 7 | 
 8 | /**
 9 |  * Comprehensive dependency age information with analysis and guidance.
10 |  *
11 |  * @param dependency the Maven coordinate analyzed
12 |  * @param latestVersion the latest version found
13 |  * @param daysSinceLastRelease days since the latest version was released
14 |  * @param lastReleaseDate ISO formatted date of the last release
15 |  * @param ageClassification age classification (fresh/current/aging/stale)
16 |  * @param ageDescription human-readable description of the dependency's age status
17 |  * @param recommendation actionable recommendation based on age analysis
18 |  * @param context7Guidance optional Context7 guidance for deeper integration insights
19 |  * @param maxAgeInDays the configured threshold for age classification
20 |  * @author Arvind Menon
21 |  * @since 1.3.0
22 |  */
23 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
24 | public record DependencyAge(
25 |     String dependency,
26 |     String latestVersion,
27 |     DependencyAgeAnalysis.AgeClassification ageClassification,
28 |     long daysSinceLastRelease,
29 |     Instant lastReleaseDate,
30 |     String ageDescription,
31 |     String recommendation,
32 |     Optional<Context7Guidance> context7Guidance) {
33 | 
34 |   /** Creates a response from a basic DependencyAgeAnalysis with Context7 guidance. */
35 |   public static DependencyAge from(DependencyAgeAnalysis analysis, boolean context7Enabled) {
36 |     // Add Context7 guidance for aging/stale dependencies when Context7 is enabled
37 |     Optional<Context7Guidance> guidance =
38 |         (context7Enabled
39 |                 && (analysis.ageClassification() == DependencyAgeAnalysis.AgeClassification.AGING
40 |                     || analysis.ageClassification()
41 |                         == DependencyAgeAnalysis.AgeClassification.STALE))
42 |             ? Optional.of(
43 |                 Context7Guidance.forModernization(
44 |                     analysis.dependency(), analysis.ageClassification().getName()))
45 |             : Optional.empty();
46 | 
47 |     return new DependencyAge(
48 |         analysis.dependency(),
49 |         analysis.latestVersion(),
50 |         analysis.ageClassification(),
51 |         analysis.daysSinceLastRelease(),
52 |         analysis.lastReleaseDate(),
53 |         analysis.ageDescription(),
54 |         analysis.recommendation(),
55 |         guidance);
56 |   }
57 | }
58 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/util/MavenCoordinateParser.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.util;
 2 | 
 3 | import com.arvindand.mcp.maven.model.MavenCoordinate;
 4 | 
 5 | /**
 6 |  * Utility class for parsing Maven coordinates from strings.
 7 |  *
 8 |  * @author Arvind Menon
 9 |  * @since 0.1.0
10 |  */
11 | public final class MavenCoordinateParser {
12 | 
13 |   private MavenCoordinateParser() {}
14 | 
15 |   /**
16 |    * Parses a Maven coordinate string in the format:
17 |    * "groupId:artifactId[:version][:packaging][:classifier]"
18 |    *
19 |    * @param dependency the dependency string to parse
20 |    * @return the parsed MavenCoordinate
21 |    * @throws IllegalArgumentException if the format is invalid
22 |    */
23 |   public static MavenCoordinate parse(String dependency) {
24 |     if (dependency == null || dependency.trim().isEmpty()) {
25 |       throw new IllegalArgumentException("Dependency string cannot be null or empty");
26 |     }
27 | 
28 |     String[] parts = dependency.split(":");
29 |     return switch (parts.length) {
30 |       case 0, 1 ->
31 |           throw new IllegalArgumentException(
32 |               "Invalid Maven coordinate format. Minimum format is 'groupId:artifactId'. Got: "
33 |                   + dependency);
34 |       case 2, 3, 4, 5 -> parseValidCoordinate(parts, dependency);
35 |       default ->
36 |           throw new IllegalArgumentException(
37 |               "Invalid Maven coordinate format. Maximum format is"
38 |                   + " 'groupId:artifactId:version:packaging:classifier'. Got: "
39 |                   + dependency);
40 |     };
41 |   }
42 | 
43 |   /**
44 |    * Validates that a Maven coordinate string has the minimum required components.
45 |    *
46 |    * @param dependency the dependency string to validate
47 |    * @throws IllegalArgumentException if the format is invalid
48 |    */
49 |   public static void validate(String dependency) {
50 |     parse(dependency);
51 |   }
52 | 
53 |   private static MavenCoordinate parseValidCoordinate(String[] parts, String original) {
54 |     String groupId = parts[0].trim();
55 |     String artifactId = parts[1].trim();
56 | 
57 |     if (groupId.isEmpty() || artifactId.isEmpty()) {
58 |       throw new IllegalArgumentException(
59 |           "GroupId and artifactId cannot be empty. Got: " + original);
60 |     }
61 | 
62 |     String version = getPartOrNull(parts, 2);
63 |     String packaging = getPartOrNull(parts, 3);
64 |     String classifier = getPartOrNull(parts, 4);
65 | 
66 |     return new MavenCoordinate(groupId, artifactId, version, packaging, classifier);
67 |   }
68 | 
69 |   private static String getPartOrNull(String[] parts, int index) {
70 |     if (index >= parts.length) return null;
71 |     String part = parts[index].trim();
72 |     return part.isEmpty() ? null : part;
73 |   }
74 | }
75 | 
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
  1 | name: CI/CD Pipeline
  2 | 
  3 | on:
  4 |   push:
  5 |     branches: [ main, develop ]
  6 |   pull_request:
  7 |     branches: [ main ]
  8 | 
  9 | jobs:
 10 |   unit-tests:
 11 |     name: Unit Tests
 12 |     runs-on: ubuntu-latest
 13 |     
 14 |     steps:
 15 |     - name: Checkout code
 16 |       uses: actions/checkout@v4
 17 |       
 18 |     - name: Set up Java 24
 19 |       uses: actions/setup-java@v4
 20 |       with:
 21 |         java-version: '24'
 22 |         distribution: 'temurin'
 23 |         
 24 |     - name: Cache Maven dependencies
 25 |       uses: actions/cache@v4
 26 |       with:
 27 |         path: ~/.m2
 28 |         key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
 29 |         restore-keys: ${{ runner.os }}-m2
 30 |         
 31 |     - name: Make mvnw executable
 32 |       run: chmod +x ./mvnw
 33 |         
 34 |     - name: Run unit tests
 35 |       run: ./mvnw clean test
 36 |       
 37 |     - name: Upload test results
 38 |       uses: actions/upload-artifact@v4
 39 |       if: always()
 40 |       with:
 41 |         name: unit-test-results
 42 |         path: target/surefire-reports/
 43 | 
 44 |   build:
 45 |     name: Build Application
 46 |     runs-on: ubuntu-latest
 47 |     needs: unit-tests
 48 |     
 49 |     steps:
 50 |     - name: Checkout code
 51 |       uses: actions/checkout@v4
 52 |       
 53 |     - name: Set up Java 24
 54 |       uses: actions/setup-java@v4
 55 |       with:
 56 |         java-version: '24'
 57 |         distribution: 'temurin'
 58 |         
 59 |     - name: Cache Maven dependencies
 60 |       uses: actions/cache@v4
 61 |       with:
 62 |         path: ~/.m2
 63 |         key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
 64 |         restore-keys: ${{ runner.os }}-m2
 65 |         
 66 |     - name: Make mvnw executable
 67 |       run: chmod +x ./mvnw
 68 |         
 69 |     - name: Build application
 70 |       run: ./mvnw clean package -DskipTests
 71 |       
 72 |     - name: Upload build artifacts
 73 |       uses: actions/upload-artifact@v4
 74 |       with:
 75 |         name: application-jar
 76 |         path: target/*.jar
 77 | 
 78 |   integration-tests:
 79 |     name: Integration Tests (Manual)
 80 |     runs-on: ubuntu-latest
 81 |     needs: build
 82 |     if: github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[run-integration]')
 83 |     
 84 |     steps:
 85 |     - name: Checkout code
 86 |       uses: actions/checkout@v4
 87 |       
 88 |     - name: Set up Java 24
 89 |       uses: actions/setup-java@v4
 90 |       with:
 91 |         java-version: '24'
 92 |         distribution: 'temurin'
 93 |         
 94 |     - name: Cache Maven dependencies
 95 |       uses: actions/cache@v4
 96 |       with:
 97 |         path: ~/.m2
 98 |         key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
 99 |         restore-keys: ${{ runner.os }}-m2
100 |         
101 |     - name: Make mvnw executable
102 |       run: chmod +x ./mvnw
103 |         
104 |     - name: Run integration tests
105 |       run: ./mvnw clean verify -DskipUTs=true
106 |       
107 |     - name: Upload integration test results
108 |       uses: actions/upload-artifact@v4
109 |       if: always()
110 |       with:
111 |         name: integration-test-results
112 |         path: target/failsafe-reports/
113 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/McpError.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import java.util.Map;
 4 | 
 5 | /**
 6 |  * Structured error response for MCP tools following MCP specification patterns.
 7 |  *
 8 |  * <p>Provides error classification, human-readable messages, contextual data, and retry guidance
 9 |  * for better error handling and LLM interpretation.
10 |  *
11 |  * @param code Error classification code (INVALID_INPUT, EXTERNAL_SERVICE_UNAVAILABLE, etc.)
12 |  * @param message Human-readable error description
13 |  * @param data Additional contextual information (coordinate, expected format, service name, etc.)
14 |  * @param retryAfter Suggested retry delay in seconds (null if no retry recommended)
15 |  * @author Arvind Menon
16 |  * @since 1.5.0
17 |  */
18 | public record McpError(String code, String message, Map<String, Object> data, Integer retryAfter) {
19 | 
20 |   // Error code constants
21 |   public static final String INVALID_INPUT = "INVALID_INPUT";
22 |   public static final String PARSE_ERROR = "PARSE_ERROR";
23 |   public static final String EXTERNAL_SERVICE_UNAVAILABLE = "EXTERNAL_SERVICE_UNAVAILABLE";
24 |   public static final String INTERNAL_ERROR = "INTERNAL_ERROR";
25 | 
26 |   /**
27 |    * Create error for invalid input parameters.
28 |    *
29 |    * @param message Human-readable error description
30 |    * @param data Contextual data about the invalid input
31 |    * @return McpError with INVALID_INPUT code
32 |    */
33 |   public static McpError invalidInput(String message, Map<String, Object> data) {
34 |     return new McpError(INVALID_INPUT, message, data, null);
35 |   }
36 | 
37 |   /**
38 |    * Create error for Maven coordinate parsing failures.
39 |    *
40 |    * @param coordinate The invalid coordinate string
41 |    * @param expectedFormat Expected format description
42 |    * @return McpError with PARSE_ERROR code and helpful context
43 |    */
44 |   public static McpError parseError(String coordinate, String expectedFormat) {
45 |     return new McpError(
46 |         PARSE_ERROR,
47 |         "Invalid Maven coordinate format",
48 |         Map.of(
49 |             "coordinate", coordinate,
50 |             "expected_format", expectedFormat,
51 |             "example", "org.springframework.boot:spring-boot-starter"),
52 |         null);
53 |   }
54 | 
55 |   /**
56 |    * Create error for Maven Central API unavailability.
57 |    *
58 |    * @param message Description of the failure
59 |    * @param retryAfter Suggested retry delay in seconds
60 |    * @return McpError with EXTERNAL_SERVICE_UNAVAILABLE code
61 |    */
62 |   public static McpError mavenCentralUnavailable(String message, int retryAfter) {
63 |     return new McpError(
64 |         EXTERNAL_SERVICE_UNAVAILABLE, message, Map.of("service", "Maven Central"), retryAfter);
65 |   }
66 | 
67 |   /**
68 |    * Create error for unexpected internal errors.
69 |    *
70 |    * @param message Error description
71 |    * @return McpError with INTERNAL_ERROR code
72 |    */
73 |   public static McpError internalError(String message) {
74 |     return new McpError(INTERNAL_ERROR, message, Map.of(), null);
75 |   }
76 | }
77 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/VersionComparison.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
 5 | import java.time.Instant;
 6 | import java.util.List;
 7 | import java.util.Optional;
 8 | 
 9 | /**
10 |  * Represents the result of comparing dependency versions with upgrade recommendations.
11 |  *
12 |  * @param comparisonDate when the comparison was performed
13 |  * @param dependencies individual comparison results for each dependency
14 |  * @param updateSummary overall summary of available updates
15 |  * @author Arvind Menon
16 |  * @since 1.3.0
17 |  */
18 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
19 | public record VersionComparison(
20 |     Instant comparisonDate,
21 |     List<DependencyComparisonResult> dependencies,
22 |     UpdateSummary updateSummary) {
23 | 
24 |   /** Individual dependency comparison result. */
25 |   public record DependencyComparisonResult(
26 |       String dependency,
27 |       String currentVersion,
28 |       String latestVersion,
29 |       String latestType,
30 |       String updateType,
31 |       boolean updateAvailable,
32 |       String status,
33 |       String error,
34 |       Optional<Context7Guidance> context7Guidance) {
35 | 
36 |     public static DependencyComparisonResult success(
37 |         String dependency,
38 |         String currentVersion,
39 |         String latestVersion,
40 |         String latestType,
41 |         String updateType,
42 |         boolean updateAvailable,
43 |         boolean context7Enabled) {
44 |       // Add Context7 guidance if update is available and Context7 is enabled
45 |       Optional<Context7Guidance> guidance =
46 |           (updateAvailable && context7Enabled)
47 |               ? Optional.of(Context7Guidance.forMigration(dependency, updateType))
48 |               : Optional.empty();
49 | 
50 |       return new DependencyComparisonResult(
51 |           dependency,
52 |           currentVersion,
53 |           latestVersion,
54 |           latestType,
55 |           updateType,
56 |           updateAvailable,
57 |           "success",
58 |           null,
59 |           guidance);
60 |     }
61 | 
62 |     public static DependencyComparisonResult notFound(String dependency, String currentVersion) {
63 |       return new DependencyComparisonResult(
64 |           dependency, currentVersion, null, null, null, false, "not_found", null, Optional.empty());
65 |     }
66 | 
67 |     public static DependencyComparisonResult noCurrentVersion(String dependency) {
68 |       return new DependencyComparisonResult(
69 |           dependency, null, null, null, null, false, "no_current_version", null, Optional.empty());
70 |     }
71 | 
72 |     public static DependencyComparisonResult error(String dependency, String error) {
73 |       return new DependencyComparisonResult(
74 |           dependency, null, null, null, null, false, "error", error, Optional.empty());
75 |     }
76 |   }
77 | 
78 |   /** Summary of update types. */
79 |   public record UpdateSummary(
80 |       int majorUpdates, int minorUpdates, int patchUpdates, int noUpdates) {}
81 | }
82 | 
```

--------------------------------------------------------------------------------
/src/main/resources/application.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | spring:
 2 |   threads:
 3 |     virtual:
 4 |       enabled: true
 5 |   main:
 6 |     web-application-type: none
 7 |     banner-mode: off
 8 |   application:
 9 |     name: maven-tools-mcp
10 |   ai:
11 |     mcp:
12 |       server:
13 |         name: maven-tools-mcp
14 |         version: 1.5.1
15 |         type: SYNC
16 |         instructions: "Universal JVM dependency intelligence server for any build tool using Maven Central Repository. Provides comprehensive dependency analysis, latest version lookup, stability filtering, bulk operations, and upgrade recommendations with intelligent caching support. Works with Maven, Gradle, SBT, Mill, and any JVM build tool."
17 |       client:
18 |         enabled: true                     # Enable MCP client with SSE transport (Docker default)
19 |         type: ASYNC
20 |         request-timeout: 15s
21 |         toolcallback:
22 |           enabled: true                   # Enable exposing Context7 tools as raw MCP tools
23 |         streamable-http:
24 |           connections:
25 |             context7:
26 |               url: https://mcp.context7.com/
27 |   cache:
28 |     type: caffeine
29 | 
30 | # Logging configuration - let logback-spring.xml handle the appenders
31 | logging:
32 |   level:
33 |     # Your app logs at INFO level (goes to STDERR via logback config)
34 |     "[com.arvindand.mcp.maven]": INFO
35 |     # Framework logs at ERROR level to minimize noise
36 |     "[org.springframework.ai.mcp]": ERROR
37 |     "[org.springframework.boot]": ERROR
38 |     "[org.springframework]": ERROR
39 |     "[org.apache.http]": ERROR
40 |     "[ch.qos.logback]": ERROR
41 |     root: ERROR
42 |   # Keep your logback configuration
43 |   config: "classpath:logback-spring.xml"
44 | 
45 | # Maven Central Repository configuration
46 | maven:
47 |   central:
48 |     repository-base-url: https://repo1.maven.org/maven2
49 |     timeout: 8s
50 |     max-results: 100
51 |     connection-pool-size: 50
52 | 
53 | # Resilience4j configuration for Maven Central API resilience
54 | resilience4j:
55 |   circuitbreaker:
56 |     instances:
57 |       maven-central:
58 |         failure-rate-threshold: 50
59 |         slow-call-rate-threshold: 50
60 |         slow-call-duration-threshold: 5s
61 |         wait-duration-in-open-state: 30s
62 |         permitted-number-of-calls-in-half-open-state: 5
63 |         sliding-window-size: 10
64 |         sliding-window-type: COUNT_BASED
65 | 
66 |   retry:
67 |     instances:
68 |       maven-central:
69 |         max-attempts: 3
70 |         wait-duration: 1s
71 |         exponential-backoff-multiplier: 2
72 |         retry-exceptions:
73 |           - java.net.ConnectException
74 |           - java.net.SocketTimeoutException
75 | 
76 |   ratelimiter:
77 |     instances:
78 |       maven-central:
79 |         limit-for-period: 10
80 |         limit-refresh-period: 1s
81 |         timeout-duration: 0s
82 | 
83 | # Context7 guidance hints - lightweight suggestions in response models to help LLMs
84 | # effectively use Context7 tools for migration guidance and upgrade strategies.
85 | # No external calls are made - only response enrichment with search hints.
86 | context7:
87 |   enabled: true                       # Set to false to disable Context7 guidance hints in responses
88 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/service/MavenDependencyToolsContext7EnabledIT.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.service;
 2 | 
 3 | import static com.arvindand.mcp.maven.TestHelpers.getSuccessData;
 4 | import static org.junit.jupiter.api.Assertions.*;
 5 | 
 6 | import com.arvindand.mcp.maven.config.Context7Properties;
 7 | import com.arvindand.mcp.maven.model.*;
 8 | import org.junit.jupiter.api.Test;
 9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.boot.test.context.TestConfiguration;
12 | import org.springframework.context.annotation.Bean;
13 | import org.springframework.context.annotation.Primary;
14 | import org.springframework.test.context.ActiveProfiles;
15 | 
16 | /**
17 |  * Integration test for MavenDependencyTools with Context7 enabled. Verifies that Context7 guidance
18 |  * hints are included when context7.enabled=true.
19 |  *
20 |  * @author Arvind Menon
21 |  * @since 1.3.0
22 |  */
23 | @SpringBootTest
24 | @ActiveProfiles("test")
25 | class MavenDependencyToolsContext7EnabledIT {
26 | 
27 |   @TestConfiguration
28 |   static class Context7EnabledTestConfig {
29 |     @Bean
30 |     @Primary
31 |     public Context7Properties context7Properties() {
32 |       return new Context7Properties(true); // Enable Context7 for this test
33 |     }
34 |   }
35 | 
36 |   @Autowired private MavenDependencyTools mavenDependencyTools;
37 | 
38 |   /** Tests that Context7 guidance is included when context7.enabled=true. */
39 |   @Test
40 |   void testContext7GuidanceEnabledForVersionComparison() {
41 |     // Use an older Spring Boot version that will trigger Context7 guidance when enabled
42 |     String oldDependencies = "org.springframework.boot:spring-boot-starter:2.5.0";
43 |     ToolResponse resp =
44 |         mavenDependencyTools.compare_dependency_versions(oldDependencies, StabilityFilter.ALL);
45 | 
46 |     VersionComparison comparison = getSuccessData(resp);
47 |     assertNotNull(comparison);
48 | 
49 |     // Check if any dependencies have updates available and Context7 guidance
50 |     for (var dep : comparison.dependencies()) {
51 |       if (dep.updateAvailable()) {
52 |         // When Context7 is enabled and updates are available, guidance should be present
53 |         assertTrue(
54 |             dep.context7Guidance().isPresent(),
55 |             "Context7 guidance should be present for updates when enabled");
56 | 
57 |         var guidance = dep.context7Guidance().get();
58 |         assertNotNull(guidance.orchestrationInstructions());
59 |         assertTrue(guidance.orchestrationInstructions().contains("resolve-library-id"));
60 |         assertTrue(guidance.orchestrationInstructions().contains("get-library-docs"));
61 |       }
62 |     }
63 |   }
64 | 
65 |   /** Tests that the tool works with Context7 enabled (simplified test). */
66 |   @Test
67 |   void testContext7EnabledBasicOperation() {
68 |     // This test just verifies that tools work when Context7 is enabled
69 |     // The main functionality is covered by the Context7 guidance test above
70 |     ToolResponse resp = mavenDependencyTools.get_latest_version("junit:junit", StabilityFilter.ALL);
71 | 
72 |     // Just verify we get a successful response
73 |     assertInstanceOf(
74 |         ToolResponse.Success.class, resp, "Should get successful response with Context7 enabled");
75 |   }
76 | }
77 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/ProjectHealthAnalysis.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
 5 | import java.util.List;
 6 | import java.util.Optional;
 7 | 
 8 | /**
 9 |  * Comprehensive health analysis for multiple dependencies in a project.
10 |  *
11 |  * @param analysisDate ISO formatted date when analysis was performed
12 |  * @param dependencyCount total number of dependencies analyzed
13 |  * @param healthSummary overall health assessment
14 |  * @param ageDistribution breakdown of dependencies by age classification
15 |  * @param recommendations prioritized list of recommendations
16 |  * @param dependencies individual analysis for each dependency
17 |  * @param maxAgeInDays the age threshold used for classification
18 |  * @author Arvind Menon
19 |  * @since 1.1.0
20 |  */
21 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
22 | public record ProjectHealthAnalysis(
23 |     String analysisDate,
24 |     int dependencyCount,
25 |     int successfulAnalysis,
26 |     int failedAnalysis,
27 |     AgeDistribution ageDistribution,
28 |     List<DependencyHealthAnalysis> dependencies,
29 |     List<String> recommendations) {
30 | 
31 |   /** Distribution of dependencies by age classification. */
32 |   public record AgeDistribution(int fresh, int current, int aging, int stale) {}
33 | 
34 |   /** Health analysis for a single dependency. */
35 |   public record DependencyHealthAnalysis(
36 |       String dependency,
37 |       String status,
38 |       String latestVersion,
39 |       String ageClassification,
40 |       long daysSinceRelease,
41 |       int healthScore,
42 |       String maintenanceLevel,
43 |       Optional<Context7Guidance> context7Guidance,
44 |       Optional<String> error) {
45 | 
46 |     /** Creates a successful analysis result. */
47 |     public static DependencyHealthAnalysis success(
48 |         String dependency,
49 |         String latestVersion,
50 |         String ageClassification,
51 |         long daysSinceRelease,
52 |         int healthScore,
53 |         String maintenanceLevel,
54 |         boolean context7Enabled) {
55 |       // Add Context7 guidance for aging/stale dependencies when Context7 is enabled
56 |       Optional<Context7Guidance> guidance =
57 |           (context7Enabled
58 |                   && ("aging".equals(ageClassification) || "stale".equals(ageClassification)))
59 |               ? Optional.of(Context7Guidance.forModernization(dependency, ageClassification))
60 |               : Optional.empty();
61 | 
62 |       return new DependencyHealthAnalysis(
63 |           dependency,
64 |           "success",
65 |           latestVersion,
66 |           ageClassification,
67 |           daysSinceRelease,
68 |           healthScore,
69 |           maintenanceLevel,
70 |           guidance,
71 |           Optional.empty());
72 |     }
73 | 
74 |     /** Creates an error result. */
75 |     public static DependencyHealthAnalysis error(String dependency, String error) {
76 |       return new DependencyHealthAnalysis(
77 |           dependency, "error", null, null, 0, 0, null, Optional.empty(), Optional.of(error));
78 |     }
79 | 
80 |     /** Creates a not found result. */
81 |     public static DependencyHealthAnalysis notFound(String dependency) {
82 |       return new DependencyHealthAnalysis(
83 |           dependency,
84 |           "not_found",
85 |           null,
86 |           null,
87 |           0,
88 |           0,
89 |           null,
90 |           Optional.empty(),
91 |           Optional.of("Dependency not found in Maven Central"));
92 |     }
93 |   }
94 | }
95 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/Context7Guidance.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
 5 | 
 6 | /**
 7 |  * Context7 orchestration instructions for LLM tool delegation.
 8 |  *
 9 |  * <p>Provides explicit step-by-step instructions for LLMs to effectively orchestrate the raw
10 |  * Context7 tools 'resolve-library-id' and 'get-library-docs', including fallback web search
11 |  * instructions when Context7 data is insufficient.
12 |  *
13 |  * @author Arvind Menon
14 |  * @since 1.2.0
15 |  */
16 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
17 | public record Context7Guidance(String orchestrationInstructions) {
18 | 
19 |   private static final String STALE = "stale";
20 |   private static final String MAJOR = "major";
21 | 
22 |   /**
23 |    * Create orchestration instructions for version migration scenarios.
24 |    *
25 |    * @param dependency the Maven coordinate (groupId:artifactId format)
26 |    * @param updateType the type of update (major, minor, patch)
27 |    * @return Context7 orchestration instructions for migration
28 |    */
29 |   public static Context7Guidance forMigration(String dependency, String updateType) {
30 |     String artifactId = extractArtifactId(dependency);
31 |     String orchestrationInstructions = generateMigrationOrchestration(artifactId, updateType);
32 | 
33 |     return new Context7Guidance(orchestrationInstructions);
34 |   }
35 | 
36 |   /**
37 |    * Create orchestration instructions for dependency modernization scenarios.
38 |    *
39 |    * @param dependency the Maven coordinate (groupId:artifactId format)
40 |    * @param ageClassification the age classification (fresh, current, aging, stale)
41 |    * @return Context7 orchestration instructions for modernization
42 |    */
43 |   public static Context7Guidance forModernization(String dependency, String ageClassification) {
44 |     String artifactId = extractArtifactId(dependency);
45 |     String orchestrationInstructions =
46 |         generateModernizationOrchestration(artifactId, ageClassification);
47 | 
48 |     return new Context7Guidance(orchestrationInstructions);
49 |   }
50 | 
51 |   private static String extractArtifactId(String dependency) {
52 |     if (dependency == null || !dependency.contains(":")) {
53 |       return dependency;
54 |     }
55 | 
56 |     String[] parts = dependency.split(":");
57 |     return parts[1]; // Return artifactId directly
58 |   }
59 | 
60 |   private static String generateMigrationOrchestration(String artifactId, String updateType) {
61 |     String topic = MAJOR.equals(updateType) ? "migration guide" : "upgrade guide";
62 |     String fallbackSearch = artifactId + " " + updateType + " version upgrade guide";
63 | 
64 |     return String.format(
65 |         "Use resolve-library-id tool with libraryName='%s' to find documentation ID. "
66 |             + "Then use get-library-docs tool with the returned Context7 ID and topic='%s' to get upgrade instructions. "
67 |             + "If Context7 doesn't provide sufficient information, perform a web search for '%s'.",
68 |         artifactId, topic, fallbackSearch);
69 |   }
70 | 
71 |   private static String generateModernizationOrchestration(
72 |       String artifactId, String ageClassification) {
73 |     String topic =
74 |         STALE.equals(ageClassification)
75 |             ? "alternatives and replacements"
76 |             : "modern usage and best practices";
77 |     String fallbackSearch =
78 |         artifactId
79 |             + " "
80 |             + (STALE.equals(ageClassification)
81 |                 ? "modernization alternatives"
82 |                 : "latest features best practices");
83 | 
84 |     return String.format(
85 |         "Use resolve-library-id tool with libraryName='%s' to find documentation ID. "
86 |             + "Then use get-library-docs tool with the returned Context7 ID and topic='%s' to get modernization guidance. "
87 |             + "If Context7 doesn't provide sufficient information, perform a web search for '%s'.",
88 |         artifactId, topic, fallbackSearch);
89 |   }
90 | }
91 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/service/MavenCentralServiceUnitTest.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.service;
  2 | 
  3 | import static org.assertj.core.api.Assertions.assertThat;
  4 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
  5 | 
  6 | import com.arvindand.mcp.maven.config.MavenCentralProperties;
  7 | import com.arvindand.mcp.maven.model.MavenCoordinate;
  8 | import java.time.Duration;
  9 | import org.junit.jupiter.api.BeforeEach;
 10 | import org.junit.jupiter.api.Test;
 11 | 
 12 | /**
 13 |  * Unit tests for MavenCentralService. Tests the service behavior with utility methods and models.
 14 |  *
 15 |  * @author Arvind Menon
 16 |  * @since 0.1.0
 17 |  */
 18 | class MavenCentralServiceUnitTest {
 19 | 
 20 |   private MavenCentralProperties properties;
 21 | 
 22 |   @BeforeEach
 23 |   void setUp() {
 24 |     properties =
 25 |         new MavenCentralProperties("https://repo1.maven.org/maven2", Duration.ofSeconds(10), 100);
 26 |   }
 27 | 
 28 |   /** Tests version comparison and ordering. */
 29 |   @Test
 30 |   void testVersionOrdering() {
 31 |     // Given versions in random order
 32 |     String[] versions = {"1.0.0", "2.1.0", "2.0.0", "1.5.0", "2.0.1"};
 33 | 
 34 |     // When getting latest
 35 |     String latest = com.arvindand.mcp.maven.util.VersionComparator.getLatest(versions);
 36 | 
 37 |     // Then
 38 |     assertThat(latest).isEqualTo("2.1.0");
 39 |   }
 40 | 
 41 |   /** Tests Maven coordinate parsing. */
 42 |   @Test
 43 |   void testMavenCoordinateParsing() {
 44 |     // Given
 45 |     String coordinateString = "org.springframework:spring-core:6.1.4:jar";
 46 | 
 47 |     // When
 48 |     MavenCoordinate coordinate =
 49 |         com.arvindand.mcp.maven.util.MavenCoordinateParser.parse(coordinateString);
 50 | 
 51 |     // Then
 52 |     assertThat(coordinate.groupId()).isEqualTo("org.springframework");
 53 |     assertThat(coordinate.artifactId()).isEqualTo("spring-core");
 54 |     assertThat(coordinate.version()).isEqualTo("6.1.4");
 55 |     assertThat(coordinate.packaging()).isEqualTo("jar");
 56 |   }
 57 | 
 58 |   /** Tests invalid coordinate parsing. */
 59 |   @Test
 60 |   void testInvalidCoordinateParsing() {
 61 |     // Given
 62 |     String invalidCoordinate = "invalid";
 63 | 
 64 |     // When & Then
 65 |     assertThatThrownBy(
 66 |             () -> com.arvindand.mcp.maven.util.MavenCoordinateParser.parse(invalidCoordinate))
 67 |         .isInstanceOf(IllegalArgumentException.class)
 68 |         .hasMessageContaining("Invalid Maven coordinate format");
 69 |   }
 70 | 
 71 |   /** Tests coordinate string formatting. */
 72 |   @Test
 73 |   void testCoordinateStringFormatting() {
 74 |     // Given
 75 |     MavenCoordinate coordinate =
 76 |         new MavenCoordinate("org.springframework", "spring-core", "6.1.4", "jar", null);
 77 | 
 78 |     // When
 79 |     String coordinateString = coordinate.toCoordinateString();
 80 | 
 81 |     // Then
 82 |     assertThat(coordinateString).isEqualTo("org.springframework:spring-core:6.1.4:jar");
 83 |   }
 84 | 
 85 |   /** Tests MavenCoordinate static factory methods. */
 86 |   @Test
 87 |   void testMavenCoordinateFactoryMethods() {
 88 |     // Test of() with groupId and artifactId only
 89 |     MavenCoordinate coord1 = MavenCoordinate.of("org.springframework", "spring-core");
 90 |     assertThat(coord1.groupId()).isEqualTo("org.springframework");
 91 |     assertThat(coord1.artifactId()).isEqualTo("spring-core");
 92 |     assertThat(coord1.version()).isNull();
 93 |     assertThat(coord1.packaging()).isNull();
 94 |     assertThat(coord1.classifier()).isNull();
 95 | 
 96 |     // Test of() with groupId, artifactId, and version
 97 |     MavenCoordinate coord2 = MavenCoordinate.of("org.springframework", "spring-core", "6.1.4");
 98 |     assertThat(coord2.groupId()).isEqualTo("org.springframework");
 99 |     assertThat(coord2.artifactId()).isEqualTo("spring-core");
100 |     assertThat(coord2.version()).isEqualTo("6.1.4");
101 |     assertThat(coord2.packaging()).isNull();
102 |     assertThat(coord2.classifier()).isNull();
103 |   }
104 | 
105 |   /** Tests properties configuration. */
106 |   @Test
107 |   void testPropertiesConfiguration() {
108 |     assertThat(properties.repositoryBaseUrl()).isEqualTo("https://repo1.maven.org/maven2");
109 |     assertThat(properties.timeout()).isEqualTo(Duration.ofSeconds(10));
110 |     assertThat(properties.maxResults()).isEqualTo(100);
111 |   }
112 | }
113 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/model/Context7GuidanceTest.java:
--------------------------------------------------------------------------------

```java
 1 | package com.arvindand.mcp.maven.model;
 2 | 
 3 | import static org.junit.jupiter.api.Assertions.assertNotNull;
 4 | import static org.junit.jupiter.api.Assertions.assertTrue;
 5 | 
 6 | import org.junit.jupiter.api.Test;
 7 | 
 8 | /**
 9 |  * Unit tests for simplified Context7Guidance model and orchestration instruction generation.
10 |  *
11 |  * @author Arvind Menon
12 |  * @since 1.2.0
13 |  */
14 | class Context7GuidanceTest {
15 | 
16 |   @Test
17 |   void testForMigration_Major() {
18 |     Context7Guidance guidance =
19 |         Context7Guidance.forMigration("org.springframework.boot:spring-boot-starter", "major");
20 | 
21 |     assertNotNull(guidance);
22 |     assertNotNull(guidance.orchestrationInstructions());
23 |     assertTrue(guidance.orchestrationInstructions().contains("resolve-library-id"));
24 |     assertTrue(guidance.orchestrationInstructions().contains("get-library-docs"));
25 |     assertTrue(guidance.orchestrationInstructions().contains("spring-boot-starter"));
26 |     assertTrue(guidance.orchestrationInstructions().contains("migration guide"));
27 |     assertTrue(guidance.orchestrationInstructions().contains("web search"));
28 |   }
29 | 
30 |   @Test
31 |   void testForMigration_Minor() {
32 |     Context7Guidance guidance =
33 |         Context7Guidance.forMigration("com.fasterxml.jackson.core:jackson-core", "minor");
34 | 
35 |     assertNotNull(guidance.orchestrationInstructions());
36 |     assertTrue(guidance.orchestrationInstructions().contains("jackson-core"));
37 |     assertTrue(guidance.orchestrationInstructions().contains("upgrade guide"));
38 |     assertTrue(guidance.orchestrationInstructions().contains("minor version"));
39 |   }
40 | 
41 |   @Test
42 |   void testForModernization_Aging() {
43 |     Context7Guidance guidance =
44 |         Context7Guidance.forModernization("org.hibernate:hibernate-core", "aging");
45 | 
46 |     assertNotNull(guidance.orchestrationInstructions());
47 |     assertTrue(guidance.orchestrationInstructions().contains("hibernate-core"));
48 |     assertTrue(guidance.orchestrationInstructions().contains("modern usage and best practices"));
49 |     assertTrue(guidance.orchestrationInstructions().contains("latest features best practices"));
50 |   }
51 | 
52 |   @Test
53 |   void testForModernization_Stale() {
54 |     Context7Guidance guidance =
55 |         Context7Guidance.forModernization("commons-lang:commons-lang", "stale");
56 | 
57 |     assertNotNull(guidance.orchestrationInstructions());
58 |     assertTrue(guidance.orchestrationInstructions().contains("commons-lang"));
59 |     assertTrue(guidance.orchestrationInstructions().contains("alternatives and replacements"));
60 |     assertTrue(guidance.orchestrationInstructions().contains("modernization alternatives"));
61 |   }
62 | 
63 |   @Test
64 |   void testArtifactIdExtraction() {
65 |     // Test that we use artifactId directly without complex library name extraction
66 |     Context7Guidance springGuidance =
67 |         Context7Guidance.forMigration("org.springframework.boot:spring-boot-starter", "major");
68 |     assertTrue(springGuidance.orchestrationInstructions().contains("spring-boot-starter"));
69 | 
70 |     Context7Guidance hibernateGuidance =
71 |         Context7Guidance.forMigration("org.hibernate:hibernate-core", "major");
72 |     assertTrue(hibernateGuidance.orchestrationInstructions().contains("hibernate-core"));
73 | 
74 |     Context7Guidance jacksonGuidance =
75 |         Context7Guidance.forMigration("com.fasterxml.jackson.core:jackson-core", "major");
76 |     assertTrue(jacksonGuidance.orchestrationInstructions().contains("jackson-core"));
77 |   }
78 | 
79 |   @Test
80 |   void testOrchestrationInstructionsContainRequiredElements() {
81 |     Context7Guidance guidance = Context7Guidance.forMigration("test:test-artifact", "patch");
82 | 
83 |     // Verify all required orchestration elements are present
84 |     String instructions = guidance.orchestrationInstructions();
85 |     assertTrue(instructions.contains("resolve-library-id tool"));
86 |     assertTrue(instructions.contains("get-library-docs tool"));
87 |     assertTrue(instructions.contains("Context7 ID"));
88 |     assertTrue(instructions.contains("web search"));
89 |     assertTrue(instructions.contains("test-artifact"));
90 |   }
91 | }
92 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/MavenMetadata.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.model;
  2 | 
  3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  4 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
  5 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
  6 | import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
  7 | import java.util.List;
  8 | 
  9 | /**
 10 |  * Represents the structure of maven-metadata.xml files found in Maven repositories.
 11 |  *
 12 |  * <p>Maven repositories contain metadata files that enable discovery and resolution operations. The
 13 |  * maven-metadata.xml file lists version information, timestamps, and other artifact details.
 14 |  *
 15 |  * @param groupId the Maven group identifier
 16 |  * @param artifactId the Maven artifact identifier
 17 |  * @param version the artifact version (for version-specific metadata)
 18 |  * @param versioning versioning information including available versions and timestamps
 19 |  * @author Arvind Menon
 20 |  * @since 1.4.0
 21 |  */
 22 | @JsonIgnoreProperties(ignoreUnknown = true)
 23 | @JacksonXmlRootElement(localName = "metadata")
 24 | public record MavenMetadata(
 25 |     @JacksonXmlProperty(localName = "groupId") String groupId,
 26 |     @JacksonXmlProperty(localName = "artifactId") String artifactId,
 27 |     @JacksonXmlProperty(localName = "version") String version,
 28 |     @JacksonXmlProperty(localName = "versioning") VersioningInfo versioning) {
 29 | 
 30 |   /**
 31 |    * Contains versioning information for the artifact.
 32 |    *
 33 |    * @param latest the latest deployed version (release or snapshot)
 34 |    * @param release the latest release version (non-snapshot)
 35 |    * @param versions list of all available versions
 36 |    * @param lastUpdated timestamp when metadata was last updated (format: yyyyMMddHHmmss)
 37 |    * @param snapshot snapshot-specific information
 38 |    */
 39 |   @JsonIgnoreProperties(ignoreUnknown = true)
 40 |   public record VersioningInfo(
 41 |       @JacksonXmlProperty(localName = "latest") String latest,
 42 |       @JacksonXmlProperty(localName = "release") String release,
 43 |       @JacksonXmlProperty(localName = "versions") VersionList versions,
 44 |       @JacksonXmlProperty(localName = "lastUpdated") String lastUpdated,
 45 |       @JacksonXmlProperty(localName = "snapshot") SnapshotInfo snapshot) {
 46 | 
 47 |     /**
 48 |      * Gets all available versions as a list of strings.
 49 |      *
 50 |      * @return list of version strings, or empty list if none available
 51 |      */
 52 |     public List<String> getVersionStrings() {
 53 |       return versions != null && versions.versionList() != null
 54 |           ? versions.versionList()
 55 |           : List.of();
 56 |     }
 57 | 
 58 |     /**
 59 |      * Checks if this versioning info contains any version information.
 60 |      *
 61 |      * @return true if versions are available
 62 |      */
 63 |     public boolean hasVersions() {
 64 |       return !getVersionStrings().isEmpty();
 65 |     }
 66 |   }
 67 | 
 68 |   /**
 69 |    * Wrapper for the list of versions to handle XML parsing. Uses JacksonXmlElementWrapper to
 70 |    * properly handle multiple version elements.
 71 |    *
 72 |    * @param versionList the list of version strings
 73 |    */
 74 |   @JsonIgnoreProperties(ignoreUnknown = true)
 75 |   public record VersionList(
 76 |       @JacksonXmlProperty(localName = "version") @JacksonXmlElementWrapper(useWrapping = false)
 77 |           List<String> versionList) {}
 78 | 
 79 |   /**
 80 |    * Contains snapshot-specific versioning information.
 81 |    *
 82 |    * @param timestamp snapshot timestamp
 83 |    * @param buildNumber snapshot build number
 84 |    * @param localCopy whether this is a local copy
 85 |    */
 86 |   @JsonIgnoreProperties(ignoreUnknown = true)
 87 |   public record SnapshotInfo(
 88 |       @JacksonXmlProperty(localName = "timestamp") String timestamp,
 89 |       @JacksonXmlProperty(localName = "buildNumber") String buildNumber,
 90 |       @JacksonXmlProperty(localName = "localCopy") Boolean localCopy) {}
 91 | 
 92 |   /**
 93 |    * Checks if this metadata contains valid versioning information.
 94 |    *
 95 |    * @return true if versioning info is available and contains versions
 96 |    */
 97 |   public boolean hasValidVersioning() {
 98 |     return versioning != null && versioning.hasVersions();
 99 |   }
100 | 
101 |   /**
102 |    * Gets the artifact coordinate string for this metadata.
103 |    *
104 |    * @return coordinate string in format "groupId:artifactId"
105 |    */
106 |   public String getCoordinate() {
107 |     return groupId + ":" + artifactId;
108 |   }
109 | }
110 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/DependencyAgeAnalysis.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.model;
  2 | 
  3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
  4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
  5 | import java.time.Instant;
  6 | import java.time.temporal.ChronoUnit;
  7 | 
  8 | /**
  9 |  * Analysis of dependency age and freshness classification.
 10 |  *
 11 |  * @param dependency the dependency coordinate
 12 |  * @param latestVersion the latest version analyzed
 13 |  * @param ageClassification age classification (fresh/current/aging/stale)
 14 |  * @param daysSinceLastRelease days since the latest version was released
 15 |  * @param lastReleaseDate when the latest version was released
 16 |  * @param ageDescription human-readable age description
 17 |  * @param recommendation suggested action based on age analysis
 18 |  * @author Arvind Menon
 19 |  * @since 1.1.0
 20 |  */
 21 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
 22 | public record DependencyAgeAnalysis(
 23 |     String dependency,
 24 |     String latestVersion,
 25 |     AgeClassification ageClassification,
 26 |     long daysSinceLastRelease,
 27 |     Instant lastReleaseDate,
 28 |     String ageDescription,
 29 |     String recommendation) {
 30 | 
 31 |   /** Age classification categories for dependencies. */
 32 |   public enum AgeClassification {
 33 |     FRESH("fresh", "Released within the last 30 days"),
 34 |     CURRENT("current", "Released within the last 6 months"),
 35 |     AGING("aging", "Released 6 months to 2 years ago"),
 36 |     STALE("stale", "Released more than 2 years ago");
 37 | 
 38 |     private final String name;
 39 |     private final String description;
 40 | 
 41 |     AgeClassification(String name, String description) {
 42 |       this.name = name;
 43 |       this.description = description;
 44 |     }
 45 | 
 46 |     public String getName() {
 47 |       return name;
 48 |     }
 49 | 
 50 |     public String getDescription() {
 51 |       return description;
 52 |     }
 53 | 
 54 |     /**
 55 |      * Classify dependency age based on days since last release.
 56 |      *
 57 |      * @param daysSinceRelease days since the latest version was released
 58 |      * @return appropriate age classification
 59 |      */
 60 |     public static AgeClassification classify(long daysSinceRelease) {
 61 |       if (daysSinceRelease <= 30) {
 62 |         return FRESH;
 63 |       } else if (daysSinceRelease <= 180) {
 64 |         return CURRENT;
 65 |       } else if (daysSinceRelease <= 730) {
 66 |         return AGING;
 67 |       } else {
 68 |         return STALE;
 69 |       }
 70 |     }
 71 |   }
 72 | 
 73 |   /**
 74 |    * Create age analysis from Maven Central timestamp.
 75 |    *
 76 |    * @param dependency the dependency coordinate
 77 |    * @param latestVersion the latest version
 78 |    * @param timestamp Maven Central timestamp (milliseconds)
 79 |    * @return dependency age analysis
 80 |    */
 81 |   public static DependencyAgeAnalysis fromTimestamp(
 82 |       String dependency, String latestVersion, long timestamp) {
 83 |     Instant releaseDate = Instant.ofEpochMilli(timestamp);
 84 |     Instant now = Instant.now();
 85 |     long daysSinceRelease = ChronoUnit.DAYS.between(releaseDate, now);
 86 | 
 87 |     AgeClassification classification = AgeClassification.classify(daysSinceRelease);
 88 |     String ageDescription = formatAgeDescription(daysSinceRelease);
 89 |     String recommendation = generateRecommendation(classification);
 90 | 
 91 |     return new DependencyAgeAnalysis(
 92 |         dependency,
 93 |         latestVersion,
 94 |         classification,
 95 |         daysSinceRelease,
 96 |         releaseDate,
 97 |         ageDescription,
 98 |         recommendation);
 99 |   }
100 | 
101 |   private static final String RELEASED = "Released ";
102 | 
103 |   private static String formatAgeDescription(long days) {
104 |     if (days <= 1) {
105 |       return "Released today or yesterday";
106 |     } else if (days <= 7) {
107 |       return RELEASED + days + " days ago";
108 |     } else if (days <= 30) {
109 |       return RELEASED + (days / 7) + " weeks ago";
110 |     } else if (days <= 365) {
111 |       return RELEASED + (days / 30) + " months ago";
112 |     } else {
113 |       return RELEASED + (days / 365) + " years ago";
114 |     }
115 |   }
116 | 
117 |   private static String generateRecommendation(AgeClassification classification) {
118 |     return switch (classification) {
119 |       case FRESH -> "Recently updated - safe to use latest version";
120 |       case CURRENT -> "Actively maintained - consider updating if needed";
121 |       case AGING -> "Consider checking for updates or alternatives";
122 |       case STALE -> "Review for continued maintenance and consider alternatives";
123 |     };
124 |   }
125 | }
126 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/BulkCheckResult.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.model;
  2 | 
  3 | import com.fasterxml.jackson.databind.PropertyNamingStrategies;
  4 | import com.fasterxml.jackson.databind.annotation.JsonNaming;
  5 | 
  6 | /**
  7 |  * Represents the result of a bulk dependency check with comprehensive version information.
  8 |  *
  9 |  * @param dependency the dependency coordinate
 10 |  * @param version the primary version (latest stable or latest overall if no stable)
 11 |  * @param type the version type of the primary version
 12 |  * @param status the status (found, not_found, error)
 13 |  * @param error the error message (if status is error)
 14 |  * @param totalVersions total versions count
 15 |  * @param stableVersions stable versions count
 16 |  * @param latestStable latest stable version info
 17 |  * @param latestRc latest RC version info
 18 |  * @param latestBeta latest beta version info
 19 |  * @param latestAlpha latest alpha version info
 20 |  * @param latestMilestone latest milestone version info
 21 |  * @author Arvind Menon
 22 |  * @since 0.1.0
 23 |  */
 24 | @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
 25 | public record BulkCheckResult(
 26 |     String dependency,
 27 |     String version,
 28 |     String type,
 29 |     String status,
 30 |     String error,
 31 |     Integer totalVersions,
 32 |     Integer stableVersions,
 33 |     VersionInfo latestStable,
 34 |     VersionInfo latestRc,
 35 |     VersionInfo latestBeta,
 36 |     VersionInfo latestAlpha,
 37 |     VersionInfo latestMilestone) {
 38 | 
 39 |   public enum Status {
 40 |     FOUND("found"),
 41 |     NOT_FOUND("not_found"),
 42 |     NO_STABLE_VERSION("no_stable_version"),
 43 |     ERROR("error");
 44 | 
 45 |     private final String value;
 46 | 
 47 |     Status(String value) {
 48 |       this.value = value;
 49 |     }
 50 | 
 51 |     public String getValue() {
 52 |       return value;
 53 |     }
 54 |   }
 55 | 
 56 |   public static BulkCheckResult found(String dependency, String version, String type) {
 57 |     return new BulkCheckResult(
 58 |         dependency,
 59 |         version,
 60 |         type,
 61 |         Status.FOUND.getValue(),
 62 |         null,
 63 |         null,
 64 |         null,
 65 |         null,
 66 |         null,
 67 |         null,
 68 |         null,
 69 |         null);
 70 |   }
 71 | 
 72 |   public static BulkCheckResult foundStable(
 73 |       String dependency, String version, String type, int totalVersions, int stableVersions) {
 74 |     return new BulkCheckResult(
 75 |         dependency,
 76 |         version,
 77 |         type,
 78 |         Status.FOUND.getValue(),
 79 |         null,
 80 |         totalVersions,
 81 |         stableVersions,
 82 |         null,
 83 |         null,
 84 |         null,
 85 |         null,
 86 |         null);
 87 |   }
 88 | 
 89 |   public static BulkCheckResult foundWithCounts(
 90 |       String dependency, String version, String type, int totalVersions, int stableVersions) {
 91 |     return new BulkCheckResult(
 92 |         dependency,
 93 |         version,
 94 |         type,
 95 |         Status.FOUND.getValue(),
 96 |         null,
 97 |         totalVersions,
 98 |         stableVersions,
 99 |         null,
100 |         null,
101 |         null,
102 |         null,
103 |         null);
104 |   }
105 | 
106 |   public static BulkCheckResult foundComprehensive(
107 |       String dependency,
108 |       String primaryVersion,
109 |       String primaryType,
110 |       int totalVersions,
111 |       int stableVersions,
112 |       VersionInfo latestStable,
113 |       VersionInfo latestRc,
114 |       VersionInfo latestBeta,
115 |       VersionInfo latestAlpha,
116 |       VersionInfo latestMilestone) {
117 |     return new BulkCheckResult(
118 |         dependency,
119 |         primaryVersion,
120 |         primaryType,
121 |         Status.FOUND.getValue(),
122 |         null,
123 |         totalVersions,
124 |         stableVersions,
125 |         latestStable,
126 |         latestRc,
127 |         latestBeta,
128 |         latestAlpha,
129 |         latestMilestone);
130 |   }
131 | 
132 |   public static BulkCheckResult notFound(String dependency) {
133 |     return new BulkCheckResult(
134 |         dependency,
135 |         null,
136 |         null,
137 |         Status.NOT_FOUND.getValue(),
138 |         null,
139 |         null,
140 |         null,
141 |         null,
142 |         null,
143 |         null,
144 |         null,
145 |         null);
146 |   }
147 | 
148 |   public static BulkCheckResult noStableVersion(String dependency, int totalVersions) {
149 |     return new BulkCheckResult(
150 |         dependency,
151 |         null,
152 |         null,
153 |         Status.NO_STABLE_VERSION.getValue(),
154 |         null,
155 |         totalVersions,
156 |         null,
157 |         null,
158 |         null,
159 |         null,
160 |         null,
161 |         null);
162 |   }
163 | 
164 |   public static BulkCheckResult error(String dependency, String error) {
165 |     return new BulkCheckResult(
166 |         dependency,
167 |         null,
168 |         null,
169 |         Status.ERROR.getValue(),
170 |         error,
171 |         null,
172 |         null,
173 |         null,
174 |         null,
175 |         null,
176 |         null,
177 |         null);
178 |   }
179 | }
180 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/service/MavenCentralServiceRepositoryIT.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.service;
  2 | 
  3 | import static org.junit.jupiter.api.Assertions.*;
  4 | 
  5 | import com.arvindand.mcp.maven.model.MavenArtifact;
  6 | import com.arvindand.mcp.maven.model.MavenCoordinate;
  7 | import java.util.List;
  8 | import org.junit.jupiter.api.Test;
  9 | import org.springframework.beans.factory.annotation.Autowired;
 10 | import org.springframework.boot.test.context.SpringBootTest;
 11 | import org.springframework.test.context.ActiveProfiles;
 12 | 
 13 | /**
 14 |  * Integration tests for MavenCentralService repository access functionality. Tests direct
 15 |  * maven-metadata.xml fetching from Maven Central repository.
 16 |  *
 17 |  * @author Arvind Menon
 18 |  * @since 1.4.0
 19 |  */
 20 | @SpringBootTest
 21 | @ActiveProfiles("test")
 22 | class MavenCentralServiceRepositoryIT {
 23 | 
 24 |   @Autowired private MavenCentralService mavenCentralService;
 25 | 
 26 |   @Test
 27 |   void testGetLatestVersionWithRepositoryAccess() {
 28 |     // Test with a well-known, stable artifact
 29 |     MavenCoordinate coordinate = new MavenCoordinate("junit", "junit", null, null, null);
 30 | 
 31 |     String latestVersion = mavenCentralService.getLatestVersion(coordinate);
 32 | 
 33 |     assertNotNull(latestVersion, "Latest version should not be null");
 34 |     assertFalse(latestVersion.trim().isEmpty(), "Latest version should not be empty");
 35 |   }
 36 | 
 37 |   @Test
 38 |   void testGetAllVersionsWithRepositoryAccess() {
 39 |     // Test with a well-known artifact that has multiple versions
 40 |     MavenCoordinate coordinate = new MavenCoordinate("org.slf4j", "slf4j-api", null, null, null);
 41 | 
 42 |     List<String> versions = mavenCentralService.getAllVersions(coordinate);
 43 | 
 44 |     assertNotNull(versions, "Versions list should not be null");
 45 |     assertFalse(versions.isEmpty(), "Versions list should not be empty");
 46 |     assertTrue(versions.size() >= 5, "Should have multiple versions available");
 47 | 
 48 |     // Verify versions are sorted in descending order (latest first)
 49 |     for (int i = 0; i < Math.min(versions.size() - 1, 3); i++) {
 50 |       String current = versions.get(i);
 51 |       String next = versions.get(i + 1);
 52 |       assertNotNull(current, "Version should not be null");
 53 |       assertNotNull(next, "Next version should not be null");
 54 |     }
 55 |   }
 56 | 
 57 |   @Test
 58 |   void testGetRecentVersionsWithAccurateTimestamps() {
 59 |     // Test with a well-known artifact
 60 |     MavenCoordinate coordinate = new MavenCoordinate("com.google.guava", "guava", null, null, null);
 61 | 
 62 |     List<MavenArtifact> versions =
 63 |         mavenCentralService.getRecentVersionsWithAccurateTimestamps(coordinate, 10);
 64 | 
 65 |     assertNotNull(versions, "Versions list should not be null");
 66 |     assertFalse(versions.isEmpty(), "Versions list should not be empty");
 67 |     assertEquals(10, versions.size(), "Should return the requested number of versions");
 68 | 
 69 |     for (MavenArtifact artifact : versions) {
 70 |       assertTrue(artifact.timestamp() > 0, "Timestamp should be valid");
 71 |     }
 72 |   }
 73 | 
 74 |   @Test
 75 |   void testCheckVersionExistsWithRepositoryAccess() {
 76 |     // Test with a specific known version
 77 |     MavenCoordinate coordinate = new MavenCoordinate("junit", "junit", null, null, null);
 78 | 
 79 |     // junit 4.13.2 is a well-known stable version
 80 |     boolean exists = mavenCentralService.checkVersionExists(coordinate, "4.13.2");
 81 |     assertTrue(exists, "junit 4.13.2 should exist");
 82 | 
 83 |     // Test with a non-existent version
 84 |     boolean notExists = mavenCentralService.checkVersionExists(coordinate, "999.999.999");
 85 |     assertFalse(notExists, "Non-existent version should return false");
 86 |   }
 87 | 
 88 |   @Test
 89 |   void testRepositoryAccessWithInvalidCoordinate() {
 90 |     // Test with a non-existent artifact
 91 |     MavenCoordinate invalidCoordinate =
 92 |         new MavenCoordinate("com.nonexistent", "invalid-artifact", null, null, null);
 93 | 
 94 |     String latestVersion = mavenCentralService.getLatestVersion(invalidCoordinate);
 95 |     assertNull(latestVersion, "Non-existent artifact should return null for latest version");
 96 | 
 97 |     List<String> versions = mavenCentralService.getAllVersions(invalidCoordinate);
 98 |     assertTrue(versions.isEmpty(), "Non-existent artifact should return empty versions list");
 99 | 
100 |     boolean exists = mavenCentralService.checkVersionExists(invalidCoordinate, "1.0.0");
101 |     assertFalse(exists, "Non-existent artifact should return false for version check");
102 | 
103 |     List<MavenArtifact> timestampedVersions =
104 |         mavenCentralService.getRecentVersionsWithAccurateTimestamps(invalidCoordinate, 10);
105 |     assertTrue(
106 |         timestampedVersions.isEmpty(),
107 |         "Non-existent artifact should return empty list for timestamped versions");
108 |   }
109 | 
110 |   @Test
111 |   void testRepositoryAccessWithSpringBootStarter() {
112 |     // Test with a Spring Boot starter to verify complex groupId handling
113 |     MavenCoordinate coordinate =
114 |         new MavenCoordinate("org.springframework.boot", "spring-boot-starter", null, null, null);
115 | 
116 |     String latestVersion = mavenCentralService.getLatestVersion(coordinate);
117 |     assertNotNull(latestVersion, "Spring Boot starter should have a latest version");
118 | 
119 |     List<String> versions = mavenCentralService.getAllVersions(coordinate);
120 |     assertFalse(versions.isEmpty(), "Spring Boot starter should have multiple versions");
121 |     assertTrue(versions.contains(latestVersion), "Latest version should be in versions list");
122 |   }
123 | }
124 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/MavenMcpServerIT.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven;
  2 | 
  3 | import static org.assertj.core.api.Assertions.assertThat;
  4 | 
  5 | import com.arvindand.mcp.maven.model.MavenCoordinate;
  6 | import com.arvindand.mcp.maven.service.MavenCentralService;
  7 | import java.util.stream.Stream;
  8 | import org.junit.jupiter.api.Test;
  9 | import org.junit.jupiter.params.ParameterizedTest;
 10 | import org.junit.jupiter.params.provider.Arguments;
 11 | import org.junit.jupiter.params.provider.MethodSource;
 12 | import org.springframework.beans.factory.annotation.Autowired;
 13 | import org.springframework.boot.test.context.SpringBootTest;
 14 | import org.springframework.test.context.ActiveProfiles;
 15 | import org.springframework.test.context.TestPropertySource;
 16 | 
 17 | /**
 18 |  * Integration tests for Maven MCP Server functionality.
 19 |  *
 20 |  * @author Arvind Menon
 21 |  * @since 0.1.0
 22 |  */
 23 | @SpringBootTest
 24 | @ActiveProfiles("test")
 25 | @TestPropertySource(properties = {"maven.central.timeout=PT10S", "maven.central.max-results=50"})
 26 | class MavenMcpServerIT {
 27 | 
 28 |   @Autowired private MavenCentralService mavenCentralService;
 29 | 
 30 |   /** Provides test data for the parameterized getLatestVersion test. */
 31 |   static Stream<Arguments> getLatestVersionTestData() {
 32 |     return Stream.of(
 33 |         Arguments.of("org.springframework", "spring-core", "Spring Core"),
 34 |         Arguments.of("junit", "junit", "JUnit"),
 35 |         Arguments.of("com.google.guava", "guava", "Google Guava"));
 36 |   }
 37 | 
 38 |   /**
 39 |    * Tests getting the latest version of various Maven artifacts. This test verifies the complete
 40 |    * integration with Maven Central API for different types of artifacts.
 41 |    */
 42 |   @ParameterizedTest(name = "Getting latest version for {2}")
 43 |   @MethodSource("getLatestVersionTestData")
 44 |   void testGetLatestVersion(String groupId, String artifactId, String displayName) {
 45 |     // Given
 46 |     MavenCoordinate coordinate = MavenCoordinate.of(groupId, artifactId);
 47 | 
 48 |     // When
 49 |     String latestVersion = mavenCentralService.getLatestVersion(coordinate);
 50 | 
 51 |     // Then - Version should be a valid version string (may contain dots, letters, numbers, etc.)
 52 |     assertThat(latestVersion).isNotNull().isNotEmpty().matches("^[\\w\\.\\-]+$");
 53 |     System.out.println(displayName + " latest version: " + latestVersion);
 54 |   }
 55 | 
 56 |   /**
 57 |    * Tests checking if a specific version exists for Spring Core. This tests the version existence
 58 |    * checking functionality.
 59 |    */
 60 |   @Test
 61 |   void testCheckVersionExists_SpringCore_ExistingVersion() {
 62 |     // Given
 63 |     MavenCoordinate coordinate = MavenCoordinate.of("org.springframework", "spring-core");
 64 | 
 65 |     // When - checking for version 6.0.0 which should exist
 66 |     boolean exists = mavenCentralService.checkVersionExists(coordinate, "6.0.0");
 67 | 
 68 |     // Then
 69 |     assertThat(exists).isTrue();
 70 |     System.out.println("Spring Core 6.0.0 exists: " + exists);
 71 |   }
 72 | 
 73 |   /**
 74 |    * Tests checking if a non-existing version exists. This tests error handling for non-existing
 75 |    * versions.
 76 |    */
 77 |   @Test
 78 |   void testCheckVersionExists_NonExistingVersion() {
 79 |     // Given
 80 |     MavenCoordinate coordinate = MavenCoordinate.of("org.springframework", "spring-core");
 81 | 
 82 |     // When - checking for a version that definitely doesn't exist
 83 |     boolean exists = mavenCentralService.checkVersionExists(coordinate, "999.999.999");
 84 | 
 85 |     // Then
 86 |     assertThat(exists).isFalse();
 87 |     System.out.println("Spring Core 999.999.999 exists: " + exists);
 88 |   }
 89 | 
 90 |   /**
 91 |    * Tests caching performance by making the same request twice. The second request should be
 92 |    * significantly faster due to caching.
 93 |    */
 94 |   @Test
 95 |   void testCaching_Performance() {
 96 |     // Given
 97 |     MavenCoordinate coordinate = MavenCoordinate.of("org.springframework", "spring-core");
 98 | 
 99 |     // When - first request (will hit Maven Central API)
100 |     long startTime1 = System.nanoTime();
101 |     String version1 = mavenCentralService.getLatestVersion(coordinate);
102 |     long duration1 = System.nanoTime() - startTime1;
103 | 
104 |     // When - second request (should use cache)
105 |     long startTime2 = System.nanoTime();
106 |     String version2 = mavenCentralService.getLatestVersion(coordinate);
107 |     long duration2 = System.nanoTime() - startTime2;
108 | 
109 |     // Then
110 |     assertThat(version1).isEqualTo(version2);
111 | 
112 |     // Convert to milliseconds for readability
113 |     long duration1Ms = duration1 / 1_000_000;
114 |     long duration2Ms = duration2 / 1_000_000;
115 | 
116 |     System.out.println("First request took: " + duration1Ms + "ms");
117 |     System.out.println("Second request took: " + duration2Ms + "ms (cached)");
118 |     System.out.println("Performance improvement: " + (duration1Ms - duration2Ms) + "ms");
119 |   }
120 | 
121 |   /**
122 |    * Tests error handling for invalid group and artifact IDs. This verifies graceful handling of
123 |    * non-existing artifacts with repository-first approach.
124 |    */
125 |   @Test
126 |   void testErrorHandling_InvalidArtifact() {
127 |     // Given
128 |     MavenCoordinate coordinate =
129 |         MavenCoordinate.of("com.nonexistent.invalid", "invalid-artifact-xyz");
130 | 
131 |     // When
132 |     String latestVersion = mavenCentralService.getLatestVersion(coordinate);
133 | 
134 |     // Then - Repository-first approach returns null for non-existent artifacts
135 |     assertThat(latestVersion).isNull();
136 |     System.out.println("Non-existent artifact gracefully returns: " + latestVersion);
137 |   }
138 | 
139 |   /**
140 |    * Tests getting the latest version for a POM artifact (different packaging type). This ensures
141 |    * our service can handle different Maven packaging types correctly.
142 |    */
143 |   @Test
144 |   void testGetLatestVersion_PomPackaging() {
145 |     // Given - Spring Boot Starter Parent is a POM artifact, not JAR
146 |     MavenCoordinate coordinate =
147 |         new MavenCoordinate(
148 |             "org.springframework.boot", "spring-boot-starter-parent", null, "pom", null);
149 | 
150 |     // When
151 |     String latestVersion = mavenCentralService.getLatestVersion(coordinate);
152 | 
153 |     // Then
154 |     assertThat(latestVersion).isNotNull().isNotEmpty().contains(".");
155 |     System.out.println("Spring Boot Starter Parent (POM) latest version: " + latestVersion);
156 |   }
157 | }
158 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/ReleasePatternAnalysis.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.model;
  2 | 
  3 | import java.time.Instant;
  4 | import java.util.List;
  5 | 
  6 | /**
  7 |  * Analysis of dependency release patterns and maintenance activity.
  8 |  *
  9 |  * @param dependency the dependency coordinate
 10 |  * @param versionsAnalyzed number of versions included in analysis
 11 |  * @param timeSpanMonths time span of analysis in months
 12 |  * @param averageDaysBetweenReleases average time between releases
 13 |  * @param releaseVelocity releases per month on average
 14 |  * @param maintenanceLevel classified maintenance activity level
 15 |  * @param releaseConsistency how consistent the release pattern is
 16 |  * @param lastReleaseDate date of the most recent release
 17 |  * @param nextReleasePrediction predicted timeframe for next release
 18 |  * @param recentReleases list of recent releases with timestamps
 19 |  * @param recommendation maintenance-based recommendation
 20 |  * @author Arvind Menon
 21 |  * @since 1.1.0
 22 |  */
 23 | public record ReleasePatternAnalysis(
 24 |     String dependency,
 25 |     int versionsAnalyzed,
 26 |     int timeSpanMonths,
 27 |     double averageDaysBetweenReleases,
 28 |     double releaseVelocity,
 29 |     MaintenanceLevel maintenanceLevel,
 30 |     ReleaseConsistency releaseConsistency,
 31 |     Instant lastReleaseDate,
 32 |     String nextReleasePrediction,
 33 |     List<ReleaseInfo> recentReleases,
 34 |     String recommendation) {
 35 | 
 36 |   /** Maintenance activity level classification. */
 37 |   public enum MaintenanceLevel {
 38 |     ACTIVE("active", "Frequent releases with consistent maintenance"),
 39 |     MODERATE("moderate", "Regular releases with good maintenance"),
 40 |     SLOW("slow", "Infrequent releases but still maintained"),
 41 |     INACTIVE("inactive", "Very rare releases, possible maintenance issues");
 42 | 
 43 |     private final String name;
 44 |     private final String description;
 45 | 
 46 |     MaintenanceLevel(String name, String description) {
 47 |       this.name = name;
 48 |       this.description = description;
 49 |     }
 50 | 
 51 |     public String getName() {
 52 |       return name;
 53 |     }
 54 | 
 55 |     public String getDescription() {
 56 |       return description;
 57 |     }
 58 | 
 59 |     /**
 60 |      * Classify maintenance level based on release velocity.
 61 |      *
 62 |      * @param releasesPerMonth average releases per month
 63 |      * @param daysSinceLastRelease days since most recent release
 64 |      * @return appropriate maintenance level
 65 |      */
 66 |     public static MaintenanceLevel classify(double releasesPerMonth, long daysSinceLastRelease) {
 67 |       // Factor in recency of last release
 68 |       if (daysSinceLastRelease > 365) {
 69 |         return INACTIVE;
 70 |       } else if (releasesPerMonth >= 2.0) {
 71 |         return ACTIVE;
 72 |       } else if (releasesPerMonth >= 0.5) {
 73 |         return MODERATE;
 74 |       } else if (releasesPerMonth >= 0.1) {
 75 |         return SLOW;
 76 |       } else {
 77 |         return INACTIVE;
 78 |       }
 79 |     }
 80 |   }
 81 | 
 82 |   /** Release pattern consistency classification. */
 83 |   public enum ReleaseConsistency {
 84 |     VERY_CONSISTENT("very_consistent", "Highly predictable release schedule"),
 85 |     CONSISTENT("consistent", "Generally predictable timing"),
 86 |     VARIABLE("variable", "Irregular but reasonable timing"),
 87 |     ERRATIC("erratic", "Unpredictable release patterns");
 88 | 
 89 |     private final String name;
 90 |     private final String description;
 91 | 
 92 |     ReleaseConsistency(String name, String description) {
 93 |       this.name = name;
 94 |       this.description = description;
 95 |     }
 96 | 
 97 |     public String getName() {
 98 |       return name;
 99 |     }
100 | 
101 |     public String getDescription() {
102 |       return description;
103 |     }
104 | 
105 |     /**
106 |      * Classify release consistency based on variance in release intervals.
107 |      *
108 |      * @param averageDays average days between releases
109 |      * @param maxInterval longest interval between releases
110 |      * @param minInterval shortest interval between releases
111 |      * @return appropriate consistency classification
112 |      */
113 |     public static ReleaseConsistency classify(
114 |         double averageDays, long maxInterval, long minInterval) {
115 |       if (averageDays == 0) return ERRATIC;
116 | 
117 |       double variance = (maxInterval - minInterval) / averageDays;
118 | 
119 |       if (variance <= 0.5) {
120 |         return VERY_CONSISTENT;
121 |       } else if (variance <= 1.0) {
122 |         return CONSISTENT;
123 |       } else if (variance <= 2.0) {
124 |         return VARIABLE;
125 |       } else {
126 |         return ERRATIC;
127 |       }
128 |     }
129 |   }
130 | 
131 |   /**
132 |    * Information about a specific release.
133 |    *
134 |    * @param version the version string
135 |    * @param releaseDate when this version was released
136 |    * @param daysSincePrevious days since the previous release
137 |    */
138 |   public record ReleaseInfo(String version, Instant releaseDate, Long daysSincePrevious) {}
139 | 
140 |   /**
141 |    * Generate recommendation based on maintenance analysis.
142 |    *
143 |    * @param maintenanceLevel the classified maintenance level
144 |    * @return maintenance-based recommendation
145 |    */
146 |   public static String generateRecommendation(MaintenanceLevel maintenanceLevel) {
147 | 
148 |     return switch (maintenanceLevel) {
149 |       case ACTIVE -> "Well-maintained dependency with active development - safe to use";
150 |       case MODERATE -> "Regularly maintained - good choice for production use";
151 |       case SLOW ->
152 |           "Slowly maintained - monitor for updates and consider alternatives for critical projects";
153 |       case INACTIVE -> "Minimal maintenance activity - evaluate alternatives and migration plan";
154 |     };
155 |   }
156 | 
157 |   /**
158 |    * Predict next release timeframe based on historical patterns.
159 |    *
160 |    * @param averageDaysBetweenReleases average interval between releases
161 |    * @param daysSinceLastRelease days since most recent release
162 |    * @param consistency how consistent the release pattern is
163 |    * @return predicted next release timeframe
164 |    */
165 |   public static String predictNextRelease(
166 |       double averageDaysBetweenReleases,
167 |       long daysSinceLastRelease,
168 |       ReleaseConsistency consistency) {
169 | 
170 |     if (averageDaysBetweenReleases == 0) {
171 |       return "Unable to predict - insufficient release history";
172 |     }
173 | 
174 |     long expectedDays = Math.round(averageDaysBetweenReleases);
175 |     long overdue = daysSinceLastRelease - expectedDays;
176 | 
177 |     if (consistency == ReleaseConsistency.ERRATIC) {
178 |       return "Unpredictable release schedule";
179 |     }
180 | 
181 |     if (overdue > expectedDays / 2) {
182 |       return "Overdue - expected " + (overdue) + " days ago";
183 |     } else if (overdue > 0) {
184 |       return "Due soon - typically releases every " + expectedDays + " days";
185 |     } else {
186 |       long remaining = expectedDays - daysSinceLastRelease;
187 |       if (remaining <= 7) {
188 |         return "Expected within the next week";
189 |       } else if (remaining <= 30) {
190 |         return "Expected in " + remaining + " days";
191 |       } else {
192 |         return "Expected in " + (remaining / 30) + " months";
193 |       }
194 |     }
195 |   }
196 | }
197 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/service/MavenDependencyToolsIT.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.service;
  2 | 
  3 | import static com.arvindand.mcp.maven.TestHelpers.getSuccessData;
  4 | import static org.junit.jupiter.api.Assertions.*;
  5 | 
  6 | import com.arvindand.mcp.maven.model.*;
  7 | import java.util.List;
  8 | import org.junit.jupiter.api.Test;
  9 | import org.springframework.beans.factory.annotation.Autowired;
 10 | import org.springframework.boot.test.context.SpringBootTest;
 11 | import org.springframework.test.context.ActiveProfiles;
 12 | 
 13 | /**
 14 |  * Integration tests for Maven Dependency Tools using type-safe object assertions.
 15 |  *
 16 |  * <p>Tests the complete flow from MCP tool invocation to business logic validation, using proper
 17 |  * object assertions instead of fragile JSON string matching.
 18 |  *
 19 |  * @author Arvind Menon
 20 |  * @since 0.1.0
 21 |  */
 22 | @SpringBootTest
 23 | @ActiveProfiles("test")
 24 | class MavenDependencyToolsIT {
 25 | 
 26 |   @Autowired private MavenDependencyTools mavenDependencyTools;
 27 | 
 28 |   @Test
 29 |   void testGetLatestVersion() {
 30 |     ToolResponse response =
 31 |         mavenDependencyTools.get_latest_version(
 32 |             "org.springframework:spring-core", StabilityFilter.ALL);
 33 | 
 34 |     VersionsByType result = getSuccessData(response);
 35 |     assertEquals("org.springframework:spring-core", result.dependency());
 36 |     assertTrue(result.latestStable().isPresent() || result.totalVersions() > 0);
 37 | 
 38 |     if (result.latestStable().isPresent()) {
 39 |       VersionInfo stable = result.latestStable().get();
 40 |       assertEquals(VersionInfo.VersionType.STABLE, stable.type());
 41 |       assertNotNull(stable.version());
 42 |     }
 43 |   }
 44 | 
 45 |   @Test
 46 |   void testCheckVersionExists() {
 47 |     ToolResponse response = mavenDependencyTools.check_version_exists("junit:junit", "4.13.2");
 48 | 
 49 |     DependencyInfo result = getSuccessData(response);
 50 |     assertEquals("success", result.status());
 51 |     assertEquals("junit", result.groupId());
 52 |     assertEquals("junit", result.artifactId());
 53 |     assertEquals("4.13.2", result.version());
 54 |     assertTrue(result.exists());
 55 |     assertEquals("stable", result.type());
 56 |     assertTrue(result.isStable());
 57 |   }
 58 | 
 59 |   @Test
 60 |   void testCheckVersionExistsNotFound() {
 61 |     ToolResponse response = mavenDependencyTools.check_version_exists("junit:junit", "999.999.999");
 62 | 
 63 |     DependencyInfo result = getSuccessData(response);
 64 |     assertEquals("success", result.status());
 65 |     assertEquals("junit", result.groupId());
 66 |     assertEquals("junit", result.artifactId());
 67 |     assertEquals("999.999.999", result.version());
 68 |     assertFalse(result.exists());
 69 |   }
 70 | 
 71 |   @Test
 72 |   void testBulkCheckDependencies() {
 73 |     String dependencies = "org.springframework:spring-core,junit:junit";
 74 |     ToolResponse response =
 75 |         mavenDependencyTools.check_multiple_dependencies(dependencies, StabilityFilter.ALL);
 76 | 
 77 |     List<BulkCheckResult> results = getSuccessData(response);
 78 |     assertEquals(2, results.size());
 79 | 
 80 |     // Verify spring-core result
 81 |     BulkCheckResult springResult =
 82 |         results.stream()
 83 |             .filter(r -> r.dependency().contains("spring-core"))
 84 |             .findFirst()
 85 |             .orElseThrow(() -> new AssertionError("Expected spring-core result"));
 86 |     assertEquals("found", springResult.status());
 87 |     assertNotNull(springResult.version());
 88 | 
 89 |     // Verify junit result
 90 |     BulkCheckResult junitResult =
 91 |         results.stream()
 92 |             .filter(r -> r.dependency().contains("junit:junit"))
 93 |             .findFirst()
 94 |             .orElseThrow(() -> new AssertionError("Expected junit result"));
 95 |     assertEquals("found", junitResult.status());
 96 |     assertNotNull(junitResult.version());
 97 |   }
 98 | 
 99 |   @Test
100 |   void testBulkCheckStableOnly() {
101 |     String dependencies = "org.springframework:spring-core,com.fasterxml.jackson.core:jackson-core";
102 |     ToolResponse response =
103 |         mavenDependencyTools.check_multiple_dependencies(dependencies, StabilityFilter.STABLE_ONLY);
104 | 
105 |     List<BulkCheckResult> results = getSuccessData(response);
106 |     assertEquals(2, results.size());
107 | 
108 |     // All results should be stable versions when stableOnly=true
109 |     for (BulkCheckResult result : results) {
110 |       assertEquals("found", result.status());
111 |       assertEquals("stable", result.type());
112 |       assertNotNull(result.version());
113 |     }
114 |   }
115 | 
116 |   @Test
117 |   void testVersionComparison() {
118 |     String currentDependencies = "junit:junit:4.12";
119 |     ToolResponse response =
120 |         mavenDependencyTools.compare_dependency_versions(currentDependencies, StabilityFilter.ALL);
121 | 
122 |     VersionComparison comparison = getSuccessData(response);
123 |     assertNotNull(comparison.comparisonDate());
124 |     assertNotNull(comparison.updateSummary());
125 |     assertEquals(1, comparison.dependencies().size());
126 | 
127 |     var depResult = comparison.dependencies().get(0);
128 |     assertEquals("junit:junit:4.12", depResult.dependency());
129 |     assertEquals("4.12", depResult.currentVersion());
130 |     assertEquals("success", depResult.status());
131 |     assertNotNull(depResult.latestVersion());
132 | 
133 |     // Context7 guidance should be empty in test profile (disabled)
134 |     // Note: Context7 might be enabled for this dependency, so just verify structure
135 |     assertNotNull(depResult.context7Guidance());
136 |   }
137 | 
138 |   @Test
139 |   void testAnalyzeDependencyAge() {
140 |     ToolResponse response = mavenDependencyTools.analyze_dependency_age("junit:junit", null);
141 | 
142 |     DependencyAge result = getSuccessData(response);
143 |     assertEquals("junit:junit", result.dependency());
144 |     assertNotNull(result.latestVersion());
145 |     assertNotNull(result.ageClassification());
146 |     assertTrue(result.daysSinceLastRelease() >= 0);
147 |     assertNotNull(result.lastReleaseDate());
148 |     assertNotNull(result.recommendation());
149 | 
150 |     // Context7 guidance should be empty in test profile (disabled)
151 |     // Note: Context7 might be enabled for this dependency, so just verify structure
152 |     assertNotNull(result.context7Guidance());
153 |   }
154 | 
155 |   @Test
156 |   void testProjectHealthAnalysis() {
157 |     String dependencies = "junit:junit:4.12,org.slf4j:slf4j-api:1.7.30";
158 |     ToolResponse response = mavenDependencyTools.analyze_project_health(dependencies, null, null);
159 | 
160 |     ProjectHealthAnalysis result = getSuccessData(response);
161 |     assertNotNull(result.analysisDate());
162 |     assertTrue(result.dependencyCount() >= 2);
163 |     assertTrue(result.dependencies().size() >= 2);
164 |     assertNotNull(result.ageDistribution());
165 |     assertFalse(result.recommendations().isEmpty());
166 | 
167 |     // Verify individual dependency analyses
168 |     for (var depHealth : result.dependencies()) {
169 |       assertNotNull(depHealth.dependency());
170 |       assertNotNull(depHealth.latestVersion());
171 |       assertNotNull(depHealth.ageClassification());
172 |       assertTrue(
173 |           depHealth.healthScore() >= 0); // healthScore is primitive int, check for valid range
174 |     }
175 |   }
176 | 
177 |   @Test
178 |   void testErrorHandling() {
179 |     ToolResponse response =
180 |         mavenDependencyTools.check_version_exists("invalid.group:nonexistent", "1.0.0");
181 | 
182 |     // Should still be success response, but with exists=false
183 |     DependencyInfo result = getSuccessData(response);
184 |     assertEquals("success", result.status());
185 |     assertFalse(result.exists());
186 |   }
187 | }
188 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/config/NativeImageConfiguration.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.config;
  2 | 
  3 | import com.arvindand.mcp.maven.model.BulkCheckResult;
  4 | import com.arvindand.mcp.maven.model.Context7Guidance;
  5 | import com.arvindand.mcp.maven.model.DependencyAge;
  6 | import com.arvindand.mcp.maven.model.DependencyAgeAnalysis;
  7 | import com.arvindand.mcp.maven.model.DependencyInfo;
  8 | import com.arvindand.mcp.maven.model.MavenArtifact;
  9 | import com.arvindand.mcp.maven.model.MavenCoordinate;
 10 | import com.arvindand.mcp.maven.model.MavenMetadata;
 11 | import com.arvindand.mcp.maven.model.ProjectHealthAnalysis;
 12 | import com.arvindand.mcp.maven.model.ReleasePatternAnalysis;
 13 | import com.arvindand.mcp.maven.model.ToolResponse;
 14 | import com.arvindand.mcp.maven.model.VersionComparison;
 15 | import com.arvindand.mcp.maven.model.VersionInfo;
 16 | import com.arvindand.mcp.maven.model.VersionTimelineAnalysis;
 17 | import com.arvindand.mcp.maven.model.VersionsByType;
 18 | import org.springframework.aot.hint.MemberCategory;
 19 | import org.springframework.aot.hint.RuntimeHints;
 20 | import org.springframework.aot.hint.RuntimeHintsRegistrar;
 21 | import org.springframework.context.annotation.Configuration;
 22 | import org.springframework.context.annotation.ImportRuntimeHints;
 23 | import org.springframework.lang.NonNull;
 24 | import org.springframework.lang.Nullable;
 25 | 
 26 | /**
 27 |  * Native image configuration for reflection hints. This configuration ensures that record classes
 28 |  * and their methods are accessible in native images, particularly for SpEL expression evaluation in
 29 |  * caching annotations and Jackson JSON serialization.
 30 |  *
 31 |  * @author Arvind Menon
 32 |  * @since 0.1.0
 33 |  */
 34 | @Configuration(proxyBeanMethods = false)
 35 | @ImportRuntimeHints(NativeImageConfiguration.MavenRecordHints.class)
 36 | public class NativeImageConfiguration {
 37 | 
 38 |   private NativeImageConfiguration() {
 39 |     // Prevent instantiation
 40 |   }
 41 | 
 42 |   /**
 43 |    * Runtime hints registrar for Maven record classes to ensure proper reflection access in native
 44 |    * images.
 45 |    */
 46 |   static class MavenRecordHints implements RuntimeHintsRegistrar {
 47 |     @Override
 48 |     public void registerHints(@NonNull RuntimeHints hints, @Nullable ClassLoader classLoader) {
 49 |       // Register all record classes with comprehensive reflection access
 50 |       registerRecordClass(hints, MavenCoordinate.class);
 51 |       registerRecordClass(hints, BulkCheckResult.class);
 52 |       registerRecordClass(hints, DependencyInfo.class);
 53 |       registerRecordClass(hints, VersionComparison.class);
 54 |       registerRecordClass(hints, VersionComparison.DependencyComparisonResult.class);
 55 |       registerRecordClass(hints, VersionComparison.UpdateSummary.class);
 56 |       registerRecordClass(hints, VersionInfo.class);
 57 | 
 58 |       // Register MavenArtifact for timestamp analysis compatibility
 59 |       registerRecordClass(hints, MavenArtifact.class);
 60 | 
 61 |       // Register MavenMetadata and its nested records for XML deserialization (v1.4.0)
 62 |       registerRecordClass(hints, MavenMetadata.class);
 63 |       registerRecordClass(hints, MavenMetadata.VersioningInfo.class);
 64 |       registerRecordClass(hints, MavenMetadata.VersionList.class);
 65 |       registerRecordClass(hints, MavenMetadata.SnapshotInfo.class);
 66 | 
 67 |       // Register VersionComparator record for version parsing
 68 |       registerRecordClass(
 69 |           hints, com.arvindand.mcp.maven.util.VersionComparator.VersionComponents.class);
 70 | 
 71 |       // Register new analytical intelligence record classes (v1.1.0)
 72 |       registerRecordClass(hints, DependencyAgeAnalysis.class);
 73 |       registerRecordClass(hints, ReleasePatternAnalysis.class);
 74 |       registerRecordClass(hints, ReleasePatternAnalysis.ReleaseInfo.class);
 75 |       registerRecordClass(hints, VersionTimelineAnalysis.class);
 76 |       registerRecordClass(hints, VersionTimelineAnalysis.TimelineEntry.class);
 77 |       registerRecordClass(hints, VersionTimelineAnalysis.VelocityTrend.class);
 78 |       registerRecordClass(hints, VersionTimelineAnalysis.StabilityPattern.class);
 79 |       registerRecordClass(hints, VersionTimelineAnalysis.RecentActivity.class);
 80 | 
 81 |       // Register Context7 integration record classes (v1.2.0)
 82 |       registerRecordClass(hints, Context7Guidance.class);
 83 |       registerRecordClass(hints, DependencyAge.class);
 84 | 
 85 |       // Register structured response record classes (v1.2.0)
 86 |       registerRecordClass(hints, ProjectHealthAnalysis.class);
 87 |       registerRecordClass(hints, ProjectHealthAnalysis.AgeDistribution.class);
 88 |       registerRecordClass(hints, ProjectHealthAnalysis.DependencyHealthAnalysis.class);
 89 |       registerRecordClass(hints, VersionsByType.class);
 90 | 
 91 |       registerRecordClass(hints, ToolResponse.class);
 92 |       registerRecordClass(hints, ToolResponse.Success.class);
 93 |       registerRecordClass(hints, ToolResponse.Error.class);
 94 | 
 95 |       // Register configuration properties record classes
 96 |       registerRecordClass(hints, MavenCentralProperties.class);
 97 |       registerRecordClass(hints, Context7Properties.class);
 98 | 
 99 |       // Register enum classes for Jackson serialization and reflection access
100 |       registerEnumClass(hints, VersionInfo.VersionType.class);
101 |       registerEnumClass(hints, BulkCheckResult.Status.class);
102 | 
103 |       // Register new analytical intelligence enum classes (v1.1.0)
104 |       registerEnumClass(hints, DependencyAgeAnalysis.AgeClassification.class);
105 |       registerEnumClass(hints, ReleasePatternAnalysis.MaintenanceLevel.class);
106 |       registerEnumClass(hints, ReleasePatternAnalysis.ReleaseConsistency.class);
107 |       registerEnumClass(hints, VersionTimelineAnalysis.TimelineEntry.ReleaseGap.class);
108 |       registerEnumClass(hints, VersionTimelineAnalysis.VelocityTrend.TrendDirection.class);
109 |       registerEnumClass(hints, VersionTimelineAnalysis.RecentActivity.ActivityLevel.class);
110 |     }
111 | 
112 |     /**
113 |      * Register a record class with all necessary reflection access for Jackson serialization and
114 |      * SpEL expression evaluation.
115 |      */
116 |     private void registerRecordClass(RuntimeHints hints, Class<?> recordClass) {
117 |       hints
118 |           .reflection()
119 |           .registerType(
120 |               recordClass,
121 |               typeHint ->
122 |                   typeHint.withMembers(
123 |                       MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS,
124 |                       MemberCategory.INVOKE_PUBLIC_METHODS,
125 |                       MemberCategory.DECLARED_FIELDS,
126 |                       MemberCategory.PUBLIC_FIELDS,
127 |                       MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS,
128 |                       MemberCategory.INTROSPECT_PUBLIC_METHODS,
129 |                       MemberCategory.INTROSPECT_DECLARED_METHODS));
130 |     }
131 | 
132 |     /**
133 |      * Register an enum class with reflection access for Jackson serialization and native image
134 |      * compatibility.
135 |      */
136 |     private void registerEnumClass(RuntimeHints hints, Class<?> enumClass) {
137 |       hints
138 |           .reflection()
139 |           .registerType(
140 |               enumClass,
141 |               typeHint ->
142 |                   typeHint.withMembers(
143 |                       MemberCategory.PUBLIC_FIELDS,
144 |                       MemberCategory.INVOKE_PUBLIC_METHODS,
145 |                       MemberCategory.INTROSPECT_PUBLIC_METHODS,
146 |                       MemberCategory.INTROSPECT_DECLARED_METHODS));
147 |     }
148 |   }
149 | }
150 | 
```

--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------

```
  1 | <# : batch portion
  2 | @REM ----------------------------------------------------------------------------
  3 | @REM Licensed to the Apache Software Foundation (ASF) under one
  4 | @REM or more contributor license agreements.  See the NOTICE file
  5 | @REM distributed with this work for additional information
  6 | @REM regarding copyright ownership.  The ASF licenses this file
  7 | @REM to you under the Apache License, Version 2.0 (the
  8 | @REM "License"); you may not use this file except in compliance
  9 | @REM with the License.  You may obtain a copy of the License at
 10 | @REM
 11 | @REM    http://www.apache.org/licenses/LICENSE-2.0
 12 | @REM
 13 | @REM Unless required by applicable law or agreed to in writing,
 14 | @REM software distributed under the License is distributed on an
 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 16 | @REM KIND, either express or implied.  See the License for the
 17 | @REM specific language governing permissions and limitations
 18 | @REM under the License.
 19 | @REM ----------------------------------------------------------------------------
 20 | 
 21 | @REM ----------------------------------------------------------------------------
 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2
 23 | @REM
 24 | @REM Optional ENV vars
 25 | @REM   MVNW_REPOURL - repo url base for downloading maven distribution
 26 | @REM   MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
 27 | @REM   MVNW_VERBOSE - true: enable verbose log; others: silence the output
 28 | @REM ----------------------------------------------------------------------------
 29 | 
 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
 31 | @SET __MVNW_CMD__=
 32 | @SET __MVNW_ERROR__=
 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
 34 | @SET PSModulePath=
 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
 36 |   IF "%%A"=="MVN_CMD" (set "__MVNW_CMD__=%%B") ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
 37 | )
 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
 39 | @SET __MVNW_PSMODULEP_SAVE=
 40 | @SET __MVNW_ARG0_NAME__=
 41 | @SET MVNW_USERNAME=
 42 | @SET MVNW_PASSWORD=
 43 | @IF NOT "%__MVNW_CMD__%"=="" (call "%__MVNW_CMD__%" %* && exit /b 0)
 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1
 45 | @GOTO :EOF
 46 | : end batch / begin powershell #>
 47 | 
 48 | $ErrorActionPreference = "Stop"
 49 | if ($env:MVNW_VERBOSE -eq "true") {
 50 |   $VerbosePreference = "Continue"
 51 | }
 52 | 
 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
 55 | if (!$distributionUrl) {
 56 |   Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
 57 | }
 58 | 
 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
 60 |   "maven-mvnd-*" {
 61 |     $USE_MVND = $true
 62 |     $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
 63 |     $MVN_CMD = "mvnd.cmd"
 64 |     break
 65 |   }
 66 |   default {
 67 |     $USE_MVND = $false
 68 |     $MVN_CMD = $script -replace '^mvnw','mvn'
 69 |     break
 70 |   }
 71 | }
 72 | 
 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME
 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
 75 | if ($env:MVNW_REPOURL) {
 76 |   $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
 77 |   $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
 78 | }
 79 | $distributionUrlName = $distributionUrl -replace '^.*/',''
 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
 81 | $MAVEN_HOME_PARENT = "$env:USERPROFILE\.m2\wrapper\dists\$distributionUrlNameMain"
 82 | if ($env:MAVEN_USER_HOME) {
 83 |   $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME\wrapper\dists\$distributionUrlNameMain"
 84 | }
 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT\$MAVEN_HOME_NAME"
 87 | 
 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
 89 |   Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
 90 |   Write-Output "MVN_CMD=$MAVEN_HOME\bin\$MVN_CMD"
 91 |   exit $?
 92 | }
 93 | 
 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
 95 |   Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
 96 | }
 97 | 
 98 | # prepare tmp dir
 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
102 | trap {
103 |   if ($TMP_DOWNLOAD_DIR.Exists) {
104 |     try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
105 |     catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
106 |   }
107 | }
108 | 
109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
110 | 
111 | # Download and Install Apache Maven
112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
113 | Write-Verbose "Downloading from: $distributionUrl"
114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
115 | 
116 | $webclient = New-Object System.Net.WebClient
117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
118 |   $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
119 | }
120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
122 | 
123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file
124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
125 | if ($distributionSha256Sum) {
126 |   if ($USE_MVND) {
127 |     Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
128 |   }
129 |   Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
130 |   if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
131 |     Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
132 |   }
133 | }
134 | 
135 | # unzip and move
136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
138 | try {
139 |   Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
140 | } catch {
141 |   if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
142 |     Write-Error "fail to move MAVEN_HOME"
143 |   }
144 | } finally {  try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
145 |   catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
146 | }
147 | 
148 | Write-Output "MVN_CMD=$MAVEN_HOME\bin\$MVN_CMD"
149 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/arvindand/mcp/maven/model/VersionTimelineAnalysis.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.model;
  2 | 
  3 | import java.time.Instant;
  4 | import java.time.temporal.ChronoUnit;
  5 | import java.util.List;
  6 | 
  7 | /**
  8 |  * Enhanced version timeline with temporal analysis and patterns.
  9 |  *
 10 |  * @param dependency the dependency coordinate
 11 |  * @param totalVersions total number of versions available
 12 |  * @param versionsReturned number of versions in this response
 13 |  * @param timeSpanMonths time span covered by the timeline
 14 |  * @param versionTimeline chronological list of versions with analysis
 15 |  * @param releaseVelocityTrend how release velocity has changed over time
 16 |  * @param stabilityPattern pattern of stable vs pre-release versions
 17 |  * @param recentActivity summary of recent release activity
 18 |  * @param insights key insights from timeline analysis
 19 |  * @author Arvind Menon
 20 |  * @since 1.1.0
 21 |  */
 22 | public record VersionTimelineAnalysis(
 23 |     String dependency,
 24 |     int totalVersions,
 25 |     int versionsReturned,
 26 |     int timeSpanMonths,
 27 |     List<TimelineEntry> versionTimeline,
 28 |     VelocityTrend releaseVelocityTrend,
 29 |     StabilityPattern stabilityPattern,
 30 |     RecentActivity recentActivity,
 31 |     List<String> insights) {
 32 | 
 33 |   /**
 34 |    * Individual version entry in the timeline.
 35 |    *
 36 |    * @param version the version string
 37 |    * @param versionType type classification of this version
 38 |    * @param releaseDate when this version was released
 39 |    * @param relativeTime human-readable relative time (e.g., "2 months ago")
 40 |    * @param daysSincePrevious days since the previous version
 41 |    * @param isBreakingChange whether this appears to be a breaking change
 42 |    * @param releaseGap gap classification for this release
 43 |    */
 44 |   public record TimelineEntry(
 45 |       String version,
 46 |       VersionInfo.VersionType versionType,
 47 |       Instant releaseDate,
 48 |       String relativeTime,
 49 |       Long daysSincePrevious,
 50 |       boolean isBreakingChange,
 51 |       ReleaseGap releaseGap) {
 52 | 
 53 |     /** Classification of gaps between releases. */
 54 |     public enum ReleaseGap {
 55 |       RAPID("rapid", "Released very quickly after previous"),
 56 |       NORMAL("normal", "Normal time interval"),
 57 |       SLOW("slow", "Longer than usual interval"),
 58 |       MAJOR_GAP("major_gap", "Significant gap in releases");
 59 | 
 60 |       private final String name;
 61 |       private final String description;
 62 | 
 63 |       ReleaseGap(String name, String description) {
 64 |         this.name = name;
 65 |         this.description = description;
 66 |       }
 67 | 
 68 |       public String getName() {
 69 |         return name;
 70 |       }
 71 | 
 72 |       public String getDescription() {
 73 |         return description;
 74 |       }
 75 | 
 76 |       /** Classify release gap based on days since previous and average interval. */
 77 |       public static ReleaseGap classify(long daysSincePrevious, double averageInterval) {
 78 |         if (averageInterval == 0 || daysSincePrevious <= 0) {
 79 |           return NORMAL;
 80 |         }
 81 | 
 82 |         double ratio = daysSincePrevious / averageInterval;
 83 |         if (ratio <= 0.3) {
 84 |           return RAPID;
 85 |         } else if (ratio <= 1.5) {
 86 |           return NORMAL;
 87 |         } else if (ratio <= 3.0) {
 88 |           return SLOW;
 89 |         } else {
 90 |           return MAJOR_GAP;
 91 |         }
 92 |       }
 93 |     }
 94 |   }
 95 | 
 96 |   /**
 97 |    * Analysis of how release velocity has changed over time.
 98 |    *
 99 |    * @param trend the overall trend direction
100 |    * @param description human-readable description of the trend
101 |    * @param recentVelocity releases per month in recent period
102 |    * @param historicalVelocity releases per month historically
103 |    * @param changePercentage percentage change in velocity
104 |    */
105 |   public record VelocityTrend(
106 |       TrendDirection trend,
107 |       String description,
108 |       double recentVelocity,
109 |       double historicalVelocity,
110 |       double changePercentage) {
111 | 
112 |     public enum TrendDirection {
113 |       ACCELERATING("accelerating", "Release frequency is increasing"),
114 |       STABLE("stable", "Release frequency is consistent"),
115 |       DECLINING("declining", "Release frequency is decreasing"),
116 |       ERRATIC("erratic", "Release frequency varies significantly");
117 | 
118 |       private final String name;
119 |       private final String description;
120 | 
121 |       TrendDirection(String name, String description) {
122 |         this.name = name;
123 |         this.description = description;
124 |       }
125 | 
126 |       public String getName() {
127 |         return name;
128 |       }
129 | 
130 |       public String getDescription() {
131 |         return description;
132 |       }
133 |     }
134 |   }
135 | 
136 |   /**
137 |    * Pattern analysis of stable vs pre-release versions.
138 |    *
139 |    * @param stablePercentage percentage of versions that are stable
140 |    * @param prereleasePattern how pre-releases are typically used
141 |    * @param stableReleasePattern typical pattern for stable releases
142 |    * @param recommendation recommendation based on stability patterns
143 |    */
144 |   public record StabilityPattern(
145 |       double stablePercentage,
146 |       String prereleasePattern,
147 |       String stableReleasePattern,
148 |       String recommendation) {}
149 | 
150 |   /**
151 |    * Summary of recent release activity.
152 |    *
153 |    * @param releasesLastMonth number of releases in the last month
154 |    * @param releasesLastQuarter number of releases in the last quarter
155 |    * @param activityLevel classified activity level
156 |    * @param lastReleaseAge days since the last release
157 |    * @param activityDescription human-readable activity description
158 |    */
159 |   public record RecentActivity(
160 |       int releasesLastMonth,
161 |       int releasesLastQuarter,
162 |       ActivityLevel activityLevel,
163 |       long lastReleaseAge,
164 |       String activityDescription) {
165 | 
166 |     public enum ActivityLevel {
167 |       VERY_ACTIVE("very_active", "Multiple releases per month"),
168 |       ACTIVE("active", "Regular monthly releases"),
169 |       MODERATE("moderate", "Quarterly releases"),
170 |       LOW("low", "Infrequent releases"),
171 |       DORMANT("dormant", "No recent releases");
172 | 
173 |       private final String name;
174 |       private final String description;
175 | 
176 |       ActivityLevel(String name, String description) {
177 |         this.name = name;
178 |         this.description = description;
179 |       }
180 | 
181 |       public String getName() {
182 |         return name;
183 |       }
184 | 
185 |       public String getDescription() {
186 |         return description;
187 |       }
188 | 
189 |       public static ActivityLevel classify(int releasesLastMonth, int releasesLastQuarter) {
190 |         if (releasesLastMonth >= 3) {
191 |           return VERY_ACTIVE;
192 |         } else if (releasesLastMonth >= 1) {
193 |           return ACTIVE;
194 |         } else if (releasesLastQuarter >= 2) {
195 |           return MODERATE;
196 |         } else if (releasesLastQuarter >= 1) {
197 |           return LOW;
198 |         } else {
199 |           return DORMANT;
200 |         }
201 |       }
202 |     }
203 |   }
204 | 
205 |   /** Format relative time description. */
206 |   public static String formatRelativeTime(Instant releaseDate, Instant now) {
207 |     long days = ChronoUnit.DAYS.between(releaseDate, now);
208 | 
209 |     if (days == 0) {
210 |       return "today";
211 |     } else if (days == 1) {
212 |       return "yesterday";
213 |     } else if (days <= 7) {
214 |       return days + " days ago";
215 |     } else if (days <= 30) {
216 |       return (days / 7) + " weeks ago";
217 |     } else if (days <= 365) {
218 |       return (days / 30) + " months ago";
219 |     } else {
220 |       long years = days / 365;
221 |       long months = (days % 365) / 30;
222 |       if (months == 0) {
223 |         return years + " years ago";
224 |       } else {
225 |         return years + " years, " + months + " months ago";
226 |       }
227 |     }
228 |   }
229 | }
230 | 
```

--------------------------------------------------------------------------------
/src/test/java/com/arvindand/mcp/maven/service/MavenDependencyToolsPerformanceIT.java:
--------------------------------------------------------------------------------

```java
  1 | package com.arvindand.mcp.maven.service;
  2 | 
  3 | import static org.junit.jupiter.api.Assertions.assertNotNull;
  4 | import static org.junit.jupiter.api.Assertions.assertTrue;
  5 | 
  6 | import com.arvindand.mcp.maven.model.StabilityFilter;
  7 | import com.arvindand.mcp.maven.model.ToolResponse;
  8 | import java.time.Duration;
  9 | import java.time.Instant;
 10 | import org.junit.jupiter.api.Test;
 11 | import org.springframework.beans.factory.annotation.Autowired;
 12 | import org.springframework.boot.test.context.SpringBootTest;
 13 | import org.springframework.test.context.ActiveProfiles;
 14 | 
 15 | /**
 16 |  * Performance tests for Maven Dependency Tools to validate that our optimizations actually improve
 17 |  * performance rather than hurt it.
 18 |  *
 19 |  * @author Arvind Menon
 20 |  * @since 0.1.0
 21 |  */
 22 | @SpringBootTest
 23 | @ActiveProfiles("test")
 24 | class MavenDependencyToolsPerformanceIT {
 25 | 
 26 |   @Autowired private MavenDependencyTools mavenDependencyTools;
 27 | 
 28 |   private static final String SMALL_DEPENDENCY_LIST = "org.springframework:spring-core,junit:junit";
 29 | 
 30 |   private static final String MEDIUM_DEPENDENCY_LIST =
 31 |       "org.springframework:spring-core,junit:junit,com.fasterxml.jackson.core:jackson-core,"
 32 |           + "org.apache.commons:commons-lang3,com.google.guava:guava";
 33 | 
 34 |   private static final String LARGE_DEPENDENCY_LIST =
 35 |       "org.springframework:spring-core,junit:junit,com.fasterxml.jackson.core:jackson-core,"
 36 |           + "org.apache.commons:commons-lang3,com.google.guava:guava,org.slf4j:slf4j-api,"
 37 |           + "ch.qos.logback:logback-classic,org.apache.httpcomponents:httpclient,"
 38 |           + "com.squareup.okhttp3:okhttp,org.apache.maven:maven-core";
 39 | 
 40 |   @Test
 41 |   void testSmallBulkCheckLatestPerformance() {
 42 |     Instant start = Instant.now();
 43 |     ToolResponse resp =
 44 |         mavenDependencyTools.check_multiple_dependencies(
 45 |             SMALL_DEPENDENCY_LIST, StabilityFilter.ALL);
 46 |     Duration duration = Duration.between(start, Instant.now());
 47 | 
 48 |     System.out.println("Small bulk check (2 deps) took: " + duration.toMillis() + "ms");
 49 |     assertNotNull(resp);
 50 |     assertTrue(duration.toSeconds() < 10, "Small bulk check should complete in under 10 seconds");
 51 |   }
 52 | 
 53 |   @Test
 54 |   void testMediumBulkCheckLatestPerformance() {
 55 |     Instant start = Instant.now();
 56 |     ToolResponse resp =
 57 |         mavenDependencyTools.check_multiple_dependencies(
 58 |             MEDIUM_DEPENDENCY_LIST, StabilityFilter.ALL);
 59 |     Duration duration = Duration.between(start, Instant.now());
 60 | 
 61 |     System.out.println("Medium bulk check (5 deps) took: " + duration.toMillis() + "ms");
 62 |     assertNotNull(resp);
 63 |     assertTrue(duration.toSeconds() < 20, "Medium bulk check should complete in under 20 seconds");
 64 |   }
 65 | 
 66 |   @Test
 67 |   void testLargeBulkCheckLatestPerformance() {
 68 |     Instant start = Instant.now();
 69 |     ToolResponse resp =
 70 |         mavenDependencyTools.check_multiple_dependencies(
 71 |             LARGE_DEPENDENCY_LIST, StabilityFilter.ALL);
 72 |     Duration duration = Duration.between(start, Instant.now());
 73 | 
 74 |     System.out.println("Large bulk check (10 deps) took: " + duration.toMillis() + "ms");
 75 |     assertNotNull(resp);
 76 |     assertTrue(duration.toSeconds() < 40, "Large bulk check should complete in under 40 seconds");
 77 |   }
 78 | 
 79 |   @Test
 80 |   void testBulkStablePerformance() {
 81 |     Instant start = Instant.now();
 82 |     ToolResponse resp =
 83 |         mavenDependencyTools.check_multiple_dependencies(
 84 |             MEDIUM_DEPENDENCY_LIST, StabilityFilter.STABLE_ONLY);
 85 |     Duration duration = Duration.between(start, Instant.now());
 86 | 
 87 |     System.out.println("Bulk stable check (5 deps) took: " + duration.toMillis() + "ms");
 88 |     assertNotNull(resp);
 89 |     assertTrue(duration.toSeconds() < 20, "Bulk stable check should complete in under 20 seconds");
 90 |   }
 91 | 
 92 |   @Test
 93 |   void testCompareVersionsPerformance() {
 94 |     String currentDependencies =
 95 |         "org.springframework:spring-core:5.0.0,junit:junit:4.10,"
 96 |             + "com.fasterxml.jackson.core:jackson-core:2.10.0";
 97 | 
 98 |     Instant start = Instant.now();
 99 |     ToolResponse resp =
100 |         mavenDependencyTools.compare_dependency_versions(currentDependencies, StabilityFilter.ALL);
101 |     Duration duration = Duration.between(start, Instant.now());
102 | 
103 |     System.out.println("Version comparison (3 deps) took: " + duration.toMillis() + "ms");
104 |     assertNotNull(resp);
105 |     assertTrue(duration.toSeconds() < 15, "Version comparison should complete in under 15 seconds");
106 |   }
107 | 
108 |   @Test
109 |   void testIndividualCallPerformance() {
110 |     // Test that individual calls are reasonably fast
111 |     Instant start = Instant.now();
112 |     ToolResponse resp =
113 |         mavenDependencyTools.get_latest_version(
114 |             "org.springframework:spring-core", StabilityFilter.ALL);
115 |     Duration duration = Duration.between(start, Instant.now());
116 | 
117 |     System.out.println("Individual get_latest_version took: " + duration.toMillis() + "ms");
118 |     assertNotNull(resp);
119 |     assertTrue(duration.toSeconds() < 5, "Individual call should complete in under 5 seconds");
120 |   }
121 | 
122 |   @Test
123 |   void testCachingEffectiveness() {
124 |     // Use a unique dependency that's unlikely to be cached from other tests
125 |     String uniqueDependency = "com.github.ben-manes.caffeine:caffeine";
126 | 
127 |     // Use nanosecond precision for more accurate timing
128 |     long start1 = System.nanoTime();
129 |     ToolResponse r1 =
130 |         mavenDependencyTools.get_latest_version(uniqueDependency, StabilityFilter.ALL);
131 |     long duration1Nanos = System.nanoTime() - start1;
132 | 
133 |     // Second call should be faster (cached)
134 |     long start2 = System.nanoTime();
135 |     ToolResponse r2 =
136 |         mavenDependencyTools.get_latest_version(uniqueDependency, StabilityFilter.ALL);
137 |     long duration2Nanos = System.nanoTime() - start2;
138 | 
139 |     // Convert to milliseconds for display
140 |     long duration1Ms = duration1Nanos / 1_000_000;
141 |     long duration2Ms = duration2Nanos / 1_000_000;
142 | 
143 |     System.out.println("First call (no cache): " + duration1Ms + "ms (" + duration1Nanos + "ns)");
144 |     System.out.println("Second call (cached): " + duration2Ms + "ms (" + duration2Nanos + "ns)");
145 | 
146 |     assertNotNull(r1);
147 |     assertNotNull(r2);
148 |     // Note: ToolResponse objects may not be equal due to timestamps, but they should both be
149 |     // successful
150 | 
151 |     // More robust caching check:
152 |     // 1. If both calls are very fast (< 1ms each), assume caching is working efficiently
153 |     // 2. Otherwise, cached call should be faster or at least not significantly slower
154 |     if (duration1Ms == 0 && duration2Ms == 0) {
155 |       // Both calls completed in under 1ms - cache is working very efficiently
156 |       System.out.println(
157 |           "Cache performance: Both calls completed in under 1ms - excellent cache performance");
158 |       assertTrue(
159 |           duration2Nanos <= duration1Nanos * 2,
160 |           "Even with sub-millisecond timing, cached call should not be significantly slower");
161 |     } else {
162 |       // Standard timing comparison when we can measure meaningful differences
163 |       assertTrue(
164 |           duration2Ms <= duration1Ms,
165 |           "Cached call should be faster than or equal to uncached call");
166 | 
167 |       if (duration2Ms < duration1Ms) {
168 |         System.out.println(
169 |             "Cache performance: Cached call was " + (duration1Ms - duration2Ms) + "ms faster");
170 |       } else {
171 |         System.out.println(
172 |             "Cache performance: Both calls took similar time, indicating efficient caching");
173 |       }
174 |     }
175 |   }
176 | }
177 | 
```
Page 1/2FirstPrevNextLast