Portal Community

What Makes a Valid Custom Server

Any HTTP, SSE, or StdIo process that implements the Model Context Protocol specification (tools/list + tools/call endpoints) can be registered as a custom MCP server. The server must expose a tools/list endpoint so the registry can discover its tool catalog.

Registering via API

POST /api/mcp/servers
{
  "serverId":      "acme-underwriting-ai",
  "displayName":   "Acme Underwriting AI Server",
  "transportType": "Http",
  "endpointUrl":   "https://underwriting.acme-internal.com/mcp",
  "credentialId":  301,
  "timeoutSeconds": 45
}

Registering in Code (DI)

// In your server registration module (e.g., Startup.cs / Program.cs):
services.AddMCPServer(options =>
{
    options.ServerId      = "acme-underwriting-ai";
    options.DisplayName   = "Acme Underwriting AI Server";
    options.TransportType = MCPTransportType.Http;
    options.EndpointUrl   = "https://underwriting.acme-internal.com/mcp";
    options.CredentialId  = 301;   // resolved via ICredentialResolver at call time
    options.TimeoutSeconds = 45;
});

IMCPServerDefinition — Custom Implementation

For advanced scenarios where server metadata is sourced from a database or configuration service, implement IMCPServerDefinition directly:

public class DatabaseBackedMCPServerDefinition : IMCPServerDefinition
{
    public string ServerId      { get; init; } = default!;
    public string DisplayName   { get; init; } = default!;
    public MCPTransportType TransportType { get; init; }
    public string EndpointUrl   { get; init; } = default!;
    public int CredentialId     { get; init; }
    public int TimeoutSeconds   { get; init; }
}

// Registration from database at startup:
public class MCPServerLoader : IHostedService
{
    private readonly IMCPServerRegistry _registry;
    private readonly IServerConfigRepository _config;

    public async Task StartAsync(CancellationToken ct)
    {
        var servers = await _config.GetAllActiveServersAsync(ct);
        foreach (var s in servers)
            _registry.Register(new DatabaseBackedMCPServerDefinition
            {
                ServerId      = s.ServerId,
                DisplayName   = s.DisplayName,
                TransportType = Enum.Parse<MCPTransportType>(s.TransportType),
                EndpointUrl   = s.EndpointUrl,
                CredentialId  = s.CredentialId,
                TimeoutSeconds = s.TimeoutSeconds
            });
    }

    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}

StdIo Server Registration

{
  "serverId":      "local-nlp-server",
  "displayName":   "Local NLP Processor",
  "transportType": "StdIo",
  "endpointUrl":   "/opt/bfai/nlp-server/run.sh",
  "credentialId":  0,
  "timeoutSeconds": 30
}

For StdIo servers, endpointUrl is the path to the executable. The engine launches it as a child process, communicates via stdin/stdout using the MCP JSON-RPC protocol, and terminates the process when the tool call completes.

Health check before registration: After registering a custom server, call GET /api/mcp/servers/{serverId}/health to verify reachability and confirm the tool catalog loads correctly. A toolCount: 0 response indicates the server is reachable but tools/list returned no tools — check the server implementation.
Tenant scoping for custom servers: By default, a registered custom server is available to all tenants on the platform. To restrict a server to specific tenants, set allowedTenantIds in the registration payload. Workflow nodes in other tenants will receive a "server not found" error at runtime.