Skip to content
Merged
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
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ dependencies {
// JTokkit dependencies
implementation("com.knuddels:jtokkit:1.1.0")
implementation("org.commonmark:commonmark:0.27.1")
implementation("org.jsoup:jsoup:1.18.3")
implementation("io.netty:netty-all:4.2.10.Final")

// Logging
Expand Down
28 changes: 20 additions & 8 deletions docusaurus/docs/features/agent-mode.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
sidebar_position: 2
title: Agent Mode
description: Enable agent mode to let the LLM autonomously explore and modify your codebase using built-in tools. Run tests automatically after code changes. Use parallel sub-agents for concurrent read-only exploration of multiple aspects.
keywords: [devoxxgenie, agent mode, sub-agents, parallel explore, codebase exploration, multi-agent, tools, run tests, test execution]
description: Enable agent mode to let the LLM autonomously explore and modify your codebase using built-in tools. Fetch web pages for documentation. Run tests automatically after code changes. Use parallel sub-agents for concurrent read-only exploration. Enable or disable individual tools.
keywords: [devoxxgenie, agent mode, sub-agents, parallel explore, codebase exploration, multi-agent, tools, run tests, test execution, fetch page, tool control]
---

# Agent Mode
Expand All @@ -22,6 +22,7 @@ When Agent Mode is enabled, the LLM gains access to a set of **built-in tools**
| `read_file` | Read the contents of a file in the project |
| `list_files` | List files and directories (with optional recursion) |
| `search_files` | Search for regex patterns across project files |
| `fetch_page` | Fetch a web page by URL and return its readable text content (HTML/CSS/JS stripped) |

### Write Tools

Expand All @@ -32,7 +33,16 @@ When Agent Mode is enabled, the LLM gains access to a set of **built-in tools**
| `run_command` | Execute terminal commands in the project directory (30s timeout) |
| `run_tests` | Auto-detect build system and run tests with structured results (configurable timeout) |

As you chat with the LLM, it decides when to use these tools. For exploration tasks, the agent might search, list, and read files to understand your codebase. For development tasks, it can create new files, edit existing code, run commands, or run tests to verify changes.
As you chat with the LLM, it decides when to use these tools. For exploration tasks, the agent might search, list, and read files to understand your codebase. The `fetch_page` tool lets the agent read external documentation, API references, or web pages to gather additional context. For development tasks, it can create new files, edit existing code, run commands, or run tests to verify changes.

### Per-Tool Enable/Disable

Each built-in tool can be individually enabled or disabled in the **Built-in Tools** section of the Agent settings. Uncheck a tool to prevent the agent from using it. This is useful when you want to:

- **Restrict capabilities** — disable `run_command` to prevent terminal access, or `fetch_page` to block web access
- **Reduce context window usage** — each enabled tool adds its specification to the LLM prompt, consuming tokens. Disabling tools you don't need frees up context for your actual conversation, which is especially important with smaller context window models

The `run_tests`, `parallel_explore`, and backlog tools have their own dedicated toggles in separate settings sections.

:::tip Safety
Write operations require user approval by default. You'll see a diff preview before any changes are applied to your project.
Expand Down Expand Up @@ -115,7 +125,7 @@ When the LLM decides it needs to investigate multiple areas simultaneously, it c

Each sub-agent:

- Has **read-only access** to your project (read_file, list_files, search_files only)
- Has **read-only access** to your project (read_file, list_files, search_files, fetch_page only)
- Runs with its own **isolated chat memory** and tool call budget
- Can use a **different LLM provider/model** than the main agent (e.g., a cheaper/faster model)
- Returns a **concise markdown summary** of its findings back to the main agent
Expand All @@ -133,6 +143,7 @@ All agent settings are in **Settings > Tools > DevoxxGenie > Agent**.
| Setting | Default | Description |
|---------|---------|-------------|
| **Enable Agent Mode** | Disabled | Enables the agent with full tool access (read, write, and execute) |
| **Built-in Tools** | All enabled | Per-tool checkboxes to enable/disable individual tools (read_file, write_file, edit_file, list_files, search_files, run_command, fetch_page) |
| **Enable Debug Logs** | Disabled | Adds detailed logging of tool arguments and results |

### Test Execution Settings
Expand Down Expand Up @@ -231,7 +242,8 @@ User Prompt
Main Agent (your configured LLM)
|
|--> read_file, write_file, edit_file
|--> list_files, search_files, run_command, run_tests
|--> list_files, search_files, run_command
|--> fetch_page, run_tests
| |
| v
| Tool Results
Expand All @@ -250,9 +262,9 @@ Main Agent (your configured LLM)
|
|--> parallel_explore(queries: ["query1", "query2", "query3"])
| |
| |--> Sub-Agent #1 (read_file, list_files, search_files)
| |--> Sub-Agent #2 (read_file, list_files, search_files)
| |--> Sub-Agent #3 (read_file, list_files, search_files)
| |--> Sub-Agent #1 (read_file, list_files, search_files, fetch_page)
| |--> Sub-Agent #2 (read_file, list_files, search_files, fetch_page)
| |--> Sub-Agent #3 (read_file, list_files, search_files, fetch_page)
| |
| v
| Combined Results (markdown)
Expand Down
24 changes: 22 additions & 2 deletions docusaurus/docs/features/mcp_expanded.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
sidebar_position: 3
title: MCP Support - Model Context Protocol
description: Learn how DevoxxGenie integrates with MCP servers to give your LLM access to external tools, files, databases, and APIs.
keywords: [devoxxgenie, mcp, model context protocol, tools, agents, intellij plugin, marketplace]
description: Learn how DevoxxGenie integrates with MCP servers to give your LLM access to external tools, files, databases, and APIs. Enable or disable individual tools per server.
keywords: [devoxxgenie, mcp, model context protocol, tools, agents, intellij plugin, marketplace, tool control]
---

# MCP Support
Expand Down Expand Up @@ -186,6 +186,26 @@ Each MCP server exposes a set of tools. To see what tools a server provides:
2. A dialog shows a table of tool names and their descriptions
3. The tools column in the main table also shows a summary count

## Per-Tool Enable/Disable

You can selectively enable or disable individual tools exposed by each MCP server. This gives you fine-grained control over which capabilities the LLM can use during conversations.

![MCP Tools Activation](/img/MCPToolsActivation.jpg)

### How to Use

1. In the MCP settings table, click the **View** button for a server
2. Each tool is listed with a checkbox next to its name
3. Uncheck a tool to disable it — the LLM will no longer be able to call it
4. Check a tool to re-enable it

### Use Cases

- **Security**: Disable write or delete tools on a filesystem server to make it read-only
- **Focus**: Disable tools that aren't relevant to your current task to reduce noise
- **Testing**: Temporarily disable a tool to see how the LLM behaves without it
- **Cost control**: Disable expensive API-calling tools when not needed

## Troubleshooting

### Connection Problems
Expand Down
Binary file added docusaurus/static/img/MCPToolsActivation.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docusaurus/static/img/agent-mode-top.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions src/main/java/com/devoxx/genie/model/mcp/MCPServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;

/**
* Represents an MCP server configuration.
Expand Down Expand Up @@ -55,6 +52,9 @@ public enum TransportType {

@Builder.Default
private Map<String, String> toolDescriptions = new HashMap<>();


@Builder.Default
private Set<String> disabledTools = new HashSet<>();

private String toolsDescription;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@

/**
* Wraps tools with conditional approval logic.
* Read-only tools (read_file, list_files, search_files) can be auto-approved.
* Read-only tools (read_file, list_files, search_files, fetch_page) can be auto-approved.
* Write tools (write_file, run_command) always require approval.
*/
@Slf4j
public class AgentApprovalProvider implements ToolProvider {

private static final Set<String> READ_ONLY_TOOLS = Set.of(
"read_file", "list_files", "search_files",
"read_file", "list_files", "search_files", "fetch_page",
"backlog_task_list", "backlog_task_search", "backlog_task_view",
"backlog_document_list", "backlog_document_view", "backlog_document_search",
"backlog_milestone_list"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.*;

/**
* Provides built-in IDE tools for agentic interactions:
* read_file, write_file, edit_file, list_files, search_files, run_command, run_tests, parallel_explore.
* read_file, write_file, edit_file, list_files, search_files, run_command, fetch_page, run_tests, parallel_explore.
*/
public class BuiltInToolProvider implements ToolProvider {

Expand Down Expand Up @@ -116,6 +115,22 @@ public BuiltInToolProvider(@NotNull Project project) {
new RunCommandToolExecutor(project)
);

// fetch_page
tools.put(
ToolSpecification.builder()
.name("fetch_page")
.description("Fetch a web page by URL and return its readable text content. " +
"HTML tags, CSS, and JavaScript are stripped. " +
"Useful for reading documentation, API references, and web pages. " +
"Large pages are truncated to 100K characters.")
.parameters(JsonObjectSchema.builder()
.addStringProperty("url", "The URL to fetch (must start with http:// or https://)")
.required("url")
.build())
.build(),
new FetchPageToolExecutor()
);

// run_tests — only when test execution is enabled
if (Boolean.TRUE.equals(DevoxxGenieStateService.getInstance().getTestExecutionEnabled())) {
tools.put(
Expand Down Expand Up @@ -210,9 +225,14 @@ public ParallelExploreToolExecutor getParallelExploreExecutor() {

@Override
public ToolProviderResult provideTools(ToolProviderRequest request) {
List<String> disabledTools = DevoxxGenieStateService.getInstance().getDisabledAgentTools();
Set<String> disabledSet = disabledTools != null ? new HashSet<>(disabledTools) : Collections.emptySet();

ToolProviderResult.Builder builder = ToolProviderResult.builder();
for (Map.Entry<ToolSpecification, ToolExecutor> entry : tools.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
if (!disabledSet.contains(entry.getKey().name())) {
builder.add(entry.getKey(), entry.getValue());
}
}
return builder.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.devoxx.genie.service.agent.tool;

import dev.langchain4j.agent.tool.ToolExecutionRequest;
import dev.langchain4j.service.tool.ToolExecutor;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

@Slf4j
public class FetchPageToolExecutor implements ToolExecutor {

private static final int MAX_OUTPUT_CHARS = 100_000;
private static final Duration TIMEOUT = Duration.ofSeconds(15);

private final HttpClient httpClient;

public FetchPageToolExecutor() {
this.httpClient = HttpClient.newBuilder()
.connectTimeout(TIMEOUT)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
}

/** Constructor for testing with a custom HttpClient. */
FetchPageToolExecutor(HttpClient httpClient) {
this.httpClient = httpClient;
}

@Override
public String execute(ToolExecutionRequest request, Object memoryId) {
try {
String url = ToolArgumentParser.getString(request.arguments(), "url");
if (url == null || url.isBlank()) {
return "Error: 'url' parameter is required.";
}

if (!url.startsWith("http://") && !url.startsWith("https://")) {
return "Error: URL must start with http:// or https://";
}

HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(TIMEOUT)
.header("User-Agent", "Mozilla/5.0 (compatible; DevoxxGenie/1.0)")
.header("Accept", "text/html,application/xhtml+xml,*/*")
.GET()
.build();

HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() >= 400) {
return "Error: HTTP " + response.statusCode() + " fetching " + url;
}

String body = response.body();
if (body == null || body.isBlank()) {
return "Error: Empty response from " + url;
}

Document doc = Jsoup.parse(body, url);
doc.select("script, style, noscript").remove();
String text = doc.text();

if (text.length() > MAX_OUTPUT_CHARS) {
text = text.substring(0, MAX_OUTPUT_CHARS) + "\n\n[Content truncated at " + MAX_OUTPUT_CHARS + " characters]";
}

return text;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Error: Request interrupted";
} catch (Exception e) {
log.error("Error fetching page", e);
return "Error: Failed to fetch page - " + e.getMessage();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.devoxx.genie.service.mcp;

import com.devoxx.genie.model.mcp.MCPServer;
import com.devoxx.genie.ui.settings.DevoxxGenieStateService;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.service.tool.ToolExecutor;
import dev.langchain4j.service.tool.ToolProvider;
import dev.langchain4j.service.tool.ToolProviderRequest;
import dev.langchain4j.service.tool.ToolProviderResult;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Wraps an MCP ToolProvider and filters out tools that have been disabled
* by the user at the individual tool level in MCP server settings.
*/
@Slf4j
public class FilteredMcpToolProvider implements ToolProvider {

private final ToolProvider delegate;

public FilteredMcpToolProvider(@NotNull ToolProvider delegate) {
this.delegate = delegate;
}

@Override
public ToolProviderResult provideTools(@NotNull ToolProviderRequest request) {
ToolProviderResult delegateResult = delegate.provideTools(request);

// Collect all disabled tool names across all enabled MCP servers
Set<String> allDisabledTools = collectDisabledTools();

if (allDisabledTools.isEmpty()) {
return delegateResult;
}

ToolProviderResult.Builder builder = ToolProviderResult.builder();

for (Map.Entry<ToolSpecification, ToolExecutor> entry : delegateResult.tools().entrySet()) {
ToolSpecification spec = entry.getKey();
if (!allDisabledTools.contains(spec.name())) {
builder.add(spec, entry.getValue());
} else {
MCPService.logDebug("Filtered out disabled MCP tool: " + spec.name());
}
}

return builder.build();
}

/**
* Collects all disabled tool names from all enabled MCP servers.
*/
@NotNull
private static Set<String> collectDisabledTools() {
Set<String> disabledTools = new HashSet<>();
Map<String, MCPServer> mcpServers = DevoxxGenieStateService.getInstance()
.getMcpSettings()
.getMcpServers();

for (MCPServer server : mcpServers.values()) {
if (server.isEnabled() && server.getDisabledTools() != null) {
disabledTools.addAll(server.getDisabledTools());
}
}
return disabledTools;
}
}
Loading
Loading