Skip to content

Commit d016f02

Browse files
authored
feat: add configurable MCP health check method with ping fallback (#1433)
## Add configurable MCP health check method This PR adds a new `is_ping_available` configuration option for MCP clients, allowing users to specify whether a server supports the lightweight ping method for health checks. When disabled, Bifrost will use the heavier but more universally supported `listTools` method instead. ## Changes - Added `is_ping_available` boolean field to MCP client configuration (defaults to true) - Modified health monitor to use either ping or listTools based on this setting - Added database migration to support the new column - Updated UI to include a toggle for this setting in MCP client forms - Added example HTTP server without ping support to demonstrate the feature - Updated documentation with details on health check methods and configuration options ## Type of change - [x] Feature - [x] Documentation ## Affected areas - [x] Core (Go) - [x] Transports (HTTP) - [x] UI (Next.js) - [x] Docs ## How to test 1. Start Bifrost and add a new MCP server with `is_ping_available: false` 2. Run the example no-ping server: ```sh cd examples/mcps/http-no-ping-server go run main.go ``` 3. Connect to the server in Bifrost and verify health checks work despite ping being unavailable 4. Toggle the setting in the UI and observe health check behavior changes ## Screenshots/Recordings ![Ping Available Toggle](/media/ui-mcp-ping-available.png) ## Breaking changes - [x] No ## Related issues Addresses the need for health checks with MCP servers that don't implement the optional ping method. ## Security considerations No security implications - this is a configuration option for health check behavior. ## Checklist - [x] I added/updated tests where appropriate - [x] I updated documentation where needed - [x] I verified builds succeed (Go and UI)
2 parents 8ac0990 + 91f316d commit d016f02

File tree

26 files changed

+715
-56
lines changed

26 files changed

+715
-56
lines changed

core/changelog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- feat: add is_ping_available configuration for MCP health checks
12
- fix: allow flat $defs and propertyOrdering in jsonschema and forward it to Gemini
23
- feat: adds beta feature for Anthropic
34
- deferLoading
@@ -6,6 +7,6 @@
67
- inputExamples
78
- add IsError to tool_result content
89
- add "none" option to ToolChoice.Type
9-
10+
1011
- fix: implement structured output handling for Anthropic models on Vertex where the beta structured-output header is unsupported
1112
- fix: duplicate error when adding first MCP server

core/mcp/clientmanager.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,16 +189,29 @@ func (m *MCPManager) EditClient(id string, updatedConfig schemas.MCPClientConfig
189189
return fmt.Errorf("invalid MCP client configuration: %w", err)
190190
}
191191

192+
// Check if is_ping_available changed
193+
isPingAvailableChanged := client.ExecutionConfig.IsPingAvailable != updatedConfig.IsPingAvailable
194+
192195
// Update the client's execution config with new tool filters
193196
config := client.ExecutionConfig
194197
config.Name = updatedConfig.Name
195198
config.IsCodeModeClient = updatedConfig.IsCodeModeClient
196199
config.Headers = updatedConfig.Headers
197200
config.ToolsToExecute = updatedConfig.ToolsToExecute
198201
config.ToolsToAutoExecute = updatedConfig.ToolsToAutoExecute
202+
config.IsPingAvailable = updatedConfig.IsPingAvailable
199203

200204
// Store the updated config
201205
client.ExecutionConfig = config
206+
207+
// If is_ping_available changed, update the health monitor
208+
if isPingAvailableChanged {
209+
// Stop and restart the health monitor with the new is_ping_available setting
210+
m.healthMonitorManager.StopMonitoring(id)
211+
monitor := NewClientHealthMonitor(m, id, DefaultHealthCheckInterval, config.IsPingAvailable)
212+
m.healthMonitorManager.StartMonitoring(monitor)
213+
}
214+
202215
return nil
203216
}
204217

@@ -445,7 +458,7 @@ func (m *MCPManager) connectToMCPClient(config schemas.MCPClientConfig) error {
445458
}
446459

447460
// Start health monitoring for the client
448-
monitor := NewClientHealthMonitor(m, config.ID, DefaultHealthCheckInterval)
461+
monitor := NewClientHealthMonitor(m, config.ID, DefaultHealthCheckInterval, config.IsPingAvailable)
449462
m.healthMonitorManager.StartMonitoring(monitor)
450463

451464
return nil

core/mcp/health_monitor.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sync"
77
"time"
88

9+
"github.com/mark3labs/mcp-go/mcp"
910
"github.com/maximhq/bifrost/core/schemas"
1011
)
1112

@@ -29,13 +30,15 @@ type ClientHealthMonitor struct {
2930
cancel context.CancelFunc
3031
isMonitoring bool
3132
consecutiveFailures int
33+
isPingAvailable bool // Whether the MCP server supports ping for health checks
3234
}
3335

3436
// NewClientHealthMonitor creates a new health monitor for an MCP client
3537
func NewClientHealthMonitor(
3638
manager *MCPManager,
3739
clientID string,
3840
interval time.Duration,
41+
isPingAvailable bool,
3942
) *ClientHealthMonitor {
4043
if interval == 0 {
4144
interval = DefaultHealthCheckInterval
@@ -49,6 +52,7 @@ func NewClientHealthMonitor(
4952
maxConsecutiveFailures: MaxConsecutiveFailures,
5053
isMonitoring: false,
5154
consecutiveFailures: 0,
55+
isPingAvailable: isPingAvailable,
5256
}
5357
}
5458

@@ -119,11 +123,26 @@ func (chm *ClientHealthMonitor) performHealthCheck() {
119123
return
120124
}
121125

122-
// Perform ping with timeout
126+
// Perform health check with timeout
123127
ctx, cancel := context.WithTimeout(context.Background(), chm.timeout)
124128
defer cancel()
125129

126-
err := clientState.Conn.Ping(ctx)
130+
var err error
131+
if chm.isPingAvailable {
132+
// Use lightweight ping for health check
133+
err = clientState.Conn.Ping(ctx)
134+
} else {
135+
// Fall back to listTools for servers that don't support ping
136+
listRequest := mcp.ListToolsRequest{
137+
PaginatedRequest: mcp.PaginatedRequest{
138+
Request: mcp.Request{
139+
Method: string(mcp.MethodToolsList),
140+
},
141+
},
142+
}
143+
_, err = clientState.Conn.ListTools(ctx, listRequest)
144+
}
145+
127146
if err != nil {
128147
chm.incrementFailures()
129148

core/schemas/mcp.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ type MCPClientConfig struct {
6767
// - nil/omitted => treated as [] (no tools)
6868
// - ["tool1", "tool2"] => auto-execute only the specified tools
6969
// Note: If a tool is in ToolsToAutoExecute but not in ToolsToExecute, it will be skipped.
70-
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
70+
IsPingAvailable bool `json:"is_ping_available"` // Whether the MCP server supports ping for health checks (default: true). If false, uses listTools for health checks.
71+
ConfigHash string `json:"-"` // Config hash for reconciliation (not serialized)
7172
}
7273

7374
// NewMCPClientConfigFromMap creates a new MCP client config from a map[string]any.

docs/mcp/connecting-to-servers.mdx

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ Configure MCP clients in your `config.json`:
223223
{
224224
"name": "filesystem",
225225
"connection_type": "stdio",
226+
"is_ping_available": true,
226227
"stdio_config": {
227228
"command": "npx",
228229
"args": ["-y", "@anthropic/mcp-filesystem"],
@@ -234,12 +235,14 @@ Configure MCP clients in your `config.json`:
234235
"name": "web_search",
235236
"connection_type": "http",
236237
"connection_string": "env.WEB_SEARCH_MCP_URL",
238+
"is_ping_available": false,
237239
"tools_to_execute": ["search", "fetch_url"]
238240
},
239241
{
240242
"name": "database",
241243
"connection_type": "sse",
242244
"connection_string": "https://db-mcp.example.com/sse",
245+
"is_ping_available": true,
243246
"tools_to_execute": []
244247
}
245248
]
@@ -273,8 +276,9 @@ func main() {
273276
mcpConfig := &schemas.MCPConfig{
274277
ClientConfigs: []schemas.MCPClientConfig{
275278
{
276-
Name: "filesystem",
277-
ConnectionType: schemas.MCPConnectionTypeSTDIO,
279+
Name: "filesystem",
280+
ConnectionType: schemas.MCPConnectionTypeSTDIO,
281+
IsPingAvailable: true, // Use lightweight ping for health checks
278282
StdioConfig: &schemas.MCPStdioConfig{
279283
Command: "npx",
280284
Args: []string{"-y", "@anthropic/mcp-filesystem"},
@@ -286,6 +290,7 @@ func main() {
286290
Name: "web_search",
287291
ConnectionType: schemas.MCPConnectionTypeHTTP,
288292
ConnectionString: bifrost.Ptr("http://localhost:3001/mcp"),
293+
IsPingAvailable: false, // Use listTools for health checks
289294
ToolsToExecute: []string{"search", "fetch_url"},
290295
},
291296
},
@@ -451,17 +456,83 @@ err = client.EditMCPClientTools("filesystem", []string{"read_file", "list_direct
451456

452457
## Health Monitoring
453458

454-
Bifrost automatically monitors MCP client health:
459+
Bifrost automatically monitors MCP client health with periodic checks every 10 seconds by default.
455460

456-
- **Periodic Pings**: Every 10 seconds by default
457-
- **Auto-Detection**: Disconnections are detected automatically
458-
- **State Updates**: Client state changes are reflected in API responses
461+
### Health Check Methods
462+
463+
By default, Bifrost uses the lightweight **ping method** for health checks. However, you can configure the health check method based on your MCP server's capabilities:
464+
465+
| Method | When to Use | Overhead | Fallback |
466+
|--------|------------|----------|----------|
467+
| **Ping** (default) | Server supports MCP ping protocol | Minimal | Best for most servers |
468+
| **ListTools** | Server doesn't support ping, or you need heavier checks | Higher | More resource-intensive |
469+
470+
### Configuring Health Check Method
471+
472+
You can toggle the `is_ping_available` setting for each client:
473+
474+
#### Via Web UI
475+
476+
1. Navigate to **MCP Gateway** and select a server
477+
2. In the configuration panel, toggle **"Ping Available for Health Check"**
478+
3. Enable: Uses lightweight ping for health checks
479+
4. Disable: Uses listTools method for health checks instead
480+
481+
<Frame>
482+
<img src="/media/ui-mcp-ping-available.png" alt="Ping Available Toggle" />
483+
</Frame>
484+
485+
#### Via API
486+
487+
```bash
488+
curl -X PUT http://localhost:8080/api/mcp/client/{id} \
489+
-H "Content-Type: application/json" \
490+
-d '{
491+
"name": "my_server",
492+
"is_ping_available": false
493+
}'
494+
```
495+
496+
#### Via config.json
497+
498+
```json
499+
{
500+
"mcp": {
501+
"client_configs": [
502+
{
503+
"name": "filesystem",
504+
"connection_type": "stdio",
505+
"is_ping_available": true,
506+
"stdio_config": {
507+
"command": "npx",
508+
"args": ["-y", "@anthropic/mcp-filesystem"]
509+
}
510+
}
511+
]
512+
}
513+
}
514+
```
515+
516+
#### Via Go SDK
517+
518+
```go
519+
err := client.EditMCPClient(context.Background(), schemas.MCPClientConfig{
520+
ID: "filesystem",
521+
Name: "filesystem",
522+
IsPingAvailable: false, // Use listTools instead of ping
523+
ToolsToExecute: []string{"*"},
524+
})
525+
```
526+
527+
### Health Check Behavior
459528

460529
When a client disconnects:
461530
1. State changes to `disconnected`
462531
2. Tools from that client become unavailable
463532
3. You can reconnect via API or UI
464533

534+
**Note:** Changing `is_ping_available` takes effect immediately without requiring a client reconnection.
535+
465536
---
466537

467538
## Naming Conventions
2.01 MB
Loading

docs/openapi/schemas/management/mcp.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ MCPClientConfig:
3838
type: string
3939
is_code_mode_client:
4040
type: boolean
41+
is_ping_available:
42+
type: boolean
43+
default: true
44+
description: |
45+
Whether the MCP server supports ping for health checks.
46+
If true, uses lightweight ping method for health checks.
47+
If false, uses listTools method for health checks instead.
4148
connection_type:
4249
$ref: '#/MCPConnectionType'
4350
connection_string:

0 commit comments

Comments
 (0)