Contributing to the Gopher & Gemini MCP Server
Thank you for your interest in contributing to the Gopher & Gemini MCP Server! This document provides guidelines and information for contributors.
Quick Start for Contributors
- Fork the repository on GitHub
- Clone your fork locally:
- Set up the development environment:
- Create a feature branch:
Development Environment
Prerequisites
- Python 3.11+ - Download here
- uv package manager - Install uv
- Git - Install Git
Setup
The project uses uv for dependency management and a cross-platform task runner:
# Set up development environment (installs dependencies and pre-commit hooks)
uv run task dev-setup
# Verify setup
uv run task quality
The task runner
Project tasks — linting, tests, docs, the local CI pipeline — run through
taskipy, whose task table lives in
pyproject.toml under [tool.taskipy.tasks]. That table is the single
definition of every task; there is nothing to keep in sync with it.
This works identically on Windows, macOS and Linux, and needs nothing installed
beyond uv and the project's dependency groups (uv sync --all-groups, which
scripts/dev-setup.sh / scripts\dev-setup.bat runs for you).
On Unix-like systems make <command> is a convenience wrapper: the Makefile
is a catch-all target that forwards straight to uv run task <command>, so the
two are interchangeable. make with no target runs the help task.
To see the tasks that actually exist, ask the runner rather than this page:
The tasks, grouped:
| Group | Tasks |
|---|---|
| Setup | dev-setup, dev-setup-win, install-hooks |
| Code quality | lint, format, typecheck, check, quality |
| Testing | test, test-cov, test-unit, test-integration, test-slow |
| Server | serve, serve-http, serve-sse |
| Documentation | docs-serve, docs-build |
| Maintenance | clean, clean-win, ci, help |
A few of them are worth explaining:
lintandformatdeliberately cover the whole repository rather than justsrc/andtests/, because CI runsruff check .; scoping them down would hide violations inscripts/and other top-level Python that CI still fails on.typecheckmatches CI'smypy srcexactly, without a blanket--ignore-missing-importsthat would let a new untyped import pass locally and fail in CI.checkislintthentypecheck;qualityislint,typecheck,test;ciischeckthentest-cov— the local mirror of the CI pipeline.dev-setup-winandclean-winexist because Windows has nobashand taskipy cannot branch on the platform at runtime, so the platform is in the task name.
Adding a task. Add one entry under [tool.taskipy.tasks] and run it:
Nothing else needs updating. There is no second copy of the task table: the
project used to ship a task.py holding a hand-maintained duplicate that
nothing compared against, and it had already drifted from pyproject.toml by
the time it was removed. If you are following an older document that says to run
python task.py <command>, the file no longer exists — use
uv run task <command>.
Code Standards
Code Quality
We maintain high code quality standards:
- Type hints for all functions and methods
- Comprehensive tests (CI enforces a minimum of 95% coverage)
- Documentation for all public APIs
- Security considerations for all network operations
- Cross-platform compatibility (Windows, macOS, Linux)
Code Style
- Formatter: Ruff (automatically applied)
- Linter: Ruff with strict settings
- Type Checker: mypy with strict mode
- Import Sorting: Handled by Ruff
Pre-commit Hooks
Pre-commit hooks automatically run on every commit to ensure code quality:
# Install hooks (done automatically by dev-setup)
uv run task install-hooks
# Run hooks manually
pre-commit run --all-files
Testing
Test Structure
tests/
├── test_server.py # MCP server + tool tests
├── test_gopher_client.py # Gopher client tests
├── test_gemini_client.py # Gemini client tests
├── test_gemini_tls.py # Gemini TLS / TOFU tests
├── test_client_certs.py # Client certificate tests
├── test_config.py # Configuration tests
├── test_security.py # Security / SSRF tests
├── test_mcp_protocol.py # The MCP wire envelope, over an in-memory session
├── test_integration.py # Integration tests
├── conftest.py # Pytest fixtures and configuration
└── ... # Additional protocol, model, and util tests
Running Tests
# Run all tests
uv run task test
# Run with coverage report
uv run task test-cov
# Run specific test file
uv run pytest tests/test_server.py
Writing Tests
- Use pytest for all tests
- Include type hints in test functions
- Use descriptive test names that explain what is being tested
- Include docstrings for complex test scenarios
- Mock external dependencies (network calls, file system)
Example test structure:
import pytest
from gopher_mcp.config import GopherConfig
def test_default_configuration():
"""The Gopher config exposes the documented defaults."""
config = GopherConfig()
assert config.max_response_size == 1048576
assert config.timeout_seconds == 30.0
@pytest.mark.asyncio
async def test_gopher_fetch_returns_structured_result():
"""gopher_fetch returns a structured dict for a menu URL."""
from gopher_mcp.server import gopher_fetch
result = await gopher_fetch("gopher://gopher.floodgap.com/1/")
assert isinstance(result, dict)
Documentation
Documentation Standards
- Docstrings for all public functions, classes, and modules
- Type hints for all function parameters and return values
- Examples in docstrings for complex functions
- README updates for new features or configuration options
Documentation Format
We use Google-style docstrings:
def fetch_gopher_resource(url: str, timeout: int = 30) -> GopherResult:
"""Fetch a resource from a Gopher server.
Args:
url: The Gopher URL to fetch
timeout: Request timeout in seconds
Returns:
A GopherResult containing the fetched data
Raises:
GopherError: If the request fails or times out
Example:
>>> result = fetch_gopher_resource("gopher://example.com/1/")
>>> print(result.content)
"""
Building Documentation
# Serve documentation locally, with live reload
uv run task docs-serve
# Build documentation
uv run task docs-build
CI builds the site with mkdocs build --clean --strict, so a broken relative
link, a missing anchor or an unresolved mkdocstrings reference fails the build
rather than shipping. Reproduce it before pushing:
--strict only proves that nothing warned, not that anything rendered:
check-docs-render.sh asserts the landing page's mermaid diagram came out as a
diagram, which is the regression that shipped as literal graph TB text for a
year while every strict build passed.
Two pages are one-line --8<-- includes rather than second copies:
docs/contributing.md includes this file, and docs/changelog.md includes
CHANGELOG.md. Edit the root file; the page follows. Any further include of a
file outside docs/ also belongs in the paths: filter of
.github/workflows/docs.yml, or an edit to it will not redeploy the site.
Three retired pages — advanced-features.md, gemini-configuration.md and
task-runner.md — keep answering through mkdocs-redirects, configured under
plugins.redirects.redirect_maps in mkdocs.yml. Removing or renaming a page
whose URL has been published means adding an entry there in the same change.
Security Considerations
Security Guidelines
- Input validation for all user-provided data
- Timeout limits for all network operations
- Size limits for response data
- URL validation to prevent malicious requests
- Error handling that doesn't leak sensitive information
Security Testing
- Include security-focused tests
- Use
banditfor security linting (runs automatically, and fails CI) - Use
pip-auditfor dependency vulnerability checking. It runs on every PR but is advisory only — an advisory published against a pinned dependency is not a defect in your PR, so it annotates the run instead of failing it. A nightly audit workflow files them under thesecurity-auditlabel instead.
Bug Reports
Open one with the Bug Report template, which collects this information as a form. Blank issues are turned off, so the new-issue page always starts from a template.
Before Submitting a Bug Report
- Search existing issues to avoid duplicates
- Test with the latest version from the main branch
- Gather relevant information: your OS, your Python version, the exact
error
codeand message from the tool result, and the package version —gopher-mcp --versionreports it, and works for theuvxand Docker installs where importing the package is not an option
Feature Requests
Open one with the Feature Request template. Questions that are not requests belong on the Question template.
Feature Request Guidelines
- Clear use case - Explain why this feature would be valuable
- Detailed description - Provide specific implementation ideas
- Backward compatibility - Consider impact on existing users
- Security implications - Consider any security aspects
Pull Request Process
Before Submitting a Pull Request
- Create an issue to discuss major changes
- Write tests for new functionality
- Update documentation as needed
- Run quality checks:
uv run task quality - Test cross-platform if possible
Pull Request Template
**Description**
Brief description of changes.
**Type of change**
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
**Testing**
- [ ] Tests pass locally
- [ ] New tests added for new functionality
- [ ] Manual testing completed
**Checklist**
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No new security vulnerabilities introduced
Review Process
- Automated checks must pass (CI/CD pipeline)
- Code review by maintainers
- Testing on multiple platforms if needed
- Documentation review for user-facing changes
Release Process
Versioning
We follow Semantic Versioning:
- MAJOR version for incompatible API changes
- MINOR version for backward-compatible functionality additions
- PATCH version for backward-compatible bug fixes
Release Checklist
- Update version in
pyproject.tomlandserver.json(which carries it twice), then runuv lock.scripts/prepare-release.pydoes all of this, and the release workflow fails the tag if the three disagree - Update
CHANGELOG.md - Create release PR
- Tag release after merge
- Publish to PyPI (automated)
See the Releasing guide for the full process, trusted-publishing setup, and the complete pre-release checklist.
Community Guidelines
Code of Conduct
- Be respectful and inclusive
- Focus on constructive feedback
- Help others learn and grow
- Maintain a welcoming environment
Communication
- GitHub Issues - Bug reports, feature requests and questions, each with its own template on the new-issue page
- Pull Requests - Code contributions and reviews
Getting Help
- Documentation: Project Docs
- Issues: GitHub Issues
- Questions: Open a question issue
Made with ❤️ by Cameron Rye