mirror of
https://github.com/aljazceru/goose.git
synced 2026-01-03 22:44:23 +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",
|
||||
"Tools to save and retrieve durable memories",
|
||||
)
|
||||
.item(
|
||||
"tutorial",
|
||||
"Tutorial",
|
||||
"Access interactive tutorials and guides",
|
||||
)
|
||||
.item("jetbrains", "JetBrains", "Connect to jetbrains IDEs")
|
||||
.interact()?
|
||||
.to_string();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use goose_mcp::{
|
||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
||||
TutorialRouter,
|
||||
};
|
||||
use mcp_server::router::RouterService;
|
||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||
@@ -21,6 +22,7 @@ pub async fn run_server(name: &str) -> Result<()> {
|
||||
Some(Box::new(RouterService(router)))
|
||||
}
|
||||
"memory" => Some(Box::new(RouterService(MemoryRouter::new()))),
|
||||
"tutorial" => Some(Box::new(RouterService(TutorialRouter::new()))),
|
||||
_ => 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
|
||||
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
|
||||
may show ignored or hidden files. For example *do not* use `find` or `ls -r`
|
||||
- List files by name: `rg --files | rg <filename>`
|
||||
|
||||
@@ -12,9 +12,11 @@ mod developer;
|
||||
mod google_drive;
|
||||
mod jetbrains;
|
||||
mod memory;
|
||||
mod tutorial;
|
||||
|
||||
pub use computercontroller::ComputerControllerRouter;
|
||||
pub use developer::DeveloperRouter;
|
||||
pub use google_drive::GoogleDriveRouter;
|
||||
pub use jetbrains::JetBrainsRouter;
|
||||
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 goose_mcp::{
|
||||
ComputerControllerRouter, DeveloperRouter, GoogleDriveRouter, JetBrainsRouter, MemoryRouter,
|
||||
TutorialRouter,
|
||||
};
|
||||
use mcp_server::router::RouterService;
|
||||
use mcp_server::{BoundedService, ByteTransport, Server};
|
||||
@@ -20,6 +21,7 @@ pub async fn run(name: &str) -> Result<()> {
|
||||
Some(Box::new(RouterService(router)))
|
||||
}
|
||||
"memory" => Some(Box::new(RouterService(MemoryRouter::new()))),
|
||||
"tutorial" => Some(Box::new(RouterService(TutorialRouter::new()))),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user