Skip to content

Gopher-mcp-js Cross-Platform Distribution #2

Description

@bettercallsaulj

Overview

gopher-mcp-js is a TypeScript SDK for Gopher Orch - an AI Agent orchestration framework with native performance. It uses koffi for FFI to call C++ libraries (gopher-orch).

Current Architecture

Project Structure

gopher-mcp-js/
├── src/
│   ├── index.ts           # Main exports
│   ├── agent.ts           # GopherAgent class
│   ├── config.ts          # Configuration
│   ├── ffi/
│   │   ├── index.ts
│   │   └── library.ts     # koffi FFI bindings
│   └── ...
├── native/
│   └── lib/               # Native libraries (currently macOS only)
│       ├── libgopher-orch.dylib
│       ├── libgopher-mcp.dylib
│       └── ...
├── third_party/
│   └── gopher-orch/       # Git submodule
└── build.sh               # Builds from source

Current Build Process

  • gopher-orch is a git submodule in third_party/gopher-orch
  • build.sh compiles C++ from source using CMake
  • Built libraries go to native/lib/
  • Works for development but requires C++ toolchain

Target Platforms

Platform Architecture Library Name
Linux x64 libgopher-orch.so
Linux ARM64 libgopher-orch.so
macOS x64 (Intel) libgopher-orch.dylib
macOS ARM64 (Apple Silicon) libgopher-orch.dylib
Windows x64 gopher-orch.dll
Windows ARM64 gopher-orch.dll

Distribution Approaches

Approach 1: Platform-specific Optional Dependencies (Recommended)

Used by esbuild, SWC, Bun - the modern standard for native npm packages.

Structure

@gopher-orch/core           # Main TypeScript SDK (no binaries)
@gopher-orch/darwin-arm64   # macOS ARM64 binaries only
@gopher-orch/darwin-x64     # macOS x64 binaries only
@gopher-orch/linux-arm64    # Linux ARM64 binaries only
@gopher-orch/linux-x64      # Linux x64 binaries only
@gopher-orch/win32-arm64    # Windows ARM64 binaries only
@gopher-orch/win32-x64      # Windows x64 binaries only

Main package.json

{
  "name": "gopher-orch",
  "optionalDependencies": {
    "@gopher-orch/darwin-arm64": "0.1.0",
    "@gopher-orch/darwin-x64": "0.1.0",
    "@gopher-orch/linux-arm64": "0.1.0",
    "@gopher-orch/linux-x64": "0.1.0",
    "@gopher-orch/win32-arm64": "0.1.0",
    "@gopher-orch/win32-x64": "0.1.0"
  }
}

How It Works

  1. npm install - npm automatically installs only the matching platform package
  2. At runtime - SDK detects platform and loads binary from the correct package
  3. User code - Same across all platforms, no platform-specific code needed

Benefits

  • Users only download binaries for their platform (~4MB vs ~24MB)
  • No postinstall scripts (works with --ignore-scripts)
  • Works with npm, yarn, pnpm
  • Fast installs
  • Works offline after initial install

Drawbacks

  • Multiple packages to publish and maintain
  • More complex release process

Approach 2: Download on Postinstall

Used by sharp, node-canvas - simpler single-package approach.

Structure

{
  "name": "gopher-orch",
  "scripts": {
    "postinstall": "node scripts/download-native.js"
  }
}

How It Works

  1. Binaries stored on GitHub releases
  2. postinstall script detects platform and downloads correct binary
  3. Binary cached locally

Benefits

  • Single npm package
  • Binaries updated without npm publish

Drawbacks

  • Network required during install
  • postinstall scripts can be disabled by users
  • Slower installs

Self-Contained Library (BUILD_BUNDLED_SHARED)

The Problem

Previously, gopher-orch required multiple library files:

  • libgopher-orch.so/dylib/dll
  • libgopher-mcp.so/dylib/dll
  • libgopher-mcp-event.so/dylib/dll
  • libfmt.so/dylib/dll

This complicated distribution and caused runtime path issues.

The Solution

Added BUILD_BUNDLED_SHARED CMake option that creates a single self-contained shared library with all dependencies statically linked inside.

# In gopher-orch/CMakeLists.txt
option(BUILD_BUNDLED_SHARED "Build self-contained shared library with all deps statically linked (for SDK distribution)" OFF)

How It Works

When BUILD_BUNDLED_SHARED=ON:

  1. Uses static versions of dependencies (gopher-mcp-static, gopher-mcp-event-static, fmt)
  2. Links with whole-archive to embed all symbols
  3. Results in single library file containing everything

Platform-specific Linking

macOS:

target_link_libraries(gopher-orch-shared PRIVATE
    -Wl,-force_load,$<TARGET_FILE:gopher-mcp-static>
    -Wl,-force_load,$<TARGET_FILE:gopher-mcp-event-static>
)

Linux:

target_link_libraries(gopher-orch-shared PRIVATE
    -Wl,--whole-archive
    gopher-mcp-static
    gopher-mcp-event-static
    -Wl,--no-whole-archive
)

Windows:

target_link_libraries(gopher-orch-shared PRIVATE
    -Wl,--whole-archive
    gopher-mcp-static
    gopher-mcp-event-static
    -Wl,--no-whole-archive
)

Result

Each platform package now contains just:

@gopher-orch/linux-x64/
├── lib/
│   └── libgopher-orch.so    # Single self-contained library
└── package.json

Library Loading in TypeScript

Current Implementation (library.ts)

import * as koffi from 'koffi';
import * as path from 'path';
import * as os from 'os';

export class GopherOrchLibrary {
  private loadLibrary(): void {
    const libraryName = this.getLibraryName();
    const searchPaths = this.getSearchPaths();

    // Try custom path from environment variable
    const envPath = process.env['GOPHER_ORCH_LIBRARY_PATH'];
    if (envPath && fs.existsSync(envPath)) {
      this.lib = koffi.load(envPath);
      // ...
    }

    // Try search paths
    for (const searchPath of searchPaths) {
      const libFile = path.join(searchPath, libraryName);
      if (fs.existsSync(libFile)) {
        this.lib = koffi.load(libFile);
        // ...
      }
    }
  }

  private getLibraryName(): string {
    switch (os.platform()) {
      case 'darwin': return 'libgopher-orch.dylib';
      case 'win32': return 'gopher-orch.dll';
      default: return 'libgopher-orch.so';
    }
  }

  private getSearchPaths(): string[] {
    const moduleDir = path.dirname(path.dirname(__dirname));
    return [
      path.join(process.cwd(), 'native', 'lib'),
      path.join(moduleDir, 'native', 'lib'),
      // ... system paths
    ];
  }
}

Updated Implementation for Optional Dependencies

private getSearchPaths(): string[] {
  const platform = os.platform();  // 'darwin', 'linux', 'win32'
  const arch = os.arch();          // 'arm64', 'x64'

  // Map to package names
  const platformMap: Record<string, string> = {
    'darwin': 'darwin',
    'linux': 'linux',
    'win32': 'win32'
  };

  const packageName = `@gopher-orch/${platformMap[platform]}-${arch}`;

  const paths = [];

  // Try to find the platform-specific package
  try {
    const packagePath = require.resolve(`${packageName}/package.json`);
    const packageDir = path.dirname(packagePath);
    paths.push(path.join(packageDir, 'lib'));
  } catch (e) {
    // Package not installed
  }

  // Fallback to local native/lib
  paths.push(path.join(process.cwd(), 'native', 'lib'));

  return paths;
}

gopher-orch Release Artifacts

The gopher-orch GitHub releases now provide:

Artifact Platform Contents
libgopher-orch-linux-x64.tar.gz Linux x64 libgopher-orch.so, headers, verify_orch
libgopher-orch-linux-arm64.tar.gz Linux ARM64 libgopher-orch.so, headers, verify_orch
libgopher-orch-macos-x64.tar.gz macOS x64 libgopher-orch.dylib, headers, verify_orch
libgopher-orch-macos-arm64.tar.gz macOS ARM64 libgopher-orch.dylib, headers, verify_orch
libgopher-orch-windows-x64.zip Windows x64 gopher-orch.dll, headers
libgopher-orch-windows-arm64.zip Windows ARM64 gopher-orch.dll, headers

All libraries are self-contained with gopher-mcp, gopher-mcp-event, and fmt statically linked.

User Experience

For SDK Users

// Same code works on all platforms
import { GopherAgent, GopherAgentConfig } from 'gopher-orch';

const agent = GopherAgent.create(
  GopherAgentConfig.builder()
    .provider('AnthropicProvider')
    .model('claude-3-haiku-20240307')
    .apiKey('your-api-key')
    .build()
);

const answer = agent.run('What time is it in Tokyo?');
console.log(answer);

agent.dispose();

For SDK Developers

# Development (builds from source)
./build.sh

# Testing specific platform
GOPHER_ORCH_LIBRARY_PATH=/path/to/libgopher-orch.so npm test

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions