wealthi-coach-mcp-server

wealthi-coach-mcp-server

Read-only MCP server providing Wealthi's AI Coach with scoped access to student progress data from Firestore and Supabase, enabling AI to retrieve student profiles, progress, assessment results, and learning signals without direct database credentials.

Category
Visit Server

README

wealthi-coach-mcp-server

Read-only MCP server giving Wealthi's AI Coach scoped access to student progress data, without the AI ever touching Firestore or Supabase directly.

Why this exists

Before this server, two clients wrote and read student data directly via SDK calls: the dashboard against Supabase, the mobile app against Firestore. Any AI feature built on top of that would need direct database credentials — which violates Wealthi's own AI philosophy ("AI should never receive unrestricted database access," "AI should not expose internal system data"). This server is the single, narrow, auditable path by which AI systems read student data. It does not replace the app's existing read/write paths for UI rendering; it exists specifically for AI consumption.

Domain ownership

Domain System of record Read by
XP, streak, points, level Firestore (users/{uid}) get_student_progress, get_coach_context
Quiz attempts Firestore (users/{uid}/quizAttempts) get_assessment_results, get_curriculum_progress
Achievements Firestore (users/{uid}/achievements) (reserved — not yet exposed; see Future Work)
Identity, grade band, parent link Supabase profiles (project qsawrfybwwpgajefndnk) get_student_profile, get_coach_context
Coach seen-content tracker Supabase profiles.routing_signals get_learning_signals, get_coach_context

This split is intentional and permanent, not a migration waypoint. See project history for the full reasoning — short version: Firestore owns event-heavy gamification data because that's already its strength and mobile's home turf; Supabase owns identity/relational data because it needs joins and row-level security that Firestore doesn't offer.

Known infrastructure note: as of 2026-06, qsawrfybwwpgajefndnk is the confirmed-correct Supabase project — verified directly against wealthihome/.env's VITE_SUPABASE_URL, which is what the live app actually uses. Wealthi has had multiple Supabase projects connected to the same Lovable workspace with no "active" indicator in Lovable's panel itself, so don't trust Lovable's panel alone to determine which project is correct — always cross-check against the app's actual env var.

Read-only scope (by design, not just by convention)

This server never writes to Firestore or Supabase. All six tools carry readOnlyHint: true / destructiveHint: false annotations, and the service layer (src/services/) contains no write methods at all — there's no update, set, or delete call anywhere in this codebase. Writes continue to go through existing paths:

  • Mobile's direct Firestore SDK writes (XP, streak, quiz submission)
  • Dashboard's existing Supabase writes (profile updates, routing_signals)
  • The dormancy-decay Edge Function (Supabase) for pattern-state decay

If a future use case needs the AI to act (e.g., "mark this content as seen" instead of just reading seen-content), that should be a deliberately designed, narrowly-scoped write tool added later with its own review — not an extension of this server's existing read tools, and not a broadening of its current credentials to writable ones.

Authentication & Credential Scoping

This server uses two separate, dedicated credentials, neither reused from any other Wealthi service. This matters because of a prior credential exposure incident — the fix isn't just rotating one key, it's making sure no future leak from this server compromises anything beyond what this server itself can read.

Firebase Admin (Firestore access)

  1. In the Firebase console for the project backing the mobile app, go to Project Settings → Service Accounts.
  2. Create a new service account specifically for this server — do not reuse the mobile app's or NestJS API's existing service account.
  3. Grant it the Cloud Datastore Viewer IAM role (Google Cloud Console → IAM, not the Firebase console) — this is a read-only role scoped to Firestore/Datastore. Firestore has no separate "read-only Admin SDK mode"; the restriction must be enforced at the IAM role level. Do not grant roles/datastore.user or roles/owner — both include write access.
  4. Generate a private key (JSON) for this service account and populate FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, FIREBASE_PRIVATE_KEY in .env from its contents.
  5. Store the JSON file itself in a secrets manager (not in this repo, not in plain .env in any deployed environment) — inject the three env vars at deploy time instead.

Supabase (identity + routing_signals access)

  1. In the Supabase dashboard for project qsawrfybwwpgajefndnk, go to Authentication → Policies and confirm profiles has row-level security enabled (it should already, given existing app usage).
  2. Create a dedicated Postgres role or use Supabase's API key management to issue a key scoped to read-only SELECT on profiles only — not the service_role key used by the existing Edge Functions. If Supabase's built-in key types don't offer this granularity directly, create a custom Postgres role with GRANT SELECT ON profiles TO wealthi_coach_readonly; and authenticate via that role's credentials, rather than defaulting to service_role.
  3. Populate SUPABASE_URL and SUPABASE_COACH_READONLY_KEY in .env.

Rotation discipline

Given the prior exposure incident: rotate both credentials on a fixed schedule (recommend quarterly), and immediately if this repo's CI/CD config, deployment logs, or any environment dump is ever suspected compromised. Because credentials are scoped narrowly and not shared with any other service, rotating them only requires redeploying this server — no coordination needed with the mobile app, dashboard, or NestJS API.

Repo location

This server lives in its own repo (wealthi-coach-mcp-server), separate from wealthi-ai/wealthihome (dashboard) and the mobile app's repo. It is not the "Wealthi Intelligence" Supabase project (a separate, unrelated leads-generation database) — that naming collision was identified and deliberately avoided when naming this repo. If "Wealthi Intelligence" or "Wealthi System" comes up in conversation, confirm which of (a) this repo, (b) the leads-gen Supabase project, or (c) the qsawrfybwwpgajefndnk Supabase project is actually meant — they are three different things that have been confused before.

Project structure

wealthi-coach-mcp-server/
├── package.json
├── tsconfig.json
├── .env.example
├── src/
│   ├── index.ts                      # entry point, transport selection
│   ├── types.ts                      # Coach-facing domain shapes
│   ├── constants.ts                  # collection/table names, limits
│   ├── tools/                        # MCP tool contracts (thin)
│   │   ├── getStudentProfile.ts
│   │   ├── getStudentProgress.ts
│   │   ├── getAssessmentResults.ts
│   │   ├── getCurriculumProgress.ts
│   │   ├── getLearningSignals.ts
│   │   └── getCoachContext.ts
│   ├── services/                     # DB clients + query/composition logic
│   │   ├── firebaseClient.ts
│   │   ├── supabaseClient.ts
│   │   ├── firestoreProgressService.ts
│   │   ├── supabaseProfileService.ts
│   │   ├── curriculumService.ts
│   │   └── learningSignalsService.ts
│   └── schemas/
│       └── studentInput.ts           # shared Zod input schemas
└── dist/                             # build output (gitignored)

Setup

npm install
cp .env.example .env   # fill in real credentials, see above
npm run build
npm start               # stdio mode by default

For HTTP mode (remote deployment):

TRANSPORT=http PORT=3000 npm start

Test with the MCP Inspector:

npm run inspect

How the AI Coach client calls this server

The AI Coach currently runs as a feature inside the dashboard (AICoachCard, coachContent.ts). To call this server from that context, add it as an MCP server in the Claude API request the Coach feature already makes:

const response = await fetch("https://api.anthropic.com/v1/messages", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "claude-sonnet-4-6",
    max_tokens: 1000,
    messages: [
      {
        role: "user",
        content: "Open a Coach session for this student and recommend what to show next."
      }
    ],
    mcp_servers: [
      {
        type: "url",
        url: "https://<your-deployed-host>/mcp",
        name: "wealthi-coach-mcp-server"
      }
    ]
  })
});

In practice, the Coach prompt should instruct the model to call get_coach_context first, with the current student's ID, before generating any response — that's the single round-trip that gives it profile, progress, learning signals, and curriculum state together. The finer-grained tools (get_student_profile, get_student_progress, etc.) exist for cases where only one piece is needed, or for debugging which specific data source is returning unexpected values.

The student ID passed to these tools should come from the authenticated session on the dashboard/mobile side, never from anything the AI itself infers or that a user can supply via chat — that's what keeps this a properly scoped, per-student tool rather than an open query surface.

Future work (not built yet)

  • getAssessmentResults-style achievement tool (Firestore achievements collection is in the domain table but not yet exposed as a tool — add when Coach actually needs to reference specific achievements).
  • Teacher/School platform tools, once those surfaces exist — they belong in this same tools/+services/ structure, not a separate server.
  • If dormancy-decay and other currently-dormant Edge Functions get reactivated (see open infra note: they're pointed at a different Supabase project, jatohkwzfdoxzevnxrfl, than the live app uses, and have 0 invocations to date), get_learning_signals's momentum derivation may want to read their output directly instead of recomputing a simpler heuristic here.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured