Skip to main content
Glama
jezweb

Australian Postcodes MCP Server

mcp_config.py10.3 kB
"""Canonical MCP Configuration Format. This module defines the standard configuration format for Model Context Protocol (MCP) servers. It provides a client-agnostic, extensible format that can be used across all MCP implementations. The configuration format supports both stdio and remote (HTTP/SSE) transports, with comprehensive field definitions for server metadata, authentication, and execution parameters. Example configuration: ```json { "mcpServers": { "my-server": { "command": "npx", "args": ["-y", "@my/mcp-server"], "env": {"API_KEY": "secret"}, "timeout": 30000, "description": "My MCP server" } } } ``` """ from __future__ import annotations import datetime import re from pathlib import Path from typing import TYPE_CHECKING, Annotated, Any, Literal from urllib.parse import urlparse import httpx from pydantic import ( AnyUrl, BaseModel, ConfigDict, Field, ValidationInfo, model_validator, ) from typing_extensions import Self, override from fastmcp.tools.tool_transform import ToolTransformConfig from fastmcp.utilities.types import FastMCPBaseModel if TYPE_CHECKING: from fastmcp.client.transports import ( ClientTransport, FastMCPTransport, SSETransport, StdioTransport, StreamableHttpTransport, ) def infer_transport_type_from_url( url: str | AnyUrl, ) -> Literal["http", "sse"]: """ Infer the appropriate transport type from the given URL. """ url = str(url) if not url.startswith("http"): raise ValueError(f"Invalid URL: {url}") parsed_url = urlparse(url) path = parsed_url.path # Match /sse followed by /, ?, &, or end of string if re.search(r"/sse(/|\?|&|$)", path): return "sse" else: return "http" class _TransformingMCPServerMixin(FastMCPBaseModel): """A mixin that enables wrapping an MCP Server with tool transforms.""" tools: dict[str, ToolTransformConfig] = Field(...) """The multi-tool transform to apply to the tools.""" include_tags: set[str] | None = Field( default=None, description="The tags to include in the proxy.", ) exclude_tags: set[str] | None = Field( default=None, description="The tags to exclude in the proxy.", ) def to_transport(self) -> FastMCPTransport: """Get the transport for the server.""" from fastmcp.client.transports import FastMCPTransport from fastmcp.server.server import FastMCP transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType] wrapped_mcp_server = FastMCP.as_proxy( transport, tool_transformations=self.tools, include_tags=self.include_tags, exclude_tags=self.exclude_tags, ) return FastMCPTransport(wrapped_mcp_server) class StdioMCPServer(BaseModel): """MCP server configuration for stdio transport. This is the canonical configuration format for MCP servers using stdio transport. """ # Required fields command: str # Common optional fields args: list[str] = Field(default_factory=list) env: dict[str, Any] = Field(default_factory=dict) # Transport specification transport: Literal["stdio"] = "stdio" type: Literal["stdio"] | None = None # Alternative transport field name # Execution context cwd: str | None = None # Working directory for command execution timeout: int | None = None # Maximum response time in milliseconds # Metadata description: str | None = None # Human-readable server description icon: str | None = None # Icon path or URL for UI display # Authentication configuration authentication: dict[str, Any] | None = None # Auth configuration object model_config = ConfigDict(extra="allow") # Preserve unknown fields def to_transport(self) -> StdioTransport: from fastmcp.client.transports import StdioTransport return StdioTransport( command=self.command, args=self.args, env=self.env, cwd=self.cwd, ) class TransformingStdioMCPServer(_TransformingMCPServerMixin, StdioMCPServer): """A Stdio server with tool transforms.""" class RemoteMCPServer(BaseModel): """MCP server configuration for HTTP/SSE transport. This is the canonical configuration format for MCP servers using remote transports. """ # Required fields url: str # Transport configuration transport: Literal["http", "streamable-http", "sse"] | None = None headers: dict[str, str] = Field(default_factory=dict) # Authentication auth: Annotated[ str | Literal["oauth"] | httpx.Auth | None, Field( description='Either a string representing a Bearer token, the literal "oauth" to use OAuth authentication, or an httpx.Auth instance for custom authentication.', ), ] = None # Timeout configuration sse_read_timeout: datetime.timedelta | int | float | None = None timeout: int | None = None # Maximum response time in milliseconds # Metadata description: str | None = None # Human-readable server description icon: str | None = None # Icon path or URL for UI display # Authentication configuration authentication: dict[str, Any] | None = None # Auth configuration object model_config = ConfigDict( extra="allow", arbitrary_types_allowed=True ) # Preserve unknown fields def to_transport(self) -> StreamableHttpTransport | SSETransport: from fastmcp.client.transports import SSETransport, StreamableHttpTransport if self.transport is None: transport = infer_transport_type_from_url(self.url) else: transport = self.transport if transport == "sse": return SSETransport( self.url, headers=self.headers, auth=self.auth, sse_read_timeout=self.sse_read_timeout, ) else: # Both "http" and "streamable-http" map to StreamableHttpTransport return StreamableHttpTransport( self.url, headers=self.headers, auth=self.auth, sse_read_timeout=self.sse_read_timeout, ) class TransformingRemoteMCPServer(_TransformingMCPServerMixin, RemoteMCPServer): """A Remote server with tool transforms.""" TransformingMCPServerTypes = TransformingStdioMCPServer | TransformingRemoteMCPServer CanonicalMCPServerTypes = StdioMCPServer | RemoteMCPServer MCPServerTypes = TransformingMCPServerTypes | CanonicalMCPServerTypes class MCPConfig(BaseModel): """A configuration object for MCP Servers that conforms to the canonical MCP configuration format while adding additional fields for enabling FastMCP-specific features like tool transformations and filtering by tags. For an MCPConfig that is strictly canonical, see the `CanonicalMCPConfig` class. """ mcpServers: dict[str, MCPServerTypes] model_config = ConfigDict(extra="allow") # Preserve unknown top-level fields @model_validator(mode="before") def validate_mcp_servers(self, info: ValidationInfo) -> dict[str, Any]: """Validate the MCP servers.""" if not isinstance(self, dict): raise ValueError("MCPConfig format requires a dictionary of servers.") if "mcpServers" not in self: self = {"mcpServers": self} return self def add_server(self, name: str, server: MCPServerTypes) -> None: """Add or update a server in the configuration.""" self.mcpServers[name] = server @classmethod def from_dict(cls, config: dict[str, Any]) -> Self: """Parse MCP configuration from dictionary format.""" return cls.model_validate(config) def to_dict(self) -> dict[str, Any]: """Convert MCPConfig to dictionary format, preserving all fields.""" return self.model_dump(exclude_none=True) def write_to_file(self, file_path: Path) -> None: """Write configuration to JSON file.""" file_path.parent.mkdir(parents=True, exist_ok=True) file_path.write_text(self.model_dump_json(indent=2)) @classmethod def from_file(cls, file_path: Path) -> Self: """Load configuration from JSON file.""" if file_path.exists(): if content := file_path.read_text().strip(): return cls.model_validate_json(content) raise ValueError(f"No MCP servers defined in the config: {file_path}") class CanonicalMCPConfig(MCPConfig): """Canonical MCP configuration format. This defines the standard configuration format for Model Context Protocol servers. The format is designed to be client-agnostic and extensible for future use cases. """ mcpServers: dict[str, CanonicalMCPServerTypes] @override def add_server(self, name: str, server: CanonicalMCPServerTypes) -> None: """Add or update a server in the configuration.""" self.mcpServers[name] = server def update_config_file( file_path: Path, server_name: str, server_config: CanonicalMCPServerTypes, ) -> None: """Update an MCP configuration file from a server object, preserving existing fields. This is used for updating the mcpServer configurations of third-party tools so we do not worry about transforming server objects here.""" config = MCPConfig.from_file(file_path) # If updating an existing server, merge with existing configuration # to preserve any unknown fields if existing_server := config.mcpServers.get(server_name): # Get the raw dict representation of both servers existing_dict = existing_server.model_dump() new_dict = server_config.model_dump(exclude_none=True) # Merge, with new values taking precedence merged_config = server_config.model_validate({**existing_dict, **new_dict}) config.add_server(server_name, merged_config) else: config.add_server(server_name, server_config) config.write_to_file(file_path)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jezweb/australian-postcodes-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server