Guide
Integrate into your app
Two shapes put Lumin inside another product. A conversational surface where users type questions, and a button-driven feature where your UI sends a fixed prompt and renders structured data. Both run against the same MCP endpoint with the same API key.
When to use this
tools/call request directly, same endpoint, same API key, no LLM in the loop. This guide is for the case where a model needs to pick tools and reason across several of them.The two shapes
Pick the shape based on whether the user composes the question or your product does.
| Shape | User surface | Prompt source | Model output | Best for |
|---|---|---|---|---|
| Chat | Text input, message thread | Typed by the user | Free-form prose | Coaching, exploration, "ask your chart" |
| Button | Buttons, forms, dashboards | Hardcoded by your app | Structured JSON | Forecasts, embedded widgets, scheduled reports |
Both shapes share infrastructure. You can ship them in the same product, against the same Lumin API key, metered against the same monthly allowance.
The architecture
+----------------+
| Your frontend | user clicks a button or types a question
+--------+-------+
|
v
+----------------+
| Your backend | runs an LLM SDK with your model key
+--------+-------+
| mcp_servers: { url: mcp.lumin.guru/mcp, token: your Lumin key }
v
+----------------+
| Lumin MCP | 221 tools the model can call as needed
+----------------+What you bring
- Your model provider key. The model bill is yours.
- Your prompt design. The system prompt steers tool selection.
- Your UI. Lumin returns data; your product renders it.
- Your domain data. Combine the chart with the user's history, holdings, calendar.
What Lumin brings
- 218 engine-backed tools: chart, dashas, transits, significators, timing, plus domain-specific analysis and 8 composite workflows.
- The reading protocol. set_birth_profile returns the tool list and the call floor for each question, so the model does not have to remember the methodology.
- The math. Each call returns the components used to derive it.
- Auth and metering. Your Lumin API key counts each successful tool call against its monthly allowance.
Pattern 1: chat surface
Send the user's question to the model with Lumin's MCP server attached. The model picks tools from Lumin's catalog as it reasons. You stream the model's text back to your UI.
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const stream = anthropic.beta.messages.stream({
model: "claude-opus-5",
max_tokens: 16000,
thinking: { type: "adaptive" },
output_config: { effort: "xhigh" },
mcp_servers: [
{
type: "url",
url: "https://mcp.lumin.guru/mcp",
name: "lumin",
authorization_token: process.env.LUMIN_API_KEY,
},
],
// The model can only reach tools named here. Everything else on the
// server, all 221 tools, stays unreachable from this call.
tools: [
{
type: "mcp_toolset",
mcp_server_name: "lumin",
default_config: { enabled: false },
configs: {
set_birth_profile: { enabled: true },
get_full_chart: { enabled: true },
get_smart_current_dasha: { enabled: true },
get_financial_analysis: { enabled: true },
get_transit_advanced: { enabled: true },
},
},
],
messages: [
{
role: "user",
content:
"I was born 1992-08-14 04:32 in Colombo (6.927, 79.861, +330). " +
"What dasha am I in and what does it suggest about money this month?",
},
],
betas: ["mcp-client-2025-11-20"],
});
const response = await stream.finalMessage();The model calls set_birth_profile first, passing the user's question. That returns the numbered tool list for a money question and the floor that applies, so the model works down the plan: get_full_chart, get_smart_current_dasha, get_financial_analysis, get_transit_advanced and the rest, then weaves the results into narrative text. Your UI renders the text in a chat bubble.
Allowlist the tools the feature actually uses
default_config: { enabled: false } plus an explicit configs allowlist is the right default for a fixed feature, not just this one: it bounds what the call can cost, bounds the blast radius if the model is ever coaxed into calling something it should not, and doubles as documentation of exactly what this feature reads. A real money question routes to more than five tools, list the full working set from the reading plan rather than the shortened one above.
Raise your tool-call cap, and budget max_tokens for the whole turn
A life-area question has a floor of 20 tool calls and typically spends 25 to 40. An agent loop capped at 8 or 10 will stop mid-chart and answer from a fraction of it. max_tokens is a whole-turn budget: every tool-use block and every tool result the model reads counts against it alongside the prose, so 4,096 will not survive a real reading and 16,000 or higher is a safer floor. Reading depth and call floors has the tiers and the budget arithmetic.
Handle pause_turn
A long tool-calling turn can come back with stop_reason: "pause_turn" before the model is actually done, meaning the turn ran long and the API is handing control back rather than dropping work. The fix is to append response.content to your message list and call again with the same request, unmodified. Treating pause_turn as a finished answer is the single most common integration bug against this API: the reading looks truncated because, from the model's side, it was.
Pattern 2: button-driven feature
Same SDK call, but the prompt is fixed by your app and the system instruction asks for JSON. You parse the JSON and render it in your own components, with no chat surface visible to the user.
Worked example: a spending forecaster button inside an expense tracker.
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic();
const SYSTEM = `You forecast personal spending risk using KP astrology.
Use the Lumin tools to compute the user's current dasha, antardasha, and
upcoming financial transits for the requested window. Identify favourable
and cautious days.
Return ONE JSON object matching this schema. No prose, no markdown:
{
"favorable_windows": [
{ "start": "YYYY-MM-DD", "end": "YYYY-MM-DD", "score": 0.0, "reason": string }
],
"cautious_windows": [
{ "start": "YYYY-MM-DD", "end": "YYYY-MM-DD", "score": 0.0, "reason": string }
],
"summary": string
}`;
const TOOLSET = {
type: "mcp_toolset" as const,
mcp_server_name: "lumin",
default_config: { enabled: false },
configs: {
set_birth_profile: { enabled: true },
get_smart_current_dasha: { enabled: true },
get_financial_analysis: { enabled: true },
get_transit_advanced: { enabled: true },
},
};
export async function forecastSpending(profile: BirthProfile, days = 30) {
// Derived from the SDK call itself rather than a hand-named import, so
// this keeps compiling if the exported type name ever changes.
type StreamParams = Parameters<typeof anthropic.beta.messages.stream>[0];
let messages: StreamParams["messages"] = [
{
role: "user",
content: JSON.stringify({
birth_datetime: profile.birthDatetime,
latitude: profile.latitude,
longitude: profile.longitude,
utc_offset_minutes: profile.utcOffsetMinutes,
window_days: days,
}),
},
];
for (;;) {
const stream = anthropic.beta.messages.stream({
model: "claude-sonnet-5",
max_tokens: 16000,
system: SYSTEM,
thinking: { type: "adaptive" },
output_config: { effort: "xhigh" },
mcp_servers: [
{
type: "url",
url: "https://mcp.lumin.guru/mcp",
name: "lumin",
authorization_token: process.env.LUMIN_API_KEY,
},
],
tools: [TOOLSET],
messages,
betas: ["mcp-client-2025-11-20"],
});
const response = await stream.finalMessage();
// A long tool-calling turn can pause before the model is actually
// done. Append what came back and call again with the same request,
// rather than treating a pause as a finished answer.
if (response.stop_reason === "pause_turn") {
messages = [...messages, { role: "assistant", content: response.content }];
continue;
}
const text = response.content
.filter((b) => b.type === "text")
.map((b) => (b as { text: string }).text)
.join("");
return JSON.parse(text) as SpendingForecast;
}
}Your frontend renders favorable_windows as green strips on the calendar and cautious_windows as red strips. The user never sees a model and never sees Lumin. They see your product.
Tip
Authentication
Generate an API key at developer.lumin.guru. The key starts with mcp_ and goes in the authorization_token field of the SDK config (or as Authorization: Bearer for raw HTTP). You can hold up to five named keys, one per application or environment; rotate by creating the replacement first, moving traffic, then revoking the old one.
Each successful tool call counts against your account's monthly allowance, which all your keys share. See the limits page for the numbers and the 429 shape, and API key vs OAuth for when to use OAuth instead.
Where to next
- Tool reference to pick the tools to mention in your system prompt.
- Recover from errors for retry semantics on transient failures.
- Stream responses for streaming the model's text to your chat UI.