Skip to content

Commit a40e341

Browse files
Merge pull request #16 from teaguesterling/feature/wasm
Add WASM/Emscripten conditional compilation
2 parents ee0806b + 83d9e04 commit a40e341

18 files changed

+184
-57
lines changed

CMakeLists.txt

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ set(LOADABLE_EXTENSION_NAME ${TARGET_NAME}_loadable_extension)
88

99
project(${TARGET_NAME})
1010
include_directories(src/include)
11-
include_directories(${CMAKE_SOURCE_DIR}/third_party/httplib)
11+
if(NOT EMSCRIPTEN)
12+
include_directories(${CMAKE_SOURCE_DIR}/third_party/httplib)
13+
endif()
1214

1315
set(EXTENSION_SOURCES
1416
src/duckdb_mcp_extension.cpp
@@ -17,25 +19,33 @@ set(EXTENSION_SOURCES
1719
src/duckdb_mcp_logging.cpp
1820
src/json_utils.cpp
1921
src/result_formatter.cpp
20-
src/mcpfs/mcp_path.cpp
21-
src/mcpfs/mcp_file_system.cpp
2222
src/protocol/mcp_message.cpp
23-
src/protocol/mcp_transport.cpp
24-
src/protocol/mcp_connection.cpp
2523
src/protocol/mcp_template.cpp
2624
src/protocol/mcp_pagination.cpp
27-
src/client/mcp_storage_extension.cpp
28-
src/client/mcp_transaction_manager.cpp
29-
src/catalog/mcp_catalog.cpp
30-
src/catalog/mcp_schema_entry.cpp
3125
src/server/mcp_server.cpp
32-
src/server/http_server_transport.cpp
3326
src/server/resource_providers.cpp
3427
src/server/tool_handlers.cpp
35-
src/server/stdio_server_transport.cpp
3628
src/server/memory_transport.cpp
3729
)
3830

31+
# Sources that are incompatible with WASM (process management, HTTP server,
32+
# file I/O, client-side ATTACH, threading)
33+
if(NOT EMSCRIPTEN)
34+
list(APPEND EXTENSION_SOURCES
35+
src/protocol/mcp_transport.cpp
36+
src/protocol/mcp_connection.cpp
37+
src/protocol/mcp_http_transport.cpp
38+
src/server/http_server_transport.cpp
39+
src/server/stdio_server_transport.cpp
40+
src/client/mcp_storage_extension.cpp
41+
src/client/mcp_transaction_manager.cpp
42+
src/catalog/mcp_catalog.cpp
43+
src/catalog/mcp_schema_entry.cpp
44+
src/mcpfs/mcp_path.cpp
45+
src/mcpfs/mcp_file_system.cpp
46+
)
47+
endif()
48+
3949
build_static_extension(${TARGET_NAME} ${EXTENSION_SOURCES})
4050
build_loadable_extension(${TARGET_NAME} " " ${EXTENSION_SOURCES})
4151

src/duckdb_mcp_extension.cpp

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
#include <cstdlib>
99

1010
using namespace duckdb_yyjson;
11+
#ifndef __EMSCRIPTEN__
1112
#include "mcpfs/mcp_file_system.hpp"
1213
#include "client/mcp_storage_extension.hpp"
1314
#include "protocol/mcp_connection.hpp"
15+
#endif
1416
#include "protocol/mcp_message.hpp"
1517
#include "protocol/mcp_template.hpp"
1618
#include "protocol/mcp_pagination.hpp"
@@ -33,6 +35,8 @@ using namespace duckdb_yyjson;
3335

3436
namespace duckdb {
3537

38+
#ifndef __EMSCRIPTEN__
39+
3640
// Get MCP resource content
3741
static void MCPGetResourceFunction(DataChunk &args, ExpressionState &state, Vector &result) {
3842
auto &server_vector = args.data[0];
@@ -455,6 +459,8 @@ static void MCPServerHealthFunction(DataChunk &args, ExpressionState &state, Vec
455459
}
456460
}
457461

462+
#endif // !__EMSCRIPTEN__
463+
458464
// MCPStatus struct type definition
459465
static LogicalType GetMCPStatusType() {
460466
child_list_t<LogicalType> members;
@@ -596,7 +602,19 @@ static Value MCPServerStartImpl(ExpressionState &state, const string &transport,
596602
}
597603

598604
// Handle different transport types
599-
if (transport == "stdio") {
605+
if (transport == "memory") {
606+
// Memory transport is always background mode (for testing with mcp_server_send_request)
607+
// The server doesn't do I/O - it just waits for requests via ProcessRequest()
608+
server_config.background = true;
609+
if (server_manager.StartServer(server_config)) {
610+
return CreateMCPStatus(true, true, "MCP server started on memory transport (background mode)",
611+
transport, bind_address, port, true);
612+
} else {
613+
return CreateMCPStatus(false, false, "Failed to start MCP server", transport, bind_address, port, true);
614+
}
615+
}
616+
#ifndef __EMSCRIPTEN__
617+
else if (transport == "stdio") {
600618
if (server_config.background) {
601619
// Background mode: use server manager (non-blocking, starts thread)
602620
if (server_manager.StartServer(server_config)) {
@@ -626,16 +644,6 @@ static Value MCPServerStartImpl(ExpressionState &state, const string &transport,
626644
server.GetErrorsReturned());
627645
}
628646
}
629-
} else if (transport == "memory") {
630-
// Memory transport is always background mode (for testing with mcp_server_send_request)
631-
// The server doesn't do I/O - it just waits for requests via ProcessRequest()
632-
server_config.background = true;
633-
if (server_manager.StartServer(server_config)) {
634-
return CreateMCPStatus(true, true, "MCP server started on memory transport (background mode)",
635-
transport, bind_address, port, true);
636-
} else {
637-
return CreateMCPStatus(false, false, "Failed to start MCP server", transport, bind_address, port, true);
638-
}
639647
} else if (transport == "http" || transport == "https") {
640648
// HTTP/HTTPS transport
641649
if (server_config.background) {
@@ -667,14 +675,22 @@ static Value MCPServerStartImpl(ExpressionState &state, const string &transport,
667675
server.GetErrorsReturned());
668676
}
669677
}
670-
} else {
678+
}
679+
#endif // !__EMSCRIPTEN__
680+
else {
681+
// Unknown transport (or non-memory transport in WASM)
682+
#ifdef __EMSCRIPTEN__
683+
return CreateMCPStatus(false, false, "Only 'memory' transport is available in WASM", transport,
684+
bind_address, port, false);
685+
#else
671686
// For other transports (TCP/WebSocket), use background thread
672687
if (server_manager.StartServer(server_config)) {
673688
return CreateMCPStatus(true, true, "MCP server started on " + transport, transport, bind_address, port,
674689
true);
675690
} else {
676691
return CreateMCPStatus(false, false, "Failed to start MCP server", transport, bind_address, port, true);
677692
}
693+
#endif
678694
}
679695

680696
} catch (const std::exception &e) {
@@ -1300,10 +1316,12 @@ static void MCPGetDiagnosticsFunction(DataChunk &args, ExpressionState &state, V
13001316
}
13011317

13021318
// Callback functions for MCP configuration settings
1319+
#ifndef __EMSCRIPTEN__
13031320
static void SetAllowedMCPCommands(ClientContext &context, SetScope scope, Value &parameter) {
13041321
auto &security = MCPSecurityConfig::GetInstance();
13051322
security.SetAllowedCommands(parameter.ToString());
13061323
}
1324+
#endif
13071325

13081326
static void SetMCPLogLevel(ClientContext &context, SetScope scope, Value &parameter) {
13091327
auto level_str = parameter.ToString();
@@ -1325,14 +1343,17 @@ static void SetMCPLogLevel(ClientContext &context, SetScope scope, Value &parame
13251343
MCPLogger::GetInstance().SetLogLevel(level);
13261344
}
13271345

1346+
#ifndef __EMSCRIPTEN__
13281347
static void SetMCPLogFile(ClientContext &context, SetScope scope, Value &parameter) {
13291348
MCPLogger::GetInstance().SetLogFile(parameter.ToString());
13301349
}
1350+
#endif
13311351

13321352
static void SetMCPConsoleLogging(ClientContext &context, SetScope scope, Value &parameter) {
13331353
MCPLogger::GetInstance().EnableConsoleLogging(parameter.GetValue<bool>());
13341354
}
13351355

1356+
#ifndef __EMSCRIPTEN__
13361357
static void SetAllowedMCPUrls(ClientContext &context, SetScope scope, Value &parameter) {
13371358
auto &security = MCPSecurityConfig::GetInstance();
13381359
security.SetAllowedUrls(parameter.ToString());
@@ -1348,13 +1369,16 @@ static void SetMCPLockServers(ClientContext &context, SetScope scope, Value &par
13481369
bool lock = parameter.GetValue<bool>();
13491370
security.LockServers(lock);
13501371
}
1372+
#endif
13511373

13521374
static void SetMCPDisableServing(ClientContext &context, SetScope scope, Value &parameter) {
13531375
auto &security = MCPSecurityConfig::GetInstance();
13541376
bool disable = parameter.GetValue<bool>();
13551377
security.SetServingDisabled(disable);
13561378
}
13571379

1380+
#ifndef __EMSCRIPTEN__
1381+
13581382
// MCP-Compliant Pagination Functions
13591383

13601384
// List resources with optional cursor (MCP-compliant)
@@ -1489,6 +1513,8 @@ static void MCPListPromptsWithCursorFunction(DataChunk &args, ExpressionState &s
14891513
}
14901514
}
14911515

1516+
#endif // !__EMSCRIPTEN__
1517+
14921518
// MCP Template Functions
14931519

14941520
static void MCPRegisterPromptTemplateFunction(DataChunk &args, ExpressionState &state, Vector &result) {
@@ -1619,20 +1645,21 @@ static void MCPRenderPromptTemplateFunction(DataChunk &args, ExpressionState &st
16191645

16201646
static void LoadInternal(ExtensionLoader &loader) {
16211647
auto &db = loader.GetDatabaseInstance();
1648+
auto &config = DBConfig::GetConfig(db);
16221649

1650+
#ifndef __EMSCRIPTEN__
16231651
// Register MCPFS file system
16241652
auto &fs = FileSystem::GetFileSystem(db);
16251653
fs.RegisterSubSystem(make_uniq<MCPFileSystem>());
16261654

16271655
// Register MCP storage extension for ATTACH support
1628-
auto &config = DBConfig::GetConfig(db);
16291656
#ifdef DUCKDB_V15
16301657
StorageExtension::Register(config, "mcp", MCPStorageExtension::Create());
16311658
#else
16321659
config.storage_extensions["mcp"] = MCPStorageExtension::Create();
16331660
#endif
16341661

1635-
// Register MCP configuration options
1662+
// Register native-only MCP configuration options
16361663
config.AddExtensionOption("allowed_mcp_commands",
16371664
"Colon-delimited list of executable paths allowed for MCP servers (security: executable "
16381665
"paths only, no arguments)",
@@ -1648,45 +1675,38 @@ static void LoadInternal(ExtensionLoader &loader) {
16481675
"Lock MCP server configuration to prevent runtime changes (security feature)",
16491676
LogicalType::BOOLEAN, Value(false), SetMCPLockServers);
16501677

1651-
config.AddExtensionOption("mcp_disable_serving", "Disable MCP server functionality entirely (client-only mode)",
1652-
LogicalType::BOOLEAN, Value(false), SetMCPDisableServing);
1653-
1654-
// Register MCP logging configuration options
1655-
config.AddExtensionOption("mcp_log_level", "MCP logging level (trace, debug, info, warn, error, off)",
1656-
LogicalType::VARCHAR, Value("warn"), SetMCPLogLevel);
1657-
16581678
config.AddExtensionOption("mcp_log_file", "Path to MCP log file (empty for no file logging)", LogicalType::VARCHAR,
16591679
Value(""), SetMCPLogFile);
16601680

1661-
config.AddExtensionOption("mcp_console_logging", "Enable MCP logging to console/stderr", LogicalType::BOOLEAN,
1662-
Value(false), SetMCPConsoleLogging);
1663-
16641681
// Initialize default security settings only if not already configured.
1665-
// The singleton may already be locked from a previous LoadInternal call
1666-
// (e.g., multiple database instances) or from user SET commands.
1667-
// Avoid resetting locked state — doing so could widen permissions.
16681682
auto &security = MCPSecurityConfig::GetInstance();
16691683
if (!security.AreCommandsLocked()) {
1670-
// Set defaults for URL and server file (these don't lock).
1671-
// Do NOT call SetAllowedCommands("") here — the constructor defaults
1672-
// (empty + unlocked) are sufficient. The user's first explicit
1673-
// SET allowed_mcp_commands will lock commands.
16741684
security.SetAllowedUrls("");
16751685
security.SetServerFile("./.mcp.json");
16761686
security.LockServers(false);
16771687
}
1688+
#endif // !__EMSCRIPTEN__
1689+
1690+
// Configuration options available on all platforms (including WASM)
1691+
config.AddExtensionOption("mcp_disable_serving", "Disable MCP server functionality entirely (client-only mode)",
1692+
LogicalType::BOOLEAN, Value(false), SetMCPDisableServing);
1693+
1694+
config.AddExtensionOption("mcp_log_level", "MCP logging level (trace, debug, info, warn, error, off)",
1695+
LogicalType::VARCHAR, Value("warn"), SetMCPLogLevel);
1696+
1697+
config.AddExtensionOption("mcp_console_logging", "Enable MCP logging to console/stderr", LogicalType::BOOLEAN,
1698+
Value(false), SetMCPConsoleLogging);
16781699

1679-
// Register MCP resource functions
1700+
#ifndef __EMSCRIPTEN__
1701+
// Register client-side MCP functions (require MCPConnectionRegistry)
16801702
auto get_resource_func = ScalarFunction("mcp_get_resource", {LogicalType::VARCHAR, LogicalType::VARCHAR},
16811703
LogicalType::JSON(), MCPGetResourceFunction);
16821704
loader.RegisterFunction(get_resource_func);
16831705

1684-
// Create overloaded versions for list_resources (with and without cursor)
16851706
auto list_resources_func_simple =
16861707
ScalarFunction("mcp_list_resources", {LogicalType::VARCHAR}, LogicalType::JSON(), MCPListResourcesFunction);
16871708
auto list_resources_func_cursor = ScalarFunction("mcp_list_resources", {LogicalType::VARCHAR, LogicalType::VARCHAR},
16881709
LogicalType::JSON(), MCPListResourcesWithCursorFunction);
1689-
16901710
loader.RegisterFunction(list_resources_func_simple);
16911711
loader.RegisterFunction(list_resources_func_cursor);
16921712

@@ -1695,15 +1715,13 @@ static void LoadInternal(ExtensionLoader &loader) {
16951715
LogicalType::JSON(), MCPCallToolFunction);
16961716
loader.RegisterFunction(call_tool_func);
16971717

1698-
// Register MCP tool functions (with and without cursor)
16991718
auto list_tools_func_simple =
17001719
ScalarFunction("mcp_list_tools", {LogicalType::VARCHAR}, LogicalType::JSON(), MCPListToolsFunction);
17011720
auto list_tools_func_cursor = ScalarFunction("mcp_list_tools", {LogicalType::VARCHAR, LogicalType::VARCHAR},
17021721
LogicalType::JSON(), MCPListToolsWithCursorFunction);
17031722
loader.RegisterFunction(list_tools_func_simple);
17041723
loader.RegisterFunction(list_tools_func_cursor);
17051724

1706-
// Register MCP prompt functions (with and without cursor)
17071725
auto list_prompts_func_simple =
17081726
ScalarFunction("mcp_list_prompts", {LogicalType::VARCHAR}, LogicalType::JSON(), MCPListPromptsFunction);
17091727
auto list_prompts_func_cursor = ScalarFunction("mcp_list_prompts", {LogicalType::VARCHAR, LogicalType::VARCHAR},
@@ -1716,17 +1734,16 @@ static void LoadInternal(ExtensionLoader &loader) {
17161734
LogicalType::JSON(), MCPGetPromptFunction);
17171735
loader.RegisterFunction(get_prompt_func);
17181736

1719-
// Register MCP connection management functions
17201737
auto reconnect_func = ScalarFunction("mcp_reconnect_server", {LogicalType::VARCHAR}, LogicalType::VARCHAR,
17211738
MCPReconnectServerFunction);
17221739
loader.RegisterFunction(reconnect_func);
17231740

17241741
auto health_func =
17251742
ScalarFunction("mcp_server_health", {LogicalType::VARCHAR}, LogicalType::VARCHAR, MCPServerHealthFunction);
17261743
loader.RegisterFunction(health_func);
1744+
#endif // !__EMSCRIPTEN__
17271745

1728-
// Register MCP server functions (multiple overloads for convenience)
1729-
// All server management functions return MCPStatus struct type
1746+
// Server-side functions (work via memory transport in WASM)
17301747
LogicalType mcp_status_type = GetMCPStatusType();
17311748

17321749
// mcp_server_start(transport) - simplest form for stdio

src/duckdb_mcp_logging.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "duckdb_mcp_logging.hpp"
22
#include <iostream>
3+
#ifndef __EMSCRIPTEN__
34
#include <fstream>
5+
#endif
46
#include <iomanip>
57
#include <sstream>
68

@@ -19,9 +21,11 @@ MCPLogger::MCPLogger() {
1921

2022
MCPLogger::~MCPLogger() {
2123
std::lock_guard<std::mutex> lock(log_mutex);
24+
#ifndef __EMSCRIPTEN__
2225
if (log_file.is_open()) {
2326
log_file.close();
2427
}
28+
#endif
2529
}
2630

2731
void MCPLogger::SetLogLevel(MCPLogLevel level) {
@@ -37,6 +41,13 @@ MCPLogLevel MCPLogger::GetLogLevel() const {
3741
void MCPLogger::SetLogFile(const string &file_path) {
3842
std::lock_guard<std::mutex> lock(log_mutex);
3943

44+
#ifdef __EMSCRIPTEN__
45+
// File logging is not available in WASM — use console logging instead
46+
if (!file_path.empty()) {
47+
std::cerr << "[MCP-WARN] File logging not available in WASM, ignoring log file: " << file_path << std::endl;
48+
}
49+
return;
50+
#else
4051
// Close existing file if open
4152
if (log_file.is_open()) {
4253
log_file.close();
@@ -52,6 +63,7 @@ void MCPLogger::SetLogFile(const string &file_path) {
5263
} else {
5364
log_file_path.clear();
5465
}
66+
#endif
5567
}
5668

5769
void MCPLogger::EnableConsoleLogging(bool enable) {
@@ -73,11 +85,13 @@ void MCPLogger::LogMessage(MCPLogLevel level, const string &component, const str
7385
}
7486
}
7587

88+
#ifndef __EMSCRIPTEN__
7689
// Write to file if available
7790
if (log_file.is_open()) {
7891
log_file << formatted_message << std::endl;
7992
log_file.flush();
8093
}
94+
#endif
8195
}
8296

8397
string MCPLogger::FormatLogEntry(MCPLogLevel level, const string &component, const string &message) {

0 commit comments

Comments
 (0)