@centia-io/mcp-server 1.0.5 → 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 +148 -131
- package/centia-api.json +3946 -3134
- package/dist/index.js +112 -15
- package/package.json +33 -32
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
|
|
13
|
-
const
|
|
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 = `${
|
|
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
|
-
|
|
351
|
-
config.headers["Authorization"] = `Bearer ${
|
|
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,30 +460,77 @@ 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
|
-
|
|
415
|
-
return {
|
|
416
|
-
content: [
|
|
417
|
-
{
|
|
418
|
-
type: "text",
|
|
419
|
-
text: response.status === 200 ? JSON.stringify(response.data, null, 2) : response.statusText,
|
|
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
|
|
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
536
|
console.error("Centia MCP Server running on stdio");
|
package/package.json
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@centia-io/mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"publishConfig": { "access": "public" },
|
|
5
|
-
"description": "Centia MCP Server",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"mcp-server": "dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"centia-api.json"
|
|
13
|
-
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "tsc",
|
|
16
|
-
"start": "node dist/index.js",
|
|
17
|
-
"dev": "tsx src/index.ts"
|
|
18
|
-
},
|
|
19
|
-
"keywords": [],
|
|
20
|
-
"author": "",
|
|
21
|
-
"license": "ISC",
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@centia-io/mcp-server",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"publishConfig": { "access": "public" },
|
|
5
|
+
"description": "Centia MCP Server",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"centia-api.json"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"dev": "tsx src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@centia-io/sdk": "^0.1.0",
|
|
24
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
25
|
+
"axios": "^1.13.5",
|
|
26
|
+
"zod": "^4.3.6"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.2.2",
|
|
30
|
+
"tsx": "^4.21.0",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
}
|
|
33
|
+
}
|