Skip to content

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.


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.


MCPPortOwner calledContract
mcp-kisum-artists3001Backend-Kisum-ArtistsFour canonical artist/profile/platform/provider tools
mcp-kisum-data3011Backend-Kisum-MusicDataExactly 40 read-only analyst/chart/platform/catalog/event tools
mcp-search3002Google Custom Searchgoogle_search only
mcp-soundchart-data3006SoundchartsFiltered venue, festival and discovery allowlist
mcp-trends3012Trends MCP (hosted)Bearer proxy for consumer/search/social demand trends
mcp-kisum-graph3014Promoters / Artists / Venues / Finance recommendation routesFour 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.


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.md rationale).

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]

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

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.

CategoryModels tried firstToolsPromptSteps
Greeting / identityllama-3.1-8b → nemotron-nanononecore2
Simple lookupgpt-oss-20b → nemotron-nano1 groupcore8
Data pull / reportsgpt-oss-20b → nemotron-super1–2 groupscore + module rules12
P&L / moneyopenai → gemini (never ultra)benchmarks + event moneycore + module + P&L doctrine24
Write / draftingnemotron-super → openaithe one module groupcore + module rules16
Research / strategynemotron ultra → superknowledgecore + research rules36

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.

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/events404. 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.

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).

  • No unverified numbers. The agent may state a platform metric only when a musicdata_* / soundchart tool returned it in that same turn. rag_search output 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.

SourceCadenceNote
Promoters PostgresnightlyPast shows + future confirmed shows; upserts, so a corrected event fixes its own rows
Legacy MongoDBonceMust run before primuse_crm is shut down. Not a migration — a few thousand cost lines, read-only
Historical Excel P&Lsper batchBehind 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.

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/confirm on ai-agent-chat (public: /ai/actions/:actionId/confirm) executes the draft via the module server’s POST /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

ConcernOwner
Users, memberships, sessions, JWT issuanceAuth
Company / BU master, entitlementsCore
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-dataExisting 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.

Question typeMCP source
Kisum directory rank, ticket sales, manager contactsmcp-kisum-artists, mcp-rag
Spotify listeners, social followers, artist charts, catalogs, eventsmcp-kisum-data (MusicData)
Named venue / festival / concerts at venue / festival lineupmcp-soundchart-data (search_venue, search_festival, get_venue_concerts, get_festival_edition, …)
What’s trending / keyword demand growth / live trend leaderboardsmcp-trends (hosted Trends MCP tools; chat name trendsData)
Artist tour schedulesmcp-kisum-data (musicdata_artist_events) + optional google_search
Tour/market feasibilitymcp-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.


Aligned with ai-agent-chat/README.md, Swagger, and MCP.md §9.

LayerBaseChatHistory
Upstream (origin / local)https://api-a-ai.kisum.dev or http://localhost:3002POST /chatGET/DELETE /sessions
Public gatewayhttps://api.kisum.ioPOST /ai/chatGET/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.

MethodUpstreamPublic gatewayAuthPurpose
GET/healthsameNoLiveness; 503 if Postgres configured but down
POST/chat/ai/chatJWTLangChain + MCP (main product route)
GET/sessions/ai/sessionsJWTList threads (?limit, ?offset)
GET/sessions/:id/ai/sessions/:idJWTLoad one thread
DELETE/sessions/:id/ai/sessions/:idJWTDelete thread
POST/gemini, /openai, /nvidia-*JWTDirect model shortcuts (upstream only)
GET/DELETE/api/cache/*JWTRedis cache admin (ops)
  • 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-Key when AUTH_INTERNAL_API_KEY is configured (see ai-agent-chat auth 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.

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 in ai-agent-chat). When present and Postgres chat memory (POSTGRES_URI / related config) is enabled, the service loads capped history (CHAT_MEMORY_MAX_MESSAGES per ai-agent-chat README / MEMORY). Omit for stateless turns. Reuse the same sessionId for 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).

When POSTGRES_URI is configured:

MethodUpstreamPublicResponse
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; rejects unknown-user / internal-service (401).
  • No Postgres: 503.
  • Clients: Frontend-Kisum-Promoters (/ai/chat page → /ai/sessions) and MobileApp-KisumAI (sync SQLite from /ai/sessions).

Condensed from MCP.md §6–7. Detailed argument JSON and examples live in-repo.

ConcernMCP packageMCP tool names
Kisum Mongo artist lookupmcp-kisum-datafind_artist, get_artist_platforms (plus get_artist_by_id where implemented)
External searchmcp-searchbandsintown_events, google_search
Predictions / feasibilitymcp-predictionartist_market_prediction, market_prediction
Internal RAGmcp-ragrag_search
Utilitiesmcp-datetoday_date
Real P&L cost datamcp-pnl-benchmarkspnl_cost_benchmarks, pnl_income_benchmarks, pnl_comparable_shows, pnl_benchmark_coverage
Relationship / recommendation / riskmcp-kisum-graphgraph_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:

  1. 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.
  2. Validate at draft time. Anything the API requires — a venue on a show row, a numeric countryId, an eventName — 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:

ToolWhy it exists
promoter_list_expense_categoriesExpenses need the category CODE (talent_programming), not the display name. Requires ?companyId= or the tenant check reads as a permission error.
promoter_get_countryOffers 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 listingexclusive 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.


PathRole
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.ymlPins 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.


From MCP.md §10 — enforce in every environment:

  • Public ingress: ai-agent-chat only (host ports map).
  • Private: all mcp-* services + Postgres + Mongo — use Docker expose, not ports, toward the Internet.
  • Secrets: per-service env_file (.gitignore); never embed keys in repo docs.

Historical reference (MCP.md §6): each old n8n “tool node” maps to one MCP or inner agent concern.

n8n-era conceptMCP / runtime
Find Artistmcp-kisum-datafind_artist
BandsInTown / Google HTTP toolsmcp-search
Prediction workflowmcp-prediction
RAG HTTPmcp-rag
Simple MemoryPostgres memory in ai-agent-chat
JWT verify code stepai-agent-chat middleware

  • 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).