# How MCP Servers Are Integrated Per Repository in Background-Agents

> Learn how Background-Agents integrates MCP servers per repository. Discover how scoped server definitions in D1 are resolved and configured for your runtime.

- Repository: [Cole Murray/background-agents](https://github.com/ColeMurray/background-agents)
- Tags: internals
- Published: 2026-07-13

---

**Background-Agents enables repository-specific MCP server integration by storing scoped server definitions in D1, resolving them via a lookup service during sandbox initialization, and injecting the configurations into the runtime where they are resolved into usable tools.**

The ColeMurray/background-agents platform allows each codebase to define its own Managed Compute Platform (MCP) servers, ensuring that agents running in sandboxed environments automatically receive access to custom CLI tools, private packages, or remote APIs based on the repositories involved in a session. This MCP server integration per repository works through three distinct architectural layers: storage and validation, session-time lookup, and runtime resolution.

## Storage and Validation of Per-Repository MCP Servers

### Database Schema and Scope Definition

In [`packages/control-plane/src/db/mcp-servers.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/db/mcp-servers.ts), each MCP server record contains a **scope** array that defines which repositories the server applies to. Each scope entry specifies `repoOwner` and `repoName` pairs, creating a direct link between the server configuration and specific codebases. The validation logic enforces that local servers must provide a `command` field while remote servers must supply a `URL`, ensuring configuration integrity before storage in the D1 database.

### Creating MCP Servers via the Web Interface

The web frontend uses a custom hook to manage server creation. In [`packages/web/src/hooks/use-mcp-servers.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/hooks/use-mcp-servers.ts), the `create` function posts new server definitions to the API:

```tsx
// packages/web/src/hooks/use-mcp-servers.ts
const { data, mutate } = useSWR<McpServerMetadata[]>(session ? "/api/mcp-servers" : null);
const create = async (payload: NewMcpServer) => {
  const res = await fetch("/api/mcp-servers", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  if (!res.ok) throw new Error((await res.json()).error);
  await mutate();               // refresh list
};

```

The API endpoint in [`packages/web/src/app/api/mcp-servers/route.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/web/src/app/api/mcp-servers/route.ts) handles the persistence:

```ts
// packages/web/src/app/api/mcp-servers/route.ts
export async function POST(req: Request) {
  const { name, command, url, scopes } = await req.json();
  const server = await db.insertMcpServer({ name, command, url, scopes });
  return NextResponse.json(server, { status: 201 });
}

```

## Session-Time Lookup and Configuration Injection

### The McpServerLookup Service

When a sandbox session initializes, the control-plane must resolve which MCP servers apply to the specific repositories in that session. The `McpServerLookup` class in [`packages/control-plane/src/db/mcp-servers.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/db/mcp-servers.ts) filters the global server list by checking for scope intersections:

```typescript
// packages/control-plane/src/db/mcp-servers.ts
export class McpServerLookup implements McpServerLookup {
  async getDecryptedForSession(repos) {
    const rows = await db.queryMcpServers();                // all rows
    return rows
      .filter(row => row.scopes.some(s => repos.some(r => s.repoOwner === r.repoOwner && s.repoName === r.repoName)))
      .map(decryptMcpRow);
  }
}

```

### Injecting into the Sandbox Lifecycle

The lifecycle manager receives the lookup instance during initialization. In [`packages/control-plane/src/sandbox/lifecycle/manager.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/sandbox/lifecycle/manager.ts), the `mcpServerLookup` object is passed as part of the `SandboxLifecycleConfig`. The router injects this dependency when spawning sessions:

```typescript
// packages/control-plane/src/router.ts
router.get("/sessions/:id", async (req) => {
  const session = await getSession(req.params.id);
  const mcpLookup = new McpServerLookup();
  const manager = new SandboxLifecycleManager(provider, storage, broadcaster, wsManager, alarmScheduler, idGen, {
    controlPlaneUrl,
    model,
    sessionId: session.id,
    mcpServerLookup: mcpLookup,
  });
  await manager.spawnSandbox();
});

```

## Runtime Resolution and Tool Integration

### Resolving MCP Servers in the Sandbox

Inside the sandbox runtime, the injected configuration becomes available to the agent environment. The `resolve_mcp_servers` function in [`packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py`](https://github.com/ColeMurray/background-agents/blob/main/packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py) (lines 965-1044) processes the `McpServerConfig` list:

- For **local servers**: Executes the declared command via `npx`
- For **remote servers**: Contacts the specified URL to fetch required packages

This resolution turns injected configurations into active tools within the sandbox environment.

### Repository-Specific Capabilities

By resolving MCP servers at runtime based on the session's repository scope, Background-Agents ensures that agents only access tools relevant to their current context. A repository configured with custom CLI tools receives those capabilities automatically, while other sessions remain unaffected by unrelated server definitions.

## Summary

- **Storage Layer**: MCP server definitions live in D1 with scoped repository lists, validated to ensure local servers have commands and remote servers have URLs.
- **Lookup Layer**: The `McpServerLookup` service filters servers by repository scope during session initialization, returning only relevant configurations.
- **Runtime Layer**: The sandbox runtime resolves these configurations into active tools via `resolve_mcp_servers`, executing local commands or fetching remote packages as needed.
- **Integration Flow**: Web UI → API Route → Database → Lifecycle Manager → Sandbox Runtime creates a seamless per-repository tooling pipeline.

## Frequently Asked Questions

### How does Background-Agents determine which MCP servers to load for a specific session?

The system uses the `McpServerLookup` class to compare the session's member repositories against stored server scopes. Only servers with matching `repoOwner`/`repoName` pairs in their scope arrays are decrypted and injected into the sandbox configuration via the `SandboxLifecycleConfig`.

### What validation ensures MCP server configurations are correct?

According to the source code in [`packages/control-plane/src/db/mcp-servers.ts`](https://github.com/ColeMurray/background-agents/blob/main/packages/control-plane/src/db/mcp-servers.ts), validation rules require that local servers specify a `command` field while remote servers must provide a `URL`. This prevents malformed configurations from reaching the runtime environment.

### Can a single MCP server be shared across multiple repositories?

Yes. The `scope` field in each MCP server record is an array that can contain multiple `repoOwner`/`repoName` pairs, allowing a single server definition to apply to many repositories simultaneously.

### Where does the actual tool resolution happen in the runtime?

The `resolve_mcp_servers` function in [`packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py`](https://github.com/ColeMurray/background-agents/blob/main/packages/sandbox-runtime/src/sandbox_runtime/entrypoint.py) (lines 965-1044) handles the final resolution, executing `npx` commands for local servers or HTTP requests for remote URLs to instantiate the tools.