Project Overview
Development Setup
Prerequisites
| Requirement | Notes |
|---|---|
JDK 21 |
The Gradle toolchain targets Java 21. Use the wrapper ( |
Docker with Compose |
Runs the local MongoDB and Elasticsearch. |
Node.js 20 or newer, npm |
For the Studio. |
Keycloak client credentials |
The Studio signs in through the shared Keycloak (realm |
Provider credentials |
Only for the features you work on: Azure OpenAI (AI Assistant), OpenAI (knowledge embeddings, dictation), Azure AI Document Intelligence (file knowledge), Stripe, Mailgun. |
ffmpeg (optional) |
Re-encodes uploaded interactive-content videos as in a deployment ( |
The Gradle module of the backend is :promethist-platform-backend — the root project name is
prefixed to the directory name, so :backend:... does not exist.
Local Infrastructure
docker compose up -d
This starts the two stores the backend needs, with their data under the git-ignored data/
directory:
| Service | Local address | Notes |
|---|---|---|
MongoDB 7 |
|
Database |
Elasticsearch 8.18 |
|
Single node with security disabled. |
Keycloak is not part of the compose file: the backend validates tokens against the shared Keycloak.
Running the Backend
The committed backend/src/main/resources/application.properties targets the cloud services, so the
backend is run with a local Spring profile whose file you create yourself:
backend/src/main/resources/application-local.properties. It is ignored by git (the *local.*
rule in .gitignore) and must never be committed — it holds your credentials.
./gradlew :promethist-platform-backend:bootRun --args='--spring.profiles.active=local'
The backend listens on 9310 (HTTP) and 9311 (Socket.IO). A working local profile sets at least:
| Property | Local value |
|---|---|
|
|
|
|
|
The local Studio and backend: |
|
Your provider keys, if you work on the AI Assistant or knowledge. |
The full list of properties is in Configuration.
To point the backend’s calls to the engine and the bridge at a deployed environment instead of a local engine, pass that environment’s domain and namespace:
./gradlew :promethist-platform-backend:bootRun \
--args='--spring.profiles.active=local --app.domain=eu.promethist.ai --app.namespace=preview'
| A fresh local database has no realtime configurations ("voices"), and an agent cannot be completed without one — the AI Assistant’s agent generation fails outright. Staff accounts can create one in the Studio under Admin → Voices. |
Running the Studio
cd frontend
npm install
npm run dev
The Studio runs on http://localhost:3000. Keep that port: the Keycloak client accepts only
http://localhost:3000 as a local redirect URI, so a Studio on another port cannot complete a
sign-in. Sign-in offers Google, Microsoft and work email (company SSO); all go through Keycloak
(Studio Sign-In).
Configuration comes from frontend/.env.local (git-ignored). frontend/.env.example is a template
for running only the Studio locally against the preview environment’s backend; copy it and add the
two secrets. To run against a local backend, point the Studio at it:
AUTH_URL=http://localhost:3000
NEXT_PUBLIC_URL=http://localhost:3000
NEXT_PUBLIC_OVERRIDE_PLATFORM_URL=http://localhost:9310
NEXT_OVERRIDE_PLATFORM_URL=http://localhost:9310
NEXT_PUBLIC_OVERRIDE_PLATFORM_SOCKET_URL=http://127.0.0.1:9311
MCP_OAUTH_ISSUER=http://localhost:3000
MCP_RESOURCE=http://localhost:9310/api/v1/mcp
MCP_OAUTH_PRIVATE_JWK=<generated, see below>
The Socket.IO URL deliberately uses 127.0.0.1: cookies are scoped to the host, and the browser
would otherwise send the Studio’s large session cookies to localhost:9311, where they exceed the
Socket.IO server’s 8 KB header limit and the assistant connection keeps failing with
websocket error.
MCP_OAUTH_PRIVATE_JWK is only needed to sign tokens for the hosted MCP server. Generate a key of
your own with the jose package the Studio already depends on, run from frontend/:
node -e "import('jose').then(async (j) => { const { privateKey } = await j.generateKeyPair('RS256', { extractable: true }); const jwk = await j.exportJWK(privateKey); jwk.kid = 'local-' + Date.now(); console.log(JSON.stringify(jwk)) })"
Every environment needs its own key; never reuse a deployed one locally.
All environment variables are listed in Configuration.
Local Ports
| Port | Service |
|---|---|
|
Studio (Next.js dev server) |
|
Backend HTTP: REST API, MCP endpoint, Stripe webhook, Actuator |
|
Backend Socket.IO: AI Assistant |
|
MongoDB (compose) |
|
Elasticsearch (compose) |
Tests
# the whole backend suite
./gradlew :promethist-platform-backend:test
# one class
./gradlew :promethist-platform-backend:test --tests 'ai.promethist.platform.mcp.McpAgentToolsTest'
The test task skips classes named *IntegrationTest; those run with integrationTests. Results are
in backend/build/test-results/test/.
Things to know about the suite:
-
PlatformApplicationTests.contextLoadsstarts the full application and needs the infrastructure and credentials of a real environment; it fails on a bare machine.LLMServiceTestandAzureParserTestcall the real providers and run only when thelocalprofile carries Azure credentials. -
The contract tests of the MCP server —
McpToolRegistryTest(tool names, confirmation gates, argument naming, description length budget) andMcpGuideServiceTest(guide size budget) — run in the default suite. They are described in Hosted MCP Server. -
Writing tests has a few rules of its own — the test properties file, the Mockito helpers for Kotlin, where test configurations may live — collected in Code Conventions.
The pipeline does not run the backend tests (gradle:build builds the distribution only), so run
the suite before you push, together with the formatting, lint and type checks of
Code Conventions.
Feature Flags for Local Work
| Variable | Effect |
|---|---|
|
In development, billing requests are skipped unless this is |
|
In development, the subscription checks that gate parts of the Studio are skipped unless this is
|
|
Injects the Marker.io feedback widget in production builds when |