@topolo/mcp 0.8.0 → 0.9.1
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 +29 -1
- package/dist/http.js +172 -172
- package/dist/index.js +114 -18
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// ../../../topolo-platform/packages/topolo-sdk/dist/index.js
|
|
7
7
|
function applyAuthHeaders(headers, credential) {
|
|
8
8
|
if (credential.kind === "api_key") {
|
|
9
9
|
headers.set("X-Api-Key", credential.apiKey);
|
|
@@ -3369,7 +3369,7 @@ function createTopolo(options) {
|
|
|
3369
3369
|
};
|
|
3370
3370
|
}
|
|
3371
3371
|
|
|
3372
|
-
//
|
|
3372
|
+
// ../../../topolo-platform/packages/topolo-sdk/dist/node/credential-store.js
|
|
3373
3373
|
import { mkdir, readFile, writeFile, chmod } from "fs/promises";
|
|
3374
3374
|
import { existsSync } from "fs";
|
|
3375
3375
|
import { homedir, platform } from "os";
|
|
@@ -3699,6 +3699,46 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
|
|
|
3699
3699
|
return cred;
|
|
3700
3700
|
}
|
|
3701
3701
|
}
|
|
3702
|
+
async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
|
|
3703
|
+
const config = await readConfig();
|
|
3704
|
+
const profile = profileForEnvironment(config, environment);
|
|
3705
|
+
const context = profile.contexts?.[key];
|
|
3706
|
+
const credential = context?.credential;
|
|
3707
|
+
if (!credential || credential.kind !== "access_token") return credential ?? null;
|
|
3708
|
+
const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
|
|
3709
|
+
if (!credential.refreshToken || !credential.expiresAt || !oauthClientId) return credential;
|
|
3710
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
3711
|
+
if (!options.force && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
|
|
3712
|
+
try {
|
|
3713
|
+
const oauth = new TopoloOAuth({
|
|
3714
|
+
serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
|
|
3715
|
+
});
|
|
3716
|
+
const tokens = await oauth.refreshToken({
|
|
3717
|
+
clientId: oauthClientId,
|
|
3718
|
+
refreshToken: credential.refreshToken
|
|
3719
|
+
});
|
|
3720
|
+
const refreshed = {
|
|
3721
|
+
kind: "access_token",
|
|
3722
|
+
accessToken: tokens.access_token,
|
|
3723
|
+
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
|
|
3724
|
+
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
3725
|
+
};
|
|
3726
|
+
await writeConfig(writeProfileConfig(config, environment, {
|
|
3727
|
+
...profile.activeContext === key ? { credential: refreshed } : {},
|
|
3728
|
+
contexts: {
|
|
3729
|
+
...profile.contexts ?? {},
|
|
3730
|
+
[key]: {
|
|
3731
|
+
...context,
|
|
3732
|
+
credential: refreshed,
|
|
3733
|
+
oauthClientId
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
}));
|
|
3737
|
+
return refreshed;
|
|
3738
|
+
} catch {
|
|
3739
|
+
return credential;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3702
3742
|
function resolveAgentName(configAgentName) {
|
|
3703
3743
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
|
3704
3744
|
}
|
|
@@ -3721,15 +3761,6 @@ function resolveServiceUrls(configUrls, environment = selectedEnvironment()) {
|
|
|
3721
3761
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
3722
3762
|
}
|
|
3723
3763
|
|
|
3724
|
-
// src/auth.ts
|
|
3725
|
-
function resolveCredentialFromEnv() {
|
|
3726
|
-
const apiKey = process.env.TOPOLO_API_KEY;
|
|
3727
|
-
if (apiKey) return { kind: "api_key", apiKey };
|
|
3728
|
-
const accessToken = process.env.TOPOLO_ACCESS_TOKEN;
|
|
3729
|
-
if (accessToken) return { kind: "access_token", accessToken };
|
|
3730
|
-
return null;
|
|
3731
|
-
}
|
|
3732
|
-
|
|
3733
3764
|
// src/server.ts
|
|
3734
3765
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3735
3766
|
import {
|
|
@@ -4444,14 +4475,79 @@ function toolError(message) {
|
|
|
4444
4475
|
};
|
|
4445
4476
|
}
|
|
4446
4477
|
|
|
4478
|
+
// src/auth.ts
|
|
4479
|
+
function resolveCredentialFromEnv() {
|
|
4480
|
+
const apiKey = process.env.TOPOLO_API_KEY;
|
|
4481
|
+
if (apiKey) return { kind: "api_key", apiKey };
|
|
4482
|
+
const accessToken = process.env.TOPOLO_ACCESS_TOKEN;
|
|
4483
|
+
if (accessToken) return { kind: "access_token", accessToken };
|
|
4484
|
+
return null;
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
// src/context-credential.ts
|
|
4488
|
+
var DEFAULT_DEPENDENCIES = {
|
|
4489
|
+
readConfig,
|
|
4490
|
+
refreshContextCredentialIfNeeded,
|
|
4491
|
+
refreshCredentialIfNeeded,
|
|
4492
|
+
resolveCredentialFromEnv
|
|
4493
|
+
};
|
|
4494
|
+
function resolvePinnedContextKey(profile, selector) {
|
|
4495
|
+
const normalized = selector.trim().toLowerCase();
|
|
4496
|
+
if (!normalized) {
|
|
4497
|
+
throw new Error("TOPOLO_CONTEXT must not be empty");
|
|
4498
|
+
}
|
|
4499
|
+
const contexts = Object.entries(profile.contexts ?? {});
|
|
4500
|
+
const exact = contexts.find(([key]) => key.toLowerCase() === normalized);
|
|
4501
|
+
if (exact) return exact[0];
|
|
4502
|
+
const matches = contexts.filter(([, context]) => [
|
|
4503
|
+
context.organizationId,
|
|
4504
|
+
context.organizationSlug,
|
|
4505
|
+
context.organizationName
|
|
4506
|
+
].some((value) => String(value || "").trim().toLowerCase() === normalized));
|
|
4507
|
+
if (matches.length === 1) return matches[0][0];
|
|
4508
|
+
if (matches.length > 1) {
|
|
4509
|
+
throw new Error(`TOPOLO_CONTEXT "${selector}" is ambiguous; use an exact context key`);
|
|
4510
|
+
}
|
|
4511
|
+
throw new Error(`TOPOLO_CONTEXT "${selector}" is not signed in for this environment`);
|
|
4512
|
+
}
|
|
4513
|
+
async function resolveMcpCredential(environment, selector = process.env.TOPOLO_CONTEXT, dependencies = DEFAULT_DEPENDENCIES) {
|
|
4514
|
+
const environmentCredential = dependencies.resolveCredentialFromEnv();
|
|
4515
|
+
const requestedSelector = selector?.trim();
|
|
4516
|
+
if (environmentCredential) {
|
|
4517
|
+
if (requestedSelector) {
|
|
4518
|
+
throw new Error("TOPOLO_CONTEXT cannot be combined with an environment credential");
|
|
4519
|
+
}
|
|
4520
|
+
return { credential: environmentCredential, contextKey: null };
|
|
4521
|
+
}
|
|
4522
|
+
if (requestedSelector) {
|
|
4523
|
+
const config = await dependencies.readConfig();
|
|
4524
|
+
const contextKey = resolvePinnedContextKey(
|
|
4525
|
+
profileForEnvironment(config, environment),
|
|
4526
|
+
requestedSelector
|
|
4527
|
+
);
|
|
4528
|
+
const credential = await dependencies.refreshContextCredentialIfNeeded(environment, contextKey);
|
|
4529
|
+
return {
|
|
4530
|
+
credential,
|
|
4531
|
+
contextKey,
|
|
4532
|
+
refreshCredential: () => dependencies.refreshContextCredentialIfNeeded(
|
|
4533
|
+
environment,
|
|
4534
|
+
contextKey,
|
|
4535
|
+
{ force: true }
|
|
4536
|
+
)
|
|
4537
|
+
};
|
|
4538
|
+
}
|
|
4539
|
+
return {
|
|
4540
|
+
credential: await dependencies.refreshCredentialIfNeeded(environment),
|
|
4541
|
+
contextKey: null,
|
|
4542
|
+
refreshCredential: () => dependencies.refreshCredentialIfNeeded(environment, { force: true })
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
4545
|
+
|
|
4447
4546
|
// src/index.ts
|
|
4448
4547
|
async function main() {
|
|
4449
4548
|
const environment = normalizeEnvironment(process.env.TOPOLO_ENV);
|
|
4450
|
-
const
|
|
4451
|
-
|
|
4452
|
-
if (!credential) {
|
|
4453
|
-
credential = await refreshCredentialIfNeeded(environment);
|
|
4454
|
-
}
|
|
4549
|
+
const resolvedCredential = await resolveMcpCredential(environment);
|
|
4550
|
+
const credential = resolvedCredential.credential;
|
|
4455
4551
|
if (!credential) {
|
|
4456
4552
|
process.stderr.write(
|
|
4457
4553
|
"TopoloMCP: no Topolo credential. Run `topolo auth login`, or set TOPOLO_API_KEY / TOPOLO_ACCESS_TOKEN.\n"
|
|
@@ -4471,7 +4567,7 @@ async function main() {
|
|
|
4471
4567
|
},
|
|
4472
4568
|
serviceUrls,
|
|
4473
4569
|
requireConfirmForWrites: true,
|
|
4474
|
-
|
|
4570
|
+
...resolvedCredential.refreshCredential ? { refreshCredential: resolvedCredential.refreshCredential } : {}
|
|
4475
4571
|
});
|
|
4476
4572
|
const scopes = await loadScopes(topolo);
|
|
4477
4573
|
const server = buildServer(topolo, scopes);
|
|
@@ -4479,7 +4575,7 @@ async function main() {
|
|
|
4479
4575
|
await server.connect(transport);
|
|
4480
4576
|
const toolNames = await advertisedToolNames(topolo, scopes);
|
|
4481
4577
|
process.stderr.write(
|
|
4482
|
-
`TopoloMCP v${MCP_VERSION} connected (${environment}). Tools available: ${toolNames.join(", ") || "(none)"}
|
|
4578
|
+
`TopoloMCP v${MCP_VERSION} connected (${environment}${resolvedCredential.contextKey ? `, ${resolvedCredential.contextKey}` : ""}). Tools available: ${toolNames.join(", ") || "(none)"}
|
|
4483
4579
|
`
|
|
4484
4580
|
);
|
|
4485
4581
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"topolo-mcp": "
|
|
7
|
+
"topolo-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
"./dispatch": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@topolo/sdk": "^0.9.
|
|
39
|
+
"@topolo/sdk": "^0.9.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@topolo/sdk": "file:../topolo-sdk",
|