# Directory Structure
```
├── .gitignore
├── pom.xml
├── README.md
└── src
└── main
├── java
│ └── com
│ └── bootcamptoprod
│ ├── config
│ │ └── McpConfiguration.java
│ ├── service
│ │ └── ConfluenceServiceClient.java
│ └── SpringBootAiConfluenceMcpServerApplication.java
└── resources
└── application.properties
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
.idea
target
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Confluence MCP Server Example
This repository contains the complete source code for our Confluence MCP Server example built with Spring Boot AI. The server integrates with Confluence Cloud and exposes various document management operations as callable tools using the **@Tool** annotation. You can test this server with any MCP client, including the Claude desktop app.
For a detailed explanation on how to build an MCP server, check out our blog:
**[Build MCP Servers with Spring Boot AI: A Beginner’s Guide](https://bootcamptoprod.com/build-mcp-servers-with-spring-boot-ai/)**
---
## Features
- **Confluence Cloud Integration:** Connects to Confluence Cloud to manage spaces, pages, and document history.
- **Callable Tools:** Exposes operations like listing spaces and creating documents using the `@Tool` annotation.
- **Tool Registration:** Uses `ToolCallbackProvider` to register the available service methods with the MCP framework.
- **Testable with MCP Clients:** Easily test the server using any MCP client, including the Claude desktop app.
---
```
--------------------------------------------------------------------------------
/src/main/java/com/bootcamptoprod/SpringBootAiConfluenceMcpServerApplication.java:
--------------------------------------------------------------------------------
```java
package com.bootcamptoprod;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootAiConfluenceMcpServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootAiConfluenceMcpServerApplication.class, args);
}
}
```
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
```
spring.application.name=spring-boot-ai-confluence-mcp-server
spring.main.banner-mode=off
spring.main.web-application-type=none
logging.file.name=./logs/spring-boot-ai-confluence-mcp-server.log
logging.pattern.console=
spring.ai.mcp.server.name=confluence-mcp-server
spring.ai.mcp.server.version=0.0.1
confluence.auth.email=${CONFLUENCE_AUTH_EMAIL}
confluence.auth.apiToken=${CONFLUENCE_AUTH_API_TOKEN}
confluence.baseUrl=${CONFLUENCE_BASE_URL}
```
--------------------------------------------------------------------------------
/src/main/java/com/bootcamptoprod/config/McpConfiguration.java:
--------------------------------------------------------------------------------
```java
package com.bootcamptoprod.config;
import com.bootcamptoprod.service.ConfluenceServiceClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class McpConfiguration {
@Bean
public ToolCallbackProvider confluenceTools(ConfluenceServiceClient confluenceServiceClient) {
return MethodToolCallbackProvider.builder().toolObjects(confluenceServiceClient).build();
}
}
```
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
```
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bootcamptoprod</groupId>
<artifactId>spring-boot-ai-confluence-mcp-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-ai-confluence-mcp-server</name>
<description>A Spring Boot AI-powered Model Context Protocol Server for interacting with Confluence Cloud</description>
<properties>
<java.version>21</java.version>
<spring-ai.version>1.0.0-M7</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
```
--------------------------------------------------------------------------------
/src/main/java/com/bootcamptoprod/service/ConfluenceServiceClient.java:
--------------------------------------------------------------------------------
```java
package com.bootcamptoprod.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class ConfluenceServiceClient {
private static final Logger logger = LoggerFactory.getLogger(ConfluenceServiceClient.class);
private final RestTemplate restTemplate;
public ConfluenceServiceClient(RestTemplateBuilder builder,
@Value("${confluence.auth.email}") String email,
@Value("${confluence.auth.apiToken}") String apiToken,
@Value("${confluence.baseUrl}") String confluenceBaseUrl) {
this.restTemplate = builder
.rootUri(confluenceBaseUrl)
.basicAuthentication(email, apiToken)
.build();
logger.info("ConfluenceServiceClient initialized");
}
/**
* List all spaces in Confluence.
*/
@Tool(description = "List all spaces in Confluence.")
public Map<String, Object> listSpaces() {
logger.info("Fetching list of spaces from Confluence...");
try {
ResponseEntity<Map> response = restTemplate.getForEntity("/space", Map.class);
logger.info("Successfully retrieved spaces.");
return response.getBody();
} catch (Exception e) {
logger.error("Error listing spaces: {}", e.getMessage(), e);
return Map.of("error", "Error listing spaces: " + e.getMessage());
}
}
/**
* Get the number of documents in a specific space.
*/
@Tool(description = "Get the number of documents in a specific Confluence space.")
public Map<String, Object> getDocumentCountInSpace(String spaceKey) {
logger.info("Fetching document count for space: {}", spaceKey);
try {
String cql = "space=" + spaceKey;
ResponseEntity<Map> response = restTemplate.getForEntity("/content/search?cql={cql}", Map.class, cql);
logger.info("Successfully retrieved document count for space: {}", spaceKey);
return Map.of("documentCount", response.getBody().get("size"));
} catch (Exception e) {
logger.error("Error fetching document count for space {}: {}", spaceKey, e.getMessage(), e);
return Map.of("error", "Error fetching document count: " + e.getMessage());
}
}
/**
* List documents in a specific space.
*/
@Tool(description = "List all documents in a specific Confluence space.")
public Map<String, Object> listDocumentsInSpace(String spaceKey) {
logger.info("Fetching documents in space: {}", spaceKey);
try {
ResponseEntity<Map> response = restTemplate.getForEntity("/space/{spaceKey}/content", Map.class, spaceKey);
logger.info("Successfully retrieved documents in space: {}", spaceKey);
return response.getBody();
} catch (Exception e) {
logger.error("Error listing documents in space {}: {}", spaceKey, e.getMessage(), e);
return Map.of("error", "Error listing documents: " + e.getMessage());
}
}
/**
* Create a new document with specified content.
*/
@Tool(description = "Create a new Confluence document with a given title and content in a specific space.")
public Map<String, Object> createDocument(String spaceKey, String title, String content) {
logger.info("Creating a new document '{}' in space '{}'", title, spaceKey);
try {
String payload = String.format(
"{\"type\":\"page\",\"title\":\"%s\",\"space\":{\"key\":\"%s\"},\"body\":{\"storage\":{\"value\":\"%s\",\"representation\":\"storage\"}}}",
title, spaceKey, content
);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<>(payload, headers);
ResponseEntity<Map> response = restTemplate.postForEntity("/content", request, Map.class);
logger.info("Successfully created document '{}' in space '{}'", title, spaceKey);
return response.getBody();
} catch (Exception e) {
logger.error("Error creating document '{}' in space '{}': {}", title, spaceKey, e.getMessage(), e);
return Map.of("error", "Error creating document: " + e.getMessage());
}
}
/**
* Extract page history of a specific document.
*/
@Tool(description = "Extract the version history of a specific Confluence document.")
public Map<String, Object> getPageHistory(String documentId) {
logger.info("Fetching page history for document: {}", documentId);
try {
ResponseEntity<Map> response = restTemplate.getForEntity("/content/{id}/version", Map.class, documentId);
logger.info("Successfully retrieved page history for document: {}", documentId);
return response.getBody();
} catch (Exception e) {
logger.error("Error fetching page history for document {}: {}", documentId, e.getMessage(), e);
return Map.of("error", "Error fetching page history: " + e.getMessage());
}
}
/**
* Extract metadata of a specific document.
*/
@Tool(description = "Extract metadata of a specific Confluence document.")
public Map<String, Object> getDocumentMetadata(String documentId) {
logger.info("Fetching metadata for document: {}", documentId);
try {
ResponseEntity<Map> response = restTemplate.getForEntity("/content/{id}", Map.class, documentId);
logger.info("Successfully retrieved metadata for document: {}", documentId);
return response.getBody();
} catch (Exception e) {
logger.error("Error fetching metadata for document {}: {}", documentId, e.getMessage(), e);
return Map.of("error", "Error fetching document metadata: " + e.getMessage());
}
}
}
```