Skip to content
docs
UseBuildChangelog
Open Lumin

Examples

Recipes

Patterns, not whole apps. Every worked example in this repo uses all five of these. Copy the one you need.

1. Deny-by-default tool allowlisting

The server sees 204 tools. Every example allows a fixed, named subset and nothing else, by passing default_config: { enabled: false } and enabling only the tools in the request. This is not documentation, it is enforcement: the model cannot call a tool that is not in configs, no matter what the prompt says or what a user tries to talk it into.

typescript
const ALLOWED_TOOLS = [
  "set_birth_profile",
  "get_ayurvedic_constitution",
  "get_shadbala",
  // ...the rest of what THIS feature needs, and nothing else
] as const;

const configs: Record<string, { enabled: true }> = {};
for (const tool of ALLOWED_TOOLS) configs[tool] = { enabled: true };

tools: [
  {
    type: "mcp_toolset",
    mcp_server_name: "lumin",
    default_config: { enabled: false }, // deny by default
    configs,
  },
],

Two reasons this matters beyond security:

  • It bounds the metered cost. A feature with a 9-tool allowlist cannot accidentally run a 40-call reading because the model decided to be thorough.
  • It documents the feature. The array of tool names is a more accurate description of what a feature actually reads than any paragraph of prose, and it cannot drift out of date the way a comment can.

2. Reading a paged tool to exhaustion

Eight tools page: get_weather_windows, get_health_transit_alerts, get_transit_crossings, analyze_natal_promise, get_ephemeris, get_rp_interval, find_birth_time and get_election_catalog. None of them was capped for a reason your product gets to decide; a 3-month get_transit_crossings scan returns over a thousand rows, and every one of them is a KP claim nothing else can produce.

typescript
async function readToExhaustion<T>(
  callPage: (page: number) => Promise<{
    rows: T[];
    pagination: { page: number; totalItems: number; pageNote?: string };
  }>,
): Promise<T[]> {
  const all: T[] = [];
  let page = 1;
  for (;;) {
    const { rows, pagination } = await callPage(page);
    all.push(...rows);
    if (all.length >= pagination.totalItems) break;
    page += 1;
  }
  return all;
}

A page is a unit of thinking, not a payload optimisation

The point of paging is that each slice gets a real reasoning pass, not that the total response is smaller. Framing paging, narrowing or a composite tool as ways to finish sooner inverts the design. More calls and more elapsed time reading each page is the intent, not a cost to be minimised.

3. The composite-versus-expanded call-count tradeoff

Calls are topped up in any amount, starting at 1 USD for 400 tool calls. The eight run_* composites chain several engine calls into one, so the choice between a composite and its expanded form is a real product decision, not a style preference. More calls means more readings, and deeper ones.

PatternMetered callsWhat you getWhen to use it
Composite1 (e.g. run_kundli_match_complete)One blended, synthesized resultYour UI shows one verdict and does not need the intermediate results, or your budget is tight
Expanded3 to 10 (e.g. get_ashta_koota_milan, check_compatibility, get_compatibility_advanced)Each system's own verdict, independently inspectableYour UI shows disagreement between systems as a feature, the way kundli-match does

Neither is wrong. Pick the composite when your UI collapses the result anyway, and the expanded chain when the disagreement between systems is the product. The expanded chain costs more calls and returns more to reason over.

4. Rendering a cross-system chip

70 of 201 tools are not orthodox KP: 46 Parashari, 15 KP-extended, 6 Jaimini, 2 Tajik, 1 Lumin extension. Call get_tool_catalog with a tool filter (or read it once and cache the map) to get each tool's system field, and render it beside any panel built from that tool's output. Presenting a Parashari or Jaimini finding as a KP verdict is a methodology error that reads as thoroughness.

tsx
const SYSTEM_LABEL: Record<string, string> = {
  kp: "KP",
  "kp-extended": "KP-extended",
  "vedic-parashari": "Vedic Parashari",
  jaimini: "Jaimini",
  tajik: "Tajik",
  "lumin-extension": "Lumin extension",
};

function SystemChip({ system }: { system: string }) {
  const label = SYSTEM_LABEL[system] ?? system;
  const isKp = system === "kp";
  return (
    <span
      className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ${
        isKp
          ? "bg-primary/10 text-primary ring-primary/20"
          : "bg-muted text-muted-foreground ring-black/10"
      }`}
    >
      {label}
    </span>
  );
}

// e.g. next to a get_health_organ_panel card:
<SystemChip system="vedic-parashari" />

Showing the tradition an answer came from is part of reading it correctly. A Parashari result and a KP result answer the same question by different methods, and a product that merges them silently is stating something neither system said.

5. The chart-integrity gate as a confidence pill

run_pre_verdict_audit bundles the sub-lord boundary check, combustion, planetary war and vargottama strength into one confidence band (HIGH, MODERATE, LOW) and a numeric modifier. get_boundary_warnings alone gives the finer-grained signal: a CRITICAL flag means a birth time or ayanamsa correction of a few arc-minutes could flip a sub lord and invert a verdict. Render either as a small pill next to the result it qualifies, not buried in a tooltip.

tsx
const BAND_STYLE: Record<string, string> = {
  high: "bg-emerald-50 text-emerald-700 ring-emerald-200",
  moderate: "bg-amber-50 text-amber-700 ring-amber-200",
  low: "bg-rose-50 text-rose-700 ring-rose-200",
};

function ConfidencePill({ band, summary }: { band: string; summary: string }) {
  return (
    <span
      title={summary}
      className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ${
        BAND_STYLE[band] ?? BAND_STYLE.moderate
      }`}
    >
      Chart confidence: {band}
    </span>
  );
}

health-risk-analyzer and kundli-match both render this pill, in the second case once per person, so a low-confidence chart on either side of a match is visible before the compatibility read is trusted.

Where to next