diff --git a/.clang-format b/.clang-format deleted file mode 100644 index bb00198a..00000000 --- a/.clang-format +++ /dev/null @@ -1,53 +0,0 @@ ---- -# Google C++ Style Guide -# https://google.github.io/styleguide/cppguide.html -BasedOnStyle: Google -IndentWidth: 2 -ColumnLimit: 80 ---- -Language: Cpp -# Force pointers to the type for C++. -DerivePointerAlignment: false -PointerAlignment: Left -# Other adjustments -AccessModifierOffset: -1 -AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: true -BinPackParameters: false -BreakBeforeBraces: Attach -BreakConstructorInitializers: BeforeColon -ConstructorInitializerAllOnOneLineOrOnePerLine: true -Cpp11BracedListStyle: true -IncludeBlocks: Regroup -IncludeCategories: - # Standard library headers - - Regex: '^<[^/]+>$' - Priority: 1 - # Other library headers - - Regex: '^<.+>$' - Priority: 2 - # Project headers with quotes - - Regex: '^"mcp/.+"$' - Priority: 3 - # Other project headers - - Regex: '^".+"$' - Priority: 4 -IndentCaseLabels: true -KeepEmptyLinesAtTheStartOfBlocks: false -NamespaceIndentation: None -SortIncludes: true -SpaceAfterCStyleCast: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesInAngles: false -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: c++14 -UseTab: Never -# Remove trailing whitespace -InsertTrailingCommas: None \ No newline at end of file diff --git a/.github/workflows/pr-format-check.yml b/.github/workflows/pr-format-check.yml index 769b322b..cc12359d 100644 --- a/.github/workflows/pr-format-check.yml +++ b/.github/workflows/pr-format-check.yml @@ -5,8 +5,8 @@ on: types: [opened, synchronize, reopened] jobs: - clang-format: - name: Clang Format + rust-format: + name: Rust Format runs-on: ubuntu-latest permissions: contents: read @@ -17,63 +17,47 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - submodules: recursive + submodules: false - - name: Install clang-format - run: | - sudo apt-get update - sudo apt-get install -y clang-format-14 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt - name: Check changed files id: changed-files run: | - # Get list of changed C/C++ files (excluding submodules) + # Get list of changed Rust files (excluding third_party) git diff --name-only origin/${{ github.base_ref }}...HEAD | \ - grep -E '\.(h|hpp|c|cc|cpp)$' | \ + grep -E '\.rs$' | \ grep -v '^third_party/' > changed_files.txt || true if [ -s changed_files.txt ]; then echo "has_changes=true" >> $GITHUB_OUTPUT - echo "Changed C/C++ files:" + echo "Changed Rust files:" cat changed_files.txt else echo "has_changes=false" >> $GITHUB_OUTPUT - echo "No C/C++ files changed" + echo "No Rust files changed" fi - - name: Check formatting of changed files + - name: Check formatting if: steps.changed-files.outputs.has_changes == 'true' run: | - exit_code=0 - while IFS= read -r file; do - if [ -f "$file" ]; then - echo "Checking $file..." - clang-format-14 --style=file --dry-run --Werror "$file" || { - echo "::error file=$file::File is not properly formatted" - exit_code=1 - } - fi - done < changed_files.txt - - if [ $exit_code -ne 0 ]; then - echo "" - echo "::error::Some files are not properly formatted." - echo "To fix, run: make format" - exit 1 - fi + cargo fmt --all -- --check - name: Post PR comment on failure if: failure() && steps.changed-files.outputs.has_changes == 'true' uses: actions/github-script@v7 with: script: | - const comment = `## Code Formatting Check Failed + const comment = `## Rust Format Check Failed - Some files in this PR are not properly formatted according to the project's clang-format rules. + Some Rust files in this PR are not properly formatted. **To fix this issue:** \`\`\`bash - make format + cargo fmt \`\`\` Then commit and push the changes.`; diff --git a/.gitignore b/.gitignore index 457c7687..8a4dfe5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,109 +1,39 @@ -# Build directories -build/ -build-*/ -build_*/ -cmake-build-*/ -out/ -bin/ -lib/ -# Exception: Allow Ruby SDK lib directory -!sdk/ruby/lib/ +# Rust +/target/ +**/*.rs.bk +Cargo.lock -# CMake generated files -CMakeCache.txt -CMakeFiles/ -cmake_install.cmake -CTestTestfile.cmake -Testing/ -_deps/ -# Note: We have a hand-written Makefile at root, so only ignore generated ones in subdirs -*/Makefile +# Native libraries (generated) +/native/ -# Compiled object files -*.o -*.obj -*.lo -*.slo - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app -test_variant -test_variant_advanced -test_variant_extensive -test_optional -test_optional_advanced -test_optional_extensive -test_type_helpers -test_mcp_types -test_mcp_types_extended -test_mcp_type_helpers -test_compat -test_buffer -test_json -test_event_loop -test_io_socket_handle -test_address -test_socket -test_socket_interface -test_socket_option - -# IDE specific files +# IDE .vscode/ .idea/ *.swp *.swo *~ -.DS_Store - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb -# Dependency directories -node_modules/ -vendor/ - -# Coverage files -*.gcov -*.gcda -*.gcno -coverage/ -*.info +# OS +.DS_Store +Thumbs.db -# Documentation -docs/html/ -docs/latex/ -doxygen/ +# Build artifacts +*.o +*.so +*.dylib +*.dll +*.a # Temporary files *.tmp *.temp *.log -# Python cache (if using Python scripts) -__pycache__/ -*.py[cod] -*$py.class +# Coverage +*.gcov +*.gcda +*.gcno +coverage/ -# OS generated files -Thumbs.db -Desktop.ini \ No newline at end of file +# Node modules (for example servers) +node_modules/ diff --git a/.gitmodules b/.gitmodules index f7c1a54e..d5cd4211 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,3 @@ -[submodule "third_party/gopher-mcp"] - path = third_party/gopher-mcp - url = https://github.com/GopherSecurity/gopher-mcp.git - branch = main +[submodule "third_party/gopher-orch"] + path = third_party/gopher-orch + url = https://github.com/GopherSecurity/gopher-orch.git diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 80c90036..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,253 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(gopher-orch VERSION 0.1.0 LANGUAGES C CXX) - -# Prevent in-source builds -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) - message(FATAL_ERROR "In-source builds are not allowed. Please create a build directory and run cmake from there.") -endif() - -# Set C++ standard -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -message(STATUS "Using C++14") - -# Default to Debug build -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) -endif() - -# Build options -option(BUILD_SHARED_LIBS "Build shared libraries" ON) -option(BUILD_STATIC_LIBS "Build static libraries" ON) -option(BUILD_TESTS "Build tests" ON) -option(BUILD_EXAMPLES "Build examples" ON) -option(ORCH_STRICT_WARNINGS "Enable strict compiler warnings" OFF) -option(USE_SUBMODULE_GOPHER_MCP "Use gopher-mcp as submodule (vs find_package)" ON) -option(BUILD_WITHOUT_GOPHER_MCP "Build without gopher-mcp dependency (for testing)" OFF) - -# Set output directories -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# Compiler flags -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_compile_options(-g -O0) - add_compile_definitions(_DEBUG) -elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - add_compile_options(-O3) - add_compile_definitions(NDEBUG) -endif() - -# Platform-specific settings -if(APPLE) - set(CMAKE_MACOSX_RPATH ON) - set(CMAKE_INSTALL_RPATH "@loader_path/../lib") -elseif(UNIX) - set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") -endif() - -# Warning flags -if(ORCH_STRICT_WARNINGS) - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - add_compile_options( - -Wall -Wextra -Wpedantic - -Wno-unused-parameter - -Wno-unused-variable - -Wno-unused-function - -Werror - ) - elseif(MSVC) - add_compile_options(/W4 /WX) - endif() -else() - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - add_compile_options( - -Wall - -Wno-unused-parameter - -Wno-unused-variable - -Wno-unused-function - ) - endif() -endif() - -# Handle gopher-mcp dependency -if(BUILD_WITHOUT_GOPHER_MCP) - # Build without gopher-mcp for testing - message(STATUS "Building without gopher-mcp dependency") - set(GOPHER_MCP_LIBRARIES "") - set(GOPHER_MCP_INCLUDE_DIR "") -elseif(USE_SUBMODULE_GOPHER_MCP) - # Use gopher-mcp as submodule - if(NOT EXISTS "${CMAKE_SOURCE_DIR}/third_party/gopher-mcp/.git") - message(STATUS "gopher-mcp submodule not found. Initializing...") - execute_process( - COMMAND git submodule add https://github.com/GopherSecurity/gopher-mcp.git third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_RESULT - ) - if(NOT GIT_SUBMOD_RESULT EQUAL "0") - # Submodule might already exist, try update - execute_process( - COMMAND git submodule update --init --recursive third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_UPDATE_RESULT - ) - if(NOT GIT_SUBMOD_UPDATE_RESULT EQUAL "0") - message(FATAL_ERROR "Failed to initialize gopher-mcp submodule") - endif() - endif() - else() - message(STATUS "Updating gopher-mcp submodule...") - execute_process( - COMMAND git submodule update --init --recursive third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - endif() - - # Set include directories for gopher-mcp BEFORE adding subdirectory - set(GOPHER_MCP_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/third_party/gopher-mcp/include) - - # Temporarily add gopher-mcp include directories before processing subdirectory - # This ensures gopher-mcp can find its own headers when building as submodule - include_directories(${GOPHER_MCP_INCLUDE_DIR}) - - # Disable gopher-mcp tests and examples to speed up build - set(BUILD_TESTS_SAVED ${BUILD_TESTS}) - set(BUILD_EXAMPLES_SAVED ${BUILD_EXAMPLES}) - set(BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(BUILD_BINDINGS_EXAMPLES OFF CACHE BOOL "" FORCE) - - # Disable fmt installation if gopher-mcp uses it - set(FMT_INSTALL OFF CACHE BOOL "Disable fmt installation" FORCE) - - # Add gopher-mcp subdirectory - add_subdirectory(third_party/gopher-mcp EXCLUDE_FROM_ALL) - - # Restore our settings - set(BUILD_TESTS ${BUILD_TESTS_SAVED} CACHE BOOL "" FORCE) - set(BUILD_EXAMPLES ${BUILD_EXAMPLES_SAVED} CACHE BOOL "" FORCE) - - # Make gopher-mcp libraries available - # Use static libraries for tests to avoid duplicate initialization - if(BUILD_TESTS AND TARGET gopher-mcp-static) - set(GOPHER_MCP_LIBRARIES gopher-mcp-static gopher-mcp-event-static) - else() - set(GOPHER_MCP_LIBRARIES gopher-mcp gopher-mcp-event) - endif() - - message(STATUS "Using gopher-mcp from submodule") -else() - # Use system-installed gopher-mcp - find_package(gopher-mcp REQUIRED) - message(STATUS "Using system gopher-mcp: ${gopher-mcp_DIR}") -endif() - -# Include directories -message(STATUS "GOPHER_MCP_INCLUDE_DIR: ${GOPHER_MCP_INCLUDE_DIR}") -include_directories( - ${CMAKE_SOURCE_DIR}/include - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Export compile commands for tools like clangd -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# Find required packages -find_package(Threads REQUIRED) - -# Testing setup -if(BUILD_TESTS) - enable_testing() - include(CTest) - - # Fetch Google Test - include(FetchContent) - - # Prevent Google Test from being installed - set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) - set(INSTALL_GMOCK OFF CACHE BOOL "Disable installation of googlemock" FORCE) - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.14.0 - CMAKE_ARGS -DINSTALL_GTEST=OFF -DINSTALL_GMOCK=OFF - ) - - FetchContent_MakeAvailable(googletest) - - # Include Google Test and Google Mock - include(GoogleTest) -endif() - -# Add subdirectories -add_subdirectory(src) - -if(BUILD_TESTS) - add_subdirectory(tests) -endif() - -if(BUILD_EXAMPLES) - add_subdirectory(examples) -endif() - -# Installation rules -install(DIRECTORY include/orch - DESTINATION include - COMPONENT development - FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" -) - -# Package configuration -include(CMakePackageConfigHelpers) - -configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/gopher-orch-config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config.cmake" - INSTALL_DESTINATION lib/cmake/gopher-orch -) - -write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion -) - -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config-version.cmake" - DESTINATION lib/cmake/gopher-orch - COMPONENT development -) - -# Add uninstall target -if(NOT TARGET uninstall) - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE @ONLY - ) - - add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake - ) -endif() - -# Print configuration summary -message(STATUS "") -message(STATUS "=== gopher-orch Configuration Summary ===") -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}") -message(STATUS "Build shared libs: ${BUILD_SHARED_LIBS}") -message(STATUS "Build static libs: ${BUILD_STATIC_LIBS}") -message(STATUS "Build tests: ${BUILD_TESTS}") -message(STATUS "Build examples: ${BUILD_EXAMPLES}") -message(STATUS "Strict warnings: ${ORCH_STRICT_WARNINGS}") -message(STATUS "Use submodule gopher-mcp: ${USE_SUBMODULE_GOPHER_MCP}") -message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") -message(STATUS "==========================================") -message(STATUS "") diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..2352cddf --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "gopher-orch" +version = "0.1.0" +edition = "2021" +authors = ["GopherSecurity"] +description = "Rust SDK for Gopher Orch - AI Agent orchestration framework" +license = "MIT" +repository = "https://github.com/GopherSecurity/gopher-mcp-rust" +keywords = ["ai", "agent", "mcp", "llm", "orchestration"] +categories = ["api-bindings", "development-tools"] + +[lib] +name = "gopher_orch" +path = "src/lib.rs" + +[[example]] +name = "client_example_json" +path = "examples/client_example_json.rs" + +[dependencies] +libloading = "0.7" +once_cell = "=1.17.0" + +[dev-dependencies] + +[build-dependencies] +# None needed - we use runtime loading + +[features] +default = [] diff --git a/Makefile b/Makefile deleted file mode 100644 index 5639a8ff..00000000 --- a/Makefile +++ /dev/null @@ -1,374 +0,0 @@ -# gopher-orch Makefile -# Consolidates all CMake commands for easy building - -# Build configuration -BUILD_DIR ?= build -BUILD_TYPE ?= Debug -GENERATOR ?= "Unix Makefiles" -PARALLEL_JOBS ?= $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) - -# Library build options (both by default) -BUILD_STATIC ?= ON -BUILD_SHARED ?= ON - -# CMake options -CMAKE_OPTIONS ?= -VERBOSE ?= 0 - -# Colors for output -RED := \033[0;31m -GREEN := \033[0;32m -YELLOW := \033[1;33m -BLUE := \033[0;34m -NC := \033[0m # No Color - -# Default target -.PHONY: all -all: build test - @echo "$(GREEN)Build and test completed successfully$(NC)" - -# Configure with CMake -.PHONY: configure -configure: - @echo "$(BLUE)Configuring with CMake...$(NC)" - @echo " Build type: $(BUILD_TYPE)" - @echo " Static library: $(BUILD_STATIC)" - @echo " Shared library: $(BUILD_SHARED)" - @mkdir -p $(BUILD_DIR) - @cd $(BUILD_DIR) && cmake .. -G $(GENERATOR) \ - -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DBUILD_STATIC_LIBS=$(BUILD_STATIC) \ - -DBUILD_SHARED_LIBS=$(BUILD_SHARED) \ - $(CMAKE_OPTIONS) - @echo "$(GREEN)Configuration complete$(NC)" - -# Build the project -.PHONY: build -build: configure - @echo "$(BLUE)Building gopher-orch libraries...$(NC)" - @cmake --build $(BUILD_DIR) -- -j$(PARALLEL_JOBS) - @echo "$(GREEN)Build complete$(NC)" - @$(MAKE) --no-print-directory lib-info-summary - -# Build in release mode -.PHONY: release -release: - @echo "$(BLUE)Building in Release mode...$(NC)" - @$(MAKE) BUILD_TYPE=Release build test - @echo "$(GREEN)Release build complete$(NC)" - -# Build in debug mode (explicit) -.PHONY: debug -debug: - @echo "$(BLUE)Building in Debug mode...$(NC)" - @$(MAKE) BUILD_TYPE=Debug build - @echo "$(GREEN)Debug build complete$(NC)" - -# Run tests -.PHONY: test -test: build - @echo "$(BLUE)Running tests...$(NC)" - @cd $(BUILD_DIR) && ctest --output-on-failure - @echo "$(GREEN)All tests passed$(NC)" - -# Run tests with verbose output -.PHONY: test-verbose -test-verbose: build - @echo "$(BLUE)Running tests (verbose)...$(NC)" - @cd $(BUILD_DIR) && ctest -V - @echo "$(GREEN)All tests passed$(NC)" - -# Run tests in parallel -.PHONY: test-parallel -test-parallel: build - @echo "$(BLUE)Running tests in parallel...$(NC)" - @cd $(BUILD_DIR) && ctest -j$(PARALLEL_JOBS) --output-on-failure - @echo "$(GREEN)All tests passed$(NC)" - -# Run specific test -.PHONY: test-one -test-one: build - @if [ -z "$(TEST)" ]; then \ - echo "$(RED)Error: TEST variable not set. Usage: make test-one TEST=test_name$(NC)"; \ - exit 1; \ - fi - @echo "$(BLUE)Running test: $(TEST)...$(NC)" - @cd $(BUILD_DIR) && ctest -R $(TEST) -V - @echo "$(GREEN)Test complete$(NC)" - -# Build only the libraries (respects current configuration) -.PHONY: libs -libs: configure - @echo "$(BLUE)Building libraries...$(NC)" - @if [ -f $(BUILD_DIR)/CMakeCache.txt ]; then \ - if grep -q "BUILD_STATIC_LIBS:BOOL=ON" $(BUILD_DIR)/CMakeCache.txt 2>/dev/null; then \ - cmake --build $(BUILD_DIR) --target gopher-orch-static -- -j$(PARALLEL_JOBS); \ - fi; \ - if grep -q "BUILD_SHARED_LIBS:BOOL=ON" $(BUILD_DIR)/CMakeCache.txt 2>/dev/null; then \ - cmake --build $(BUILD_DIR) --target gopher-orch-shared -- -j$(PARALLEL_JOBS); \ - fi; \ - else \ - cmake --build $(BUILD_DIR) --target gopher-orch-static -- -j$(PARALLEL_JOBS); \ - fi - @echo "$(GREEN)Libraries built$(NC)" - -# Build only the examples -.PHONY: examples -examples: libs - @echo "$(BLUE)Building examples...$(NC)" - @cmake --build $(BUILD_DIR) --target hello_world_example -- -j$(PARALLEL_JOBS) - @echo "$(GREEN)Examples built$(NC)" - -# Run the hello world example -.PHONY: run-hello -run-hello: examples - @echo "$(BLUE)Running hello_world_example...$(NC)" - @$(BUILD_DIR)/bin/hello_world_example - @echo "$(GREEN)Example completed$(NC)" - -# Clean build directory -.PHONY: clean -clean: - @echo "$(YELLOW)Cleaning build directory...$(NC)" - @rm -rf $(BUILD_DIR) - @echo "$(GREEN)Clean complete$(NC)" - -# Deep clean (including submodules) -.PHONY: distclean -distclean: clean - @echo "$(YELLOW)Deep cleaning...$(NC)" - @git submodule deinit -f . - @rm -rf third_party/gopher-mcp - @rm -rf .git/modules/third_party - @echo "$(GREEN)Deep clean complete$(NC)" - -# Format all source files -.PHONY: format -format: - @echo "$(BLUE)Formatting all source files with clang-format...$(NC)" - @find . -path "./$(BUILD_DIR)*" -prune -o -path "./third_party" -prune -o \ - \( -name "*.h" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.cc" -o -name "*.c" \) -print | \ - xargs clang-format -i - @echo "$(GREEN)Formatting complete$(NC)" - -# Check formatting without modifying files -.PHONY: check-format -check-format: - @echo "$(BLUE)Checking source file formatting...$(NC)" - @find . -path "./$(BUILD_DIR)*" -prune -o -path "./third_party" -prune -o \ - \( -name "*.h" -o -name "*.hpp" -o -name "*.cpp" -o -name "*.cc" -o -name "*.c" \) -print | \ - xargs clang-format --dry-run --Werror - @if [ $$? -eq 0 ]; then \ - echo "$(GREEN)All files are properly formatted$(NC)"; \ - else \ - echo "$(RED)Format check failed - run 'make format' to fix$(NC)"; \ - exit 1; \ - fi - -# Alias for consistency with gopher-mcp -.PHONY: format-check -format-check: check-format - -# Install the library -.PHONY: install -install: build - @echo "$(BLUE)Installing gopher-orch...$(NC)" - @cmake --build $(BUILD_DIR) --target install - @echo "$(GREEN)Installation complete$(NC)" - -# Uninstall the library -.PHONY: uninstall -uninstall: - @echo "$(YELLOW)Uninstalling gopher-orch...$(NC)" - @if [ ! -f $(BUILD_DIR)/install_manifest.txt ]; then \ - echo "$(RED)Error: No installation found. Run 'make install' first.$(NC)"; \ - exit 1; \ - fi - @cmake --build $(BUILD_DIR) --target uninstall - @echo "$(GREEN)Uninstall complete$(NC)" - -# Generate documentation (requires doxygen) -.PHONY: docs -docs: - @echo "$(BLUE)Generating documentation...$(NC)" - @doxygen Doxyfile 2>/dev/null || echo "$(YELLOW)Warning: Doxygen not found or configured$(NC)" - @echo "$(GREEN)Documentation generated$(NC)" - -# Update submodules -.PHONY: update-submodules -update-submodules: - @echo "$(BLUE)Updating submodules...$(NC)" - @git submodule update --init --recursive - @echo "$(GREEN)Submodules updated$(NC)" - -# Configure to use system gopher-mcp instead of submodule -.PHONY: use-system-gopher-mcp -use-system-gopher-mcp: - @echo "$(BLUE)Configuring to use system gopher-mcp...$(NC)" - @$(MAKE) CMAKE_OPTIONS="-DUSE_SUBMODULE_GOPHER_MCP=OFF" configure - @echo "$(GREEN)Configured to use system gopher-mcp$(NC)" - -# Configure to use submodule gopher-mcp -.PHONY: use-submodule-gopher-mcp -use-submodule-gopher-mcp: - @echo "$(BLUE)Configuring to use submodule gopher-mcp...$(NC)" - @$(MAKE) CMAKE_OPTIONS="-DUSE_SUBMODULE_GOPHER_MCP=ON" configure - @echo "$(GREEN)Configured to use submodule gopher-mcp$(NC)" - -# Build shared library only -.PHONY: shared -shared: - @$(MAKE) BUILD_STATIC=OFF BUILD_SHARED=ON clean build - -# Build static library only -.PHONY: static -static: - @$(MAKE) BUILD_STATIC=ON BUILD_SHARED=OFF clean build - -# Build both static and shared libraries (default behavior) -.PHONY: both -both: - @$(MAKE) BUILD_STATIC=ON BUILD_SHARED=ON clean build - -# Build standalone (without gopher-mcp dependency) -.PHONY: standalone -standalone: - @echo "$(BLUE)Building standalone (without gopher-mcp)...$(NC)" - @$(MAKE) CMAKE_OPTIONS="-DBUILD_WITHOUT_GOPHER_MCP=ON" build - @echo "$(GREEN)Standalone build complete$(NC)" - -# Show brief library summary (used after build) -.PHONY: lib-info-summary -lib-info-summary: - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.a ]; then \ - echo " $(GREEN)Static library: $(BUILD_DIR)/lib/libgopher-orch.a ($$(du -h $(BUILD_DIR)/lib/libgopher-orch.a 2>/dev/null | cut -f1))$(NC)"; \ - fi - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.so ]; then \ - echo " $(GREEN)Shared library: $(BUILD_DIR)/lib/libgopher-orch.so ($$(du -h $(BUILD_DIR)/lib/libgopher-orch.so 2>/dev/null | cut -f1))$(NC)"; \ - elif [ -f $(BUILD_DIR)/lib/libgopher-orch.dylib ]; then \ - echo " $(GREEN)Shared library: $(BUILD_DIR)/lib/libgopher-orch.dylib ($$(du -h $(BUILD_DIR)/lib/libgopher-orch.dylib 2>/dev/null | cut -f1))$(NC)"; \ - fi - -# Show detailed library information -.PHONY: lib-info -lib-info: - @echo "$(BLUE)Library Information:$(NC)" - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.a ]; then \ - echo "$(GREEN)Static library:$(NC)"; \ - echo " Path: $(BUILD_DIR)/lib/libgopher-orch.a"; \ - echo " Size: $$(du -h $(BUILD_DIR)/lib/libgopher-orch.a | cut -f1)"; \ - if command -v ar >/dev/null 2>&1; then \ - echo " Objects: $$(ar -t $(BUILD_DIR)/lib/libgopher-orch.a 2>/dev/null | wc -l) files"; \ - fi; \ - else \ - echo "$(YELLOW)Static library not found$(NC)"; \ - fi - @echo "" - @if [ -f $(BUILD_DIR)/lib/libgopher-orch.so ] || [ -f $(BUILD_DIR)/lib/libgopher-orch.dylib ]; then \ - echo "$(GREEN)Shared library:$(NC)"; \ - LIB_PATH=$$(find $(BUILD_DIR)/lib -name "libgopher-orch.so*" -o -name "libgopher-orch.dylib" | head -1); \ - if [ -n "$$LIB_PATH" ]; then \ - echo " Path: $$LIB_PATH"; \ - echo " Size: $$(du -h $$LIB_PATH | cut -f1)"; \ - if command -v ldd >/dev/null 2>&1; then \ - echo " Dependencies:"; \ - ldd $$LIB_PATH | head -5 | sed 's/^/ /'; \ - elif command -v otool >/dev/null 2>&1; then \ - echo " Dependencies:"; \ - otool -L $$LIB_PATH | head -5 | sed 's/^/ /'; \ - fi; \ - fi; \ - else \ - echo "$(YELLOW)Shared library not found$(NC)"; \ - fi - @echo "" - @if [ -f $(BUILD_DIR)/CMakeCache.txt ]; then \ - echo "$(BLUE)Current configuration:$(NC)"; \ - grep -E "^(BUILD_SHARED_LIBS|BUILD_STATIC_LIBS):BOOL=" $(BUILD_DIR)/CMakeCache.txt | sed 's/^/ /'; \ - fi - -# Show build configuration -.PHONY: info -info: - @echo "$(BLUE)Build Configuration:$(NC)" - @echo " Build directory: $(BUILD_DIR)" - @echo " Build type: $(BUILD_TYPE)" - @echo " Generator: $(GENERATOR)" - @echo " Parallel jobs: $(PARALLEL_JOBS)" - @echo " Build static libs: $(BUILD_STATIC)" - @echo " Build shared libs: $(BUILD_SHARED)" - @echo " CMake options: $(CMAKE_OPTIONS)" - @if [ -f $(BUILD_DIR)/CMakeCache.txt ]; then \ - echo "\n$(BLUE)Current CMake cache:$(NC)"; \ - grep -E "^(CMAKE_BUILD_TYPE|BUILD_SHARED_LIBS|BUILD_STATIC_LIBS|USE_SUBMODULE_GOPHER_MCP)" $(BUILD_DIR)/CMakeCache.txt || true; \ - else \ - echo "\n$(YELLOW)No build directory found. Run 'make configure' first.$(NC)"; \ - fi - -# Help target -.PHONY: help -help: - @echo "$(BLUE)gopher-orch Build System$(NC)" - @echo "" - @echo "$(GREEN)Common targets:$(NC)" - @echo " make - Build both libraries and run tests (default)" - @echo " make build - Build both static and shared libraries" - @echo " make release - Build and test in release mode" - @echo " make test - Run tests" - @echo " make clean - Clean build directory" - @echo " make install - Install the libraries" - @echo " make uninstall - Uninstall the libraries" - @echo "" - @echo "$(GREEN)Library build targets:$(NC)" - @echo " make both - Build both library types (default)" - @echo " make static - Build static library only (with clean)" - @echo " make shared - Build shared library only (with clean)" - @echo " make libs - Build libraries (current config)" - @echo " make lib-info - Show detailed library information" - @echo "" - @echo "$(GREEN)Build modes:$(NC)" - @echo " make debug - Build in debug mode" - @echo " make release - Build in release mode" - @echo " make standalone - Build without gopher-mcp" - @echo "" - @echo "$(GREEN)Test targets:$(NC)" - @echo " make test-verbose - Run tests with verbose output" - @echo " make test-parallel - Run tests in parallel" - @echo " make test-one TEST=name - Run specific test" - @echo "" - @echo "$(GREEN)Component targets:$(NC)" - @echo " make libs - Build only libraries" - @echo " make examples - Build examples" - @echo " make run-hello - Run hello world example" - @echo "" - @echo "$(GREEN)Dependency management:$(NC)" - @echo " make update-submodules - Update git submodules" - @echo " make use-system-gopher-mcp - Use system gopher-mcp" - @echo " make use-submodule-gopher-mcp - Use submodule gopher-mcp" - @echo "" - @echo "$(GREEN)Code Quality:$(NC)" - @echo " make format - Auto-format all source files" - @echo " make check-format - Check formatting without modifying" - @echo " make format-check - Alias for check-format" - @echo "" - @echo "$(GREEN)Utilities:$(NC)" - @echo " make docs - Generate documentation" - @echo " make info - Show build configuration" - @echo " make distclean - Deep clean including submodules" - @echo "" - @echo "$(GREEN)Variables:$(NC)" - @echo " BUILD_DIR=dir - Set build directory (default: build)" - @echo " BUILD_TYPE=type - Set build type (Debug/Release, default: Debug)" - @echo " BUILD_STATIC=ON/OFF - Build static library (default: ON)" - @echo " BUILD_SHARED=ON/OFF - Build shared library (default: ON)" - @echo " CMAKE_OPTIONS=opts - Additional CMake options" - @echo " PARALLEL_JOBS=n - Number of parallel jobs" - @echo "" - @echo "$(GREEN)Examples:$(NC)" - @echo " make - Build both libraries (default)" - @echo " make BUILD_SHARED=OFF - Build static library only" - @echo " make BUILD_TYPE=Release - Build both libraries in release mode" - @echo " make static - Build only static library" - -.DEFAULT_GOAL := all diff --git a/README.md b/README.md new file mode 100644 index 00000000..90b53127 --- /dev/null +++ b/README.md @@ -0,0 +1,652 @@ +# gopher-orch - Rust SDK + +Rust SDK for Gopher Orch - AI Agent orchestration framework with native C++ performance. + +## Table of Contents + +- [Features](#features) +- [When to Use This SDK](#when-to-use-this-sdk) +- [Architecture](#architecture) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Building from Source](#building-from-source) + - [Prerequisites](#prerequisites) + - [Step 1: Clone the Repository](#step-1-clone-the-repository) + - [Step 2: Build Everything](#step-2-build-everything) + - [Step 3: Verify the Build](#step-3-verify-the-build) + - [Step 4: Run Tests](#step-4-run-tests) +- [Native Library Details](#native-library-details) + - [Library Location](#library-location) + - [Platform-Specific Library Names](#platform-specific-library-names) + - [Library Search Order](#library-search-order) +- [API Documentation](#api-documentation) + - [GopherAgent](#gopheragent) + - [ConfigBuilder](#configbuilder) + - [Error Handling](#error-handling) +- [Examples](#examples) + - [Basic Usage with API Key](#basic-usage-with-api-key) + - [Using Local MCP Servers](#using-local-mcp-servers) + - [Running the Example](#running-the-example) +- [Development](#development) + - [Project Structure](#project-structure) + - [Build Scripts](#build-scripts) + - [Rebuilding Native Library](#rebuilding-native-library) + - [Updating Submodules](#updating-submodules) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [License](#license) +- [Links](#links) +- [Acknowledgments](#acknowledgments) + +--- + +## Features + +- **Native Performance** - Powered by C++ core with Rust bindings via libloading +- **AI Agent Framework** - Build intelligent agents with LLM integration +- **MCP Protocol** - Model Context Protocol client and server support +- **Tool Orchestration** - Manage and execute tools across multiple MCP servers +- **State Management** - Built-in state graph for complex workflows +- **Memory Safety** - Rust's ownership system with zero-cost abstractions + +## When to Use This SDK + +This SDK is ideal for: + +- **Rust applications** that need high-performance AI agent orchestration +- **Systems programming** requiring MCP protocol support with memory safety +- **CLI tools** integrating AI agents with native performance +- **WebAssembly targets** (with appropriate native library builds) +- **Embedded systems** needing lightweight agent infrastructure + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Your Application │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Rust SDK (gopher_orch) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ GopherAgent │ │ConfigBuilder│ │ Error Types │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ FFI (libloading) + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Native Library (libgopher-orch) │ +│ ┌───────────────┐ ┌───────────────┐ ┌─────────────────┐ │ +│ │ Agent Engine │ │ LLM Providers │ │ MCP Client │ │ +│ │ │ │ - Anthropic │ │ - HTTP/SSE │ │ +│ │ │ │ - OpenAI │ │ - Tool Registry │ │ +│ └───────────────┘ └───────────────┘ └─────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ MCP Servers │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ +│ │ Weather API │ │ Database │ │ Custom Tools │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Installation + +### Option 1: Cargo (when published) + +```toml +[dependencies] +gopher-orch = "0.1.0" +``` + +### Option 2: Git Dependency + +```toml +[dependencies] +gopher-orch = { git = "https://github.com/GopherSecurity/gopher-mcp-rust.git" } +``` + +### Option 3: Build from Source + +See [Building from Source](#building-from-source) section below. + +## Quick Start + +```rust +use gopher_orch::{GopherAgent, ConfigBuilder}; + +fn main() -> Result<(), Box> { + // Create an agent with API key (fetches server config from remote API) + let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_api_key("your-api-key") + .build(); + + let agent = GopherAgent::create(config)?; + + // Run the agent + let result = agent.run("What is the weather in Tokyo?")?; + println!("{}", result); + + // Agent is automatically cleaned up when dropped + Ok(()) +} +``` + +--- + +## Building from Source + +This SDK wraps a native C++ library via libloading. You must build the native library before using the SDK. + +### Prerequisites + +| Requirement | Version | Notes | +|-------------|---------|-------| +| Rust | >= 1.64 | Stable toolchain | +| Cargo | Latest | Comes with Rust | +| Git | Latest | For cloning and submodules | +| CMake | >= 3.15 | Native library build system | +| C++ Compiler | C++14+ | Clang (macOS), GCC (Linux), MSVC (Windows) | + +**Platform-specific requirements:** + +- **macOS**: Xcode Command Line Tools (`xcode-select --install`) +- **Linux**: `build-essential`, `libssl-dev` +- **Windows**: Visual Studio 2019+ with C++ workload + +### Step 1: Clone the Repository + +```bash +git clone https://github.com/GopherSecurity/gopher-mcp-rust.git +cd gopher-mcp-rust +``` + +### Step 2: Build Everything + +**Using build.sh (recommended)** + +The `build.sh` script handles everything automatically: + +```bash +./build.sh +``` + +**Using build.sh with Multiple GitHub Accounts:** + +If you have multiple GitHub accounts configured with SSH host aliases, use the `GITHUB_SSH_HOST` environment variable: + +```bash +# Use custom SSH host alias for cloning private submodules +GITHUB_SSH_HOST=your-ssh-alias ./build.sh + +# Example: if your ~/.ssh/config has "Host github-work" for work account +GITHUB_SSH_HOST=github-work ./build.sh +``` + +**What happens during build:** + +1. **Submodule update** - Initializes and updates submodules (with SSH URL rewriting if `GITHUB_SSH_HOST` is set) +2. **CMake configure** - Configures the C++ build with Release settings +3. **Native compilation** - Compiles C++ to shared libraries +4. **Library installation** - Copies libraries to `native/lib/` +5. **Dependency copying** - Copies required dependencies (gopher-mcp, fmt) +6. **macOS fixes** - Fixes dylib install names for proper runtime loading +7. **Cargo build** - Compiles Rust SDK +8. **Tests** - Runs Cargo tests + +### Step 3: Verify the Build + +```bash +# Check native libraries were built +ls -la native/lib/ + +# Expected output (macOS): +# libgopher-orch.dylib +# libgopher-mcp.dylib +# libgopher-mcp-event.dylib +# libfmt.dylib + +# Verify Rust build +cargo build +``` + +### Step 4: Run Tests + +```bash +cargo test +``` + +--- + +## Native Library Details + +### Library Location + +After building, native libraries are installed to: + +``` +native/ +├── lib/ # Shared libraries +│ ├── libgopher-orch.dylib # Main orchestration library (macOS) +│ ├── libgopher-orch.so # Main orchestration library (Linux) +│ ├── libgopher-mcp.dylib # MCP protocol library +│ ├── libgopher-mcp-event.dylib # Event handling +│ └── libfmt.dylib # Formatting library +└── include/ # C++ headers (for development) + └── orch/ + └── core/ +``` + +### Platform-Specific Library Names + +| Platform | Library Extension | Example | +|----------|------------------|---------| +| macOS | `.dylib` | `libgopher-orch.dylib` | +| Linux | `.so` | `libgopher-orch.so` | +| Windows | `.dll` | `gopher-orch.dll` | + +### Library Search Order + +The SDK searches for the native library in this order: + +1. `native/lib/` relative to executable +2. `./native/lib/` relative to working directory +3. `CARGO_MANIFEST_DIR/native/lib/` (compile time) +4. System library paths + +--- + +## API Documentation + +### GopherAgent + +The main struct for creating and running AI agents: + +```rust +use gopher_orch::{GopherAgent, ConfigBuilder, AgentResult}; + +// Initialize the library (called automatically on first create) +gopher_orch::init()?; + +// Create with API key (fetches server config from remote API) +let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_api_key("your-api-key") + .build(); + +let agent = GopherAgent::create(config)?; + +// Or create with JSON server config +let server_config = r#" + { + "succeeded": true, + "data": { + "servers": [{ + "serverId": "server1", + "name": "My MCP Server", + "transport": "http_sse", + "config": {"url": "http://localhost:3001/mcp"} + }] + } + } +"#; + +let agent = GopherAgent::create_with_server_config( + "AnthropicProvider", + "claude-3-haiku-20240307", + server_config, +)?; + +// Run a query +let result = agent.run("Your prompt here")?; + +// Run with custom timeout (default: 60000ms) +let result = agent.run_with_timeout("Your prompt here", 30000)?; + +// Run with detailed result information +let detailed: AgentResult = agent.run_detailed("Your prompt here"); +// Returns AgentResult with: response(), status(), iteration_count(), tokens_used() + +// Manual cleanup (optional - happens automatically on drop) +drop(agent); + +// Shutdown library +gopher_orch::shutdown(); +``` + +### ConfigBuilder + +Builder for creating agent configurations: + +```rust +use gopher_orch::ConfigBuilder; + +// With API key +let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_api_key("your-api-key") + .build(); + +// With server config +let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_server_config(r#"{"succeeded": true, "data": {"servers": []}}"#) + .build(); + +// Check configuration +assert!(config.has_api_key()); +assert!(!config.has_server_config()); +``` + +### Error Handling + +The SDK provides typed errors for different failure scenarios: + +```rust +use gopher_orch::{GopherAgent, ConfigBuilder, Error}; + +fn main() { + let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_api_key("invalid-key") + .build(); + + match GopherAgent::create(config) { + Ok(agent) => { + match agent.run("query") { + Ok(result) => println!("{}", result), + Err(Error::Timeout(msg)) => eprintln!("Query timed out: {}", msg), + Err(e) => eprintln!("Query error: {}", e), + } + } + Err(Error::Library(msg)) => eprintln!("Library not found: {}", msg), + Err(Error::Config(msg)) => eprintln!("Invalid config: {}", msg), + Err(Error::Agent(msg)) => eprintln!("Agent error: {}", msg), + Err(e) => eprintln!("Error: {}", e), + } +} +``` + +--- + +## Examples + +### Basic Usage with API Key + +```rust +use gopher_orch::{GopherAgent, ConfigBuilder}; +use std::env; + +fn main() -> Result<(), Box> { + let api_key = env::var("GOPHER_API_KEY")?; + + let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_api_key(&api_key) + .build(); + + let agent = GopherAgent::create(config)?; + + let answer = agent.run("What time is it in London?")?; + println!("Answer: {}", answer); + + Ok(()) +} +``` + +### Using Local MCP Servers + +```rust +use gopher_orch::{GopherAgent, ConfigBuilder}; + +const SERVER_CONFIG: &str = r#"{ + "succeeded": true, + "code": 200, + "message": "OK", + "data": { + "servers": [ + { + "version": "1.0.0", + "serverId": "weather-server", + "name": "Weather Service", + "transport": "http_sse", + "config": { + "url": "http://localhost:3001/mcp", + "headers": {} + }, + "connectTimeout": 5000, + "requestTimeout": 30000 + } + ] + } +}"#; + +fn main() -> Result<(), Box> { + let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_server_config(SERVER_CONFIG) + .build(); + + let agent = GopherAgent::create(config)?; + + let result = agent.run("What is the weather in New York?")?; + println!("{}", result); + + Ok(()) +} +``` + +### Running the Example + +```bash +# Run with the convenience script (starts servers automatically) +cd examples +./client_example_json_run.sh + +# Or manually: +# Terminal 1: Start server3001 +cd examples/server3001 && npm install && npm run dev + +# Terminal 2: Start server3002 +cd examples/server3002 && npm install && npm run dev + +# Terminal 3: Run the Rust client +ANTHROPIC_API_KEY=your-key cargo run --example client_example_json +``` + +--- + +## Development + +### Project Structure + +``` +gopher-mcp-rust/ +├── src/ +│ ├── lib.rs # Library entry point +│ ├── agent.rs # GopherAgent implementation +│ ├── config.rs # Configuration builder +│ ├── error.rs # Error types +│ ├── result.rs # AgentResult types +│ └── ffi.rs # FFI bindings (libloading) +├── tests/ +│ └── ffi_tests.rs # Integration tests +├── native/ # Native libraries (generated) +│ ├── lib/ # Shared libraries (.dylib, .so, .dll) +│ └── include/ # C++ headers +├── third_party/ # Git submodules +│ └── gopher-orch/ # C++ implementation +├── examples/ # Example code +│ ├── client_example_json.rs +│ ├── client_example_json_run.sh +│ ├── server3001/ # Mock weather MCP server +│ └── server3002/ # Mock tools MCP server +├── build.sh # Build orchestration script +├── Cargo.toml # Cargo build configuration +└── README.md +``` + +### Build Scripts + +| Script | Description | +|--------|-------------| +| `./build.sh` | Full build (submodules + native + Rust SDK) | +| `GITHUB_SSH_HOST=alias ./build.sh` | Build with custom SSH host | +| `cargo build` | Compile Rust SDK | +| `cargo test` | Run tests | +| `cargo build --release` | Release build | +| `cargo run --example client_example_json` | Run example | + +### Rebuilding Native Library + +If you modify the C++ code or switch branches: + +```bash +# Clean and rebuild +rm -rf third_party/gopher-orch/build native/lib +./build.sh +``` + +### Updating Submodules + +To pull latest changes from native libraries: + +```bash +# Update to latest commit +cd third_party/gopher-orch +git fetch origin +git checkout +cd ../.. + +# Rebuild +rm -rf native/lib +./build.sh +``` + +--- + +## Troubleshooting + +### "Library not found" Error + +**Cause**: Native library not built or not in expected location. + +**Solution**: +```bash +# Rebuild native library +./build.sh + +# Verify library exists +ls native/lib/libgopher-orch.* +``` + +### "Submodule is empty" Error + +**Cause**: Git submodules not initialized. + +**Solution**: +```bash +git submodule update --init --recursive +``` + +### CMake Configuration Fails + +**Cause**: Missing dependencies or wrong CMake version. + +**Solution**: +```bash +# macOS +brew install cmake + +# Linux (Ubuntu/Debian) +sudo apt-get install cmake build-essential libssl-dev + +# Verify version +cmake --version # Should be >= 3.15 +``` + +### Linking Error at Runtime + +**Cause**: libloading can't find the native library. + +**Solution**: +```bash +# Run from project root +cargo run --example client_example_json + +# Or set library path +export DYLD_LIBRARY_PATH=$PWD/native/lib:$DYLD_LIBRARY_PATH # macOS +export LD_LIBRARY_PATH=$PWD/native/lib:$LD_LIBRARY_PATH # Linux +``` + +### Build Fails on Apple Silicon (M1/M2) + +**Cause**: Architecture mismatch. + +**Solution**: +```bash +# Ensure using native arm64 toolchain +arch -arm64 ./build.sh +``` + +### Rust Version Compatibility + +**Cause**: Using older Rust toolchain. + +**Solution**: +```bash +# Update Rust +rustup update stable + +# Or use minimum supported version +rustup install 1.64 +rustup default 1.64 +``` + +--- + +## Contributing + +Contributions are welcome! Please read our contributing guidelines. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Ensure submodules are initialized (`git submodule update --init --recursive`) +4. Make your changes +5. Run tests (`cargo test`) +6. Commit your changes (`git commit -m 'Add amazing feature'`) +7. Push to the branch (`git push origin feature/amazing-feature`) +8. Open a Pull Request + +--- + +## License + +MIT License - see [LICENSE](LICENSE) file for details. + +## Links + +- [GitHub Repository](https://github.com/GopherSecurity/gopher-mcp-rust) +- [Java SDK](https://github.com/GopherSecurity/gopher-mcp-java) +- [Python SDK](https://github.com/GopherSecurity/gopher-mcp-python) +- [TypeScript SDK](https://github.com/GopherSecurity/gopher-orch-js) +- [Native C++ Implementation](https://github.com/GopherSecurity/gopher-orch) +- [Model Context Protocol](https://modelcontextprotocol.io/) + +## Acknowledgments + +- Built on [gopher-orch](https://github.com/GopherSecurity/gopher-orch) C++ framework +- Uses [gopher-mcp](https://github.com/GopherSecurity/gopher-mcp) for MCP protocol +- Inspired by LangChain and LangGraph +- FFI bindings via [libloading](https://github.com/nagisa/rust_libloading) diff --git a/build.sh b/build.sh index 3aa8a1ca..c36aa4a5 100755 --- a/build.sh +++ b/build.sh @@ -1,123 +1,210 @@ -#!/bin/bash -x +#!/bin/bash -# Build script for gopher-orch with submodule support - -set -e +set -e # Exit on error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' -BLUE='\033[0;34m' NC='\033[0m' # No Color -echo -e "${BLUE}=== gopher-orch Build Script ===${NC}" - -# Parse arguments -BUILD_TYPE="${BUILD_TYPE:-Debug}" -BUILD_DIR="${BUILD_DIR:-build}" -USE_SUBMODULE=ON -BUILD_TESTS=ON -BUILD_EXAMPLES=ON - -for arg in "$@"; do - case $arg in - --release) - BUILD_TYPE=Release - shift - ;; - --no-submodule) - USE_SUBMODULE=OFF - shift - ;; - --no-tests) - BUILD_TESTS=OFF - shift - ;; - --no-examples) - BUILD_EXAMPLES=OFF - shift - ;; - --standalone) - # Build without gopher-mcp for testing - USE_SUBMODULE=OFF - BUILD_WITHOUT_MCP=ON - shift - ;; - --clean) - echo -e "${YELLOW}Cleaning build directory...${NC}" - rm -rf "$BUILD_DIR" - shift - ;; - --help) - echo "Usage: $0 [options]" - echo "Options:" - echo " --release Build in Release mode (default: Debug)" - echo " --no-submodule Use system gopher-mcp instead of submodule" - echo " --no-tests Don't build tests" - echo " --no-examples Don't build examples" - echo " --standalone Build without gopher-mcp dependency" - echo " --clean Clean build directory before building" - echo " --help Show this help message" - exit 0 - ;; - esac -done - -# Initialize submodule if needed -if [ "$USE_SUBMODULE" = "ON" ] && [ "${BUILD_WITHOUT_MCP:-OFF}" = "OFF" ]; then - if [ ! -f "third_party/gopher-mcp/CMakeLists.txt" ]; then - echo -e "${YELLOW}Initializing gopher-mcp submodule...${NC}" - git submodule update --init --recursive third_party/gopher-mcp - else - echo -e "${GREEN}gopher-mcp submodule already initialized${NC}" +# Get the script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NATIVE_DIR="${SCRIPT_DIR}/third_party/gopher-orch" +BUILD_DIR="${NATIVE_DIR}/build" + +# Handle --clean flag (cleans CMake cache but preserves _deps) +if [ "$1" = "--clean" ]; then + echo -e "${YELLOW}Cleaning build artifacts (preserving _deps)...${NC}" + rm -rf "${SCRIPT_DIR}/native" + rm -rf "${SCRIPT_DIR}/target" + rm -f "${BUILD_DIR}/CMakeCache.txt" + rm -rf "${BUILD_DIR}/CMakeFiles" + rm -rf "${BUILD_DIR}/lib" + rm -rf "${BUILD_DIR}/bin" + echo -e "${GREEN}✓ Clean complete${NC}" + if [ "$2" != "--build" ]; then + exit 0 + fi +fi + +echo -e "${GREEN}======================================${NC}" +echo -e "${GREEN}Building gopher-orch Rust SDK${NC}" +echo -e "${GREEN}======================================${NC}" +echo "" + +# Step 1: Update submodules recursively +echo -e "${YELLOW}Step 1: Updating submodules...${NC}" + +# Support custom SSH host for multiple GitHub accounts +# Usage: GITHUB_SSH_HOST=bettercallsaulj ./build.sh +SSH_HOST="${GITHUB_SSH_HOST:-github.com}" +if [ -n "${GITHUB_SSH_HOST}" ]; then + echo -e "${YELLOW} Using custom SSH host: ${GITHUB_SSH_HOST}${NC}" +fi + +# Configure SSH URL rewrite for GopherSecurity repos +git config --local url."git@${SSH_HOST}:GopherSecurity/".insteadOf "https://github.com/GopherSecurity/" +git config --local submodule.third_party/gopher-orch.url "git@${SSH_HOST}:GopherSecurity/gopher-orch.git" + +# Update main submodule +if ! git submodule update --init 2>/dev/null; then + echo -e "${RED}Error: Failed to clone gopher-orch submodule${NC}" + echo -e "${YELLOW}If you have multiple GitHub accounts, use:${NC}" + echo -e " GITHUB_SSH_HOST=your-ssh-alias ./build.sh" + exit 1 +fi + +# Update nested submodule (gopher-mcp inside gopher-orch) +# Note: gopher-orch/.gitmodules has 'update = none' so we must explicitly update +if [ -d "${NATIVE_DIR}" ]; then + cd "${NATIVE_DIR}" + git config --local url."git@${SSH_HOST}:GopherSecurity/".insteadOf "https://github.com/GopherSecurity/" + # Override 'update = none' by using --checkout + git submodule update --init --checkout third_party/gopher-mcp 2>/dev/null || true + # Also update gopher-mcp's nested submodules recursively + if [ -d "third_party/gopher-mcp" ]; then + cd third_party/gopher-mcp + git config --local url."git@${SSH_HOST}:GopherSecurity/".insteadOf "https://github.com/GopherSecurity/" + git submodule update --init --recursive 2>/dev/null || true fi + cd "${SCRIPT_DIR}" +fi + +echo -e "${GREEN}✓ Submodules updated${NC}" +echo "" + +# Step 2: Check if gopher-orch exists +if [ ! -d "${NATIVE_DIR}" ]; then + echo -e "${RED}Error: gopher-orch submodule not found at ${NATIVE_DIR}${NC}" + echo -e "${RED}Run: git submodule update --init --recursive${NC}" + exit 1 fi +# Step 3: Build gopher-orch native library +echo -e "${YELLOW}Step 2: Building gopher-orch native library...${NC}" +cd "${NATIVE_DIR}" + # Create build directory -mkdir -p "$BUILD_DIR" - -# Configure -echo -e "${BLUE}Configuring with CMake...${NC}" -echo " Build type: $BUILD_TYPE" -echo " Use submodule: $USE_SUBMODULE" -echo " Build tests: $BUILD_TESTS" -echo " Build examples: $BUILD_EXAMPLES" - -CMAKE_ARGS=( - -DCMAKE_BUILD_TYPE="$BUILD_TYPE" - -DUSE_SUBMODULE_GOPHER_MCP="$USE_SUBMODULE" - -DBUILD_TESTS="$BUILD_TESTS" - -DBUILD_EXAMPLES="$BUILD_EXAMPLES" -) - -if [ "${BUILD_WITHOUT_MCP:-OFF}" = "ON" ]; then - CMAKE_ARGS+=(-DBUILD_WITHOUT_GOPHER_MCP=ON) - echo -e "${YELLOW}Building without gopher-mcp dependency (standalone mode)${NC}" +if [ ! -d "${BUILD_DIR}" ]; then + mkdir -p "${BUILD_DIR}" fi -cmake -B "$BUILD_DIR" -S . "${CMAKE_ARGS[@]}" +cd "${BUILD_DIR}" + +# Configure with CMake +echo -e "${YELLOW} Configuring CMake...${NC}" +cmake .. \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="${SCRIPT_DIR}/native" \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_POSITION_INDEPENDENT_CODE=ON # Build -echo -e "${BLUE}Building...${NC}" -cmake --build "$BUILD_DIR" -j$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) +echo -e "${YELLOW} Compiling...${NC}" +cmake --build . --config Release -j$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4) + +# Install to native directory +echo -e "${YELLOW} Installing...${NC}" +cmake --install . + +# Copy dependency libraries (gopher-mcp, fmt) that gopher-orch depends on +echo -e "${YELLOW} Copying dependency libraries...${NC}" +NATIVE_LIB_DIR="${SCRIPT_DIR}/native/lib" +mkdir -p "${NATIVE_LIB_DIR}" + +# Copy gopher-mcp libraries +cp -P "${BUILD_DIR}/lib/libgopher-mcp"*.dylib "${NATIVE_LIB_DIR}/" 2>/dev/null || \ +cp -P "${BUILD_DIR}/lib/libgopher-mcp"*.so "${NATIVE_LIB_DIR}/" 2>/dev/null || true + +# Copy fmt library +cp -P "${BUILD_DIR}/lib/libfmt"*.dylib "${NATIVE_LIB_DIR}/" 2>/dev/null || \ +cp -P "${BUILD_DIR}/lib/libfmt"*.so "${NATIVE_LIB_DIR}/" 2>/dev/null || true + +# Fix library install names on macOS (remove @rpath so DYLD_LIBRARY_PATH works) +if [[ "$OSTYPE" == "darwin"* ]]; then + echo -e "${YELLOW} Fixing library install names for macOS...${NC}" + cd "${NATIVE_LIB_DIR}" + + # Fix install names (the library's own ID) + [ -f "libgopher-orch.0.1.0.dylib" ] && install_name_tool -id "libgopher-orch.0.dylib" libgopher-orch.0.1.0.dylib + [ -f "libgopher-mcp.0.1.0.dylib" ] && install_name_tool -id "libgopher-mcp.0.dylib" libgopher-mcp.0.1.0.dylib + [ -f "libgopher-mcp-event.0.1.0.dylib" ] && install_name_tool -id "libgopher-mcp-event.0.dylib" libgopher-mcp-event.0.1.0.dylib + [ -f "libgopher-mcp-logging.dylib" ] && install_name_tool -id "libgopher-mcp-logging.dylib" libgopher-mcp-logging.dylib + [ -f "libfmt.10.2.1.dylib" ] && install_name_tool -id "libfmt.10.dylib" libfmt.10.2.1.dylib + + # Fix @rpath references in libgopher-mcp-event + if [ -f "libgopher-mcp-event.0.1.0.dylib" ]; then + install_name_tool -change "@rpath/libgopher-mcp.0.dylib" "libgopher-mcp.0.dylib" libgopher-mcp-event.0.1.0.dylib + install_name_tool -change "@rpath/libgopher-mcp-logging.dylib" "libgopher-mcp-logging.dylib" libgopher-mcp-event.0.1.0.dylib + install_name_tool -change "@rpath/libfmt.10.dylib" "libfmt.10.dylib" libgopher-mcp-event.0.1.0.dylib + fi -echo -e "${GREEN}Build completed successfully!${NC}" + cd "${SCRIPT_DIR}" +fi + +echo -e "${GREEN}✓ Native library built successfully${NC}" +echo "" + +# Step 4: Verify build artifacts +echo -e "${YELLOW}Step 3: Verifying native build artifacts...${NC}" -# Run tests if built -if [ "$BUILD_TESTS" = "ON" ]; then - echo -e "${BLUE}Running tests...${NC}" - (cd "$BUILD_DIR" && ctest --output-on-failure) || { - echo -e "${RED}Some tests failed${NC}" - exit 1 - } - echo -e "${GREEN}All tests passed!${NC}" +NATIVE_LIB_DIR="${SCRIPT_DIR}/native/lib" +NATIVE_INCLUDE_DIR="${SCRIPT_DIR}/native/include" + +if [ -d "${NATIVE_LIB_DIR}" ]; then + echo -e "${GREEN}✓ Libraries installed to: ${NATIVE_LIB_DIR}${NC}" + ls -lh "${NATIVE_LIB_DIR}"/*.dylib 2>/dev/null || ls -lh "${NATIVE_LIB_DIR}"/*.so 2>/dev/null || true +else + echo -e "${YELLOW}⚠ Library directory not found: ${NATIVE_LIB_DIR}${NC}" +fi + +if [ -d "${NATIVE_INCLUDE_DIR}" ]; then + echo -e "${GREEN}✓ Headers installed to: ${NATIVE_INCLUDE_DIR}${NC}" +else + echo -e "${YELLOW}⚠ Include directory not found: ${NATIVE_INCLUDE_DIR}${NC}" fi -# Show example usage -if [ "$BUILD_EXAMPLES" = "ON" ] && [ -f "$BUILD_DIR/bin/hello_world_example" ]; then - echo -e "${BLUE}Example built:${NC}" - echo " Run: ./$BUILD_DIR/bin/hello_world_example" +echo "" + +# Step 4: Build Rust SDK +echo -e "${YELLOW}Step 4: Building Rust SDK...${NC}" +cd "${SCRIPT_DIR}" + +# Check for Rust/Cargo +if ! command -v cargo &> /dev/null; then + echo -e "${RED}Error: Cargo not found. Please install Rust first.${NC}" + echo -e "${YELLOW} Install: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh${NC}" + exit 1 fi -echo -e "${GREEN}=== Build Complete ===${NC}" +# Build with Cargo +echo -e "${YELLOW} Compiling Rust SDK...${NC}" +LIBRARY_PATH="${NATIVE_LIB_DIR}" \ +LD_LIBRARY_PATH="${NATIVE_LIB_DIR}" \ +DYLD_LIBRARY_PATH="${NATIVE_LIB_DIR}" \ +cargo build --release + +echo -e "${GREEN}✓ Rust SDK built successfully${NC}" +echo "" + +# Step 5: Run tests +echo -e "${YELLOW}Step 5: Running tests...${NC}" +LIBRARY_PATH="${NATIVE_LIB_DIR}" \ +LD_LIBRARY_PATH="${NATIVE_LIB_DIR}" \ +DYLD_LIBRARY_PATH="${NATIVE_LIB_DIR}" \ +cargo test && echo -e "${GREEN}✓ Tests passed${NC}" || echo -e "${YELLOW}⚠ Some tests may have failed (native library required)${NC}" + +echo "" +echo -e "${GREEN}======================================${NC}" +echo -e "${GREEN}Build completed successfully!${NC}" +echo -e "${GREEN}======================================${NC}" +echo "" +echo -e "Native libraries: ${YELLOW}${NATIVE_LIB_DIR}${NC}" +echo -e "Native headers: ${YELLOW}${NATIVE_INCLUDE_DIR}${NC}" +echo "" +echo -e "To run tests manually:" +echo -e " ${YELLOW}DYLD_LIBRARY_PATH=\$(pwd)/native/lib cargo test${NC}" +echo "" +echo -e "To build:" +echo -e " ${YELLOW}cargo build --release${NC}" diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in deleted file mode 100644 index 25ae5708..00000000 --- a/cmake/cmake_uninstall.cmake.in +++ /dev/null @@ -1,49 +0,0 @@ -# cmake_uninstall.cmake.in -# Uninstall script for gopher-orch - -if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") - message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") -endif() - -file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) -string(REGEX REPLACE "\n" ";" files "${files}") - -foreach(file ${files}) - message(STATUS "Uninstalling $ENV{DESTDIR}${file}") - if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) - if(NOT "${rm_retval}" STREQUAL 0) - message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") - endif() - else() - message(STATUS "File $ENV{DESTDIR}${file} does not exist.") - endif() -endforeach() - -# Remove empty directories -set(DIRS_TO_CHECK - "@CMAKE_INSTALL_PREFIX@/lib/cmake/gopher-orch" - "@CMAKE_INSTALL_PREFIX@/include/orch/core" - "@CMAKE_INSTALL_PREFIX@/include/orch" -) - -foreach(dir ${DIRS_TO_CHECK}) - if(EXISTS "$ENV{DESTDIR}${dir}") - file(GLOB dir_contents "$ENV{DESTDIR}${dir}/*") - list(LENGTH dir_contents n_contents) - if(n_contents EQUAL 0) - message(STATUS "Removing empty directory: $ENV{DESTDIR}${dir}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove_directory \"$ENV{DESTDIR}${dir}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) - endif() - endif() -endforeach() - -message(STATUS "Uninstall complete") diff --git a/cmake/gopher-orch-config.cmake.in b/cmake/gopher-orch-config.cmake.in deleted file mode 100644 index 89457eef..00000000 --- a/cmake/gopher-orch-config.cmake.in +++ /dev/null @@ -1,23 +0,0 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -# Find required dependencies -if(@USE_SUBMODULE_GOPHER_MCP@) - # When gopher-orch was built with submodule, users need gopher-mcp - find_dependency(gopher-mcp REQUIRED) -endif() - -# Find threads -find_dependency(Threads REQUIRED) - -# Include the targets file -include("${CMAKE_CURRENT_LIST_DIR}/gopher-orch-targets.cmake") - -# Set variables for compatibility -set(gopher-orch_FOUND TRUE) -set(gopher-orch_INCLUDE_DIRS "@CMAKE_INSTALL_PREFIX@/include") -set(gopher-orch_LIBRARIES gopher-orch) - -# Check required components -check_required_components(gopher-orch) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt deleted file mode 100644 index 240bb3ee..00000000 --- a/examples/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -# gopher-orch examples - -# Hello World example (basic orch functionality) -add_subdirectory(hello_world) - -# MCP Client example (demonstrates gopher-mcp integration) -add_subdirectory(mcp_client) diff --git a/examples/client_example_json.rs b/examples/client_example_json.rs new file mode 100644 index 00000000..4fe114c8 --- /dev/null +++ b/examples/client_example_json.rs @@ -0,0 +1,77 @@ +//! Example using JSON server configuration. + +use gopher_orch::{ConfigBuilder, GopherAgent}; +use std::env; + +/// Server configuration for local MCP servers +const SERVER_CONFIG: &str = r#"{ + "succeeded": true, + "code": 200000000, + "message": "success", + "data": { + "servers": [ + { + "version": "2025-01-09", + "serverId": "1", + "name": "server1", + "transport": "http_sse", + "config": {"url": "http://127.0.0.1:3001/mcp", "headers": {}}, + "connectTimeout": 5000, + "requestTimeout": 30000 + }, + { + "version": "2025-01-09", + "serverId": "2", + "name": "server2", + "transport": "http_sse", + "config": {"url": "http://127.0.0.1:3002/mcp", "headers": {}}, + "connectTimeout": 5000, + "requestTimeout": 30000 + } + ] + } +}"#; + +fn main() { + let provider = "AnthropicProvider"; + let model = "claude-3-haiku-20240307"; + + // Create agent with JSON server configuration + let config = ConfigBuilder::new() + .with_provider(provider) + .with_model(model) + .with_server_config(SERVER_CONFIG) + .build(); + + let agent = match GopherAgent::create(config) { + Ok(agent) => agent, + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); + } + }; + + println!("GopherAgent created!"); + + // Get question from command line args or use default + let args: Vec = env::args().collect(); + let question = if args.len() > 1 { + args[1..].join(" ") + } else { + "What is the weather like in New York?".to_string() + }; + + println!("Question: {}", question); + + // Run the query + match agent.run(&question) { + Ok(answer) => { + println!("Answer:"); + println!("{}", answer); + } + Err(e) => { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } +} diff --git a/examples/client_example_json_run.sh b/examples/client_example_json_run.sh new file mode 100755 index 00000000..921dcf5e --- /dev/null +++ b/examples/client_example_json_run.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +# Run the Rust client example with local MCP servers + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Get the script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# Cleanup function +cleanup() { + echo -e "\n${YELLOW}Cleaning up...${NC}" + # Kill the process groups to ensure child processes (tsx/node) are also killed + if [ -n "$SERVER3001_PID" ]; then + kill -- -$SERVER3001_PID 2>/dev/null || kill $SERVER3001_PID 2>/dev/null || true + fi + if [ -n "$SERVER3002_PID" ]; then + kill -- -$SERVER3002_PID 2>/dev/null || kill $SERVER3002_PID 2>/dev/null || true + fi + # Also kill any remaining tsx/node processes on our ports + lsof -ti:3001 | xargs kill 2>/dev/null || true + lsof -ti:3002 | xargs kill 2>/dev/null || true + echo -e "${GREEN}Done${NC}" +} + +trap cleanup EXIT + +echo -e "${GREEN}======================================${NC}" +echo -e "${GREEN}Running Rust Client Example${NC}" +echo -e "${GREEN}======================================${NC}" +echo "" + +# Check if native library exists +if [ ! -d "$PROJECT_DIR/native/lib" ]; then + echo -e "${RED}Error: Native library not found at $PROJECT_DIR/native/lib${NC}" + echo -e "${YELLOW}Please run ./build.sh first${NC}" + exit 1 +fi + +# Start server3001 +echo -e "${YELLOW}Starting server3001...${NC}" +cd "$SCRIPT_DIR/server3001" +if [ ! -d "node_modules" ]; then + echo -e "${YELLOW}Installing dependencies for server3001...${NC}" + npm install +fi +npm run dev & +SERVER3001_PID=$! +echo -e "${GREEN}server3001 started (PID: $SERVER3001_PID)${NC}" + +# Start server3002 +echo -e "${YELLOW}Starting server3002...${NC}" +cd "$SCRIPT_DIR/server3002" +if [ ! -d "node_modules" ]; then + echo -e "${YELLOW}Installing dependencies for server3002...${NC}" + npm install +fi +npm run dev & +SERVER3002_PID=$! +echo -e "${GREEN}server3002 started (PID: $SERVER3002_PID)${NC}" + +# Wait for servers to start +echo -e "${YELLOW}Waiting for servers to start...${NC}" +sleep 3 + +# Build and run the Rust example +echo "" +echo -e "${YELLOW}Building and running Rust client...${NC}" +echo "" +cd "$PROJECT_DIR" + +# Build the example +LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +LD_LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +DYLD_LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +cargo build --example client_example_json --release + +# Run the example +LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +LD_LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +DYLD_LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +./target/release/examples/client_example_json "$@" + +echo "" +echo -e "${GREEN}Example completed${NC}" diff --git a/examples/hello_world/CMakeLists.txt b/examples/hello_world/CMakeLists.txt deleted file mode 100644 index 236c04f8..00000000 --- a/examples/hello_world/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -add_executable(hello_world_example main.cpp) - -target_link_libraries(hello_world_example - gopher-orch - ${GOPHER_MCP_LIBRARIES} - Threads::Threads -) - -target_include_directories(hello_world_example PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Examples are not installed by default -# To install, use: cmake --install . --component examples diff --git a/examples/hello_world/main.cpp b/examples/hello_world/main.cpp deleted file mode 100644 index 5c79dbd8..00000000 --- a/examples/hello_world/main.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -#include "orch/core/hello.h" -#include "orch/core/version.h" - -using namespace gopher::orch::core; - -int main(int argc, char* argv[]) { - std::cout << "gopher-orch version: " << Version::string() << std::endl; - std::cout << "----------------------------------------" << std::endl; - - // Basic usage - { - std::cout << "\n1. Basic Hello usage:" << std::endl; - Hello hello; - std::cout << " " << hello.greet() << std::endl; - - hello.set_name("gopher-orch User"); - std::cout << " " << hello.greet() << std::endl; - } - - // Constructor with parameter - { - std::cout << "\n2. Parameterized constructor:" << std::endl; - Hello hello("Alice"); - std::cout << " " << hello.greet() << std::endl; - } - - // Custom prefix - { - std::cout << "\n3. Custom prefix greetings:" << std::endl; - Hello hello("Bob"); - std::cout << " " << hello.greet_with_prefix("Hi") << std::endl; - std::cout << " " << hello.greet_with_prefix("Welcome") << std::endl; - std::cout << " " << hello.greet_with_prefix("Greetings") << std::endl; - } - - // Builder pattern - { - std::cout << "\n4. Using HelloBuilder:" << std::endl; - HelloBuilder builder; - - auto hello1 = builder.with_name("Charlie").build(); - std::cout << " " << hello1->greet() << std::endl; - - auto hello2 = - builder.with_name("Diana").with_greeting_style("formal").build(); - std::cout << " " << hello2->greet() << std::endl; - } - - // Command line argument - if (argc > 1) { - std::cout << "\n5. Using command line argument:" << std::endl; - Hello hello(argv[1]); - std::cout << " " << hello.greet() << std::endl; - } - - // Multiple instances - { - std::cout << "\n6. Multiple instances:" << std::endl; - std::vector> hellos; - - hellos.push_back(std::make_unique("User1")); - hellos.push_back(std::make_unique("User2")); - hellos.push_back(std::make_unique("User3")); - - for (const auto& hello : hellos) { - std::cout << " " << hello->greet() << std::endl; - } - } - - std::cout << "\n----------------------------------------" << std::endl; - std::cout << "Example completed successfully!" << std::endl; - - return 0; -} diff --git a/examples/mcp_client/CMakeLists.txt b/examples/mcp_client/CMakeLists.txt deleted file mode 100644 index 6437f789..00000000 --- a/examples/mcp_client/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# MCP Client Example -# Demonstrates gopher-mcp integration with gopher-orch -cmake_minimum_required(VERSION 3.10) - -# Define the executable -add_executable(mcp_client_example - mcp_client_example.cc -) - -# Set target properties -set_target_properties(mcp_client_example PROPERTIES - CXX_STANDARD 14 - CXX_STANDARD_REQUIRED ON - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" -) - -# Link against gopher-orch and gopher-mcp libraries -target_link_libraries(mcp_client_example PRIVATE - gopher-orch-static - gopher-mcp - gopher-mcp-event - ${CMAKE_THREAD_LIBS_INIT} -) - -# Include directories -target_include_directories(mcp_client_example PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Examples are not installed by default -# To install, use: cmake --install . --component examples diff --git a/examples/mcp_client/mcp_client_example.cc b/examples/mcp_client/mcp_client_example.cc deleted file mode 100644 index 28575339..00000000 --- a/examples/mcp_client/mcp_client_example.cc +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @file mcp_client_example.cc - * @brief Example demonstrating gopher-orch integration with gopher-mcp - * - * This example shows how gopher-orch can extend and use gopher-mcp - * functionality. It demonstrates: - * 1. Using gopher-orch's Hello class - * 2. Using gopher-mcp types and utilities - * 3. Integration between both libraries - */ - -#include -#include -#include - -// gopher-orch includes -#include "orch/core/hello.h" -#include "orch/core/version.h" - -// gopher-mcp includes -#include "mcp/json/json_bridge.h" -#include "mcp/types.h" - -using namespace gopher::orch::core; -using namespace mcp; - -int main(int argc, char* argv[]) { - std::cout << "=== gopher-orch + gopher-mcp Integration Example ===" - << std::endl; - std::cout << std::endl; - - // Show versions - std::cout << "Versions:" << std::endl; - std::cout << " gopher-orch: " << Version::string() << std::endl; - std::cout << std::endl; - - // Demonstrate gopher-orch Hello class - std::cout << "1. gopher-orch Hello class:" << std::endl; - Hello hello("MCP User"); - std::cout << " " << hello.greet() << std::endl; - std::cout << std::endl; - - // Demonstrate gopher-mcp types - std::cout << "2. gopher-mcp types:" << std::endl; - - // Create a Tool definition - Tool calculator_tool; - calculator_tool.name = "calculator"; - calculator_tool.description = make_optional( - std::string("A simple calculator tool for basic arithmetic")); - - // Create input schema - json::JsonValue schema; - schema["type"] = "object"; - schema["properties"]["operation"]["type"] = "string"; - schema["properties"]["a"]["type"] = "number"; - schema["properties"]["b"]["type"] = "number"; - - auto required_arr = json::JsonValue::array(); - required_arr.push_back("operation"); - required_arr.push_back("a"); - required_arr.push_back("b"); - schema["required"] = required_arr; - - calculator_tool.inputSchema = make_optional(schema); - - std::cout << " Created Tool: " << calculator_tool.name << std::endl; - if (calculator_tool.description.has_value()) { - std::cout << " Description: " << calculator_tool.description.value() - << std::endl; - } - std::cout << std::endl; - - // Create a Resource definition - Resource sample_resource; - sample_resource.uri = "file:///example/data.json"; - sample_resource.name = "Example Data"; - sample_resource.description = - make_optional(std::string("Sample JSON data resource for testing")); - sample_resource.mimeType = make_optional(std::string("application/json")); - - std::cout << "3. MCP Resource:" << std::endl; - std::cout << " URI: " << sample_resource.uri << std::endl; - std::cout << " Name: " << sample_resource.name << std::endl; - if (sample_resource.mimeType.has_value()) { - std::cout << " MIME Type: " << sample_resource.mimeType.value() - << std::endl; - } - std::cout << std::endl; - - // Create a Prompt definition - Prompt greeting_prompt; - greeting_prompt.name = "greeting"; - greeting_prompt.description = - make_optional(std::string("A simple greeting prompt")); - - PromptArgument name_arg; - name_arg.name = "name"; - name_arg.description = make_optional(std::string("The name to greet")); - name_arg.required = true; - - greeting_prompt.arguments = - make_optional(std::vector{name_arg}); - - std::cout << "4. MCP Prompt:" << std::endl; - std::cout << " Name: " << greeting_prompt.name << std::endl; - if (greeting_prompt.description.has_value()) { - std::cout << " Description: " << greeting_prompt.description.value() - << std::endl; - } - if (greeting_prompt.arguments.has_value()) { - std::cout << " Arguments: " << greeting_prompt.arguments.value().size() - << std::endl; - for (const auto& arg : greeting_prompt.arguments.value()) { - std::cout << " - " << arg.name; - if (arg.required) { - std::cout << " (required)"; - } - std::cout << std::endl; - } - } - std::cout << std::endl; - - // Demonstrate JSON serialization - std::cout << "5. JSON Operations:" << std::endl; - json::JsonValue data; - data["greeting"] = hello.greet(); - data["version"] = Version::string(); - data["tool_count"] = 1; - data["resource_count"] = 1; - - std::cout << " Created JSON object with greeting and version info" - << std::endl; - std::cout << std::endl; - - // Integration example: Using gopher-orch to enhance gopher-mcp - std::cout << "6. Integration Example:" << std::endl; - HelloBuilder builder; - auto orchestrator = builder.with_name("MCP Orchestrator").build(); - std::cout << " " << orchestrator->greet() << std::endl; - std::cout - << " This demonstrates gopher-orch extending gopher-mcp capabilities" - << std::endl; - std::cout << std::endl; - - std::cout << "=== Example Complete ===" << std::endl; - std::cout - << "The gopher-orch library successfully integrates with gopher-mcp!" - << std::endl; - - return 0; -} diff --git a/examples/server3001/README.md b/examples/server3001/README.md new file mode 100644 index 00000000..379c47a8 --- /dev/null +++ b/examples/server3001/README.md @@ -0,0 +1,21 @@ +# MCP Server 3001 + +Simple MCP server with weather tools. + +## Tools + +- `get-weather` - Get current weather for a city +- `get-forecast` - Get weather forecast for a city +- `get-weather-alerts` - Get weather alerts for a region + +## Quick Start + +```bash +npm install +npm run dev +``` + +## Endpoints + +- MCP: http://127.0.0.1:3001/mcp +- Health: http://127.0.0.1:3001/health diff --git a/examples/server3001/package-lock.json b/examples/server3001/package-lock.json new file mode 100644 index 00000000..fbe27aa0 --- /dev/null +++ b/examples/server3001/package-lock.json @@ -0,0 +1,1644 @@ +{ + "name": "mcp-server-3001", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-server-3001", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "body-parser": "^2.2.0", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.5", + "@types/node": "^20.11.5", + "tsx": "^4.7.0", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "../../mcp-cpp-sdk/sdk/typescript": { + "name": "@mcp/filter-sdk", + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.20.0", + "body-parser": "^1.20.3", + "cors": "^2.8.5", + "express": "^4.21.0", + "jose": "^5.10.0", + "koffi": "^2.13.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.4", + "@types/jest": "^29.5.14", + "@types/node": "^18.19.123", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.0.0", + "jest": "^29.0.0", + "prettier": "^3.6.2", + "ts-jest": "^29.0.0", + "tsx": "^4.20.6", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0 <19.0.0" + } + }, + "../gopher-auth-sdk-nodejs": { + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.5", + "express": "^5.1.0", + "jose": "^5.2.0" + }, + "devDependencies": { + "@types/express": "^5.0.5", + "@types/jest": "^29.5.11", + "@types/node": "^20.11.5", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "prettier": "^3.2.4", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "sdk": { + "name": "@mcp/filter-sdk", + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.20.0", + "body-parser": "^1.20.3", + "cors": "^2.8.5", + "express": "^4.21.0", + "jose": "^5.10.0", + "koffi": "^2.13.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.4", + "@types/jest": "^29.5.14", + "@types/node": "^18.19.123", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.0.0", + "jest": "^29.0.0", + "prettier": "^3.6.2", + "ts-jest": "^29.0.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=16.0.0 <19.0.0" + } + } + } +} diff --git a/examples/server3001/package.json b/examples/server3001/package.json new file mode 100644 index 00000000..fa6c1510 --- /dev/null +++ b/examples/server3001/package.json @@ -0,0 +1,35 @@ +{ + "name": "mcp-server-3001", + "version": "1.0.0", + "description": "Simple MCP server (weather tools)", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc", + "dev": "tsx src/index.ts", + "start": "node dist/src/index.js", + "lint": "eslint src --ext .ts", + "format": "prettier --write \"src/**/*.ts\"" + }, + "keywords": [ + "mcp", + "model-context-protocol" + ], + "author": "", + "license": "MIT", + "dependencies": { + "body-parser": "^2.2.0", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.5", + "@types/node": "^20.11.5", + "tsx": "^4.7.0", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/examples/server3001/src/index.ts b/examples/server3001/src/index.ts new file mode 100644 index 00000000..a6a356f6 --- /dev/null +++ b/examples/server3001/src/index.ts @@ -0,0 +1,143 @@ +#!/usr/bin/env node + +/** + * Simple MCP Server (No Authentication) + */ + +import express, { Request, Response } from 'express'; +import cors from 'cors'; +import bodyParser from 'body-parser'; + +import { getWeather } from './tools/get-weather.js'; +import { getForecast } from './tools/get-forecast.js'; +import { getAlerts } from './tools/get-alerts.js'; + +const SERVER_PORT = parseInt(process.env.SERVER_PORT || '3001', 10); +const SERVER_URL = process.env.SERVER_URL || `http://127.0.0.1:${SERVER_PORT}`; +const SERVER_NAME = process.env.SERVER_NAME || 'mcp-server-3001'; +const SERVER_VERSION = process.env.SERVER_VERSION || '1.0.0'; + +const TOOLS = [ + { + name: 'get-weather', + description: 'Get current weather for a city', + inputSchema: { + type: 'object', + properties: { city: { type: 'string', description: 'City name' } }, + required: ['city'], + }, + }, + { + name: 'get-forecast', + description: 'Get weather forecast for a city', + inputSchema: { + type: 'object', + properties: { + city: { type: 'string', description: 'City name' }, + days: { type: 'number', description: 'Days (1-7)', minimum: 1, maximum: 7 }, + }, + required: ['city'], + }, + }, + { + name: 'get-weather-alerts', + description: 'Get weather alerts for a region', + inputSchema: { + type: 'object', + properties: { region: { type: 'string', description: 'Region name' } }, + required: ['region'], + }, + }, +]; + +async function startServer() { + const app = express(); + + app.use(cors({ origin: true, credentials: true })); + app.use(bodyParser.json()); + + // Health check + app.get('/health', (_req: Request, res: Response) => { + res.json({ status: 'healthy', timestamp: new Date().toISOString() }); + }); + + // MCP endpoint + app.all('/mcp', async (req: Request, res: Response) => { + const { method, params, id } = req.body || {}; + let response: any; + + switch (method) { + case 'initialize': + response = { + jsonrpc: '2.0', + result: { + protocolVersion: params?.protocolVersion || '2024-11-05', + capabilities: { tools: {} }, + serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }, + }, + id, + }; + break; + + case 'tools/list': + response = { jsonrpc: '2.0', result: { tools: TOOLS }, id }; + break; + + case 'tools/call': + try { + const toolName = params?.name; + let result: any; + + switch (toolName) { + case 'get-weather': + result = await getWeather.handler(req.body); + break; + case 'get-forecast': + result = await getForecast.handler(req.body); + break; + case 'get-weather-alerts': + result = await getAlerts.handler(req.body); + break; + default: + result = { + content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], + isError: true, + }; + } + response = { jsonrpc: '2.0', result, id }; + } catch (error) { + response = { + jsonrpc: '2.0', + error: { code: -32603, message: error instanceof Error ? error.message : 'Error' }, + id, + }; + } + break; + + default: + response = { + jsonrpc: '2.0', + error: { code: -32601, message: `Method not found: ${method}` }, + id, + }; + } + + res.json(response); + }); + + app.listen(SERVER_PORT, '127.0.0.1', () => { + console.log(`MCP Server running at ${SERVER_URL}`); + console.log(` POST ${SERVER_URL}/mcp`); + console.log(` GET ${SERVER_URL}/health`); + }); + + process.on('SIGINT', () => { + console.log('\nShutting down...'); + process.exit(0); + }); +} + +startServer().catch(error => { + console.error('Failed to start server:', error); + process.exit(1); +}); diff --git a/examples/server3001/src/tools/get-alerts.ts b/examples/server3001/src/tools/get-alerts.ts new file mode 100644 index 00000000..653d5062 --- /dev/null +++ b/examples/server3001/src/tools/get-alerts.ts @@ -0,0 +1,57 @@ +/** + * Get weather alerts + * Requires mcp:admin scope for severe weather alerts + */ +export const getAlerts = { + name: 'get-weather-alerts', + description: 'Get weather alerts and warnings for a region (requires mcp:admin scope)', + inputSchema: { + type: 'object', + properties: { + region: { + type: 'string', + description: 'Region or city name', + }, + }, + required: ['region'], + }, + handler: async (request: any) => { + const { region } = request.params; + + // Simulate weather alerts + const alerts = [ + { + severity: 'moderate', + type: 'Heavy Rain', + message: 'Heavy rain expected in the next 6 hours', + }, + { severity: 'low', type: 'Wind', message: 'Strong winds possible this evening' }, + ]; + + const hasAlerts = Math.random() > 0.5; + + if (!hasAlerts) { + return { + content: [ + { + type: 'text', + text: `No active weather alerts for ${region}`, + }, + ], + }; + } + + const alertText = alerts + .map(alert => `[${alert.severity.toUpperCase()}] ${alert.type}: ${alert.message}`) + .join('\n'); + + return { + content: [ + { + type: 'text', + text: `Weather Alerts for ${region}:\n\n${alertText}`, + }, + ], + }; + }, +}; diff --git a/examples/server3001/src/tools/get-forecast.ts b/examples/server3001/src/tools/get-forecast.ts new file mode 100644 index 00000000..2808e8dc --- /dev/null +++ b/examples/server3001/src/tools/get-forecast.ts @@ -0,0 +1,58 @@ +/** + * Get 5-day weather forecast + * Requires mcp:read scope + */ +export const getForecast = { + name: 'get-forecast', + description: + 'Get 5-day weather forecast for a city (requires authentication with mcp:read scope)', + inputSchema: { + type: 'object', + properties: { + city: { + type: 'string', + description: 'City name', + }, + days: { + type: 'number', + description: 'Number of days to forecast (1-5)', + default: 5, + }, + }, + required: ['city'], + }, + handler: async (request: any) => { + const { city, days = 5 } = request.params; + + // In a real app, you'd check authentication here + // For now, just return mock data + + const forecastDays = Math.min(days, 5); + const forecast = []; + + for (let i = 0; i < forecastDays; i++) { + const date = new Date(); + date.setDate(date.getDate() + i); + + forecast.push({ + date: date.toLocaleDateString(), + temp_high: Math.floor(Math.random() * 10) + 20, + temp_low: Math.floor(Math.random() * 10) + 10, + condition: ['Sunny', 'Cloudy', 'Rainy', 'Partly Cloudy'][Math.floor(Math.random() * 4)], + }); + } + + const forecastText = forecast + .map(day => `${day.date}: ${day.temp_low}-${day.temp_high}°C, ${day.condition}`) + .join('\n'); + + return { + content: [ + { + type: 'text', + text: `${forecastDays}-day forecast for ${city}:\n\n${forecastText}`, + }, + ], + }; + }, +}; diff --git a/examples/server3001/src/tools/get-weather.ts b/examples/server3001/src/tools/get-weather.ts new file mode 100644 index 00000000..4e1ea376 --- /dev/null +++ b/examples/server3001/src/tools/get-weather.ts @@ -0,0 +1,45 @@ +/** + * Get current weather for a city + * No authentication required - public tool + */ +export const getWeather = { + name: 'get-weather', + description: 'Get current weather information for a specific city', + inputSchema: { + type: 'object', + properties: { + city: { + type: 'string', + description: 'City name (e.g., "London", "New York", "Tokyo")', + }, + }, + required: ['city'], + }, + handler: async (request: any) => { + const { city } = request.params; + + // Simulate weather data (in a real app, you'd call a weather API) + const weatherData = { + London: { temp: 15, condition: 'Cloudy', humidity: 75 }, + 'New York': { temp: 22, condition: 'Sunny', humidity: 60 }, + Tokyo: { temp: 18, condition: 'Rainy', humidity: 80 }, + Paris: { temp: 17, condition: 'Partly Cloudy', humidity: 70 }, + Sydney: { temp: 25, condition: 'Sunny', humidity: 55 }, + }; + + const weather = weatherData[city as keyof typeof weatherData] || { + temp: Math.floor(Math.random() * 30) + 10, + condition: ['Sunny', 'Cloudy', 'Rainy', 'Partly Cloudy'][Math.floor(Math.random() * 4)], + humidity: Math.floor(Math.random() * 40) + 50, + }; + + return { + content: [ + { + type: 'text', + text: `Weather in ${city}:\nTemperature: ${weather.temp}°C\nCondition: ${weather.condition}\nHumidity: ${weather.humidity}%`, + }, + ], + }; + }, +}; diff --git a/examples/server3001/start-mcp-server.sh b/examples/server3001/start-mcp-server.sh new file mode 100755 index 00000000..5ac0cf6a --- /dev/null +++ b/examples/server3001/start-mcp-server.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Start MCP Server (Weather Tools) + +set -e + +DIR_CURR=$(cd "$(dirname "$0")";pwd) +cd $DIR_CURR + +# Stop existing server on port 3001 +lsof -i :3001 | grep LISTEN | awk '{print $2}' | xargs kill -9 2>/dev/null || true +sleep 1 + +# Install dependencies if needed +if [ ! -d "node_modules" ]; then + npm install +fi + +echo "🚀 Starting MCP Server on port 3001..." +echo " 📡 MCP Endpoint: http://127.0.0.1:3001/mcp" +echo " 💚 Health: http://127.0.0.1:3001/health" +echo "" + +npm run dev diff --git a/examples/server3001/tsconfig.json b/examples/server3001/tsconfig.json new file mode 100644 index 00000000..7f748950 --- /dev/null +++ b/examples/server3001/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*", "sdk/src/**/*"], + "exclude": ["node_modules", "dist", "src/examples"] +} diff --git a/examples/server3002/README.md b/examples/server3002/README.md new file mode 100644 index 00000000..46fe9266 --- /dev/null +++ b/examples/server3002/README.md @@ -0,0 +1,20 @@ +# MCP Server 3002 + +Simple MCP server with utility tools. + +## Tools + +- `get-time` - Get current time for a timezone or city +- `generate-password` - Generate a secure password + +## Quick Start + +```bash +npm install +npm run dev +``` + +## Endpoints + +- MCP: http://127.0.0.1:3002/mcp +- Health: http://127.0.0.1:3002/health diff --git a/examples/server3002/package-lock.json b/examples/server3002/package-lock.json new file mode 100644 index 00000000..bad6cec4 --- /dev/null +++ b/examples/server3002/package-lock.json @@ -0,0 +1,1644 @@ +{ + "name": "mcp-server-3002", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-server-3002", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "body-parser": "^2.2.0", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.5", + "@types/node": "^20.11.5", + "tsx": "^4.7.0", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "../../mcp-cpp-sdk/sdk/typescript": { + "name": "@mcp/filter-sdk", + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.20.0", + "body-parser": "^1.20.3", + "cors": "^2.8.5", + "express": "^4.21.0", + "jose": "^5.10.0", + "koffi": "^2.13.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.4", + "@types/jest": "^29.5.14", + "@types/node": "^18.19.123", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.0.0", + "jest": "^29.0.0", + "prettier": "^3.6.2", + "ts-jest": "^29.0.0", + "tsx": "^4.20.6", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0 <19.0.0" + } + }, + "../gopher-auth-sdk-nodejs": { + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.5", + "express": "^5.1.0", + "jose": "^5.2.0" + }, + "devDependencies": { + "@types/express": "^5.0.5", + "@types/jest": "^29.5.11", + "@types/node": "^20.11.5", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "prettier": "^3.2.4", + "ts-jest": "^29.1.1", + "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", + "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "sdk": { + "name": "@mcp/filter-sdk", + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.20.0", + "body-parser": "^1.20.3", + "cors": "^2.8.5", + "express": "^4.21.0", + "jose": "^5.10.0", + "koffi": "^2.13.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.4", + "@types/jest": "^29.5.14", + "@types/node": "^18.19.123", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.0.0", + "jest": "^29.0.0", + "prettier": "^3.6.2", + "ts-jest": "^29.0.0", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=16.0.0 <19.0.0" + } + } + } +} diff --git a/examples/server3002/package.json b/examples/server3002/package.json new file mode 100644 index 00000000..ef13ccff --- /dev/null +++ b/examples/server3002/package.json @@ -0,0 +1,35 @@ +{ + "name": "mcp-server-3002", + "version": "1.0.0", + "description": "Simple MCP server (time and password tools)", + "main": "dist/index.js", + "type": "module", + "scripts": { + "build": "tsc", + "dev": "tsx src/index.ts", + "start": "node dist/src/index.js", + "lint": "eslint src --ext .ts", + "format": "prettier --write \"src/**/*.ts\"" + }, + "keywords": [ + "mcp", + "model-context-protocol" + ], + "author": "", + "license": "MIT", + "dependencies": { + "body-parser": "^2.2.0", + "cors": "^2.8.5", + "express": "^5.1.0" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.5", + "@types/node": "^20.11.5", + "tsx": "^4.7.0", + "typescript": "^5.9.3" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/examples/server3002/src/index.ts b/examples/server3002/src/index.ts new file mode 100644 index 00000000..31ec8b2e --- /dev/null +++ b/examples/server3002/src/index.ts @@ -0,0 +1,138 @@ +#!/usr/bin/env node + +/** + * Simple MCP Server (No Authentication) + */ + +import express, { Request, Response } from 'express'; +import cors from 'cors'; +import bodyParser from 'body-parser'; + +import { getTime } from './tools/get-time.js'; +import { generatePassword } from './tools/generate-password.js'; + +const SERVER_PORT = parseInt(process.env.SERVER_PORT || '3002', 10); +const SERVER_URL = process.env.SERVER_URL || `http://127.0.0.1:${SERVER_PORT}`; +const SERVER_NAME = process.env.SERVER_NAME || 'mcp-server-3002'; +const SERVER_VERSION = process.env.SERVER_VERSION || '1.0.0'; + +const TOOLS = [ + { + name: 'get-time', + description: 'Get current time for a timezone or city', + inputSchema: { + type: 'object', + properties: { + location: { + type: 'string', + description: 'Timezone (e.g., "UTC", "America/New_York") or city name', + }, + }, + required: ['location'], + }, + }, + { + name: 'generate-password', + description: 'Generate a secure password', + inputSchema: { + type: 'object', + properties: { + length: { type: 'number', description: 'Length (8-128)', minimum: 8, maximum: 128 }, + includeUppercase: { type: 'boolean', description: 'Include A-Z' }, + includeLowercase: { type: 'boolean', description: 'Include a-z' }, + includeNumbers: { type: 'boolean', description: 'Include 0-9' }, + includeSymbols: { type: 'boolean', description: 'Include symbols' }, + }, + required: [], + }, + }, +]; + +async function startServer() { + const app = express(); + + app.use(cors({ origin: true, credentials: true })); + app.use(bodyParser.json()); + + // Health check + app.get('/health', (_req: Request, res: Response) => { + res.json({ status: 'healthy', timestamp: new Date().toISOString() }); + }); + + // MCP endpoint + app.all('/mcp', async (req: Request, res: Response) => { + const { method, params, id } = req.body || {}; + let response: any; + + switch (method) { + case 'initialize': + response = { + jsonrpc: '2.0', + result: { + protocolVersion: params?.protocolVersion || '2024-11-05', + capabilities: { tools: {} }, + serverInfo: { name: SERVER_NAME, version: SERVER_VERSION }, + }, + id, + }; + break; + + case 'tools/list': + response = { jsonrpc: '2.0', result: { tools: TOOLS }, id }; + break; + + case 'tools/call': + try { + const toolName = params?.name; + let result: any; + + switch (toolName) { + case 'get-time': + result = await getTime.handler(req.body); + break; + case 'generate-password': + result = await generatePassword.handler(req.body); + break; + default: + result = { + content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], + isError: true, + }; + } + response = { jsonrpc: '2.0', result, id }; + } catch (error) { + response = { + jsonrpc: '2.0', + error: { code: -32603, message: error instanceof Error ? error.message : 'Error' }, + id, + }; + } + break; + + default: + response = { + jsonrpc: '2.0', + error: { code: -32601, message: `Method not found: ${method}` }, + id, + }; + } + + res.json(response); + }); + + app.listen(SERVER_PORT, '127.0.0.1', () => { + console.log(`MCP Server running at ${SERVER_URL}`); + console.log(` POST ${SERVER_URL}/mcp`); + console.log(` GET ${SERVER_URL}/health`); + }); + + process.on('SIGINT', () => { + console.log('\nShutting down...'); + process.exit(0); + }); +} + +startServer().catch(error => { + console.error('Failed to start server:', error); + process.exit(1); +}); diff --git a/examples/server3002/src/tools/generate-password.ts b/examples/server3002/src/tools/generate-password.ts new file mode 100644 index 00000000..5315b081 --- /dev/null +++ b/examples/server3002/src/tools/generate-password.ts @@ -0,0 +1,196 @@ +/** + * Generate a secure password with customizable options + * No authentication required - public tool + */ +export const generatePassword = { + name: 'generate-password', + description: 'Generate a secure password with customizable length and character sets', + inputSchema: { + type: 'object', + properties: { + length: { + type: 'number', + description: 'Length of the password (8-128 characters)', + minimum: 8, + maximum: 128, + default: 16, + }, + includeUppercase: { + type: 'boolean', + description: 'Include uppercase letters (A-Z)', + default: true, + }, + includeLowercase: { + type: 'boolean', + description: 'Include lowercase letters (a-z)', + default: true, + }, + includeNumbers: { + type: 'boolean', + description: 'Include numbers (0-9)', + default: true, + }, + includeSymbols: { + type: 'boolean', + description: 'Include symbols (!@#$%^&*)', + default: true, + }, + excludeSimilar: { + type: 'boolean', + description: 'Exclude similar looking characters (0,O,l,1,I)', + default: false, + }, + }, + required: [], + }, + handler: async (request: any) => { + // Handle different parameter structures + let params = {}; + + if (request.params?.arguments) { + if (typeof request.params.arguments === 'string') { + // Gopher-orch sends arguments as JSON string + try { + params = JSON.parse(request.params.arguments); + } catch (e) { + params = {}; + } + } else { + // Direct calls send arguments as object + params = request.params.arguments; + } + } else { + // Fallback to direct params + params = request.params || {}; + } + + const { + length = 16, + includeUppercase = true, + includeLowercase = true, + includeNumbers = true, + includeSymbols = true, + excludeSimilar = false, + } = params; + + // Validate length + if (length < 8 || length > 128) { + return { + content: [ + { + type: 'text', + text: 'Error: Password length must be between 8 and 128 characters.', + }, + ], + isError: true, + }; + } + + // Character sets + let uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + let lowercase = 'abcdefghijklmnopqrstuvwxyz'; + let numbers = '0123456789'; + let symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?'; + + // Exclude similar characters if requested + if (excludeSimilar) { + uppercase = uppercase.replace(/[O]/g, ''); + lowercase = lowercase.replace(/[l]/g, ''); + numbers = numbers.replace(/[01]/g, ''); + symbols = symbols.replace(/[|]/g, ''); + } + + // Build character pool + let charPool = ''; + let requiredChars: string[] = []; + + if (includeUppercase) { + charPool += uppercase; + requiredChars.push(uppercase[Math.floor(Math.random() * uppercase.length)]); + } + if (includeLowercase) { + charPool += lowercase; + requiredChars.push(lowercase[Math.floor(Math.random() * lowercase.length)]); + } + if (includeNumbers) { + charPool += numbers; + requiredChars.push(numbers[Math.floor(Math.random() * numbers.length)]); + } + if (includeSymbols) { + charPool += symbols; + requiredChars.push(symbols[Math.floor(Math.random() * symbols.length)]); + } + + // Ensure at least one character set is selected + if (charPool.length === 0) { + return { + content: [ + { + type: 'text', + text: 'Error: At least one character set must be included.', + }, + ], + isError: true, + }; + } + + // Generate password + let password = ''; + + // First, add required characters to ensure all selected types are present + for (const char of requiredChars) { + password += char; + } + + // Fill the rest with random characters + for (let i = password.length; i < length; i++) { + password += charPool[Math.floor(Math.random() * charPool.length)]; + } + + // Shuffle the password to randomize the position of required characters + password = password + .split('') + .sort(() => Math.random() - 0.5) + .join(''); + + // Calculate password strength + let strength = 0; + let strengthText = ''; + + if (includeUppercase) strength += 26; + if (includeLowercase) strength += 26; + if (includeNumbers) strength += 10; + if (includeSymbols) strength += symbols.length; + + const entropy = Math.log2(Math.pow(strength, length)); + + if (entropy < 40) { + strengthText = 'Weak'; + } else if (entropy < 60) { + strengthText = 'Fair'; + } else if (entropy < 80) { + strengthText = 'Good'; + } else if (entropy < 100) { + strengthText = 'Strong'; + } else { + strengthText = 'Very Strong'; + } + + // Build settings summary + const settings: string[] = []; + if (includeUppercase) settings.push('Uppercase'); + if (includeLowercase) settings.push('Lowercase'); + if (includeNumbers) settings.push('Numbers'); + if (includeSymbols) settings.push('Symbols'); + if (excludeSimilar) settings.push('No Similar Chars'); + + return { + content: [ + { + type: 'text', + text: `Generated Password:\n\n🔐 ${password}\n\nSettings:\n• Length: ${length} characters\n• Character types: ${settings.join(', ')}\n• Strength: ${strengthText}\n• Entropy: ${entropy.toFixed(1)} bits\n\n💡 Tip: Store this password securely and don't reuse it for multiple accounts.`, + }, + ], + }; + }, +}; diff --git a/examples/server3002/src/tools/get-time.ts b/examples/server3002/src/tools/get-time.ts new file mode 100644 index 00000000..160e16bf --- /dev/null +++ b/examples/server3002/src/tools/get-time.ts @@ -0,0 +1,130 @@ +/** + * Get current time for a timezone or location + * No authentication required - public tool + */ +export const getTime = { + name: 'get-time', + description: 'Get current time and date for a specific timezone or city', + inputSchema: { + type: 'object', + properties: { + location: { + type: 'string', + description: + 'Timezone (e.g., "UTC", "America/New_York") or city name (e.g., "London", "Tokyo")', + }, + }, + required: ['location'], + }, + handler: async (request: any) => { + // Handle different parameter structures + let location; + + if (request.params?.arguments) { + if (typeof request.params.arguments === 'string') { + // Gopher-orch sends arguments as JSON string + try { + const parsedArgs = JSON.parse(request.params.arguments); + location = parsedArgs.location; + } catch (e) { + location = undefined; + } + } else { + // Direct calls send arguments as object + location = request.params.arguments.location; + } + } else { + // Fallback to direct params + location = request.params?.location; + } + + if (!location) { + return { + content: [ + { + type: 'text', + text: 'Error: No location parameter provided', + }, + ], + isError: true, + }; + } + + // Map common city names to timezones + const cityToTimezone: { [key: string]: string } = { + london: 'Europe/London', + 'new york': 'America/New_York', + tokyo: 'Asia/Tokyo', + paris: 'Europe/Paris', + france: 'Europe/Paris', + sydney: 'Australia/Sydney', + 'los angeles': 'America/Los_Angeles', + chicago: 'America/Chicago', + dubai: 'Asia/Dubai', + singapore: 'Asia/Singapore', + mumbai: 'Asia/Kolkata', + beijing: 'Asia/Shanghai', + moscow: 'Europe/Moscow', + berlin: 'Europe/Berlin', + toronto: 'America/Toronto', + }; + + // Determine timezone + let timezone = location; + const normalizedLocation = location.toLowerCase(); + + if (cityToTimezone[normalizedLocation]) { + timezone = cityToTimezone[normalizedLocation]; + } + + try { + // Get current time in specified timezone + const now = new Date(); + const timeInTimezone = now.toLocaleString('en-US', { + timeZone: timezone, + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short', + }); + + const dateOnly = now.toLocaleDateString('en-US', { + timeZone: timezone, + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }); + + const timeOnly = now.toLocaleTimeString('en-US', { + timeZone: timezone, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short', + }); + + return { + content: [ + { + type: 'text', + text: `Current time in ${location}:\n\nFull Date & Time: ${timeInTimezone}\nDate: ${dateOnly}\nTime: ${timeOnly}\nTimezone: ${timezone}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: 'text', + text: `Error: Invalid timezone or location "${location}". Please use a valid timezone (e.g., "UTC", "America/New_York") or city name (e.g., "London", "Tokyo").`, + }, + ], + isError: true, + }; + } + }, +}; diff --git a/examples/server3002/start-mcp-server.sh b/examples/server3002/start-mcp-server.sh new file mode 100755 index 00000000..3e00f8ad --- /dev/null +++ b/examples/server3002/start-mcp-server.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Start MCP Server (Time & Password Tools) + +set -e + +DIR_CURR=$(cd "$(dirname "$0")";pwd) +cd $DIR_CURR + +# Stop existing server on port 3002 +lsof -i :3002 | grep LISTEN | awk '{print $2}' | xargs kill -9 2>/dev/null || true +sleep 1 + +# Install dependencies if needed +if [ ! -d "node_modules" ]; then + npm install +fi + +echo "🚀 Starting MCP Server on port 3002..." +echo " 📡 MCP Endpoint: http://127.0.0.1:3002/mcp" +echo " 💚 Health: http://127.0.0.1:3002/health" +echo "" + +npm run dev diff --git a/examples/server3002/tsconfig.json b/examples/server3002/tsconfig.json new file mode 100644 index 00000000..7f748950 --- /dev/null +++ b/examples/server3002/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "lib": ["ES2022"], + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*", "sdk/src/**/*"], + "exclude": ["node_modules", "dist", "src/examples"] +} diff --git a/include/orch/core/hello.h b/include/orch/core/hello.h deleted file mode 100644 index 48c6cdc7..00000000 --- a/include/orch/core/hello.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include - -namespace gopher { -namespace orch { -namespace core { - -// Hello class demonstrates basic gopher-orch functionality -// This is a simple example to verify the build system works correctly -class Hello { - public: - Hello(); - explicit Hello(const std::string& name); - ~Hello(); - - std::string greet() const; - std::string greet_with_prefix(const std::string& prefix) const; - - void set_name(const std::string& name); - const std::string& get_name() const; - - static std::string get_version(); - - private: - class Impl; - std::unique_ptr impl_; -}; - -// Builder pattern for Hello class construction -class HelloBuilder { - public: - HelloBuilder& with_name(const std::string& name); - HelloBuilder& with_greeting_style(const std::string& style); - - std::unique_ptr build() const; - - private: - std::string name_ = "World"; - std::string style_ = "default"; -}; - -} // namespace core -} // namespace orch -} // namespace gopher diff --git a/include/orch/core/version.h b/include/orch/core/version.h deleted file mode 100644 index d86166e9..00000000 --- a/include/orch/core/version.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#define GOPHER_ORCH_VERSION_MAJOR 0 -#define GOPHER_ORCH_VERSION_MINOR 1 -#define GOPHER_ORCH_VERSION_PATCH 0 - -#define GOPHER_ORCH_VERSION_STRING "0.1.0" - -namespace gopher { -namespace orch { -namespace core { - -struct Version { - static constexpr int major() { return GOPHER_ORCH_VERSION_MAJOR; } - static constexpr int minor() { return GOPHER_ORCH_VERSION_MINOR; } - static constexpr int patch() { return GOPHER_ORCH_VERSION_PATCH; } - static const char* string() { return GOPHER_ORCH_VERSION_STRING; } -}; - -} // namespace core -} // namespace orch -} // namespace gopher diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 709369ef..00000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,103 +0,0 @@ -# gopher-orch source files - -# Core library sources (orch-specific extensions) -set(ORCH_CORE_SOURCES - orch/hello.cpp -) - -# Combine all sources -set(GOPHER_ORCH_SOURCES - ${ORCH_CORE_SOURCES} -) - -# Build static library -if(BUILD_STATIC_LIBS) - add_library(gopher-orch-static STATIC ${GOPHER_ORCH_SOURCES}) - target_include_directories(gopher-orch-static PUBLIC - $ - $ - $ - ) - - # Link dependencies - if(NOT BUILD_WITHOUT_GOPHER_MCP) - target_link_libraries(gopher-orch-static PUBLIC - ${GOPHER_MCP_LIBRARIES} - Threads::Threads - ) - else() - target_link_libraries(gopher-orch-static PUBLIC - Threads::Threads - ) - endif() - - set_target_properties(gopher-orch-static PROPERTIES - OUTPUT_NAME gopher-orch - POSITION_INDEPENDENT_CODE ON - ) - - # Set the main library alias - add_library(gopher-orch ALIAS gopher-orch-static) - - # Installation - install(TARGETS gopher-orch-static - EXPORT gopher-orch-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - COMPONENT libraries - ) -endif() - -# Build shared library -if(BUILD_SHARED_LIBS) - add_library(gopher-orch-shared SHARED ${GOPHER_ORCH_SOURCES}) - target_include_directories(gopher-orch-shared PUBLIC - $ - $ - $ - ) - - # Link dependencies - if(NOT BUILD_WITHOUT_GOPHER_MCP) - target_link_libraries(gopher-orch-shared PUBLIC - ${GOPHER_MCP_LIBRARIES} - Threads::Threads - ) - else() - target_link_libraries(gopher-orch-shared PUBLIC - Threads::Threads - ) - endif() - - set_target_properties(gopher-orch-shared PROPERTIES - OUTPUT_NAME gopher-orch - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR} - ) - - # If only building shared, set it as the main library - if(NOT BUILD_STATIC_LIBS) - add_library(gopher-orch ALIAS gopher-orch-shared) - endif() - - # Installation - install(TARGETS gopher-orch-shared - EXPORT gopher-orch-targets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - COMPONENT libraries - ) -endif() - -# Export targets only when not using submodule -# (When using submodule, gopher-mcp targets aren't installable) -if(NOT USE_SUBMODULE_GOPHER_MCP) - install(EXPORT gopher-orch-targets - FILE gopher-orch-targets.cmake - NAMESPACE gopher-orch:: - DESTINATION lib/cmake/gopher-orch - COMPONENT development - ) -endif() diff --git a/src/agent.rs b/src/agent.rs new file mode 100644 index 00000000..b1c7b1dc --- /dev/null +++ b/src/agent.rs @@ -0,0 +1,226 @@ +//! GopherAgent implementation. + +use std::sync::atomic::{AtomicBool, Ordering}; + +use crate::config::Config; +use crate::error::{Error, Result}; +use crate::ffi::{self, AgentHandle}; +use crate::init; +use crate::result::AgentResult; + +/// Default timeout for agent queries (60 seconds). +const DEFAULT_TIMEOUT_MS: u64 = 60_000; + +/// A gopher-orch agent for running AI queries. +pub struct GopherAgent { + handle: AgentHandle, + disposed: AtomicBool, +} + +// Safety: The native handle is thread-safe according to the C++ implementation +unsafe impl Send for GopherAgent {} +unsafe impl Sync for GopherAgent {} + +impl GopherAgent { + /// Create a new GopherAgent with the given configuration. + pub fn create(config: Config) -> Result { + init()?; + + let handle = if config.has_api_key() { + ffi::agent_create_by_api_key( + config.provider(), + config.model(), + config.api_key().unwrap_or(""), + ) + } else if config.has_server_config() { + ffi::agent_create_by_json( + config.provider(), + config.model(), + config.server_config().unwrap_or(""), + ) + } else { + return Err(Error::config( + "Either API key or server config must be provided", + )); + }; + + if handle.is_null() { + let err_msg = ffi::get_last_error(); + ffi::clear_error(); + let msg = if err_msg.is_empty() { + "Failed to create agent".to_string() + } else { + err_msg + }; + return Err(Error::agent(msg)); + } + + Ok(GopherAgent { + handle, + disposed: AtomicBool::new(false), + }) + } + + /// Create a new GopherAgent with an API key. + pub fn create_with_api_key(provider: &str, model: &str, api_key: &str) -> Result { + let config = crate::ConfigBuilder::new() + .with_provider(provider) + .with_model(model) + .with_api_key(api_key) + .build(); + Self::create(config) + } + + /// Create a new GopherAgent with a server config. + pub fn create_with_server_config( + provider: &str, + model: &str, + server_config: &str, + ) -> Result { + let config = crate::ConfigBuilder::new() + .with_provider(provider) + .with_model(model) + .with_server_config(server_config) + .build(); + Self::create(config) + } + + /// Run a query against the agent with the default timeout (60 seconds). + pub fn run(&self, query: &str) -> Result { + self.run_with_timeout(query, DEFAULT_TIMEOUT_MS) + } + + /// Run a query against the agent with a custom timeout. + pub fn run_with_timeout(&self, query: &str, timeout_ms: u64) -> Result { + self.ensure_not_disposed()?; + + let response = ffi::agent_run(self.handle, query, timeout_ms); + if response.is_empty() { + Ok(format!("No response for query: \"{}\"", query)) + } else { + Ok(response) + } + } + + /// Run a query and return detailed result information. + pub fn run_detailed(&self, query: &str) -> AgentResult { + self.run_detailed_with_timeout(query, DEFAULT_TIMEOUT_MS) + } + + /// Run a query with custom timeout and return detailed result. + pub fn run_detailed_with_timeout(&self, query: &str, timeout_ms: u64) -> AgentResult { + match self.run_with_timeout(query, timeout_ms) { + Ok(response) => AgentResult::success(response), + Err(Error::Timeout(msg)) => AgentResult::timeout(msg), + Err(e) => AgentResult::error(e.to_string()), + } + } + + /// Check if the agent has been disposed. + pub fn is_disposed(&self) -> bool { + self.disposed.load(Ordering::SeqCst) + } + + /// Ensure the agent has not been disposed. + fn ensure_not_disposed(&self) -> Result<()> { + if self.is_disposed() { + Err(Error::Disposed) + } else { + Ok(()) + } + } + + /// Dispose of the agent, releasing native resources. + fn dispose(&self) { + if self + .disposed + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_ok() + && !self.handle.is_null() + { + ffi::agent_release(self.handle); + } + } +} + +impl Drop for GopherAgent { + fn drop(&mut self) { + self.dispose(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_SERVER_CONFIG: &str = r#"{ + "succeeded": true, + "code": 200000000, + "message": "success", + "data": { + "servers": [ + { + "version": "2025-01-09", + "serverId": "1", + "name": "test-server", + "transport": "http_sse", + "config": {"url": "http://127.0.0.1:9999/mcp", "headers": {}}, + "connectTimeout": 5000, + "requestTimeout": 30000 + } + ] + } + }"#; + + fn skip_if_native_library_not_available() -> bool { + !ffi::is_available() + } + + #[test] + fn test_create_with_empty_config() { + if skip_if_native_library_not_available() { + return; + } + + let config = crate::ConfigBuilder::new().build(); + let result = GopherAgent::create(config); + assert!(result.is_err()); + } + + #[test] + fn test_create_with_server_config() { + if skip_if_native_library_not_available() { + return; + } + + let result = GopherAgent::create_with_server_config( + "AnthropicProvider", + "claude-3-haiku-20240307", + TEST_SERVER_CONFIG, + ); + + // May fail without API key, but should not panic + if let Ok(agent) = result { + assert!(!agent.is_disposed()); + } + } + + #[test] + fn test_disposed_after_drop() { + if skip_if_native_library_not_available() { + return; + } + + let result = GopherAgent::create_with_server_config( + "AnthropicProvider", + "claude-3-haiku-20240307", + TEST_SERVER_CONFIG, + ); + + if let Ok(agent) = result { + assert!(!agent.is_disposed()); + drop(agent); + // Agent is now disposed (dropped) + } + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..2e77fcbd --- /dev/null +++ b/src/config.rs @@ -0,0 +1,130 @@ +//! Configuration types for creating agents. + +/// Configuration for creating a GopherAgent. +#[derive(Debug, Clone, Default)] +pub struct Config { + provider: String, + model: String, + api_key: Option, + server_config: Option, +} + +impl Config { + /// Get the provider name. + pub fn provider(&self) -> &str { + &self.provider + } + + /// Get the model name. + pub fn model(&self) -> &str { + &self.model + } + + /// Get the API key (if set). + pub fn api_key(&self) -> Option<&str> { + self.api_key.as_deref() + } + + /// Get the server config JSON (if set). + pub fn server_config(&self) -> Option<&str> { + self.server_config.as_deref() + } + + /// Check if an API key is configured. + pub fn has_api_key(&self) -> bool { + self.api_key.as_ref().map_or(false, |k| !k.is_empty()) + } + + /// Check if a server config is configured. + pub fn has_server_config(&self) -> bool { + self.server_config.as_ref().map_or(false, |c| !c.is_empty()) + } +} + +/// Builder for creating Config instances. +#[derive(Debug, Default)] +pub struct ConfigBuilder { + config: Config, +} + +impl ConfigBuilder { + /// Create a new ConfigBuilder. + pub fn new() -> Self { + Self::default() + } + + /// Set the provider name. + pub fn with_provider>(mut self, provider: S) -> Self { + self.config.provider = provider.into(); + self + } + + /// Set the model name. + pub fn with_model>(mut self, model: S) -> Self { + self.config.model = model.into(); + self + } + + /// Set the API key. + pub fn with_api_key>(mut self, api_key: S) -> Self { + self.config.api_key = Some(api_key.into()); + self + } + + /// Set the server config JSON. + pub fn with_server_config>(mut self, server_config: S) -> Self { + self.config.server_config = Some(server_config.into()); + self + } + + /// Build the Config. + pub fn build(self) -> Config { + self.config + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_builder_with_api_key() { + let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_api_key("test-api-key") + .build(); + + assert_eq!(config.provider(), "AnthropicProvider"); + assert_eq!(config.model(), "claude-3-haiku-20240307"); + assert!(config.has_api_key()); + assert!(!config.has_server_config()); + assert_eq!(config.api_key(), Some("test-api-key")); + } + + #[test] + fn test_builder_with_server_config() { + let server_config = r#"{"succeeded": true}"#; + let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_server_config(server_config) + .build(); + + assert_eq!(config.provider(), "AnthropicProvider"); + assert_eq!(config.model(), "claude-3-haiku-20240307"); + assert!(!config.has_api_key()); + assert!(config.has_server_config()); + assert_eq!(config.server_config(), Some(server_config)); + } + + #[test] + fn test_empty_config() { + let config = ConfigBuilder::new().build(); + + assert!(!config.has_api_key()); + assert!(!config.has_server_config()); + assert!(config.provider().is_empty()); + assert!(config.model().is_empty()); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..cd558b40 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,75 @@ +//! Error types for the gopher-orch SDK. + +use std::error::Error as StdError; +use std::fmt; + +/// Result type alias for gopher-orch operations. +pub type Result = std::result::Result; + +/// Error types for gopher-orch operations. +#[derive(Debug)] +pub enum Error { + /// Error loading or using the native library. + Library(String), + + /// Error creating an agent. + Agent(String), + + /// Invalid API key error. + ApiKey(String), + + /// Connection error. + Connection(String), + + /// Timeout error. + Timeout(String), + + /// Configuration error. + Config(String), + + /// Agent has been disposed. + Disposed, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Library(msg) => write!(f, "Library error: {}", msg), + Error::Agent(msg) => write!(f, "Agent error: {}", msg), + Error::ApiKey(msg) => write!(f, "API key error: {}", msg), + Error::Connection(msg) => write!(f, "Connection error: {}", msg), + Error::Timeout(msg) => write!(f, "Timeout error: {}", msg), + Error::Config(msg) => write!(f, "Configuration error: {}", msg), + Error::Disposed => write!(f, "Agent has been disposed"), + } + } +} + +impl StdError for Error {} + +impl Error { + /// Create a new agent error. + pub fn agent>(msg: S) -> Self { + Error::Agent(msg.into()) + } + + /// Create a new library error. + pub fn library>(msg: S) -> Self { + Error::Library(msg.into()) + } + + /// Create a new connection error. + pub fn connection>(msg: S) -> Self { + Error::Connection(msg.into()) + } + + /// Create a new timeout error. + pub fn timeout>(msg: S) -> Self { + Error::Timeout(msg.into()) + } + + /// Create a new config error. + pub fn config>(msg: S) -> Self { + Error::Config(msg.into()) + } +} diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 00000000..e94ccbe4 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,249 @@ +//! FFI bindings to the native gopher-orch library. + +use libloading::Library; +use once_cell::sync::OnceCell; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_void}; +use std::path::PathBuf; + +/// Opaque handle to a native agent +pub type AgentHandle = *mut c_void; + +/// Error info structure from native library +#[repr(C)] +pub struct ErrorInfo { + pub code: i32, + pub message: *const c_char, + pub details: *const c_char, + pub file: *const c_char, + pub line: i32, +} + +// Type aliases for FFI function signatures +type AgentCreateByJsonFn = + unsafe extern "C" fn(*const c_char, *const c_char, *const c_char) -> AgentHandle; +type AgentCreateByApiKeyFn = + unsafe extern "C" fn(*const c_char, *const c_char, *const c_char) -> AgentHandle; +type AgentRunFn = unsafe extern "C" fn(AgentHandle, *const c_char, u64) -> *mut c_char; +type AgentReleaseFn = unsafe extern "C" fn(AgentHandle); +type AgentAddRefFn = unsafe extern "C" fn(AgentHandle); +type ApiFetchServersFn = unsafe extern "C" fn(*const c_char) -> *mut c_char; +type LastErrorFn = unsafe extern "C" fn() -> *mut ErrorInfo; +type ClearErrorFn = unsafe extern "C" fn(); +type FreeFn = unsafe extern "C" fn(*mut c_void); + +/// Native library wrapper +struct NativeLibrary { + _library: Library, + agent_create_by_json: AgentCreateByJsonFn, + agent_create_by_api_key: AgentCreateByApiKeyFn, + agent_run: AgentRunFn, + agent_release: AgentReleaseFn, + #[allow(dead_code)] + agent_add_ref: AgentAddRefFn, + #[allow(dead_code)] + api_fetch_servers: ApiFetchServersFn, + last_error: LastErrorFn, + clear_error: ClearErrorFn, + free: FreeFn, +} + +unsafe impl Send for NativeLibrary {} +unsafe impl Sync for NativeLibrary {} + +static NATIVE_LIB: OnceCell> = OnceCell::new(); + +fn get_library_path() -> PathBuf { + // Try to find the library in common locations + let candidates = [ + // Relative to executable + PathBuf::from("native/lib"), + // Relative to current directory + PathBuf::from("./native/lib"), + // Absolute path from project root + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("native/lib"), + ]; + + for candidate in &candidates { + let lib_path = if cfg!(target_os = "macos") { + candidate.join("libgopher-orch.dylib") + } else if cfg!(target_os = "windows") { + candidate.join("gopher-orch.dll") + } else { + candidate.join("libgopher-orch.so") + }; + + if lib_path.exists() { + return lib_path; + } + } + + // Fall back to system library search + if cfg!(target_os = "macos") { + PathBuf::from("libgopher-orch.dylib") + } else if cfg!(target_os = "windows") { + PathBuf::from("gopher-orch.dll") + } else { + PathBuf::from("libgopher-orch.so") + } +} + +fn load_library() -> Option { + let lib_path = get_library_path(); + + unsafe { + let library = Library::new(&lib_path).ok()?; + + // Get all symbols and copy the function pointers before moving library + let agent_create_by_json: AgentCreateByJsonFn = + *library.get(b"gopher_orch_agent_create_by_json").ok()?; + let agent_create_by_api_key: AgentCreateByApiKeyFn = + *library.get(b"gopher_orch_agent_create_by_api_key").ok()?; + let agent_run: AgentRunFn = *library.get(b"gopher_orch_agent_run").ok()?; + let agent_release: AgentReleaseFn = *library.get(b"gopher_orch_agent_release").ok()?; + let agent_add_ref: AgentAddRefFn = *library.get(b"gopher_orch_agent_add_ref").ok()?; + let api_fetch_servers: ApiFetchServersFn = + *library.get(b"gopher_orch_api_fetch_servers").ok()?; + let last_error: LastErrorFn = *library.get(b"gopher_orch_last_error").ok()?; + let clear_error: ClearErrorFn = *library.get(b"gopher_orch_clear_error").ok()?; + let free: FreeFn = *library.get(b"gopher_orch_free").ok()?; + + Some(NativeLibrary { + _library: library, + agent_create_by_json, + agent_create_by_api_key, + agent_run, + agent_release, + agent_add_ref, + api_fetch_servers, + last_error, + clear_error, + free, + }) + } +} + +fn get_lib() -> Option<&'static NativeLibrary> { + NATIVE_LIB.get_or_init(load_library).as_ref() +} + +/// Check if the native library is available +pub fn is_available() -> bool { + get_lib().is_some() +} + +/// Create an agent with JSON server configuration +pub fn agent_create_by_json(provider: &str, model: &str, server_json: &str) -> AgentHandle { + let lib = match get_lib() { + Some(lib) => lib, + None => return std::ptr::null_mut(), + }; + + let c_provider = CString::new(provider).unwrap(); + let c_model = CString::new(model).unwrap(); + let c_server_json = CString::new(server_json).unwrap(); + + unsafe { + (lib.agent_create_by_json)( + c_provider.as_ptr(), + c_model.as_ptr(), + c_server_json.as_ptr(), + ) + } +} + +/// Create an agent with API key +pub fn agent_create_by_api_key(provider: &str, model: &str, api_key: &str) -> AgentHandle { + let lib = match get_lib() { + Some(lib) => lib, + None => return std::ptr::null_mut(), + }; + + let c_provider = CString::new(provider).unwrap(); + let c_model = CString::new(model).unwrap(); + let c_api_key = CString::new(api_key).unwrap(); + + unsafe { + (lib.agent_create_by_api_key)(c_provider.as_ptr(), c_model.as_ptr(), c_api_key.as_ptr()) + } +} + +/// Run a query against the agent +pub fn agent_run(agent: AgentHandle, query: &str, timeout_ms: u64) -> String { + let lib = match get_lib() { + Some(lib) => lib, + None => return String::new(), + }; + + if agent.is_null() { + return String::new(); + } + + let c_query = CString::new(query).unwrap(); + + unsafe { + let result = (lib.agent_run)(agent, c_query.as_ptr(), timeout_ms); + if result.is_null() { + return String::new(); + } + + let rust_string = CStr::from_ptr(result).to_string_lossy().into_owned(); + (lib.free)(result as *mut c_void); + rust_string + } +} + +/// Release an agent handle +pub fn agent_release(agent: AgentHandle) { + let lib = match get_lib() { + Some(lib) => lib, + None => return, + }; + + if !agent.is_null() { + unsafe { + (lib.agent_release)(agent); + } + } +} + +/// Get the last error message +pub fn get_last_error() -> String { + let lib = match get_lib() { + Some(lib) => lib, + None => return String::new(), + }; + + unsafe { + let error_info = (lib.last_error)(); + if error_info.is_null() || (*error_info).message.is_null() { + return String::new(); + } + CStr::from_ptr((*error_info).message) + .to_string_lossy() + .into_owned() + } +} + +/// Clear the last error +pub fn clear_error() { + let lib = match get_lib() { + Some(lib) => lib, + None => return, + }; + + unsafe { + (lib.clear_error)(); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_available() { + // Just check it doesn't panic + let _ = is_available(); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..4232b604 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,95 @@ +//! # Gopher Orch Rust SDK +//! +//! Rust SDK for Gopher Orch - AI Agent orchestration framework with native C++ performance. +//! +//! ## Quick Start +//! +//! ```rust,no_run +//! use gopher_orch::{GopherAgent, ConfigBuilder}; +//! +//! fn main() -> Result<(), Box> { +//! // Create agent with server configuration +//! let config = ConfigBuilder::new() +//! .with_provider("AnthropicProvider") +//! .with_model("claude-3-haiku-20240307") +//! .with_server_config(r#"{"succeeded": true, "data": {"servers": []}}"#) +//! .build(); +//! +//! let agent = GopherAgent::create(config)?; +//! +//! // Run a query +//! let result = agent.run("What is the weather in Tokyo?")?; +//! println!("{}", result); +//! +//! Ok(()) +//! } +//! ``` + +mod agent; +mod config; +mod error; +mod ffi; +mod result; + +pub use agent::GopherAgent; +pub use config::{Config, ConfigBuilder}; +pub use error::{Error, Result}; +pub use result::{AgentResult, AgentResultStatus}; + +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Once; + +static INIT: Once = Once::new(); +static INITIALIZED: AtomicBool = AtomicBool::new(false); + +/// Initialize the gopher-orch library. +/// Called automatically by `GopherAgent::create()` if not already initialized. +pub fn init() -> Result<()> { + let mut init_result = Ok(()); + + INIT.call_once(|| { + if !ffi::is_available() { + init_result = Err(Error::Library( + "Failed to load gopher-orch native library".into(), + )); + return; + } + INITIALIZED.store(true, Ordering::SeqCst); + }); + + init_result +} + +/// Returns true if the library is initialized. +pub fn is_initialized() -> bool { + INITIALIZED.load(Ordering::SeqCst) +} + +/// Shutdown the gopher-orch library. +pub fn shutdown() { + INITIALIZED.store(false, Ordering::SeqCst); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_init() { + // Just test that init doesn't panic + let _ = init(); + } + + #[test] + fn test_config_builder() { + let config = ConfigBuilder::new() + .with_provider("TestProvider") + .with_model("test-model") + .with_api_key("test-key") + .build(); + + assert_eq!(config.provider(), "TestProvider"); + assert_eq!(config.model(), "test-model"); + assert!(config.has_api_key()); + } +} diff --git a/src/orch/hello.cpp b/src/orch/hello.cpp deleted file mode 100644 index 9b05263c..00000000 --- a/src/orch/hello.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "orch/core/hello.h" - -#include - -#include "orch/core/version.h" - -namespace gopher { -namespace orch { -namespace core { - -class Hello::Impl { - public: - Impl() : name_("World") {} - explicit Impl(const std::string& name) : name_(name) {} - - std::string name_; -}; - -Hello::Hello() : impl_(std::make_unique()) {} - -Hello::Hello(const std::string& name) : impl_(std::make_unique(name)) {} - -Hello::~Hello() = default; - -std::string Hello::greet() const { - std::ostringstream oss; - oss << "Hello, " << impl_->name_ << "!"; - return oss.str(); -} - -std::string Hello::greet_with_prefix(const std::string& prefix) const { - std::ostringstream oss; - oss << prefix << " " << impl_->name_ << "!"; - return oss.str(); -} - -void Hello::set_name(const std::string& name) { impl_->name_ = name; } - -const std::string& Hello::get_name() const { return impl_->name_; } - -std::string Hello::get_version() { return Version::string(); } - -HelloBuilder& HelloBuilder::with_name(const std::string& name) { - name_ = name; - return *this; -} - -HelloBuilder& HelloBuilder::with_greeting_style(const std::string& style) { - style_ = style; - return *this; -} - -std::unique_ptr HelloBuilder::build() const { - return std::make_unique(name_); -} - -} // namespace core -} // namespace orch -} // namespace gopher diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 00000000..5e2294f8 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,220 @@ +//! Result types for agent queries. + +use std::fmt; + +/// Status of an agent result. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AgentResultStatus { + /// The query was successful. + Success, + /// An error occurred. + Error, + /// The query timed out. + Timeout, + /// Maximum iterations were reached. + MaxIterationsReached, +} + +impl fmt::Display for AgentResultStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + AgentResultStatus::Success => write!(f, "SUCCESS"), + AgentResultStatus::Error => write!(f, "ERROR"), + AgentResultStatus::Timeout => write!(f, "TIMEOUT"), + AgentResultStatus::MaxIterationsReached => write!(f, "MAX_ITERATIONS_REACHED"), + } + } +} + +/// Result of an agent query with detailed information. +#[derive(Debug, Clone)] +pub struct AgentResult { + response: String, + status: AgentResultStatus, + error_message: Option, + iteration_count: usize, + tokens_used: u64, +} + +impl AgentResult { + /// Get the agent's response. + pub fn response(&self) -> &str { + &self.response + } + + /// Get the result status. + pub fn status(&self) -> AgentResultStatus { + self.status + } + + /// Get the error message (if any). + pub fn error_message(&self) -> Option<&str> { + self.error_message.as_deref() + } + + /// Get the iteration count. + pub fn iteration_count(&self) -> usize { + self.iteration_count + } + + /// Get the number of tokens used. + pub fn tokens_used(&self) -> u64 { + self.tokens_used + } + + /// Check if the result was successful. + pub fn is_success(&self) -> bool { + self.status == AgentResultStatus::Success + } + + /// Create a successful result. + pub fn success>(response: S) -> Self { + AgentResult { + response: response.into(), + status: AgentResultStatus::Success, + error_message: None, + iteration_count: 1, + tokens_used: 0, + } + } + + /// Create an error result. + pub fn error>(message: S) -> Self { + AgentResult { + response: String::new(), + status: AgentResultStatus::Error, + error_message: Some(message.into()), + iteration_count: 0, + tokens_used: 0, + } + } + + /// Create a timeout result. + pub fn timeout>(message: S) -> Self { + AgentResult { + response: String::new(), + status: AgentResultStatus::Timeout, + error_message: Some(message.into()), + iteration_count: 0, + tokens_used: 0, + } + } +} + +/// Builder for creating AgentResult instances. +#[derive(Debug, Default)] +#[allow(dead_code)] +pub struct AgentResultBuilder { + response: String, + status: Option, + error_message: Option, + iteration_count: usize, + tokens_used: u64, +} + +#[allow(dead_code)] +impl AgentResultBuilder { + /// Create a new builder. + pub fn new() -> Self { + Self::default() + } + + /// Set the response. + pub fn with_response>(mut self, response: S) -> Self { + self.response = response.into(); + self + } + + /// Set the status. + pub fn with_status(mut self, status: AgentResultStatus) -> Self { + self.status = Some(status); + self + } + + /// Set the error message. + pub fn with_error_message>(mut self, message: S) -> Self { + self.error_message = Some(message.into()); + self + } + + /// Set the iteration count. + pub fn with_iteration_count(mut self, count: usize) -> Self { + self.iteration_count = count; + self + } + + /// Set the tokens used. + pub fn with_tokens_used(mut self, tokens: u64) -> Self { + self.tokens_used = tokens; + self + } + + /// Build the AgentResult. + pub fn build(self) -> AgentResult { + AgentResult { + response: self.response, + status: self.status.unwrap_or(AgentResultStatus::Success), + error_message: self.error_message, + iteration_count: self.iteration_count, + tokens_used: self.tokens_used, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_success_result() { + let result = AgentResult::success("Hello, world!"); + + assert_eq!(result.response(), "Hello, world!"); + assert_eq!(result.status(), AgentResultStatus::Success); + assert!(result.is_success()); + } + + #[test] + fn test_error_result() { + let result = AgentResult::error("Something went wrong"); + + assert_eq!(result.error_message(), Some("Something went wrong")); + assert_eq!(result.status(), AgentResultStatus::Error); + assert!(!result.is_success()); + } + + #[test] + fn test_timeout_result() { + let result = AgentResult::timeout("Operation timed out"); + + assert_eq!(result.error_message(), Some("Operation timed out")); + assert_eq!(result.status(), AgentResultStatus::Timeout); + assert!(!result.is_success()); + } + + #[test] + fn test_builder() { + let result = AgentResultBuilder::new() + .with_response("Test response") + .with_status(AgentResultStatus::Success) + .with_iteration_count(5) + .with_tokens_used(100) + .build(); + + assert_eq!(result.response(), "Test response"); + assert_eq!(result.status(), AgentResultStatus::Success); + assert_eq!(result.iteration_count(), 5); + assert_eq!(result.tokens_used(), 100); + } + + #[test] + fn test_status_display() { + assert_eq!(AgentResultStatus::Success.to_string(), "SUCCESS"); + assert_eq!(AgentResultStatus::Error.to_string(), "ERROR"); + assert_eq!(AgentResultStatus::Timeout.to_string(), "TIMEOUT"); + assert_eq!( + AgentResultStatus::MaxIterationsReached.to_string(), + "MAX_ITERATIONS_REACHED" + ); + } +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt deleted file mode 100644 index 88a9c7d4..00000000 --- a/tests/CMakeLists.txt +++ /dev/null @@ -1,83 +0,0 @@ -# gopher-orch tests - -# Test utilities and helpers -set(TEST_UTIL_SOURCES - # test_utils.cpp -) - -# Orch-specific core tests -set(ORCH_CORE_TEST_SOURCES - orch/hello_test.cpp -) - -# Helper function to create orch test executables -function(add_orch_test test_name test_sources) - add_executable(${test_name} ${test_sources} ${TEST_UTIL_SOURCES}) - # Use static library for tests to avoid duplicate symbol issues - if(TARGET gopher-orch-static) - set(GOPHER_ORCH_TEST_LIB gopher-orch-static) - else() - set(GOPHER_ORCH_TEST_LIB gopher-orch) - endif() - target_link_libraries(${test_name} - ${GOPHER_ORCH_TEST_LIB} - ${GOPHER_MCP_LIBRARIES} - GTest::gtest - GTest::gtest_main - GTest::gmock - Threads::Threads - ) - target_include_directories(${test_name} PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/tests - ${GOPHER_MCP_INCLUDE_DIR} - ) - - # Add test to CTest - gtest_discover_tests(${test_name} - WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} - PROPERTIES LABELS ${ARGN} - ) -endfunction() - -# Create individual orch test executables -add_orch_test(hello_test "${ORCH_CORE_TEST_SOURCES}" "orch") - -# Create a combined orch test executable for convenience -add_executable(gopher-orch-tests - ${ORCH_CORE_TEST_SOURCES} - ${TEST_UTIL_SOURCES} -) - -# Use static library for tests to avoid duplicate symbol issues -if(TARGET gopher-orch-static) - set(GOPHER_ORCH_TEST_LIB gopher-orch-static) -else() - set(GOPHER_ORCH_TEST_LIB gopher-orch) -endif() - -target_link_libraries(gopher-orch-tests - ${GOPHER_ORCH_TEST_LIB} - ${GOPHER_MCP_LIBRARIES} - GTest::gtest - GTest::gtest_main - GTest::gmock - Threads::Threads -) - -target_include_directories(gopher-orch-tests PRIVATE - ${CMAKE_SOURCE_DIR}/include - ${CMAKE_SOURCE_DIR}/tests - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Custom test targets -add_custom_target(test-verbose - COMMAND ${CMAKE_CTEST_COMMAND} -V - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} -) - -add_custom_target(test-parallel - COMMAND ${CMAKE_CTEST_COMMAND} -j${CMAKE_BUILD_PARALLEL_LEVEL} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} -) diff --git a/tests/ffi_tests.rs b/tests/ffi_tests.rs new file mode 100644 index 00000000..9e80f3dc --- /dev/null +++ b/tests/ffi_tests.rs @@ -0,0 +1,203 @@ +//! FFI verification tests for the Rust SDK. +//! +//! These tests verify that the FFI bindings work correctly with the native library. + +use gopher_orch::{init, is_initialized, ConfigBuilder, GopherAgent}; +use std::path::Path; + +const TEST_SERVER_CONFIG: &str = r#"{ + "succeeded": true, + "code": 200000000, + "message": "success", + "data": { + "servers": [ + { + "version": "2025-01-09", + "serverId": "1", + "name": "test-server", + "transport": "http_sse", + "config": {"url": "http://127.0.0.1:9999/mcp", "headers": {}}, + "connectTimeout": 5000, + "requestTimeout": 30000 + } + ] + } +}"#; + +/// Skip test if native library is not available +fn skip_if_native_library_not_available() -> bool { + !Path::new("native/lib").exists() +} + +#[test] +fn test_initialization() { + if skip_if_native_library_not_available() { + println!("Skipping test: Native library not built. Run ./build.sh first"); + return; + } + + // Test that initialization works + let result = init(); + assert!(result.is_ok() || is_initialized()); +} + +#[test] +fn test_create_agent_with_server_config() { + if skip_if_native_library_not_available() { + println!("Skipping test: Native library not built. Run ./build.sh first"); + return; + } + + let config = ConfigBuilder::new() + .with_provider("AnthropicProvider") + .with_model("claude-3-haiku-20240307") + .with_server_config(TEST_SERVER_CONFIG) + .build(); + + let result = GopherAgent::create(config); + + // Agent creation may fail without API key - this is expected in test environment + match result { + Ok(agent) => { + assert!(!agent.is_disposed()); + } + Err(e) => { + println!("Agent creation failed (expected without API key): {}", e); + } + } +} + +#[test] +fn test_create_agent_with_helper_method() { + if skip_if_native_library_not_available() { + println!("Skipping test: Native library not built. Run ./build.sh first"); + return; + } + + let result = GopherAgent::create_with_server_config( + "AnthropicProvider", + "claude-3-haiku-20240307", + TEST_SERVER_CONFIG, + ); + + // May fail without API key + if let Ok(agent) = result { + assert!(!agent.is_disposed()); + } +} + +#[test] +fn test_create_agent_with_api_key() { + if skip_if_native_library_not_available() { + println!("Skipping test: Native library not built. Run ./build.sh first"); + return; + } + + // Call with a dummy API key - should not crash + let result = GopherAgent::create_with_api_key( + "AnthropicProvider", + "claude-3-haiku-20240307", + "test-api-key-12345", + ); + + // May return error if API key is invalid, but should not panic + match result { + Ok(agent) => { + assert!(!agent.is_disposed()); + } + Err(e) => { + println!( + "Agent creation failed (expected with invalid API key): {}", + e + ); + } + } +} + +#[test] +fn test_create_with_empty_config() { + if skip_if_native_library_not_available() { + println!("Skipping test: Native library not built. Run ./build.sh first"); + return; + } + + let config = ConfigBuilder::new().build(); + let result = GopherAgent::create(config); + + assert!(result.is_err()); + match result { + Err(e) => assert!(e.to_string().contains("API key or server config")), + Ok(_) => panic!("Expected error for empty config"), + } +} + +#[test] +fn test_run_after_dispose() { + if skip_if_native_library_not_available() { + println!("Skipping test: Native library not built. Run ./build.sh first"); + return; + } + + let result = GopherAgent::create_with_server_config( + "AnthropicProvider", + "claude-3-haiku-20240307", + TEST_SERVER_CONFIG, + ); + + if let Ok(agent) = result { + // Drop the agent (disposes it) + drop(agent); + // Can't test run after dispose since we dropped it + // But we verified it compiles and doesn't panic + } +} + +#[test] +fn test_run_detailed_returns_result() { + if skip_if_native_library_not_available() { + println!("Skipping test: Native library not built. Run ./build.sh first"); + return; + } + + let result = GopherAgent::create_with_server_config( + "AnthropicProvider", + "claude-3-haiku-20240307", + TEST_SERVER_CONFIG, + ); + + if let Ok(agent) = result { + // Run with very short timeout to get quick response + let result = agent.run_detailed_with_timeout("test query", 100); + + // Should have some status (success, error, or timeout) + println!("Result status: {}", result.status()); + } +} + +#[test] +fn test_config_builder() { + let config = ConfigBuilder::new() + .with_provider("TestProvider") + .with_model("test-model") + .with_api_key("test-key") + .build(); + + assert_eq!(config.provider(), "TestProvider"); + assert_eq!(config.model(), "test-model"); + assert!(config.has_api_key()); + assert!(!config.has_server_config()); +} + +#[test] +fn test_config_builder_with_server_config() { + let config = ConfigBuilder::new() + .with_provider("TestProvider") + .with_model("test-model") + .with_server_config(TEST_SERVER_CONFIG) + .build(); + + assert_eq!(config.provider(), "TestProvider"); + assert_eq!(config.model(), "test-model"); + assert!(!config.has_api_key()); + assert!(config.has_server_config()); +} diff --git a/tests/orch/hello_test.cpp b/tests/orch/hello_test.cpp deleted file mode 100644 index a6672772..00000000 --- a/tests/orch/hello_test.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "orch/core/hello.h" - -#include -#include - -#include "orch/core/version.h" - -using namespace gopher::orch::core; -using namespace testing; - -class HelloTest : public ::testing::Test { - protected: - void SetUp() override { - // Setup code if needed - } - - void TearDown() override { - // Teardown code if needed - } -}; - -TEST_F(HelloTest, DefaultConstructor) { - Hello hello; - EXPECT_EQ(hello.greet(), "Hello, World!"); - EXPECT_EQ(hello.get_name(), "World"); -} - -TEST_F(HelloTest, ParameterizedConstructor) { - Hello hello("Alice"); - EXPECT_EQ(hello.greet(), "Hello, Alice!"); - EXPECT_EQ(hello.get_name(), "Alice"); -} - -TEST_F(HelloTest, SetName) { - Hello hello; - hello.set_name("Bob"); - EXPECT_EQ(hello.greet(), "Hello, Bob!"); - EXPECT_EQ(hello.get_name(), "Bob"); -} - -TEST_F(HelloTest, GreetWithPrefix) { - Hello hello("Charlie"); - EXPECT_EQ(hello.greet_with_prefix("Hi"), "Hi Charlie!"); - EXPECT_EQ(hello.greet_with_prefix("Welcome"), "Welcome Charlie!"); -} - -TEST_F(HelloTest, GetVersion) { EXPECT_EQ(Hello::get_version(), "0.1.0"); } - -TEST_F(HelloTest, EmptyName) { - Hello hello(""); - EXPECT_EQ(hello.greet(), "Hello, !"); - EXPECT_EQ(hello.get_name(), ""); -} - -TEST_F(HelloTest, SpecialCharacters) { - Hello hello("User@123!"); - EXPECT_EQ(hello.greet(), "Hello, User@123!!"); - EXPECT_EQ(hello.get_name(), "User@123!"); -} - -TEST_F(HelloTest, LongName) { - std::string long_name(1000, 'a'); - Hello hello(long_name); - EXPECT_EQ(hello.get_name(), long_name); - EXPECT_THAT(hello.greet(), StartsWith("Hello, ")); - EXPECT_THAT(hello.greet(), EndsWith("!")); -} - -// Test HelloBuilder -class HelloBuilderTest : public ::testing::Test { - protected: - HelloBuilder builder; -}; - -TEST_F(HelloBuilderTest, DefaultBuild) { - auto hello = builder.build(); - EXPECT_EQ(hello->greet(), "Hello, World!"); -} - -TEST_F(HelloBuilderTest, WithName) { - auto hello = builder.with_name("Diana").build(); - EXPECT_EQ(hello->greet(), "Hello, Diana!"); -} - -TEST_F(HelloBuilderTest, ChainedCalls) { - auto hello = builder.with_name("Eve").with_greeting_style("formal").build(); - EXPECT_EQ(hello->greet(), "Hello, Eve!"); -} - -TEST_F(HelloBuilderTest, MultipleBuildsSameBuilder) { - builder.with_name("Frank"); - auto hello1 = builder.build(); - auto hello2 = builder.build(); - - EXPECT_EQ(hello1->greet(), "Hello, Frank!"); - EXPECT_EQ(hello2->greet(), "Hello, Frank!"); - - // Verify they are independent objects - hello1->set_name("George"); - EXPECT_EQ(hello1->get_name(), "George"); - EXPECT_EQ(hello2->get_name(), "Frank"); -} - -// Version tests -TEST(VersionTest, VersionConstants) { - EXPECT_EQ(Version::major(), 0); - EXPECT_EQ(Version::minor(), 1); - EXPECT_EQ(Version::patch(), 0); - EXPECT_STREQ(Version::string(), "0.1.0"); -} diff --git a/third_party/gopher-mcp b/third_party/gopher-mcp deleted file mode 160000 index 5f6d6fd4..00000000 --- a/third_party/gopher-mcp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5f6d6fd4c70149eacab072cf9ba9a333107c0d36 diff --git a/third_party/gopher-orch b/third_party/gopher-orch new file mode 160000 index 00000000..6b45ffbb --- /dev/null +++ b/third_party/gopher-orch @@ -0,0 +1 @@ +Subproject commit 6b45ffbbee74d5ae034008fc2cb2a927f3131992