API Reference
This document provides a comprehensive reference for the Gopher & Gemini MCP Server API.
For the exhaustive, always-in-sync field definitions of every result type, see the auto-generated Data Models page.
MCP Tools
The server provides four tools: gopher_fetch and gemini_fetch for single
resources, plus gopher_batch_fetch and gemini_batch_fetch for fetching
multiple URLs in a single call.
gopher_fetch
Fetches content from Gopher protocol servers.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | Full Gopher URL (e.g., gopher://gopher.floodgap.com/1/) |
Examples
Fetching a Gopher Menu
from gopher_mcp.server import gopher_fetch
# Fetch a directory listing
result = await gopher_fetch("gopher://gopher.floodgap.com/1/")
if result["kind"] == "menu":
print(f"Found {len(result['items'])} menu items")
for item in result["items"]:
print(f" {item['title']} ({item['type']})")
Fetching a Text File
# Fetch a text document
result = await gopher_fetch("gopher://gopher.floodgap.com/0/gopher/tech/history.txt")
if result["kind"] == "text":
print(f"Content ({result['bytes']} bytes):")
print(result["text"])
Performing a Gopher Search
# Search using a Gopher search server (type 7)
result = await gopher_fetch("gopher://gopher.floodgap.com/7/v2/vs?search+query")
if result["kind"] == "menu":
print(f"Search returned {len(result['items'])} results")
Handling Binary Content
# Fetch binary file metadata
result = await gopher_fetch("gopher://gopher.floodgap.com/9/file.zip")
if result["kind"] == "binary":
print(f"Binary file: {result['note']}")
print(f"Type: {result['mime_type']}")
print(f"Size: {result['bytes']} bytes")
Error Handling
# Handle errors gracefully
result = await gopher_fetch("gopher://invalid.example.com/1/")
if result["kind"] == "error":
print(f"Error [{result['error']['code']}]: {result['error']['message']}")
Response Types
gopher_fetch returns one of these result objects, distinguished by the kind
field. See Data Models for the complete, always-in-sync
field definitions generated from the source.
kind |
Type | Returned for |
|---|---|---|
menu |
MenuResult |
Gopher menus (type 1) and search results (type 7); the items are GopherMenuItem entries |
text |
TextResult |
Text files (type 0) |
binary |
BinaryResult |
Binary item types (4, 5, 6, 9, g, I) — metadata only |
error |
ErrorResult |
Errors and unsupported content |
Every result also carries a request_info object (request URL, host, port, and
timing metadata).
gemini_fetch
Fetches content from Gemini protocol servers with full TLS security.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | Yes | Full Gemini URL (e.g., gemini://geminiprotocol.net/) |
input |
string | No | Text to answer a Gemini input prompt (status 10/11); it is percent-encoded into the query string |
Examples
Fetching Gemtext Content
from gopher_mcp.server import gemini_fetch
# Fetch a gemtext page
result = await gemini_fetch("gemini://geminiprotocol.net/")
if result["kind"] == "gemtext":
lines = result["document"]["lines"]
headings = [ln for ln in lines if ln["type"].startswith("heading")]
print(f"Document has {len(lines)} lines")
print(f"Found {len(result['document']['links'])} links")
print(f"Found {len(headings)} headings")
# Print all headings
for heading in headings:
print(f"{'#' * heading['level']} {heading['heading']['text']}")
Fetching Plain Text
# Fetch plain text content
result = await gemini_fetch("gemini://example.com/document.txt")
if result["kind"] == "success":
mime = result["mime_type"]
print(f"MIME type: {mime['type']}/{mime['subtype']}")
if mime["type"] == "text":
print(f"Content:\n{result['content']}")
Handling Redirects
# Handle redirect responses
result = await gemini_fetch("gemini://example.com/old-page")
if result["kind"] == "redirect":
print(f"Redirected to: {result['new_url']}")
print(f"Permanent: {result['permanent']}")
# Follow the redirect
new_result = await gemini_fetch(result["new_url"])
Handling Input Requests
# Handle input requests
result = await gemini_fetch("gemini://example.com/search")
if result["kind"] == "input":
print(f"Server requests input: {result['prompt']}")
print(f"Sensitive: {result['sensitive']}")
# Answer the prompt with the gemini_fetch input parameter
new_result = await gemini_fetch("gemini://example.com/search", input="search query")
Handling Certificate Requests
# Handle client certificate requests
result = await gemini_fetch("gemini://example.com/private")
if result["kind"] == "certificate":
print(f"Certificate required: {result['message']}")
print(f"Status code: {result['status']}")
# Client certificates are automatically managed by the server
Error Handling
# Handle various error types
result = await gemini_fetch("gemini://example.com/notfound")
if result["kind"] == "error":
err = result["error"]
print(f"Error {err['status']}: {err['message']}")
if err["temporary"]:
print("This is a temporary error - retry may succeed")
else:
print("Permanent error - do not retry")
Working with Links
# Extract and process all links from a gemtext page
result = await gemini_fetch("gemini://example.com/links")
if result["kind"] == "gemtext":
for link in result["document"]["links"]:
print(f"Link: {link['url']}")
if link.get("text"):
print(f" Text: {link['text']}")
Response Types
gemini_fetch returns one of these result objects, distinguished by the kind
field. See Data Models for the complete field definitions.
kind |
Type | Returned for |
|---|---|---|
gemtext |
GeminiGemtextResult |
text/gemini content, parsed into a GemtextDocument of GemtextLine items |
success |
GeminiSuccessResult |
Textual success responses (status 20-29); the MIME type is a GeminiMimeType |
binary |
GeminiBinaryResult |
Binary success responses — metadata only (size + MIME type), no raw bytes |
input |
GeminiInputResult |
Input prompts (status 10-11) — answer with the gemini_fetch input parameter |
redirect |
GeminiRedirectResult |
Redirects (status 30-31) |
error |
GeminiErrorResult |
Errors (status 40-59) |
certificate |
GeminiCertificateResult |
Client-certificate requests (status 60-69) |
gopher_batch_fetch
Fetches several Gopher resources in a single call.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
urls |
string[] | Yes | List of full Gopher URLs (maximum 50) |
Behavior
- Returns a list of results aligned by index with the input
urls. - Each element has the same shape as a
gopher_fetchresponse (MenuResult,TextResult,BinaryResult, orErrorResult). - Requests run with bounded concurrency (up to 5 at a time). Passing more than 50 URLs returns one
ErrorResultper URL instead of fetching.
from gopher_mcp.server import gopher_batch_fetch
results = await gopher_batch_fetch([
"gopher://gopher.floodgap.com/1/",
"gopher://gopher.floodgap.com/0/gopher/welcome",
])
for result in results:
print(result["kind"])
gemini_batch_fetch
Fetches several Gemini resources in a single call.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
urls |
string[] | Yes | List of full Gemini URLs (maximum 50) |
Behavior
- Returns a list of results aligned by index with the input
urls. - Each element has the same shape as a
gemini_fetchresponse (gemtext, success, input, redirect, error, or certificate). - Requests run with bounded concurrency (up to 5 at a time). Passing more than 50 URLs returns one error result per URL instead of fetching.
from gopher_mcp.server import gemini_batch_fetch
results = await gemini_batch_fetch([
"gemini://geminiprotocol.net/",
"gemini://geminiprotocol.net/docs/",
])
for result in results:
print(result["kind"])
Common Types
request_info
Every result includes a request_info field — a free-form object
(dict[str, Any]) carrying metadata about the request, such as the requested
URL, host, port, and timing. It is not a fixed schema, so treat its keys as
best-effort metadata rather than a guaranteed contract.
Status Codes
Gopher Protocol
Gopher uses item types rather than status codes:
| Type | Description |
|---|---|
0 |
Text file |
1 |
Menu/directory |
4 |
BinHex file |
5 |
DOS binary |
6 |
UUEncoded file |
7 |
Search server |
9 |
Binary file |
g |
GIF image |
I |
Image file |
h |
HTML file |
i |
Informational text |
s |
Sound file |
Gemini Protocol
Gemini uses two-digit status codes:
Input (10-19)
| Code | Description |
|---|---|
10 |
Input required |
11 |
Sensitive input required |
Success (20-29)
| Code | Description |
|---|---|
20 |
Success |
Redirect (30-39)
| Code | Description |
|---|---|
30 |
Temporary redirect |
31 |
Permanent redirect |
Temporary Failure (40-49)
| Code | Description |
|---|---|
40 |
Temporary failure |
41 |
Server unavailable |
42 |
CGI error |
43 |
Proxy error |
44 |
Slow down |
Permanent Failure (50-59)
| Code | Description |
|---|---|
50 |
Permanent failure |
51 |
Not found |
52 |
Gone |
53 |
Proxy request refused |
59 |
Bad request |
Client Certificate Required (60-69)
| Code | Description |
|---|---|
60 |
Client certificate required |
61 |
Certificate not authorized |
62 |
Certificate not valid |
Error Handling
Gopher Errors
Common Gopher errors and how to handle them:
Connection Timeout
Error: "Connection timeout: Server not responding"
Cause: Server is unreachable or slow to respond
Solution:
# Increase timeout in configuration
# GOPHER_TIMEOUT_SECONDS=60
result = await gopher_fetch("gopher://slow-server.example.com/1/")
if result["kind"] == "error" and "timeout" in result["error"]["message"].lower():
print("Server is slow or unreachable - try again later")
Invalid URL
Error: "Invalid Gopher URL format"
Cause: Malformed URL structure
Solution:
# Ensure URL follows gopher://host[:port]/type/selector format
valid_url = "gopher://gopher.floodgap.com/1/"
invalid_url = "gopher://gopher.floodgap.com" # Missing type and selector
result = await gopher_fetch(valid_url)
Unsupported Type
Error: "Unsupported Gopher item type: X"
Cause: Server returned unknown or unsupported item type
Solution:
result = await gopher_fetch("gopher://example.com/X/unknown")
if result["kind"] == "error" and "unsupported" in result["error"]["message"].lower():
print("This content type is not supported")
Content Too Large
Error: "Response exceeds maximum size limit"
Cause: Response size exceeds configured maximum
Solution:
# Increase size limit in configuration
# GOPHER_MAX_RESPONSE_SIZE=2097152
result = await gopher_fetch("gopher://example.com/0/large-file.txt")
if result["kind"] == "error" and "size" in result["error"]["message"].lower():
print("File is too large - increase GOPHER_MAX_RESPONSE_SIZE")
Gemini Errors
Common Gemini errors and how to handle them:
TLS Handshake Failure
Error: "TLS connection failed: Handshake error"
Cause: Certificate or TLS configuration issues
Solution:
result = await gemini_fetch("gemini://tls-error.example.com/")
if result["kind"] == "error" and "tls" in result["error"]["message"].lower():
print("TLS connection failed - server may have invalid certificate")
print("Check server TLS configuration")
TOFU Validation Failure
Error: "TOFU validation failed: Certificate fingerprint mismatch"
Cause: Server certificate changed since first visit
Solution:
# Certificate changed - manual intervention required
# 1. Verify the change is legitimate
# 2. Remove old certificate from TOFU storage
# 3. Retry the request
# TOFU storage location: ~/.gemini/tofu.json
result = await gemini_fetch("gemini://changed-cert.example.com/")
if result["kind"] == "error" and "tofu" in result["error"]["message"].lower():
print("Certificate changed - verify this is expected")
print("Remove old entry from TOFU storage if legitimate")
Invalid Status Code
Error: "Invalid Gemini status code: XX"
Cause: Server returned malformed or invalid status code
Solution:
result = await gemini_fetch("gemini://broken-server.example.com/")
if result["kind"] == "error" and "status" in result["error"]["message"].lower():
print("Server returned invalid response - contact server admin")
Content Too Large
Error: "Response exceeds maximum size limit"
Cause: Response size exceeds configured maximum
Solution:
# Increase size limit in configuration
# GEMINI_MAX_RESPONSE_SIZE=2097152
result = await gemini_fetch("gemini://example.com/large-document")
if result["kind"] == "error" and "size" in result["error"]["message"].lower():
print("Content too large - increase GEMINI_MAX_RESPONSE_SIZE")
Host Not Allowed
Error: "Host not in allowed hosts list"
Cause: Server not in configured allowlist
Solution:
# Add host to allowlist in configuration
# GEMINI_ALLOWED_HOSTS=geminiprotocol.net,example.com
result = await gemini_fetch("gemini://blocked.example.com/")
if result["kind"] == "error" and "allowed" in result["error"]["message"].lower():
print("Host not allowed - add to GEMINI_ALLOWED_HOSTS")
Error Response Structure
All error responses include:
# Gopher error
{
"kind": "error",
"error": {
"code": "ERROR_CODE", # Machine-readable error code
"message": "Human-readable error message",
},
"request_info": { ... }, # Free-form request metadata
}
# Gemini error (also carries status / temporary)
{
"kind": "error",
"error": {
"code": "TEMPORARY_ERROR", # or "PERMANENT_ERROR", etc.
"message": "Human-readable error message",
"status": 51, # Gemini status code
"temporary": False,
},
"request_info": { ... },
}
Rate Limiting
Both protocols implement rate limiting to prevent abuse:
- Request timeout: Configurable per protocol (covers DNS, connect and read)
- Response size limit: Configurable maximum response size, enforced incrementally during the read
- Concurrency cap: Optional limit on simultaneous in-flight fetches
(
*_MAX_CONCURRENT_REQUESTS, off by default). Each fetch opens a fresh connection; there is no connection pooling/reuse. - Cache TTL: Configurable cache time-to-live
Security Considerations
Gopher Security
- No encryption: Gopher traffic is unencrypted
- Input sanitization: All inputs are validated
- Size limits: Responses are limited in size
- Timeout protection: Requests have configurable timeouts
Gemini Security
- Mandatory TLS: All connections use TLS 1.2+
- TOFU validation: Certificate fingerprints are verified
- Client certificates: Automatic generation and management
- Host allowlists: Configurable allowed hosts
- Input validation: URLs and responses are validated
Performance
Caching
Both protocols support intelligent caching:
- Response caching: Successful responses are cached
- TTL-based expiration: Configurable cache lifetime
- Size-based eviction: LRU eviction when cache is full
- Cache bypass: Option to disable caching per protocol
Connection Management
- Fresh connection per request: each fetch opens a new connection; there is no connection pooling or reuse
- Async/await: Non-blocking I/O operations
- Streaming: Memory-efficient content handling
- Resource cleanup: Automatic connection cleanup
Configuration
See the main README.md for complete configuration options for both protocols.