How MCP Servers Are Integrated Per Repository in Background-Agents
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, 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, the create function posts new server definitions to the API:
// 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 handles the persistence:
// 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 filters the global server list by checking for scope intersections:
// 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, the mcpServerLookup object is passed as part of the SandboxLifecycleConfig. The router injects this dependency when spawning sessions:
// 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 (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
McpServerLookupservice 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, 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 (lines 965-1044) handles the final resolution, executing npx commands for local servers or HTTP requests for remote URLs to instantiate the tools.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →