Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions hawkbit-mcp-server/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/mvnw text eol=lf
*.cmd text eol=crlf
33 changes: 33 additions & 0 deletions hawkbit-mcp-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/

### VS Code ###
.vscode/
57 changes: 57 additions & 0 deletions hawkbit-mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# hawkBit MCP Server

This project provides an implementation of a **Model Context Protocol (MCP) server**
that exposes **Eclipse hawkBit** management capabilities to **intelligent agents**.

---

## Configuration

The MCP server is configured via `application.yaml`.

Example configuration:

```yaml
hawkbit:
server:
mgmt-url: http://localhost:8080 # hawkBit Management API URL

server:
port: 8090 # MCP server port

```

## Build

This module is built independently from the main HawkBit services.

Run the Maven build **from the `hawkbit-mcp-server` module directory only**:

```bash
cd hawkbit-mcp-server
mvn clean package
```

This will generate the executable JAR under:
```bash
target/hawkbit-mcp-server-<version>.jar
```

## MCP Client Integration

To connect this server to an MCP client or Agent, register it in the client configuration.

Example MCP client configuration:
```json
{
"mcpServers": {
"hawkbit": {
"command": "java",
"args": [
"-jar",
"/path/target/hawkbit-mcp-server-0.0.1-SNAPSHOT.jar"
]
}
}
}
```
65 changes: 65 additions & 0 deletions hawkbit-mcp-server/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?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.eclipse.hawkbit</groupId>
<artifactId>hawkbit-parent</artifactId>
<version>${revision}</version>
</parent>

<artifactId>hawkbit-mcp-server</artifactId>
<version>${revision}</version>
<name>hawkbit-mcp-server</name>
<description>MCP Server for Eclipse Hawkbit integration and management via LLM(intelligent agents).</description>

<properties>
<java.version>17</java.version>
<spring-ai.version>1.1.2</spring-ai.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-sdk-mgmt</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2026 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.hawkbit.mcp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HawkbitMcpServerApplication {

public static void main(String[] args) {
SpringApplication.run(HawkbitMcpServerApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.eclipse.hawkbit.mcp.config;

import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.HawkbitServer;
import org.eclipse.hawkbit.sdk.Tenant;
import org.eclipse.hawkbit.sdk.mgmt.AuthenticationSetupHelper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.Contract;
import feign.codec.Decoder;
import feign.codec.Encoder;

@Configuration
public class HawkbitClientConfig {

@Bean
public HawkbitClient hawkbitClient(final HawkbitServer hawkbitServer, final Encoder encoder, final Decoder decoder,
final Contract contract) {
return new HawkbitClient(hawkbitServer, encoder, decoder, contract);
}

@Bean
AuthenticationSetupHelper mgmtApi(final Tenant tenant, final HawkbitClient hawkbitClient) {
return new AuthenticationSetupHelper(tenant, hawkbitClient);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2026 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.hawkbit.mcp.feature.enumeration;

import lombok.Getter;

@Getter
public enum Operator {

EQUALS("==", OperatorType.COMPARISON),
NOT_EQUALS("!=", OperatorType.COMPARISON),

GREATER_OR_EQUAL("=ge=", OperatorType.COMPARISON),
LESS_OR_EQUAL("=le=", OperatorType.COMPARISON),

IN("=in=", OperatorType.COLLECTION),
OUT("=out=", OperatorType.COLLECTION),

IS_NULL("=is=null", OperatorType.NULL_CHECK),
IS_NOT_NULL("=not=null", OperatorType.NULL_CHECK),

AND(";", OperatorType.LOGICAL),
OR(",", OperatorType.LOGICAL);

private final String symbol;
private final OperatorType type;

Operator(String symbol, OperatorType type) {
this.symbol = symbol;
this.type = type;
}

public static String documentation() {
return "OPS:\n== != =ge= =le= =in= =out= =is=null =not=null ; ,";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2026 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.hawkbit.mcp.feature.enumeration;

public enum OperatorType {
COMPARISON,
LOGICAL,
NULL_CHECK,
COLLECTION
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2026 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.hawkbit.mcp.feature.target;

import org.springaicommunity.mcp.annotation.McpResource;
import org.springframework.stereotype.Component;

@Component
public class TargetFilterDoc {

@McpResource(uri = "hawkbit://targets/filter", name = "Target Filter Schema", description = "Canonical schema for target filtering.", mimeType = "text/plain")
public String getTargetFilterSchema() {
return TargetFilterSchema.documentation();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2026 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.hawkbit.mcp.feature.target;

import org.eclipse.hawkbit.mcp.feature.enumeration.Operator;

public final class TargetFilterSchema {

public static final String FIELDS = """
id,name,description,createdat,lastmodifiedat,controllerid,ipaddress,lastcontrollerrequestat,updatestatus
attribute.<key>
metadata.<key>
tag.name
targettype.key,targettype.name
assignedds.name,assignedds.version,installedds.name,installedds.version
""";

public static String documentation() {
return """
FIELDS:
%s

%s
""".formatted(FIELDS, Operator.documentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2026 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.hawkbit.mcp.feature.target;

import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.target.MgmtTarget;
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTargetRestApi;
import org.eclipse.hawkbit.sdk.HawkbitClient;
import org.eclipse.hawkbit.sdk.Tenant;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Service;

@Service
public class TargetMcpService {

private final MgmtTargetRestApi targetApi;

public TargetMcpService(final HawkbitClient hawkbitClient, final Tenant tenant) {
this.targetApi = hawkbitClient.mgmtService(MgmtTargetRestApi.class, tenant);
}

@McpTool(name = "listTargets", description = """
Search for targets (devices) with pagination.
Supports FIQL filters.

Filter schema:
hawkbit://targets/filter
""")
PagedList<MgmtTarget> getTargets(
@McpToolParam(description = "FIQL filter expression. Example: updatestatus==ERROR", required = false) String rsqlParam,

@McpToolParam(description = "Page offset (zero-based).", required = true) int offset,

@McpToolParam(description = "Page size (max 50).", required = true) int size,

@McpToolParam(description = "Sort parameter. Example: name:asc.", required = false) String sortParam) {
return targetApi.getTargets(rsqlParam, offset, size, sortParam).getBody();
}

}
Loading