Skip to content

Commit a3911ed

Browse files
committed
chore: experimental mcp server
1 parent 9942a72 commit a3911ed

4 files changed

Lines changed: 1732 additions & 0 deletions

File tree

mcp/README.md

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
# KFS MCP Server
2+
3+
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that exposes the **Kalisio Features Service** (KFS) to Claude and other MCP-compatible clients.
4+
5+
KFS implements the [OGC API Features](https://ogcapi.ogc.org/features/) standard (Part 1). This MCP server wraps every KFS endpoint as a tool so you can explore datasets, query features, apply spatial/temporal/CQL filters and retrieve individual features — all from a natural-language conversation.
6+
7+
---
8+
9+
## Prerequisites
10+
11+
- Node.js >= 18
12+
- A running KFS instance (see the [KFS documentation](../README.md))
13+
- [Claude Desktop](https://claude.ai/download) or another MCP-compatible client
14+
15+
---
16+
17+
## Installation
18+
19+
```bash
20+
cd mcp
21+
npm install
22+
```
23+
24+
---
25+
26+
## Configuration
27+
28+
One environment variable controls the server:
29+
30+
| Variable | Default | Description |
31+
|-----------|--------------------------------|----------------------------------------------------------|
32+
| `KFS_URL` | `http://localhost:8081/api` | Base URL of the KFS API |
33+
| `KFS_JWT` | *(unset)* | JWT sent as `Authorization: Bearer <token>` on every request. Required only when KFS is deployed behind authentication. |
34+
35+
---
36+
37+
## Usage with Claude Desktop
38+
39+
Add the server to your `claude_desktop_config.json`
40+
(usually `~/.config/Claude/claude_desktop_config.json` on Linux,
41+
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
42+
43+
```json
44+
{
45+
"mcpServers": {
46+
"kfs": {
47+
"command": "node",
48+
"args": ["/absolute/path/to/kfs/mcp/server.js"],
49+
"env": {
50+
"KFS_URL": "http://localhost:8081/api",
51+
"KFS_JWT": "your.jwt.token"
52+
}
53+
}
54+
}
55+
}
56+
```
57+
58+
Replace `/absolute/path/to/kfs` with the actual path to this repository.
59+
Restart Claude Desktop after saving the file.
60+
61+
### Pointing at a remote authenticated instance
62+
63+
```json
64+
"env": {
65+
"KFS_URL": "https://my-kfs-server.example.com/api",
66+
"KFS_JWT": "your.jwt.token"
67+
}
68+
```
69+
70+
`KFS_JWT` is optional — omit it entirely for unauthenticated deployments.
71+
72+
---
73+
74+
## Usage with Claude Code (CLI)
75+
76+
Add the server to your project or user MCP settings:
77+
78+
```bash
79+
claude mcp add kfs -- node /absolute/path/to/kfs/mcp/server.js
80+
```
81+
82+
Or with a custom URL and JWT:
83+
84+
```bash
85+
claude mcp add kfs \
86+
-e KFS_URL=https://my-kfs-server.example.com/api \
87+
-e KFS_JWT=your.jwt.token \
88+
-- node /absolute/path/to/kfs/mcp/server.js
89+
```
90+
91+
---
92+
93+
## Available Tools
94+
95+
### `healthcheck`
96+
Check that the KFS service is reachable and return its version.
97+
98+
---
99+
100+
### `get_landing_page`
101+
Retrieve the OGC API Features landing page: service title, description and links to the conformance, definition and collections endpoints.
102+
103+
---
104+
105+
### `get_conformance`
106+
Return the list of OGC conformance classes supported by this KFS instance.
107+
108+
---
109+
110+
### `get_api_definition`
111+
Fetch the full OpenAPI 3.0 specification of the service.
112+
113+
---
114+
115+
### `list_collections`
116+
List all feature collections currently exposed by KFS, with their ids, titles and spatial extents.
117+
118+
---
119+
120+
### `get_collection`
121+
122+
Get metadata for one collection.
123+
124+
| Parameter | Type | Required | Description |
125+
|------------|--------|----------|--------------------------------------------------|
126+
| `name` | string | yes | Collection id, e.g. `myLayer` or `myLayer~sub` |
127+
| `context` | string | no | Context segment (user/org id) for scoped layers |
128+
129+
---
130+
131+
### `get_features`
132+
133+
Query features from a collection. Returns a GeoJSON FeatureCollection.
134+
135+
| Parameter | Type | Required | Description |
136+
|---------------|------------------|----------|-------------|
137+
| `name` | string | yes | Collection id |
138+
| `context` | string | no | Context segment |
139+
| `limit` | integer | no | Max features to return |
140+
| `offset` | integer | no | Skip N features (for pagination) |
141+
| `bbox` | string | no | Spatial filter: `"minLon,minLat,maxLon,maxLat"` in WGS 84 |
142+
| `bbox_crs` | string | no | CRS of the bbox (OGC URI/URN, default WGS 84) |
143+
| `datetime` | string | no | Temporal filter (ISO 8601 instant or interval) |
144+
| `sortby` | string | no | Sort fields, e.g. `"-time,+name"` |
145+
| `filter` | string | no | CQL filter expression |
146+
| `filter_lang` | `cql-text`\|`cql-json` | no | CQL dialect (default `cql-text`) |
147+
| `properties` | object | no | Additional property equality filters |
148+
149+
#### Temporal filter examples
150+
151+
```
152+
# Single instant
153+
2024-01-15T12:00:00Z
154+
155+
# Closed interval
156+
2024-01-01T00:00:00Z/2024-01-31T23:59:59Z
157+
158+
# Open-ended (from a date onward)
159+
2024-01-01T00:00:00Z/..
160+
```
161+
162+
#### CQL filter examples (cql-text)
163+
164+
```
165+
# Numeric comparison
166+
temperature > 20
167+
168+
# Combined with logical operators
169+
temperature > 20 AND status = 'active'
170+
171+
# Spatial filter (WKT geometry)
172+
S_INTERSECTS(geometry, POLYGON((2.3 48.8, 2.4 48.8, 2.4 48.9, 2.3 48.9, 2.3 48.8)))
173+
174+
# Null check
175+
windSpeed IS NULL
176+
177+
# In list
178+
category IN ('A', 'B', 'C')
179+
```
180+
181+
#### CQL filter example (cql-json)
182+
183+
```json
184+
{
185+
"op": "and",
186+
"args": [
187+
{ "op": "gt", "args": [{ "property": "temperature" }, 20] },
188+
{ "op": "eq", "args": [{ "property": "status" }, "active"] }
189+
]
190+
}
191+
```
192+
193+
#### Sorting examples
194+
195+
```
196+
# Descending time (most recent first)
197+
-time
198+
199+
# Multiple fields
200+
-time,+name
201+
```
202+
203+
#### Property filter examples (the `properties` parameter)
204+
205+
```json
206+
{ "status": "active", "country": "FR" }
207+
```
208+
209+
To avoid automatic type conversion of a numeric-looking string, wrap it in single quotes:
210+
211+
```json
212+
{ "code": "'1000'" }
213+
```
214+
215+
---
216+
217+
### `get_feature`
218+
219+
Retrieve a single feature by its id.
220+
221+
| Parameter | Type | Required | Description |
222+
|-----------|--------|----------|-----------------------|
223+
| `name` | string | yes | Collection id |
224+
| `id` | string | yes | Feature id |
225+
| `context` | string | no | Context segment |
226+
227+
---
228+
229+
## Example conversation
230+
231+
Once the MCP server is connected, you can ask Claude things like:
232+
233+
> *"List all available datasets in KFS."*
234+
235+
> *"Show me the 10 most recent features in the `weather-stations` collection."*
236+
237+
> *"Find weather stations in metropolitan France (bbox: -5.14,41.33,9.56,51.09) where temperature is above 30°C."*
238+
239+
> *"Get the details of feature `64a3f1c2b0e12d0011abcdef` from the `weather-stations` collection."*
240+
241+
> *"Query the `fires` collection for events that occurred between 2024-06-01 and 2024-08-31 and sort them by descending time."*
242+
243+
---
244+
245+
## Supported CQL operators
246+
247+
| Category | Operators |
248+
|-------------|--------------------------------------------------|
249+
| Logical | `AND`, `OR`, `NOT` |
250+
| Comparison | `=`, `<`, `>`, `<=`, `>=`, `BETWEEN`, `IN` |
251+
| Spatial | `S_INTERSECTS`, `S_WITHIN` |
252+
| Temporal | `T_BEFORE`, `T_AFTER`, `T_DURING` |
253+
| Null | `IS NULL` |
254+
255+
---
256+
257+
## Running the server manually (for testing)
258+
259+
```bash
260+
# Unauthenticated
261+
KFS_URL=http://localhost:8081/api node server.js
262+
263+
# Authenticated
264+
KFS_URL=https://my-kfs-server.example.com/api KFS_JWT=your.jwt.token node server.js
265+
```
266+
267+
The server communicates over stdio (standard MCP transport), so no HTTP port is opened.

0 commit comments

Comments
 (0)