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 deleted file mode 100644 index 769b322b..00000000 --- a/.github/workflows/pr-format-check.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: PR Format Check - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - clang-format: - name: Clang Format - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - - steps: - - name: Checkout PR - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: recursive - - - name: Install clang-format - run: | - sudo apt-get update - sudo apt-get install -y clang-format-14 - - - name: Check changed files - id: changed-files - run: | - # Get list of changed C/C++ files (excluding submodules) - git diff --name-only origin/${{ github.base_ref }}...HEAD | \ - grep -E '\.(h|hpp|c|cc|cpp)$' | \ - 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:" - cat changed_files.txt - else - echo "has_changes=false" >> $GITHUB_OUTPUT - echo "No C/C++ files changed" - fi - - - name: Check formatting of changed files - 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 - - - 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 - - Some files in this PR are not properly formatted according to the project's clang-format rules. - - **To fix this issue:** - \`\`\`bash - make format - \`\`\` - - Then commit and push the changes.`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }); diff --git a/.gitignore b/.gitignore index 457c7687..64528d80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,109 +1,119 @@ -# Build directories -build/ -build-*/ -build_*/ -cmake-build-*/ -out/ -bin/ -lib/ -# Exception: Allow Ruby SDK lib directory -!sdk/ruby/lib/ - -# 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 - -# Compiled object files -*.o -*.obj -*.lo -*.slo +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ -# Precompiled Headers -*.gch -*.pch +# Visual Studio files +.vs/ +*.user +*.userosscache +*.sln.docstates +*.suo +*.cache +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc -# 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 +# Visual Studio Code .vscode/ + +# Rider .idea/ -*.swp -*.swo -*~ -.DS_Store -# Debug files -*.dSYM/ -*.su -*.idb +# User-specific files +*.rsuser +*.userprefs + +# NuGet +*.nupkg +*.snupkg +packages/ +project.lock.json +project.fragment.lock.json +artifacts/ +packages/ + +# MSBuild +*.gpState + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch *.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.vsix +*.manifest +*.spec -# Dependency directories -node_modules/ -vendor/ +# Test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# JetBrains Rider +*.sln.iml -# Coverage files -*.gcov -*.gcda -*.gcno -coverage/ -*.info +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db -# Documentation -docs/html/ -docs/latex/ -doxygen/ +# Native library build +native/ +cmake-build-*/ # Temporary files *.tmp *.temp -*.log +*.swp +*.swo +*~ -# Python cache (if using Python scripts) -__pycache__/ -*.py[cod] -*$py.class +# Node.js (for example MCP servers) +node_modules/ -# OS generated files -Thumbs.db -Desktop.ini \ No newline at end of file +# NuGet packages output +packages/ +examples/ClientExampleJson/ 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/GopherOrch.sln b/GopherOrch.sln new file mode 100644 index 00000000..b05b3b10 --- /dev/null +++ b/GopherOrch.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherOrch", "src\GopherOrch\GopherOrch.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GopherOrch.Tests", "tests\GopherOrch.Tests\GopherOrch.Tests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU + {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal 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..99d7abe7 --- /dev/null +++ b/README.md @@ -0,0 +1,218 @@ +# gopher-orch C# SDK + +C# SDK for gopher-orch, providing AI agent orchestration with native C++ performance through P/Invoke bindings. + +## Features + +- .NET Standard 2.0 compatibility (works with .NET Core 2.0+, .NET 5+, .NET Framework 4.6.1+) +- P/Invoke FFI bindings to native gopher-orch library +- `GopherAgent` class with builder pattern configuration +- `IDisposable` support for automatic resource cleanup +- Typed exceptions (`AgentException`, `ApiKeyException`, etc.) +- xUnit tests + +## Requirements + +- .NET SDK 6.0 or later (for building) +- CMake 3.16+ (for native library) +- C++17 compatible compiler + +## Quick Start + +### Building + +```bash +# Build everything (native library + C# SDK) +./build.sh + +# Or build manually: +dotnet build +dotnet test +``` + +### Usage + +```csharp +using GopherOrch; + +// Create an agent with API key +using var agent = GopherAgent.Create( + GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithModel("claude-3-haiku-20240307") + .WithApiKey("your-api-key") + .Build() +); + +// Run a query +string answer = agent.Run("What time is it in Tokyo?"); +Console.WriteLine(answer); +``` + +### Using JSON Server Configuration + +```csharp +using GopherOrch; + +string serverConfig = @"{ + ""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 + } + ] + } +}"; + +using var agent = GopherAgent.CreateWithServerConfig( + "AnthropicProvider", + "claude-3-haiku-20240307", + serverConfig +); + +string answer = agent.Run("What tools are available?"); +Console.WriteLine(answer); +``` + +## API Reference + +### GopherAgent + +Main entry point for agent operations. + +```csharp +// Create with config +var agent = GopherAgent.Create(config); + +// Create with API key +var agent = GopherAgent.Create(provider, model, apiKey); + +// Create with server config +var agent = GopherAgent.CreateWithServerConfig(provider, model, serverConfig); + +// Run query +string response = agent.Run("your query"); + +// Run with timeout +string response = agent.Run("your query", timeoutMs: 30000); + +// Run with detailed result +AgentResult result = agent.RunDetailed("your query"); + +// Cleanup +agent.Dispose(); +``` + +### GopherAgentConfig + +Configuration builder for agent creation. + +```csharp +var config = GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithModel("claude-3-haiku-20240307") + .WithApiKey("your-api-key") // OR + .WithServerConfig(jsonConfig) // Use one or the other + .Build(); +``` + +### AgentResult + +Detailed result from query execution. + +```csharp +AgentResult result = agent.RunDetailed("query"); + +Console.WriteLine(result.Response); +Console.WriteLine(result.Status); // Success, Error, Timeout +Console.WriteLine(result.IsSuccess); +Console.WriteLine(result.IterationCount); +Console.WriteLine(result.TokensUsed); +``` + +### Exceptions + +- `AgentException` - Base exception for agent operations +- `ApiKeyException` - API key related errors +- `ConnectionException` - Connection related errors +- `TimeoutException` - Timeout related errors + +## Development + +### Code Formatting + +```bash +# Format code +dotnet format + +# Check formatting without making changes +dotnet format --verify-no-changes + +# Build with warnings +dotnet build + +# Run tests +dotnet test +``` + +## Examples + +The `examples/` directory contains runnable examples: + +```bash +# Run the JSON server config example +./examples/client_example_json_run.sh +``` + +This starts two MCP servers (server3001, server3002) and runs a client that queries them. + +## Project Structure + +``` +gopher-mcp-csharp/ +├── GopherOrch.sln # Solution file +├── build.sh # Build script +├── src/ +│ └── GopherOrch/ # Main library +│ ├── GopherOrch.csproj +│ ├── GopherAgent.cs +│ ├── GopherAgentConfig.cs +│ ├── AgentResult.cs +│ ├── AgentResultStatus.cs +│ ├── ServerConfig.cs +│ ├── Errors/ +│ │ ├── AgentException.cs +│ │ ├── ApiKeyException.cs +│ │ ├── ConnectionException.cs +│ │ └── TimeoutException.cs +│ └── Ffi/ +│ └── GopherOrchLibrary.cs +├── tests/ +│ └── GopherOrch.Tests/ # Unit tests +│ ├── GopherOrch.Tests.csproj +│ ├── AgentResultTest.cs +│ └── GopherAgentConfigTest.cs +├── examples/ +│ ├── client_example_json_run.sh # Example runner script +│ ├── ClientExampleJson/ # Example client project +│ ├── server3001/ # MCP server example +│ └── server3002/ # MCP server example +├── third_party/ +│ └── gopher-orch/ # Native library (git submodule) +└── native/ # Built native libraries (generated) + ├── lib/ + └── include/ +``` + +## License + +See [LICENSE](LICENSE) for details. diff --git a/build.sh b/build.sh index 3aa8a1ca..a3ab76c0 100755 --- a/build.sh +++ b/build.sh @@ -1,123 +1,183 @@ -#!/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 -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 C# 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) -echo -e "${GREEN}Build completed successfully!${NC}" +# 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 + +echo -e "${GREEN}✓ Native library built successfully${NC}" +echo "" + +# Step 4: Verify build artifacts +echo -e "${YELLOW}Step 3: Verifying native build artifacts...${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 -# 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}" +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 C# SDK +echo -e "${YELLOW}Step 4: Building C# SDK...${NC}" +cd "${SCRIPT_DIR}" + +# Check for dotnet +if ! command -v dotnet &> /dev/null; then + echo -e "${RED}Error: .NET SDK not found. Please install .NET SDK first.${NC}" + echo -e "${YELLOW} macOS: brew install dotnet${NC}" + echo -e "${YELLOW} Linux: https://docs.microsoft.com/en-us/dotnet/core/install/linux${NC}" + exit 1 fi -echo -e "${GREEN}=== Build Complete ===${NC}" +# Build with dotnet +echo -e "${YELLOW} Compiling C# SDK...${NC}" +dotnet build -c Release + +echo -e "${GREEN}✓ C# SDK built successfully${NC}" +echo "" + +# Step 5: Run tests +echo -e "${YELLOW}Step 5: Running tests...${NC}" +dotnet test -c Release --no-build 2>/dev/null && echo -e "${GREEN}✓ Tests passed${NC}" || echo -e "${YELLOW}⚠ Some tests may have failed (native library required)${NC}" + +# Package NuGet +echo -e "${YELLOW}Step 6: Packaging NuGet...${NC}" +dotnet pack -c Release --no-build -o "${SCRIPT_DIR}/packages" +echo -e "${GREEN}✓ NuGet package created${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 -e "Run tests: ${YELLOW}dotnet test${NC}" +echo -e "Package NuGet: ${YELLOW}dotnet pack${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/ClientExampleJson.cs b/examples/ClientExampleJson.cs new file mode 100644 index 00000000..c84c258b --- /dev/null +++ b/examples/ClientExampleJson.cs @@ -0,0 +1,74 @@ +using System; +using GopherOrch; + +namespace Examples +{ + /// + /// Example using JSON server configuration. + /// + public class ClientExampleJson + { + // Server configuration for local MCP servers + private static readonly string ServerConfig = "{" + + "\"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" + + " }" + + " ]" + + "}" + + "}"; + + public static void Main(string[] args) + { + string provider = "AnthropicProvider"; + string model = "claude-3-haiku-20240307"; + + try + { + // Create agent with JSON server configuration + using var agent = GopherAgent.CreateWithServerConfig( + provider, + model, + ServerConfig + ); + Console.WriteLine("GopherAgent created!"); + + // Get question from command line args or use default + string question = args.Length > 0 + ? string.Join(" ", args) + : "What is the weather like in New York?"; + Console.WriteLine($"Question: {question}"); + + // Run the query + string answer = agent.Run(question); + Console.WriteLine("Answer:"); + Console.WriteLine(answer); + } + catch (Exception e) + { + Console.Error.WriteLine($"Error: {e.Message}"); + Console.Error.WriteLine(e.StackTrace); + Environment.Exit(1); + } + } + } +} diff --git a/examples/client_example_json_run.sh b/examples/client_example_json_run.sh new file mode 100755 index 00000000..5f2b49e0 --- /dev/null +++ b/examples/client_example_json_run.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# Run the C# 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 C# 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 C# example +echo "" +echo -e "${YELLOW}Building and running C# client...${NC}" +echo "" +cd "$PROJECT_DIR" + +# Create a temporary console project for the example +EXAMPLE_PROJECT_DIR="$PROJECT_DIR/examples/ClientExampleJson" +mkdir -p "$EXAMPLE_PROJECT_DIR" + +# Create project file (use forward slashes for cross-platform compatibility) +cat > "$EXAMPLE_PROJECT_DIR/ClientExampleJson.csproj" << 'EOF' + + + + Exe + net6.0 + 10.0 + enable + + + + + + + + + + + +EOF + +# Run with dotnet +DYLD_LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +LD_LIBRARY_PATH="$PROJECT_DIR/native/lib" \ +dotnet run --project "$EXAMPLE_PROJECT_DIR/ClientExampleJson.csproj" -- "$@" + +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/GopherOrch/AgentResult.cs b/src/GopherOrch/AgentResult.cs new file mode 100644 index 00000000..e96c9215 --- /dev/null +++ b/src/GopherOrch/AgentResult.cs @@ -0,0 +1,129 @@ +using System; + +namespace GopherOrch +{ + /// + /// Result from agent query execution. + /// + public sealed class AgentResult + { + /// + /// The response from the agent. + /// + public string Response { get; } + + /// + /// The status of the query execution. + /// + public AgentResultStatus Status { get; } + + /// + /// Number of iterations performed. + /// + public int? IterationCount { get; } + + /// + /// Number of tokens used. + /// + public int? TokensUsed { get; } + + private AgentResult(string response, AgentResultStatus status, int? iterationCount, int? tokensUsed) + { + Response = response ?? throw new ArgumentNullException(nameof(response)); + Status = status; + IterationCount = iterationCount; + TokensUsed = tokensUsed; + } + + /// + /// Check if the query was successful. + /// + public bool IsSuccess => Status == AgentResultStatus.Success; + + /// + /// Create a new builder for AgentResult. + /// + public static Builder CreateBuilder() => new Builder(); + + /// + /// Create a success result. + /// + public static AgentResult Success(string response) + { + return CreateBuilder() + .WithResponse(response) + .WithStatus(AgentResultStatus.Success) + .Build(); + } + + /// + /// Create an error result. + /// + public static AgentResult Error(string message) + { + return CreateBuilder() + .WithResponse(message) + .WithStatus(AgentResultStatus.Error) + .Build(); + } + + /// + /// Create a timeout result. + /// + public static AgentResult Timeout(string message) + { + return CreateBuilder() + .WithResponse(message) + .WithStatus(AgentResultStatus.Timeout) + .Build(); + } + + public override string ToString() + { + return $"AgentResult{{Response='{Response}', Status={Status}, IterationCount={IterationCount}, TokensUsed={TokensUsed}}}"; + } + + /// + /// Builder for AgentResult. + /// + public sealed class Builder + { + private string? _response; + private AgentResultStatus _status; + private int? _iterationCount; + private int? _tokensUsed; + + public Builder WithResponse(string response) + { + _response = response; + return this; + } + + public Builder WithStatus(AgentResultStatus status) + { + _status = status; + return this; + } + + public Builder WithIterationCount(int? iterationCount) + { + _iterationCount = iterationCount; + return this; + } + + public Builder WithTokensUsed(int? tokensUsed) + { + _tokensUsed = tokensUsed; + return this; + } + + public AgentResult Build() + { + if (_response == null) + throw new InvalidOperationException("Response is required"); + + return new AgentResult(_response, _status, _iterationCount, _tokensUsed); + } + } + } +} diff --git a/src/GopherOrch/AgentResultStatus.cs b/src/GopherOrch/AgentResultStatus.cs new file mode 100644 index 00000000..0f69648a --- /dev/null +++ b/src/GopherOrch/AgentResultStatus.cs @@ -0,0 +1,23 @@ +namespace GopherOrch +{ + /// + /// Status of agent query execution. + /// + public enum AgentResultStatus + { + /// + /// Query completed successfully. + /// + Success, + + /// + /// Query failed with an error. + /// + Error, + + /// + /// Query timed out. + /// + Timeout + } +} diff --git a/src/GopherOrch/Errors/AgentException.cs b/src/GopherOrch/Errors/AgentException.cs new file mode 100644 index 00000000..ed7e5915 --- /dev/null +++ b/src/GopherOrch/Errors/AgentException.cs @@ -0,0 +1,35 @@ +using System; + +namespace GopherOrch.Errors +{ + /// + /// Base exception class for agent operations. + /// + public class AgentException : Exception + { + /// + /// Error code from the native library. + /// + public string? Code { get; } + + public AgentException(string message) : base(message) + { + Code = null; + } + + public AgentException(string message, string? code) : base(message) + { + Code = code; + } + + public AgentException(string message, Exception innerException) : base(message, innerException) + { + Code = null; + } + + public AgentException(string message, string? code, Exception innerException) : base(message, innerException) + { + Code = code; + } + } +} diff --git a/src/GopherOrch/Errors/ApiKeyException.cs b/src/GopherOrch/Errors/ApiKeyException.cs new file mode 100644 index 00000000..85a61873 --- /dev/null +++ b/src/GopherOrch/Errors/ApiKeyException.cs @@ -0,0 +1,16 @@ +namespace GopherOrch.Errors +{ + /// + /// Exception for API key related errors. + /// + public class ApiKeyException : AgentException + { + public ApiKeyException(string message) : base(message, "API_KEY_ERROR") + { + } + + public ApiKeyException(string message, string? code) : base(message, code) + { + } + } +} diff --git a/src/GopherOrch/Errors/ConnectionException.cs b/src/GopherOrch/Errors/ConnectionException.cs new file mode 100644 index 00000000..6f5c722c --- /dev/null +++ b/src/GopherOrch/Errors/ConnectionException.cs @@ -0,0 +1,16 @@ +namespace GopherOrch.Errors +{ + /// + /// Exception for connection related errors. + /// + public class ConnectionException : AgentException + { + public ConnectionException(string message) : base(message, "CONNECTION_ERROR") + { + } + + public ConnectionException(string message, string? code) : base(message, code) + { + } + } +} diff --git a/src/GopherOrch/Errors/TimeoutException.cs b/src/GopherOrch/Errors/TimeoutException.cs new file mode 100644 index 00000000..c19550c6 --- /dev/null +++ b/src/GopherOrch/Errors/TimeoutException.cs @@ -0,0 +1,16 @@ +namespace GopherOrch.Errors +{ + /// + /// Exception for timeout related errors. + /// + public class TimeoutException : AgentException + { + public TimeoutException(string message) : base(message, "TIMEOUT_ERROR") + { + } + + public TimeoutException(string message, string? code) : base(message, code) + { + } + } +} diff --git a/src/GopherOrch/Ffi/GopherOrchLibrary.cs b/src/GopherOrch/Ffi/GopherOrchLibrary.cs new file mode 100644 index 00000000..adae6958 --- /dev/null +++ b/src/GopherOrch/Ffi/GopherOrchLibrary.cs @@ -0,0 +1,187 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace GopherOrch.Ffi +{ + /// + /// P/Invoke bindings to the gopher-orch native library. + /// + public static class GopherOrchLibrary + { + private const string LibraryName = "gopher-orch"; + + private static bool? _isAvailable; + private static readonly object _lock = new object(); + + /// + /// Check if the native library is available. + /// + public static bool IsAvailable + { + get + { + if (_isAvailable.HasValue) + return _isAvailable.Value; + + lock (_lock) + { + if (_isAvailable.HasValue) + return _isAvailable.Value; + + try + { + // Try to call a simple function to verify library is loaded + var ptr = gopher_orch_last_error(); + _isAvailable = true; + } + catch (DllNotFoundException) + { + _isAvailable = false; + } + catch (EntryPointNotFoundException) + { + _isAvailable = false; + } + + return _isAvailable.Value; + } + } + } + + // Agent functions + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public static extern IntPtr gopher_orch_agent_create_by_json( + string provider, + string model, + string serverJson); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public static extern IntPtr gopher_orch_agent_create_by_api_key( + string provider, + string model, + string apiKey); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "gopher_orch_agent_run")] + private static extern IntPtr gopher_orch_agent_run_native( + IntPtr agent, + string query, + long timeoutMs); + + /// + /// Run a query on the agent. + /// + public static string? AgentRun(IntPtr agent, string query, long timeoutMs) + { + IntPtr resultPtr = gopher_orch_agent_run_native(agent, query, timeoutMs); + if (resultPtr == IntPtr.Zero) + return null; + return Marshal.PtrToStringAnsi(resultPtr); + } + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] + public static extern void gopher_orch_agent_add_ref(IntPtr agent); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] + public static extern void gopher_orch_agent_release(IntPtr agent); + + // API functions + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "gopher_orch_api_fetch_servers")] + private static extern IntPtr gopher_orch_api_fetch_servers_native(string apiKey); + + /// + /// Fetch servers configuration from API. + /// + public static string? ApiFetchServers(string apiKey) + { + IntPtr resultPtr = gopher_orch_api_fetch_servers_native(apiKey); + if (resultPtr == IntPtr.Zero) + return null; + return Marshal.PtrToStringAnsi(resultPtr); + } + + // Error functions + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr gopher_orch_last_error(); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] + public static extern void gopher_orch_clear_error(); + + [DllImport(LibraryName, CallingConvention = CallingConvention.Cdecl)] + public static extern void gopher_orch_free(IntPtr ptr); + + /// + /// Get the last error message from the native library. + /// + public static string? GetLastError() + { + if (!IsAvailable) + return null; + + try + { + IntPtr errorPtr = gopher_orch_last_error(); + if (errorPtr == IntPtr.Zero) + return null; + + // Read the error info structure + var errorInfo = Marshal.PtrToStructure(errorPtr); + return errorInfo.Message; + } + catch + { + return null; + } + } + + /// + /// Set the native library search path. + /// Call this before any other library operations. + /// + public static void SetLibraryPath(string path) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + SetDllDirectory(path); + } + else + { + // On Unix, we need to set the path before library load + var ldPath = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH") ?? ""; + var dyldPath = Environment.GetEnvironmentVariable("DYLD_LIBRARY_PATH") ?? ""; + + Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", + string.IsNullOrEmpty(ldPath) ? path : path + Path.PathSeparator + ldPath); + Environment.SetEnvironmentVariable("DYLD_LIBRARY_PATH", + string.IsNullOrEmpty(dyldPath) ? path : path + Path.PathSeparator + dyldPath); + } + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SetDllDirectory(string lpPathName); + } + + /// + /// Error info structure matching C: + /// typedef struct { + /// gopher_orch_error_t code; + /// const char* message; + /// const char* details; + /// const char* file; + /// int32_t line; + /// } gopher_orch_error_info_t; + /// + [StructLayout(LayoutKind.Sequential)] + public struct GopherOrchErrorInfo + { + public int Code; + public IntPtr MessagePtr; + public IntPtr DetailsPtr; + public IntPtr FilePtr; + public int Line; + + public string? Message => MessagePtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(MessagePtr) : null; + public string? Details => DetailsPtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(DetailsPtr) : null; + public string? File => FilePtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(FilePtr) : null; + } +} diff --git a/src/GopherOrch/GopherAgent.cs b/src/GopherOrch/GopherAgent.cs new file mode 100644 index 00000000..0ba49172 --- /dev/null +++ b/src/GopherOrch/GopherAgent.cs @@ -0,0 +1,306 @@ +using System; +using GopherOrch.Errors; +using GopherOrch.Ffi; + +namespace GopherOrch +{ + /// + /// GopherAgent - Main entry point for the gopher-orch C# SDK. + /// + /// Provides a clean, C#-friendly interface to the gopher-orch agent functionality. + /// + /// Example: + /// + /// // Create an agent with API key + /// var agent = GopherAgent.Create( + /// GopherAgentConfig.CreateBuilder() + /// .WithProvider("AnthropicProvider") + /// .WithModel("claude-3-haiku-20240307") + /// .WithApiKey("your-api-key") + /// .Build() + /// ); + /// + /// // Run a query + /// string answer = agent.Run("What time is it in Tokyo?"); + /// Console.WriteLine(answer); + /// + /// // Cleanup (optional - happens automatically on Dispose) + /// agent.Dispose(); + /// + /// + /// Or use `using` statement for automatic cleanup: + /// + /// using (var agent = GopherAgent.Create(config)) + /// { + /// string answer = agent.Run("What time is it in Tokyo?"); + /// Console.WriteLine(answer); + /// } + /// + /// + public sealed class GopherAgent : IDisposable + { + private static readonly object _initLock = new object(); + private static bool _initialized; + + private readonly IntPtr _handle; + private bool _disposed; + private readonly object _disposeLock = new object(); + + private GopherAgent(IntPtr handle) + { + _handle = handle; + } + + /// + /// Initialize the gopher-orch library. + /// Must be called before creating any agents. Called automatically by Create() if not already initialized. + /// + public static void Init() + { + lock (_initLock) + { + if (_initialized) + return; + + if (!GopherOrchLibrary.IsAvailable) + { + throw new AgentException("Failed to load gopher-orch native library"); + } + + _initialized = true; + } + } + + /// + /// Shutdown the gopher-orch library. + /// + public static void Shutdown() + { + lock (_initLock) + { + _initialized = false; + } + } + + /// + /// Check if the library is initialized. + /// + public static bool IsInitialized + { + get + { + lock (_initLock) + { + return _initialized; + } + } + } + + /// + /// Create a new GopherAgent instance. + /// + /// Agent configuration + /// GopherAgent instance + /// If agent creation fails + public static GopherAgent Create(GopherAgentConfig config) + { + if (config == null) + throw new ArgumentNullException(nameof(config)); + + if (!_initialized) + { + Init(); + } + + IntPtr handle; + try + { + if (config.HasApiKey) + { + handle = GopherOrchLibrary.gopher_orch_agent_create_by_api_key( + config.Provider, + config.Model, + config.ApiKey! + ); + } + else + { + handle = GopherOrchLibrary.gopher_orch_agent_create_by_json( + config.Provider, + config.Model, + config.ServerConfig! + ); + } + } + catch (Exception e) + { + throw new AgentException("Failed to create agent: " + e.Message, e); + } + + if (handle == IntPtr.Zero) + { + var error = GopherOrchLibrary.GetLastError(); + GopherOrchLibrary.gopher_orch_clear_error(); + throw new AgentException(error ?? "Failed to create agent from JSON configuration"); + } + + return new GopherAgent(handle); + } + + /// + /// Create a new GopherAgent with API key. + /// + /// Provider name (e.g., "AnthropicProvider") + /// Model name (e.g., "claude-3-haiku-20240307") + /// API key for fetching remote server config + /// GopherAgent instance + public static GopherAgent Create(string provider, string model, string apiKey) + { + return Create(GopherAgentConfig.CreateBuilder() + .WithProvider(provider) + .WithModel(model) + .WithApiKey(apiKey) + .Build()); + } + + /// + /// Create a new GopherAgent with JSON server config. + /// + /// Provider name (e.g., "AnthropicProvider") + /// Model name (e.g., "claude-3-haiku-20240307") + /// JSON server configuration + /// GopherAgent instance + public static GopherAgent CreateWithServerConfig(string provider, string model, string serverConfig) + { + return Create(GopherAgentConfig.CreateBuilder() + .WithProvider(provider) + .WithModel(model) + .WithServerConfig(serverConfig) + .Build()); + } + + /// + /// Run a query against the agent. + /// + /// The user query to process + /// The agent's response + /// If the query fails + public string Run(string query) + { + return Run(query, 60000); + } + + /// + /// Run a query against the agent with custom timeout. + /// + /// The user query to process + /// Timeout in milliseconds + /// The agent's response + /// If the query fails + public string Run(string query, long timeoutMs) + { + EnsureNotDisposed(); + + try + { + var response = GopherOrchLibrary.AgentRun(_handle, query, timeoutMs); + if (response == null) + { + return $"No response for query: \"{query}\""; + } + return response; + } + catch (Exception e) + { + throw new AgentException("Query execution failed: " + e.Message, e); + } + } + + /// + /// Run a query with detailed result information. + /// + /// The user query to process + /// AgentResult with response and metadata + public AgentResult RunDetailed(string query) + { + return RunDetailed(query, 60000); + } + + /// + /// Run a query with detailed result information and custom timeout. + /// + /// The user query to process + /// Timeout in milliseconds + /// AgentResult with response and metadata + public AgentResult RunDetailed(string query, long timeoutMs) + { + try + { + var response = Run(query, timeoutMs); + return AgentResult.CreateBuilder() + .WithResponse(response) + .WithStatus(AgentResultStatus.Success) + .WithIterationCount(1) + .WithTokensUsed(0) + .Build(); + } + catch (Errors.TimeoutException e) + { + return AgentResult.Timeout(e.Message); + } + catch (Exception e) + { + return AgentResult.Error(e.Message); + } + } + + /// + /// Check if agent is disposed. + /// + public bool IsDisposed + { + get + { + lock (_disposeLock) + { + return _disposed; + } + } + } + + /// + /// Dispose of the agent and free resources. + /// + public void Dispose() + { + lock (_disposeLock) + { + if (_disposed) + return; + + _disposed = true; + + if (_handle != IntPtr.Zero) + { + GopherOrchLibrary.gopher_orch_agent_release(_handle); + } + } + } + + private void EnsureNotDisposed() + { + lock (_disposeLock) + { + if (_disposed) + { + throw new AgentException("Agent has been disposed"); + } + } + } + + ~GopherAgent() + { + Dispose(); + } + } +} diff --git a/src/GopherOrch/GopherAgentConfig.cs b/src/GopherOrch/GopherAgentConfig.cs new file mode 100644 index 00000000..8695a4bc --- /dev/null +++ b/src/GopherOrch/GopherAgentConfig.cs @@ -0,0 +1,121 @@ +using System; + +namespace GopherOrch +{ + /// + /// Configuration options for creating a GopherAgent. + /// + public sealed class GopherAgentConfig + { + /// + /// The LLM provider (e.g., "AnthropicProvider"). + /// + public string Provider { get; } + + /// + /// The model name (e.g., "claude-3-haiku-20240307"). + /// + public string Model { get; } + + /// + /// The API key for fetching remote server config (optional). + /// + public string? ApiKey { get; } + + /// + /// The JSON server configuration (optional). + /// + public string? ServerConfig { get; } + + private GopherAgentConfig(string provider, string model, string? apiKey, string? serverConfig) + { + Provider = provider ?? throw new ArgumentNullException(nameof(provider)); + Model = model ?? throw new ArgumentNullException(nameof(model)); + ApiKey = apiKey; + ServerConfig = serverConfig; + + if (apiKey == null && serverConfig == null) + { + throw new ArgumentException("Either apiKey or serverConfig is required"); + } + if (apiKey != null && serverConfig != null) + { + throw new ArgumentException("Cannot specify both apiKey and serverConfig"); + } + } + + /// + /// Check if this config uses an API key. + /// + public bool HasApiKey => ApiKey != null; + + /// + /// Check if this config uses a server config. + /// + public bool HasServerConfig => ServerConfig != null; + + /// + /// Create a new builder for GopherAgentConfig. + /// + public static Builder CreateBuilder() => new Builder(); + + /// + /// Builder for GopherAgentConfig. + /// + public sealed class Builder + { + private string? _provider; + private string? _model; + private string? _apiKey; + private string? _serverConfig; + + /// + /// Set the LLM provider (e.g., "AnthropicProvider"). + /// + public Builder WithProvider(string provider) + { + _provider = provider; + return this; + } + + /// + /// Set the model name (e.g., "claude-3-haiku-20240307"). + /// + public Builder WithModel(string model) + { + _model = model; + return this; + } + + /// + /// Set the API key for fetching remote server config. + /// Mutually exclusive with ServerConfig. + /// + public Builder WithApiKey(string apiKey) + { + _apiKey = apiKey; + return this; + } + + /// + /// Set the JSON server configuration. + /// Mutually exclusive with ApiKey. + /// + public Builder WithServerConfig(string serverConfig) + { + _serverConfig = serverConfig; + return this; + } + + public GopherAgentConfig Build() + { + if (_provider == null) + throw new InvalidOperationException("Provider is required"); + if (_model == null) + throw new InvalidOperationException("Model is required"); + + return new GopherAgentConfig(_provider, _model, _apiKey, _serverConfig); + } + } + } +} diff --git a/src/GopherOrch/GopherOrch.csproj b/src/GopherOrch/GopherOrch.csproj new file mode 100644 index 00000000..74627a6a --- /dev/null +++ b/src/GopherOrch/GopherOrch.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + 8.0 + enable + GopherOrch + 0.1.0 + GopherSecurity + GopherSecurity + C# SDK for gopher-orch - MCP client orchestration framework + mcp;ai;orchestration;gopher + false + + + + + + + diff --git a/src/GopherOrch/ServerConfig.cs b/src/GopherOrch/ServerConfig.cs new file mode 100644 index 00000000..6571bd3a --- /dev/null +++ b/src/GopherOrch/ServerConfig.cs @@ -0,0 +1,20 @@ +using GopherOrch.Ffi; + +namespace GopherOrch +{ + /// + /// Utility class for fetching server configuration from the API. + /// + public static class ServerConfig + { + /// + /// Fetch server configuration from the API using an API key. + /// + /// The API key to use for authentication + /// JSON string containing server configuration + public static string? FetchServers(string apiKey) + { + return GopherOrchLibrary.ApiFetchServers(apiKey); + } + } +} 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/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/GopherOrch.Tests/AgentResultTest.cs b/tests/GopherOrch.Tests/AgentResultTest.cs new file mode 100644 index 00000000..3a1bcbca --- /dev/null +++ b/tests/GopherOrch.Tests/AgentResultTest.cs @@ -0,0 +1,63 @@ +using Xunit; + +namespace GopherOrch.Tests +{ + public class AgentResultTest + { + [Fact] + public void TestSuccessResult() + { + var result = AgentResult.Success("test response"); + + Assert.Equal("test response", result.Response); + Assert.Equal(AgentResultStatus.Success, result.Status); + Assert.True(result.IsSuccess); + } + + [Fact] + public void TestErrorResult() + { + var result = AgentResult.Error("error message"); + + Assert.Equal("error message", result.Response); + Assert.Equal(AgentResultStatus.Error, result.Status); + Assert.False(result.IsSuccess); + } + + [Fact] + public void TestTimeoutResult() + { + var result = AgentResult.Timeout("timeout message"); + + Assert.Equal("timeout message", result.Response); + Assert.Equal(AgentResultStatus.Timeout, result.Status); + Assert.False(result.IsSuccess); + } + + [Fact] + public void TestBuilderWithAllFields() + { + var result = AgentResult.CreateBuilder() + .WithResponse("response") + .WithStatus(AgentResultStatus.Success) + .WithIterationCount(5) + .WithTokensUsed(100) + .Build(); + + Assert.Equal("response", result.Response); + Assert.Equal(AgentResultStatus.Success, result.Status); + Assert.Equal(5, result.IterationCount); + Assert.Equal(100, result.TokensUsed); + } + + [Fact] + public void TestToString() + { + var result = AgentResult.Success("test"); + var str = result.ToString(); + + Assert.Contains("test", str); + Assert.Contains("Success", str); + } + } +} diff --git a/tests/GopherOrch.Tests/Ffi/GopherOrchLibraryTest.cs b/tests/GopherOrch.Tests/Ffi/GopherOrchLibraryTest.cs new file mode 100644 index 00000000..e1cf82f9 --- /dev/null +++ b/tests/GopherOrch.Tests/Ffi/GopherOrchLibraryTest.cs @@ -0,0 +1,198 @@ +using System; +using GopherOrch.Ffi; +using Xunit; + +namespace GopherOrch.Tests.Ffi +{ + /// + /// Tests for FFI bindings to the native gopher-orch library. + /// These tests verify that the C# side can correctly call C++ functions + /// through P/Invoke FFI bindings. + /// + public class GopherOrchLibraryTest + { + private static readonly string ServerConfig = "{" + + "\"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" + + " }" + + " ]" + + "}" + + "}"; + + /// + /// Check if native library is available for tests. + /// + private static bool IsNativeLibraryAvailable => GopherOrchLibrary.IsAvailable; + + [SkippableFact] + public void TestLibraryIsAvailable() + { + // This test verifies that the native library can be loaded + bool available = GopherOrchLibrary.IsAvailable; + Skip.IfNot(available, "Native library not available. " + + "Run ./build.sh first to build the native library."); + Assert.True(available); + } + + [SkippableFact] + public void TestAgentCreateByJsonWithValidConfig() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Valid server configuration JSON + // Call native function to create agent + var handle = GopherOrchLibrary.gopher_orch_agent_create_by_json( + "AnthropicProvider", + "claude-3-haiku-20240307", + ServerConfig + ); + + // Agent should be created (handle may be null if no API key, but function should not crash) + // The important thing is that the FFI call works without throwing + if (handle != IntPtr.Zero) + { + // Clean up if agent was created + GopherOrchLibrary.gopher_orch_agent_release(handle); + } + } + + [SkippableFact] + public void TestAgentCreateByJsonWithEmptyConfig() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Empty/invalid config should return null handle + var handle = GopherOrchLibrary.gopher_orch_agent_create_by_json( + "AnthropicProvider", + "claude-3-haiku-20240307", + "{}" + ); + + // Should handle gracefully (null or valid pointer, but no crash) + if (handle != IntPtr.Zero) + { + GopherOrchLibrary.gopher_orch_agent_release(handle); + } + } + + [SkippableFact] + public void TestAgentCreateByApiKey() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Call with a dummy API key - should not crash + var handle = GopherOrchLibrary.gopher_orch_agent_create_by_api_key( + "AnthropicProvider", + "claude-3-haiku-20240307", + "test-api-key-12345" + ); + + // May return null if API key is invalid, but should not crash + if (handle != IntPtr.Zero) + { + GopherOrchLibrary.gopher_orch_agent_release(handle); + } + } + + [SkippableFact] + public void TestLastErrorAndClearError() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Try to get last error (may be null if no error) + var exception = Record.Exception(() => + { + var errorPtr = GopherOrchLibrary.gopher_orch_last_error(); + // errorPtr may be null or have null message if no error + }); + + Assert.Null(exception); + + // Clear error should not throw + exception = Record.Exception(() => + { + GopherOrchLibrary.gopher_orch_clear_error(); + }); + + Assert.Null(exception); + } + + [SkippableFact] + public void TestApiFetchServers() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Call with dummy API key - should return JSON (possibly error response) + var exception = Record.Exception(() => + { + var result = GopherOrchLibrary.ApiFetchServers("test-api-key"); + // Result may be null or contain error, but should not crash + }); + + Assert.Null(exception); + } + + [SkippableFact] + public void TestAgentRunWithNullHandle() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Running with null handle should be handled gracefully + var exception = Record.Exception(() => + { + var result = GopherOrchLibrary.AgentRun(IntPtr.Zero, "test query", 1000); + // May return null or error message, but should not crash + }); + + // Exception is acceptable for null handle + } + + [SkippableFact] + public void TestAgentReleaseWithNullHandle() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Releasing null handle should be handled gracefully + var exception = Record.Exception(() => + { + GopherOrchLibrary.gopher_orch_agent_release(IntPtr.Zero); + }); + + // Exception is acceptable for null handle + } + + [SkippableFact] + public void TestFreeWithNullPointer() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Free with null should be handled gracefully + var exception = Record.Exception(() => + { + GopherOrchLibrary.gopher_orch_free(IntPtr.Zero); + }); + + // Exception is acceptable for null pointer + } + + [Fact] + public void TestGetLastErrorWhenLibraryNotLoaded() + { + // This tests the static helper method + var error = GopherOrchLibrary.GetLastError(); + // Should return null gracefully, not throw + // (error may be null or a message depending on state) + } + } +} diff --git a/tests/GopherOrch.Tests/GopherAgentConfigTest.cs b/tests/GopherOrch.Tests/GopherAgentConfigTest.cs new file mode 100644 index 00000000..eda9551d --- /dev/null +++ b/tests/GopherOrch.Tests/GopherAgentConfigTest.cs @@ -0,0 +1,85 @@ +using System; +using Xunit; + +namespace GopherOrch.Tests +{ + public class GopherAgentConfigTest + { + [Fact] + public void TestBuildWithApiKey() + { + var config = GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithModel("claude-3-haiku-20240307") + .WithApiKey("test-api-key") + .Build(); + + Assert.Equal("AnthropicProvider", config.Provider); + Assert.Equal("claude-3-haiku-20240307", config.Model); + Assert.Equal("test-api-key", config.ApiKey); + Assert.Null(config.ServerConfig); + Assert.True(config.HasApiKey); + Assert.False(config.HasServerConfig); + } + + [Fact] + public void TestBuildWithServerConfig() + { + var serverConfig = "{\"servers\": []}"; + var config = GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithModel("claude-3-haiku-20240307") + .WithServerConfig(serverConfig) + .Build(); + + Assert.Equal("AnthropicProvider", config.Provider); + Assert.Equal("claude-3-haiku-20240307", config.Model); + Assert.Null(config.ApiKey); + Assert.Equal(serverConfig, config.ServerConfig); + Assert.False(config.HasApiKey); + Assert.True(config.HasServerConfig); + } + + [Fact] + public void TestBuildWithoutProviderThrows() + { + var builder = GopherAgentConfig.CreateBuilder() + .WithModel("claude-3-haiku-20240307") + .WithApiKey("test-api-key"); + + Assert.Throws(() => builder.Build()); + } + + [Fact] + public void TestBuildWithoutModelThrows() + { + var builder = GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithApiKey("test-api-key"); + + Assert.Throws(() => builder.Build()); + } + + [Fact] + public void TestBuildWithoutApiKeyOrServerConfigThrows() + { + var builder = GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithModel("claude-3-haiku-20240307"); + + Assert.Throws(() => builder.Build()); + } + + [Fact] + public void TestBuildWithBothApiKeyAndServerConfigThrows() + { + var builder = GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithModel("claude-3-haiku-20240307") + .WithApiKey("test-api-key") + .WithServerConfig("{\"servers\": []}"); + + Assert.Throws(() => builder.Build()); + } + } +} diff --git a/tests/GopherOrch.Tests/GopherAgentIntegrationTest.cs b/tests/GopherOrch.Tests/GopherAgentIntegrationTest.cs new file mode 100644 index 00000000..738ee376 --- /dev/null +++ b/tests/GopherOrch.Tests/GopherAgentIntegrationTest.cs @@ -0,0 +1,260 @@ +using System; +using GopherOrch.Errors; +using GopherOrch.Ffi; +using Xunit; + +namespace GopherOrch.Tests +{ + /// + /// Integration tests for GopherAgent that verify the full flow through FFI. + /// These tests require the native library to be built and available. + /// + public class GopherAgentIntegrationTest + { + private static readonly string ServerConfig = "{" + + "\"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" + + " }" + + " ]" + + "}" + + "}"; + + /// + /// Check if native library is available for tests. + /// + private static bool IsNativeLibraryAvailable => GopherOrchLibrary.IsAvailable; + + /// + /// Helper method to create an agent, skipping test if creation fails. + /// Agent creation may fail without API key or network access. + /// + private GopherAgent? TryCreateAgent() + { + try + { + return GopherAgent.CreateWithServerConfig( + "AnthropicProvider", + "claude-3-haiku-20240307", + ServerConfig + ); + } + catch (AgentException) + { + // Agent creation failed - skip test + return null; + } + } + + [SkippableFact] + public void TestInitialization() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Test that initialization works + GopherAgent.Init(); + Assert.True(GopherAgent.IsInitialized); + } + + [SkippableFact] + public void TestCreateAgentWithServerConfig() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Test creating agent with server config through FFI + var config = GopherAgentConfig.CreateBuilder() + .WithProvider("AnthropicProvider") + .WithModel("claude-3-haiku-20240307") + .WithServerConfig(ServerConfig) + .Build(); + + GopherAgent? agent = null; + try + { + agent = GopherAgent.Create(config); + Assert.NotNull(agent); + Assert.False(agent.IsDisposed); + } + catch (AgentException) + { + // Agent creation may fail without API key - this is expected + Skip.If(true, "Agent creation failed - likely no API key available"); + } + finally + { + if (agent != null) + { + agent.Dispose(); + Assert.True(agent.IsDisposed); + } + } + } + + [SkippableFact] + public void TestCreateAgentWithHelperMethod() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Test the convenience method + var agent = TryCreateAgent(); + Skip.If(agent == null, "Agent creation failed - likely no API key available"); + + try + { + Assert.NotNull(agent); + } + finally + { + agent?.Dispose(); + } + } + + [SkippableFact] + public void TestUsingStatement() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Test IDisposable implementation + GopherAgent? capturedAgent = null; + + var agent = TryCreateAgent(); + Skip.If(agent == null, "Agent creation failed - likely no API key available"); + + using (agent) + { + Assert.NotNull(agent); + Assert.False(agent.IsDisposed); + capturedAgent = agent; + } + + // After using statement, agent should be disposed + Assert.NotNull(capturedAgent); + Assert.True(capturedAgent.IsDisposed); + } + + [SkippableFact] + public void TestDisposeIdempotent() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Test that dispose can be called multiple times safely + var agent = TryCreateAgent(); + Skip.If(agent == null, "Agent creation failed - likely no API key available"); + + agent.Dispose(); + Assert.True(agent.IsDisposed); + + // Second dispose should not throw + agent.Dispose(); + Assert.True(agent.IsDisposed); + } + + [SkippableFact] + public void TestRunAfterDisposeThrows() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + var agent = TryCreateAgent(); + Skip.If(agent == null, "Agent creation failed - likely no API key available"); + + agent.Dispose(); + + // Running on disposed agent should throw + Assert.Throws(() => agent.Run("test query")); + } + + [SkippableFact] + public void TestRunDetailedReturnsResult() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + var agent = TryCreateAgent(); + Skip.If(agent == null, "Agent creation failed - likely no API key available"); + + using (agent) + { + // Run with very short timeout to get quick response (likely timeout or error) + var result = agent.RunDetailed("test query", 100); + + Assert.NotNull(result); + Assert.NotNull(result.Response); + } + } + + [SkippableFact] + public void TestMultipleAgentsCanBeCreated() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + var agent1 = TryCreateAgent(); + Skip.If(agent1 == null, "Agent creation failed - likely no API key available"); + + var agent2 = TryCreateAgent(); + Skip.If(agent2 == null, "Agent creation failed - likely no API key available"); + + try + { + Assert.NotNull(agent1); + Assert.NotNull(agent2); + Assert.NotSame(agent1, agent2); + } + finally + { + agent1?.Dispose(); + agent2?.Dispose(); + } + } + + [Fact] + public void TestCreateWithoutNativeLibraryHandled() + { + // This test verifies error handling when native library is not available + // Skip if library IS available (we're testing the error case) + if (GopherOrchLibrary.IsAvailable) + { + // Library is available, so we can't test the "not available" case + // Just verify that create works (or gracefully fails without API key) + try + { + using var agent = GopherAgent.CreateWithServerConfig( + "AnthropicProvider", + "claude-3-haiku-20240307", + ServerConfig + ); + } + catch (AgentException) + { + // Expected if no API key is available + } + } + // If library is not available, Init() would throw AgentException + } + + [SkippableFact] + public void TestShutdownAndReinit() + { + Skip.IfNot(IsNativeLibraryAvailable, "Native library not available"); + + // Test that shutdown and re-init work correctly + GopherAgent.Init(); + Assert.True(GopherAgent.IsInitialized); + + GopherAgent.Shutdown(); + Assert.False(GopherAgent.IsInitialized); + + // Re-init should work + GopherAgent.Init(); + Assert.True(GopherAgent.IsInitialized); + } + } +} diff --git a/tests/GopherOrch.Tests/GopherOrch.Tests.csproj b/tests/GopherOrch.Tests/GopherOrch.Tests.csproj new file mode 100644 index 00000000..960abda1 --- /dev/null +++ b/tests/GopherOrch.Tests/GopherOrch.Tests.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + 10.0 + enable + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + 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