From b55dde37ef445816040325402e87aeaf0be4669a Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 09:28:35 +0800 Subject: [PATCH 1/9] Clean up C++ files and update submodule for PHP SDK Prepare repository for PHP SDK development: - Remove C++ source files (src/, include/, tests/, examples/) - Remove C++ build files (CMakeLists.txt, Makefile, cmake/) - Remove C++ build script and .clang-format - Update .gitignore for PHP development - Replace gopher-mcp submodule with gopher-orch - Pin gopher-orch to commit 6b45ffbb (MCP server connection fix) The gopher-orch submodule includes gopher-mcp as a nested submodule with 'update = none' in .gitmodules to prevent automatic updates. --- .clang-format | 53 --- .gitignore | 131 ++------ .gitmodules | 7 +- CMakeLists.txt | 253 --------------- Makefile | 374 ---------------------- build.sh | 123 ------- cmake/cmake_uninstall.cmake.in | 49 --- cmake/gopher-orch-config.cmake.in | 23 -- examples/CMakeLists.txt | 7 - examples/hello_world/CMakeLists.txt | 15 - examples/hello_world/main.cpp | 78 ----- examples/mcp_client/CMakeLists.txt | 32 -- examples/mcp_client/mcp_client_example.cc | 152 --------- include/orch/core/hello.h | 46 --- include/orch/core/version.h | 22 -- src/CMakeLists.txt | 103 ------ src/orch/hello.cpp | 59 ---- tests/CMakeLists.txt | 83 ----- tests/orch/hello_test.cpp | 110 ------- third_party/gopher-mcp | 1 - third_party/gopher-orch | 1 + 21 files changed, 37 insertions(+), 1685 deletions(-) delete mode 100644 .clang-format delete mode 100644 CMakeLists.txt delete mode 100644 Makefile delete mode 100755 build.sh delete mode 100644 cmake/cmake_uninstall.cmake.in delete mode 100644 cmake/gopher-orch-config.cmake.in delete mode 100644 examples/CMakeLists.txt delete mode 100644 examples/hello_world/CMakeLists.txt delete mode 100644 examples/hello_world/main.cpp delete mode 100644 examples/mcp_client/CMakeLists.txt delete mode 100644 examples/mcp_client/mcp_client_example.cc delete mode 100644 include/orch/core/hello.h delete mode 100644 include/orch/core/version.h delete mode 100644 src/CMakeLists.txt delete mode 100644 src/orch/hello.cpp delete mode 100644 tests/CMakeLists.txt delete mode 100644 tests/orch/hello_test.cpp delete mode 160000 third_party/gopher-mcp create mode 160000 third_party/gopher-orch 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/.gitignore b/.gitignore index 457c7687..e89555c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,109 +1,44 @@ -# 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 - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app -test_variant -test_variant_advanced -test_variant_extensive -test_optional -test_optional_advanced -test_optional_extensive -test_type_helpers -test_mcp_types -test_mcp_types_extended -test_mcp_type_helpers -test_compat -test_buffer -test_json -test_event_loop -test_io_socket_handle -test_address -test_socket -test_socket_interface -test_socket_option +# PHP specific +/vendor/ +composer.lock +*.phar + +# PHPUnit +.phpunit.result.cache +phpunit.xml +coverage/ -# IDE specific files -.vscode/ +# IDE - PhpStorm .idea/ -*.swp -*.swo -*~ -.DS_Store +*.iws +*.iml +*.ipr -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb +# IDE - VS Code +.vscode/ -# Dependency directories -node_modules/ -vendor/ +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db -# Coverage files -*.gcov -*.gcda -*.gcno -coverage/ -*.info +# Native library build +native/ +cmake-build-*/ -# Documentation -docs/html/ -docs/latex/ -doxygen/ +# Logs +*.log # Temporary files *.tmp *.temp -*.log - -# Python cache (if using Python scripts) -__pycache__/ -*.py[cod] -*$py.class +*.swp +*.swo +*~ -# OS generated files -Thumbs.db -Desktop.ini \ No newline at end of file +# Node.js (for example MCP servers) +node_modules/ diff --git a/.gitmodules b/.gitmodules index f7c1a54e..d5cd4211 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,3 @@ -[submodule "third_party/gopher-mcp"] - path = third_party/gopher-mcp - url = https://github.com/GopherSecurity/gopher-mcp.git - branch = main +[submodule "third_party/gopher-orch"] + path = third_party/gopher-orch + url = https://github.com/GopherSecurity/gopher-orch.git diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 80c90036..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,253 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(gopher-orch VERSION 0.1.0 LANGUAGES C CXX) - -# Prevent in-source builds -if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) - message(FATAL_ERROR "In-source builds are not allowed. Please create a build directory and run cmake from there.") -endif() - -# Set C++ standard -set(CMAKE_CXX_STANDARD 14) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -message(STATUS "Using C++14") - -# Default to Debug build -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE) -endif() - -# Build options -option(BUILD_SHARED_LIBS "Build shared libraries" ON) -option(BUILD_STATIC_LIBS "Build static libraries" ON) -option(BUILD_TESTS "Build tests" ON) -option(BUILD_EXAMPLES "Build examples" ON) -option(ORCH_STRICT_WARNINGS "Enable strict compiler warnings" OFF) -option(USE_SUBMODULE_GOPHER_MCP "Use gopher-mcp as submodule (vs find_package)" ON) -option(BUILD_WITHOUT_GOPHER_MCP "Build without gopher-mcp dependency (for testing)" OFF) - -# Set output directories -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - -# Compiler flags -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_compile_options(-g -O0) - add_compile_definitions(_DEBUG) -elseif(CMAKE_BUILD_TYPE STREQUAL "Release") - add_compile_options(-O3) - add_compile_definitions(NDEBUG) -endif() - -# Platform-specific settings -if(APPLE) - set(CMAKE_MACOSX_RPATH ON) - set(CMAKE_INSTALL_RPATH "@loader_path/../lib") -elseif(UNIX) - set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") -endif() - -# Warning flags -if(ORCH_STRICT_WARNINGS) - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - add_compile_options( - -Wall -Wextra -Wpedantic - -Wno-unused-parameter - -Wno-unused-variable - -Wno-unused-function - -Werror - ) - elseif(MSVC) - add_compile_options(/W4 /WX) - endif() -else() - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - add_compile_options( - -Wall - -Wno-unused-parameter - -Wno-unused-variable - -Wno-unused-function - ) - endif() -endif() - -# Handle gopher-mcp dependency -if(BUILD_WITHOUT_GOPHER_MCP) - # Build without gopher-mcp for testing - message(STATUS "Building without gopher-mcp dependency") - set(GOPHER_MCP_LIBRARIES "") - set(GOPHER_MCP_INCLUDE_DIR "") -elseif(USE_SUBMODULE_GOPHER_MCP) - # Use gopher-mcp as submodule - if(NOT EXISTS "${CMAKE_SOURCE_DIR}/third_party/gopher-mcp/.git") - message(STATUS "gopher-mcp submodule not found. Initializing...") - execute_process( - COMMAND git submodule add https://github.com/GopherSecurity/gopher-mcp.git third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_RESULT - ) - if(NOT GIT_SUBMOD_RESULT EQUAL "0") - # Submodule might already exist, try update - execute_process( - COMMAND git submodule update --init --recursive third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE GIT_SUBMOD_UPDATE_RESULT - ) - if(NOT GIT_SUBMOD_UPDATE_RESULT EQUAL "0") - message(FATAL_ERROR "Failed to initialize gopher-mcp submodule") - endif() - endif() - else() - message(STATUS "Updating gopher-mcp submodule...") - execute_process( - COMMAND git submodule update --init --recursive third_party/gopher-mcp - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - ) - endif() - - # Set include directories for gopher-mcp BEFORE adding subdirectory - set(GOPHER_MCP_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/third_party/gopher-mcp/include) - - # Temporarily add gopher-mcp include directories before processing subdirectory - # This ensures gopher-mcp can find its own headers when building as submodule - include_directories(${GOPHER_MCP_INCLUDE_DIR}) - - # Disable gopher-mcp tests and examples to speed up build - set(BUILD_TESTS_SAVED ${BUILD_TESTS}) - set(BUILD_EXAMPLES_SAVED ${BUILD_EXAMPLES}) - set(BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(BUILD_BINDINGS_EXAMPLES OFF CACHE BOOL "" FORCE) - - # Disable fmt installation if gopher-mcp uses it - set(FMT_INSTALL OFF CACHE BOOL "Disable fmt installation" FORCE) - - # Add gopher-mcp subdirectory - add_subdirectory(third_party/gopher-mcp EXCLUDE_FROM_ALL) - - # Restore our settings - set(BUILD_TESTS ${BUILD_TESTS_SAVED} CACHE BOOL "" FORCE) - set(BUILD_EXAMPLES ${BUILD_EXAMPLES_SAVED} CACHE BOOL "" FORCE) - - # Make gopher-mcp libraries available - # Use static libraries for tests to avoid duplicate initialization - if(BUILD_TESTS AND TARGET gopher-mcp-static) - set(GOPHER_MCP_LIBRARIES gopher-mcp-static gopher-mcp-event-static) - else() - set(GOPHER_MCP_LIBRARIES gopher-mcp gopher-mcp-event) - endif() - - message(STATUS "Using gopher-mcp from submodule") -else() - # Use system-installed gopher-mcp - find_package(gopher-mcp REQUIRED) - message(STATUS "Using system gopher-mcp: ${gopher-mcp_DIR}") -endif() - -# Include directories -message(STATUS "GOPHER_MCP_INCLUDE_DIR: ${GOPHER_MCP_INCLUDE_DIR}") -include_directories( - ${CMAKE_SOURCE_DIR}/include - ${GOPHER_MCP_INCLUDE_DIR} -) - -# Export compile commands for tools like clangd -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# Find required packages -find_package(Threads REQUIRED) - -# Testing setup -if(BUILD_TESTS) - enable_testing() - include(CTest) - - # Fetch Google Test - include(FetchContent) - - # Prevent Google Test from being installed - set(INSTALL_GTEST OFF CACHE BOOL "Disable installation of googletest" FORCE) - set(INSTALL_GMOCK OFF CACHE BOOL "Disable installation of googlemock" FORCE) - set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) - - FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.14.0 - CMAKE_ARGS -DINSTALL_GTEST=OFF -DINSTALL_GMOCK=OFF - ) - - FetchContent_MakeAvailable(googletest) - - # Include Google Test and Google Mock - include(GoogleTest) -endif() - -# Add subdirectories -add_subdirectory(src) - -if(BUILD_TESTS) - add_subdirectory(tests) -endif() - -if(BUILD_EXAMPLES) - add_subdirectory(examples) -endif() - -# Installation rules -install(DIRECTORY include/orch - DESTINATION include - COMPONENT development - FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" -) - -# Package configuration -include(CMakePackageConfigHelpers) - -configure_package_config_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/gopher-orch-config.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config.cmake" - INSTALL_DESTINATION lib/cmake/gopher-orch -) - -write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion -) - -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/gopher-orch-config-version.cmake" - DESTINATION lib/cmake/gopher-orch - COMPONENT development -) - -# Add uninstall target -if(NOT TARGET uninstall) - configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" - "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" - IMMEDIATE @ONLY - ) - - add_custom_target(uninstall - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake - ) -endif() - -# Print configuration summary -message(STATUS "") -message(STATUS "=== gopher-orch Configuration Summary ===") -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") -message(STATUS "C++ Standard: ${CMAKE_CXX_STANDARD}") -message(STATUS "Build shared libs: ${BUILD_SHARED_LIBS}") -message(STATUS "Build static libs: ${BUILD_STATIC_LIBS}") -message(STATUS "Build tests: ${BUILD_TESTS}") -message(STATUS "Build examples: ${BUILD_EXAMPLES}") -message(STATUS "Strict warnings: ${ORCH_STRICT_WARNINGS}") -message(STATUS "Use submodule gopher-mcp: ${USE_SUBMODULE_GOPHER_MCP}") -message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") -message(STATUS "==========================================") -message(STATUS "") diff --git a/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/build.sh b/build.sh deleted file mode 100755 index 3aa8a1ca..00000000 --- a/build.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/bin/bash -x - -# Build script for gopher-orch with submodule support - -set -e - -# 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}" - fi -fi - -# 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}" -fi - -cmake -B "$BUILD_DIR" -S . "${CMAKE_ARGS[@]}" - -# 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 "${GREEN}Build completed successfully!${NC}" - -# Run tests if built -if [ "$BUILD_TESTS" = "ON" ]; then - echo -e "${BLUE}Running tests...${NC}" - (cd "$BUILD_DIR" && ctest --output-on-failure) || { - echo -e "${RED}Some tests failed${NC}" - exit 1 - } - echo -e "${GREEN}All tests passed!${NC}" -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" -fi - -echo -e "${GREEN}=== Build Complete ===${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/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/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/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/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 From 99807a947b705d1d1d136f8194c6395688af49f9 Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 09:29:37 +0800 Subject: [PATCH 2/9] Add build.sh for native library compilation Build script that handles the complete build process: - Updates git submodules with SSH URL rewriting support - Handles 'update = none' in gopher-orch/.gitmodules - Builds gopher-orch native library with CMake - Copies dependency libraries (gopher-mcp, fmt) - Fixes macOS dylib install names for PHP FFI - Checks PHP environment and FFI extension - Runs PHPUnit tests if available --- build.sh | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100755 build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..641b375a --- /dev/null +++ b/build.sh @@ -0,0 +1,243 @@ +#!/bin/bash + +set -e # Exit on error + +# 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)" +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 PHP 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 +if [ ! -d "${BUILD_DIR}" ]; then + mkdir -p "${BUILD_DIR}" +fi + +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 "${YELLOW} Compiling...${NC}" +cmake --build . --config Release -j$(sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 4) + +# Install to native directory +echo -e "${YELLOW} Installing...${NC}" +cmake --install . + +# Copy dependency libraries (gopher-mcp, fmt) that gopher-orch depends on +echo -e "${YELLOW} Copying dependency libraries...${NC}" +NATIVE_LIB_DIR="${SCRIPT_DIR}/native/lib" +mkdir -p "${NATIVE_LIB_DIR}" + +# Copy gopher-mcp libraries +cp -P "${BUILD_DIR}/lib/libgopher-mcp"*.dylib "${NATIVE_LIB_DIR}/" 2>/dev/null || \ +cp -P "${BUILD_DIR}/lib/libgopher-mcp"*.so "${NATIVE_LIB_DIR}/" 2>/dev/null || true + +# Copy fmt library +cp -P "${BUILD_DIR}/lib/libfmt"*.dylib "${NATIVE_LIB_DIR}/" 2>/dev/null || \ +cp -P "${BUILD_DIR}/lib/libfmt"*.so "${NATIVE_LIB_DIR}/" 2>/dev/null || true + +# Fix dylib install names on macOS (required for PHP FFI to find dependencies) +if [[ "$OSTYPE" == "darwin"* ]]; then + echo -e "${YELLOW} Fixing dylib install names for macOS...${NC}" + cd "${NATIVE_LIB_DIR}" + + # Fix libgopher-orch to use @rpath for dependencies + if [ -f "libgopher-orch.dylib" ]; then + install_name_tool -id "@rpath/libgopher-orch.dylib" libgopher-orch.dylib 2>/dev/null || true + install_name_tool -change "@rpath/libgopher-mcp.dylib" "@loader_path/libgopher-mcp.dylib" libgopher-orch.dylib 2>/dev/null || true + install_name_tool -change "@rpath/libgopher-mcp-event.dylib" "@loader_path/libgopher-mcp-event.dylib" libgopher-orch.dylib 2>/dev/null || true + install_name_tool -change "@rpath/libfmt.11.dylib" "@loader_path/libfmt.11.dylib" libgopher-orch.dylib 2>/dev/null || true + install_name_tool -change "libfmt.so.11" "@loader_path/libfmt.11.dylib" libgopher-orch.dylib 2>/dev/null || true + fi + + # Fix libgopher-mcp + if [ -f "libgopher-mcp.dylib" ]; then + install_name_tool -id "@rpath/libgopher-mcp.dylib" libgopher-mcp.dylib 2>/dev/null || true + install_name_tool -change "@rpath/libgopher-mcp-event.dylib" "@loader_path/libgopher-mcp-event.dylib" libgopher-mcp.dylib 2>/dev/null || true + install_name_tool -change "@rpath/libfmt.11.dylib" "@loader_path/libfmt.11.dylib" libgopher-mcp.dylib 2>/dev/null || true + install_name_tool -change "libfmt.so.11" "@loader_path/libfmt.11.dylib" libgopher-mcp.dylib 2>/dev/null || true + fi + + # Fix libgopher-mcp-event + if [ -f "libgopher-mcp-event.dylib" ]; then + install_name_tool -id "@rpath/libgopher-mcp-event.dylib" libgopher-mcp-event.dylib 2>/dev/null || true + install_name_tool -change "@rpath/libfmt.11.dylib" "@loader_path/libfmt.11.dylib" libgopher-mcp-event.dylib 2>/dev/null || true + install_name_tool -change "libfmt.so.11" "@loader_path/libfmt.11.dylib" libgopher-mcp-event.dylib 2>/dev/null || true + fi + + # Fix libfmt + if [ -f "libfmt.11.dylib" ]; then + install_name_tool -id "@rpath/libfmt.11.dylib" libfmt.11.dylib 2>/dev/null || true + fi + + cd "${SCRIPT_DIR}" +fi + +echo -e "${GREEN}βœ“ Native library built successfully${NC}" +echo "" + +# Step 4: Verify build artifacts +echo -e "${YELLOW}Step 3: Verifying native build artifacts...${NC}" + +NATIVE_LIB_DIR="${SCRIPT_DIR}/native/lib" +NATIVE_INCLUDE_DIR="${SCRIPT_DIR}/native/include" + +if [ -d "${NATIVE_LIB_DIR}" ]; then + echo -e "${GREEN}βœ“ Libraries installed to: ${NATIVE_LIB_DIR}${NC}" + ls -lh "${NATIVE_LIB_DIR}"/*.dylib 2>/dev/null || ls -lh "${NATIVE_LIB_DIR}"/*.so 2>/dev/null || true +else + echo -e "${YELLOW}⚠ Library directory not found: ${NATIVE_LIB_DIR}${NC}" +fi + +if [ -d "${NATIVE_INCLUDE_DIR}" ]; then + echo -e "${GREEN}βœ“ Headers installed to: ${NATIVE_INCLUDE_DIR}${NC}" +else + echo -e "${YELLOW}⚠ Include directory not found: ${NATIVE_INCLUDE_DIR}${NC}" +fi + +echo "" + +# Step 5: Check PHP and Composer +echo -e "${YELLOW}Step 4: Checking PHP environment...${NC}" +cd "${SCRIPT_DIR}" + +# Check for PHP +if ! command -v php &> /dev/null; then + echo -e "${RED}Error: PHP not found. Please install PHP first.${NC}" + echo -e "${YELLOW} macOS: brew install php${NC}" + echo -e "${YELLOW} Linux: sudo apt-get install php php-ffi${NC}" + exit 1 +fi + +# Check PHP version +PHP_VERSION=$(php -v | head -n 1 | cut -d ' ' -f 2 | cut -d '.' -f 1,2) +echo -e "${GREEN}βœ“ PHP version: ${PHP_VERSION}${NC}" + +# Check for FFI extension +if ! php -m | grep -q "^FFI$"; then + echo -e "${YELLOW}⚠ PHP FFI extension not enabled. Please enable it in php.ini:${NC}" + echo -e "${YELLOW} extension=ffi${NC}" + echo -e "${YELLOW} ffi.enable=true${NC}" +fi + +# Check for Composer +if ! command -v composer &> /dev/null; then + echo -e "${YELLOW}⚠ Composer not found. Install with:${NC}" + echo -e "${YELLOW} macOS: brew install composer${NC}" + echo -e "${YELLOW} Linux: sudo apt-get install composer${NC}" +else + echo -e "${GREEN}βœ“ Composer found${NC}" + + # Install dependencies if composer.json exists + if [ -f "composer.json" ]; then + echo -e "${YELLOW} Installing dependencies...${NC}" + composer install --quiet 2>/dev/null || true + echo -e "${GREEN}βœ“ Dependencies installed${NC}" + fi +fi + +echo "" + +# Step 6: Run tests if PHPUnit is available +echo -e "${YELLOW}Step 5: Running tests...${NC}" +if [ -f "vendor/bin/phpunit" ]; then + ./vendor/bin/phpunit --testdox 2>/dev/null && echo -e "${GREEN}βœ“ Tests passed${NC}" || echo -e "${YELLOW}⚠ Some tests may have failed (native library required)${NC}" +elif command -v phpunit &> /dev/null; then + phpunit --testdox 2>/dev/null && echo -e "${GREEN}βœ“ Tests passed${NC}" || echo -e "${YELLOW}⚠ Some tests may have failed (native library required)${NC}" +else + echo -e "${YELLOW}⚠ PHPUnit not found, skipping tests${NC}" +fi + +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}./vendor/bin/phpunit${NC}" +echo -e "Run example: ${YELLOW}php examples/client_example_json.php${NC}" From d9396447c285f74921409fcdb0c10ea2b18b5963 Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 09:32:55 +0800 Subject: [PATCH 3/9] Add PHP SDK with Composer build and FFI bindings PHP SDK for gopher-orch with native FFI bindings: Core classes: - GopherOrch: FFI loader and native library bindings - GopherAgent: Main agent class for running AI queries - Config/ConfigBuilder: Configuration management - AgentResult/AgentResultStatus: Query result handling Features: - PHP 7.4+ compatibility - PHP FFI extension for native library binding - Builder pattern for configuration - Automatic resource cleanup via destructor - PSR-4 autoloading Requirements: - PHP >= 7.4 - ext-ffi (PHP FFI extension) --- composer.json | 38 +++++ phpunit.xml.dist | 25 +++ src/AgentResult.php | 112 +++++++++++++ src/AgentResultStatus.php | 58 +++++++ src/Config.php | 82 ++++++++++ src/ConfigBuilder.php | 89 ++++++++++ src/Exception/AgentException.php | 12 ++ src/Exception/ConfigException.php | 12 ++ src/Exception/DisposedException.php | 16 ++ src/Exception/GopherException.php | 14 ++ src/Exception/LibraryException.php | 12 ++ src/Exception/TimeoutException.php | 12 ++ src/GopherAgent.php | 224 +++++++++++++++++++++++++ src/GopherOrch.php | 245 ++++++++++++++++++++++++++++ 14 files changed, 951 insertions(+) create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/AgentResult.php create mode 100644 src/AgentResultStatus.php create mode 100644 src/Config.php create mode 100644 src/ConfigBuilder.php create mode 100644 src/Exception/AgentException.php create mode 100644 src/Exception/ConfigException.php create mode 100644 src/Exception/DisposedException.php create mode 100644 src/Exception/GopherException.php create mode 100644 src/Exception/LibraryException.php create mode 100644 src/Exception/TimeoutException.php create mode 100644 src/GopherAgent.php create mode 100644 src/GopherOrch.php diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..9da279d9 --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "gophersecurity/gopher-orch", + "description": "PHP SDK for Gopher Orch - AI Agent orchestration framework", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "GopherSecurity", + "email": "dev@gophersecurity.com" + } + ], + "keywords": ["ai", "agent", "mcp", "llm", "orchestration", "ffi"], + "homepage": "https://github.com/GopherSecurity/gopher-mcp-php", + "require": { + "php": ">=7.4", + "ext-ffi": "*" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "autoload": { + "psr-4": { + "GopherSecurity\\Orch\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GopherSecurity\\Orch\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "phpunit --testdox", + "test-coverage": "phpunit --coverage-html coverage" + }, + "config": { + "sort-packages": true + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..a22017fe --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + tests + + + + + + src + + + diff --git a/src/AgentResult.php b/src/AgentResult.php new file mode 100644 index 00000000..8e2cc6af --- /dev/null +++ b/src/AgentResult.php @@ -0,0 +1,112 @@ +response = $response; + $this->status = $status; + $this->errorMessage = $errorMessage; + $this->iterationCount = $iterationCount; + $this->tokensUsed = $tokensUsed; + } + + /** + * Create a successful result. + * + * @param string $response The response content + * @return self + */ + public static function success(string $response): self + { + return new self($response, AgentResultStatus::success(), null, 1, 0); + } + + /** + * Create an error result. + * + * @param string $message The error message + * @return self + */ + public static function error(string $message): self + { + return new self('', AgentResultStatus::error(), $message, 0, 0); + } + + /** + * Create a timeout result. + * + * @param string $message The timeout message + * @return self + */ + public static function timeout(string $message): self + { + return new self('', AgentResultStatus::timeout(), $message, 0, 0); + } + + /** + * Get the agent's response. + */ + public function getResponse(): string + { + return $this->response; + } + + /** + * Get the result status. + */ + public function getStatus(): AgentResultStatus + { + return $this->status; + } + + /** + * Get the error message (if any). + */ + public function getErrorMessage(): ?string + { + return $this->errorMessage; + } + + /** + * Get the iteration count. + */ + public function getIterationCount(): int + { + return $this->iterationCount; + } + + /** + * Get the number of tokens used. + */ + public function getTokensUsed(): int + { + return $this->tokensUsed; + } + + /** + * Check if the result was successful. + */ + public function isSuccess(): bool + { + return $this->status->isSuccess(); + } +} diff --git a/src/AgentResultStatus.php b/src/AgentResultStatus.php new file mode 100644 index 00000000..b20dceab --- /dev/null +++ b/src/AgentResultStatus.php @@ -0,0 +1,58 @@ +value = $value; + } + + public static function success(): self + { + return new self(self::SUCCESS); + } + + public static function error(): self + { + return new self(self::ERROR); + } + + public static function timeout(): self + { + return new self(self::TIMEOUT); + } + + public static function maxIterationsReached(): self + { + return new self(self::MAX_ITERATIONS_REACHED); + } + + public function getValue(): string + { + return $this->value; + } + + public function isSuccess(): bool + { + return $this->value === self::SUCCESS; + } + + public function __toString(): string + { + return $this->value; + } +} diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 00000000..6d26a035 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,82 @@ +provider = $provider; + $this->model = $model; + $this->apiKey = $apiKey; + $this->serverConfig = $serverConfig; + } + + /** + * Get the LLM provider name. + */ + public function getProvider(): string + { + return $this->provider; + } + + /** + * Get the model name. + */ + public function getModel(): string + { + return $this->model; + } + + /** + * Get the API key. + */ + public function getApiKey(): ?string + { + return $this->apiKey; + } + + /** + * Get the server configuration. + */ + public function getServerConfig(): ?string + { + return $this->serverConfig; + } + + /** + * Check if an API key is set. + */ + public function hasApiKey(): bool + { + return $this->apiKey !== null; + } + + /** + * Check if a server configuration is set. + */ + public function hasServerConfig(): bool + { + return $this->serverConfig !== null; + } +} diff --git a/src/ConfigBuilder.php b/src/ConfigBuilder.php new file mode 100644 index 00000000..3f60e838 --- /dev/null +++ b/src/ConfigBuilder.php @@ -0,0 +1,89 @@ +provider = $provider; + return $this; + } + + /** + * Set the model name. + * + * @param string $model The model name (e.g., "claude-3-haiku-20240307") + * @return $this + */ + public function withModel(string $model): self + { + $this->model = $model; + return $this; + } + + /** + * Set the API key for fetching remote server config. + * Mutually exclusive with serverConfig. + * + * @param string $apiKey The API key + * @return $this + */ + public function withApiKey(string $apiKey): self + { + $this->apiKey = $apiKey; + return $this; + } + + /** + * Set the JSON server configuration. + * Mutually exclusive with apiKey. + * + * @param string $serverConfig The JSON server configuration + * @return $this + */ + public function withServerConfig(string $serverConfig): self + { + $this->serverConfig = $serverConfig; + return $this; + } + + /** + * Build the Config instance. + * + * @return Config + */ + public function build(): Config + { + return new Config( + $this->provider, + $this->model, + $this->apiKey, + $this->serverConfig + ); + } +} diff --git a/src/Exception/AgentException.php b/src/Exception/AgentException.php new file mode 100644 index 00000000..eabf68f9 --- /dev/null +++ b/src/Exception/AgentException.php @@ -0,0 +1,12 @@ +handle = $handle; + } + + /** + * Create a new GopherAgent with the given configuration. + * + * @param Config $config The agent configuration + * @return self + * @throws ConfigException if configuration is invalid + * @throws AgentException if agent creation fails + */ + public static function create(Config $config): self + { + GopherOrch::init(); + + if ($config->hasApiKey()) { + $handle = GopherOrch::agentCreateByApiKey( + $config->getProvider(), + $config->getModel(), + $config->getApiKey() ?? '' + ); + } elseif ($config->hasServerConfig()) { + $handle = GopherOrch::agentCreateByJson( + $config->getProvider(), + $config->getModel(), + $config->getServerConfig() ?? '' + ); + } else { + throw new ConfigException('Either API key or server config must be provided'); + } + + if ($handle === null || \FFI::isNull($handle)) { + $errorMsg = GopherOrch::getLastError(); + GopherOrch::clearError(); + $message = $errorMsg !== '' ? $errorMsg : 'Failed to create agent'; + throw new AgentException($message); + } + + return new self($handle); + } + + /** + * Create a new GopherAgent with an API key. + * + * @param string $provider The LLM provider name + * @param string $model The model name + * @param string $apiKey The API key + * @return self + * @throws AgentException if agent creation fails + */ + public static function createWithApiKey(string $provider, string $model, string $apiKey): self + { + $config = ConfigBuilder::create() + ->withProvider($provider) + ->withModel($model) + ->withApiKey($apiKey) + ->build(); + + return self::create($config); + } + + /** + * Create a new GopherAgent with a server config. + * + * @param string $provider The LLM provider name + * @param string $model The model name + * @param string $serverConfig The JSON server configuration + * @return self + * @throws AgentException if agent creation fails + */ + public static function createWithServerConfig(string $provider, string $model, string $serverConfig): self + { + $config = ConfigBuilder::create() + ->withProvider($provider) + ->withModel($model) + ->withServerConfig($serverConfig) + ->build(); + + return self::create($config); + } + + /** + * Run a query against the agent with the default timeout (60 seconds). + * + * @param string $query The query string + * @return string The response + * @throws DisposedException if the agent has been disposed + */ + public function run(string $query): string + { + return $this->runWithTimeout($query, self::DEFAULT_TIMEOUT_MS); + } + + /** + * Run a query against the agent with a custom timeout. + * + * @param string $query The query string + * @param int $timeoutMs Timeout in milliseconds + * @return string The response + * @throws DisposedException if the agent has been disposed + */ + public function runWithTimeout(string $query, int $timeoutMs): string + { + $this->ensureNotDisposed(); + + $response = GopherOrch::agentRun($this->handle, $query, $timeoutMs); + + if ($response === '') { + return sprintf('No response for query: "%s"', $query); + } + + return $response; + } + + /** + * Run a query and return detailed result information. + * + * @param string $query The query string + * @return AgentResult + */ + public function runDetailed(string $query): AgentResult + { + return $this->runDetailedWithTimeout($query, self::DEFAULT_TIMEOUT_MS); + } + + /** + * Run a query with custom timeout and return detailed result. + * + * @param string $query The query string + * @param int $timeoutMs Timeout in milliseconds + * @return AgentResult + */ + public function runDetailedWithTimeout(string $query, int $timeoutMs): AgentResult + { + try { + $response = $this->runWithTimeout($query, $timeoutMs); + return AgentResult::success($response); + } catch (DisposedException $e) { + return AgentResult::error($e->getMessage()); + } catch (\Exception $e) { + // Check if it's a timeout based on error message + if (stripos($e->getMessage(), 'timeout') !== false) { + return AgentResult::timeout($e->getMessage()); + } + return AgentResult::error($e->getMessage()); + } + } + + /** + * Check if the agent has been disposed. + */ + public function isDisposed(): bool + { + return $this->disposed; + } + + /** + * Ensure the agent has not been disposed. + * + * @throws DisposedException + */ + private function ensureNotDisposed(): void + { + if ($this->disposed) { + throw new DisposedException(); + } + } + + /** + * Dispose of the agent, releasing native resources. + */ + public function dispose(): void + { + if ($this->disposed) { + return; + } + + $this->disposed = true; + + if ($this->handle !== null) { + GopherOrch::agentRelease($this->handle); + $this->handle = null; + } + } + + /** + * Destructor - automatically dispose of the agent. + */ + public function __destruct() + { + $this->dispose(); + } +} diff --git a/src/GopherOrch.php b/src/GopherOrch.php new file mode 100644 index 00000000..980e9663 --- /dev/null +++ b/src/GopherOrch.php @@ -0,0 +1,245 @@ +getMessage(), + 0, + $e + ); + } + } + + /** + * Check if the library is initialized. + */ + public static function isInitialized(): bool + { + return self::$initialized; + } + + /** + * Check if the native library is available. + */ + public static function isAvailable(): bool + { + try { + self::init(); + return true; + } catch (LibraryException $e) { + return false; + } + } + + /** + * Find the native library path. + */ + private static function findLibrary(): ?string + { + $extension = PHP_OS_FAMILY === 'Darwin' ? 'dylib' : (PHP_OS_FAMILY === 'Windows' ? 'dll' : 'so'); + $libName = PHP_OS_FAMILY === 'Windows' ? 'gopher-orch' : 'libgopher-orch'; + + $candidates = [ + // Relative to current working directory + getcwd() . '/native/lib/' . $libName . '.' . $extension, + // Relative to this file + dirname(__DIR__) . '/native/lib/' . $libName . '.' . $extension, + // System paths + '/usr/local/lib/' . $libName . '.' . $extension, + '/opt/homebrew/lib/' . $libName . '.' . $extension, + ]; + + // Check environment variable + $envPath = getenv('GOPHER_ORCH_LIBRARY_PATH'); + if ($envPath !== false) { + array_unshift($candidates, $envPath); + } + + foreach ($candidates as $path) { + if (file_exists($path)) { + return $path; + } + } + + return null; + } + + /** + * Get the FFI instance. + * + * @throws LibraryException if not initialized + */ + public static function getFFI(): FFI + { + if (!self::$initialized || self::$ffi === null) { + self::init(); + } + + return self::$ffi; + } + + /** + * Create an agent with JSON server configuration. + * + * @param string $provider The LLM provider name + * @param string $model The model name + * @param string $serverJson The JSON server configuration + * @return FFI\CData|null The agent handle or null on failure + */ + public static function agentCreateByJson(string $provider, string $model, string $serverJson) + { + $ffi = self::getFFI(); + return $ffi->gopher_orch_agent_create_by_json($provider, $model, $serverJson); + } + + /** + * Create an agent with API key. + * + * @param string $provider The LLM provider name + * @param string $model The model name + * @param string $apiKey The API key + * @return FFI\CData|null The agent handle or null on failure + */ + public static function agentCreateByApiKey(string $provider, string $model, string $apiKey) + { + $ffi = self::getFFI(); + return $ffi->gopher_orch_agent_create_by_api_key($provider, $model, $apiKey); + } + + /** + * Run a query against the agent. + * + * @param FFI\CData $agent The agent handle + * @param string $query The query string + * @param int $timeoutMs Timeout in milliseconds + * @return string The response string + */ + public static function agentRun($agent, string $query, int $timeoutMs): string + { + $ffi = self::getFFI(); + $result = $ffi->gopher_orch_agent_run($agent, $query, $timeoutMs); + + if ($result === null || FFI::isNull($result)) { + return ''; + } + + $response = FFI::string($result); + $ffi->gopher_orch_free($result); + + return $response; + } + + /** + * Release an agent handle. + * + * @param FFI\CData $agent The agent handle + */ + public static function agentRelease($agent): void + { + if ($agent === null || FFI::isNull($agent)) { + return; + } + + $ffi = self::getFFI(); + $ffi->gopher_orch_agent_release($agent); + } + + /** + * Get the last error message. + * + * @return string The error message or empty string + */ + public static function getLastError(): string + { + $ffi = self::getFFI(); + $errorInfo = $ffi->gopher_orch_last_error(); + + if ($errorInfo === null || FFI::isNull($errorInfo)) { + return ''; + } + + if ($errorInfo->message === null || FFI::isNull($errorInfo->message)) { + return ''; + } + + return FFI::string($errorInfo->message); + } + + /** + * Clear the last error. + */ + public static function clearError(): void + { + $ffi = self::getFFI(); + $ffi->gopher_orch_clear_error(); + } + + /** + * Shutdown the library. + */ + public static function shutdown(): void + { + self::$initialized = false; + self::$ffi = null; + } +} From 4767aece235c6754827fb1f9a2678398ae2c9637 Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 09:35:05 +0800 Subject: [PATCH 4/9] Add PHP SDK examples with MCP server integration Example code demonstrating PHP SDK usage: client_example_json.php: - Creates GopherAgent with JSON server configuration - Queries the agent and displays the response - Shows proper error handling and resource cleanup client_example_json_run.sh: - Convenience script to run the example - Starts local MCP servers (server3001, server3002) - Automatically cleans up on exit Mock MCP servers (TypeScript): - server3001: Weather service (get-weather, get-forecast, get-alerts) - server3002: Utility tools (get-time, generate-password) - Both servers use HTTP/SSE transport on localhost --- examples/client_example_json.php | 76 + examples/client_example_json_run.sh | 85 + examples/server3001/README.md | 21 + examples/server3001/package-lock.json | 1644 +++++++++++++++++ examples/server3001/package.json | 35 + examples/server3001/src/index.ts | 143 ++ examples/server3001/src/tools/get-alerts.ts | 57 + examples/server3001/src/tools/get-forecast.ts | 58 + examples/server3001/src/tools/get-weather.ts | 45 + examples/server3001/start-mcp-server.sh | 24 + examples/server3001/tsconfig.json | 21 + examples/server3002/README.md | 20 + examples/server3002/package-lock.json | 1644 +++++++++++++++++ examples/server3002/package.json | 35 + examples/server3002/src/index.ts | 138 ++ .../server3002/src/tools/generate-password.ts | 196 ++ examples/server3002/src/tools/get-time.ts | 130 ++ examples/server3002/start-mcp-server.sh | 24 + examples/server3002/tsconfig.json | 21 + 19 files changed, 4417 insertions(+) create mode 100644 examples/client_example_json.php create mode 100755 examples/client_example_json_run.sh create mode 100644 examples/server3001/README.md create mode 100644 examples/server3001/package-lock.json create mode 100644 examples/server3001/package.json create mode 100644 examples/server3001/src/index.ts create mode 100644 examples/server3001/src/tools/get-alerts.ts create mode 100644 examples/server3001/src/tools/get-forecast.ts create mode 100644 examples/server3001/src/tools/get-weather.ts create mode 100755 examples/server3001/start-mcp-server.sh create mode 100644 examples/server3001/tsconfig.json create mode 100644 examples/server3002/README.md create mode 100644 examples/server3002/package-lock.json create mode 100644 examples/server3002/package.json create mode 100644 examples/server3002/src/index.ts create mode 100644 examples/server3002/src/tools/generate-password.ts create mode 100644 examples/server3002/src/tools/get-time.ts create mode 100755 examples/server3002/start-mcp-server.sh create mode 100644 examples/server3002/tsconfig.json diff --git a/examples/client_example_json.php b/examples/client_example_json.php new file mode 100644 index 00000000..eb2265f7 --- /dev/null +++ b/examples/client_example_json.php @@ -0,0 +1,76 @@ +withProvider($provider) + ->withModel($model) + ->withServerConfig(SERVER_CONFIG) + ->build(); + + $agent = GopherAgent::create($config); + echo "GopherAgent created!\n"; + + // Get question from command line args or use default + $question = $argc > 1 + ? implode(' ', array_slice($argv, 1)) + : 'What is the weather like in New York?'; + echo "Question: $question\n"; + + // Run the query + $answer = $agent->run($question); + echo "Answer:\n"; + echo "$answer\n"; + + // Cleanup (optional - happens automatically) + $agent->dispose(); + +} catch (Exception $e) { + fwrite(STDERR, "Error: " . $e->getMessage() . "\n"); + exit(1); +} diff --git a/examples/client_example_json_run.sh b/examples/client_example_json_run.sh new file mode 100755 index 00000000..26965fa6 --- /dev/null +++ b/examples/client_example_json_run.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +# Run the PHP 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}" + if [ -n "$SERVER3001_PID" ]; then + kill $SERVER3001_PID 2>/dev/null || true + fi + if [ -n "$SERVER3002_PID" ]; then + kill $SERVER3002_PID 2>/dev/null || true + fi + echo -e "${GREEN}Done${NC}" +} + +trap cleanup EXIT + +echo -e "${GREEN}======================================${NC}" +echo -e "${GREEN}Running PHP 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 + +# Check if vendor directory exists +if [ ! -d "$PROJECT_DIR/vendor" ]; then + echo -e "${YELLOW}Installing Composer dependencies...${NC}" + cd "$PROJECT_DIR" + composer install +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 + +# Run the PHP client +echo "" +echo -e "${YELLOW}Running PHP client...${NC}" +echo "" +cd "$PROJECT_DIR" + +php examples/client_example_json.php "$@" + +echo "" +echo -e "${GREEN}Example completed${NC}" 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"] +} From 8dbd96fa409dfe8bc1190a1a27daefdbcadc6579 Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 09:37:14 +0800 Subject: [PATCH 5/9] Add FFI verification tests for PHP SDK PHPUnit tests for verifying FFI bindings and SDK functionality: GopherOrchTest.php: - Tests native library availability and initialization - Tests FFI function calls (agent creation, error handling) - Tests library shutdown and re-initialization GopherAgentIntegrationTest.php: - Tests full agent creation through FFI - Tests dispose/cleanup functionality ConfigBuilderTest.php: - Tests builder pattern fluent interface - Tests API key and server config modes - Tests empty/default configuration All tests gracefully skip if native library is not built, allowing unit tests to pass in CI environments without the native library. --- tests/AgentResultTest.php | 49 +++++++ tests/ConfigBuilderTest.php | 69 +++++++++ tests/GopherAgentIntegrationTest.php | 206 +++++++++++++++++++++++++++ tests/GopherOrchTest.php | 178 +++++++++++++++++++++++ 4 files changed, 502 insertions(+) create mode 100644 tests/AgentResultTest.php create mode 100644 tests/ConfigBuilderTest.php create mode 100644 tests/GopherAgentIntegrationTest.php create mode 100644 tests/GopherOrchTest.php diff --git a/tests/AgentResultTest.php b/tests/AgentResultTest.php new file mode 100644 index 00000000..7494f4f6 --- /dev/null +++ b/tests/AgentResultTest.php @@ -0,0 +1,49 @@ +assertEquals('Hello, world!', $result->getResponse()); + $this->assertTrue($result->getStatus()->isSuccess()); + $this->assertTrue($result->isSuccess()); + $this->assertNull($result->getErrorMessage()); + $this->assertEquals(1, $result->getIterationCount()); + $this->assertEquals(0, $result->getTokensUsed()); + } + + public function testErrorResult(): void + { + $result = AgentResult::error('Something went wrong'); + + $this->assertEquals('', $result->getResponse()); + $this->assertFalse($result->getStatus()->isSuccess()); + $this->assertFalse($result->isSuccess()); + $this->assertEquals('Something went wrong', $result->getErrorMessage()); + $this->assertEquals(0, $result->getIterationCount()); + } + + public function testTimeoutResult(): void + { + $result = AgentResult::timeout('Operation timed out'); + + $this->assertEquals('', $result->getResponse()); + $this->assertFalse($result->getStatus()->isSuccess()); + $this->assertFalse($result->isSuccess()); + $this->assertEquals('Operation timed out', $result->getErrorMessage()); + $this->assertEquals(AgentResultStatus::TIMEOUT, $result->getStatus()->getValue()); + } +} diff --git a/tests/ConfigBuilderTest.php b/tests/ConfigBuilderTest.php new file mode 100644 index 00000000..96444542 --- /dev/null +++ b/tests/ConfigBuilderTest.php @@ -0,0 +1,69 @@ +withProvider('TestProvider') + ->withModel('test-model') + ->withApiKey('test-key') + ->build(); + + $this->assertEquals('TestProvider', $config->getProvider()); + $this->assertEquals('test-model', $config->getModel()); + $this->assertEquals('test-key', $config->getApiKey()); + $this->assertTrue($config->hasApiKey()); + $this->assertFalse($config->hasServerConfig()); + } + + public function testBuildWithServerConfig(): void + { + $serverConfig = '{"servers": []}'; + + $config = ConfigBuilder::create() + ->withProvider('TestProvider') + ->withModel('test-model') + ->withServerConfig($serverConfig) + ->build(); + + $this->assertEquals('TestProvider', $config->getProvider()); + $this->assertEquals('test-model', $config->getModel()); + $this->assertEquals($serverConfig, $config->getServerConfig()); + $this->assertFalse($config->hasApiKey()); + $this->assertTrue($config->hasServerConfig()); + } + + public function testBuildWithEmptyValues(): void + { + $config = ConfigBuilder::create()->build(); + + $this->assertEquals('', $config->getProvider()); + $this->assertEquals('', $config->getModel()); + $this->assertNull($config->getApiKey()); + $this->assertNull($config->getServerConfig()); + $this->assertFalse($config->hasApiKey()); + $this->assertFalse($config->hasServerConfig()); + } + + public function testBuilderIsFluent(): void + { + $builder = ConfigBuilder::create(); + + $this->assertSame($builder, $builder->withProvider('Test')); + $this->assertSame($builder, $builder->withModel('test')); + $this->assertSame($builder, $builder->withApiKey('key')); + $this->assertSame($builder, $builder->withServerConfig('{}')); + } +} diff --git a/tests/GopherAgentIntegrationTest.php b/tests/GopherAgentIntegrationTest.php new file mode 100644 index 00000000..7086cddc --- /dev/null +++ b/tests/GopherAgentIntegrationTest.php @@ -0,0 +1,206 @@ +markTestSkipped( + 'Native library not available. Run ./build.sh first.' + ); + } + } + + /** + * Helper method to create an agent, skipping test if creation fails. + * Agent creation may fail without API key or network access. + */ + private function tryCreateAgent(): GopherAgent + { + try { + return GopherAgent::createWithServerConfig( + 'AnthropicProvider', + 'claude-3-haiku-20240307', + self::SERVER_CONFIG + ); + } catch (AgentException $e) { + $this->markTestSkipped('Agent creation failed: ' . $e->getMessage()); + } + } + + public function testInitialization(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + GopherOrch::init(); + $this->assertTrue(GopherOrch::isInitialized()); + } + + public function testCreateAgentWithServerConfig(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + $config = ConfigBuilder::create() + ->withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withServerConfig(self::SERVER_CONFIG) + ->build(); + + $agent = null; + try { + $agent = GopherAgent::create($config); + $this->assertNotNull($agent, 'Agent should be created'); + $this->assertFalse($agent->isDisposed(), 'Agent should not be disposed'); + } catch (AgentException $e) { + // Agent creation may fail without API key - this is expected in test environment + $this->markTestSkipped('Skipping test: ' . $e->getMessage()); + } finally { + if ($agent !== null) { + $agent->dispose(); + $this->assertTrue($agent->isDisposed(), 'Agent should be disposed after dispose()'); + } + } + } + + public function testCreateAgentWithHelperMethod(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + $agent = $this->tryCreateAgent(); + try { + $this->assertNotNull($agent); + } finally { + $agent->dispose(); + } + } + + public function testDisposeIdempotent(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + $agent = $this->tryCreateAgent(); + + $agent->dispose(); + $this->assertTrue($agent->isDisposed()); + + // Second dispose should not throw + $agent->dispose(); + $this->assertTrue($agent->isDisposed()); + } + + public function testRunAfterDisposeThrows(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + $agent = $this->tryCreateAgent(); + $agent->dispose(); + + // Running on disposed agent should throw + $this->expectException(DisposedException::class); + $agent->run('test query'); + } + + public function testRunDetailedReturnsResult(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + $agent = $this->tryCreateAgent(); + try { + // Run with very short timeout to get quick response (likely timeout or error) + $result = $agent->runDetailedWithTimeout('test query', 100); + + $this->assertNotNull($result, 'Result should not be null'); + $this->assertNotNull($result->getResponse(), 'Response should not be null'); + $this->assertNotNull($result->getStatus(), 'Status should not be null'); + } finally { + $agent->dispose(); + } + } + + public function testMultipleAgentsCanBeCreated(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + $agent1 = null; + $agent2 = null; + + try { + $agent1 = $this->tryCreateAgent(); + $agent2 = $this->tryCreateAgent(); + + $this->assertNotNull($agent1); + $this->assertNotNull($agent2); + $this->assertNotSame($agent1, $agent2, 'Should be different agent instances'); + } finally { + if ($agent1 !== null) { + $agent1->dispose(); + } + if ($agent2 !== null) { + $agent2->dispose(); + } + } + } + + public function testShutdownAndReinit(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + GopherOrch::init(); + $this->assertTrue(GopherOrch::isInitialized()); + + GopherOrch::shutdown(); + $this->assertFalse(GopherOrch::isInitialized()); + + // Re-init should work + GopherOrch::init(); + $this->assertTrue(GopherOrch::isInitialized()); + } +} diff --git a/tests/GopherOrchTest.php b/tests/GopherOrchTest.php new file mode 100644 index 00000000..5d2720aa --- /dev/null +++ b/tests/GopherOrchTest.php @@ -0,0 +1,178 @@ +markTestSkipped( + 'Native library not available. Run ./build.sh first.' + ); + } + } + + public function testLibraryIsAvailable(): void + { + $available = GopherOrch::isAvailable(); + $this->assertTrue( + $available, + 'Native library should be available. Make sure to run ./build.sh first.' + ); + } + + public function testInit(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + // Should not throw + GopherOrch::init(); + $this->assertTrue(GopherOrch::isInitialized()); + } + + public function testGetFFI(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + $ffi = GopherOrch::getFFI(); + $this->assertNotNull($ffi); + } + + public function testAgentCreateByJsonWithValidConfig(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + // Call native function to create agent + $handle = GopherOrch::agentCreateByJson( + 'AnthropicProvider', + 'claude-3-haiku-20240307', + self::SERVER_CONFIG + ); + + // 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 !== null && !\FFI::isNull($handle)) { + // Clean up if agent was created + GopherOrch::agentRelease($handle); + } + + // Test passed if we got here without exception + $this->assertTrue(true); + } + + public function testAgentCreateByJsonWithEmptyConfig(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + // Empty/invalid config should return null handle + $handle = GopherOrch::agentCreateByJson( + 'AnthropicProvider', + 'claude-3-haiku-20240307', + '{}' + ); + + // Should handle gracefully (null or valid pointer, but no crash) + if ($handle !== null && !\FFI::isNull($handle)) { + GopherOrch::agentRelease($handle); + } + + // Test passed if we got here without exception + $this->assertTrue(true); + } + + public function testAgentCreateByApiKey(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + // Call with a dummy API key - should not crash + $handle = GopherOrch::agentCreateByApiKey( + 'AnthropicProvider', + 'claude-3-haiku-20240307', + 'test-api-key-12345' + ); + + // May return null if API key is invalid, but should not crash + if ($handle !== null && !\FFI::isNull($handle)) { + GopherOrch::agentRelease($handle); + } + + // Test passed if we got here without exception + $this->assertTrue(true); + } + + public function testLastErrorAndClearError(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + // Try to get last error (may be empty if no error) + $error = GopherOrch::getLastError(); + // Should be a string (possibly empty) + $this->assertIsString($error); + + // Clear error should not throw + GopherOrch::clearError(); + + // Test passed if we got here without exception + $this->assertTrue(true); + } + + public function testShutdown(): void + { + $this->skipIfNativeLibraryNotAvailable(); + + GopherOrch::init(); + $this->assertTrue(GopherOrch::isInitialized()); + + GopherOrch::shutdown(); + $this->assertFalse(GopherOrch::isInitialized()); + + // Re-init should work + GopherOrch::init(); + $this->assertTrue(GopherOrch::isInitialized()); + } +} From a6aa2e1290fe4cba21bcb5038e96927e09b0972b Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 12:20:57 +0800 Subject: [PATCH 6/9] Fix build.sh for PHP SDK - Make PHP check optional (warning instead of error if PHP not installed) - Handle multiple libfmt versions (10, 11, 12) in dylib fixes - Native library build succeeds even without PHP installed --- build.sh | 63 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/build.sh b/build.sh index 641b375a..e436211a 100755 --- a/build.sh +++ b/build.sh @@ -125,33 +125,44 @@ if [[ "$OSTYPE" == "darwin"* ]]; then echo -e "${YELLOW} Fixing dylib install names for macOS...${NC}" cd "${NATIVE_LIB_DIR}" - # Fix libgopher-orch to use @rpath for dependencies + # Find the actual libfmt version (e.g., libfmt.10.dylib or libfmt.11.dylib) + LIBFMT_DYLIB=$(ls libfmt.*.dylib 2>/dev/null | grep -E 'libfmt\.[0-9]+\.dylib$' | head -1) + LIBFMT_VERSION=$(echo "$LIBFMT_DYLIB" | sed 's/libfmt\.\([0-9]*\)\.dylib/\1/') + + # Fix libgopher-orch to use @loader_path for dependencies if [ -f "libgopher-orch.dylib" ]; then install_name_tool -id "@rpath/libgopher-orch.dylib" libgopher-orch.dylib 2>/dev/null || true install_name_tool -change "@rpath/libgopher-mcp.dylib" "@loader_path/libgopher-mcp.dylib" libgopher-orch.dylib 2>/dev/null || true install_name_tool -change "@rpath/libgopher-mcp-event.dylib" "@loader_path/libgopher-mcp-event.dylib" libgopher-orch.dylib 2>/dev/null || true - install_name_tool -change "@rpath/libfmt.11.dylib" "@loader_path/libfmt.11.dylib" libgopher-orch.dylib 2>/dev/null || true - install_name_tool -change "libfmt.so.11" "@loader_path/libfmt.11.dylib" libgopher-orch.dylib 2>/dev/null || true + # Handle different libfmt versions + for v in 10 11 12; do + install_name_tool -change "@rpath/libfmt.${v}.dylib" "@loader_path/libfmt.${v}.dylib" libgopher-orch.dylib 2>/dev/null || true + install_name_tool -change "libfmt.so.${v}" "@loader_path/libfmt.${v}.dylib" libgopher-orch.dylib 2>/dev/null || true + done fi # Fix libgopher-mcp if [ -f "libgopher-mcp.dylib" ]; then install_name_tool -id "@rpath/libgopher-mcp.dylib" libgopher-mcp.dylib 2>/dev/null || true install_name_tool -change "@rpath/libgopher-mcp-event.dylib" "@loader_path/libgopher-mcp-event.dylib" libgopher-mcp.dylib 2>/dev/null || true - install_name_tool -change "@rpath/libfmt.11.dylib" "@loader_path/libfmt.11.dylib" libgopher-mcp.dylib 2>/dev/null || true - install_name_tool -change "libfmt.so.11" "@loader_path/libfmt.11.dylib" libgopher-mcp.dylib 2>/dev/null || true + for v in 10 11 12; do + install_name_tool -change "@rpath/libfmt.${v}.dylib" "@loader_path/libfmt.${v}.dylib" libgopher-mcp.dylib 2>/dev/null || true + install_name_tool -change "libfmt.so.${v}" "@loader_path/libfmt.${v}.dylib" libgopher-mcp.dylib 2>/dev/null || true + done fi # Fix libgopher-mcp-event if [ -f "libgopher-mcp-event.dylib" ]; then install_name_tool -id "@rpath/libgopher-mcp-event.dylib" libgopher-mcp-event.dylib 2>/dev/null || true - install_name_tool -change "@rpath/libfmt.11.dylib" "@loader_path/libfmt.11.dylib" libgopher-mcp-event.dylib 2>/dev/null || true - install_name_tool -change "libfmt.so.11" "@loader_path/libfmt.11.dylib" libgopher-mcp-event.dylib 2>/dev/null || true + for v in 10 11 12; do + install_name_tool -change "@rpath/libfmt.${v}.dylib" "@loader_path/libfmt.${v}.dylib" libgopher-mcp-event.dylib 2>/dev/null || true + install_name_tool -change "libfmt.so.${v}" "@loader_path/libfmt.${v}.dylib" libgopher-mcp-event.dylib 2>/dev/null || true + done fi # Fix libfmt - if [ -f "libfmt.11.dylib" ]; then - install_name_tool -id "@rpath/libfmt.11.dylib" libfmt.11.dylib 2>/dev/null || true + if [ -n "$LIBFMT_DYLIB" ] && [ -f "$LIBFMT_DYLIB" ]; then + install_name_tool -id "@rpath/$LIBFMT_DYLIB" "$LIBFMT_DYLIB" 2>/dev/null || true fi cd "${SCRIPT_DIR}" @@ -181,27 +192,29 @@ fi echo "" -# Step 5: Check PHP and Composer +# Step 5: Check PHP and Composer (optional) echo -e "${YELLOW}Step 4: Checking PHP environment...${NC}" cd "${SCRIPT_DIR}" +PHP_AVAILABLE=false + # Check for PHP if ! command -v php &> /dev/null; then - echo -e "${RED}Error: PHP not found. Please install PHP first.${NC}" + echo -e "${YELLOW}⚠ PHP not found. Install PHP to use the SDK:${NC}" echo -e "${YELLOW} macOS: brew install php${NC}" echo -e "${YELLOW} Linux: sudo apt-get install php php-ffi${NC}" - exit 1 -fi - -# Check PHP version -PHP_VERSION=$(php -v | head -n 1 | cut -d ' ' -f 2 | cut -d '.' -f 1,2) -echo -e "${GREEN}βœ“ PHP version: ${PHP_VERSION}${NC}" - -# Check for FFI extension -if ! php -m | grep -q "^FFI$"; then - echo -e "${YELLOW}⚠ PHP FFI extension not enabled. Please enable it in php.ini:${NC}" - echo -e "${YELLOW} extension=ffi${NC}" - echo -e "${YELLOW} ffi.enable=true${NC}" +else + PHP_AVAILABLE=true + # Check PHP version + PHP_VERSION=$(php -v | head -n 1 | cut -d ' ' -f 2 | cut -d '.' -f 1,2) + echo -e "${GREEN}βœ“ PHP version: ${PHP_VERSION}${NC}" + + # Check for FFI extension + if ! php -m | grep -q "^FFI$"; then + echo -e "${YELLOW}⚠ PHP FFI extension not enabled. Please enable it in php.ini:${NC}" + echo -e "${YELLOW} extension=ffi${NC}" + echo -e "${YELLOW} ffi.enable=true${NC}" + fi fi # Check for Composer @@ -224,7 +237,9 @@ echo "" # Step 6: Run tests if PHPUnit is available echo -e "${YELLOW}Step 5: Running tests...${NC}" -if [ -f "vendor/bin/phpunit" ]; then +if [ "$PHP_AVAILABLE" = false ]; then + echo -e "${YELLOW}⚠ Skipping tests (PHP not available)${NC}" +elif [ -f "vendor/bin/phpunit" ]; then ./vendor/bin/phpunit --testdox 2>/dev/null && echo -e "${GREEN}βœ“ Tests passed${NC}" || echo -e "${YELLOW}⚠ Some tests may have failed (native library required)${NC}" elif command -v phpunit &> /dev/null; then phpunit --testdox 2>/dev/null && echo -e "${GREEN}βœ“ Tests passed${NC}" || echo -e "${YELLOW}⚠ Some tests may have failed (native library required)${NC}" From 82c4cf75b9ee71c64045eae35f98d3d77b2389b6 Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 12:21:08 +0800 Subject: [PATCH 7/9] Add README.md documentation for PHP SDK Comprehensive documentation including: - Features and architecture overview - Quick start guide with code examples - Building from source instructions - Native library details and search order - API documentation for GopherAgent and ConfigBuilder - Error handling patterns with exception types - Examples with local MCP servers - Development workflow and project structure - Troubleshooting guide for common issues - Contributing guidelines --- README.md | 683 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 683 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..12e984de --- /dev/null +++ b/README.md @@ -0,0 +1,683 @@ +# gopher-orch - PHP SDK + +PHP SDK for Gopher Orch - AI Agent orchestration framework with native C++ performance. + +## Table of Contents + +- [Features](#features) +- [When to Use This SDK](#when-to-use-this-sdk) +- [Architecture](#architecture) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Building from Source](#building-from-source) + - [Prerequisites](#prerequisites) + - [Step 1: Clone the Repository](#step-1-clone-the-repository) + - [Step 2: Build Everything](#step-2-build-everything) + - [Step 3: Verify the Build](#step-3-verify-the-build) + - [Step 4: Run Tests](#step-4-run-tests) +- [Native Library Details](#native-library-details) + - [Library Location](#library-location) + - [Platform-Specific Library Names](#platform-specific-library-names) + - [Library Search Order](#library-search-order) +- [API Documentation](#api-documentation) + - [GopherAgent](#gopheragent) + - [ConfigBuilder](#configbuilder) + - [Error Handling](#error-handling) +- [Examples](#examples) + - [Basic Usage with API Key](#basic-usage-with-api-key) + - [Using Local MCP Servers](#using-local-mcp-servers) + - [Running the Example](#running-the-example) +- [Development](#development) + - [Project Structure](#project-structure) + - [Build Scripts](#build-scripts) + - [Rebuilding Native Library](#rebuilding-native-library) + - [Updating Submodules](#updating-submodules) +- [Troubleshooting](#troubleshooting) +- [Contributing](#contributing) +- [License](#license) +- [Links](#links) +- [Acknowledgments](#acknowledgments) + +--- + +## Features + +- **Native Performance** - Powered by C++ core with PHP bindings via FFI +- **AI Agent Framework** - Build intelligent agents with LLM integration +- **MCP Protocol** - Model Context Protocol client and server support +- **Tool Orchestration** - Manage and execute tools across multiple MCP servers +- **State Management** - Built-in state graph for complex workflows +- **Type Safety** - Full PHP type declarations and strict mode support + +## When to Use This SDK + +This SDK is ideal for: + +- **PHP web applications** that need high-performance AI agent orchestration +- **Laravel/Symfony services** requiring MCP protocol support +- **WordPress plugins** integrating AI agents +- **CLI tools** built with PHP +- **API backends** needing reliable agent infrastructure + +## Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Your Application β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHP SDK (GopherSecurity\Orch) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ GopherAgent β”‚ β”‚ConfigBuilderβ”‚ β”‚ Exception Classes β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ FFI (PHP FFI Extension) + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Native Library (libgopher-orch) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Agent Engine β”‚ β”‚ LLM Providers β”‚ β”‚ MCP Client β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ - Anthropic β”‚ β”‚ - HTTP/SSE β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ - OpenAI β”‚ β”‚ - Tool Registry β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ MCP Servers β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Weather API β”‚ β”‚ Database β”‚ β”‚ Custom Tools β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Installation + +### Option 1: Composer (when published) + +```bash +composer require gophersecurity/gopher-orch +``` + +### Option 2: Git Repository + +```bash +composer config repositories.gopher-orch vcs https://github.com/GopherSecurity/gopher-mcp-php.git +composer require gophersecurity/gopher-orch:dev-main +``` + +### Option 3: Build from Source + +See [Building from Source](#building-from-source) section below. + +## Quick Start + +```php +withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withApiKey('your-api-key') + ->build(); + +$agent = GopherAgent::create($config); + +// Run the agent +$result = $agent->run('What is the weather in Tokyo?'); +echo $result . "\n"; + +// Cleanup (optional - happens automatically) +$agent->dispose(); +``` + +--- + +## Building from Source + +This SDK wraps a native C++ library via PHP FFI. You must build the native library before using the SDK. + +### Prerequisites + +| Requirement | Version | Notes | +|-------------|---------|-------| +| PHP | >= 7.4 | With FFI extension enabled | +| Composer | Latest | Dependency manager | +| Git | Latest | For cloning and submodules | +| CMake | >= 3.15 | Native library build system | +| C++ Compiler | C++14+ | Clang (macOS), GCC (Linux), MSVC (Windows) | + +**Platform-specific requirements:** + +- **macOS**: Xcode Command Line Tools (`xcode-select --install`) +- **Linux**: `build-essential`, `libssl-dev`, `php-ffi` +- **Windows**: Visual Studio 2019+ with C++ workload + +**PHP FFI Extension:** + +Ensure FFI is enabled in your `php.ini`: + +```ini +extension=ffi +ffi.enable=true +``` + +### Step 1: Clone the Repository + +```bash +git clone https://github.com/GopherSecurity/gopher-mcp-php.git +cd gopher-mcp-php +``` + +### Step 2: Build Everything + +**Using build.sh (recommended)** + +The `build.sh` script handles everything automatically: + +```bash +./build.sh +``` + +**Using build.sh with Multiple GitHub Accounts:** + +If you have multiple GitHub accounts configured with SSH host aliases, use the `GITHUB_SSH_HOST` environment variable: + +```bash +# Use custom SSH host alias for cloning private submodules +GITHUB_SSH_HOST=your-ssh-alias ./build.sh + +# Example: if your ~/.ssh/config has "Host github-work" for work account +GITHUB_SSH_HOST=github-work ./build.sh +``` + +**What happens during build:** + +1. **Submodule update** - Initializes and updates submodules (with SSH URL rewriting if `GITHUB_SSH_HOST` is set) +2. **CMake configure** - Configures the C++ build with Release settings +3. **Native compilation** - Compiles C++ to shared libraries +4. **Library installation** - Copies libraries to `native/lib/` +5. **Dependency copying** - Copies required dependencies (gopher-mcp, fmt) +6. **macOS fixes** - Fixes dylib install names for proper PHP FFI loading +7. **Composer install** - Installs PHP dependencies +8. **PHPUnit tests** - Runs test suite + +### Step 3: Verify the Build + +```bash +# Check native libraries were built +ls -la native/lib/ + +# Expected output (macOS): +# libgopher-orch.dylib +# libgopher-mcp.dylib +# libgopher-mcp-event.dylib +# libfmt.dylib + +# Verify PHP can load the SDK +php -r "require 'vendor/autoload.php'; echo GopherSecurity\Orch\GopherOrch::isAvailable() ? 'OK' : 'FAIL';" +``` + +### Step 4: Run Tests + +```bash +./vendor/bin/phpunit --testdox +``` + +--- + +## Native Library Details + +### Library Location + +After building, native libraries are installed to: + +``` +native/ +β”œβ”€β”€ lib/ # Shared libraries +β”‚ β”œβ”€β”€ libgopher-orch.dylib # Main orchestration library (macOS) +β”‚ β”œβ”€β”€ libgopher-orch.so # Main orchestration library (Linux) +β”‚ β”œβ”€β”€ libgopher-mcp.dylib # MCP protocol library +β”‚ β”œβ”€β”€ libgopher-mcp-event.dylib # Event handling +β”‚ └── libfmt.dylib # Formatting library +└── include/ # C++ headers (for development) + └── orch/ + └── core/ +``` + +### Platform-Specific Library Names + +| Platform | Library Extension | Example | +|----------|------------------|---------| +| macOS | `.dylib` | `libgopher-orch.dylib` | +| Linux | `.so` | `libgopher-orch.so` | +| Windows | `.dll` | `gopher-orch.dll` | + +### Library Search Order + +The SDK searches for the native library in this order: + +1. `GOPHER_ORCH_LIBRARY_PATH` environment variable +2. `native/lib/` relative to current working directory +3. `native/lib/` relative to the SDK source directory +4. System paths (`/usr/local/lib`, `/opt/homebrew/lib`) + +--- + +## API Documentation + +### GopherAgent + +The main class for creating and running AI agents: + +```php +use GopherSecurity\Orch\ConfigBuilder; +use GopherSecurity\Orch\GopherAgent; +use GopherSecurity\Orch\GopherOrch; + +// Initialize the library (called automatically on first create) +GopherOrch::init(); + +// Create with API key (fetches server config from remote API) +$config = ConfigBuilder::create() + ->withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withApiKey('your-api-key') + ->build(); + +$agent = GopherAgent::create($config); + +// Or create with JSON server config +$serverConfig = <<<'JSON' +{ + "succeeded": true, + "data": { + "servers": [{ + "serverId": "server1", + "name": "My MCP Server", + "transport": "http_sse", + "config": {"url": "http://localhost:3001/mcp"} + }] + } +} +JSON; + +$agent = GopherAgent::createWithServerConfig( + 'AnthropicProvider', + 'claude-3-haiku-20240307', + $serverConfig +); + +// Run a query +$result = $agent->run('Your prompt here'); + +// Run with custom timeout (default: 60000ms) +$result = $agent->runWithTimeout('Your prompt here', 30000); + +// Run with detailed result information +$detailed = $agent->runDetailed('Your prompt here'); +// Returns AgentResult with: getResponse(), getStatus(), getIterationCount(), getTokensUsed() + +// Manual cleanup (optional - happens automatically on destruction) +$agent->dispose(); + +// Shutdown library +GopherOrch::shutdown(); +``` + +### ConfigBuilder + +Builder for creating agent configurations: + +```php +use GopherSecurity\Orch\ConfigBuilder; + +// With API key +$config = ConfigBuilder::create() + ->withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withApiKey('your-api-key') + ->build(); + +// With server config +$config = ConfigBuilder::create() + ->withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withServerConfig('{"succeeded": true, "data": {"servers": []}}') + ->build(); + +// Check configuration +$config->hasApiKey(); // true +$config->hasServerConfig(); // false +``` + +### Error Handling + +The SDK provides typed exceptions for different failure scenarios: + +```php +use GopherSecurity\Orch\GopherAgent; +use GopherSecurity\Orch\ConfigBuilder; +use GopherSecurity\Orch\Exception\LibraryException; +use GopherSecurity\Orch\Exception\ConfigException; +use GopherSecurity\Orch\Exception\AgentException; +use GopherSecurity\Orch\Exception\TimeoutException; +use GopherSecurity\Orch\Exception\DisposedException; + +try { + $config = ConfigBuilder::create() + ->withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withApiKey('invalid-key') + ->build(); + + $agent = GopherAgent::create($config); + $result = $agent->run('query'); +} catch (LibraryException $e) { + echo "Library not found: " . $e->getMessage() . "\n"; +} catch (ConfigException $e) { + echo "Invalid config: " . $e->getMessage() . "\n"; +} catch (AgentException $e) { + echo "Agent error: " . $e->getMessage() . "\n"; +} catch (DisposedException $e) { + echo "Agent disposed: " . $e->getMessage() . "\n"; +} +``` + +--- + +## Examples + +### Basic Usage with API Key + +```php +withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withApiKey($apiKey) + ->build(); + +$agent = GopherAgent::create($config); + +$answer = $agent->run('What time is it in London?'); +echo "Answer: $answer\n"; + +$agent->dispose(); +``` + +### Using Local MCP Servers + +```php +withProvider('AnthropicProvider') + ->withModel('claude-3-haiku-20240307') + ->withServerConfig(SERVER_CONFIG) + ->build(); + +$agent = GopherAgent::create($config); + +$result = $agent->run('What is the weather in New York?'); +echo "$result\n"; + +$agent->dispose(); +``` + +### Running the Example + +```bash +# Run with the convenience script (starts servers automatically) +cd examples && ./client_example_json_run.sh + +# Or manually: +# Terminal 1: Start server3001 +cd examples/server3001 && npm install && npm run dev + +# Terminal 2: Start server3002 +cd examples/server3002 && npm install && npm run dev + +# Terminal 3: Run the PHP client +ANTHROPIC_API_KEY=your-key php examples/client_example_json.php +``` + +--- + +## Development + +### Project Structure + +``` +gopher-mcp-php/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ GopherOrch.php # FFI loader and bindings +β”‚ β”œβ”€β”€ GopherAgent.php # Main agent class +β”‚ β”œβ”€β”€ Config.php # Configuration class +β”‚ β”œβ”€β”€ ConfigBuilder.php # Configuration builder +β”‚ β”œβ”€β”€ AgentResult.php # Result class +β”‚ β”œβ”€β”€ AgentResultStatus.php # Result status +β”‚ └── Exception/ # Exception classes +β”‚ β”œβ”€β”€ GopherException.php +β”‚ β”œβ”€β”€ LibraryException.php +β”‚ β”œβ”€β”€ ConfigException.php +β”‚ β”œβ”€β”€ AgentException.php +β”‚ β”œβ”€β”€ TimeoutException.php +β”‚ └── DisposedException.php +β”œβ”€β”€ tests/ # PHPUnit tests +β”‚ β”œβ”€β”€ GopherOrchTest.php +β”‚ β”œβ”€β”€ GopherAgentIntegrationTest.php +β”‚ β”œβ”€β”€ ConfigBuilderTest.php +β”‚ └── AgentResultTest.php +β”œβ”€β”€ native/ # Native libraries (generated) +β”‚ β”œβ”€β”€ lib/ # Shared libraries (.dylib, .so, .dll) +β”‚ └── include/ # C++ headers +β”œβ”€β”€ third_party/ # Git submodules +β”‚ └── gopher-orch/ # C++ implementation +β”œβ”€β”€ examples/ # Example code +β”‚ β”œβ”€β”€ client_example_json.php +β”‚ β”œβ”€β”€ client_example_json_run.sh +β”‚ β”œβ”€β”€ server3001/ # Mock weather MCP server +β”‚ └── server3002/ # Mock tools MCP server +β”œβ”€β”€ build.sh # Build orchestration script +β”œβ”€β”€ composer.json # Composer configuration +β”œβ”€β”€ phpunit.xml.dist # PHPUnit configuration +└── README.md +``` + +### Build Scripts + +| Script | Description | +|--------|-------------| +| `./build.sh` | Full build (submodules + native + Composer) | +| `./build.sh --clean` | Clean CMake cache while preserving _deps | +| `./build.sh --clean --build` | Clean and rebuild | +| `GITHUB_SSH_HOST=alias ./build.sh` | Build with custom SSH host | +| `composer install` | Install PHP dependencies | +| `./vendor/bin/phpunit` | Run tests | + +### Rebuilding Native Library + +If you modify the C++ code or switch branches: + +```bash +# Clean and rebuild (preserves downloaded dependencies) +./build.sh --clean --build +``` + +### Updating Submodules + +To pull latest changes from native libraries: + +```bash +# Update to latest commit +cd third_party/gopher-orch +git fetch origin +git checkout +cd ../.. + +# Rebuild +./build.sh --clean --build +``` + +--- + +## Troubleshooting + +### "Library not found" Error + +**Cause**: Native library not built or not in expected location. + +**Solution**: +```bash +# Rebuild native library +./build.sh + +# Verify library exists +ls native/lib/libgopher-orch.* +``` + +### "Submodule is empty" Error + +**Cause**: Git submodules not initialized. + +**Solution**: +```bash +git submodule update --init --recursive +``` + +### CMake Configuration Fails + +**Cause**: Missing dependencies or wrong CMake version. + +**Solution**: +```bash +# macOS +brew install cmake + +# Linux (Ubuntu/Debian) +sudo apt-get install cmake build-essential libssl-dev + +# Verify version +cmake --version # Should be >= 3.15 +``` + +### FFI Extension Not Loaded + +**Cause**: PHP FFI extension not enabled. + +**Solution**: + +Edit your `php.ini`: +```ini +extension=ffi +ffi.enable=true +``` + +Find your php.ini location: +```bash +php --ini +``` + +### "Class not found" Error + +**Cause**: Composer autoload not configured. + +**Solution**: +```bash +composer install +composer dump-autoload +``` + +### Build Fails on Apple Silicon (M1/M2) + +**Cause**: Architecture mismatch. + +**Solution**: +```bash +# Ensure using native arm64 toolchain +arch -arm64 ./build.sh +``` + +--- + +## Contributing + +Contributions are welcome! Please read our contributing guidelines. + +1. Fork the repository +2. Create your feature branch (`git checkout -b feature/amazing-feature`) +3. Ensure submodules are initialized (`git submodule update --init --recursive`) +4. Make your changes +5. Run tests (`./vendor/bin/phpunit`) +6. Commit your changes (`git commit -m 'Add amazing feature'`) +7. Push to the branch (`git push origin feature/amazing-feature`) +8. Open a Pull Request + +--- + +## License + +MIT License - see [LICENSE](LICENSE) file for details. + +## Links + +- [GitHub Repository](https://github.com/GopherSecurity/gopher-mcp-php) +- [Java SDK](https://github.com/GopherSecurity/gopher-mcp-java) +- [Rust SDK](https://github.com/GopherSecurity/gopher-mcp-rust) +- [Python SDK](https://github.com/GopherSecurity/gopher-mcp-python) +- [TypeScript SDK](https://github.com/GopherSecurity/gopher-orch-js) +- [Native C++ Implementation](https://github.com/GopherSecurity/gopher-orch) +- [Model Context Protocol](https://modelcontextprotocol.io/) + +## Acknowledgments + +- Built on [gopher-orch](https://github.com/GopherSecurity/gopher-orch) C++ framework +- Uses [gopher-mcp](https://github.com/GopherSecurity/gopher-mcp) for MCP protocol +- Inspired by LangChain and LangGraph +- FFI bindings via [PHP FFI](https://www.php.net/manual/en/book.ffi.php) From 5ca7ca44d9184e25859f484d140f377b27d847f6 Mon Sep 17 00:00:00 2001 From: RahulHere Date: Fri, 23 Jan 2026 12:34:58 +0800 Subject: [PATCH 8/9] Fix example script cleanup to terminate all child processes The npm run dev command spawns child processes that weren't being terminated when the parent PID was killed. This fix ensures proper cleanup by using process groups and port-based killing as fallback. Changes: - Kill by process group (negative PID) to terminate child processes - Add port-based killing via lsof as fallback for remaining processes - Add INT and TERM signals to the cleanup trap --- examples/client_example_json_run.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/client_example_json_run.sh b/examples/client_example_json_run.sh index 26965fa6..554c5e9e 100755 --- a/examples/client_example_json_run.sh +++ b/examples/client_example_json_run.sh @@ -14,19 +14,23 @@ NC='\033[0m' # No Color SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" -# Cleanup function +# Cleanup function - kill process groups to ensure all child processes are terminated cleanup() { echo -e "\n${YELLOW}Cleaning up...${NC}" + # Kill by process group (negative PID) if [ -n "$SERVER3001_PID" ]; then - kill $SERVER3001_PID 2>/dev/null || true + 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 || true + kill -- -$SERVER3002_PID 2>/dev/null || kill $SERVER3002_PID 2>/dev/null || true fi + # Also kill any remaining node processes on the specific 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 +trap cleanup EXIT INT TERM echo -e "${GREEN}======================================${NC}" echo -e "${GREEN}Running PHP Client Example${NC}" From 57732729056679d80d0f99d3a9044b0c7f852143 Mon Sep 17 00:00:00 2001 From: RahulHere Date: Mon, 26 Jan 2026 11:56:31 +0800 Subject: [PATCH 9/9] Add PHP-CS-Fixer for code formatting Set up PHP code formatting with PHP-CS-Fixer: - Add friendsofphp/php-cs-fixer as dev dependency - Create .php-cs-fixer.php configuration (PSR-12 based) - Add composer scripts: cs-fix, cs-check - Update .gitignore with .php-cs-fixer.cache - Add Code Formatting section to README - Apply formatting fixes to all PHP files Changes: - composer.json: Add php-cs-fixer dependency and scripts - .php-cs-fixer.php: Configuration file - .gitignore: Add cache file - README.md: Add Code Formatting section - src/*.php: Apply PSR-12 formatting - tests/*.php: Apply formatting, remove unused imports - examples/*.php: Apply formatting --- .gitignore | 3 +++ .php-cs-fixer.php | 24 ++++++++++++++++++++++++ README.md | 17 +++++++++++++++++ composer.json | 5 ++++- examples/client_example_json.php | 2 +- src/ConfigBuilder.php | 4 ++++ src/GopherAgent.php | 3 +++ src/GopherOrch.php | 3 +++ tests/ConfigBuilderTest.php | 1 - tests/GopherAgentIntegrationTest.php | 7 +++++-- tests/GopherOrchTest.php | 1 - 11 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 .php-cs-fixer.php diff --git a/.gitignore b/.gitignore index e89555c6..b0c84687 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ composer.lock phpunit.xml coverage/ +# PHP-CS-Fixer +.php-cs-fixer.cache + # IDE - PhpStorm .idea/ *.iws diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 00000000..dc0ab115 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,24 @@ +in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') + ->in(__DIR__ . '/examples') + ->name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + +return (new PhpCsFixer\Config()) + ->setRules([ + '@PSR12' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'no_unused_imports' => true, + 'single_quote' => true, + 'trailing_comma_in_multiline' => true, + 'blank_line_before_statement' => [ + 'statements' => ['return', 'throw', 'try'], + ], + ]) + ->setFinder($finder) + ->setRiskyAllowed(false); diff --git a/README.md b/README.md index 12e984de..1d53f366 100644 --- a/README.md +++ b/README.md @@ -491,6 +491,23 @@ ANTHROPIC_API_KEY=your-key php examples/client_example_json.php ## Development +### Code Formatting + +```bash +# Format code +composer cs-fix + +# Check formatting without making changes +composer cs-check + +# Or run directly +./vendor/bin/php-cs-fixer fix +./vendor/bin/php-cs-fixer fix --dry-run --diff + +# Run tests +./vendor/bin/phpunit --testdox +``` + ### Project Structure ``` diff --git a/composer.json b/composer.json index 9da279d9..78c6c3fa 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "ext-ffi": "*" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3.93", "phpunit/phpunit": "^9.5" }, "autoload": { @@ -30,7 +31,9 @@ }, "scripts": { "test": "phpunit --testdox", - "test-coverage": "phpunit --coverage-html coverage" + "test-coverage": "phpunit --coverage-html coverage", + "cs-fix": "php-cs-fixer fix", + "cs-check": "php-cs-fixer fix --dry-run --diff" }, "config": { "sort-packages": true diff --git a/examples/client_example_json.php b/examples/client_example_json.php index eb2265f7..b4ab57cd 100644 --- a/examples/client_example_json.php +++ b/examples/client_example_json.php @@ -71,6 +71,6 @@ $agent->dispose(); } catch (Exception $e) { - fwrite(STDERR, "Error: " . $e->getMessage() . "\n"); + fwrite(STDERR, 'Error: ' . $e->getMessage() . "\n"); exit(1); } diff --git a/src/ConfigBuilder.php b/src/ConfigBuilder.php index 3f60e838..a66f1051 100644 --- a/src/ConfigBuilder.php +++ b/src/ConfigBuilder.php @@ -31,6 +31,7 @@ public static function create(): self public function withProvider(string $provider): self { $this->provider = $provider; + return $this; } @@ -43,6 +44,7 @@ public function withProvider(string $provider): self public function withModel(string $model): self { $this->model = $model; + return $this; } @@ -56,6 +58,7 @@ public function withModel(string $model): self public function withApiKey(string $apiKey): self { $this->apiKey = $apiKey; + return $this; } @@ -69,6 +72,7 @@ public function withApiKey(string $apiKey): self public function withServerConfig(string $serverConfig): self { $this->serverConfig = $serverConfig; + return $this; } diff --git a/src/GopherAgent.php b/src/GopherAgent.php index b087b1b9..8a3fa34b 100644 --- a/src/GopherAgent.php +++ b/src/GopherAgent.php @@ -64,6 +64,7 @@ public static function create(Config $config): self $errorMsg = GopherOrch::getLastError(); GopherOrch::clearError(); $message = $errorMsg !== '' ? $errorMsg : 'Failed to create agent'; + throw new AgentException($message); } @@ -165,6 +166,7 @@ public function runDetailedWithTimeout(string $query, int $timeoutMs): AgentResu { try { $response = $this->runWithTimeout($query, $timeoutMs); + return AgentResult::success($response); } catch (DisposedException $e) { return AgentResult::error($e->getMessage()); @@ -173,6 +175,7 @@ public function runDetailedWithTimeout(string $query, int $timeoutMs): AgentResu if (stripos($e->getMessage(), 'timeout') !== false) { return AgentResult::timeout($e->getMessage()); } + return AgentResult::error($e->getMessage()); } } diff --git a/src/GopherOrch.php b/src/GopherOrch.php index 980e9663..c64832b1 100644 --- a/src/GopherOrch.php +++ b/src/GopherOrch.php @@ -85,6 +85,7 @@ public static function isAvailable(): bool { try { self::init(); + return true; } catch (LibraryException $e) { return false; @@ -149,6 +150,7 @@ public static function getFFI(): FFI public static function agentCreateByJson(string $provider, string $model, string $serverJson) { $ffi = self::getFFI(); + return $ffi->gopher_orch_agent_create_by_json($provider, $model, $serverJson); } @@ -163,6 +165,7 @@ public static function agentCreateByJson(string $provider, string $model, string public static function agentCreateByApiKey(string $provider, string $model, string $apiKey) { $ffi = self::getFFI(); + return $ffi->gopher_orch_agent_create_by_api_key($provider, $model, $apiKey); } diff --git a/tests/ConfigBuilderTest.php b/tests/ConfigBuilderTest.php index 96444542..b834bfdd 100644 --- a/tests/ConfigBuilderTest.php +++ b/tests/ConfigBuilderTest.php @@ -4,7 +4,6 @@ namespace GopherSecurity\Orch\Tests; -use GopherSecurity\Orch\Config; use GopherSecurity\Orch\ConfigBuilder; use PHPUnit\Framework\TestCase; diff --git a/tests/GopherAgentIntegrationTest.php b/tests/GopherAgentIntegrationTest.php index 7086cddc..f33681b8 100644 --- a/tests/GopherAgentIntegrationTest.php +++ b/tests/GopherAgentIntegrationTest.php @@ -5,10 +5,10 @@ namespace GopherSecurity\Orch\Tests; use GopherSecurity\Orch\ConfigBuilder; -use GopherSecurity\Orch\GopherAgent; -use GopherSecurity\Orch\GopherOrch; use GopherSecurity\Orch\Exception\AgentException; use GopherSecurity\Orch\Exception\DisposedException; +use GopherSecurity\Orch\GopherAgent; +use GopherSecurity\Orch\GopherOrch; use PHPUnit\Framework\TestCase; /** @@ -95,6 +95,7 @@ public function testCreateAgentWithServerConfig(): void ->build(); $agent = null; + try { $agent = GopherAgent::create($config); $this->assertNotNull($agent, 'Agent should be created'); @@ -115,6 +116,7 @@ public function testCreateAgentWithHelperMethod(): void $this->skipIfNativeLibraryNotAvailable(); $agent = $this->tryCreateAgent(); + try { $this->assertNotNull($agent); } finally { @@ -153,6 +155,7 @@ public function testRunDetailedReturnsResult(): void $this->skipIfNativeLibraryNotAvailable(); $agent = $this->tryCreateAgent(); + try { // Run with very short timeout to get quick response (likely timeout or error) $result = $agent->runDetailedWithTimeout('test query', 100); diff --git a/tests/GopherOrchTest.php b/tests/GopherOrchTest.php index 5d2720aa..b0fae5d6 100644 --- a/tests/GopherOrchTest.php +++ b/tests/GopherOrchTest.php @@ -5,7 +5,6 @@ namespace GopherSecurity\Orch\Tests; use GopherSecurity\Orch\GopherOrch; -use GopherSecurity\Orch\Exception\LibraryException; use PHPUnit\Framework\TestCase; /**