mirror of
https://github.com/aljazceru/goose.git
synced 2025-12-19 15:14:21 +01:00
feat: tutorial extension (#1169)
Co-authored-by: Kalvin Chau <kalvin@squareup.com>
This commit is contained in:
@@ -422,6 +422,11 @@ pub fn configure_extensions_dialog() -> Result<(), Box<dyn Error>> {
|
|||||||
"Memory",
|
"Memory",
|
||||||
"Tools to save and retrieve durable memories",
|
"Tools to save and retrieve durable memories",
|
||||||
)
|
)
|
||||||
|
.item(
|
||||||
|
"tutorial",
|
||||||
|
"Tutorial",
|
||||||
|
"Access interactive tutorials and guides",
|
||||||
|
)
|
||||||
.item("jetbrains", "JetBrains", "Connect to jetbrains IDEs")
|
.item("jetbrains", "JetBrains", "Connect to jetbrains IDEs")
|
||||||
.interact()?
|
.interact()?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use goose_mcp::{
|
use goose_mcp::{
|
||||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
||||||
|
TutorialRouter,
|
||||||
};
|
};
|
||||||
use mcp_server::router::RouterService;
|
use mcp_server::router::RouterService;
|
||||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||||
@@ -21,6 +22,7 @@ pub async fn run_server(name: &str) -> Result<()> {
|
|||||||
Some(Box::new(RouterService(router)))
|
Some(Box::new(RouterService(router)))
|
||||||
}
|
}
|
||||||
"memory" => Some(Box::new(RouterService(MemoryRouter::new()))),
|
"memory" => Some(Box::new(RouterService(MemoryRouter::new()))),
|
||||||
|
"tutorial" => Some(Box::new(RouterService(TutorialRouter::new()))),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ impl DeveloperRouter {
|
|||||||
If you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that
|
If you need to run a long lived command, background it - e.g. `uvicorn main:app &` so that
|
||||||
this tool does not run indefinitely.
|
this tool does not run indefinitely.
|
||||||
|
|
||||||
|
**Important**: Each shell command runs in its own process. Things like directory changes or
|
||||||
|
sourcing files do not persist between tool calls. So you may need to repeat them each time by
|
||||||
|
stringing together commands, e.g. `cd example && ls` or `source env/bin/activate && pip install numpy`
|
||||||
|
|
||||||
**Important**: Use ripgrep - `rg` - when you need to locate a file or a code reference, other solutions
|
**Important**: Use ripgrep - `rg` - when you need to locate a file or a code reference, other solutions
|
||||||
may show ignored or hidden files. For example *do not* use `find` or `ls -r`
|
may show ignored or hidden files. For example *do not* use `find` or `ls -r`
|
||||||
- List files by name: `rg --files | rg <filename>`
|
- List files by name: `rg --files | rg <filename>`
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ mod developer;
|
|||||||
mod google_drive;
|
mod google_drive;
|
||||||
mod jetbrains;
|
mod jetbrains;
|
||||||
mod memory;
|
mod memory;
|
||||||
|
mod tutorial;
|
||||||
|
|
||||||
pub use computercontroller::ComputerControllerRouter;
|
pub use computercontroller::ComputerControllerRouter;
|
||||||
pub use developer::DeveloperRouter;
|
pub use developer::DeveloperRouter;
|
||||||
pub use google_drive::GoogleDriveRouter;
|
pub use google_drive::GoogleDriveRouter;
|
||||||
pub use jetbrains::JetBrainsRouter;
|
pub use jetbrains::JetBrainsRouter;
|
||||||
pub use memory::MemoryRouter;
|
pub use memory::MemoryRouter;
|
||||||
|
pub use tutorial::TutorialRouter;
|
||||||
|
|||||||
168
crates/goose-mcp/src/tutorial/mod.rs
Normal file
168
crates/goose-mcp/src/tutorial/mod.rs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use include_dir::{include_dir, Dir};
|
||||||
|
use indoc::formatdoc;
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use std::{future::Future, pin::Pin};
|
||||||
|
|
||||||
|
use mcp_core::{
|
||||||
|
handler::{ResourceError, ToolError},
|
||||||
|
protocol::ServerCapabilities,
|
||||||
|
resource::Resource,
|
||||||
|
role::Role,
|
||||||
|
tool::Tool,
|
||||||
|
};
|
||||||
|
use mcp_server::router::CapabilitiesBuilder;
|
||||||
|
use mcp_server::Router;
|
||||||
|
|
||||||
|
use mcp_core::content::Content;
|
||||||
|
|
||||||
|
static TUTORIALS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/tutorial/tutorials");
|
||||||
|
|
||||||
|
pub struct TutorialRouter {
|
||||||
|
tools: Vec<Tool>,
|
||||||
|
instructions: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TutorialRouter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TutorialRouter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let load_tutorial = Tool::new(
|
||||||
|
"load_tutorial".to_string(),
|
||||||
|
"Load a specific tutorial by name. The tutorial will be returned as markdown content that provides step by step instructions.".to_string(),
|
||||||
|
json!({
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the tutorial to load, e.g. 'getting-started' or 'developer-mcp'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get base instructions and available tutorials
|
||||||
|
let available_tutorials = Self::get_available_tutorials();
|
||||||
|
|
||||||
|
let instructions = formatdoc! {r#"
|
||||||
|
Because the tutorial extension is enabled, be aware that the user may be new to using Goose
|
||||||
|
or looking for help with specific features. Proactively offer relevant tutorials when appropriate.
|
||||||
|
|
||||||
|
Available tutorials:
|
||||||
|
{tutorials}
|
||||||
|
|
||||||
|
The specific content of the tutorial are available in by running load_tutorial.
|
||||||
|
To run through a tutorial, make sure to be interactive with the user. Don't run more than
|
||||||
|
a few related tool calls in a row. Make sure to prompt the user for understanding and participation.
|
||||||
|
|
||||||
|
**Important**: Make sure that you provide guidance or info *before* you run commands, as the command will
|
||||||
|
run immediately for the user. For example while running a game tutorial, let the user know what to expect
|
||||||
|
before you run a command to start the game itself.
|
||||||
|
"#,
|
||||||
|
tutorials=available_tutorials,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
tools: vec![load_tutorial],
|
||||||
|
instructions,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_available_tutorials() -> String {
|
||||||
|
let mut tutorials = String::new();
|
||||||
|
for file in TUTORIALS_DIR.files() {
|
||||||
|
// Use first line for additional context
|
||||||
|
let first_line = file
|
||||||
|
.contents_utf8()
|
||||||
|
.and_then(|s| s.lines().next().map(|line| line.to_string()))
|
||||||
|
.unwrap_or_else(String::new);
|
||||||
|
|
||||||
|
if let Some(name) = file.path().file_stem() {
|
||||||
|
tutorials.push_str(&format!("- {}: {}\n", name.to_string_lossy(), first_line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tutorials
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_tutorial(&self, name: &str) -> Result<String, ToolError> {
|
||||||
|
let file_name = format!("{}.md", name);
|
||||||
|
let file = TUTORIALS_DIR
|
||||||
|
.get_file(&file_name)
|
||||||
|
.ok_or(ToolError::ExecutionError(format!(
|
||||||
|
"Could not locate tutorial '{}'",
|
||||||
|
name
|
||||||
|
)))?;
|
||||||
|
Ok(String::from_utf8_lossy(file.contents()).into_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Router for TutorialRouter {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"tutorial".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instructions(&self) -> String {
|
||||||
|
self.instructions.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn capabilities(&self) -> ServerCapabilities {
|
||||||
|
CapabilitiesBuilder::new().with_tools(false).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_tools(&self) -> Vec<Tool> {
|
||||||
|
self.tools.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_tool(
|
||||||
|
&self,
|
||||||
|
tool_name: &str,
|
||||||
|
arguments: Value,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<Vec<Content>, ToolError>> + Send + 'static>> {
|
||||||
|
let this = self.clone();
|
||||||
|
let tool_name = tool_name.to_string();
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
match tool_name.as_str() {
|
||||||
|
"load_tutorial" => {
|
||||||
|
let name = arguments
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ToolError::InvalidParameters("Missing 'name' parameter".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let content = this.load_tutorial(name).await?;
|
||||||
|
Ok(vec![
|
||||||
|
Content::text(content).with_audience(vec![Role::Assistant])
|
||||||
|
])
|
||||||
|
}
|
||||||
|
_ => Err(ToolError::NotFound(format!("Tool {} not found", tool_name))),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_resources(&self) -> Vec<Resource> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_resource(
|
||||||
|
&self,
|
||||||
|
_uri: &str,
|
||||||
|
) -> Pin<Box<dyn Future<Output = Result<String, ResourceError>> + Send + 'static>> {
|
||||||
|
Box::pin(async move { Ok("".to_string()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for TutorialRouter {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
tools: self.tools.clone(),
|
||||||
|
instructions: self.instructions.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
415
crates/goose-mcp/src/tutorial/tutorials/build-mcp-extension.md
Normal file
415
crates/goose-mcp/src/tutorial/tutorials/build-mcp-extension.md
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
# Building an Extension with MCP (Model Context Protocol)
|
||||||
|
|
||||||
|
For this tutorial you will guide the user through building an MCP extension.
|
||||||
|
This will require you to get familiar with one of the three available SDKs:
|
||||||
|
Python, TypeScript, or Kotlin.
|
||||||
|
|
||||||
|
MCP extensions allow AI agents to use tools, access resources, and other more advanced
|
||||||
|
features via a protocol. The extension does not need to include all of these features.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
|
||||||
|
- You will help users implement MCP extensions using their chosen SDK
|
||||||
|
- You should adapt your explanations based on the user's experience level and questions
|
||||||
|
- Always reference the SDK implementations for accurate, up-to-date details
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
**Very Important:**
|
||||||
|
You (the agent) should **always** run the following so that you can get an up to date
|
||||||
|
reference of the SDK to refer to.
|
||||||
|
|
||||||
|
Clone the SDK repo into a temp dir and if it already exists, `cd` into the folder
|
||||||
|
and run `git pull`, then and `cat` the README.md
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p /tmp/mcp-reference && cd /tmp/mcp-reference
|
||||||
|
([ -d [python|typescript|kotlin]-sdk/.git ] && (cd [python|typescript|kotlin]-sdk && git pull) \
|
||||||
|
|| git clone https://github.com/modelcontextprotocol/[python|typescript|kotlin]-sdk.git
|
||||||
|
cat /tmp/mcp-reference/[python|typescript|kotlin]-sdk/README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, as needed, use ripgrep to search within the mcp-reference dir.
|
||||||
|
**Important**: reference this implementation to make sure you have up to date implementation
|
||||||
|
|
||||||
|
## Core Implementation Guide
|
||||||
|
|
||||||
|
### 0. Scaffolding
|
||||||
|
|
||||||
|
You should help the user scaffold out a project directory if they don't
|
||||||
|
already have one. This includes any necessary build tools or dependencies.
|
||||||
|
|
||||||
|
**Important**:
|
||||||
|
|
||||||
|
- Always check the reference SDK for typing and correct usage
|
||||||
|
- Python: Initialize a project using `uv init $PROJECT NAME`
|
||||||
|
- Python: Use `uv add` for all python package management, to keep `pyproject.toml` up to date
|
||||||
|
- Typescript: Initialize a project using `npm init -y`
|
||||||
|
- Kotlin: Use the following `gradle init` command to initialize:
|
||||||
|
```bash
|
||||||
|
gradle init \
|
||||||
|
--type kotlin-application \
|
||||||
|
--dsl kotlin \
|
||||||
|
--test-framework junit-jupiter \
|
||||||
|
--package my.project \
|
||||||
|
--project-name $PROJECT_NAME \
|
||||||
|
--no-split-project \
|
||||||
|
--java-version 21
|
||||||
|
```
|
||||||
|
|
||||||
|
Include the relevant SDK package:
|
||||||
|
|
||||||
|
1. `mcp` for python
|
||||||
|
2. `"io.modelcontextprotocol:kotlin-sdk:0.3.0"` for kotlin
|
||||||
|
3. `@modelcontextprotocol/sdk` for typescript
|
||||||
|
|
||||||
|
**Important for kotlin development:**
|
||||||
|
To get started with a Kotlin MCP server, look at the kotlin-mcp-server example included
|
||||||
|
in the Kotlin SDK. After cloning the SDK repository, you can find this sample inside the
|
||||||
|
samples/kotlin-mcp-server directory. There, you’ll see how the Gradle build files,
|
||||||
|
properties, and settings are configured, as well as the initial set of dependencies. Use
|
||||||
|
these existing gradle configurations to get the user started. Be sure to check out the
|
||||||
|
Main.kt file for a basic implementation that you can build upon.
|
||||||
|
|
||||||
|
### 1. Basic Server Setup
|
||||||
|
|
||||||
|
Help the user create their initial server file. Here are some patterns to get started with:
|
||||||
|
|
||||||
|
Python:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from mcp.server.stdio import stdio_server
|
||||||
|
|
||||||
|
mcp = FastMCP("Extension Name")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
mcp.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
TypeScript:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
|
||||||
|
const server = new McpServer({
|
||||||
|
name: "Extension Name",
|
||||||
|
version: "1.0.0",
|
||||||
|
});
|
||||||
|
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
```
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.server.Server
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
|
||||||
|
|
||||||
|
val server = Server(
|
||||||
|
serverInfo = Implementation(
|
||||||
|
name = "Extension Name",
|
||||||
|
version = "1.0.0"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val transport = StdioServerTransport()
|
||||||
|
server.connect(transport)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Implementing Resources
|
||||||
|
|
||||||
|
Resources provide data to the LLM. Guide users through implementing resources based on these patterns:
|
||||||
|
|
||||||
|
Python:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@mcp.resource("example://{param}")
|
||||||
|
def get_example(param: str) -> str:
|
||||||
|
return f"Data for {param}"
|
||||||
|
```
|
||||||
|
|
||||||
|
TypeScript:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
server.resource(
|
||||||
|
"example",
|
||||||
|
new ResourceTemplate("example://{param}", { list: undefined }),
|
||||||
|
async (uri, { param }) => ({
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: uri.href,
|
||||||
|
text: `Data for ${param}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
server.addResource(
|
||||||
|
uri = "example://{param}",
|
||||||
|
name = "Example",
|
||||||
|
description = "Example resource"
|
||||||
|
) { request ->
|
||||||
|
ReadResourceResult(
|
||||||
|
contents = listOf(
|
||||||
|
TextResourceContents(
|
||||||
|
text = "Data for ${request.params["param"]}",
|
||||||
|
uri = request.uri,
|
||||||
|
mimeType = "text/plain"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Implementing Tools
|
||||||
|
|
||||||
|
Tools allow the LLM to take actions. Guide users through implementing tools based on these patterns:
|
||||||
|
|
||||||
|
Python:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@mcp.tool()
|
||||||
|
def example_tool(param: str) -> str:
|
||||||
|
"""Example description for tool"""
|
||||||
|
return f"Processed {param}"
|
||||||
|
```
|
||||||
|
|
||||||
|
TypeScript:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
server.tool(
|
||||||
|
"example-tool",
|
||||||
|
"example description for tool",
|
||||||
|
{ param: z.string() },
|
||||||
|
async ({ param }) => ({
|
||||||
|
content: [{ type: "text", text: `Processed ${param}` }],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
server.addTool(
|
||||||
|
name = "example-tool",
|
||||||
|
description = "Example tool"
|
||||||
|
) { request ->
|
||||||
|
ToolCallResult(
|
||||||
|
content = listOf(
|
||||||
|
TextContent(
|
||||||
|
type = "text",
|
||||||
|
text = "Processed ${request.arguments["param"]}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing and Debugging Guide
|
||||||
|
|
||||||
|
Help users test their MCP extension using these steps:
|
||||||
|
|
||||||
|
### 1. Initial Testing
|
||||||
|
|
||||||
|
Instruct users to start a Goose session with their extension.
|
||||||
|
|
||||||
|
**Important**: You cannot start the goose session for them, as it is interactive. You will have to let them
|
||||||
|
know to start it in a terminal. Make sure you include instructions on how to setup the environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Python example
|
||||||
|
goose session --with-extension "python server.py"
|
||||||
|
|
||||||
|
# TypeScript example
|
||||||
|
goose session --with-extension "node server.js"
|
||||||
|
|
||||||
|
# Kotlin example
|
||||||
|
goose session --with-extension "java -jar build/libs/extension.jar"
|
||||||
|
```
|
||||||
|
|
||||||
|
Tell users to watch for startup errors. If the session fails to start, they should share the error message with you for debugging.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
You can run a feedback loop using a headless goose session, however if the process hangs you get into a stuck action.
|
||||||
|
Ask the user if they want you to do that, and let them know they will manually need to kill any stuck processes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Python example
|
||||||
|
goose run --with-extension "python server.py" --text "EXAMPLE PROMPT HERE"
|
||||||
|
|
||||||
|
# TypeScript example
|
||||||
|
goose run --with-extension "node server.js" --text "EXAMPLE PROMPT HERE"
|
||||||
|
|
||||||
|
# Kotlin example
|
||||||
|
goose run --with-extension "java -jar build/libs/extension.jar" --text "EXAMPLE PROMPT HERE"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Testing Tools and Resources
|
||||||
|
|
||||||
|
Once the session starts successfully, guide users to test their implementation:
|
||||||
|
|
||||||
|
- For tools, they should ask Goose to use the tool directly
|
||||||
|
- For resources, they should ask Goose to access the relevant data
|
||||||
|
|
||||||
|
Example prompts they can use:
|
||||||
|
|
||||||
|
```
|
||||||
|
"Please use the example-tool with parameter 'test'"
|
||||||
|
"Can you read the data from example://test-param"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Adding Logging for Debugging
|
||||||
|
|
||||||
|
If the user encounters an unclear error, guide them to add file-based logging to the server.
|
||||||
|
Here are the patterns for each SDK:
|
||||||
|
|
||||||
|
Python:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
filename='mcp_extension.log',
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def example_tool(param: str) -> str:
|
||||||
|
logging.debug(f"example_tool called with param: {param}")
|
||||||
|
try:
|
||||||
|
result = f"Processed {param}"
|
||||||
|
logging.debug(f"example_tool succeeded: {result}")
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"example_tool failed: {str(e)}", exc_info=True)
|
||||||
|
raise
|
||||||
|
```
|
||||||
|
|
||||||
|
TypeScript:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
function log(message: string) {
|
||||||
|
fs.appendFileSync(
|
||||||
|
"mcp_extension.log",
|
||||||
|
`${new Date().toISOString()} - ${message}\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.tool("example-tool", { param: z.string() }, async ({ param }) => {
|
||||||
|
log(`example-tool called with param: ${param}`);
|
||||||
|
try {
|
||||||
|
const result = `Processed ${param}`;
|
||||||
|
log(`example-tool succeeded: ${result}`);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: result }],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
log(`example-tool failed: ${error}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Kotlin:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import java.io.File
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
fun log(message: String) {
|
||||||
|
File("mcp_extension.log").appendText("${LocalDateTime.now()} - $message\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
server.addTool(
|
||||||
|
name = "example-tool",
|
||||||
|
description = "Example tool"
|
||||||
|
) { request ->
|
||||||
|
log("example-tool called with param: ${request.arguments["param"]}")
|
||||||
|
try {
|
||||||
|
val result = "Processed ${request.arguments["param"]}"
|
||||||
|
log("example-tool succeeded: $result")
|
||||||
|
ToolCallResult(
|
||||||
|
content = listOf(
|
||||||
|
TextContent(
|
||||||
|
type = "text",
|
||||||
|
text = result
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log("example-tool failed: ${e.message}")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Debugging Process
|
||||||
|
|
||||||
|
When users encounter issues:
|
||||||
|
|
||||||
|
1. First, check if there are any immediate error messages in the Goose session
|
||||||
|
|
||||||
|
2. If the error isn't clear, guide them to:
|
||||||
|
|
||||||
|
- Add logging to their implementation using the patterns above
|
||||||
|
- Restart their session with the updated code
|
||||||
|
- Check the mcp_extension.log file for detailed error information
|
||||||
|
|
||||||
|
3. Common issues to watch for:
|
||||||
|
|
||||||
|
- Incorrect parameter types or missing parameters
|
||||||
|
- Malformed resource URIs
|
||||||
|
- Exceptions in tool implementation
|
||||||
|
- Protocol message formatting errors
|
||||||
|
|
||||||
|
4. If users share log contents with you:
|
||||||
|
- Look for error messages and stack traces
|
||||||
|
- Check if parameters are being passed correctly
|
||||||
|
- Verify the implementation matches the SDK patterns
|
||||||
|
- Suggest specific fixes based on the error details
|
||||||
|
|
||||||
|
## Important Guidelines for You (the Agent)
|
||||||
|
|
||||||
|
1. Always start by asking the user what they want to build
|
||||||
|
|
||||||
|
2. Always ask the user which SDK they want to use before providing specific implementation details
|
||||||
|
|
||||||
|
3. Always use the reference implementations:
|
||||||
|
|
||||||
|
- Always clone the relevant SDK repo before starting with basic steup
|
||||||
|
- After cloning the relevant SDK, find and `cat` the `README.md` for context
|
||||||
|
- Use ripgrep to find specific examples within the reference
|
||||||
|
- Reference real implementations rather than making assumptions
|
||||||
|
|
||||||
|
4. When building the project, if any compliation or type issues occur, _always_ check the reference SDK before making a fix.
|
||||||
|
|
||||||
|
5. When helping with implementations:
|
||||||
|
|
||||||
|
- Start with the basic server setup
|
||||||
|
- Add one resource or tool at a time
|
||||||
|
- Test each addition before moving on
|
||||||
|
|
||||||
|
6. Common Gotchas to Watch For:
|
||||||
|
|
||||||
|
- Python: Ensure decorators are properly imported
|
||||||
|
- TypeScript: Remember to import zod for parameter validation
|
||||||
|
- Kotlin: Pay attention to proper type declarations
|
||||||
|
|
||||||
|
7. When users ask about implementation details:
|
||||||
|
- First check the reference SDK
|
||||||
|
- Use ripgrep to find relevant examples
|
||||||
|
- Provide context-specific guidance based on their SDK choice
|
||||||
|
|
||||||
|
Remember: Your role is to guide and explain, adapting based on the user's needs and questions. Don't dump all implementation details at once - help users build their extension step by step.
|
||||||
178
crates/goose-mcp/src/tutorial/tutorials/first-game.md
Normal file
178
crates/goose-mcp/src/tutorial/tutorials/first-game.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Building Your First Game
|
||||||
|
|
||||||
|
This tutorial provides a framework for guiding a user through building their first simple game. The default suggestion is a Flappy Bird clone using Python and Pygame, but you should adapt based on user preferences and experience.
|
||||||
|
|
||||||
|
## Initial Discussion
|
||||||
|
|
||||||
|
Start by understanding the user's context and preferences:
|
||||||
|
|
||||||
|
1. Ask about their programming experience:
|
||||||
|
- Are they completely new to programming?
|
||||||
|
- Do they have experience with specific languages?
|
||||||
|
- Have they done any game development before?
|
||||||
|
|
||||||
|
2. Discuss game preferences:
|
||||||
|
- Suggest simple starter games they could build:
|
||||||
|
* Flappy Bird (default) - focuses on physics and collision
|
||||||
|
* Snake - focuses on grid-based movement and growth mechanics
|
||||||
|
* Pong - focuses on two-player interaction and ball physics
|
||||||
|
* Breakout - focuses on collision and scoring mechanics
|
||||||
|
- Let them suggest alternatives if they have something specific in mind
|
||||||
|
- Help them understand the complexity of their choice and adjust if needed
|
||||||
|
|
||||||
|
3. Choose technology stack:
|
||||||
|
- Default suggestion: Python + Pygame (beginner-friendly, cross-platform)
|
||||||
|
- Alternative suggestions based on user experience:
|
||||||
|
* JavaScript + Canvas (web-based, good for sharing)
|
||||||
|
* Lua + LÖVE (lightweight, good for learning)
|
||||||
|
* C# + MonoGame (good for Windows users/Unity transition)
|
||||||
|
- Consider factors like:
|
||||||
|
* Installation complexity on their OS
|
||||||
|
* Learning curve
|
||||||
|
* Available learning resources
|
||||||
|
* Their future goals in programming
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
Guide them through setting up their development environment:
|
||||||
|
|
||||||
|
1. Version Control:
|
||||||
|
- Help them install and configure git
|
||||||
|
- Explain basic version control concepts if they're new
|
||||||
|
- Create initial repository
|
||||||
|
|
||||||
|
2. Programming Language:
|
||||||
|
- Walk through installation for their chosen language
|
||||||
|
- Verify installation (help troubleshoot if needed)
|
||||||
|
- Explain how to run code in their environment
|
||||||
|
|
||||||
|
3. Dependency Management:
|
||||||
|
- Explain why dependency isolation is important
|
||||||
|
- For Python: Guide through virtualenv setup:
|
||||||
|
```bash
|
||||||
|
python -m venv env
|
||||||
|
source env/bin/activate # or env\Scripts\activate on Windows
|
||||||
|
```
|
||||||
|
- Similar isolation for other languages:
|
||||||
|
* Node: package.json
|
||||||
|
* Rust: Cargo.toml
|
||||||
|
* etc.
|
||||||
|
|
||||||
|
4. Game Framework:
|
||||||
|
- Install and verify chosen framework
|
||||||
|
- Create minimal test program
|
||||||
|
- Ensure they can run it successfully
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
Help them set up a maintainable project:
|
||||||
|
|
||||||
|
1. Discuss project organization:
|
||||||
|
- File structure
|
||||||
|
- Code organization
|
||||||
|
- Asset management (if needed)
|
||||||
|
|
||||||
|
2. Create initial files:
|
||||||
|
- Main game file
|
||||||
|
- Configuration (if needed)
|
||||||
|
- Asset directories (if needed)
|
||||||
|
|
||||||
|
3. Set up version control:
|
||||||
|
- .gitignore for their stack
|
||||||
|
- Initial commit
|
||||||
|
- Explain commit strategy
|
||||||
|
|
||||||
|
## Core Game Loop
|
||||||
|
|
||||||
|
Guide them through building the basic game structure:
|
||||||
|
|
||||||
|
1. Window Setup:
|
||||||
|
- Creating a game window
|
||||||
|
- Setting up the game loop
|
||||||
|
- Handling basic events (exit, restart)
|
||||||
|
|
||||||
|
2. Game State:
|
||||||
|
- Define core game objects
|
||||||
|
- Set up state management
|
||||||
|
- Create update/draw separation
|
||||||
|
|
||||||
|
## Game Mechanics
|
||||||
|
|
||||||
|
Break down implementation into manageable pieces:
|
||||||
|
|
||||||
|
1. Player Interaction:
|
||||||
|
- Input handling
|
||||||
|
- Basic movement
|
||||||
|
- Test and refine "feel"
|
||||||
|
|
||||||
|
2. Core Mechanics:
|
||||||
|
- Main game elements (varies by game type)
|
||||||
|
- Basic collision detection
|
||||||
|
- Score tracking
|
||||||
|
|
||||||
|
3. Progressive Enhancement:
|
||||||
|
- Additional features
|
||||||
|
- Polish and refinement
|
||||||
|
- Bug fixes
|
||||||
|
|
||||||
|
## Testing and Refinement
|
||||||
|
|
||||||
|
Help them improve their game:
|
||||||
|
|
||||||
|
1. Playability:
|
||||||
|
- Test core mechanics
|
||||||
|
- Adjust difficulty
|
||||||
|
- Refine controls
|
||||||
|
|
||||||
|
2. Code Quality:
|
||||||
|
- Identify repetitive code
|
||||||
|
- Suggest improvements
|
||||||
|
- Explain benefits
|
||||||
|
|
||||||
|
## Extensions and Learning
|
||||||
|
|
||||||
|
Suggest next steps based on their interests:
|
||||||
|
|
||||||
|
1. Possible Enhancements:
|
||||||
|
- Graphics improvements
|
||||||
|
- Sound effects
|
||||||
|
- Additional features
|
||||||
|
- Menu systems
|
||||||
|
|
||||||
|
2. Learning Opportunities:
|
||||||
|
- Code structure improvements
|
||||||
|
- Performance optimization
|
||||||
|
- Advanced features
|
||||||
|
- Related topics to explore
|
||||||
|
|
||||||
|
## Notes for Agent
|
||||||
|
|
||||||
|
- Adapt the pace based on user understanding
|
||||||
|
- Provide more detailed explanations when needed
|
||||||
|
- Suggest breaks at good stopping points
|
||||||
|
- Celebrate small victories and progress
|
||||||
|
- Be ready to troubleshoot common issues:
|
||||||
|
* Installation problems
|
||||||
|
* Framework-specific errors
|
||||||
|
* Game logic bugs
|
||||||
|
* Performance issues
|
||||||
|
|
||||||
|
Remember to:
|
||||||
|
- Check understanding frequently
|
||||||
|
- Provide context for new concepts
|
||||||
|
- Relate to user's existing knowledge
|
||||||
|
- Be patient with debugging
|
||||||
|
- Encourage experimentation
|
||||||
|
- Maintain a positive learning environment
|
||||||
|
|
||||||
|
Default Implementation:
|
||||||
|
- If user has no strong preferences, guide them through:
|
||||||
|
* Python + Pygame
|
||||||
|
* Flappy Bird clone
|
||||||
|
* virtualenv for dependency management
|
||||||
|
* git for version control
|
||||||
|
- This combination provides:
|
||||||
|
* Minimal setup complexity
|
||||||
|
* Quick visible progress
|
||||||
|
* Clear next steps
|
||||||
|
* Manageable scope
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use goose_mcp::{
|
use goose_mcp::{
|
||||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
||||||
|
TutorialRouter,
|
||||||
};
|
};
|
||||||
use mcp_server::router::RouterService;
|
use mcp_server::router::RouterService;
|
||||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||||
@@ -20,6 +21,7 @@ pub async fn run(name: &str) -> Result<()> {
|
|||||||
Some(Box::new(RouterService(router)))
|
Some(Box::new(RouterService(router)))
|
||||||
}
|
}
|
||||||
"memory" => Some(Box::new(RouterService(MemoryRouter::new()))),
|
"memory" => Some(Box::new(RouterService(MemoryRouter::new()))),
|
||||||
|
"tutorial" => Some(Box::new(RouterService(TutorialRouter::new()))),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user