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
51 changes: 51 additions & 0 deletions contrib/samples/mcpfilesystem/McpFilesystemAgent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.example.mcpfilesystem;

import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.mcp.McpToolset;
import com.google.adk.tools.mcp.StdioServerParameters;
import com.google.common.collect.ImmutableList;

/** Defines an agent that wires the MCP stdio filesystem server via {@link McpToolset}. */
public final class McpFilesystemAgent {
/** Root agent instance exposed to runners and registries. */
public static final LlmAgent ROOT_AGENT =
LlmAgent.builder()
.name("filesystem_agent")
.description("Assistant that performs file operations through the MCP filesystem server.")
.model("gemini-2.0-flash")
.instruction(
"""
You are a file system assistant. Use the provided tools to read, write, search, and manage
files and directories. Ask clarifying questions when unsure about file operations.
When the user requests that you append to a file, read the current contents, add the
appended text with a newline if needed, then overwrite the file with the combined
content.
""")
.tools(ImmutableList.of(createMcpToolset()))
.build();

private McpFilesystemAgent() {}

private static McpToolset createMcpToolset() {
StdioServerParameters stdioParams =
StdioServerParameters.builder()
.command("npx")
.args(
ImmutableList.of("-y", "@modelcontextprotocol/server-filesystem", "/tmp/mcp-demo"))
.build();
return new McpToolset(stdioParams.toServerParameters());
}
}
98 changes: 98 additions & 0 deletions contrib/samples/mcpfilesystem/McpFilesystemRun.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.example.mcpfilesystem;

import com.google.adk.agents.RunConfig;
import com.google.adk.artifacts.InMemoryArtifactService;
import com.google.adk.events.Event;
import com.google.adk.memory.InMemoryMemoryService;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/** Console runner that exercises {@link McpFilesystemAgent#ROOT_AGENT}. */
public final class McpFilesystemRun {
private final String userId;
private final String sessionId;
private final Runner runner;

private McpFilesystemRun() {
String appName = "mcp-filesystem-app";
this.userId = "mcp-filesystem-user";
this.sessionId = UUID.randomUUID().toString();

InMemorySessionService sessionService = new InMemorySessionService();
this.runner =
new Runner(
McpFilesystemAgent.ROOT_AGENT,
appName,
new InMemoryArtifactService(),
sessionService,
new InMemoryMemoryService());

ConcurrentMap<String, Object> initialState = new ConcurrentHashMap<>();
var unused =
sessionService.createSession(appName, userId, initialState, sessionId).blockingGet();
}

private void run(String prompt) {
System.out.println("You> " + prompt);
Content userMessage =
Content.builder()
.role("user")
.parts(ImmutableList.of(Part.builder().text(prompt).build()))
.build();
RunConfig runConfig = RunConfig.builder().build();
Flowable<Event> eventStream =
this.runner.runAsync(this.userId, this.sessionId, userMessage, runConfig);
List<Event> agentEvents = Lists.newArrayList(eventStream.blockingIterable());

StringBuilder sb = new StringBuilder();
sb.append("Agent> ");
for (Event event : agentEvents) {
sb.append(event.stringifyContent().stripTrailing());
}
System.out.println(sb);
}

/**
* Entry point for the sample runner.
*
* @param args Optional command-line arguments. Pass {@code --run-extended} for additional
* prompts.
*/
public static void main(String[] args) {
McpFilesystemRun runner = new McpFilesystemRun();
try {
runner.run("List the files available in /tmp/mcp-demo");
if (args.length > 0 && Objects.equals(args[0], "--run-extended")) {
runner.run(
"Create or overwrite /tmp/mcp-demo/notes.txt with the text 'MCP demo note generated by"
+ " the sample.'");
runner.run("Read /tmp/mcp-demo/notes.txt to confirm the contents.");
runner.run("Search /tmp/mcp-demo for the phrase 'MCP demo note'.");
runner.run(
"Append the line 'Appended by the extended run.' to /tmp/mcp-demo/notes.txt and show"
+ " the updated file.");
}
} finally {
McpFilesystemAgent.ROOT_AGENT
.toolsets()
.forEach(
toolset -> {
try {
toolset.close();
} catch (Exception e) {
System.err.println("Failed to close toolset: " + e.getMessage());
}
});
}
}
}
71 changes: 71 additions & 0 deletions contrib/samples/mcpfilesystem/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# MCP Filesystem Agent Sample

This sample mirrors the `tool_mcp_stdio_file_system_config/root_agent.yaml` configuration by wiring
an MCP filesystem toolset programmatically. The agent launches the filesystem stdio server via
`npx @modelcontextprotocol/server-filesystem` and interacts with it using the Google ADK runtime.

## Project Layout

```
├── McpFilesystemAgent.java // Agent definition and MCP toolset wiring
├── McpFilesystemRun.java // Console runner entry point
├── pom.xml // Maven configuration and exec main class
└── README.md // This file
```

## Prerequisites

- Java 17+
- Maven 3.9+
- Node.js 18+ with `npx` available (for `@modelcontextprotocol/server-filesystem`)

`npx` downloads the MCP filesystem server on first run. Subsequent executions reuse the cached
package, so expect a longer startup time the first time you run the sample.

## Build and Run

Set the Gemini environment variables and launch the interactive session from this directory. One
command compiles and runs the sample:

```bash
export GOOGLE_GENAI_USE_VERTEXAI=FALSE
export GOOGLE_API_KEY=your_api_key
mvn clean compile exec:java
```

The runner sends an initial prompt asking the agent to list files. To explore additional operations,
reuse the same environment and pass the `--run-extended` argument (you can keep `clean compile` if
you want compilation and execution in a single step):

```bash
mvn clean compile exec:java -Dexec.args="--run-extended"
```

If you prefer to launch (and build) the sample while staying in the `google_adk` root, point Maven at
this module’s POM and again use a single command:

```bash
export GOOGLE_GENAI_USE_VERTEXAI=FALSE
export GOOGLE_API_KEY=your_api_key
mvn -f contrib/samples/mcpfilesystem/pom.xml clean compile exec:java
```

To run the extended sequence from the repo root, reuse the same environment variables and pass
`--run-extended`:

```bash
export GOOGLE_GENAI_USE_VERTEXAI=FALSE
export GOOGLE_API_KEY=your_api_key
mvn -f contrib/samples/mcpfilesystem/pom.xml clean compile exec:java -Dexec.args="--run-extended"
```

The extended flow drives the agent through:
- creating or overwriting `/tmp/mcp-demo/notes.txt` with default content
- reading the file back for confirmation
- searching the workspace for the seeded phrase
- appending a second line and displaying the updated file contents

## Related Samples

For the configuration-driven variant of this demo, see
`../configagent/tool_mcp_stdio_file_system_config/root_agent.yaml`.
99 changes: 99 additions & 0 deletions contrib/samples/mcpfilesystem/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-parent</artifactId>
<version>0.3.1-SNAPSHOT</version>
<relativePath>../../..</relativePath>
</parent>

<groupId>com.google.adk.samples</groupId>
<artifactId>google-adk-sample-mcpfilesystem</artifactId>
<name>Google ADK - Sample - MCP Filesystem</name>
<description>
Programmatic MCP filesystem sample mirroring the YAML-based configuration under
contrib/samples/configagent.
</description>
<packaging>jar</packaging>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<auto-value.version>1.11.0</auto-value.version>
<exec.mainClass>com.example.mcpfilesystem.McpFilesystemRun</exec.mainClass>
<google-adk.version>${project.parent.version}</google-adk.version>
</properties>

<dependencies>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>${google-adk.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>.</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<mainClass>${exec.mainClass}</mainClass>
<classpathScope>runtime</classpathScope>
</configuration>
</plugin>
</plugins>
</build>
</project>
26 changes: 16 additions & 10 deletions core/src/main/java/com/google/adk/tools/mcp/AbstractMcpTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,19 +77,25 @@ public T getMcpSession() {

@Override
public Optional<FunctionDeclaration> declaration() {
JsonSchema schema = this.mcpTool.inputSchema();
JsonSchema inputSchema = this.mcpTool.inputSchema();
Map<String, Object> outputSchema = this.mcpTool.outputSchema();
try {
return Optional.ofNullable(schema)
return Optional.ofNullable(inputSchema)
.map(
value ->
FunctionDeclaration.builder()
.name(this.name())
.description(this.description())
.parametersJsonSchema(value)
.build());
} catch (Exception e) {
value -> {
FunctionDeclaration.Builder builder =
FunctionDeclaration.builder()
.name(this.name())
.description(this.description())
.parametersJsonSchema(value);
Optional.ofNullable(outputSchema).ifPresent(builder::responseJsonSchema);
return builder.build();
});
} catch (RuntimeException e) {
throw new McpToolDeclarationException(
String.format("MCP tool:%s failed to get declaration, schema:%s.", this.name(), schema),
String.format(
"MCP tool:%s failed to get declaration, inputSchema:%s. outputSchema:%s.",
this.name(), inputSchema, outputSchema),
e);
}
}
Expand Down