MCPlease
MCPlease is a lightweight Rust framework for building MCP (Model Context Protocol) servers. It provides a simple, macro-driven approach to defining tools and managing state, with optional support for session persistence and cross-process synchronization.
Features
- Simple tool definition using the
tools!macro - Automatic JSON Schema generation from Rust structs using
schemars - Session management with cross-process synchronization via file watching
- Command-line interface with automatic help generation via
clap - Example system for better tool documentation
- Stdio-based MCP communication (WebSocket support planned)
- Code generation CLI for rapid development
Quick Start with CLI (Recommended)
The fastest way to get started is with the mcplease CLI tool:
# Install the CLI
# Create a new MCP server with tools
# Navigate to your project
# Add more tools as needed
# Test that it compiles
# Run your MCP server
This creates a fully functional MCP server with:
- ✅ Proper project structure
- ✅ Generated tool implementations (with TODOs for you to fill in)
- ✅ State management boilerplate
- ✅ All necessary dependencies
- ✅ Beautifully formatted code
For detailed CLI documentation, see cli/README.md
Manual Setup
1. Create a new MCP server project
2. Add dependencies to Cargo.toml
[]
= "1.0"
= { = "4.5", = ["derive"] }
= "0.4.6"
= "0.3.0"
= "1.0.4"
= { = "1.0", = ["derive"] }
= "1.0"
3. Define your state structure
Create src/state.rs:
use Result;
use SessionStore;
use ;
use PathBuf;
4. Create tools
Create src/tools/ directory and add tool implementations. Each tool should be in its own module:
src/tools/hello.rs:
use crateMyToolsState;
use Result;
use ;
use ;
/// Say hello to someone
src/tools/set_working_directory.rs:
use crateMyToolsState;
use Result;
use ;
use ;
use PathBuf;
/// Set the working directory for relative path operations
5. Wire everything together
src/tools.rs:
use crateMyToolsState;
tools!;
src/main.rs:
use Result;
use ;
use MyToolsState;
const INSTRUCTIONS: &str = "This is my custom MCP server. Use set_working_directory to establish context.";
6. Run your server
# Run as MCP server (stdio mode)
# Or use tools directly from command line
Framework Architecture
Core Components
tools!macro: Generates the enum that implements MCP tool dispatchTooltrait: Defines how individual tools executeToolMetatrait: Examples, behavior hints, title, and icons for a toolToolOutputtrait: What a tool returns — plain text or a structured valueSessionStore: Handles persistent state with cross-process sync- JSON Schema generation: Automatic from Rust structs via
schemars
Tool Definition Pattern
Each tool follows this pattern:
State Management
The framework uses SessionStore<T> for persistent state:
- Cross-process safe: File watching detects external changes
- Atomic writes: Temporary file + rename prevents corruption
- Session-based: Multiple sessions can coexist
- JSON serialization: Human-readable storage format
Session Store API
// Get or create session data
let data = store.get_or_create?;
// Update data with closure
store.update?;
// Get without creating
let maybe_data = store.get?;
// Set directly
store.set?;
Advanced Features
Error Handling
Tools return anyhow::Result<Self::Output>. A returned Err becomes a tool
result with isError: true rather than a JSON-RPC protocol error, so the model
sees the failure and can correct itself:
Examples and Documentation
Provide meaningful examples to help users understand tool usage:
Structured Output
A tool returning String sends plain text to the model and nothing else. A tool
can instead return a structured type, which is sent both as text for the model
and as a machine-readable value the calling client can display without parsing
the text. The type's JSON Schema is advertised as the tool's outputSchema, so
a client knows the shape before ever calling it.
// Implements ToolOutput using the spec's recommended shape.
structured_output!;
By default the same value is sent both ways — the text half is its serialized
JSON — which is what keeps clients that ignore structured content working. For
large results, implement ToolOutput by hand and override to_content to send
the model a summary while the client still receives the full record.
Rich Content
Returning Vec<ContentBlock> sends images, audio, embedded resources, or
several blocks at once. Blocks carry optional annotations, which are how you
mark one block for the user and another for the model:
Tool Annotations
Behavior hints tell a client what a tool does before it runs one, which is what lets it decide what to auto-approve. The protocol's defaults are pessimistic — an undeclared tool is assumed to be a destructive, open-world mutation — so declaring them is worthwhile:
Knowing the Caller
RequestContext describes the client making this call — its protocol version,
self-reported identity, and the capabilities it supports. Capabilities are
declared per-request and must not be carried over from earlier ones, so this is
rebuilt for each call. On the command-line path it is empty, since there is no
MCP client.
Server Configuration
ServerConfig carries everything the serve loop needs beyond the tools:
let config = new
.with_instructions
// How long a client may cache the tool list. The list is fixed at compile
// time, so the default is generous (one hour).
.with_tools_ttl_ms
// Opt in only if the tool list is identical for every user.
.with_tools_cache_scope;
Optional Parameters
Use Option<T> with proper serialization handling:
Shared Session Data
For tools that need to share context across processes:
// In your state struct:
Best Practices
Tool Design
- Single responsibility: Each tool should do one thing well
- Clear documentation: Use detailed doc comments on all parameters
- Meaningful examples: Provide realistic usage scenarios
- Error context: Use
anyhow::Contextfor descriptive error messages - Defensive programming: Validate inputs and handle edge cases
State Management
- Minimal state: Only persist what's necessary across calls
- Default values: Use
#[serde(default)]for backward compatibility - Session IDs: Use logical identifiers like "default", project names, etc.
- Cleanup: Consider implementing state cleanup for old sessions
Error Messages
Return user-friendly messages that help with debugging:
// Good: Specific and actionable
Ok
// Bad: Generic and unhelpful
Err
Path Handling
Use consistent path resolution patterns:
Debugging
Logging
Set MCP_LOG_LOCATION environment variable to enable logging:
Log levels: RUST_LOG=trace,warn,error,debug,info
Testing Tools Directly
Use the command-line interface for testing:
# Test individual tools
# Get help
Common Issues
- Schema validation errors: Ensure all fields have proper serde attributes
- Session conflicts: Use unique session IDs for different contexts
- Path resolution: Always handle both absolute and relative paths
- JSON parsing: Check that tool parameters match expected schema
Contributing
When adding new tools to existing servers:
- Create a new module in
src/tools/ - Implement the required traits
- Add to the
tools!macro insrc/tools.rs - Add tests in
src/tests.rs - Update documentation and examples
The framework is designed to be extensible - new MCP servers should follow the established patterns for consistency and maintainability.