Skip to content

Public API

What you'll learn here: the small set of Python modules that most users and integrators should treat as the adapter's public code surface.


What belongs here

These modules are the ones most likely to matter if you are:

  • running the adapter from Python
  • loading and validating config programmatically
  • trying to understand the top-level contract without digging through storage and proxy internals

This is not a formal long-term compatibility promise, but it is the clearest public-facing surface in the current codebase.


remote_mcp_adapter.server

This is the main Python entry point for building the FastAPI application.

FastAPI app factory with multi-server FastMCP proxy mounts.

create_app

create_app(config=None, config_path=None)

Create the adapter FastAPI app with one MCP proxy mount per server config.

Parameters:

NameTypeDescriptionDefault
configAdapterConfig | None

Pre-built config object, used as-is when provided.

None
config_pathstr | None

Filesystem path to a YAML config file (used when config is None).

None

Returns:

TypeDescription
FastAPI

Fully-configured FastAPI application ready for uvicorn.run.

Source code in src/remote_mcp_adapter/server.py
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def create_app(config: AdapterConfig | None = None, config_path: str | None = None) -> FastAPI:
    """Create the adapter FastAPI app with one MCP proxy mount per server config.

    Args:
        config: Pre-built config object, used as-is when provided.
        config_path: Filesystem path to a YAML config file (used when
            *config* is None).

    Returns:
        Fully-configured ``FastAPI`` application ready for ``uvicorn.run``.
    """
    resolved_config = resolve_config(config, config_path)
    install_log_redaction_filter(config=resolved_config)
    telemetry = AdapterTelemetry.from_config(resolved_config)
    persistence_policy = PersistencePolicyController(
        configured_backend=resolved_config.state_persistence.type,
        unavailable_policy=resolved_config.state_persistence.unavailable_policy,
        telemetry=telemetry,
    )
    try:
        persistence_runtime = build_persistence_runtime(resolved_config)
    except Exception as exc:
        startup_action = persistence_policy.handle_startup_failure(
            phase="runtime_build",
            error=str(exc),
        )
        if startup_action == "exit":
            raise
        persistence_runtime = build_memory_persistence_runtime()
    runtime_ref: dict[str, object] = {"current": persistence_runtime}
    write_policy_lock_mode = resolve_storage_lock_mode(resolved_config)
    session_store = SessionStore(
        resolved_config,
        state_repository=persistence_runtime.state_repository,
        lock_provider=persistence_runtime.lock_provider,
        telemetry=telemetry,
    )
    proxy_map = build_proxy_map(resolved_config, session_store=session_store, telemetry=telemetry)
    upload_nonce_store = build_upload_nonce_store(config=resolved_config, runtime=persistence_runtime)
    upload_credentials = UploadCredentialManager.from_config(
        resolved_config,
        nonce_store=upload_nonce_store,
        telemetry=telemetry,
    )
    artifact_download_credentials = ArtifactDownloadCredentialManager.from_config(resolved_config)
    upstream_health = build_upstream_health_monitors(resolved_config, proxy_map, telemetry=telemetry)
    mounted_http_apps = {
        server_id: mount.proxy.http_app(path=mount.server.mount_path, transport="streamable-http")
        for server_id, mount in proxy_map.items()
    }

    combined_routes = []
    for mounted in mounted_http_apps.values():
        combined_routes.extend(mounted.routes)

    lifespan = build_lifespan(
        resolved_config=resolved_config,
        runtime_ref=runtime_ref,
        session_store=session_store,
        proxy_map=proxy_map,
        upstream_health=upstream_health,
        write_policy_lock_mode=write_policy_lock_mode,
        persistence_policy=persistence_policy,
        mounted_http_apps=mounted_http_apps,
        upload_credentials=upload_credentials,
        artifact_download_credentials=artifact_download_credentials,
        telemetry=telemetry,
        build_memory_persistence_runtime=build_memory_persistence_runtime,
    )

    app = FastAPI(
        title="MCP General Adapter",
        lifespan=lifespan,
        routes=combined_routes,
    )
    apply_cors_middleware(app, resolved_config)
    app.state.adapter_config = resolved_config
    app.state.session_store = session_store
    app.state.proxy_map = proxy_map
    app.state.upstream_health = upstream_health
    app.state.persistence_runtime = runtime_ref["current"]
    app.state.persistence_policy = persistence_policy
    app.state.upload_credentials = upload_credentials
    app.state.artifact_download_credentials = artifact_download_credentials
    app.state.telemetry = telemetry
    mount_path_to_server_id = {mount.server.mount_path: server_id for server_id, mount in proxy_map.items()}
    cancellation_observer = CancellationObserver()
    upload_route = build_server_upload_path(resolved_config.core.upload_path, "{server_id}")
    upload_route_prefix = upload_path_prefix(resolved_config.core.upload_path)

    register_middlewares(
        app=app,
        resolved_config=resolved_config,
        persistence_policy=persistence_policy,
        runtime_ref=runtime_ref,
        session_store=session_store,
        upstream_health=upstream_health,
        mount_path_to_server_id=mount_path_to_server_id,
        cancellation_observer=cancellation_observer,
        upload_path_prefix=upload_route_prefix,
        upload_credentials=upload_credentials,
        artifact_download_credentials=artifact_download_credentials,
        telemetry=telemetry,
        build_memory_persistence_runtime=build_memory_persistence_runtime,
    )
    register_routes(
        app=app,
        resolved_config=resolved_config,
        proxy_map=proxy_map,
        upstream_health=upstream_health,
        persistence_policy=persistence_policy,
        runtime_ref=runtime_ref,
        session_store=session_store,
        upload_route=upload_route,
        telemetry=telemetry,
        build_memory_persistence_runtime=build_memory_persistence_runtime,
        save_upload_stream=save_upload_stream,
    )

    return app

remote_mcp_adapter.config.load

This module loads YAML config, expands environment placeholders, and returns a validated AdapterConfig.

YAML config loader with environment interpolation support.

load_config

load_config(path)

Load YAML config and resolve ${ENV_VAR} placeholders.

Parameters:

NameTypeDescriptionDefault
pathstr | Path

Filesystem path to the YAML configuration file.

required

Returns:

TypeDescription
AdapterConfig

Validated AdapterConfig instance.

Raises:

TypeDescription
ValueError

If a required environment variable is unset.

FileNotFoundError

If the config file does not exist.

Source code in src/remote_mcp_adapter/config/load.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def load_config(path: str | Path) -> AdapterConfig:
    """Load YAML config and resolve ``${ENV_VAR}`` placeholders.

    Args:
        path: Filesystem path to the YAML configuration file.

    Returns:
        Validated ``AdapterConfig`` instance.

    Raises:
        ValueError: If a required environment variable is unset.
        FileNotFoundError: If the config file does not exist.
    """
    config_path = Path(path)
    with config_path.open("r", encoding="utf-8") as config_file:
        raw_config: Any = yaml.safe_load(config_file) or {}
    interpolated = _interpolate_env(raw_config)
    return AdapterConfig.model_validate(interpolated)

remote_mcp_adapter.config.schemas.root

This is the top-level config schema. If you want to understand what the fully validated config object looks like in Python, start here.

Top-level adapter config schema and helpers.

AdapterConfig

Bases: BaseModel

Top-level adapter configuration.

Source code in src/remote_mcp_adapter/config/schemas/root.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class AdapterConfig(BaseModel):
    """Top-level adapter configuration."""

    model_config = ConfigDict(extra="forbid")

    core: CoreConfig = Field(default_factory=CoreConfig)
    state_persistence: StatePersistenceConfig = Field(default_factory=StatePersistenceConfig)
    storage: StorageConfig = Field(default_factory=StorageConfig)
    sessions: SessionsConfig = Field(default_factory=SessionsConfig)
    uploads: UploadsConfig = Field(default_factory=UploadsConfig)
    artifacts: ArtifactsConfig = Field(default_factory=ArtifactsConfig)
    telemetry: TelemetryConfig = Field(default_factory=TelemetryConfig)
    servers: list[ServerConfig] = Field(min_length=1)

    @model_validator(mode="after")
    def validate_state_persistence(self) -> "AdapterConfig":
        """Validate persistence/storage cross-field constraints.

        Returns:
            The validated config instance.

        Raises:
            ValueError: When Redis host is missing or lock mode conflicts.
        """
        if self.state_persistence.type == "redis" and not self.state_persistence.redis.host:
            raise ValueError("state_persistence.redis.host is required when state_persistence.type='redis'")
        if self.storage.lock_mode == "redis" and self.state_persistence.type != "redis":
            raise ValueError("storage.lock_mode='redis' requires state_persistence.type='redis'")
        if self.state_persistence.disk.local_path is None:
            default_path = Path(self.storage.root) / "state" / "adapter_state.sqlite3"
            self.state_persistence.disk.local_path = str(default_path)
        return self

    @model_validator(mode="after")
    def validate_unique_servers(self) -> "AdapterConfig":
        """Ensure all server IDs and mount paths are unique.

        Returns:
            The validated config instance.

        Raises:
            ValueError: On duplicate server IDs, mount paths, or invalid
                legacy server ID references.
        """
        ids: set[str] = set()
        mounts: set[str] = set()
        for server in self.servers:
            if server.id in ids:
                raise ValueError(f"Duplicate servers[].id: {server.id}")
            ids.add(server.id)
            if server.mount_path in mounts:
                raise ValueError(f"Duplicate servers[].mount_path: {server.mount_path}")
            mounts.add(server.mount_path)
        legacy_server_id = self.state_persistence.reconciliation.legacy_server_id
        if legacy_server_id is not None and legacy_server_id not in ids:
            raise ValueError("state_persistence.reconciliation.legacy_server_id must match one configured servers[].id")
        return self

validate_state_persistence

validate_state_persistence()

Validate persistence/storage cross-field constraints.

Returns:

TypeDescription
'AdapterConfig'

The validated config instance.

Raises:

TypeDescription
ValueError

When Redis host is missing or lock mode conflicts.

Source code in src/remote_mcp_adapter/config/schemas/root.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@model_validator(mode="after")
def validate_state_persistence(self) -> "AdapterConfig":
    """Validate persistence/storage cross-field constraints.

    Returns:
        The validated config instance.

    Raises:
        ValueError: When Redis host is missing or lock mode conflicts.
    """
    if self.state_persistence.type == "redis" and not self.state_persistence.redis.host:
        raise ValueError("state_persistence.redis.host is required when state_persistence.type='redis'")
    if self.storage.lock_mode == "redis" and self.state_persistence.type != "redis":
        raise ValueError("storage.lock_mode='redis' requires state_persistence.type='redis'")
    if self.state_persistence.disk.local_path is None:
        default_path = Path(self.storage.root) / "state" / "adapter_state.sqlite3"
        self.state_persistence.disk.local_path = str(default_path)
    return self

validate_unique_servers

validate_unique_servers()

Ensure all server IDs and mount paths are unique.

Returns:

TypeDescription
'AdapterConfig'

The validated config instance.

Raises:

TypeDescription
ValueError

On duplicate server IDs, mount paths, or invalid legacy server ID references.

Source code in src/remote_mcp_adapter/config/schemas/root.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@model_validator(mode="after")
def validate_unique_servers(self) -> "AdapterConfig":
    """Ensure all server IDs and mount paths are unique.

    Returns:
        The validated config instance.

    Raises:
        ValueError: On duplicate server IDs, mount paths, or invalid
            legacy server ID references.
    """
    ids: set[str] = set()
    mounts: set[str] = set()
    for server in self.servers:
        if server.id in ids:
            raise ValueError(f"Duplicate servers[].id: {server.id}")
        ids.add(server.id)
        if server.mount_path in mounts:
            raise ValueError(f"Duplicate servers[].mount_path: {server.mount_path}")
        mounts.add(server.mount_path)
    legacy_server_id = self.state_persistence.reconciliation.legacy_server_id
    if legacy_server_id is not None and legacy_server_id not in ids:
        raise ValueError("state_persistence.reconciliation.legacy_server_id must match one configured servers[].id")
    return self

config_to_dict

config_to_dict(config)

Return a plain dict representation for debugging/logging.

Parameters:

NameTypeDescriptionDefault
configAdapterConfig

Top-level adapter configuration.

required

Returns:

TypeDescription
dict[str, Any]

Dict-serialized copy of the config.

Source code in src/remote_mcp_adapter/config/schemas/root.py
105
106
107
108
109
110
111
112
113
114
def config_to_dict(config: AdapterConfig) -> dict[str, Any]:
    """Return a plain dict representation for debugging/logging.

    Args:
        config: Top-level adapter configuration.

    Returns:
        Dict-serialized copy of the config.
    """
    return config.model_dump(mode="python")

resolve_storage_lock_mode

resolve_storage_lock_mode(config)

Resolve runtime lock mode, including auto behavior.

Parameters:

NameTypeDescriptionDefault
configAdapterConfig

Top-level adapter configuration.

required

Returns:

TypeDescription
EffectiveStorageLockMode

Effective storage lock mode string.

Source code in src/remote_mcp_adapter/config/schemas/root.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def resolve_storage_lock_mode(config: AdapterConfig) -> EffectiveStorageLockMode:
    """Resolve runtime lock mode, including ``auto`` behavior.

    Args:
        config: Top-level adapter configuration.

    Returns:
        Effective storage lock mode string.
    """
    if config.storage.lock_mode == "auto":
        if config.state_persistence.type == "redis":
            return "redis"
        return "file"
    return config.storage.lock_mode

resolve_write_policy_lock_mode

resolve_write_policy_lock_mode(config)

Resolve lock mode for current local write-policy implementation.

Parameters:

NameTypeDescriptionDefault
configAdapterConfig

Top-level adapter configuration.

required

Returns:

TypeDescription
WritePolicyLockMode

Write policy lock mode string.

Source code in src/remote_mcp_adapter/config/schemas/root.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
def resolve_write_policy_lock_mode(config: AdapterConfig) -> WritePolicyLockMode:
    """Resolve lock mode for current local write-policy implementation.

    Args:
        config: Top-level adapter configuration.

    Returns:
        Write policy lock mode string.
    """
    return resolve_storage_lock_mode(config)

Next steps

  • Previous topic: API Reference - how this section is split and how to read it.
  • Next: Internal API - implementation-oriented modules for contributors.