Skip to content

Publish a TypeScript SDK so other products can embed Kelpie cases #104

Description

@jusso-dev

Publish a typed TypeScript SDK from this repo so another product (first: Tawny-SOC) can create, read, and show a Kelpie case without copying fetch or iframing /cases/[id].

Do not rewrite Kelpie. Do not add a fifth case store. The SDK wraps the live /api/v1 contract in docs/api.md and the Tawny push rules in docs/integrations/tawny.md.

Why

Tawny-SOC already talks to Kelpie from app/api/soc/actions/route.ts with untyped fetch and a Record. That client is local to Tawny-SOC. It is not this SDK.

The live Tawny-SOC handoff also does not match this repo's contract: it posts to /api/v1/alerts with externalRef / source: "Tawny-SOC" and PUTs cases with externalRef: tawny-case-*. Kelpie's Tawny guide wants POST /api/v1/cases with sourceSystem: "tawny", sourceReference, sourceUrl. Org always comes from the klp_* token, never the body. Retries are only safe when both source fields are present.

Kelpie already has MCP at /api/mcp for agents. This ticket is a library for product code, not another MCP.

What to ship

A package in this repo (for example packages/sdk or @kelpie/sdk) that Tawny-SOC, Bower-adjacent tools, and anything else can depend on.

import { createKelpieClient } from "@kelpie/sdk";

const kelpie = createKelpieClient({
  baseUrl: process.env.KELPIE_URL,
  token: process.env.KELPIE_TOKEN, // klp_...
});

const created = await kelpie.cases.create({
  title: "Suspicious PowerShell on WEB-03",
  summary: "Host WEB-03, user jsmith",
  severity: "high",
  classification: "malware",
  tlp: "amber",
  sourceSystem: "tawny",
  sourceReference: "tawny_alert_7f3a9c21",
  sourceUrl: "https://tawny.example.com/alerts/7f3a9c21",
});
// 201 created: true | 200 created: false, same id / caseNumber

Client rules

  • Bearer klp_* only. Empty scopes already grant nothing. Do not send organisationId in any body.
  • Typed errors for 400 / 401 / 403 / 404 / 409 / 422, including created: false on idempotent create and closure_requirements_not_met.
  • Compartment behaviour stays server-side. The SDK must not invent a second ACL. A 404 for a hidden case stays a 404.
  • No browser cookie auth. Embedders use a server-side token.

Phase 1 methods (enough to embed a case pane)

These are the ones Tawny-SOC needs for Send / Promote / Open and a KelpieCasePane (number, status, assignee, last comment, SLA, link out):

  • cases.createPOST /api/v1/cases with optional sourceSystem / sourceReference / sourceUrl
  • cases.getGET /api/v1/cases/{id}
  • cases.listGET /api/v1/cases (status, severity, source=tawny, limit)
  • cases.updatePATCH /api/v1/cases/{id} (not status closed)
  • comments.list / comments.create on a case
  • observables.list / observables.create

Export a small read model for the pane: { id, caseNumber, status, assignee, severity, lastComment, sla, sourceUrl, kelpieUrl }.

Phase 2 (same package, later)

Wrap the rest of docs/api.md as needed: tasks, close/reopen, alerts, evidence, relationships, graph, ATT&CK. Do not block phase 1 on that.

Publish

  • Workspace package with generated or hand-kept types that match docs/api.md.
  • README section: install, token scopes, Tawny create example, idempotency.
  • Tests against the existing Tawny ingest scripts (scripts/test-tawny-api.ts, scripts/test-tawny-ingest.ts) so create/replay still returns 201 then 200.
  • Version independently if you publish to npm. First consumer can path-depend from git.

Embed, not iframe

Kelpie's /cases/[id] is a full IR app (SLA, queues, watchers, attack story, graph). Products that embed Kelpie should use this SDK and their own pane. Do not iframe Kelpie.

Tawny-SOC work (separate ticket on that repo): point Send / Promote at cases.create with sourceSystem: "tawny", drop the invented /api/v1/alerts send and the PUT. Hide Send once kelpieCaseId is set.

Token guidance for embedders

From docs/integrations/tawny.md:

  • Push-only producer: cases:write only.
  • Pane that reads a case: add cases:read and comments:read.
  • Do not grant cases:override_closure, audit:read, alerts:raw_payload:read, reports:admin, or reviews:admin to a product embed token.

Out of scope

  • Changing Kelpie's REST or MCP.
  • Merging Kelpie into Tawny-SOC.
  • A Python or Go client in this ticket.
  • Fixing Tawny-SOC's wire (that change lives in Tawny-SOC; it depends on this package).

Done when

  • @kelpie/sdk (name can flex) lives in this repo and can cases.create / get / list plus comments and observables against a running Kelpie.
  • A replay with the same sourceSystem + sourceReference returns the existing case (created: false), never a duplicate.
  • Types reject a reserved sourceSystem and a sourceReference without sourceSystem.
  • Docs show how Tawny-SOC (or any embedder) constructs the client and the pane fields.
  • No Clerk, no extra auth vendor. klp_* only.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions