Chat-Kisum-MCP-Node (AI agent + MCP)
Related documentation: Backend modules overview · Kisum System · Data ownership. Contrast: workspace repo Backend-MCP-Kisum (Go) is separate MCP tooling; do not confuse it with this Node stack.
Canonical implementation detail: repository Chat-Kisum-MCP-Node — start with README.md, then MCP.md for full diagrams, Compose samples, env matrix, tool I/O schemas, and security rules.
Upstream repo name: the implementation README sometimes refers to the GitHub repo as Chat-MCP-Node; this workspace folder is Chat-Kisum-MCP-Node.
Purpose
Section titled “Purpose”In one sentence (from MCP.md):
An AI agent can reason, decide, and use tools via MCP to answer questions using Kisum-linked data and external APIs.
Platform role:
Backend-Kisum-AI is an adjunct Node/TypeScript stack: Docker Compose
orchestrates ai-agent-chat plus private MCP HTTP services. Artist identity is
resolved by mcp-kisum-artists through Backend-Kisum-Artists/PostgreSQL.
Live charts, platform profiles, catalogs and events are read by the new
40-tool mcp-kisum-data through Backend-Kisum-MusicData.
This page records cross-service positioning for Kisum docs. Tool payloads, Swagger, and Compose ports evolve in-repo — treat MCP.md as the living reference.
Artist and MusicData MCP ownership
Section titled “Artist and MusicData MCP ownership”| MCP | Port | Owner called | Contract |
|---|---|---|---|
mcp-kisum-artists | 3001 | Backend-Kisum-Artists | Four canonical artist/profile/platform/provider tools |
mcp-kisum-data | 3011 | Backend-Kisum-MusicData | Exactly 40 read-only analyst/chart/platform/catalog/event tools |
mcp-search | 3002 | Google Custom Search | google_search only |
mcp-soundchart-data | 3006 | Soundcharts | Filtered venue, festival and discovery allowlist |
mcp-trends | 3012 | Trends MCP (hosted) | Bearer proxy for consumer/search/social demand trends |
mcp-kisum-graph | 3014 | Promoters / Artists / Venues / Finance recommendation routes | Four read-only graph tools. Module server, not knowledge — knowledge servers get no identity. Off unless AI_GRAPH_TOOLS_ENABLED=true |
The AI resolves the canonical numeric artist first, reads the correct external
platform ID, and then calls MusicData. The numeric Kisum ID is valid for
MusicData analyst routes only. YouTube channels use UC...; YouTube Charts
insights use /g/... or /m/... Knowledge Graph mids.
Neither MCP queries a database directly. Both use the owning module’s internal API key. OAuth, refresh, cache-admin, crontab and write routes are excluded.
Mental model
Section titled “Mental model”User / client → ai-agent-chat (brain) → MCP containers (tools) → APIs / databases → answer JSON- The agent chooses which MCP tools run, in what order, and how outputs merge into the reply (vs a fixed DAG like n8n alone).
- MCP servers are intentionally small, separate deployables: independent scaling, narrow credentials per tool surface, easier debugging (
MCP.mdrationale).
Target architecture (containers & data flows)
Section titled “Target architecture (containers & data flows)”Adapted from MCP.md. ai-agent-chat mounts routes at POST /chat, GET/DELETE /sessions, and model shortcuts — see HTTP chat contract.
flowchart TD
USER[User / Frontend / BFF]
EDGE[Reverse proxy optional]
USER --> EDGE
EDGE --> CHAT[ai-agent-chat]
CHAT --> MEMORY[(Shared Postgres chat memory)]
CHAT --> MCPKISUM[mcp-kisum-data]
CHAT --> MCPSEARCH[mcp-search]
CHAT --> MCPPREDICT[mcp-prediction]
CHAT --> MCPRAG[mcp-rag]
CHAT --> MCPDATE[mcp-date]
CHAT --> MCPSC[mcp-soundchart-data]
CHAT --> MCPTRENDS[mcp-trends]
MCPKISUM --> MONGO[(MongoDB artist read models)]
MCPSEARCH --> BANDS[BandsInTown API]
MCPSEARCH --> GOOGLE[Google Custom Search API]
MCPPREDICT --> PREDICTAPI[Prediction HTTP backends / n8n / internal APIs]
MCPRAG --> RAGAPI[Kisum RAG HTTP API]
MCPSC --> SOUNDCHARTS[Soundcharts hosted MCP]
MCPTRENDS --> TRENDSMCP[Trends MCP hosted API]
Deployment / trust zones (Compose view)
Section titled “Deployment / trust zones (Compose view)”Shows public vs private Docker network: only the agent ingress should publish host ports; MCP services stay on expose / internal DNS. Production: chat memory is an external Postgres shared by chat-a and chat-b (not a per-origin container).
flowchart LR
subgraph Public
CLIENT[Caller]
LB[AI LoadBalancer]
API_A[ai-agent-chat chat-a]
API_B[ai-agent-chat chat-b]
end
subgraph Shared
DB[(External Postgres session memory)]
end
subgraph Private Docker network per origin
MCP1[mcp-kisum-data]
MCP2[mcp-search]
MCP3[mcp-rag]
MCP4[mcp-prediction]
MCP5[mcp-date]
MCP6[mcp-soundchart-data]
MCP7[mcp-trends]
end
subgraph External deps
MONGO[(MongoDB)]
GOOGLE_EXT[Google CSE]
BANDS_EXT[BandsInTown]
RAG_EXT[Kisum RAG]
PRED_EXT[Prediction services]
SC_EXT[Soundcharts MCP]
TRENDS_EXT[Trends MCP]
end
CLIENT --> LB
LB --> API_A
LB --> API_B
API_A --> DB
API_B --> DB
API_A --> MCP1
API_A --> MCP2
API_A --> MCP3
API_A --> MCP4
API_A --> MCP5
API_A --> MCP6
API_B --> MCP1
API_B --> MCP2
API_B --> MCP3
API_B --> MCP4
API_B --> MCP5
API_B --> MCP6
MCP1 --> MONGO
MCP2 --> GOOGLE_EXT
MCP2 --> BANDS_EXT
MCP3 --> RAG_EXT
MCP4 --> PRED_EXT
MCP6 --> SC_EXT
Main request lifecycle (happy path)
Section titled “Main request lifecycle (happy path)”From MCP.md sequence diagram (JWKS verification + Postgres memory + MCP tool rounds).
sequenceDiagram
participant U as Caller
participant API as ai-agent-chat
participant AUTH as JWKS
participant MEM as PostgreSQL memory (optional)
participant AGENT as LangChain agent
participant MCP as MCP tool HTTP services
U->>API: POST /chat (public: /ai/chat)
API->>AUTH: Verify Bearer JWT or internal API key policy
API->>MEM: Load thread history when sessionId + POSTGRES_URI
API->>AGENT: User message (+ memory context + tools)
AGENT->>MCP: Call tools as needed
MCP-->>AGENT: Structured tool payloads
AGENT->>MEM: Persist turn when configured
AGENT-->>API: Final model output (+ pendingActions when write drafts)
API-->>U: JSON response
Request router — cheap questions get a small package (2026-08-01)
Section titled “Request router — cheap questions get a small package (2026-08-01)”Off by default (ROUTER_ENABLED=true switches it on). With it off the chat
behaves exactly as before.
The problem it solves. Tool schemas and the system prompt are re-sent on every agent step. All 216 tools plus the full prompt is roughly 33,000 tokens of fixed overhead, so a question that makes four tool calls costs about 175,000 input tokens — even “when is my next event?”. Model price turned out to be only the third-biggest cost.
One classification picks four things: which models to try, which tool groups get bound, which prompt sections are included, and the step budget. Measured: the core prompt is 1,381 tokens against 7,915 for the full one, and a “list my events” turn binds only the event tools instead of all 216.
| Category | Models tried first | Tools | Prompt | Steps |
|---|---|---|---|---|
| Greeting / identity | llama-3.1-8b → nemotron-nano | none | core | 2 |
| Simple lookup | gpt-oss-20b → nemotron-nano | 1 group | core | 8 |
| Data pull / reports | gpt-oss-20b → nemotron-super | 1–2 groups | core + module rules | 12 |
| P&L / money | openai → gemini (never ultra) | benchmarks + event money | core + module + P&L doctrine | 24 |
| Write / drafting | nemotron-super → openai | the one module group | core + module rules | 16 |
| Research / strategy | nemotron ultra → super | knowledge | core + research rules | 36 |
Decision order: follow-up (“and what about Singapore?”) reuses the previous turn’s category · keyword pass (English + Spanish) · tiny classifier (llama-3.3-70b, no tools, Redis-cached, any language) · unsure routes UP-tier — strong models, all tools, full prompt.
Safety properties: money work is pinned away from nemotron-ultra because it fabricated benchmark figures; being unsure always spends more, never less; the tool filter is fail-open (a filter that would bind zero tools binds all of them); and prompt sections are sliced from the live prompt at boot rather than copied, so a missing marker logs a warning and sends the full prompt instead of silently dropping a rule. Writes are safe on a cheap model because nothing executes until the user taps Confirm.
Model availability was probed live before any tier was fixed, and the result
changed the plan. moonshotai/kimi-k2.6 returns HTTP 404 — “Not found for
account” even though it appears in NVIDIA’s /v1/models listing, and
meta/llama-3.3-70b-instruct returns HTTP 503 (capacity). The rule that came
out of it: listing is not access — probe a model with a real completion and a
two-tool chain before wiring it into a tier. Models verified working:
gpt-oss-20b (3.5s, fastest), nemotron-3-super (3.9s), nemotron-3-nano-30b
(6.0s), gpt-oss-120b (7.3s). chat_template_kwargs is a Nemotron template
field and is now sent only to Nemotron models — the others can reject it with a
400.
A model card’s capability list is not a substitute for testing. NVIDIA’s card
for llama-3.1-8b advertises function calling, and it does emit well-formed tool
calls — but asked for “the GBK Madya show” it produced {"eventId":"1234"}, an
invented id, where a larger model calls the events lookup first. Given the id
outright it was flawless. The rule the platform now follows: qualify a model with
a real two-step chain (name → resolve id → use id), never with its spec sheet.
That model is used only where no ids exist to invent — greetings and the
router’s own classifier.
Measured on the same question (“list my upcoming events”), router on versus off: 3,269 input tokens in 2.0s against 8,740 tokens in 13.9s — and that was with a handful of tools loaded rather than the full catalogue. The greeting tier is starker still: “hi” costs 1,435 tokens in 0.8s against 8,740 tokens in 13.9s, because it binds no tools at all.
End-to-end verification (2026-07-31)
Section titled “End-to-end verification (2026-07-31)”The full action chain was verified locally with a real user session (browser
JWT + x-org sent straight to the chat service): list events returned real org
data, an event was resolved by name to its id, the venue marketplace returned
the live listing with its operator company id, and a ticket-creation request
produced a pendingActions[] draft only — the confirm-before-write contract
held. Note for testers: the internal-key auth path binds knowledge tools only;
module action tools require the real JWT + x-org.
Action-tool URL joining (2026-07-31 — was a total outage)
Section titled “Action-tool URL joining (2026-07-31 — was a total outage)”mcp-action-kit builds each call as new URL(path, PROMOTERS_BASE_URL). A
path beginning with / resets to the origin root and drops the base path,
so /api/events against https://api.kisum.io/promoters resolved to
https://api.kisum.io/api/events → 404. Because every tool path starts with
/api/..., no action tool worked against a prefixed base URL. The kit now
strips the leading slash before joining.
Diagnostic: POST /actions/execute with a dummy Bearer token — 404 means
the URL is wrong, 401 means the URL is right and only the token was
rejected.
MusicData endpoint (2026-07-31)
Section titled “MusicData endpoint (2026-07-31)”https://music.kisum.dev is dead — it resolves nowhere and silently broke
all 40 MusicData tools. Use https://api.kisum.io/data in production and
http://localhost:3808 in dev (from inside a container:
http://host.docker.internal:3808, because localhost is the container
itself and Backend-Kisum-MusicData runs on the host).
Answer-integrity guards (2026-07-31)
Section titled “Answer-integrity guards (2026-07-31)”- No unverified numbers. The agent may state a platform metric only when a
musicdata_*/soundcharttool returned it in that same turn.rag_searchoutput and model memory are explicitly not sources for live figures — the model was caught quoting a precise Spotify listener count it never fetched. - No dangling announcements. A reply that only announces work (“let me
check…”) with zero tool calls is treated as a failed turn and falls through
to the next model in
CHAT_MODEL_CHAIN. - Recursion-limit loops are recoverable — they fall through to the next provider instead of failing the whole request.
Two prompt/tooling rules worth knowing (2026-07-31)
Section titled “Two prompt/tooling rules worth knowing (2026-07-31)”Only SYSTEM_PROMPT_V2 is live. langchain.ts resolves
SYSTEM_PROMPT_V2 || SYSTEM_PROMPT_V1, so V1 is dead code. Behaviour rules
written into V1 silently do nothing — this caused two real defects the same day
(the “no fake progress” rule and the prediction maintenance block). Always edit
V2.
Disabling a tool is done at bind time, not in the prompt. mcpTools.ts
filters the DISABLED_TOOLS env list out of the bound tool set, so the model
cannot call a disabled tool regardless of instructions. A prompt-only ban was
tested and the model ignored it.
P&L cost benchmarks — the AI stopped guessing costs (2026-08-01)
Section titled “P&L cost benchmarks — the AI stopped guessing costs (2026-08-01)”The P&L doctrine fixed the shape of a P&L. It did nothing about the numbers: the model was inventing them, e.g. $470,000 of Technical & Infrastructure for a 10k stadium show that really runs $300-350k.
The fix is a benchmark table of our own history, not RAG and not fine-tuning. Cost data is structured numbers, so it is queried and aggregated in SQL. RAG would return similar-looking documents and leave the arithmetic to the model; fine-tuning would bake in today’s prices with no sample size and no provenance.
Where it lives. cost_observations in the AI Postgres (POSTGRES_URI),
never Finance and never Promoters. It is derived knowledge: if it is wrong we
drop and rebuild it, which is impossible for financial truth. Finance and
Promoters remain the source of truth; this is a read-only snapshot for learning.
Three writers, one reader.
| Source | Cadence | Note |
|---|---|---|
| Promoters Postgres | nightly | Past shows + future confirmed shows; upserts, so a corrected event fixes its own rows |
| Legacy MongoDB | once | Must run before primuse_crm is shut down. Not a migration — a few thousand cost lines, read-only |
| Historical Excel P&Ls | per batch | Behind a review gate; the only real source of income figures |
A benchmark is the median of per-SHOW department totals, not of individual
invoice lines, and median rather than average — one festival destroys an average.
Every group returns shows, a p25-p75 range and a settled/quoted split.
Fallback ladder: country → region → global, reported as matchLevel. Artist
type is a preference, not a filter. Fewer than 3 shows sets lowConfidence.
Prompt contract. SYSTEM_PROMPT_V2 requires a benchmark lookup before any
cost line, and every figure must carry a sample size (“median of 9 comparable
shows”) or the words “industry assumption”.
The P&L now uses the ten real expense groups from expense_categories
(leaf → group), replacing five invented departments — so a generated P&L and a
real event record line up 1:1.
The one hard ingest exclusion is anything the AI itself created. Those rows
are read out of kisum_ai_actions and skipped, and future pending events are
excluded for the same reason. Human estimates do count: they were built from
real quotes. Without that rule the model would learn from its own guesses and
its mistakes would become permanent.
Prediction tools under maintenance (2026-07-31)
Section titled “Prediction tools under maintenance (2026-07-31)”mcp-prediction stays deployed and loaded, but the chat system prompt carries
a maintenance override forbidding market_prediction and
artist_market_prediction until the prediction backend work is finished.
Feasibility questions are answered from artist directory / RAG / MusicData
data, with a note that Kisum’s prediction engine is temporarily under
maintenance. Re-enable by removing the override block in
ai-agent-chat/src/agent/systemPrompt.ts (tracked in Backend-Kisum-AI/TODO.md).
Public last-30-days pulse — mcp-last30days (2026-08-05)
Section titled “Public last-30-days pulse — mcp-last30days (2026-08-05)”New knowledge MCP (port 3015) wrapping the open-source last30days Python
engine (MIT, stdlib-only), vendored inside the package at
mcp-last30days/last30days-skill/ and copied into the image at build time. One tool, last30days_research: what real people said about a
topic in the last 30 days — Reddit, Hacker News, X, YouTube, prediction
markets — scored by upvotes/likes/real-money volume. Keyless out of the box
(Reddit + HN); optional keys in mcp-last30days/.env unlock more sources.
Handled as a slow tool: its own router group webResearch, bound ONLY on
the research tier; per-connection defaultToolTimeout 180s in mcpTools.ts;
server-side subprocess kill at 150s; output capped at 60k chars. The calling
model authors 3–5 paraphrased search_queries and the wrapper assembles the
engine’s --plan JSON — bare runs fall back to weaker deterministic retrieval.
Prompt rules: SYSTEM_PROMPT_V2 → “Public Audience Pulse” (call once, cite
engagement numbers and dates, public reaction ≠ Kisum records, outage ≠ “no one
is talking about it”).
Kisum Live Council — LIVE in the chat runtime (2026-08-05)
Section titled “Kisum Live Council — LIVE in the chat runtime (2026-08-05)”Spec: Backend-Kisum-AI/ai-agent-chat/skills/council/KISUM_LIVE_COUNCIL_SKILL.md (+ _REFERENCE.md
/ _EXAMPLES.md). Runtime: ai-agent-chat/src/agent/council/.
A material live-entertainment decision (“council this…”, “should we book X for
Y?”, “I’m torn between…”) runs a five-advisor pipeline instead of the single
agent: evidence pass (the only tool-calling stage — forced council
router category: benchmarks required, market/booking/money/webResearch breadth,
no nvidia-ultra because it fabricates ledger figures) → 5 specialist
advisors in parallel → anonymous peer review (1 call; 5 in War Room mode)
→ chairman verdict (GO / CONDITIONAL GO / RENEGOTIATE / PAUSE / NO-GO with
the skill’s exact structure). Triggers are deterministic only — no classifier
guess can start a ~10-call run; factual questions stay on their normal tiers.
Any stage failure degrades to the normal single-agent answer.
COUNCIL_ENABLED=false switches the feature off. Verified live: a Jakarta
US$150k offer question returned a benchmark-grounded NO-GO in ~45s
(5/5 advisors, ~39k tokens).
MCP server rule — stateless transport per request (2026-07-31)
Section titled “MCP server rule — stateless transport per request (2026-07-31)”Every custom knowledge MCP creates its StreamableHTTPServerTransport per
POST /mcp request (the mcp-action-kit pattern). A shared boot-time
transport accepts only its first client — on MCP SDK ≥ 1.29 it fails every
request with an empty 500 while the container still reports healthy. This
took down six of eight knowledge MCPs on 2026-07-31 and made the chat
“promise” data it could not fetch.
Knowledge tool loading is per-server with an expiring cache (2026-07-31)
Section titled “Knowledge tool loading is per-server with an expiring cache (2026-07-31)”ai-agent-chat loads knowledge MCP tools per server and logs
[MCP] Knowledge tools by server: … plus a warning naming any server that
returned zero tools. A full set is cached for KNOWLEDGE_MCP_CACHE_TTL_MS
(default 10 min); a partial set only for KNOWLEDGE_MCP_RETRY_TTL_MS
(default 60 s) so recovered MCPs rejoin without restarting the container.
The system prompt additionally forbids “fake progress”: when a mandated tool
is not bound (its MCP is down), the model must say live data is temporarily
unavailable instead of claiming it is fetching.
AI module actions (Phase 1)
Section titled “AI module actions (Phase 1)”When the caller sends a valid Bearer JWT and canonical x-org, ai-agent-chat also loads four module action MCP servers (ports 3007–3010): Promoters, Finance, Artists, Venues.
- Read tools call the persona backend with the user’s identity (permissions enforced upstream).
- Write tools only return a draft; the model cannot mutate data.
- The chat response may include
pendingActions[]; the Promoters frontend renders a confirm card. POST /actions/:actionId/confirmonai-agent-chat(public:/ai/actions/:actionId/confirm) executes the draft via the module server’sPOST /actions/execute(not exposed as an MCP tool).- Audit: Postgres table
kisum_ai_actions; Redis pending keys are single-use (GETDEL).
See repo MCP.md (module action servers) and AI-TOOLS-CATALOG.md for the full tool list.
sequenceDiagram
participant U as User
participant FE as Frontend
participant API as ai-agent-chat
participant MCP as mcp-*-actions
participant BE as Persona backend
U->>FE: Chat message
FE->>API: POST /chat (Bearer + x-org)
API->>MCP: Write tool (draft)
MCP-->>API: kisum_action_draft JSON
API-->>FE: response + pendingActions
U->>FE: Confirm
FE->>API: POST /actions/:id/confirm
API->>MCP: POST /actions/execute
MCP->>BE: Mutating HTTP call
BE-->>MCP: Result
MCP-->>API: ok
API-->>FE: Success
Ownership (source of truth)
Section titled “Ownership (source of truth)”| Concern | Owner |
|---|---|
| Users, memberships, sessions, JWT issuance | Auth |
| Company / BU master, entitlements | Core |
Thread / conversational rows for this agent (POSTGRES_URI + request sessionId) | One shared external Postgres in production (chat-a + chat-b use identical POSTGRES_URI); optional local Compose ai-memory-postgres (profile local-memory) for dev only — operational; not tenant master |
Artist read paths used by mcp-kisum-data | Existing Kisum / Base semantics consumed via URIs — MCP must not redefine Auth/Core/Base ownership |
| Live streaming/social audience, charts, discovery (Soundcharts) | Soundcharts hosted MCP via mcp-soundchart-data proxy — OAuth client credentials in that service only; not Kisum SoT |
| Venue/festival directory, concerts, festival editions/lineups (Soundcharts) | mcp-soundchart-data — e.g. search_venue, get_venue_concerts, search_festival, get_festival_edition, discover_venues, discover_festivals (Venue, Festival, Search) |
| Consumer/search/social demand trends (Google, TikTok, YouTube, Reddit, news, commerce) | Trends MCP hosted via mcp-trends Bearer proxy — TRENDSMCP_API_KEY in that service only; not Kisum SoT |
Rule: Chat-Kisum-MCP-Node is AI tooling + orchestration. It must not become a second writer of companies, memberships, or commercial entitlements.
Soundcharts data routing (KisumAI)
Section titled “Soundcharts data routing (KisumAI)”| Question type | MCP source |
|---|---|
| Kisum directory rank, ticket sales, manager contacts | mcp-kisum-artists, mcp-rag |
| Spotify listeners, social followers, artist charts, catalogs, events | mcp-kisum-data (MusicData) |
| Named venue / festival / concerts at venue / festival lineup | mcp-soundchart-data (search_venue, search_festival, get_venue_concerts, get_festival_edition, …) |
| What’s trending / keyword demand growth / live trend leaderboards | mcp-trends (hosted Trends MCP tools; chat name trendsData) |
| Artist tour schedules | mcp-kisum-data (musicdata_artist_events) + optional google_search |
| Tour/market feasibility | mcp-prediction |
User-facing attribution (KisumAI): End users must never be told data comes from Soundcharts, Trends MCP, or any third-party vendor. If asked where metrics come from, KisumAI answers Kisum-collected industry data, updated daily (streaming, charts, social, venues, festivals, demand trends, directory, ticket intelligence). Internal routing above is for developers/operators only.
HTTP chat contract (ai-agent-chat)
Section titled “HTTP chat contract (ai-agent-chat)”Aligned with ai-agent-chat/README.md, Swagger, and MCP.md §9.
URL layers
Section titled “URL layers”| Layer | Base | Chat | History |
|---|---|---|---|
| Upstream (origin / local) | https://api-a-ai.kisum.dev or http://localhost:3002 | POST /chat | GET/DELETE /sessions |
| Public gateway | https://api.kisum.io | POST /ai/chat | GET/DELETE /ai/sessions |
LoadBalancer loadbalancer-kisum strips the /ai prefix before forwarding (/ai/chat → /chat, /ai/sessions → /sessions). No /api/ai/ path on this stack.
Endpoint table
Section titled “Endpoint table”| Method | Upstream | Public gateway | Auth | Purpose |
|---|---|---|---|---|
GET | /health | same | No | Liveness; 503 if Postgres configured but down |
POST | /chat | /ai/chat | JWT | LangChain + MCP (main product route) |
GET | /sessions | /ai/sessions | JWT | List threads (?limit, ?offset) |
GET | /sessions/:id | /ai/sessions/:id | JWT | Load one thread |
DELETE | /sessions/:id | /ai/sessions/:id | JWT | Delete thread |
POST | /gemini, /openai, /nvidia-* | — | JWT | Direct model shortcuts (upstream only) |
GET/DELETE | /api/cache/* | — | JWT | Redis cache admin (ops) |
Request headers (tenant + auth)
Section titled “Request headers (tenant + auth)”Authorization: Bearer <JWT>— verified against configured JWKS (see repo env).x-auth: Bearer <JWT>— accepted as an alias (Promoters router / mobile).x-org: <company_uuid>— forward when callers need downstream tools or audit to respect Kisum tenancy (MCP.md,README.md).- Service-to-service: optional
X-Internal-API-KeywhenAUTH_INTERNAL_API_KEYis configured (seeai-agent-chatauth middleware) — follows internal-route patterns elsewhere in the workspace; browsers should still prefer JWT + canonical org context from the platform BFF, not leaked internal keys.
JSON body
Section titled “JSON body”Language route (POST /chat) — message is required.
{ "message": "Can we do Dua Lipa in Singapore?", "sessionId": "stable-thread-id-optional"}sessionId— optional stable chat thread id (trimmed, ≤ 128 chars inai-agent-chat). When present and Postgres chat memory (POSTGRES_URI/ related config) is enabled, the service loads capped history (CHAT_MEMORY_MAX_MESSAGESperai-agent-chatREADME/MEMORY). Omit for stateless turns. Reuse the samesessionIdfor every message in a thread so memory and caches stay aligned.
Provider fallback: CHAT_MODEL_CHAIN (default Ultra → Super → Gemini → OpenAI → OSS). Nemotron Ultra sometimes returns whitespace-only content (or leaks </think> tags) while still counting tokens — ai-agent-chat strips think tags and treats empty final text as a recoverable failure so the next provider runs. Clients should still expect a normal non-empty response when any provider in the chain succeeds.
Gemini tool schemas: MCP / Zod schemas often include JSON Schema keywords Gemini rejects (const, examples, exclusiveMinimum, …). On tool load, ai-agent-chat rewrites schemas to a Gemini-safe subset before bindTools. Without that step, Gemini fallback returns 400 and a long multi-provider retry can surface as a browser 504 via CloudFront.
Successful responses include sessionId when the client sent a valid (non-empty, length-capped) sessionId — see ai-agent-chat chat-langchain.ts. Typical shape (MCP.md §9; fields may evolve):
{ "model": "gemini-2.0-flash", "response": "…assistant text…", "usage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 0 }, "cached": false, "sessionId": "same-stable-thread-id-if-sent"}Note: This is not Auth JWT sessionId (revocable login session). Here it identifies the AI chat thread only. market_prediction and similar tools may receive a correlated session_id derived from chat context (MCP.md §7).
Chat history APIs (Postgres-backed)
Section titled “Chat history APIs (Postgres-backed)”When POSTGRES_URI is configured:
| Method | Upstream | Public | Response |
|---|---|---|---|
GET | /sessions?limit=&offset= | /ai/sessions?… | { sessions[], total } |
GET | /sessions/:sessionId | /ai/sessions/:sessionId | { sessionId, messages[] } |
DELETE | /sessions/:sessionId | /ai/sessions/:sessionId | { ok: true, sessionId } |
- Auth: same JWT middleware as
POST /chat; rejectsunknown-user/ internal-service (401). - No Postgres: 503.
- Clients: Frontend-Kisum-Promoters (
/ai/chatpage →/ai/sessions) and MobileApp-KisumAI (sync SQLite from/ai/sessions).
MCP tool catalogue (high level)
Section titled “MCP tool catalogue (high level)”Condensed from MCP.md §6–7. Detailed argument JSON and examples live in-repo.
| Concern | MCP package | MCP tool names |
|---|---|---|
| Kisum Mongo artist lookup | mcp-kisum-data | find_artist, get_artist_platforms (plus get_artist_by_id where implemented) |
| External search | mcp-search | bandsintown_events, google_search |
| Predictions / feasibility | mcp-prediction | artist_market_prediction, market_prediction |
| Internal RAG | mcp-rag | rag_search |
| Utilities | mcp-date | today_date |
| Real P&L cost data | mcp-pnl-benchmarks | pnl_cost_benchmarks, pnl_income_benchmarks, pnl_comparable_shows, pnl_benchmark_coverage |
| Relationship / recommendation / risk | mcp-kisum-graph | graph_recommendations, graph_relationship_path, graph_similar_entities, graph_risk_context — advisory only, confirm current state with the owning module tool |
Prediction tools call configured HTTP backends; market_prediction accepts a session_id field for correlated calls (MCP.md §7).
Write tools: what a draft has to carry (2026-08-01)
Section titled “Write tools: what a draft has to carry (2026-08-01)”Write tools prepare a draft; nothing runs until the user clicks Confirm. That
makes the schema the only thing standing between a plausible-looking card and a
failure the user only discovers on click — and a failed Confirm consumes the
pending action, so the retry is 410 Gone.
Two rules follow, and both were learned the hard way running a whole booking through the chat:
- A write tool’s schema must mirror the backend DTO exactly. An open schema
(
z.record(z.string(), z.unknown())) tells the model nothing; it invents field names and every Confirm fails. - Validate at draft time. Anything the API requires — a venue on a show row,
a numeric
countryId, aneventName— belongs in the tool’s schema, not in the error the user gets after clicking.
Two lookups exist purely to feed those required fields, and neither is optional:
| Tool | Why it exists |
|---|---|
promoter_list_expense_categories | Expenses need the category CODE (talent_programming), not the display name. Requires ?companyId= or the tenant check reads as a permission error. |
promoter_get_country | Offers and event show rows need a numeric country id. Nothing else exposes it, so it is in the router’s ALWAYS_BOUND set. |
offerChannel must be read off the listing — exclusive for an official
artist listing, middle_agent for one posted via an agency. Guessing produces a
404 that reads like the listing does not exist.
Tool binding is capped at 128 (ROUTER_MAX_TOOLS). The OpenAI API rejects
any request above that outright, so the router trims from its least-mandated
group first and requiredGroups always survive.
Components (repos paths)
Section titled “Components (repos paths)”| Path | Role |
|---|---|
ai-agent-chat/ | Fastify + JWT middleware + LangChain createAgent + MultiServerMCPClient; Redis cache hooks (Compose redis / REDIS_URL=redis://redis:6379); Postgres thread memory optional. |
ai-memory-postgres/ | Local dev only — Postgres via Compose profile local-memory. Production uses external shared Postgres (POSTGRES_URI). |
mcp-kisum-data/ | Streamable HTTP MCP → Mongo artist tools. |
mcp-search/ | Streamable HTTP MCP → BandsInTown + Google CSE. |
mcp-rag/ | Streamable HTTP MCP → Kisum RAG URL (default example in MCP.md). |
mcp-prediction/ | Streamable HTTP MCP → artist/market prediction webhooks/APIs (PREDICTION_INTERNAL_API_KEY / org headers per service env). |
mcp-date/ | Lightweight date MCP. |
mcp-pnl-benchmarks/ | Streamable HTTP MCP (port 3013) → real per-show cost/income medians from cost_observations in the AI Postgres. Read-only. |
pnl-ingest/ | Scheduled job, not a service. Builds cost_observations from Promoters Postgres (nightly), legacy Mongo (one-off) and historical Excel P&Ls (reviewed). |
mcp-kisum-graph/ | Streamable HTTP MCP (port 3014) → the module backends’ own recommendation routes, which reach the Recommendation Service and Neo4j. Read-only, no credential of its own, no free-form query input. Registered in moduleServers() so the caller’s JWT and x-org are forwarded. Bound additively on relationship intent only, never on simple lookups. See mcp-kisum-graph/README.md and Neo4j recommendations. |
docker-compose.yml | Pins in-network MCP URLs (MCP_*_URL), agent port (3000:3000), local Redis (REDIS_URL=redis://redis:6379 on all apps). ai-memory-postgres is profile local-memory only — production uses external POSTGRES_URI. expose for MCP listeners — never publish MCP ports publicly. |
Compose YAML sample with env wiring (alternative port 8787 in MCP.md draft) belongs in MCP.md; keep this Starlight page high-level unless platform wiring changes materially.
Security rules (summary)
Section titled “Security rules (summary)”From MCP.md §10 — enforce in every environment:
- Public ingress:
ai-agent-chatonly (hostportsmap). - Private: all
mcp-*services + Postgres + Mongo — use Dockerexpose, notports, toward the Internet. - Secrets: per-service
env_file(.gitignore); never embed keys in repo docs.
n8n → MCP mapping (migration lens)
Section titled “n8n → MCP mapping (migration lens)”Historical reference (MCP.md §6): each old n8n “tool node” maps to one MCP or inner agent concern.
| n8n-era concept | MCP / runtime |
|---|---|
| Find Artist | mcp-kisum-data → find_artist |
| BandsInTown / Google HTTP tools | mcp-search |
| Prediction workflow | mcp-prediction |
| RAG HTTP | mcp-rag |
| Simple Memory | Postgres memory in ai-agent-chat |
| JWT verify code step | ai-agent-chat middleware |
Related workspace repos
Section titled “Related workspace repos”Backend-MCP-Kisum— Go MCP / platform integration (different codebase; complementary).Chat-Kisum-MCP-Node— Node Compose stack documented here.
When behavior, URLs, authentication, or tool contracts change materially, update repository CHANGELOG.md / MEMORY.md and this page (or defer detail to MCP.md and link only).