#
tokens: 12369/50000 18/18 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitattributes
├── .github
│   └── workflows
│       └── build-and-release.yaml
├── .gitignore
├── .idea
│   └── copyright
│       ├── broadcom.xml
│       └── profiles_settings.xml
├── .mvn
│   └── wrapper
│       └── maven-wrapper.properties
├── LICENSE
├── mvnw
├── mvnw.cmd
├── pom.xml
├── README.md
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── broadcom
    │   │           └── tanzu
    │   │               └── demos
    │   │                   └── mcp
    │   │                       └── chess
    │   │                           ├── Application.java
    │   │                           ├── ChessEngine.java
    │   │                           ├── ChessTools.java
    │   │                           ├── McpConfig.java
    │   │                           └── stockfishonline
    │   │                               ├── StockfishOnline.java
    │   │                               ├── StockfishOnlineEngine.java
    │   │                               └── StockfishOnlineEngineConfig.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── com
                └── broadcom
                    └── tanzu
                        └── demos
                            └── mcp
                                └── chess
                                    └── ApplicationTests.java
```

# Files

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

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

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

```
 1 | HELP.md
 2 | target/
 3 | !.mvn/wrapper/maven-wrapper.jar
 4 | !**/src/main/**/target/
 5 | !**/src/test/**/target/
 6 | 
 7 | ### STS ###
 8 | .apt_generated
 9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 | 
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 | 
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 | 
32 | ### VS Code ###
33 | .vscode/
34 | 
```

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

```markdown
  1 | # MCP Chess
  2 | 
  3 | A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that provides
  4 | chess functionality for [Claude AI](https://claude.ai/) Assistant.
  5 | 
  6 | https://github.com/user-attachments/assets/324ed381-35f3-45b7-b877-127ef27fd97d
  7 | 
  8 | ## Features
  9 | 
 10 | This server implements tools that extend Claude's capabilities to:
 11 | 
 12 | - Generate chess board images from a Forsyth-Edwards Notation (FEN) string
 13 | - Suggest the next move in a chess game
 14 | - Check if a move is legal
 15 | 
 16 | ## Installation for Claude Desktop
 17 | 
 18 | 1. Download the latest binary from the [Releases](https://github.com/alexandreroman/mcp-chess/releases) page:
 19 |     - For Windows: `mcp-chess-windows.exe`
 20 |     - For macOS: `mcp-chess-darwin`
 21 |     - For Linux: `mcp-chess-linux`
 22 | 
 23 | 2. Make the file executable (macOS/Linux only):
 24 |    ```bash
 25 |    chmod +x mcp-chess-darwin   # for macOS
 26 |    chmod +x mcp-chess-linux    # for Linux
 27 |    ```
 28 | 
 29 | 3. For macOS users - Bypassing Security Warnings:
 30 | 
 31 |    When you first try to run the application, macOS may display a security warning because the application is not signed by an identified developer. To bypass this:
 32 | 
 33 |     - Right-click (or Control-click) on the `mcp-chess-darwin` file
 34 |     - Select "Open" from the context menu
 35 |     - Click "Open" in the dialog box that appears
 36 | 
 37 |    Alternatively, you can use Terminal:
 38 |    ```bash
 39 |    xattr -d com.apple.quarantine /path/to/mcp-chess-darwin
 40 |    ```
 41 | 
 42 |    This only needs to be done once.
 43 | 
 44 | 4. Configure Claude Desktop:
 45 |     - Open Claude Desktop
 46 |     - Select "Settings", and click on the "Developer" tab
 47 |     - Click "Edit Config"
 48 |     - Add the MCP server configuration
 49 |     - Save the file
 50 |     - Restart Claude Desktop
 51 | 
 52 | Here's an example for the MCP server configuration:
 53 | 
 54 | ```json
 55 | {
 56 |   "mcpServers": {
 57 |     "mcp-chess": {
 58 |       "command": "/path/to/mcp-chess-binary"
 59 |     }
 60 |   }
 61 | }
 62 | ```
 63 | 
 64 | ## Using with Claude
 65 | 
 66 | Once properly configured, you can ask Claude to perform various chess-related tasks:
 67 | 
 68 | ```
 69 | Show me the starting position of a chess game.
 70 | ```
 71 | 
 72 | ```
 73 | Let's play a chess game. Check that each move is legal. Suggest the best move to play.
 74 | ```
 75 | 
 76 | ```
 77 | Is Nf3 a legal move from the starting position?
 78 | ```
 79 | 
 80 | ```
 81 | What's a good move for white in this position: "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2"?
 82 | ```
 83 | 
 84 | ## Technical Details
 85 | 
 86 | ### Development
 87 | 
 88 | This project is built with:
 89 | - Spring Boot
 90 | - Spring AI (MCP server implementation)
 91 | - Java 21
 92 | - GraalVM native compilation
 93 | 
 94 | ### Building from Source
 95 | 
 96 | ```bash
 97 | # Clone the repository
 98 | git clone https://github.com/alexandreroman/mcp-chess.git
 99 | cd mcp-chess
100 | 
101 | # Build with Maven
102 | ./mvnw clean package
103 | 
104 | # Build a native executable
105 | ./mvnw -Pnative native:compile
106 | ```
107 | 
108 | ## License
109 | 
110 | This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
111 | 
112 | ## Credits
113 | 
114 | - [ChessGame](https://github.com/wolfraam/chess-game) - Java chess library
115 | - [ChessImage](https://github.com/alexandreroman/chessimage) - Chess board renderer
116 | - [Stockfish.online](https://stockfish.online/) - Chess engine API
117 | - [Spring AI](https://spring.io/projects/spring-ai) - AI application framework
118 | 
```

--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------

```
1 | <component name="CopyrightManager">
2 |   <settings default="broadcom" />
3 | </component>
```

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

```
 1 | spring.application.name=mcp-chess
 2 | 
 3 | spring.main.web-application-type=none
 4 | spring.main.banner-mode=off
 5 | logging.pattern.console=
 6 | 
 7 | spring.ai.mcp.server.name=${spring.application.name}
 8 | [email protected]@
 9 | spring.ai.mcp.server.stdio=true
10 | 
11 | app.stockfish-online.url=https://stockfish.online
12 | 
```

--------------------------------------------------------------------------------
/.idea/copyright/broadcom.xml:
--------------------------------------------------------------------------------

```
1 | <component name="CopyrightManager">
2 |   <copyright>
3 |     <option name="notice" value="Copyright (c) &amp;#36;today.year Broadcom, Inc. or its affiliates&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;     http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
4 |     <option name="myName" value="broadcom" />
5 |   </copyright>
6 | </component>
```

--------------------------------------------------------------------------------
/src/test/java/com/broadcom/tanzu/demos/mcp/chess/ApplicationTests.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
 3 |  *
 4 |  * Licensed under the Apache License, Version 2.0 (the "License");
 5 |  * you may not use this file except in compliance with the License.
 6 |  * You may obtain a copy of the License at
 7 |  *
 8 |  *      http://www.apache.org/licenses/LICENSE-2.0
 9 |  *
10 |  * Unless required by applicable law or agreed to in writing, software
11 |  * distributed under the License is distributed on an "AS IS" BASIS,
12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  * See the License for the specific language governing permissions and
14 |  * limitations under the License.
15 |  */
16 | 
17 | package com.broadcom.tanzu.demos.mcp.chess;
18 | 
19 | import org.junit.jupiter.api.Test;
20 | import org.springframework.boot.test.context.SpringBootTest;
21 | 
22 | @SpringBootTest
23 | class ApplicationTests {
24 |     @Test
25 |     void contextLoads() {
26 |     }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/.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/broadcom/tanzu/demos/mcp/chess/ChessEngine.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
 3 |  *
 4 |  * Licensed under the Apache License, Version 2.0 (the "License");
 5 |  * you may not use this file except in compliance with the License.
 6 |  * You may obtain a copy of the License at
 7 |  *
 8 |  *      http://www.apache.org/licenses/LICENSE-2.0
 9 |  *
10 |  * Unless required by applicable law or agreed to in writing, software
11 |  * distributed under the License is distributed on an "AS IS" BASIS,
12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  * See the License for the specific language governing permissions and
14 |  * limitations under the License.
15 |  */
16 | 
17 | package com.broadcom.tanzu.demos.mcp.chess;
18 | 
19 | import io.github.wolfraam.chessgame.ChessGame;
20 | import io.github.wolfraam.chessgame.move.Move;
21 | 
22 | import java.util.Optional;
23 | 
24 | public interface ChessEngine {
25 |     /**
26 |      * Get the next move to play.
27 |      *
28 |      * @param game board game instance
29 |      * @return the move to play eventually
30 |      */
31 |     Optional<Move> getNextMove(ChessGame game);
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/broadcom/tanzu/demos/mcp/chess/Application.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
 3 |  *
 4 |  * Licensed under the Apache License, Version 2.0 (the "License");
 5 |  * you may not use this file except in compliance with the License.
 6 |  * You may obtain a copy of the License at
 7 |  *
 8 |  *      http://www.apache.org/licenses/LICENSE-2.0
 9 |  *
10 |  * Unless required by applicable law or agreed to in writing, software
11 |  * distributed under the License is distributed on an "AS IS" BASIS,
12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  * See the License for the specific language governing permissions and
14 |  * limitations under the License.
15 |  */
16 | 
17 | package com.broadcom.tanzu.demos.mcp.chess;
18 | 
19 | import org.springframework.boot.SpringApplication;
20 | import org.springframework.boot.autoconfigure.SpringBootApplication;
21 | 
22 | @SpringBootApplication
23 | public class Application {
24 |     public static void main(String[] args) {
25 |         // Run this app in headless move (no AWT window required).
26 |         System.setProperty("java.awt.headless", "true");
27 |         SpringApplication.run(Application.class, args);
28 |     }
29 | }
30 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/broadcom/tanzu/demos/mcp/chess/stockfishonline/StockfishOnline.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
 3 |  *
 4 |  * Licensed under the Apache License, Version 2.0 (the "License");
 5 |  * you may not use this file except in compliance with the License.
 6 |  * You may obtain a copy of the License at
 7 |  *
 8 |  *      http://www.apache.org/licenses/LICENSE-2.0
 9 |  *
10 |  * Unless required by applicable law or agreed to in writing, software
11 |  * distributed under the License is distributed on an "AS IS" BASIS,
12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  * See the License for the specific language governing permissions and
14 |  * limitations under the License.
15 |  */
16 | 
17 | package com.broadcom.tanzu.demos.mcp.chess.stockfishonline;
18 | 
19 | import org.springframework.web.bind.annotation.RequestParam;
20 | import org.springframework.web.service.annotation.GetExchange;
21 | 
22 | interface StockfishOnline {
23 |     @GetExchange("/api/s/v2.php")
24 |     StockfishOnlineResponse getNextMove(@RequestParam String fen, @RequestParam(defaultValue = "10") int depth);
25 | }
26 | 
27 | record StockfishOnlineResponse(
28 |         boolean success,
29 |         Integer mate,
30 |         String bestmove
31 | ) {
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/broadcom/tanzu/demos/mcp/chess/stockfishonline/StockfishOnlineEngineConfig.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
 3 |  *
 4 |  * Licensed under the Apache License, Version 2.0 (the "License");
 5 |  * you may not use this file except in compliance with the License.
 6 |  * You may obtain a copy of the License at
 7 |  *
 8 |  *      http://www.apache.org/licenses/LICENSE-2.0
 9 |  *
10 |  * Unless required by applicable law or agreed to in writing, software
11 |  * distributed under the License is distributed on an "AS IS" BASIS,
12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  * See the License for the specific language governing permissions and
14 |  * limitations under the License.
15 |  */
16 | 
17 | package com.broadcom.tanzu.demos.mcp.chess.stockfishonline;
18 | 
19 | import org.springframework.beans.factory.annotation.Value;
20 | import org.springframework.context.annotation.Bean;
21 | import org.springframework.context.annotation.Configuration;
22 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
23 | import org.springframework.web.client.RestClient;
24 | import org.springframework.web.client.support.RestClientAdapter;
25 | import org.springframework.web.service.invoker.HttpServiceProxyFactory;
26 | 
27 | @Configuration(proxyBeanMethods = false)
28 | class StockfishOnlineEngineConfig {
29 |     @Bean
30 |     StockfishOnline stockfishOnline(RestClient.Builder clientBuilder,
31 |                                     @Value("${app.stockfish-online.url}") String stockfishOnlineUrl) {
32 |         final var client = clientBuilder
33 |                 .clone()
34 |                 .requestFactory(new HttpComponentsClientHttpRequestFactory())
35 |                 .baseUrl(stockfishOnlineUrl)
36 |                 .build();
37 |         return HttpServiceProxyFactory.builderFor(RestClientAdapter.create(client))
38 |                 .build().createClient(StockfishOnline.class);
39 |     }
40 | 
41 |     @Bean
42 |     StockfishOnlineEngine chessEngine(StockfishOnline stockfishOnline) {
43 |         return new StockfishOnlineEngine(stockfishOnline);
44 |     }
45 | }
46 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/broadcom/tanzu/demos/mcp/chess/stockfishonline/StockfishOnlineEngine.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
 3 |  *
 4 |  * Licensed under the Apache License, Version 2.0 (the "License");
 5 |  * you may not use this file except in compliance with the License.
 6 |  * You may obtain a copy of the License at
 7 |  *
 8 |  *      http://www.apache.org/licenses/LICENSE-2.0
 9 |  *
10 |  * Unless required by applicable law or agreed to in writing, software
11 |  * distributed under the License is distributed on an "AS IS" BASIS,
12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  * See the License for the specific language governing permissions and
14 |  * limitations under the License.
15 |  */
16 | 
17 | package com.broadcom.tanzu.demos.mcp.chess.stockfishonline;
18 | 
19 | import com.broadcom.tanzu.demos.mcp.chess.ChessEngine;
20 | import io.github.wolfraam.chessgame.ChessGame;
21 | import io.github.wolfraam.chessgame.move.Move;
22 | import io.github.wolfraam.chessgame.notation.NotationType;
23 | import org.slf4j.Logger;
24 | import org.slf4j.LoggerFactory;
25 | 
26 | import java.util.Optional;
27 | import java.util.regex.Pattern;
28 | 
29 | class StockfishOnlineEngine implements ChessEngine {
30 |     private final Logger logger = LoggerFactory.getLogger(StockfishOnlineEngine.class);
31 |     private final Pattern bestmovePattern = Pattern.compile("bestmove\\s(\\S+)");
32 |     private final StockfishOnline api;
33 | 
34 |     StockfishOnlineEngine(StockfishOnline api) {
35 |         this.api = api;
36 |     }
37 | 
38 |     public Optional<Move> getNextMove(ChessGame game) {
39 |         final var fen = game.getFen();
40 |         logger.atDebug().log("Using Stockfish.online to guess next move using FEN: {}", fen);
41 |         final var resp = api.getNextMove(fen, 3);
42 |         if (resp == null || !resp.success()) {
43 |             logger.atWarn().log("No next move found using Stockfish.online using FEN: {}", fen);
44 |             return Optional.empty();
45 |         }
46 | 
47 |         final var matcher = bestmovePattern.matcher(resp.bestmove());
48 |         if (!matcher.find()) {
49 |             logger.atWarn().log("Unable to read best move from Stockfish.online using FEN '{}': {}", fen, resp.bestmove());
50 |             return Optional.empty();
51 |         }
52 | 
53 |         final var bestMove = matcher.group(1);
54 |         final var nextMove = game.getMove(NotationType.UCI, bestMove);
55 |         logger.atInfo().log("Found next move with Stockfish.online using FEN '{}': {}", fen, nextMove);
56 |         return Optional.of(nextMove);
57 |     }
58 | 
59 |     @Override
60 |     public String toString() {
61 |         return "Stockfish.online";
62 |     }
63 | }
64 | 
```

--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------

```
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3 |          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 4 |     <modelVersion>4.0.0</modelVersion>
 5 |     <parent>
 6 |         <groupId>org.springframework.boot</groupId>
 7 |         <artifactId>spring-boot-starter-parent</artifactId>
 8 |         <version>3.4.3</version>
 9 |         <relativePath/> <!-- lookup parent from repository -->
10 |     </parent>
11 |     <groupId>com.broadcom.tanzu.demos</groupId>
12 |     <artifactId>mcp-chess</artifactId>
13 |     <version>1.1.0-SNAPSHOT</version>
14 |     <name>MCP Server :: Chess</name>
15 | 
16 |     <properties>
17 |         <java.version>21</java.version>
18 |         <spring-ai.version>1.0.0-M6</spring-ai.version>
19 |     </properties>
20 | 
21 |     <dependencies>
22 |         <dependency>
23 |             <groupId>org.springframework.ai</groupId>
24 |             <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
25 |         </dependency>
26 | 
27 |         <dependency>
28 |             <groupId>io.github.wolfraam</groupId>
29 |             <artifactId>chessgame</artifactId>
30 |             <version>2.3</version>
31 |         </dependency>
32 |         <dependency>
33 |             <groupId>com.github.alexandreroman</groupId>
34 |             <artifactId>chessimage</artifactId>
35 |             <version>1.0.0</version>
36 |         </dependency>
37 | 
38 |         <dependency>
39 |             <groupId>org.apache.httpcomponents.client5</groupId>
40 |             <artifactId>httpclient5</artifactId>
41 |         </dependency>
42 |         <dependency>
43 |             <groupId>org.springframework.boot</groupId>
44 |             <artifactId>spring-boot-starter-web</artifactId>
45 |         </dependency>
46 | 
47 |         <!-- Workaround for a strange issue when using GitHub Actions: -->
48 |         <!-- this dependency brought by spring-ai-mcp is somehow missing -->
49 |         <dependency>
50 |             <groupId>com.fasterxml</groupId>
51 |             <artifactId>classmate</artifactId>
52 |         </dependency>
53 | 
54 |         <dependency>
55 |             <groupId>org.springframework.boot</groupId>
56 |             <artifactId>spring-boot-starter-test</artifactId>
57 |             <scope>test</scope>
58 |         </dependency>
59 |         <dependency>
60 |             <groupId>org.mockito</groupId>
61 |             <artifactId>mockito-junit-jupiter</artifactId>
62 |             <scope>test</scope>
63 |         </dependency>
64 |     </dependencies>
65 | 
66 |     <dependencyManagement>
67 |         <dependencies>
68 |             <dependency>
69 |                 <groupId>org.springframework.ai</groupId>
70 |                 <artifactId>spring-ai-bom</artifactId>
71 |                 <version>${spring-ai.version}</version>
72 |                 <type>pom</type>
73 |                 <scope>import</scope>
74 |             </dependency>
75 |         </dependencies>
76 |     </dependencyManagement>
77 | 
78 |     <repositories>
79 |         <repository>
80 |             <id>jitpack.io</id>
81 |             <url>https://jitpack.io</url>
82 |         </repository>
83 |     </repositories>
84 | 
85 |     <build>
86 |         <plugins>
87 |             <plugin>
88 |                 <groupId>org.graalvm.buildtools</groupId>
89 |                 <artifactId>native-maven-plugin</artifactId>
90 |             </plugin>
91 |             <plugin>
92 |                 <groupId>org.springframework.boot</groupId>
93 |                 <artifactId>spring-boot-maven-plugin</artifactId>
94 |             </plugin>
95 |         </plugins>
96 |     </build>
97 | </project>
98 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/broadcom/tanzu/demos/mcp/chess/ChessTools.java:
--------------------------------------------------------------------------------

```java
 1 | /*
 2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
 3 |  *
 4 |  * Licensed under the Apache License, Version 2.0 (the "License");
 5 |  * you may not use this file except in compliance with the License.
 6 |  * You may obtain a copy of the License at
 7 |  *
 8 |  *      http://www.apache.org/licenses/LICENSE-2.0
 9 |  *
10 |  * Unless required by applicable law or agreed to in writing, software
11 |  * distributed under the License is distributed on an "AS IS" BASIS,
12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 |  * See the License for the specific language governing permissions and
14 |  * limitations under the License.
15 |  */
16 | 
17 | package com.broadcom.tanzu.demos.mcp.chess;
18 | 
19 | import com.fasterxml.jackson.annotation.JsonClassDescription;
20 | import com.fasterxml.jackson.annotation.JsonInclude;
21 | import com.fasterxml.jackson.annotation.JsonPropertyDescription;
22 | import io.github.wolfraam.chessgame.ChessGame;
23 | import io.github.wolfraam.chessgame.notation.NotationType;
24 | import org.slf4j.Logger;
25 | import org.slf4j.LoggerFactory;
26 | import org.springframework.ai.tool.annotation.Tool;
27 | import org.springframework.ai.tool.annotation.ToolParam;
28 | import org.springframework.stereotype.Component;
29 | 
30 | @Component
31 | class ChessTools {
32 |     private final Logger logger = LoggerFactory.getLogger(ChessTools.class);
33 |     private final ChessEngine chessEngine;
34 | 
35 |     ChessTools(ChessEngine chessEngine) {
36 |         this.chessEngine = chessEngine;
37 |     }
38 | 
39 |     @Tool(name = "chess_guess_next_move", description = """
40 |             Guess the next move to play in a chess game.
41 |             """)
42 |     NextMove guessNextMove(@ToolParam(description = "Board state in Forsyth-Edwards Notation") String fen) {
43 |         logger.atDebug().log("Guessing next move from FEN: {}", fen);
44 |         final var game = new ChessGame(fen);
45 |         final var resp = chessEngine.getNextMove(game)
46 |                 .map(move -> game.getNotation(NotationType.UCI, move))
47 |                 .orElse(null);
48 |         logger.atInfo().log("Guessed next move from FEN: {}=>{}", fen, resp);
49 |         return new NextMove(fen, resp);
50 |     }
51 | 
52 |     @Tool(name = "chess_is_legal_move", description = """
53 |             Check if a move is legal in a chess game.
54 |             """)
55 |     MoveLegality isLegalMove(@ToolParam(description = "Board state in Forsyth-Edwards Notation") String fen,
56 |                              @ToolParam(description = "Move in UCI format") String move) {
57 |         logger.atDebug().log("Checking if the move {} is legal in FEN: {}", move, fen);
58 |         final var game = new ChessGame(fen);
59 |         final var resp = game.isLegalMove(game.getMove(NotationType.UCI, move));
60 |         logger.atInfo().log("Is move {} legal in FEN {}? {}", move, fen, resp ? "yes" : "no");
61 |         return new MoveLegality(fen, move, resp);
62 |     }
63 | }
64 | 
65 | @JsonClassDescription("A structure holding the legality of a move in a chess game")
66 | record MoveLegality(
67 |         @JsonPropertyDescription("Board state in Forsyth-Edwards Notation (FEN) before the move")
68 |         String fen,
69 |         @JsonPropertyDescription("Move to check in UCI format (for instance: d2d3)")
70 |         String move,
71 |         @JsonPropertyDescription("Move legality: true if the move is legal in the current board state")
72 |         boolean legal
73 | ) {
74 | }
75 | 
76 | @JsonInclude(JsonInclude.Include.NON_EMPTY)
77 | @JsonClassDescription("A structure holding a board state and the best move to play in a chess game")
78 | record NextMove(
79 |         @JsonPropertyDescription("Board state in Forsyth-Edwards Notation (FEN)")
80 |         String fen,
81 |         @JsonPropertyDescription("Next move to play in UCI format (for instance: d2d3), if any")
82 |         String nextMove
83 | ) {
84 | }
85 | 
```

--------------------------------------------------------------------------------
/.github/workflows/build-and-release.yaml:
--------------------------------------------------------------------------------

```yaml
  1 | name: Build and release
  2 | 
  3 | on:
  4 |   push:
  5 |     branches:
  6 |     - main
  7 |     tags:
  8 |     - "v*"
  9 |   pull_request:
 10 |     branches:
 11 |     - main
 12 | 
 13 | permissions:
 14 |   contents: write
 15 | 
 16 | concurrency:
 17 |   group: ${{ github.workflow }}
 18 |   cancel-in-progress: true
 19 | 
 20 | env:
 21 |   JAVA_VERSION: 21
 22 | 
 23 | jobs:
 24 |   init:
 25 |     name: Initializing
 26 |     runs-on: ubuntu-latest
 27 |     outputs:
 28 |       artifact-id: ${{ steps.artifact-id.outputs.artifact-id }}
 29 |     steps:
 30 |     - uses: actions/checkout@v4
 31 |     - name: Get artifact id
 32 |       id: artifact-id
 33 |       run: |
 34 |         ARTIFACT_ID=$(./mvnw -B org.apache.maven.plugins:maven-help-plugin:evaluate -Dexpression=project.artifactId -q -DforceStdout)
 35 |         echo artifact-id=$ARTIFACT_ID >> $GITHUB_OUTPUT
 36 | 
 37 |   build:
 38 |     name: Building on ${{ matrix.os }}
 39 |     needs: init
 40 |     runs-on: ${{ matrix.os }}
 41 |     strategy:
 42 |       matrix:
 43 |         os: [ macos-latest, windows-latest, ubuntu-latest ]
 44 |     steps:
 45 |     - uses: actions/checkout@v4
 46 |     - uses: graalvm/setup-graalvm@v1
 47 |       with:
 48 |         java-version: ${{ env.JAVA_VERSION }}
 49 |         distribution: liberica
 50 |         cache: maven
 51 |         github-token: ${{ secrets.GITHUB_TOKEN }}
 52 |         native-image-job-reports: 'true'
 53 |     - name: Build and test on Windows
 54 |       if: runner.os == 'Windows'
 55 |       run: |
 56 |         .\mvnw -B -Pnative native:compile
 57 |     - name: Build and test
 58 |       if: runner.os != 'Windows'
 59 |       run: |
 60 |         ./mvnw -B -Pnative native:compile
 61 |     - uses: actions/upload-artifact@v4
 62 |       if: startsWith(github.ref, 'refs/tags/v') && runner.os == 'Windows'
 63 |       with:
 64 |         name: artifact-windows
 65 |         path: target/${{ needs.init.outputs.artifact-id }}.exe
 66 |         if-no-files-found: error
 67 |         compression-level: 0
 68 |     - uses: actions/upload-artifact@v4
 69 |       if: startsWith(github.ref, 'refs/tags/v') && runner.os == 'macOS'
 70 |       with:
 71 |         name: artifact-darwin
 72 |         path: target/${{ needs.init.outputs.artifact-id }}
 73 |         if-no-files-found: error
 74 |         compression-level: 0
 75 |     - uses: actions/upload-artifact@v4
 76 |       if: startsWith(github.ref, 'refs/tags/v') && runner.os == 'Linux'
 77 |       with:
 78 |         name: artifact-linux
 79 |         path: target/${{ needs.init.outputs.artifact-id }}
 80 |         if-no-files-found: error
 81 |         compression-level: 0
 82 | 
 83 |   release:
 84 |     name: Creating release
 85 |     if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
 86 |     needs: [ init, build ]
 87 |     runs-on: ubuntu-latest
 88 |     env:
 89 |       GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
 90 |     steps:
 91 |     - name: Download all artifacts
 92 |       uses: actions/download-artifact@v4
 93 |       with:
 94 |         path: artifacts
 95 |     - name: Display structure of downloaded files
 96 |       run: ls -R artifacts
 97 |     - uses: actions/create-release@v1
 98 |       id: create-release
 99 |       with:
100 |         release_name: ${{ needs.init.outputs.artifact-id }}-${{ github.ref_name }}
101 |         tag_name: ${{ github.ref_name }}
102 |         draft: 'true'
103 |     - uses: actions/upload-release-asset@v1
104 |       with:
105 |         upload_url: ${{ steps.create-release.outputs.upload_url }}
106 |         asset_path: ./artifacts/artifact-windows/${{ needs.init.outputs.artifact-id }}.exe
107 |         asset_name: ${{ needs.init.outputs.artifact-id }}-windows.exe
108 |         asset_content_type: application/x-msdownload
109 |     - uses: actions/upload-release-asset@v1
110 |       with:
111 |         upload_url: ${{ steps.create-release.outputs.upload_url }}
112 |         asset_path: ./artifacts/artifact-darwin/${{ needs.init.outputs.artifact-id }}
113 |         asset_name: ${{ needs.init.outputs.artifact-id }}-darwin
114 |         asset_content_type: application/x-mach-binary
115 |     - uses: actions/upload-release-asset@v1
116 |       with:
117 |         upload_url: ${{ steps.create-release.outputs.upload_url }}
118 |         asset_path: ./artifacts/artifact-linux/${{ needs.init.outputs.artifact-id }}
119 |         asset_name: ${{ needs.init.outputs.artifact-id }}-linux
120 |         asset_content_type: application/x-executable
121 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/broadcom/tanzu/demos/mcp/chess/McpConfig.java:
--------------------------------------------------------------------------------

```java
  1 | /*
  2 |  * Copyright (c) 2025 Broadcom, Inc. or its affiliates
  3 |  *
  4 |  * Licensed under the Apache License, Version 2.0 (the "License");
  5 |  * you may not use this file except in compliance with the License.
  6 |  * You may obtain a copy of the License at
  7 |  *
  8 |  *      http://www.apache.org/licenses/LICENSE-2.0
  9 |  *
 10 |  * Unless required by applicable law or agreed to in writing, software
 11 |  * distributed under the License is distributed on an "AS IS" BASIS,
 12 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13 |  * See the License for the specific language governing permissions and
 14 |  * limitations under the License.
 15 |  */
 16 | 
 17 | package com.broadcom.tanzu.demos.mcp.chess;
 18 | 
 19 | import com.github.alexandreroman.chessimage.ChessRenderer;
 20 | import io.modelcontextprotocol.server.McpServerFeatures;
 21 | import io.modelcontextprotocol.spec.McpSchema;
 22 | import org.slf4j.Logger;
 23 | import org.slf4j.LoggerFactory;
 24 | import org.springframework.ai.tool.ToolCallbackProvider;
 25 | import org.springframework.ai.tool.annotation.ToolParam;
 26 | import org.springframework.ai.tool.method.MethodToolCallbackProvider;
 27 | import org.springframework.ai.util.json.schema.JsonSchemaGenerator;
 28 | import org.springframework.aot.hint.MemberCategory;
 29 | import org.springframework.aot.hint.annotation.RegisterReflection;
 30 | import org.springframework.context.annotation.Bean;
 31 | import org.springframework.context.annotation.Configuration;
 32 | import org.springframework.util.ReflectionUtils;
 33 | 
 34 | import java.io.ByteArrayOutputStream;
 35 | import java.util.Base64;
 36 | import java.util.Collections;
 37 | import java.util.List;
 38 | 
 39 | @Configuration(proxyBeanMethods = false)
 40 | @RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_METHODS)
 41 | class McpConfig {
 42 |     private final Logger logger = LoggerFactory.getLogger(McpConfig.class);
 43 | 
 44 |     @Bean
 45 |     ToolCallbackProvider chessToolsProvider(ChessTools chessTools) {
 46 |         // Register Spring AI tools as MCP tools.
 47 |         return MethodToolCallbackProvider.builder().toolObjects(chessTools).build();
 48 |     }
 49 | 
 50 |     @Bean
 51 |     public List<McpServerFeatures.SyncPromptRegistration> prompts() {
 52 |         return List.of(
 53 |                 new McpServerFeatures.SyncPromptRegistration(new McpSchema.Prompt(
 54 |                         "start-a-new-game",
 55 |                         "A prompt to start playing a chess game",
 56 |                         Collections.emptyList()), req -> {
 57 |                     final var m = new McpSchema.PromptMessage(McpSchema.Role.USER, new McpSchema.TextContent("Let's play a chess game. Check that each move is legal. Suggest the best move to play."));
 58 |                     return new McpSchema.GetPromptResult("A message to start playing a chess game", List.of(m));
 59 |                 })
 60 |         );
 61 |     }
 62 | 
 63 |     @Bean
 64 |     List<McpServerFeatures.SyncToolRegistration> syncToolsReg() {
 65 |         // We're about to declare our own tool for generating board images.
 66 |         // We need to generate a JSON schema for the tool signature:
 67 |         // let's reuse utility classes from Spring AI with a dummy tool method.
 68 |         final var generateBoardImageMethod = ReflectionUtils.findMethod(McpConfig.class, "dummyGenerateBoardImage", String.class);
 69 |         assert generateBoardImageMethod != null;
 70 |         final var generateBoardImageInputSchema = JsonSchemaGenerator.generateForMethodInput(generateBoardImageMethod);
 71 | 
 72 |         // This is the MCP tool definition we need, including a JSON schema for the signature and a description.
 73 |         final var tool = new McpSchema.Tool("chess_generate_board_image",
 74 |                 "Generate a board image in a chess game from a Forsyth-Edwards Notation (FEN).",
 75 |                 generateBoardImageInputSchema);
 76 | 
 77 |         return List.of(
 78 |                 new McpServerFeatures.SyncToolRegistration(tool, req -> {
 79 |                     try {
 80 |                         // Get FEN from the tool arguments.
 81 |                         final var fen = (String) req.get("fen");
 82 | 
 83 |                         logger.atInfo().log("Rendering board to PNG image: {}", fen);
 84 |                         final var out = new ByteArrayOutputStream(1024 * 4);
 85 |                         new ChessRenderer().render(fen, out);
 86 | 
 87 |                         logger.atInfo().log("Encoding PNG board image to base64: {}", fen);
 88 |                         final var imgB64 = Base64.getEncoder().encodeToString(out.toByteArray());
 89 | 
 90 |                         // We take care of returning an image using the MCP schema:
 91 |                         // Spring AI currently doesn't support this feature.
 92 |                         final List<McpSchema.Content> contents = List.of(
 93 |                                 new McpSchema.ImageContent(Collections.singletonList(McpSchema.Role.USER),
 94 |                                         1.0d, "image", imgB64, "image/png")
 95 |                         );
 96 |                         return new McpSchema.CallToolResult(contents, false);
 97 |                     } catch (Exception e) {
 98 |                         final var msg = "Failed to generate board image: %s".formatted(e.getMessage());
 99 |                         return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(msg)), true);
100 |                     }
101 |                 })
102 |         );
103 |     }
104 | 
105 |     private void dummyGenerateBoardImage(@ToolParam(description = "Board state in Forsyth-Edwards Notation") String fen) {
106 |         // This method does nothing on purpose.
107 |         // We just want to generate a JSON schema from its arguments.
108 |     }
109 | }
110 | 
```

--------------------------------------------------------------------------------
/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__%"=="" (%__MVNW_CMD__% %*)
 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 = "$HOME/.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 {
145 |   try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
146 |   catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
147 | }
148 | 
149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
150 | 
```