# How OpenMAIC Handles Capability Discovery for External Providers: Architecture and Code Walkthrough

> Explore OpenMAIC's architecture and code to understand how it handles capability discovery for external providers by loading configurations and dynamically registering tools.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: architecture
- Published: 2026-09-12

---

**OpenMAIC discovers available external providers by loading YAML configuration and environment variables at startup, resolving concrete capability objects only for properly configured and non-disabled services, and dynamically registering corresponding tools in the agent runtime.**

The OpenMAIC repository implements a robust capability discovery system that determines which external services—such as LLMs, TTS, ASR, image generation, and web search—are operational at deployment time. This process ensures that the agent runtime exposes only functional tools by validating provider configurations against server-side credentials and operator-level disable flags.

## Configuration Loading and Environment Resolution

OpenMAIC initiates capability discovery by ingesting provider settings from [`server-providers.yml`](https://github.com/THU-MAIC/OpenMAIC/blob/main/server-providers.yml) and associated environment variables. The system maps specific environment variable prefixes to provider identifiers, enabling seamless integration of API keys and base URLs without code changes.

### YAML Structure and Environment Mapping

The configuration loader in [`lib/server/provider-config.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/provider-config.ts) defines the `ServerConfig` type and environment mapping constants such as `LLM_ENV_MAP` and `WEB_SEARCH_ENV_MAP`. These maps translate environment variables like `OPENAI_API_KEY` into provider IDs (e.g., `openai`) and `TAVILY_API_KEY` into `tavily`. When the server starts, it parses the YAML file and overlays environment variables to construct a complete server-side configuration object.

```typescript
// lib/server/provider-config.ts defines the mapping structure
const WEB_SEARCH_ENV_MAP = {
  'TAVILY_API_KEY': 'tavily',
  'SERPER_API_KEY': 'serper',
  // Additional providers...
} as const;

```

### The ServerConfig Type

The `ServerConfig` interface aggregates provider-specific blocks, including sections for `web-search`, `tts`, `image`, and `video`. Each block contains the provider ID, API key, optional base URL, and model lists. This centralized configuration serves as the source of truth for all subsequent capability resolution steps.

## Capability Resolution and Validation

After loading configurations, OpenMAIC executes capability resolvers that validate whether a specific service is usable. These resolvers check for the presence of required credentials and respect global disable switches before instantiating capability objects.

### The Web Search Resolver Pattern

The `resolveWebSearchCapability` function in [`lib/server/agent-runtime/web-search.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/agent-runtime/web-search.ts) exemplifies the resolution pattern. It inspects the loaded configuration, verifies that the provider is not force-disabled, and returns a capability object containing the validated API key and endpoint. If validation fails or the provider is disabled, the resolver returns `null`, signaling that the capability is unavailable.

```typescript
import { resolveWebSearchCapability } from '@/lib/server/agent-runtime/web-search';

const capability = resolveWebSearchCapability();
if (capability) {
  // Register the tool – the model can now call `web_search`
  registerTool(buildWebSearchTool(capability));
}

```

### Force-Off Controls for Operators

Operators can globally disable specific providers across all users regardless of individual settings. The `DISABLE_ENV_MAPS` in [`lib/server/provider-config.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/provider-config.ts) processes environment variables following the pattern `<CAPABILITY>_<PREFIX>_ENABLED=false`. For example, setting `TTS_ELEVENLABS_ENABLED=false` forces the TTS resolver to return `null`, preventing tool registration even if valid API keys exist in the configuration.

```typescript
// lib/server/provider-config.ts (lines 35-38 concept)
const isForceDisabled = (providerId: string) => {
  return process.env[`TTS_${providerId.toUpperCase()}_ENABLED`] === 'false';
};

```

## Tool Registration in the Agent Runtime

The agent runtime, specifically in [`lib/server/agent-runtime/runner.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/agent-runtime/runner.ts), imports capability resolvers and conditionally registers tools based on their return values. A tool is added to the agent's toolset only when its corresponding resolver returns a non-null capability object. This gating mechanism guarantees that models cannot invoke unavailable services, as the tool simply does not exist in the runtime context when its capability is null.

```typescript
// lib/server/agent-runtime/runner.ts
const search = resolveWebSearchCapability();
if (search) {
  tools.web_search = buildWebSearchTool(search);
}
// If search is null, the web_search tool remains undefined

```

## Server-Managed vs. Client-Provided Credentials

OpenMAIC distinguishes between server-managed providers and unmanaged providers to maintain security boundaries. When a provider is configured server-side (server-managed), the `resolveApiKey` function in [`lib/server/provider-config.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/provider-config.ts) returns only the server-side credential, ignoring any client-supplied key. For unmanaged providers lacking server configuration, the system accepts client-provided keys, enabling per-session overrides without exposing server credentials.

```typescript
import { resolveApiKey } from '@/lib/server/provider-config';

// Server key takes precedence for managed providers
const apiKey = resolveApiKey('openai', clientSuppliedKey);
// Returns server env key if present, otherwise falls back to client key

```

This credential isolation is verified in the unit tests ([`provider-config.test.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/provider-config.test.ts), lines 25-33), ensuring that server credentials never leak to client contexts while still allowing flexibility for user-provided configurations.

## Summary

- **Configuration Loading**: OpenMAIC reads [`server-providers.yml`](https://github.com/THU-MAIC/OpenMAIC/blob/main/server-providers.yml) and environment variables via mappings in [`lib/server/provider-config.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/provider-config.ts) to build the `ServerConfig` object.
- **Capability Resolution**: Resolvers like `resolveWebSearchCapability` validate configurations and check force-disable flags, returning `null` for unavailable services.
- **Dynamic Tool Registration**: The agent runtime in [`runner.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/runner.ts) registers tools only when resolvers return valid capability objects, ensuring the model sees only functional services.
- **Operator Controls**: Environment variables following the `<CAPABILITY>_<PROVIDER>_ENABLED=false` pattern allow global disabling of specific providers.
- **Credential Precedence**: Server-managed API keys always override client inputs, while unmanaged providers accept client keys for per-session flexibility.

## Frequently Asked Questions

### How does OpenMAIC determine if an external provider is available?

OpenMAIC determines availability through a three-stage process: first, it loads configuration from [`server-providers.yml`](https://github.com/THU-MAIC/OpenMAIC/blob/main/server-providers.yml) and environment variables; second, capability resolvers validate credentials and check disable flags; finally, if a resolver returns a non-null capability object, the provider is considered available and its tool is registered in the runtime. Resolvers return `null` when configurations are missing or disabled.

### Can operators disable specific providers across all users?

Yes. Operators can set environment variables using the pattern `<CAPABILITY>_<PROVIDER>_ENABLED=false` (e.g., `WEB_SEARCH_TAVILY_ENABLED=false` or `TTS_ELEVENLABS_ENABLED=false`). These flags are processed by the `DISABLE_ENV_MAPS` logic in [`lib/server/provider-config.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/provider-config.ts), forcing resolvers to return `null` and preventing tool registration regardless of individual user settings or valid API keys.

### What happens when a capability resolver returns null?

When a resolver returns `null`, the corresponding tool is not registered in the agent runtime. In [`lib/server/agent-runtime/runner.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/agent-runtime/runner.ts), the code checks the resolver output before calling `registerTool()`; if the capability is `null`, the tool property remains undefined. Consequently, the language model cannot generate function calls for that service because the tool does not exist in the available toolset.

### How does OpenMAIC handle API key precedence between server and client?

The `resolveApiKey` function in [`lib/server/provider-config.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/provider-config.ts) implements a precedence hierarchy: for server-managed providers (those with credentials in server environment variables), the server key is authoritative and client-supplied keys are ignored. For unmanaged providers lacking server configuration, the system accepts client-provided keys. This ensures credentials remain server-side while allowing per-session overrides for unsupported providers.