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
18 changes: 6 additions & 12 deletions JavaDuckerMcpServer.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
///usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 21
//DEPS io.modelcontextprotocol.sdk:mcp:1.1.0
//DEPS io.modelcontextprotocol.sdk:mcp:0.8.1
//DEPS org.slf4j:slf4j-nop:2.0.16

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.modelcontextprotocol.sdk.McpSchema;
import io.modelcontextprotocol.sdk.McpServer;
import io.modelcontextprotocol.sdk.server.transport.StdioServerTransportProvider;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.server.McpServer;
import io.modelcontextprotocol.server.transport.StdioServerTransportProvider;

import java.io.ByteArrayOutputStream;
import java.net.Socket;
Expand Down Expand Up @@ -711,15 +711,9 @@ static McpSchema.Tool tool(String name, String description, String schemaJson) {
static McpSchema.CallToolResult call(ThrowingSupplier fn) {
try {
String json = MAPPER.writeValueAsString(fn.get());
return McpSchema.CallToolResult.builder()
.content(List.of(new McpSchema.TextContent(json)))
.isError(false)
.build();
return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent(json)), false);
} catch (Exception e) {
return McpSchema.CallToolResult.builder()
.content(List.of(new McpSchema.TextContent("Error: " + e.getMessage())))
.isError(true)
.build();
return new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("Error: " + e.getMessage())), true);
}
}

Expand Down
80 changes: 66 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,27 +61,50 @@ java -jar target/javaducker-1.0.0.jar \
--javaducker.intake-dir=temp/intake
```

### Use the CLI Client
### Running Multiple Instances

Each project can have its own JavaDucker instance. Use different ports, database files, and intake directories to run multiple instances on the same machine:

```bash
# Check health
./run-client.sh health
# Instance 1: project-alpha on port 8080
java -jar target/javaducker-1.0.0.jar \
--server.port=8080 \
--javaducker.db-path=/data/alpha/javaducker.duckdb \
--javaducker.intake-dir=/data/alpha/intake

# Instance 2: project-beta on port 8081
java -jar target/javaducker-1.0.0.jar \
--server.port=8081 \
--javaducker.db-path=/data/beta/javaducker.duckdb \
--javaducker.intake-dir=/data/beta/intake

# Instance 3: project-gamma on port 8082
java -jar target/javaducker-1.0.0.jar \
--server.port=8082 \
--javaducker.db-path=/data/gamma/javaducker.duckdb \
--javaducker.intake-dir=/data/gamma/intake
```

Each instance is fully isolated — its own DuckDB database, its own intake directory, its own port. The CLI client and MCP server connect to a specific instance via `--port` or `HTTP_PORT`.

# Upload one file
./run-client.sh upload-file --file ./docs/architecture.md
### Use the CLI Client

# Upload a directory
./run-client.sh upload-dir --root ./repo --ext .java,.xml,.md,.yml,.pdf
Point the CLI at the correct port for your instance:

# Search
```bash
# Default instance (port 8080)
./run-client.sh health
./run-client.sh find --phrase "@Transactional" --mode exact
./run-client.sh find --phrase "how onboarding approvals work" --mode hybrid

# Index health check
./run-client.sh index-health
# Targeting a specific instance
./run-client.sh --port 8081 health
./run-client.sh --port 8081 upload-dir --root /projects/beta --ext .java,.xml,.md
./run-client.sh --port 8081 find --phrase "how onboarding approvals work" --mode hybrid

# View stats
./run-client.sh stats
# Other commands
./run-client.sh --port 8082 upload-file --file ./docs/architecture.md
./run-client.sh --port 8082 index-health
./run-client.sh --port 8082 stats
```

## Features
Expand Down Expand Up @@ -152,7 +175,7 @@ JavaDucker ships a JBang-based MCP server (`JavaDuckerMcpServer.java`) that expo
./run-server.sh # or run-server.cmd on Windows
```

2. Register the MCP server in your Claude Code config:
2. Register the MCP server in your Claude Code config (`.claude/settings.json` or `claude_desktop_config.json`):
```json
{
"mcpServers": {
Expand All @@ -171,6 +194,35 @@ JavaDucker ships a JBang-based MCP server (`JavaDuckerMcpServer.java`) that expo
JAVADUCKER_STALENESS_CHECK=true (default: true, set false to disable)
```

### Multiple MCP Instances (per-project)

Register separate MCP servers for different projects, each pointing to its own JavaDucker instance:

```json
{
"mcpServers": {
"javaducker-alpha": {
"command": "/path/to/code-helper/run-mcp.sh",
"env": {
"HTTP_PORT": "8080",
"PROJECT_ROOT": "/projects/alpha"
}
},
"javaducker-beta": {
"command": "/path/to/code-helper/run-mcp.sh",
"env": {
"HTTP_PORT": "8081",
"PROJECT_ROOT": "/projects/beta"
}
}
}
}
```

Each MCP server connects to a different backend via `HTTP_PORT`. Start the corresponding Spring Boot instances first (see [Running Multiple Instances](#running-multiple-instances)).

The `PROJECT_ROOT` env var tells the MCP server where the git repo root is — used by `javaducker_blame`, `javaducker_related`, and `javaducker_stale` (git diff mode).

### MCP Tools

#### Indexing & Search
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Component("javaDuckerFileWatcher")
public class FileWatcher {

private static final Logger log = LoggerFactory.getLogger(FileWatcher.class);
Expand Down
Loading