@centia-io/mcp-server 1.0.15 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,72 @@
1
+ # AGENT.md - Centia BaaS Core Rules
2
+
3
+ > Last updated: 2026-02-22
4
+
5
+ This file only contains global hard rules.
6
+ Task-specific guidance has been moved into `skills/*/SKILL.md`.
7
+
8
+ ## 1) Prime Directive
9
+
10
+ - Prefer Centia MCP tools when available.
11
+ - Use `@centia-io/sdk` for all JS/TS runtime code.
12
+ - Use raw HTTP only for provisioning or SDK gaps.
13
+ - Never re-implement SDK functionality with `fetch`/`axios` in runtime app code.
14
+
15
+ ## 2) Tool Priority (Hard Rule)
16
+
17
+ Use this order:
18
+
19
+ 1. Centia MCP tools
20
+ 2. `@centia-io/sdk`
21
+ 3. OpenAPI-defined HTTP calls
22
+ 4. Docs-backed HTTP calls
23
+
24
+ Never invent endpoints or payloads.
25
+
26
+ ## 3) Runtime vs Provisioning (Hard Rule)
27
+
28
+ - Runtime application code must not perform schema provisioning.
29
+ - Schema changes are provisioning/codegen tasks only.
30
+ - Runtime apps must assume schema already exists.
31
+
32
+ ## 4) Security and Auth Baselines
33
+
34
+ - Never hardcode credentials or tokens.
35
+ - Never commit secrets in `.env`.
36
+ - Frontend/browser code must not embed service tokens.
37
+ - Browser auth must use SDK OAuth flows; do not implement OAuth manually.
38
+
39
+ ## 5) Destructive Operations Policy (Hard Rule)
40
+
41
+ Before destructive changes, present method/tool/purpose and require explicit user confirmation.
42
+
43
+ Destructive includes:
44
+
45
+ - `DROP`, `TRUNCATE`, destructive `DELETE`
46
+ - schema/table/column/constraint/index deletes
47
+ - policy overwrite operations
48
+
49
+ ## 6) Required Skill Routing
50
+
51
+ Use the minimal matching skill(s) below for each task:
52
+
53
+ - Runtime SDK patterns: `skills/centia-runtime-sdk/SKILL.md`
54
+ - Provisioning and migrations: `skills/centia-provisioning/SKILL.md`
55
+ - JSON-RPC lifecycle and typed API: `skills/centia-json-rpc/SKILL.md`
56
+ - Auth model by runtime context: `skills/centia-auth-model/SKILL.md`
57
+ - PostgreSQL types and output formats: `skills/centia-types-formats/SKILL.md`
58
+ - Map view config and layer styling (classes/styles/labels): `skills/centia-map-styling/SKILL.md`
59
+ - Sub-user privileges and rights inheritance: `skills/centia-privileges/SKILL.md`
60
+ - Key/value store (app state, flags, preferences): `skills/centia-keyvalue/SKILL.md`
61
+ - Feature CRUD as GeoJSON by primary key: `skills/centia-feature/SKILL.md`
62
+ - Realtime WebSocket events: `skills/centia-realtime/SKILL.md`
63
+ - File import workflow: `skills/centia-file-import/SKILL.md`
64
+ - OpenAPI and docs fallback: `skills/centia-openapi-docs/SKILL.md`
65
+ - Delivery checklist and MCP reference: `skills/centia-delivery/SKILL.md`
66
+ - Danish address/matrikel search component: `skills/danish-search/SKILL.md`
67
+
68
+ ## 7) Default Paths
69
+
70
+ - SDK client: `src/baas/client.ts`
71
+ - HTTP fallback layer: `src/baas/http.ts`
72
+ - Provisioning assets: `provision/`, `migrations/`, `schema/`
package/centia-api.json CHANGED
@@ -854,7 +854,7 @@
854
854
  "tags": [
855
855
  "Feature"
856
856
  ],
857
- "description": "Delete a single feature by its primary key through a WFS-T transaction.",
857
+ "description": "Delete one or more features by primary key through a WFS-T transaction. Pass a single key or a comma-separated list (e.g. 1,2,3).",
858
858
  "operationId": "deleteFeature",
859
859
  "parameters": [
860
860
  {
@@ -880,12 +880,12 @@
880
880
  {
881
881
  "name": "feature",
882
882
  "in": "path",
883
- "description": "Primary key value of the feature",
883
+ "description": "Primary key value, or a comma-separated list of values (e.g. 1,2,3)",
884
884
  "required": true,
885
885
  "schema": {
886
886
  "type": "string"
887
887
  },
888
- "example": "1"
888
+ "example": "1,2"
889
889
  }
890
890
  ],
891
891
  "responses": {
@@ -893,7 +893,7 @@
893
893
  "description": "Deleted"
894
894
  },
895
895
  "404": {
896
- "description": "Feature not found"
896
+ "description": "No features found"
897
897
  }
898
898
  }
899
899
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centia-io/mcp-server",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -11,7 +11,9 @@
11
11
  },
12
12
  "files": [
13
13
  "dist",
14
- "centia-api.json"
14
+ "centia-api.json",
15
+ "AGENTS.md",
16
+ "skills"
15
17
  ],
16
18
  "scripts": {
17
19
  "build": "tsc",
@@ -22,7 +24,7 @@
22
24
  "author": "",
23
25
  "license": "ISC",
24
26
  "dependencies": {
25
- "@centia-io/sdk": "^0.2.9",
27
+ "@centia-io/sdk": "^0.2.11",
26
28
  "@modelcontextprotocol/sdk": "^1.26.0",
27
29
  "axios": "^1.13.5",
28
30
  "zod": "^4.3.6"
@@ -0,0 +1,29 @@
1
+ # Skills Index
2
+
3
+ This folder contains task-specific skills referenced from `AGENT.md`.
4
+
5
+ ## Quick Routing
6
+
7
+ Use the smallest matching skill set for the task.
8
+
9
+ | Skill | Use when |
10
+ |---|---|
11
+ | `centia-runtime-sdk` | Building or refactoring JS/TS runtime code with `@centia-io/sdk` |
12
+ | `centia-provisioning` | Changing schema, tables, columns, constraints, indexes, policies, migrations |
13
+ | `centia-json-rpc` | Creating/updating/calling JSON-RPC methods and generating TypeScript interfaces |
14
+ | `centia-auth-model` | Implementing or reviewing auth across browser, backend, CLI, and provisioning |
15
+ | `centia-types-formats` | Choosing PostgreSQL types, casts, `type_hints`, `type_formats`, output formats |
16
+ | `centia-file-import` | Importing files with `postFileUpload` and `postFileProcess` |
17
+ | `centia-openapi-docs` | Using OpenAPI/docs as fallback when MCP/SDK are insufficient |
18
+ | `centia-map-styling` | Configuring map center/zoom/extent or layer cartography: classes, styles, labels |
19
+ | `centia-privileges` | Granting sub-user privileges, groups, rights inheritance, layer authentication levels |
20
+ | `centia-keyvalue` | Storing/reading JSON state (settings, flags, preferences) in the key/value store |
21
+ | `centia-feature` | Reading/creating/updating/deleting single features as GeoJSON by primary key |
22
+ | `centia-realtime` | Realtime WebSocket events, subscriptions with shapes, enabling table change events |
23
+ | `centia-delivery` | Final delivery checklist, MCP coverage check, and handoff quality gate |
24
+ | `danish-search` | Danish address/matrikel typeahead search component (`@centia-io/danish-search`) |
25
+
26
+ ## Core Rule
27
+
28
+ Global hard rules stay in `AGENT.md`.
29
+ Task-specific workflows and references stay in `skills/*/SKILL.md`.
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: centia-auth-model
3
+ description: Authentication policy for Centia BaaS across browser, backend, CLI, and provisioning contexts, including required SDK flows and environment variable usage.
4
+ ---
5
+
6
+ # Auth Model Policy
7
+
8
+ Use this skill whenever authentication is implemented or reviewed.
9
+
10
+ ## Identify runtime context
11
+
12
+ Determine one of:
13
+
14
+ - browser app (SPA/frontend)
15
+ - server/backend/CLI
16
+ - provisioning script
17
+
18
+ ## Browser apps
19
+
20
+ Use SDK OAuth PKCE flow only.
21
+
22
+ Required SDK classes:
23
+
24
+ - `CodeFlow` for sign-in/sign-out/callback handling
25
+ - `SignUp` for signup redirect flow
26
+
27
+ Forbidden:
28
+
29
+ - `CENTIA_ACCESS_TOKEN` in frontend code
30
+ - service tokens in browser app code
31
+ - manual OAuth implementation
32
+ - provisioning API calls from browser runtime
33
+
34
+ ## Server and CLI
35
+
36
+ Allowed methods:
37
+
38
+ - `PasswordFlow`
39
+ - `CENTIA_ACCESS_TOKEN` for MCP/HTTP fallback
40
+
41
+ Typical use:
42
+
43
+ - provisioning
44
+ - admin/service-to-service operations
45
+ - CLI tools
46
+
47
+ ## Provisioning scripts
48
+
49
+ Provisioning runs server-side only.
50
+
51
+ Allowed:
52
+
53
+ - access token
54
+ - `PasswordFlow`
55
+ - MCP tools
56
+ - OpenAPI calls
57
+
58
+ Forbidden:
59
+
60
+ - browser auth flows
61
+
62
+ ## Responsibility matrix
63
+
64
+ - Browser app: `CodeFlow`
65
+ - Browser signup: `SignUp`
66
+ - Backend app: `PasswordFlow`
67
+ - Provisioning: `PasswordFlow` or access token
68
+ - CLI: `PasswordFlow`
69
+
70
+ ## Environment variables
71
+
72
+ Server/provisioning:
73
+
74
+ - `CENTIA_HOST`
75
+ - `CENTIA_CLIENT_ID`
76
+ - `CENTIA_CLIENT_SECRET`
77
+ - `CENTIA_USERNAME`
78
+ - `CENTIA_PASSWORD`
79
+ - `CENTIA_DATABASE`
80
+ - optional: `CENTIA_ACCESS_TOKEN`
81
+
82
+ Browser (Vite):
83
+
84
+ - `VITE_CENTIA_HOST`
85
+ - `VITE_CENTIA_CLIENT_ID`
86
+ - `VITE_CENTIA_DATABASE`
87
+
88
+ Never commit secrets. Keep `.env.example` with placeholders.
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: centia-delivery
3
+ description: Delivery and validation checklist for Centia tasks, including required output sections, MCP tool reference, geospatial mapping library selection, and final self-check criteria.
4
+ ---
5
+
6
+ # Delivery Checklist
7
+
8
+ Use this skill before finalizing Centia-related implementation work.
9
+
10
+ ## Required delivery content
11
+
12
+ Include:
13
+
14
+ - setup steps
15
+ - env vars used
16
+ - install and run commands
17
+ - SDK usage explanation
18
+ - provisioning steps (if applicable)
19
+ - validation steps
20
+ - HTTP calls made (without secrets)
21
+
22
+ ## MCP tool reference
23
+
24
+ Schema and tables:
25
+
26
+ - `getSchema`, `postSchema`, `patchSchema`, `deleteSchema`
27
+ - `getTable`, `postTable`, `patchTable`, `deleteTable`
28
+ - `getColumn`, `postColumn`, `patchColumn`, `deleteColumn`
29
+ - `getConstraint`, `postConstraint`, `deleteConstraint`
30
+ - `getIndex`, `postIndex`, `deleteIndex`
31
+ - `getSequence`, `postSequence`, `patchSequence`, `deleteSequence`
32
+
33
+ Query and API:
34
+
35
+ - `postSql`
36
+ - `postGraphQL`
37
+ - `getRpc`, `postRpc`, `patchRpc`, `deleteRpc`
38
+ - `postCall`, `postCallDry`, `getTypeScript`
39
+
40
+ Auth and access:
41
+
42
+ - `postOauth`, `postDevice`
43
+ - `getUser`, `postUser`, `patchUser`, `deleteUsers`
44
+ - `getRule`, `postRule`, `patchRule`, `deleteRule`
45
+ - `getPrivileges`, `patchPrivileges`
46
+ - `getClient`, `postClient`, `patchClient`, `deleteClient`
47
+
48
+ Metadata:
49
+
50
+ - `getMetaData`, `patchMetaData`
51
+
52
+ Import and misc:
53
+
54
+ - `postFileUpload`, `postFileProcess`
55
+ - `postCommit`
56
+ - `getStats`
57
+
58
+ ## Mapping library selection
59
+
60
+ For map UIs:
61
+
62
+ - Default to MapLibre GL JS for straightforward map display and interaction.
63
+ - Use Leaflet or OpenLayers for advanced interaction or GIS-heavy needs.
64
+ - Prefer current docs lookup (for example Context7 MCP when available) before writing mapping code.
65
+
66
+ ## Final self-check
67
+
68
+ - Used MCP tools where possible.
69
+ - Used SDK for runtime JS/TS.
70
+ - Kept provisioning separate from runtime.
71
+ - Avoided runtime schema changes.
72
+ - Used correct auth flow for context.
73
+ - Kept secrets out of source control.
74
+ - Followed OpenAPI/docs contract where HTTP was used.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: centia-feature
3
+ description: Use when reading, creating, updating, or deleting individual geographic features (rows) as GeoJSON by primary key in Centia BaaS tables, or when working with /api/v4/schemas/{schema}/tables/{table}/features endpoints, getFeature/postFeature/patchFeature/deleteFeature tools, the Features SDK class, WFS-T feature editing, or the srs/SRID of feature geometry.
4
+ ---
5
+
6
+ # Feature API (GeoJSON CRUD by primary key)
7
+
8
+ Use this skill for the v4 Feature API (`/api/v4/schemas/{schema}/tables/{table}/features/{feature}`): GeoJSON CRUD on single table rows, addressed by primary key, executed through the in-process WFS-T engine (versioning, workflow, geofence rules and tile-cache busting all apply). Successor of v2 `/api/v2/feature`.
9
+
10
+ ## Availability
11
+
12
+ Lives on the GeoCloud2 branch `dev/multiple_styles` (commits `25900b64`…`844d8fd6`, 2026-08-25/26). As of 2026-08 it is **not** on production `api.centia.io` — assume dev-only. SDK support: `@centia-io/sdk` **>= 0.2.11** (published on npm; the `Features` class is absent in <= 0.2.10).
13
+
14
+ ## Endpoints and semantics
15
+
16
+ **Token-only** (Bearer JWT on every request; there is NO anonymous read, even for public layers). Sub-users allowed, governed by per-table privileges (see `centia-privileges`). The table must be a registered layer with a **single-column primary key** (400 `NO_PRIMARY_KEY` otherwise) and WFS enabled (400 `WFS_NOT_ENABLED`).
17
+
18
+ | Operation | Route / MCP tool | Result |
19
+ |---|---|---|
20
+ | Get by key(s) | `GET .../features/{feature}` / `getFeature` — `{feature}` is one key **or a comma list** (`1,2,3`); `?srs` = output SRID (default 4326) | `200`: **one match → bare GeoJSON `Feature`; several → `FeatureCollection`**; `404` `FEATURE_NOT_FOUND` if none match (partial matches return `200` with the found ones) |
21
+ | Create | `POST .../features` (no key in path); body = `Feature` or `FeatureCollection`; `?srs` = SRID of incoming geometry / `postFeature` | `201` + `Location: .../features/{keys}` (comma list of the new keys — parse it for generated keys); `400` `NOTHING_INSERTED` if the layer is not editable |
22
+ | Update | `PATCH .../features/{feature}` (key optional) / `patchFeature` | `303 See Other` + `Location`; `404` `FEATURE_NOT_FOUND` if nothing updated |
23
+ | Delete | `DELETE .../features/{feature}` (one key **or a comma list**, like GET) / `deleteFeature` | `204`; `404` `FEATURE_NOT_FOUND` if nothing was deleted (partial matches delete the found ones → `204`) |
24
+
25
+ No PUT/upsert. No collection GET — reading a whole table is a SQL API or WFS job (`GET .../features` without a key → 400 `FEATURE_ID_REQUIRED`). Keys containing `'` are rejected (400 `INVALID_FEATURE_ID`).
26
+
27
+ ## Body and write rules
28
+
29
+ - Body must be a GeoJSON `Feature` or a **non-empty** `FeatureCollection` (else 400 `INVALID_GEOJSON`). Geometry is optional — a feature without geometry inserts/updates properties only, and reads back with `geometry: null`.
30
+ - `srs` is asymmetric: output SRID on GET, **incoming**-geometry SRID on POST/PATCH, always defaulting to 4326 (GeoJSON lon/lat). Sending e.g. 25832 coordinates without `?srs=25832` stores garbage geometry.
31
+ - POST: a primary-key value in `properties` is used as the new key; otherwise one is generated. No upsert — posting an existing key fails the WFS transaction.
32
+ - PATCH: the path key wins; without it every feature must carry its primary key in `properties` (400 `PRIMARY_KEY_MISSING`). You **cannot PATCH one path key with a collection** (400 `INVALID_DATA`) — omit the key to batch-update. Omitted properties stay untouched.
33
+ - Property encoding (WFS-T): booleans become `t`/`f`, **JSON arrays are converted to PostgreSQL array literals automatically** — send plain JSON values, do not pre-format `"{1,2,3}"` strings.
34
+
35
+ ## SDK (>= 0.2.11)
36
+
37
+ Standalone class **`Features`** (plural — `Feature` is the GeoJSON type), explicit-client pattern: `new Features(client.http)`. Methods: `getFeature<P>(schema, table, key | keys[], {srs?})` → `GeoJsonFeature<P> | GeoJsonFeatureCollection<P>` (union — normalize the shape yourself); `postFeature(schema, table, body, {srs?})` and `patchFeature(schema, table, body, {feature?, srs?})` → `{location}`; `deleteFeature(schema, table, key)`. Own minimal GeoJSON types (`GeoJsonFeature`, `GeoJsonFeatureCollection`, `GeoJsonGeometry`), no `@types/geojson`. Errors: `CentiaApiError` (`.status`/`.code`).
38
+
39
+ ## Common mistakes
40
+
41
+ | Mistake | Reality |
42
+ |---|---|
43
+ | Assuming production availability | dev/multiple_styles only; MCP tools need a server restart against the updated spec |
44
+ | Reading public layers without a token | v4 Feature API always requires a Bearer JWT (unlike the OGC routes) |
45
+ | Expecting one response shape from GET | one match = bare `Feature`, several = `FeatureCollection` |
46
+ | Listing a table via `GET .../features` | 400 `FEATURE_ID_REQUIRED` — use the SQL API or WFS for collections |
47
+ | Pre-formatting array columns as `"{a,b}"` strings | send JSON arrays; the server builds the PG array literal |
48
+ | PATCHing `/features/42` with a `FeatureCollection` | 400 `INVALID_DATA` — drop the path key and put keys in `properties` |
49
+ | Forgetting `?srs` on POST/PATCH of projected coordinates | geometry is read as EPSG:4326 by default |
50
+ | Expecting `200` from PATCH / a JSON body from POST | `303`/`201` with empty bodies; the keys are in the `Location` header |
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: centia-file-import
3
+ description: Two-step Centia file import workflow using MCP tools, covering upload, processing options, validation mode, geospatial projection settings, and supported input formats.
4
+ ---
5
+
6
+ # File Import Workflow
7
+
8
+ Use this skill for importing tabular or geospatial files into Centia.
9
+
10
+ ## Hard rule
11
+
12
+ Use MCP tools first:
13
+
14
+ 1. `postFileUpload`
15
+ 2. `postFileProcess`
16
+
17
+ Do not call raw HTTP import endpoints unless MCP is unavailable.
18
+
19
+ ## Step 1: Upload
20
+
21
+ Call `postFileUpload` and capture returned file id/name.
22
+
23
+ ## Step 2: Process
24
+
25
+ Call `postFileProcess` with uploaded file and target schema.
26
+
27
+ Key options:
28
+
29
+ - `import`: `false` for validation-only, `true` to commit
30
+ - `append`: append to existing table
31
+ - `truncate`: clear table before append
32
+ - `timestamp`: add timestamp column
33
+ - `s_srs` and `t_srs`: source/target projection fallback
34
+ - `p_multi`: promote single to multipart geometries
35
+ - `x_possible_names` and `y_possible_names`: CSV lon/lat hints
36
+
37
+ ## Validation flow
38
+
39
+ Run with `import: false`, inspect response, then rerun with `import: true`.
40
+
41
+ ## Supported formats
42
+
43
+ - GeoJSON (`.geojson`)
44
+ - Shapefile zip (`.zip` with sidecar files)
45
+ - GeoPackage (`.gpkg`)
46
+ - GML (`.gml`)
47
+ - CSV (`.csv`)
48
+
49
+ ## Typical imported table shape
50
+
51
+ - primary key `gid`
52
+ - geometry column `the_geom`
53
+ - GiST index on `the_geom`
54
+ - inferred field types
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: centia-json-rpc
3
+ description: JSON-RPC method lifecycle for Centia BaaS, including method creation, invocation, dry-run type inference, TypeScript interface generation, and runtime usage with createApi or Rpc.
4
+ ---
5
+
6
+ # JSON-RPC Workflow
7
+
8
+ Use this skill when creating, updating, testing, or calling Centia JSON-RPC methods.
9
+
10
+ ## Purpose
11
+
12
+ JSON-RPC wraps SQL in named server-side methods for reuse, consistency, and central control.
13
+
14
+ ## Define method (provisioning)
15
+
16
+ Use `postRpc`.
17
+
18
+ Parameter style:
19
+
20
+ - `:param`
21
+ - `:param::type` (required for type inference)
22
+
23
+ Example:
24
+
25
+ ```txt
26
+ postRpc({
27
+ method: "addDays",
28
+ q: "select date(:date) + :days::int as result",
29
+ type_hints: { date: "timestamptz" },
30
+ type_formats: { result: "l jS F Y" }
31
+ })
32
+ ```
33
+
34
+ Manage definitions with:
35
+
36
+ - `getRpc`
37
+ - `postRpc`
38
+ - `patchRpc`
39
+ - `deleteRpc`
40
+
41
+ ## Call method
42
+
43
+ Use `postCall` with JSON-RPC 2.0 envelope.
44
+
45
+ ```txt
46
+ postCall({
47
+ jsonrpc: "2.0",
48
+ method: "addDays",
49
+ params: { date: "2025-05-01", days: 7 },
50
+ id: "1"
51
+ })
52
+ ```
53
+
54
+ - Omit `id` for notification mode.
55
+ - `params` can be object or array of objects for batch calls.
56
+
57
+ ## Dry-run and TypeScript inference
58
+
59
+ 1. Dry-run method with `postCallDry`.
60
+ 2. Fetch generated interfaces with `getTypeScript`.
61
+ 3. Save interface (for example `src/baas/api.ts`).
62
+ 4. Use at runtime with `createApi<Api>()`.
63
+
64
+ Important: SQL params must include explicit casts (`:param::type`) to infer method signatures reliably.
65
+
66
+ ## Runtime usage
67
+
68
+ Preferred typed call:
69
+
70
+ ```ts
71
+ const api = createApi<Api>();
72
+ const rows = await api.getX({ x: 1 });
73
+ ```
74
+
75
+ Fallback generic call:
76
+
77
+ ```ts
78
+ await rpc.call({ jsonrpc: "2.0", method: "getX", params: { x: 1 }, id: 1 });
79
+ ```
80
+
81
+ ## Error codes to recognize
82
+
83
+ - `-32600` invalid request
84
+ - `-32601` method not found
85
+ - `-32602` invalid params
86
+ - `-32603` internal error
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: centia-keyvalue
3
+ description: Use when storing or reading small JSON state (app settings, feature flags, user preferences, drafts, snapshots) in the Centia BaaS key/value store, or when working with /api/v4/keyvalue endpoints, the Keyvalue SDK class, keys, owner/public flags, or paths projection.
4
+ ---
5
+
6
+ # Key/Value Store
7
+
8
+ Use this skill for the v4 Keyvalue API (`/api/v4/keyvalue/{key}`): per-database storage of arbitrary JSON under globally unique keys, with an owner/public access model.
9
+
10
+ ## Availability
11
+
12
+ Lives on the GeoCloud2 branch `dev/multiple_styles` (commits `2213a099`, `3897be13`) and requires the v3 migration run (`GET /api/v3/admin/migrations` adds `owner`/`public` columns to `settings.key_value`). As of 2026-08 it is **not** confirmed on production `api.centia.io` — assume dev-only. SDK support: `@centia-io/sdk` **>= 0.2.10** (not 0.2.9). The legacy v2 `/api/v2/keyvalue` is unchanged and separate.
13
+
14
+ ## Endpoints and semantics
15
+
16
+ Token-only (Bearer JWT on every request). Keys are **globally unique per database** — no schema namespacing, no format rules (SDK URL-encodes).
17
+
18
+ | Operation | Route / MCP tool | Result |
19
+ |---|---|---|
20
+ | List all visible keys | `GET /api/v4/keyvalue` / `getKeyvalue` (no key) | `200`, **plain JSON array** of entries (no wrapper) |
21
+ | Get one key | `GET /api/v4/keyvalue/{key}` | `200` entry; `404` `KEY_NOT_FOUND` if absent/not visible |
22
+ | Create | `POST .../{key}`, body `{value, public?}` / `postKeyvalue` | `201` + `Location`; `409` `KEY_EXISTS` if key exists |
23
+ | Update value and/or public | `PATCH .../{key}`, body `{value?, public?}` / `patchKeyvalue` | `303 See Other` + `Location` (see `centia-provisioning`) |
24
+ | Delete | `DELETE .../{key}` / `deleteKeyvalue` | `204`; `404` if absent/not owned |
25
+
26
+ Entry shape: `{id: int, key, value: <decoded JSON>, owner: string|null, public: bool}`. `value` is arbitrary JSON, required on POST — the spec declares `type: object`, but the server applies no type check, so scalars and arrays round-trip too. There is **no PUT/upsert**: create with POST, update with PATCH (on unknown state: POST, fall back to PATCH on 409). The `201`/`303` bodies are empty; their `Location` is `/api/v4/keyvalue/{key}` — GET it to read the entry back.
27
+
28
+ ## Access model
29
+
30
+ - `owner` is **always set server-side from the JWT uid** — never send it in the body; ownership never changes.
31
+ - Super user: full CRUD on every key. Sub-user: reads own keys + `public: true` keys; creates/updates/deletes **only own** keys (violations surface as `404`, not `403`).
32
+ - Legacy rows with `owner IS NULL` are treated as public and super-owned: everyone reads, only the super user modifies.
33
+
34
+ ## `paths` projection (GET single key only)
35
+
36
+ `GET .../{key}?paths=user.name,active` returns only the named sub-trees: comma separates paths, dot navigates; the result `value` is keyed by the path strings, e.g. `{"value": {"user.name": "Alice", "active": true}}`. Empty path or segment → `400` `INVALID_PATHS`. No other query params exist — **no `like`/`filter`** listing (that was v2 only); filter key names client-side.
37
+
38
+ ## SDK (>= 0.2.10)
39
+
40
+ Standalone class, explicit-client pattern: `new Keyvalue(client.http)`. Methods `getKeyvalue()` (list) / `getKeyvalue<T>(key)` / `getKeyvalue(key, paths)` / `postKeyvalue<T>(key, {value, public?})` / `patchKeyvalue<T>(key, {value?, public?})` / `deleteKeyvalue(key)`; errors are `CentiaApiError` (`.status`/`.code`). Types: `KeyvalueEntry<T>`, `KeyvalueProjection`.
41
+
42
+ ## Common mistakes
43
+
44
+ | Mistake | Reality |
45
+ |---|---|
46
+ | Assuming production availability | dev/multiple_styles + migration required; not confirmed on api.centia.io |
47
+ | PUT or POST to overwrite an existing key | No upsert; POST on existing key → `409` — use PATCH |
48
+ | Sending `owner` in the body | Owner comes from the JWT; body allows only `value` and `public` |
49
+ | `like=`/`filter=` query params on v4 | v2-only; v4 lists everything visible, filter client-side |
50
+ | Expecting `200` from PATCH | Returns `303 See Other` — disable auto-redirect |
51
+ | Expecting `403` when touching another user's key | Non-owned rows are invisible to writes → `404 KEY_NOT_FOUND` |
52
+ | Using `@centia-io/sdk` 0.2.9 for Keyvalue | The `Keyvalue` class ships in 0.2.10+ |
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: centia-map-styling
3
+ description: Map view configuration and layer cartography for Centia BaaS, including per-schema map center/zoom/extent, layers, classes, styles, and labels, MapServer-backed rendering, id round-trip rules, and payload value conventions.
4
+ ---
5
+
6
+ # Map, Layers, Classes, Styles, and Labels
7
+
8
+ Use this skill when configuring the per-schema map view or the cartographic styling of layers (classes, styles, labels) via MCP tools or the v4 API.
9
+
10
+ ## Availability
11
+
12
+ The Map API and the granular class/style/label sub-routes live on the GeoCloud2 branch `dev/multiple_styles`. As of 2026-08 they are **not** on production `api.centia.io`. SDK support: `@centia-io/sdk` >= 0.2.5 has `provisioning.maps.getMap/patchMap` and the `MapConfig` type.
13
+
14
+ ## Data model
15
+
16
+ ```txt
17
+ Map per-SCHEMA view config (center, zoom, extent) — orthogonal to layers
18
+ Layer one relation, key = schema.table.geometry_column, carries `properties` (def JSON)
19
+ └─ classes[] one rendering rule each: expression + scale range + sortid
20
+ ├─ styles[] one symbolizer each (MapServer STYLE parameters)
21
+ └─ labels[] one text label each (MapServer LABEL parameters)
22
+ ```
23
+
24
+ Classes, styles, and labels are converted server-side into a MapServer mapfile; they control WMS/tile rendering. The schema's Map `extent` is also used as the schema bbox in OGC GetCapabilities.
25
+
26
+ ## Map view config
27
+
28
+ - `GET`/`PATCH /api/v4/map/schema/{schema}` — MCP tools `getMap` / `patchMap`.
29
+ - Shape: `{ center: [lon, lat] | null, zoom: number | null, extent: [minx, miny, maxx, maxy] | null }`.
30
+ - **CRS is EPSG:4326** (lon/lat). Older copies of the OpenAPI spec say EPSG:3857 — that is outdated; the API surface transforms to/from the 3857 storage at the boundary.
31
+ - PATCH is partial: only provided keys are updated; explicit `null` clears a key. Returns `303 See Other` (see `centia-provisioning` for redirect handling).
32
+
33
+ ## Layer endpoints — two edit models
34
+
35
+ | Model | Route / MCP tool | Semantics |
36
+ |---|---|---|
37
+ | Atomic configure | `POST /api/v4/layers` / `postLayer` | Sets `properties` and **replaces the entire `classes` array** |
38
+ | Layer properties | `PATCH /api/v4/layers/{layer}` / `patchLayer` | Key-merge on the def JSON only (no classes) |
39
+ | Granular create | `POST .../classes`, `.../styles`, `.../labels` / `postLayerClass`, `postStyle`, `postLabel` | Additive; body is one object or an array |
40
+ | Granular update | `PATCH .../classes/{id}`, etc. / `patchLayerClass`, `patchStyle`, `patchLabel` | Key-merge on that entry |
41
+ | Granular delete | `DELETE .../{id}` / `deleteLayerClass`, `deleteStyle`, `deleteLabel` | `{id}` accepts comma-separated ids |
42
+
43
+ `POST /api/v4/layers` configures **existing** layers (relations already registered); it does not create tables. Preferred bulk workflow: `getLayer` → edit the document locally → one atomic `postLayer`.
44
+
45
+ ## Id and round-trip rules
46
+
47
+ - Ids on classes, styles, and labels are fixed, server-assigned (8 hex chars). Send **new** entries *without* `id` — a client-supplied id on class creation is rejected with 400.
48
+ - When using atomic `postLayer`, send **existing ids back unchanged**; entries arriving without id are treated as new and get freshly minted ids (identity cannot be guessed).
49
+ - Round-trip unknown keys untouched: legacy classes can carry flat label keys (e.g. a top-level `force`). Do not strip them.
50
+
51
+ ## Value conventions
52
+
53
+ - Numeric fields are **strings**; empty string `''` means unset. `sortid` is a real integer (convention: steps of 10; defaults to highest existing + 10).
54
+ - Colors are hex `#RRGGBB`. Opacity is `'0'`–`'100'`.
55
+ - Many style/label numeric fields accept a `[column]` reference for data-driven values (size, angle, offsets).
56
+
57
+ ## Class expressions and label text
58
+
59
+ - Do **not** use the layer properties `theme_column` (CLASSITEM) and `label_column` (LABELITEM). They are the legacy mechanism and must not be combined with class expressions / label `text` — leave both empty (`''`).
60
+ - Classify with a standalone logical MapServer `expression` on each class, and label with the label's `text` expression (e.g. `[name]`).
61
+ - Quoting in class expressions depends on the column type:
62
+ - **String columns:** quote *both* sides, including the `[column]` reference: `'[Status]'='Forslag'` (`'string'='string'`).
63
+ - **Numeric columns:** no quotes on either side: `[tal]=1` (`1=1`).
64
+
65
+ ## Enum quick reference
66
+
67
+ | Field | Values |
68
+ |---|---|
69
+ | style `linecap` | `round` `butt` `square` |
70
+ | style `geomtransform` | `bbox` `centroid` `end` `labelpnt` `labelpoly` `start` `vertices` |
71
+ | style `symbol` (built-ins) | `circle` `square` `triangle` `hatch1` `dashed1` `dot-dot` `dashed-line-short` `dashed-line-long` `dash-dot` `dash-dot-dot` `arrow` `arrow2` |
72
+ | label `position` | `auto` `ul` `uc` `ur` `cl` `cc` `cr` `ll` `lc` `lr` |
73
+ | label `angle` keywords | numeric, `auto`, `auto2`, `follow`, or `[column]` |
74
+ | label `fontweight` | `normal` `bold` `italic` `bolditalic` |
75
+ | layer `geotype` | `Default` `POINT` `LINE` `POLYGON` |
76
+ | layer `format` | `PNG` `jpeg_low` `jpeg_medium` `jpeg_high` |
77
+ | layer `cache` | `disk` `sqlite` `s3` `memcache` |
78
+
79
+ ## Scale gates (all "Numeric value stored as a string")
80
+
81
+ `minscaledenom`/`maxscaledenom` exist at layer, class, and label level; layer-level `label_min_scale`/`label_max_scale` gate all labels. Min = most-zoomed-in bound, max = most-zoomed-out bound.
82
+
83
+ ## Common mistakes
84
+
85
+ | Mistake | Reality |
86
+ |---|---|
87
+ | Sending map `center`/`extent` in EPSG:3857 | The surface is EPSG:4326 (lon/lat); 3857 in old spec copies is wrong |
88
+ | `postLayer` without existing class/style/label ids | Whole `classes` array is replaced; missing ids are re-minted, breaking references |
89
+ | Client-generated ids on new entries | Server assigns ids; class creation with an id returns 400 |
90
+ | Numeric values as JSON numbers | Most def/class/style/label numerics are strings; `''` = unset |
91
+ | Adding a class via `PATCH /layers/{layer}` | That route key-merges layer properties only; use `postLayerClass` or atomic `postLayer` |
92
+ | Stripping unrecognized keys before writing back | Legacy flat keys must round-trip unchanged |
93
+ | Setting `theme_column`/`label_column` alongside class expressions or label `text` | The two mechanisms must not be combined — leave both properties empty and use expressions/`text` |
94
+ | String expression written `[Status]='Forslag'` | Quote the column reference too for string columns: `'[Status]'='Forslag'`; numeric columns stay unquoted: `[tal]=1` |
95
+
96
+ ## WMS preview
97
+
98
+ Preview rendering with `GET /api/v4/ows/schema/{schema}/database/{database}?SERVICE=WMS&...&LAYERS={schema}.{table}`. Since GC2 `cd168572` (`dev/multiple_styles`) this single endpoint serves Bearer, HTTP Basic, and anonymous clients; the old token-only route `/api/v4/ows/schema/{schema}` is **removed**. A Bearer token must match `{database}` — a mismatch returns 401 (OGC ServiceException), never a silent downgrade to anonymous. Binary GetMap responses are a documented SDK gap — use raw HTTP there (see `centia-runtime-sdk` for the fallback boundary).
99
+
100
+ Access control for layers and OWS is covered in `centia-privileges`.