MCPcopy Index your code
hub / github.com/avahowell/offeryn

github.com/avahowell/offeryn @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
93 symbols 148 edges 16 files 18 documented · 19%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

offeryn

offeryn (Welsh for tool; implement), is a Rust implementation of modelcontextprotocol, a standard protocol for empowering large language models with tools.

It aims to offer an easy to use API that allows developers to quickly create and expose tools written in Rust to AI agents.

  • [x] JSON-RPC core MCP server protocol
  • [x] Tool use support
  • [x] Procedural macro for tool generation
  • [x] Server-Sent Events (SSE) transport
  • [x] Stdio transport
  • [ ] Resources support
  • [ ] Prompts support
  • [ ] Roots suppot
  • [ ] Client protocol
  • [ ] WebSocket transport
  • [ ] Streaming responses

Example (Stdio)

use offeryn::prelude::*;
use offeryn::{StdioTransport, McpServer};
use std::sync::Arc;
use async_trait;

/// A simple calculator that can perform basic arithmetic operations
#[derive(Default, Clone)]
struct Calculator {}

// The mcp_tool proc macro generates tool methods that are enumerated to MCP clients.
// It can handle arbitrary types, including Result<T> and Option<T>.
// A correct json-schema is generated for each function signature of the tool, based on the Rust type.
// Docstrings are used as tool descriptions, passed along in MCP to inform the AI agent of the tool's purpose.
//
// This example will generate:
// {
//     "name": "calculator_divide",
//     "description": "Divide two numbers",
//     "inputSchema": {
//         "type": "object",
//         "properties": {
//             "a": {
//                 "description": "Dividend - the number to be divided",
//                 "format": "int64",
//                 "type": "integer"
//             },
//             "b": {
//                 "description": "Divisor - the number to divide by",
//                 "format": "int64",
//                 "type": "integer"
//             }
//         },
//         "required": [
//             "a",
//             "b"
//         ]
//     }
// }
// ... (cont... calculator_add, calculator_subtract, calculator_multiply)
//
// Results are handled as you would expect, with the proper JSON-RPC error code.
#[mcp_tool]
impl Calculator {
    /// Add two numbers
    /// # Parameters
    /// * `a` - First value to add
    /// * `b` - Second value to add
    async fn add(&self, a: i64, b: i64) -> i64 {
        a + b
    }

    /// Subtract two numbers
    /// # Parameters
    /// * `a` - Number to subtract from
    /// * `b` - Number to subtract
    async fn subtract(&self, a: i64, b: i64) -> i64 {
        a - b
    }

    /// Multiply two numbers
    /// # Parameters
    /// * `a` - First factor to multiply
    /// * `b` - Second factor to multiply
    async fn multiply(&self, a: i64, b: i64) -> i64 {
        a * b
    }

    /// Divide two numbers
    /// # Parameters
    /// * `a` - Dividend - the number to be divided
    /// * `b` - Divisor - the number to divide by
    async fn divide(&self, a: i64, b: i64) -> Result<f64, String> {
        if b == 0 {
            Err("Cannot divide by zero".to_string())
        } else {
            Ok(a as f64 / b as f64)
        }
    }
}

#[tokio::main]
async fn main() {
    // Create a new server instance
    let server = Arc::new(McpServer::new("calculator", "1.0.0"));

    // Register the calculator tools
    server.register_tools(Calculator::default()).await;

    // Create and run the stdio transport
    let transport = StdioTransport::<tokio::io::Stdin, tokio::io::Stdout>::new(server);

    if let Err(e) = transport.run().await {
        eprintln!("Error: {}", e);
    }
}

Servers configured with stdio transport as above can be hooked to Claude Desktop as normal (see https://modelcontextprotocol.io/quickstart/user, build a binary with mcp-rs and point at it in mcpServers).

Example (SSE)

use offeryn::prelude::*;
use offeryn::{SseTransport, McpServer};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::Mutex;
/// A simple calculator that can perform basic arithmetic operations
#[derive(Default, Clone)]
struct Calculator {}

#[mcp_tool]
impl Calculator {
    /// Add two numbers
    /// # Parameters
    /// * `a` - First value to add
    /// * `b` - Second value to add
    async fn add(&self, a: i64, b: i64) -> i64 {
        a + b
    }

    /// Subtract two numbers
    /// # Parameters
    /// * `a` - Number to subtract from
    /// * `b` - Number to subtract
    async fn subtract(&self, a: i64, b: i64) -> i64 {
        a - b
    }

    /// Multiply two numbers
    /// # Parameters
    /// * `a` - First factor to multiply
    /// * `b` - Second factor to multiply
    async fn multiply(&self, a: i64, b: i64) -> i64 {
        a * b
    }

    /// Divide two numbers
    /// # Parameters
    /// * `a` - Dividend - the number to be divided
    /// * `b` - Divisor - the number to divide by
    async fn divide(&self, a: i64, b: i64) -> Result<f64, String> {
        if b == 0 {
            Err("Cannot divide by zero".to_string())
        } else {
            Ok(a as f64 / b as f64)
        }
    }
}

#[tokio::main]
async fn main() {
    let server = Arc::new(McpServer::new("calculator", "1.0.0"));

    server.register_tools(Calculator::default()).await;

    let app = SseTransport::create_router(server);
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("Server running on http://{}", addr);

    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();

    // You can now connect to the server using a MCP client in SSE mode.
}

Extension points exported contracts — how you extend this code

StdioTransport (Interface)
(no doc) [1 implementers]
crates/offeryn-core/src/transport/stdio.rs
McpTool (Interface)
(no doc) [1 implementers]
crates/offeryn-types/src/lib.rs
HasTools (Interface)
(no doc)
crates/offeryn-types/src/lib.rs

Core symbols most depended-on inside this repo

handle_request
called by 10
crates/offeryn-core/src/server/mod.rs
register_tools
called by 4
crates/offeryn-core/src/server/mod.rs
register_tool
called by 3
crates/offeryn-core/src/server/mod.rs
run
called by 2
crates/offeryn-core/src/transport/stdio.rs
extract_doc_string
called by 1
crates/offeryn-derive/src/lib.rs
get_type_schema
called by 1
crates/offeryn-derive/src/lib.rs
is_optional_type
called by 1
crates/offeryn-derive/src/lib.rs
extract_param_doc
called by 1
crates/offeryn-derive/src/lib.rs

Shape

Method 41
Class 26
Function 21
Interface 3
Enum 2

Languages

Rust100%

Modules by API surface

crates/offeryn-types/src/lib.rs18 symbols
crates/offeryn-core/src/transport/sse.rs12 symbols
crates/offeryn-core/tests/server_tests.rs10 symbols
crates/offeryn-core/src/transport/stdio.rs10 symbols
crates/offeryn-core/src/server/mod.rs8 symbols
crates/offeryn-derive/src/lib.rs7 symbols
examples/stdio-calculator/src/main.rs6 symbols
examples/calculator/src/main.rs6 symbols
crates/offeryn-derive/tests/schema/03-stateful.rs4 symbols
crates/offeryn-derive/tests/schema/02-doc-comments.rs4 symbols
crates/offeryn-derive/tests/schema/01-basic.rs4 symbols
crates/offeryn-core/src/error.rs3 symbols

For agents

$ claude mcp add offeryn \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact