Admin & Configuration
User Help

Admin & Configuration Guide

For administrators. This page covers every configuration surface: the admin pages, the config files, the skills (the assistant's instructions), and the embeddings catalog and its command-line tools. admin role required for the management pages & APIs

⚠️ First login — change the default administrator password

On first run, Strata seeds a single administrator: admin / strata (the default password). It is seeded once and persisted to config/users.json — never re-applied on later restarts. Change it after your first login. There is no in-app change-password screen by design; rotate it from the command line:

./strata-geoai hash-password 'your-new-password'

Paste the printed Argon2id hash into the admin user's pw_hash in config/users.json, then restart. The file stores hashes only (never plaintext) and is git-ignored. Additional accounts are created from the Users page — no file editing (see §3).

On this page

1. How it fits together

Three layers drive the assistant, and you tune each one differently:

A request flows: [MAP CONTEXT] + question → Orchestrator plans → specialists (DataExploration, Geoprocessing, MapControl, Charts, Help) run tools → reply.

2. Admin pages

These live in the slide-out drawer (☰) and are visible only to users whose role is admin (set in config/users.json). The pages and their /geoai/admin/* APIs are enforced server-side, so they stay protected even if linked directly.

The pages share one Settings tab bar, so once you're on any of them you can hop between the rest.

PageURLWhat you manage
Layer Reference/admin.html Register & curate the layers the assistant knows about: which fields it sees, field aliases, and per-layer AI instructions. Auto-creates a default entry when you add an unregistered URL.
Embeddings/embeddings.html View / add / edit / delete every catalog entry (layer, doc, tool — all languages) and export the whole catalog as Markdown. See §9.
MCP Server/mcp.html Register external MCP tool servers the agents can call, and get the Claude Desktop connector URL (§5, §11).
Basemaps/basemaps.html Add / edit the basemap library (also editable in config.json → basemap.library).
Users/users.html Add team members, set their role, change roles, remove accounts. See §3.
License/license.html See the current plan (Free / Enterprise) and activate a license key. See the README.
Plugins/plugins.html Manage optional feature plugins bundled with the deployment.
About/about.html Build / version and deployment info.

Admin role is granted in config/users.json (each account has a role — see §3). The drawer links unhide for admins; the /geoai/admin/* endpoints return 403 for everyone else, so the pages stay protected even if linked directly.

3. Authentication, users & roles

Access is controlled by one flag: server.require_login in config.json (shipped default true). With it on, every data / chat / admin surface needs an identity; with it off (local dev) everything is open and no sign-in is asked. The bundled web UI signs in with a session cookie; external apps use OAuth tokens (§4) — both issued by the same user store, so identity is unified.

Signing in

Roles

RoleCan do
adminEverything — the management pages & /geoai/admin/* APIs (users, OAuth apps, catalog), plus view/edit any saved map.
authorNormal user — chat, query, and create / own / edit maps. No management pages. (Legacy "user" records read as author.)
viewerRead-only — chat & explore, but cannot save/own maps.

Saved-map access (ACL). Every saved map has an owner and a visibility: private (owner only), shared (owner + named grants, each view or edit), or public (everyone can view). Owners set this in the map properties dialog; admins can view/edit anything. Legacy maps normalize to a public, admin-owned entry on first read.

Managing users

Open Settings → Users (/users.html). You can add a member (username, password, role), change a role, and remove an account. The store refuses deleting yourself or the last remaining admin. Accounts persist to config/users.json; passwords are Argon2id-hashed (never plaintext), the file is git-ignored.

No self-service “change password” screen by design. To rotate an existing user's password, generate a hash on the command line and paste it into that user's pw_hash:
./strata-geoai hash-password 'the-new-password'
then restart. (New accounts get their password set for them on the Users page — no hashing by hand.)

4. Signing in from an MCP client (OAuth 2.0)

Strata runs a small OAuth 2.0 authorization server for exactly one purpose: to let an MCP client — Claude Desktop — sign you in, so its tool calls run against your map. It is a sign-in mechanism, not an app platform.

There is no app registration and no API credentials. This server issues no client secrets and supports no client_credentials grant, so nothing can obtain a token that acts on its own — every token belongs to a person who signed in and approved it. (The old Settings → Apps & API page has been removed.)

How it works

  1. The client discovers the server from GET /.well-known/oauth-protected-resource (advertised by the 401 WWW-Authenticate the MCP endpoint returns) and GET /.well-known/oauth-authorization-server.
  2. It registers itself as a public PKCE client at POST /oauth/register (RFC 7591). No secret is issued.
  3. It sends you to GET /oauth/authorize. You sign in with your normal account, then meet a consent screen naming the client and the access it wants. Every client sees this screen — the registration endpoint is open by necessity, so your explicit approval is what makes a client trusted.
  4. It exchanges the one-time code at POST /oauth/token (PKCE S256 mandatory, code valid 60 s, single use) for an access token (1 hour) and a refresh token (30 days, rotated on use).
  5. It sends Authorization: Bearer <token> to /mcp. Calls run against your session (user:<name>) and are logged with it to logs/mcp.log.

Endpoints & scopes

PurposeRoute
AS metadata / discovery/.well-known/oauth-authorization-server
Protected-resource metadata/.well-known/oauth-protected-resource
Client self-registration (public PKCE only)POST /oauth/register
Authorize (code + PKCE, with consent)GET /oauth/authorize
Token (code / refresh)POST /oauth/token

Scopes: maps:read, maps:write, mcp:tools (space-separated; omit to get all three). Deleting a user immediately invalidates their tokens.

Set server.public_url in config.json (e.g. https://stratageoai.localhost) so the issuer in the discovery document matches your TLS URL.

First-party web UI vs. MCP clients

The bundled web UI signs in with a session cookie, not an OAuth token — the standard first-party pattern. The cookie and OAuth tokens come from the same user store, so identity is unified; OAuth is the door for MCP clients only (Claude Desktop — §5).

Errors

StatusMeaning
401 + WWW-Authenticate (on /mcp) No / invalid token — discover the AS from the header and sign in.
400 invalid_client_metadata (on /oauth/register) The client asked for a secret or a grant this server doesn't issue. Public PKCE clients only.
400 invalid_grant Code expired / already used / PKCE mismatch / redirect mismatch.
403 (on /oauth/authorize) User not signed in (bounced to the login page).

5. Connect Claude Desktop

Claude Desktop can drive this map directly: it connects to the MCP server as a native custom connector and calls the map tools. Map-mutating tools (add a layer, render by a field, buffer…) render on the live web map you have open at /; data/query tools return answers in Desktop. It runs under the Desktop subscription — no API key.

Prerequisites — HTTPS

Custom connectors require an https:// URL. The app serves plain HTTP on 127.0.0.1:8767/mcp; the bundled Caddy front provides the TLS hostname https://stratageoaimcp.localhost/mcp. So before connecting:

See the MCP Server page (/mcp.html) for the copy-paste connector URL and the local-HTTPS setup.

Add the connector

  1. In Claude Desktop: Settings → Connectors → Add custom connector.
  2. Paste https://stratageoaimcp.localhost/mcp and save, then fully quit (⌘Q) and reopen Claude Desktop.
  3. Keep the map open at / and prompt Desktop — e.g. “add the traffic cameras layer and render by county.”

Sign-in happens automatically over OAuth. With require_login on, Desktop's first call to /mcp gets a 401 with a WWW-Authenticate header pointing at the protected-resource metadata; Desktop discovers the authorization server, registers itself (DCR), runs the auth-code sign-in against your Strata login, and then calls with the token. Each person's tools run against their own map.

Fallback — the mcp-remote stdio bridge (OAuth-free)

If the native connector won't take the URL or OAuth misbehaves, bridge the plain-HTTP port via a local process. ./start.sh --claude writes this into ~/Library/Application Support/Claude/claude_desktop_config.json for you (and backs up the old file):

{ "mcpServers": {
    "strata-geoai": { "command": "npx",
      "args": ["-y", "mcp-remote", "http://127.0.0.1:8767/mcp", "--allow-http"] } } }

This runs locally so it bypasses OAuth entirely. With require_login on it must still pass a token (--header "Authorization: Bearer $TOKEN"); for pure-local dev, keep require_login=false and no token is needed. After editing the config, ⌘Q and relaunch Desktop.

“Couldn't register with the sign-in service.” This is almost always a stale Desktop connector cache, not a server fault: delete the connector (don't click “try again”), ⌘Q fully quit Claude Desktop and relaunch, then re-add the URL. Also confirm Caddy is up (the https:// URL loads). Note: Claude Desktop connects from this machine so it can reach *.localhost; claude.ai in a browser cannot reach localhost — for web use a public tunnel or the stdio bridge.

6. Configuration files

All configuration is file-based. Edits to config.json take effect on the next server start (skills can hot-reload separately — see §8).

FileControls
config/config.jsonThe main config — server, UI, LLM providers & modes, embeddings (duckdb), agents, basemaps, geoprocessing, charts, history. See the breakdown below.
config/services.jsonThe seed layer catalog — each entry's name / title / description / url / geometry / tags, plus optional title_ar / description_ar for a bilingual embedding row. Re-seed with rebuild-embeddings.
config/users.jsonAccounts and roles (admin gating).
config/rest_samples_mdsf311.jsonThe verified source for REST “natural-language → tool call” exemplars. Add entries here, then run build-rest-chunks + ingest-docs (§10).
docs/refs/*.jsonDocumentation chunks embedded for grounding — sdk_chunks.json (ArcGIS JS SDK), and the REST files (rest_chunks.json, rest_maryland.json, rest_examples.json, rest_sample2.json). Ingested by ingest-docs.
prompts/system.md + skills/The assistant's instructions (§8).

Key config.json sections

SectionWhat it sets
serverhost, port (default 8766), mcp_port, debug.
uiPanel width, initial map center/zoom, tool_call_iteration_limit.
llmdefault_mode (local/cloud), modes (which provider each mode uses), system_prompt_path, skills_dir, hot_reload_skills, orchestrator.max_plan_steps, and providers (§7).
duckdbEmbeddings store: registry_db_path (./data/registry.duckdb) and embedding_dim (1024 for bge-m3).
agentsPer-specialist enabled + max_iterations (tool-call budget) for orchestrator, data_exploration, geoprocessing, map_control, charts, help.
basemapdefault + the library of basemaps.
geoprocessing / charts / rest_proxy / nominatim Buffer limits, chart defaults, the proxy host allow-list, and geocoder settings.

7. Models & modes

Users toggle Local ↔ Cloud from the chat ☰ menu. The mapping lives in llm.modes: local → ollama (qwen2.5:7b-instruct), cloud → anthropic (Claude). The embedding model bge-m3 is infrastructure — it stays local in both modes and is never a user choice.

Setting up Ollama (local models)

The local mode and all embeddings run on Ollama — a local model runner. Install it, start it, and pull the two models the app expects. Ollama serves on http://localhost:11434 (matches llm.providers.ollama.base_url).

# 1. install Ollama (macOS: `brew install ollama`, or download from ollama.com), then start it
#    with the recommended performance flags (see note below):
OLLAMA_FLASH_ATTENTION=1 OLLAMA_KV_CACHE_TYPE=q8_0 ollama serve   # (the desktop app starts the server automatically)

# 2. pull the two models the app uses:
ollama pull qwen2.5:7b-instruct     # local chat / agent model
ollama pull bge-m3                  # embedding model

# 3. verify:
ollama list             # both models should appear

Performance flags (recommended). OLLAMA_FLASH_ATTENTION=1 speeds up long-context prefill, and OLLAMA_KV_CACHE_TYPE=q8_0 halves the memory of the 16K-token KV cache (it requires flash attention) — together they free enough headroom to run a larger local model (e.g. qwen2.5:14b) comfortably on a 16 GB machine, with quicker prefill on every turn. Set them on the ollama serve process. With the macOS desktop app (which auto-starts the server) set them once with launchctl setenv OLLAMA_FLASH_ATTENTION 1 and launchctl setenv OLLAMA_KV_CACHE_TYPE q8_0, then restart Ollama.

ModelRoleWhen it's used
qwen2.5:7b-instruct The local chat / agent model. Runs the Orchestrator and specialists — reads the question + map context, plans steps, and makes tool calls. Tool-calling capable. Local mode only. (Cloud mode uses Claude instead.) Swappable via llm.providers.ollama.model — no re-embed needed.
bge-m3 The embedding model (1024-dim, multilingual incl. Arabic). Turns every layer, doc, tool — and every user question — into a vector so the catalog can match by meaning. Both modes — embeddings are always local. Required for semantic search and agent grounding even when chatting with Cloud. Changing it forces a full re-embed.
An alternate local chat model (qwen3:8b) is pre-configured as the ollama_qwen3 provider — pull it (ollama pull qwen3:8b) and point a mode at it if you want to compare. Cloud mode needs no Ollama model beyond bge-m3.
If Ollama isn't running or the models aren't pulled, the server logs a provider error on boot and semantic search / grounding is disabled. Pull both models before first run.

8. Skills — the assistant's instructions

“Skills” are Markdown instruction packs. They are always injected (not retrieved), so this is where you put durable, authoritative guidance. Two parts compose per turn:

  1. prompts/system.md — the base system prompt, split into XML-tagged sections (<Shared>, <Orchestrator>, <DataExploration>, <Geoprocessing>, <MapControl>, <Charts>, <Help>).
  2. skills/<role>/SKILL.md — a detailed pack appended for that role.
Skill packRole
skills/orchestrator/SKILL.mdPlans a request into steps; resolves layer ids, geocodes, picks the right specialist. Worked planning examples.
skills/data_exploration/SKILL.mdAll read-only data questions — counts, filters, stats, group-by, distinct, top-N, histograms, spatial filters, related records.
skills/geoprocessing/SKILL.mdSpatial transforms that produce a new layer — buffer, intersect, clip, dissolve, convex hull, union, difference.
skills/map_control/SKILL.mdView navigation, add/remove layers, symbology, labels, popups, basemaps.
skills/charts/SKILL.mdRenders a chart from a rows result (only when the user used chart words).
skills/help/SKILL.mdCapability questions and disambiguation.

How to edit a skill

  1. Open the relevant skills/<role>/SKILL.md in a text editor.
  2. Write senior-to-junior, concrete guidance: name the tool, give exact arg shapes, flag case-sensitive field names. Use placeholders (<layer_id>) and mark example field names as illustrative — the model fills real values from [MAP CONTEXT].
  3. Keep claims accurate: only reference tools/parameters that exist. A wrong parameter teaches the model to emit a broken call.
  4. Apply: set llm.hot_reload_skills: true in config.json to pick up edits live, or restart the server (the default).
The packs are intentionally dense and already cover the common cases — read the existing file before adding, to extend rather than duplicate or contradict it.

9. Embeddings catalog

One local DuckDB table (./data/registry.duckdb) holds a semantic fingerprint of every layer, doc chunk, and tool. Each row carries:

Day-to-day curation happens on the Embeddings page (/embeddings.html): view, add, edit, delete entries, and export the whole catalog as Markdown (GET /geoai/admin/embeddings.md) for offline review. Most entries are created automatically when you register a layer or run the doc ingest; you add entries by hand to teach extra domain knowledge.

Bulk rebuilds need the server stopped. The catalog file takes an exclusive lock while the server runs, so the command-line tools below must be run with the server down. A safety backup of the DB is recommended before a big change.

10. Catalog command-line tools

Run from the project root with the server stopped. (Dev form shown; the built binary is strata <subcommand>.) The local embedding service (Ollama + bge-m3) must be running for any command that embeds.

CommandWhat it does
cargo run -- rebuild-embeddingsRe-seed all layer rows from config/services.json (incl. bilingual ar rows). Run after editing layers.
cargo run -- build-rest-chunksRegenerate the REST doc chunk JSON in docs/refs/ from the verified sources. Pure file transform (no embedding).
cargo run -- ingest-docsEmbed the doc chunks from docs/refs/*.json (SDK + REST). Run after build-rest-chunks or editing a doc file.
cargo run -- catalog-lintValidate the catalog — flags empty text, missing vectors, model mismatch, untagged docs, leaked URLs, and reports bilingual gaps / duplicates. Run before/after a rebuild.
cargo run -- eval-retrievalScore retrieval quality (recall@k / MRR) against docs/eval_retrieval.json.
cargo run -- eval-routingScore how well the model classifies a request to the right REST operation (grounding off vs on).

The routine “fine-tune” loop

“Fine-tuning” here means curating the catalog content (not retraining a model):

# 1. edit a source: config/services.json  or  config/rest_samples_mdsf311.json
# 2. stop the server, then (Ollama running):
cargo run -- build-rest-chunks      # only if you changed REST exemplars
cargo run -- rebuild-embeddings     # only if you changed layers
cargo run -- ingest-docs            # embed docs
cargo run -- catalog-lint           # expect 0 errors / 0 warnings
cargo run -- eval-retrieval         # confirm recall held

11. MCP servers & basemaps

12. Common tasks (cheat-sheet)

I want to…Do this
Make the assistant aware of a new layerAdd it on Layer Reference, or add it to config/services.json and run rebuild-embeddings.
Improve how a layer is found / queriedOn Layer Reference: trim fields, set aliases, write per-layer AI instructions.
Change how a specialist behavesEdit its skills/<role>/SKILL.md (§8).
Add Arabic layer namesSet title_ar/description_ar in services.json, then rebuild-embeddings.
Add a worked REST exampleAdd to config/rest_samples_mdsf311.json, then build-rest-chunks + ingest-docs.
Switch the default model / modeEdit llm.default_mode / llm.providers.<p>.model in config.json; restart.
Check the catalog is healthycargo run -- catalog-lint (server down).
Review everything that's embeddedExport Markdown from the Embeddings page, or GET /geoai/admin/embeddings.md.
Add a team memberSettings → UsersAdd a user (username, password, role). §3.
Rotate the admin password./strata-geoai hash-password '…' → paste into pw_hash in config/users.json; restart. §3.
Let Claude Desktop drive your mapAdd it as a custom connector; it registers itself and you sign in with your normal account. §4–§5 / docs/OAUTH_INTEGRATION.md.
Connect Claude DesktopAdd a custom connector for https://stratageoaimcp.localhost/mcp (Caddy must be up). §5.
Turn login on / offSet server.require_login in config.json (or ./start.sh --login / --no-login); restart. §3.

← User Help Back to the map