Skip to content

Commit 104bdde

Browse files
committed
added mcp_disconnect function and improve error handling
1 parent 7fe98e3 commit 104bdde

File tree

5 files changed

+477
-14
lines changed

5 files changed

+477
-14
lines changed

API.md

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,6 @@ SELECT mcp_connect(
6363
);
6464
```
6565

66-
**Response:**
67-
```json
68-
{"status": "connected", "transport": "streamable_http"}
69-
```
70-
7166
See [USAGE.md](USAGE.md) for more examples of using custom headers.
7267

7368
---
@@ -423,10 +418,31 @@ SELECT mcp_connect('http://localhost:8931/sse', NULL, 1);
423418

424419
## Error Handling
425420

426-
All functions return JSON with error information on failure:
421+
The sqlite-mcp extension has two types of error handling:
422+
423+
1. **JSON Functions** (ending with `_json`): Return JSON with error information
424+
2. **Non-JSON Functions**: Return error strings directly (extracted from JSON)
425+
3. **Virtual Tables**: Return no rows on error, use scalar functions to check status
426+
427+
### mcp_connect()
428+
429+
Returns `NULL` on success, or an error string on failure:
430+
431+
```sql
432+
-- Check if connection succeeded
433+
SELECT mcp_connect('http://localhost:8000/mcp') IS NULL;
434+
-- Returns 1 (true) on success, 0 (false) on failure
435+
436+
-- Get error message if connection failed
437+
SELECT mcp_connect('http://invalid:8000/mcp');
438+
-- Returns: "Failed to connect to MCP server: ..."
439+
```
440+
441+
### JSON Functions: mcp_list_tools_json() and mcp_call_tool_json()
442+
443+
Always return JSON - either with results or error information:
427444

428445
```json
429-
{"error": "Connection failed: timeout"}
430446
{"error": "Not connected. Call mcp_connect() first"}
431447
{"error": "Tool not found: invalid_tool"}
432448
{"error": "Invalid JSON arguments"}
@@ -442,4 +458,74 @@ SELECT
442458
ELSE 'Success'
443459
END
444460
FROM (SELECT mcp_call_tool_json('test', '{}') as result);
461+
```
462+
463+
### Virtual Tables: mcp_list_tools, mcp_call_tool, mcp_list_tools_respond, mcp_call_tool_respond
464+
465+
Virtual tables return no rows on error. To check for errors, use the corresponding JSON functions:
466+
467+
```sql
468+
-- Check if server is connected before querying virtual table
469+
SELECT
470+
CASE
471+
WHEN json_extract(mcp_list_tools_json(), '$.error') IS NOT NULL
472+
THEN 'Error: Cannot query virtual table - ' || json_extract(mcp_list_tools_json(), '$.error')
473+
ELSE 'OK to query virtual table'
474+
END;
475+
476+
-- Query virtual table only if no error
477+
SELECT name, description FROM mcp_list_tools WHERE name LIKE 'browser_%';
478+
```
479+
480+
### Common Error Messages
481+
482+
All functions may return these error types:
483+
484+
- **Connection errors**: `"Not connected. Call mcp_connect() first"`
485+
- **Server errors**: `"Failed to connect to MCP server: ..."`
486+
- **Tool errors**: `"Tool not found: tool_name"`
487+
- **Argument errors**: `"Invalid JSON arguments"`
488+
- **Transport errors**: `"Transport error: ..."`
489+
- **Timeout errors**: `"Request timeout"`
490+
491+
### Error Handling Best Practices
492+
493+
1. **Always check mcp_connect() result**:
494+
```sql
495+
-- Good practice: Check connection first
496+
SELECT
497+
CASE
498+
WHEN mcp_connect('http://localhost:8931/mcp') IS NULL
499+
THEN 'Connected successfully'
500+
ELSE mcp_connect('http://localhost:8931/mcp')
501+
END;
502+
```
503+
504+
2. **Use JSON functions for error checking before virtual tables**:
505+
```sql
506+
-- Check for errors before using virtual table
507+
WITH error_check AS (
508+
SELECT json_extract(mcp_list_tools_json(), '$.error') as error
509+
)
510+
SELECT
511+
CASE
512+
WHEN error_check.error IS NOT NULL
513+
THEN 'Error: ' || error_check.error
514+
ELSE 'Tools: ' || (SELECT GROUP_CONCAT(name) FROM mcp_list_tools)
515+
END
516+
FROM error_check;
517+
```
518+
519+
3. **Handle tool call errors**:
520+
```sql
521+
-- Safe tool calling with error handling
522+
SELECT
523+
CASE
524+
WHEN json_extract(result, '$.error') IS NOT NULL
525+
THEN 'Tool Error: ' || json_extract(result, '$.error')
526+
ELSE json_extract(result, '$.content[0].text')
527+
END as output
528+
FROM (
529+
SELECT mcp_call_tool_json('browser_navigate', '{"url": "https://example.com"}') as result
530+
);
445531
```

src/lib.rs

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,26 @@ use std::sync::{Mutex, OnceLock};
1313
use rmcp::transport::{SseClientTransport, StreamableHttpClientTransport};
1414
use rmcp::{ServiceExt, RoleClient};
1515
use rmcp::model::{ClientInfo, ClientCapabilities, Implementation};
16+
use serde_json;
1617

1718
// Global client instance - one client per process
1819
static GLOBAL_CLIENT: OnceLock<Mutex<Option<McpClient>>> = OnceLock::new();
1920

21+
/// Extract error message from JSON error response
22+
/// Returns the error message string if found, or the original JSON if not found
23+
fn extract_error_message(json_str: &str) -> String {
24+
match serde_json::from_str::<serde_json::Value>(json_str) {
25+
Ok(json) => {
26+
if let Some(error) = json.get("error").and_then(|e| e.as_str()) {
27+
error.to_string()
28+
} else {
29+
json_str.to_string()
30+
}
31+
}
32+
Err(_) => json_str.to_string(),
33+
}
34+
}
35+
2036
/// Initialize the MCP library
2137
/// Returns 0 on success, non-zero on error
2238
#[no_mangle]
@@ -538,30 +554,55 @@ pub extern "C" fn mcp_connect(
538554
// Return NULL on successful connection
539555
ptr::null_mut()
540556
} else {
541-
// Return error if status is not "connected"
542-
match CString::new(result) {
557+
// Return error string (extracted from JSON)
558+
let error_msg = extract_error_message(&result);
559+
match CString::new(error_msg) {
543560
Ok(c_str) => c_str.into_raw(),
544561
Err(_) => ptr::null_mut(),
545562
}
546563
}
547564
} else {
548-
// No status field found, return as error
549-
match CString::new(result) {
565+
// No status field found, extract error message
566+
let error_msg = extract_error_message(&result);
567+
match CString::new(error_msg) {
550568
Ok(c_str) => c_str.into_raw(),
551569
Err(_) => ptr::null_mut(),
552570
}
553571
}
554572
}
555573
Err(_) => {
556-
// Invalid JSON, return as error
557-
match CString::new(result) {
574+
// Invalid JSON, return as error string
575+
let error_msg = extract_error_message(&result);
576+
match CString::new(error_msg) {
558577
Ok(c_str) => c_str.into_raw(),
559578
Err(_) => ptr::null_mut(),
560579
}
561580
}
562581
}
563582
}
564583

584+
/// Disconnect from MCP server and reset global client state
585+
/// Returns NULL on success
586+
#[no_mangle]
587+
pub extern "C" fn mcp_disconnect() -> *mut c_char {
588+
let global_client = GLOBAL_CLIENT.get_or_init(|| Mutex::new(None));
589+
*global_client.lock().unwrap() = None;
590+
591+
// Also clear any active stream channels
592+
{
593+
let runtime = tokio::runtime::Runtime::new().unwrap();
594+
runtime.block_on(async {
595+
let mut channels = STREAM_CHANNELS.lock().await;
596+
channels.clear();
597+
});
598+
}
599+
600+
// Reset stream counter
601+
*STREAM_COUNTER.lock().unwrap() = 0;
602+
603+
ptr::null_mut()
604+
}
605+
565606
/// List tools available on the connected MCP server (returns raw JSON)
566607
/// Returns: JSON string with tools list (must be freed with mcp_free_string)
567608
#[no_mangle]

src/mcp_ffi.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ void mcp_client_free(McpClient* client);
5555
*/
5656
char* mcp_connect(McpClient* client, const char* server_url, const char* headers_json, int32_t legacy_sse);
5757

58+
/**
59+
* Disconnect from MCP server and reset global client state
60+
* Returns: NULL on success
61+
*/
62+
char* mcp_disconnect(void);
63+
5864
#ifdef __cplusplus
5965
}
6066
#endif

src/sqlite-mcp.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,27 @@ static void mcp_connect_func(
9393
mcp_free_string(result);
9494
}
9595

96+
static void mcp_disconnect_func(
97+
sqlite3_context *context,
98+
int argc,
99+
sqlite3_value **argv
100+
){
101+
if (argc != 0) {
102+
sqlite3_result_error(context, "mcp_disconnect takes no arguments", -1);
103+
return;
104+
}
105+
106+
char *result = mcp_disconnect();
107+
108+
// Should always return NULL (success)
109+
if (!result) {
110+
sqlite3_result_null(context);
111+
} else {
112+
sqlite3_result_text(context, result, -1, SQLITE_TRANSIENT);
113+
mcp_free_string(result);
114+
}
115+
}
116+
96117
/*
97118
** STREAMING Virtual Table for mcp_list_tools
98119
** Returns parsed tool information as rows using streaming API
@@ -1260,6 +1281,11 @@ int sqlite3_mcp_init(
12601281
0, mcp_connect_func, 0, 0);
12611282
if (rc != SQLITE_OK) return rc;
12621283

1284+
rc = sqlite3_create_function(db, "mcp_disconnect", 0,
1285+
SQLITE_UTF8,
1286+
0, mcp_disconnect_func, 0, 0);
1287+
if (rc != SQLITE_OK) return rc;
1288+
12631289
// Scalar functions that return JSON strings
12641290
rc = sqlite3_create_function(db, "mcp_list_tools_json", 0,
12651291
SQLITE_UTF8,

0 commit comments

Comments
 (0)