NCP Auth for Agents

This page is written for coding agents implementing NCP integrations in other apps.

Mechanical Recipe

  1. Build a valid NCP trigger object.
  2. Serialize it once: rawBody = JSON.stringify(trigger).
  3. Use that exact raw body as the HTTP request body.
  4. Create an ISO timestamp.
  5. Sign timestamp + "." + rawBody with HMAC-SHA256 using the NCP connection token.
  6. Send Authorization, X-NCP-Timestamp, X-NCP-Signature, and X-NCP-Event-Id.

Rules

Node / TypeScript

import { createHmac, randomUUID } from "node:crypto";

function base64url(buffer: Buffer) {
  return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}

export function signNcpRequest(token: string, rawBody: string, timestamp = new Date().toISOString()) {
  const signature = base64url(createHmac("sha256", token).update(`${timestamp}.${rawBody}`).digest());

  return {
    timestamp,
    signature: `sha256=${signature}`
  };
}

export async function sendNcpTrigger(input: {
  endpoint: string;
  token: string;
  trigger: unknown;
  eventId?: string;
}) {
  const rawBody = JSON.stringify(input.trigger);
  const { signature, timestamp } = signNcpRequest(input.token, rawBody);
  const eventId = input.eventId ?? randomUUID();

  const response = await fetch(input.endpoint, {
    method: "POST",
    headers: {
      "authorization": `Bearer ${input.token}`,
      "content-type": "application/json",
      "x-ncp-event-id": eventId,
      "x-ncp-signature": signature,
      "x-ncp-timestamp": timestamp
    },
    body: rawBody
  });

  if (response.status === 409) return { duplicate: true, ok: true };
  if (response.status === 401) throw new Error("NCP token rejected. Ask the user to reconnect NerveOS System.");
  if (response.status === 403) throw new Error("NCP token scope does not allow this source or event type.");
  if (!response.ok) throw new Error(`NCP request failed: ${response.status}`);

  return response.json();
}

Python

import base64
import hashlib
import hmac
import json
import uuid
from datetime import datetime, timezone

import requests

def base64url(raw: bytes) -> str:
    return base64.urlsafe_b64encode(raw).decode("utf-8").rstrip("=")

def sign_ncp_request(token: str, raw_body: str, timestamp: str | None = None):
    timestamp = timestamp or datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
    digest = hmac.new(token.encode("utf-8"), f"{timestamp}.{raw_body}".encode("utf-8"), hashlib.sha256).digest()
    return timestamp, f"sha256={base64url(digest)}"

def send_ncp_trigger(endpoint: str, token: str, trigger: dict, event_id: str | None = None):
    raw_body = json.dumps(trigger, separators=(",", ":"))
    timestamp, signature = sign_ncp_request(token, raw_body)
    event_id = event_id or str(uuid.uuid4())

    response = requests.post(
        endpoint,
        data=raw_body,
        headers={
            "authorization": f"Bearer {token}",
            "content-type": "application/json",
            "x-ncp-event-id": event_id,
            "x-ncp-signature": signature,
            "x-ncp-timestamp": timestamp,
        },
        timeout=10,
    )

    if response.status_code == 409:
        return {"ok": True, "duplicate": True}
    if response.status_code == 401:
        raise RuntimeError("NCP token rejected. Ask the user to reconnect NerveOS System.")
    if response.status_code == 403:
        raise RuntimeError("NCP token scope does not allow this source or event type.")
    response.raise_for_status()
    return response.json()

SummitOS Agent Prompt

You are adding NCP Auth v0.1 support to SummitOS.

Implement a per-expedition NerveOS System integration.

UI:
- In each expedition, add "Enable NerveOS System".
- Store NerveSynapse endpoint and NCP connection token server-side only.
- Let the user choose which expedition signals can fire.

Allowed event types for a mountain expedition:
- expedition.weather_window
- expedition.readiness_changed
- expedition.permit_deadline
- expedition.route_condition_warning
- expedition.partner_availability_conflict

Auth:
- POST valid NCP trigger JSON to the user's NerveSynapse endpoint.
- Use Authorization: Bearer ncp_live_...
- Sign the exact raw JSON body sent on the wire.
- X-NCP-Signature = sha256=base64url(hmac_sha256(token, timestamp + "." + rawBody))
- X-NCP-Timestamp = ISO timestamp
- X-NCP-Event-Id = stable source event ID

Error handling:
- 2xx: mark delivered.
- 409 duplicate: treat as already delivered.
- 401: disable integration and ask user to reconnect.
- 403: show permission/scope mismatch.
- 429 or 5xx: retry with backoff using the same X-NCP-Event-Id.

Privacy:
- Do not include private messages, raw GPS traces, health data, payment data, or secrets in the payload.
- Use context_refs to link back to SummitOS expedition details.