Skip to main content

Contributing

Thank you for your interest in contributing to Open MCP Gateway! This guide will help you get started.

Getting Started

Prerequisites

  • Rust 1.75+ (install via rustup)
  • Git
  • A GitHub account

Fork and Clone

# Fork the repository on GitHub, then:
git clone https://github.com/YOUR_USERNAME/mcp-gateway.git
cd mcp-gateway

# Add upstream remote
git remote add upstream https://github.com/agentic/mcp-gateway.git

Build and Test

# Build all crates
cargo build

# Run tests
cargo test

# Run with example config
cargo run --bin gateway-http -- --config examples/config.yaml

Development Workflow

1. Create a Branch

git checkout -b feature/my-feature
# or
git checkout -b fix/my-bugfix

2. Make Changes

Follow the code style guidelines below.

3. Test Your Changes

# Run all tests
cargo test

# Run specific test
cargo test test_name

# Run with logging
RUST_LOG=debug cargo test

4. Format and Lint

# Format code
cargo fmt

# Run clippy
cargo clippy -- -D warnings

# Both (recommended before commit)
cargo fmt && cargo clippy -- -D warnings

5. Commit

Use conventional commit messages:

git commit -m "feat: add Docker runtime support"
git commit -m "fix: handle connection timeout properly"
git commit -m "docs: update configuration guide"
git commit -m "refactor: simplify server manager logic"
git commit -m "test: add registry filtering tests"

Prefixes:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation only
  • refactor: Code refactoring
  • test: Adding tests
  • chore: Build, CI, dependencies

6. Push and Create PR

git push origin feature/my-feature

Then create a Pull Request on GitHub.

Code Style

Rust Guidelines

// Use descriptive names
fn start_server(server_id: &str) -> Result<()>; // Good
fn start(id: &str) -> Result<()>; // Less clear

// Document public APIs
/// Starts the specified server.
///
/// # Arguments
/// * `server_id` - The unique identifier of the server
///
/// # Errors
/// Returns an error if the server is not found or fails to start.
pub async fn start_server(&self, server_id: &str) -> Result<()>;

// Handle errors explicitly
let server = self.registry
.get(server_id)
.ok_or_else(|| GatewayError::ServerNotFound(server_id.to_string()))?;

// Use structured logging
tracing::info!(server_id = %server_id, "Starting server");
tracing::error!(error = ?e, "Failed to start server");

File Organization

// Imports grouped and sorted
use std::collections::HashMap;
use std::sync::Arc;

use async_trait::async_trait;
use tokio::sync::RwLock;

use crate::config::ServerDefinition;
use crate::error::GatewayError;

// Public items first, then private
pub struct ServerManager { ... }

impl ServerManager {
pub fn new() -> Self { ... }
pub async fn start(&self) -> Result<()> { ... }

async fn internal_method(&self) { ... }
}

Error Handling

// Define specific error types
#[derive(Error, Debug)]
pub enum GatewayError {
#[error("Server not found: {0}")]
ServerNotFound(String),

#[error("Runtime error: {0}")]
RuntimeError(#[source] anyhow::Error),
}

// Use ? for propagation
async fn example() -> Result<(), GatewayError> {
let config = load_config().map_err(GatewayError::ConfigError)?;
Ok(())
}

Project Structure

When adding new features, follow this structure:

gateway-core/src/
├── lib.rs # Add new module exports
├── new_module.rs # New module file
└── new_module/ # Or directory for complex modules
├── mod.rs
└── submodule.rs

Adding a New Runtime

  1. Create runtime file in gateway-core/src/runtime/:
// gateway-core/src/runtime/my_runtime.rs
use super::{BackendConnection, Runtime};

pub struct MyRuntime {
// ...
}

#[async_trait]
impl Runtime for MyRuntime {
async fn connect(&self) -> Result<Box<dyn BackendConnection>> {
// Implementation
}
}
  1. Add to RuntimeConfig enum in config.rs
  2. Update mod.rs to export
  3. Add tests
  4. Update documentation

Testing

Unit Tests

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_registry_lookup() {
let registry = ServerRegistry::new(servers);
assert!(registry.get("postgres").is_some());
}

#[tokio::test]
async fn test_async_function() {
let result = async_function().await;
assert!(result.is_ok());
}
}

Integration Tests

Create in tests/ directory:

// tests/integration_test.rs
#[tokio::test]
async fn test_full_workflow() {
// Setup
// Execute
// Assert
}

Documentation

  • Update relevant docs when changing behavior
  • Add doc comments to public APIs
  • Include examples in documentation
/// Creates a new server manager.
///
/// # Examples
///
/// ```
/// let manager = ServerManager::new(registry);
/// manager.start_server("postgres").await?;
/// ```
pub fn new(registry: Arc<ServerRegistry>) -> Self {
// ...
}

Pull Request Guidelines

Before Submitting

  • Code compiles without warnings
  • All tests pass
  • Code is formatted (cargo fmt)
  • Clippy passes (cargo clippy)
  • Documentation updated if needed
  • Commit messages follow convention

PR Description

Include:

  • What the change does
  • Why it's needed
  • How to test it
  • Any breaking changes

Review Process

  1. CI runs automatically
  2. Maintainer reviews code
  3. Address feedback
  4. Squash and merge

Getting Help

License

By contributing, you agree that your contributions will be licensed under the Apache-2.0 License.