@centia-io/mcp-server 1.0.6 → 1.0.7

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/README.md CHANGED
@@ -12,11 +12,22 @@ A Model Context Protocol (MCP) server that exposes the Centia API as MCP tools g
12
12
  npm install
13
13
  ```
14
14
 
15
- ## Configuration
16
- The server accepts the following environment variables:
15
+ ## Authentication
16
+
17
+ The MCP server uses the same login as the [`gc2-cli`](https://www.npmjs.com/package/@mapcentia/gc2-cli):
18
+
19
+ ```bash
20
+ npm i -g @mapcentia/gc2-cli
21
+ gc2 connect https://api.centia.io # only if not the default
22
+ gc2 login # browser, device, or password
23
+ ```
24
+
25
+ The MCP server reads `~/.config/configstore/gc2-env.json` (managed by `gc2-cli`) and refreshes the access token automatically when it expires.
26
+
27
+ For headless / CI use, set `API_TOKEN` (and optionally `API_BASE_URL`) — env vars take precedence over the stored session:
17
28
 
18
29
  - `API_BASE_URL` (optional) — Base URL for the Centia API. Default: `https://api.centia.io`
19
- - `API_TOKEN` (recommended) — Personal access token for Centia. Most endpoints/tools require authentication; set this to enable them.
30
+ - `API_TOKEN` (optional) — Static personal access token. Bypasses the stored `gc2-cli` session.
20
31
 
21
32
  The API surface is defined in `centia-api.json` (already included in the repo). The server reads it at runtime to generate tools.
22
33
 
@@ -48,7 +59,7 @@ npm start
48
59
  ```
49
60
  This compiles TypeScript to `dist/` and starts `node dist/index.js`.
50
61
 
51
- If you need environment variables:
62
+ If you prefer a static token over the stored `gc2-cli` session (e.g. CI):
52
63
  ```bash
53
64
  API_TOKEN=your_token_here npm start
54
65
  # or
@@ -73,16 +84,14 @@ Add the server to your Claude Desktop MCP config (e.g., `claude_desktop_config.j
73
84
  "mcpServers": {
74
85
  "centia": {
75
86
  "command": "npx",
76
- "args": ["-y", "@centia/mcp-server"],
77
- "env": {
78
- "API_TOKEN": "YOUR_CENTIA_TOKEN",
79
- "API_BASE_URL": "https://api.centia.io"
80
- }
87
+ "args": ["-y", "@centia/mcp-server"]
81
88
  }
82
89
  }
83
90
  }
84
91
  ```
85
92
 
93
+ Run `gc2 login` once and the server will pick up the session. To force a static token instead, add `"env": { "API_TOKEN": "YOUR_CENTIA_TOKEN" }`.
94
+
86
95
  ### Using local source
87
96
  ```json
88
97
  {
@@ -90,10 +99,6 @@ Add the server to your Claude Desktop MCP config (e.g., `claude_desktop_config.j
90
99
  "centia": {
91
100
  "command": "npm",
92
101
  "args": ["run", "start"],
93
- "env": {
94
- "API_TOKEN": "YOUR_CENTIA_TOKEN",
95
- "API_BASE_URL": "https://api.centia.io"
96
- },
97
102
  "cwd": "/absolute/path/to/your/mcp-server"
98
103
  }
99
104
  }
@@ -135,7 +140,7 @@ To ensure your AI agent follows these rules while developing your application:
135
140
 
136
141
  ## Troubleshooting
137
142
  - Tools missing or inputs look odd: ensure `centia-api.json` exists and is valid. The server generates tools from this file at startup.
138
- - 401/403 errors: set a valid `API_TOKEN` in the environment.
143
+ - 401/403 errors: run `gc2 login` (or set a valid `API_TOKEN` in the environment for headless use).
139
144
  - JSON Schema validation errors: schemas are auto‑normalized/sanitized for MCP, but if you updated `centia-api.json`, re-run and check logs for details.
140
145
  - ESM/CommonJS issues: this project uses ESM (`"type": "module"`). Use Node.js 18+ and run scripts via npm as shown above.
141
146
 
package/centia-api.json CHANGED
@@ -99,19 +99,15 @@
99
99
  "content": {
100
100
  "application/json": {
101
101
  "schema": {
102
- "allOf": [
102
+ "oneOf": [
103
103
  {
104
- "properties": {
105
- "id": {
106
- "description": "Client ID",
107
- "type": "string",
108
- "example": "66f5005bd44c6"
109
- }
110
- },
111
- "type": "object"
104
+ "$ref": "#/components/schemas/Client"
112
105
  },
113
106
  {
114
- "$ref": "#/components/schemas/Client"
107
+ "type": "array",
108
+ "items": {
109
+ "$ref": "#/components/schemas/Client"
110
+ }
115
111
  }
116
112
  ]
117
113
  }
@@ -199,7 +195,17 @@
199
195
  "content": {
200
196
  "application/json": {
201
197
  "schema": {
202
- "$ref": "#/components/schemas/Client"
198
+ "oneOf": [
199
+ {
200
+ "$ref": "#/components/schemas/Client"
201
+ },
202
+ {
203
+ "type": "array",
204
+ "items": {
205
+ "$ref": "#/components/schemas/Client"
206
+ }
207
+ }
208
+ ]
203
209
  }
204
210
  }
205
211
  }
@@ -280,7 +286,17 @@
280
286
  "content": {
281
287
  "application/json": {
282
288
  "schema": {
283
- "$ref": "#/components/schemas/Column"
289
+ "oneOf": [
290
+ {
291
+ "$ref": "#/components/schemas/Column"
292
+ },
293
+ {
294
+ "type": "array",
295
+ "items": {
296
+ "$ref": "#/components/schemas/Column"
297
+ }
298
+ }
299
+ ]
284
300
  }
285
301
  }
286
302
  }
@@ -373,7 +389,17 @@
373
389
  "content": {
374
390
  "application/json": {
375
391
  "schema": {
376
- "$ref": "#/components/schemas/Column"
392
+ "oneOf": [
393
+ {
394
+ "$ref": "#/components/schemas/Column"
395
+ },
396
+ {
397
+ "type": "array",
398
+ "items": {
399
+ "$ref": "#/components/schemas/Column"
400
+ }
401
+ }
402
+ ]
377
403
  }
378
404
  }
379
405
  }
@@ -527,7 +553,17 @@
527
553
  "content": {
528
554
  "application/json": {
529
555
  "schema": {
530
- "$ref": "#/components/schemas/Constraint"
556
+ "oneOf": [
557
+ {
558
+ "$ref": "#/components/schemas/Constraint"
559
+ },
560
+ {
561
+ "type": "array",
562
+ "items": {
563
+ "$ref": "#/components/schemas/Constraint"
564
+ }
565
+ }
566
+ ]
531
567
  }
532
568
  }
533
569
  }
@@ -620,7 +656,17 @@
620
656
  "content": {
621
657
  "application/json": {
622
658
  "schema": {
623
- "$ref": "#/components/schemas/Constraint"
659
+ "oneOf": [
660
+ {
661
+ "$ref": "#/components/schemas/Constraint"
662
+ },
663
+ {
664
+ "type": "array",
665
+ "items": {
666
+ "$ref": "#/components/schemas/Constraint"
667
+ }
668
+ }
669
+ ]
624
670
  }
625
671
  }
626
672
  }
@@ -632,6 +678,100 @@
632
678
  }
633
679
  }
634
680
  },
681
+ "/api/v4/schemas/{schema}/tables/{table}/events": {
682
+ "get": {
683
+ "tags": [
684
+ "Events"
685
+ ],
686
+ "description": "Check if event trigger is enabled.",
687
+ "operationId": "getEvents",
688
+ "parameters": [
689
+ {
690
+ "name": "schema",
691
+ "in": "path",
692
+ "description": "Schema name",
693
+ "required": true,
694
+ "schema": {
695
+ "type": "string"
696
+ },
697
+ "example": "my_schema"
698
+ },
699
+ {
700
+ "name": "table",
701
+ "in": "path",
702
+ "description": "Table name",
703
+ "required": false,
704
+ "schema": {
705
+ "type": "string"
706
+ },
707
+ "example": "my_table"
708
+ }
709
+ ],
710
+ "responses": {
711
+ "200": {
712
+ "description": "Ok",
713
+ "content": {
714
+ "application/json": {
715
+ "schema": {
716
+ "$ref": "#/components/schemas/Events"
717
+ }
718
+ }
719
+ }
720
+ },
721
+ "400": {
722
+ "description": "Bad request"
723
+ },
724
+ "404": {
725
+ "description": "Not found"
726
+ }
727
+ }
728
+ },
729
+ "patch": {
730
+ "tags": [
731
+ "Events"
732
+ ],
733
+ "description": "Install event trigger.",
734
+ "operationId": "postEvents",
735
+ "parameters": [
736
+ {
737
+ "name": "schema",
738
+ "in": "path",
739
+ "description": "Schema name",
740
+ "required": true,
741
+ "example": "my_schema"
742
+ },
743
+ {
744
+ "name": "table",
745
+ "in": "path",
746
+ "description": "Table name",
747
+ "required": true,
748
+ "example": "my_table"
749
+ }
750
+ ],
751
+ "requestBody": {
752
+ "description": "Privileges.",
753
+ "required": true,
754
+ "content": {
755
+ "application/json": {
756
+ "schema": {
757
+ "$ref": "#/components/schemas/Events"
758
+ }
759
+ }
760
+ }
761
+ },
762
+ "responses": {
763
+ "201": {
764
+ "description": "Event trigger installed."
765
+ },
766
+ "400": {
767
+ "description": "Bad request"
768
+ },
769
+ "404": {
770
+ "description": "Not found"
771
+ }
772
+ }
773
+ }
774
+ },
635
775
  "/api/v4/file/upload": {
636
776
  "post": {
637
777
  "tags": [
@@ -675,14 +815,24 @@
675
815
  "content": {
676
816
  "application/json": {
677
817
  "schema": {
678
- "$ref": "#/components/schemas/File"
818
+ "$ref": "#/components/schemas/FileProcessRequest"
679
819
  }
680
820
  }
681
821
  }
682
822
  },
683
823
  "responses": {
684
- "201": {
685
- "description": "Created"
824
+ "200": {
825
+ "description": "Files processed.",
826
+ "content": {
827
+ "application/json": {
828
+ "schema": {
829
+ "type": "array",
830
+ "items": {
831
+ "$ref": "#/components/schemas/FileProcessResponse"
832
+ }
833
+ }
834
+ }
835
+ }
686
836
  },
687
837
  "404": {
688
838
  "description": "Not found"
@@ -715,7 +865,17 @@
715
865
  "content": {
716
866
  "application/json": {
717
867
  "schema": {
718
- "$ref": "#/components/schemas/Rule"
868
+ "oneOf": [
869
+ {
870
+ "$ref": "#/components/schemas/Rule"
871
+ },
872
+ {
873
+ "type": "array",
874
+ "items": {
875
+ "$ref": "#/components/schemas/Rule"
876
+ }
877
+ }
878
+ ]
719
879
  }
720
880
  }
721
881
  }
@@ -807,7 +967,17 @@
807
967
  "content": {
808
968
  "application/json": {
809
969
  "schema": {
810
- "$ref": "#/components/schemas/Rule"
970
+ "oneOf": [
971
+ {
972
+ "$ref": "#/components/schemas/Rule"
973
+ },
974
+ {
975
+ "type": "array",
976
+ "items": {
977
+ "$ref": "#/components/schemas/Rule"
978
+ }
979
+ }
980
+ ]
811
981
  }
812
982
  }
813
983
  }
@@ -914,19 +1084,43 @@
914
1084
  "content": {
915
1085
  "application/json": {
916
1086
  "schema": {
917
- "allOf": [
1087
+ "oneOf": [
918
1088
  {
919
- "properties": {
920
- "unique": {
921
- "description": "If the index has a unique constraint",
922
- "type": "boolean",
923
- "example": true
1089
+ "allOf": [
1090
+ {
1091
+ "properties": {
1092
+ "unique": {
1093
+ "description": "If the index has a unique constraint",
1094
+ "type": "boolean",
1095
+ "example": true
1096
+ }
1097
+ },
1098
+ "type": "object"
1099
+ },
1100
+ {
1101
+ "$ref": "#/components/schemas/Index"
924
1102
  }
925
- },
926
- "type": "object"
1103
+ ]
927
1104
  },
928
1105
  {
929
- "$ref": "#/components/schemas/Index"
1106
+ "type": "array",
1107
+ "items": {
1108
+ "allOf": [
1109
+ {
1110
+ "properties": {
1111
+ "unique": {
1112
+ "description": "If the index has a unique constraint",
1113
+ "type": "boolean",
1114
+ "example": true
1115
+ }
1116
+ },
1117
+ "type": "object"
1118
+ },
1119
+ {
1120
+ "$ref": "#/components/schemas/Index"
1121
+ }
1122
+ ]
1123
+ }
930
1124
  }
931
1125
  ]
932
1126
  }
@@ -1018,7 +1212,17 @@
1018
1212
  "content": {
1019
1213
  "application/json": {
1020
1214
  "schema": {
1021
- "$ref": "#/components/schemas/Index"
1215
+ "oneOf": [
1216
+ {
1217
+ "$ref": "#/components/schemas/Index"
1218
+ },
1219
+ {
1220
+ "type": "array",
1221
+ "items": {
1222
+ "$ref": "#/components/schemas/Index"
1223
+ }
1224
+ }
1225
+ ]
1022
1226
  }
1023
1227
  }
1024
1228
  }
@@ -1133,7 +1337,17 @@
1133
1337
  "content": {
1134
1338
  "application/json": {
1135
1339
  "schema": {
1136
- "$ref": "#/components/schemas/Method"
1340
+ "oneOf": [
1341
+ {
1342
+ "$ref": "#/components/schemas/Method"
1343
+ },
1344
+ {
1345
+ "type": "array",
1346
+ "items": {
1347
+ "$ref": "#/components/schemas/Method"
1348
+ }
1349
+ }
1350
+ ]
1137
1351
  }
1138
1352
  }
1139
1353
  }
@@ -1225,7 +1439,17 @@
1225
1439
  "content": {
1226
1440
  "application/json": {
1227
1441
  "schema": {
1228
- "$ref": "#/components/schemas/Method"
1442
+ "oneOf": [
1443
+ {
1444
+ "$ref": "#/components/schemas/Method"
1445
+ },
1446
+ {
1447
+ "type": "array",
1448
+ "items": {
1449
+ "$ref": "#/components/schemas/Method"
1450
+ }
1451
+ }
1452
+ ]
1229
1453
  }
1230
1454
  }
1231
1455
  }
@@ -1342,7 +1566,17 @@
1342
1566
  "content": {
1343
1567
  "application/json": {
1344
1568
  "schema": {
1345
- "$ref": "#/components/schemas/Privilege"
1569
+ "oneOf": [
1570
+ {
1571
+ "$ref": "#/components/schemas/Privilege"
1572
+ },
1573
+ {
1574
+ "type": "array",
1575
+ "items": {
1576
+ "$ref": "#/components/schemas/Privilege"
1577
+ }
1578
+ }
1579
+ ]
1346
1580
  }
1347
1581
  }
1348
1582
  }
@@ -1375,12 +1609,22 @@
1375
1609
  }
1376
1610
  ],
1377
1611
  "requestBody": {
1378
- "description": "Privileges",
1612
+ "description": "Privileges.",
1379
1613
  "required": true,
1380
1614
  "content": {
1381
1615
  "application/json": {
1382
1616
  "schema": {
1383
- "$ref": "#/components/schemas/Privilege"
1617
+ "oneOf": [
1618
+ {
1619
+ "$ref": "#/components/schemas/Privilege"
1620
+ },
1621
+ {
1622
+ "type": "array",
1623
+ "items": {
1624
+ "$ref": "#/components/schemas/Privilege"
1625
+ }
1626
+ }
1627
+ ]
1384
1628
  }
1385
1629
  }
1386
1630
  }
@@ -1450,7 +1694,17 @@
1450
1694
  "content": {
1451
1695
  "application/json": {
1452
1696
  "schema": {
1453
- "$ref": "#/components/schemas/Schema"
1697
+ "oneOf": [
1698
+ {
1699
+ "$ref": "#/components/schemas/Schema"
1700
+ },
1701
+ {
1702
+ "type": "array",
1703
+ "items": {
1704
+ "$ref": "#/components/schemas/Schema"
1705
+ }
1706
+ }
1707
+ ]
1454
1708
  }
1455
1709
  }
1456
1710
  },
@@ -1571,7 +1825,17 @@
1571
1825
  "content": {
1572
1826
  "application/json": {
1573
1827
  "schema": {
1574
- "$ref": "#/components/schemas/Schema"
1828
+ "oneOf": [
1829
+ {
1830
+ "$ref": "#/components/schemas/Schema"
1831
+ },
1832
+ {
1833
+ "type": "array",
1834
+ "items": {
1835
+ "$ref": "#/components/schemas/Schema"
1836
+ }
1837
+ }
1838
+ ]
1575
1839
  }
1576
1840
  }
1577
1841
  }
@@ -1621,7 +1885,17 @@
1621
1885
  "content": {
1622
1886
  "application/json": {
1623
1887
  "schema": {
1624
- "$ref": "#/components/schemas/Sequence"
1888
+ "oneOf": [
1889
+ {
1890
+ "$ref": "#/components/schemas/Sequence"
1891
+ },
1892
+ {
1893
+ "type": "array",
1894
+ "items": {
1895
+ "$ref": "#/components/schemas/Sequence"
1896
+ }
1897
+ }
1898
+ ]
1625
1899
  }
1626
1900
  }
1627
1901
  }
@@ -1745,7 +2019,17 @@
1745
2019
  "content": {
1746
2020
  "application/json": {
1747
2021
  "schema": {
1748
- "$ref": "#/components/schemas/Sequence"
2022
+ "oneOf": [
2023
+ {
2024
+ "$ref": "#/components/schemas/Sequence"
2025
+ },
2026
+ {
2027
+ "type": "array",
2028
+ "items": {
2029
+ "$ref": "#/components/schemas/Sequence"
2030
+ }
2031
+ }
2032
+ ]
1749
2033
  }
1750
2034
  }
1751
2035
  }
@@ -1908,7 +2192,17 @@
1908
2192
  "content": {
1909
2193
  "application/json": {
1910
2194
  "schema": {
1911
- "$ref": "#/components/schemas/Table"
2195
+ "oneOf": [
2196
+ {
2197
+ "$ref": "#/components/schemas/Table"
2198
+ },
2199
+ {
2200
+ "type": "array",
2201
+ "items": {
2202
+ "$ref": "#/components/schemas/Table"
2203
+ }
2204
+ }
2205
+ ]
1912
2206
  }
1913
2207
  }
1914
2208
  },
@@ -2094,7 +2388,17 @@
2094
2388
  "content": {
2095
2389
  "application/json": {
2096
2390
  "schema": {
2097
- "$ref": "#/components/schemas/Table"
2391
+ "oneOf": [
2392
+ {
2393
+ "$ref": "#/components/schemas/Table"
2394
+ },
2395
+ {
2396
+ "type": "array",
2397
+ "items": {
2398
+ "$ref": "#/components/schemas/Table"
2399
+ }
2400
+ }
2401
+ ]
2098
2402
  }
2099
2403
  }
2100
2404
  }
@@ -2134,7 +2438,17 @@
2134
2438
  "content": {
2135
2439
  "application/json": {
2136
2440
  "schema": {
2137
- "$ref": "#/components/schemas/User"
2441
+ "oneOf": [
2442
+ {
2443
+ "$ref": "#/components/schemas/User"
2444
+ },
2445
+ {
2446
+ "type": "array",
2447
+ "items": {
2448
+ "$ref": "#/components/schemas/User"
2449
+ }
2450
+ }
2451
+ ]
2138
2452
  }
2139
2453
  }
2140
2454
  }
@@ -2226,7 +2540,17 @@
2226
2540
  "content": {
2227
2541
  "application/json": {
2228
2542
  "schema": {
2229
- "$ref": "#/components/schemas/User"
2543
+ "oneOf": [
2544
+ {
2545
+ "$ref": "#/components/schemas/User"
2546
+ },
2547
+ {
2548
+ "type": "array",
2549
+ "items": {
2550
+ "$ref": "#/components/schemas/User"
2551
+ }
2552
+ }
2553
+ ]
2230
2554
  }
2231
2555
  }
2232
2556
  }
@@ -2526,7 +2850,22 @@
2526
2850
  },
2527
2851
  "type": "object"
2528
2852
  },
2529
- "File": {
2853
+ "Events": {
2854
+ "description": "Events for a table.",
2855
+ "required": [
2856
+ "enabled"
2857
+ ],
2858
+ "properties": {
2859
+ "enabled": {
2860
+ "title": "Enabled",
2861
+ "description": "Enable or disable events for this table.",
2862
+ "type": "boolean",
2863
+ "example": true
2864
+ }
2865
+ },
2866
+ "type": "object"
2867
+ },
2868
+ "FileProcessRequest": {
2530
2869
  "description": "Upload files to temporary storage and then import them into database tables.",
2531
2870
  "required": [
2532
2871
  "file",
@@ -2602,6 +2941,64 @@
2602
2941
  },
2603
2942
  "type": "object"
2604
2943
  },
2944
+ "FileProcessResponse": {
2945
+ "description": "Upload files to temporary storage and then import them into database tables.",
2946
+ "required": [
2947
+ "driver",
2948
+ "count",
2949
+ "geom_type",
2950
+ "index",
2951
+ "name",
2952
+ "has_wkt",
2953
+ "auth_str",
2954
+ "error"
2955
+ ],
2956
+ "properties": {
2957
+ "driver": {
2958
+ "title": "Driver",
2959
+ "description": "Driver name.",
2960
+ "type": "string"
2961
+ },
2962
+ "count": {
2963
+ "title": "Count",
2964
+ "description": "Count of rows.",
2965
+ "type": "integer"
2966
+ },
2967
+ "geom_type": {
2968
+ "title": "Geometry type",
2969
+ "description": "Type of geometry if PostGIS table is created.",
2970
+ "type": "string",
2971
+ "example": "Multipolygon"
2972
+ },
2973
+ "index": {
2974
+ "title": "Index",
2975
+ "description": "Index of multi layered geometry files like GML and GeoPackage",
2976
+ "type": "integer"
2977
+ },
2978
+ "name": {
2979
+ "title": "Name",
2980
+ "description": "Name of created table.",
2981
+ "type": "string"
2982
+ },
2983
+ "has_wkt": {
2984
+ "title": "Has WKT",
2985
+ "description": "If file has WKT projection information.",
2986
+ "type": "boolean"
2987
+ },
2988
+ "auth_str": {
2989
+ "title": "Authority string",
2990
+ "description": "EPSG Authority string.",
2991
+ "type": "string",
2992
+ "example": "EPSG:25832"
2993
+ },
2994
+ "error": {
2995
+ "title": "Error",
2996
+ "description": "Error message.",
2997
+ "type": "string"
2998
+ }
2999
+ },
3000
+ "type": "object"
3001
+ },
2605
3002
  "Rule": {
2606
3003
  "description": "Access-control rules applied to incoming SQL before execution (allow, deny, or limit via WHERE clause).",
2607
3004
  "required": [],
@@ -3044,6 +3441,49 @@
3044
3441
  },
3045
3442
  "type": "object"
3046
3443
  },
3444
+ "Outbox": {
3445
+ "description": "",
3446
+ "required": [],
3447
+ "properties": {
3448
+ "id": {
3449
+ "title": "Unique identifier",
3450
+ "description": "",
3451
+ "type": "integer",
3452
+ "example": 1000
3453
+ },
3454
+ "operation": {
3455
+ "title": "Operation",
3456
+ "description": "",
3457
+ "type": "integer",
3458
+ "example": 1000
3459
+ },
3460
+ "schema": {
3461
+ "title": "Schema",
3462
+ "description": "",
3463
+ "type": "string",
3464
+ "example": "my_schema"
3465
+ },
3466
+ "pk_value": {
3467
+ "title": "Primary key value",
3468
+ "description": "",
3469
+ "type": "any",
3470
+ "example": "my_schema"
3471
+ },
3472
+ "payload": {
3473
+ "title": "Payload",
3474
+ "description": "",
3475
+ "type": "any",
3476
+ "example": "my_schema"
3477
+ },
3478
+ "created_at": {
3479
+ "title": "Created at",
3480
+ "description": "",
3481
+ "type": "timestamp",
3482
+ "example": ""
3483
+ }
3484
+ },
3485
+ "type": "object"
3486
+ },
3047
3487
  "Privilege": {
3048
3488
  "description": "Privileges define read/write access for sub-users on tables.",
3049
3489
  "required": [
@@ -3462,6 +3902,10 @@
3462
3902
  "name": "Commit",
3463
3903
  "description": "Commit"
3464
3904
  },
3905
+ {
3906
+ "name": "Events",
3907
+ "description": "Events"
3908
+ },
3465
3909
  {
3466
3910
  "name": "File",
3467
3911
  "description": "File"
package/dist/index.js CHANGED
@@ -6,11 +6,39 @@ import axios from "axios";
6
6
  import fs from "fs";
7
7
  import path from "path";
8
8
  import { fileURLToPath } from "url";
9
+ import { createConfigstoreTokenStore, createTokenProvider, CodeFlow, NotLoggedInError, SessionExpiredError, } from "@centia-io/sdk";
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
11
  const apiSpecPath = path.join(__dirname, "..", "centia-api.json");
11
12
  const apiSpec = JSON.parse(fs.readFileSync(apiSpecPath, "utf-8"));
12
- const API_BASE_URL = process.env.API_BASE_URL || "https://api.centia.io";
13
- const API_TOKEN = process.env.API_TOKEN;
13
+ const DEFAULT_HOST = "https://api.centia.io";
14
+ const tokenStore = createConfigstoreTokenStore("gc2-env"); // shared with gc2-cli
15
+ async function getApiBaseUrl() {
16
+ if (process.env.API_BASE_URL)
17
+ return process.env.API_BASE_URL;
18
+ const stored = await tokenStore.get();
19
+ return stored.host || DEFAULT_HOST;
20
+ }
21
+ let cachedProvider = null;
22
+ async function getTokenProvider() {
23
+ if (!cachedProvider) {
24
+ const host = await getApiBaseUrl();
25
+ // TODO: register a separate "gc2-mcp" OAuth client server-side and switch
26
+ const authService = new CodeFlow({
27
+ host,
28
+ clientId: "gc2-cli",
29
+ redirectUri: "http://127.0.0.1:5657/auth/callback",
30
+ }).service;
31
+ cachedProvider = createTokenProvider({ store: tokenStore, authService });
32
+ }
33
+ return cachedProvider;
34
+ }
35
+ async function getAccessToken() {
36
+ // Env wins for CI / headless
37
+ if (process.env.API_TOKEN)
38
+ return process.env.API_TOKEN;
39
+ const provider = await getTokenProvider();
40
+ return provider.getAccessToken();
41
+ }
14
42
  const server = new Server({
15
43
  name: "centia-io-mcp-server",
16
44
  version: "1.0.0",
@@ -341,14 +369,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
341
369
  if (!toolMeta) {
342
370
  throw new Error(`Tool not found: ${name}`);
343
371
  }
344
- let url = `${API_BASE_URL}${toolMeta.path}`;
372
+ let url = `${await getApiBaseUrl()}${toolMeta.path}`;
345
373
  const config = {
346
374
  method: toolMeta.method,
347
375
  headers: {},
348
376
  params: {},
349
377
  };
350
- if (API_TOKEN) {
351
- config.headers["Authorization"] = `Bearer ${API_TOKEN}`;
378
+ try {
379
+ config.headers["Authorization"] = `Bearer ${await getAccessToken()}`;
380
+ }
381
+ catch (e) {
382
+ if (e instanceof NotLoggedInError) {
383
+ return {
384
+ isError: true,
385
+ content: [{
386
+ type: "text",
387
+ text: "Not logged in to Centia. Run `gc2 login` (npm i -g @mapcentia/gc2-cli) " +
388
+ "or set API_TOKEN env var.",
389
+ }],
390
+ };
391
+ }
392
+ if (e instanceof SessionExpiredError) {
393
+ return {
394
+ isError: true,
395
+ content: [{
396
+ type: "text",
397
+ text: "Centia session expired. Run `gc2 login` again.",
398
+ }],
399
+ };
400
+ }
401
+ throw e;
352
402
  }
353
403
  const safeArgs = args || {};
354
404
  // Path parameters
@@ -410,36 +460,79 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
410
460
  config.data = safeArgs.requestBody;
411
461
  }
412
462
  }
463
+ const sendRequest = () => axios({
464
+ ...config,
465
+ url,
466
+ maxRedirects: 0,
467
+ validateStatus: (status) => status < 400,
468
+ });
469
+ const formatSuccess = (response) => ({
470
+ content: [
471
+ {
472
+ type: "text",
473
+ text: response.data != null ? JSON.stringify(response.data, null, 2) : response.statusText,
474
+ },
475
+ ],
476
+ });
413
477
  try {
414
- const response = await axios({ ...config, url, maxRedirects: 0, validateStatus: (status) => status < 400 });
415
- return {
416
- content: [
417
- {
418
- type: "text",
419
- text: response.data != null ? JSON.stringify(response.data, null, 2) : "OK",
420
- },
421
- ],
422
- };
478
+ return formatSuccess(await sendRequest());
423
479
  }
424
480
  catch (error) {
481
+ // 401: force a token refresh and retry once. Skip when an explicit
482
+ // API_TOKEN env is in use — refresh isn't possible there.
483
+ if (error.response?.status === 401 && !process.env.API_TOKEN) {
484
+ try {
485
+ await tokenStore.set({ token: undefined });
486
+ const refreshed = await getAccessToken();
487
+ config.headers["Authorization"] = `Bearer ${refreshed}`;
488
+ return formatSuccess(await sendRequest());
489
+ }
490
+ catch (retryError) {
491
+ if (retryError instanceof NotLoggedInError || retryError instanceof SessionExpiredError) {
492
+ return {
493
+ isError: true,
494
+ content: [{
495
+ type: "text",
496
+ text: "Centia session expired. Run `gc2 login` again.",
497
+ }],
498
+ };
499
+ }
500
+ error = retryError;
501
+ }
502
+ }
425
503
  return {
426
504
  isError: true,
427
505
  content: [
428
506
  {
429
507
  type: "text",
430
- text: error.response.data.message || error.message,
508
+ text: error.response?.data?.message || error.message,
431
509
  },
432
510
  ],
433
511
  };
434
512
  }
435
513
  });
514
+ async function logAuthStatus() {
515
+ if (process.env.API_TOKEN) {
516
+ console.error("Centia MCP: using API_TOKEN env");
517
+ return;
518
+ }
519
+ const stored = await tokenStore.get();
520
+ if (!stored.token) {
521
+ console.error("Centia MCP: no login found. Run `gc2 login` or set API_TOKEN.");
522
+ return;
523
+ }
524
+ try {
525
+ const claims = JSON.parse(Buffer.from(stored.token.split(".")[1], "base64").toString("utf-8"));
526
+ console.error(`Centia MCP: logged in as ${claims.uid} on ${stored.host || DEFAULT_HOST}`);
527
+ }
528
+ catch {
529
+ console.error("Centia MCP: token present but unreadable");
530
+ }
531
+ }
436
532
  async function main() {
533
+ await logAuthStatus();
437
534
  const transport = new StdioServerTransport();
438
535
  await server.connect(transport);
439
- await server.sendLoggingMessage({
440
- level: "info",
441
- data: "Server started successfully",
442
- });
443
536
  console.error("Centia MCP Server running on stdio");
444
537
  }
445
538
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centia-io/mcp-server",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "publishConfig": { "access": "public" },
5
5
  "description": "Centia MCP Server",
6
6
  "type": "module",
@@ -20,6 +20,7 @@
20
20
  "author": "",
21
21
  "license": "ISC",
22
22
  "dependencies": {
23
+ "@centia-io/sdk": "^0.1.0",
23
24
  "@modelcontextprotocol/sdk": "^1.26.0",
24
25
  "axios": "^1.13.5",
25
26
  "zod": "^4.3.6"