# Technology Stack

**Project:** Screen Timelapse MCP Server
**Researched:** 2026-04-12
**Scope:** v1.1 Native DWM Window Capture -- stack ADDITIONS only

## Existing Stack (validated, DO NOT change)

Node.js 20, TypeScript 5.5+, @modelcontextprotocol/sdk ^1.29.0, node-screenshots ^0.2.8, sharp ^0.34.5, @skyra/gifenc ^1.0.1, zod ^3.25.0, tsup ^8.0.0, tsx ^4.0.0.

---

## Capture API Decision: Windows.Graphics.Capture (not DwmGetDxSharedSurface)

**Recommendation: Use Windows.Graphics.Capture API via C++/WinRT.**

| Criterion | Windows.Graphics.Capture | DwmGetDxSharedSurface |
|-----------|--------------------------|----------------------|
| API Status | Official, documented, Microsoft-supported | Undocumented, exported from user32.dll |
| Stability | Stable, part of WinRT | Can break in any Windows update |
| Window types | All windows (GDI, DirectX, UWP) | DirectX-based windows only; GDI windows return blank |
| Border indicator | Yellow border by default; removable on Win11 via IsBorderRequired=false | No border |
| Min Windows version | Windows 10 1903+ | Vista+ (but undocumented) |
| Programmatic use | Yes, via IGraphicsCaptureItemInterop::CreateForWindow(HWND) | Yes, via GetProcAddress on user32 |
| Pixel data access | Via D3D11 texture mapping, then memcpy to CPU buffer | Via D3D shared surface, then texture copy |
| Documentation/samples | Excellent (Microsoft samples on GitHub) | Minimal (one OBS plugin, scattered blog posts) |

**Why Windows.Graphics.Capture wins:**
1. DwmGetDxSharedSurface is undocumented -- it only works for DirectX windows and returns blank for GDI apps. This is a dealbreaker for a general-purpose screenshot tool.
2. Windows.Graphics.Capture is the official Microsoft replacement for PrintWindow/BitBlt capture. It reads from DWM composition, meaning it captures the window as-composited (ignoring overlapping windows).
3. The yellow border can be disabled on Windows 11 via `GraphicsCaptureSession.IsBorderRequired = false`. On Windows 10, the border remains but is cosmetic (not in captured pixels).
4. Programmatic capture without user picker is supported via `IGraphicsCaptureItemInterop::CreateForWindow(hwnd)`.

**Confidence: HIGH** -- Official Microsoft API with extensive documentation and samples.

---

## New Dependencies for Native C++ Addon

### Build Toolchain

| Technology | Version | Purpose | Why | Confidence |
|------------|---------|---------|-----|------------|
| node-addon-api | ^8.7.0 | C++ wrapper for Node-API | ABI-stable across Node.js versions; C++ convenience over raw C napi; actively maintained (8.7.0 published April 2026) | HIGH |
| cmake-js | ^8.0.0 | Native addon build tool | C++/WinRT requires /std:c++17 and /await MSVC flags; CMake handles these naturally via target_compile_features. node-gyp's GYP format makes C++17 and WinRT flags painful to configure. cmake-js is the right tool when you need fine-grained compiler control. | HIGH |
| node-gyp-build | ^4.8.4 | Runtime binary loader | Loads the correct prebuilt .node binary at runtime; works with prebuild for distribution | HIGH |

**Why cmake-js over node-gyp:**
- C++/WinRT requires `/std:c++17` and `/await` MSVC compiler flags. node-gyp uses GYP format where setting C++17 is a known pain point (multiple GitHub issues about ignored cflags).
- CMake natively supports `target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)` and custom MSVC flags via `target_compile_options`.
- Linking Windows SDK libs (d3d11, dwmapi, windowsapp) is straightforward in CMake: `target_link_libraries(${PROJECT_NAME} PRIVATE d3d11 dwmapi windowsapp)`.
- CMake is the standard build system for C++ projects that use Windows SDK and WinRT.

**Why node-addon-api over nan:**
- nan (Native Abstractions for Node.js) requires recompilation for each Node.js major version. node-addon-api uses Node-API which is ABI-stable -- a binary built for Node-API version 6 works on Node 18, 20, 22+ without recompilation.
- node-screenshots already uses NAPI (it's a Rust addon using napi-rs), so this is consistent with the existing project.

### Prebuilt Binary Distribution

| Technology | Version | Purpose | Why | Confidence |
|------------|---------|---------|-----|------------|
| prebuild | ^13.0.0 | Build prebuilt binaries | Unlike prebuildify, prebuild supports cmake-js as a backend. Generates platform-specific .node binaries. | HIGH |
| prebuild-install | ^7.1.0 | Download prebuilt binaries at install time | Pairs with prebuild; downloads correct binary from GitHub releases during npm install; falls back to source build if no prebuild found | HIGH |

**Distribution strategy:**
1. CI (GitHub Actions) runs `prebuild --backend cmake-js` on Windows x64 runner to produce prebuilt `.node` binary.
2. Prebuilt binaries are attached to GitHub Releases.
3. On `npm install`, `prebuild-install` checks GitHub Releases for a matching binary. If found, downloads it (fast install, no build tools needed). If not found, falls back to cmake-js source compilation (requires MSVC + CMake + Windows SDK).
4. At runtime, `node-gyp-build` loads the correct `.node` file from the `prebuilds/` directory.

**Why prebuild over prebuildify:**
- prebuildify does NOT support cmake-js -- it only works with node-gyp. Since we need cmake-js for C++/WinRT, prebuild is the only viable option.
- prebuild downloads binaries from GitHub Releases (one HTTP request) vs prebuildify bundles all platform binaries in the npm tarball. Since this addon is Windows-only, the download approach is fine.

### Windows SDK / C++/WinRT Headers

| Technology | Version | Purpose | Why | Confidence |
|------------|---------|---------|-----|------------|
| Microsoft.Windows.CppWinRT | ^2.0.250303.1 | C++/WinRT projection headers | Header-only C++17 library that projects WinRT APIs into standard C++. Installed via NuGet or the cppwinrt.exe tool. Required for Windows.Graphics.Capture. | HIGH |
| Windows SDK | 10.0.19041+ | Windows API headers and libs | Provides d3d11.h, dwmapi.h, windows.graphics.capture.interop.h, DXGI headers. Installed with Visual Studio Build Tools. | HIGH |

**Required Windows SDK headers:**
- `winrt/Windows.Graphics.Capture.h` -- GraphicsCaptureItem, CaptureSession, FramePool
- `windows.graphics.capture.interop.h` -- IGraphicsCaptureItemInterop (CreateForWindow)
- `d3d11.h` -- D3D11 device creation, texture mapping for pixel data extraction
- `dxgi1_2.h` -- DXGI formats, surface descriptions
- `dwmapi.h` -- DwmGetWindowAttribute (for checking if DWM composition is active)
- `winrt/Windows.Foundation.h` -- WinRT base types

**Required link libraries:**
- `d3d11.lib` -- Direct3D 11
- `dxgi.lib` -- DXGI
- `dwmapi.lib` -- Desktop Window Manager
- `windowsapp.lib` -- WinRT runtime (required for C++/WinRT)

### Build Prerequisites (developer machine)

| Prerequisite | Why | Install |
|-------------|-----|---------|
| Visual Studio Build Tools 2022 | MSVC compiler with C++17 support, Windows SDK | `winget install Microsoft.VisualStudio.2022.BuildTools` |
| CMake 3.20+ | cmake-js requires CMake installed on PATH | Bundled with VS Build Tools, or `winget install Kitware.CMake` |
| Windows SDK 10.0.19041+ | Headers for WinRT, D3D11, DXGI | Installed as VS Build Tools component |

**End-user install:** If prebuilt binary exists on GitHub Releases, none of the above is needed. `prebuild-install` downloads the binary. Only developers building from source need the full toolchain.

---

## CMakeLists.txt Skeleton

```cmake
cmake_minimum_required(VERSION 3.20)
project(dwm_capture)

# C++17 required for C++/WinRT
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# MSVC-specific: enable coroutine support for WinRT async
if(MSVC)
  add_compile_options(/await)
endif()

# Node-API / node-addon-api
add_definitions(-DNAPI_VERSION=8)
include_directories(${CMAKE_JS_INC})

# Get node-addon-api include path
execute_process(
  COMMAND node -p "require('node-addon-api').include"
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  OUTPUT_VARIABLE NODE_ADDON_API_DIR
  OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
include_directories(${NODE_ADDON_API_DIR})

# Source files
file(GLOB SOURCE_FILES "src/native/*.cpp")
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")

# Windows SDK libraries
target_link_libraries(${PROJECT_NAME} PRIVATE
  ${CMAKE_JS_LIB}
  d3d11
  dxgi
  dwmapi
  windowsapp
)

# Generate node.lib (required on Windows for MSVC)
if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET)
  execute_process(
    COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF}
            /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}
  )
endif()
```

## binding.gyp Alternative (NOT recommended)

For reference only -- if cmake-js were not available, a node-gyp binding.gyp would look like this. However, C++17 and `/await` flags are unreliable in GYP format, so this path is NOT recommended:

```json
{
  "targets": [{
    "target_name": "dwm_capture",
    "sources": ["src/native/capture.cpp"],
    "include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
    "defines": ["NAPI_VERSION=8"],
    "conditions": [
      ["OS=='win'", {
        "msvs_settings": {
          "VCCLCompilerTool": {
            "AdditionalOptions": ["/std:c++17", "/await"]
          }
        },
        "libraries": ["d3d11.lib", "dxgi.lib", "dwmapi.lib", "windowsapp.lib"]
      }]
    ]
  }]
}
```

---

## Package.json Additions

```json
{
  "scripts": {
    "install": "prebuild-install -r napi || cmake-js compile",
    "prebuild": "prebuild --backend cmake-js -r napi -t 8",
    "build:native": "cmake-js compile"
  },
  "dependencies": {
    "node-addon-api": "^8.7.0",
    "node-gyp-build": "^4.8.4",
    "prebuild-install": "^7.1.0"
  },
  "devDependencies": {
    "cmake-js": "^8.0.0",
    "prebuild": "^13.0.0"
  }
}
```

---

## Integration with Existing node-screenshots

**node-screenshots remains the primary capture library.** The new native addon is a supplementary capture method used only when:
1. Platform is Windows 10 1903+ with DWM composition enabled
2. Target is a specific window (not full desktop, not region-only)
3. The window needs flicker-free capture that ignores overlapping windows

**Fallback chain:**
1. Try Windows.Graphics.Capture native addon (if loaded and platform supports it)
2. Fall back to node-screenshots `window.captureImage()` (PrintWindow-based)
3. Fall back to node-screenshots monitor capture + crop (always works)

**Loading pattern:**
```typescript
let nativeCapture: NativeCaptureModule | null = null;
try {
  // node-gyp-build finds the correct .node binary
  nativeCapture = require('node-gyp-build')(__dirname);
} catch {
  // Native addon not available (non-Windows, missing binary)
  // Silently fall back to node-screenshots
}
```

---

## What NOT to Add

| Technology | Why Not |
|------------|---------|
| nan (Native Abstractions for Node.js) | Requires recompilation per Node.js major version. node-addon-api + Node-API is ABI-stable. |
| prebuildify | Does not support cmake-js backend. Only works with node-gyp. |
| node-pre-gyp | Older tool, largely superseded by prebuild/prebuildify ecosystem. |
| @nodert-win10-20h1/windows.graphics.capture | Auto-generated NodeRT bindings; unmaintained, pinned to specific Windows SDK version, heavy dependency. Better to write a focused native addon. |
| DwmGetDxSharedSurface direct | Undocumented API; only captures DirectX windows (blank for GDI apps); can break in any Windows update. |
| DXGI OutputDuplication | Captures entire monitor output, not individual windows. Would need window rect clipping which reintroduces the "overlapping window" problem. |
| electron/screenshots or similar | Pulls in Chromium. Absurd for a CLI MCP server. |

---

## Alternatives Considered (New Additions Only)

| Category | Recommended | Alternative | Why Not |
|----------|-------------|-------------|---------|
| Capture API | Windows.Graphics.Capture | DwmGetDxSharedSurface | Undocumented; blank for GDI windows; stability risk |
| Capture API | Windows.Graphics.Capture | DXGI OutputDuplication | Monitor-level only, not per-window |
| Capture API | Windows.Graphics.Capture | BitBlt/GDI | Cannot capture hardware-accelerated content |
| NAPI wrapper | node-addon-api | nan | No ABI stability; recompile per Node version |
| Build system | cmake-js | node-gyp | C++17 and /await flags unreliable in GYP format |
| Prebuild dist | prebuild + prebuild-install | prebuildify | prebuildify does not support cmake-js |
| Prebuild dist | prebuild + prebuild-install | node-pre-gyp | Older tool, less maintained ecosystem |
| WinRT bindings | Custom C++/WinRT addon | @nodert-win10-20h1/* | Unmaintained auto-generated bindings, SDK version locked |

---

## Sources

- [Node-API official docs](https://nodejs.org/api/n-api.html) - ABI stability guarantees, version matrix
- [node-addon-api on npm](https://www.npmjs.com/package/node-addon-api) - v8.7.0, C++ wrapper for Node-API
- [cmake-js on GitHub](https://github.com/cmake-js/cmake-js) - v8.0.0, CMake-based native addon build tool
- [node-addon-api cmake-js docs](https://github.com/nodejs/node-addon-api/blob/main/doc/cmake-js.md) - Official integration guide
- [prebuildify on GitHub](https://github.com/prebuild/prebuildify) - Prebuild tool (node-gyp only)
- [prebuild cmake-js issue](https://github.com/prebuild/prebuildify/issues/49) - Confirms prebuildify does not support cmake-js
- [cmake-js prebuild support](https://github.com/cmake-js/cmake-js/issues/206) - prebuild --backend cmake-js is supported
- [node-gyp-build on npm](https://www.npmjs.com/package/node-gyp-build) - v4.8.4, binary loader
- [Windows.Graphics.Capture docs](https://learn.microsoft.com/en-us/uwp/api/windows.graphics.capture?view=winrt-26100) - Official WinRT capture API
- [IGraphicsCaptureItemInterop::CreateForWindow](https://learn.microsoft.com/en-us/windows/win32/api/windows.graphics.capture.interop/nf-windows-graphics-capture-interop-igraphicscaptureiteminterop-createforwindow) - Programmatic capture without picker
- [GraphicsCaptureSession.IsBorderRequired](https://learn.microsoft.com/en-us/uwp/api/windows.graphics.capture.graphicscapturesession.isborderrequired?view=winrt-26100) - Disable yellow border on Win11
- [Win32CaptureSample on GitHub](https://github.com/robmikh/Win32CaptureSample) - Microsoft reference implementation
- [Microsoft.Windows.CppWinRT NuGet](https://www.nuget.org/packages/Microsoft.Windows.CppWinRT/) - C++/WinRT projection headers
- [DWMCapture OBS plugin](https://github.com/notr1ch/DWMCapture/blob/master/DWMCaptureSource.cpp) - DwmGetDxSharedSurface reference (rejected approach)
- [DwmGetDxSharedSurface undocumented](https://undoc.airesoft.co.uk/user32.dll/DwmGetDxSharedSurface.php) - Documents the undocumented API risks
- [node-gyp C++17 issues](https://github.com/nodejs/node-gyp/issues/1662) - Why node-gyp is problematic for C++17
- [OBS BitBlt vs WGC comparison](https://obsproject.com/forum/threads/for-capture-method-whats-the-difference-between-bitblt-and-windows-graphics-capture.127687/) - Capture method comparison
