@topolo/mcp 0.11.0 → 0.11.2
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 +8 -50
- package/dist/http.js +172 -172
- package/dist/index.js +113 -48
- package/package.json +8 -5
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
|
+
// ../../../package-topolo-sdk-0.11.2/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);
|
|
@@ -758,6 +758,18 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, op
|
|
|
758
758
|
function topoloUploadResponseValue(response, path) {
|
|
759
759
|
return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
|
|
760
760
|
}
|
|
761
|
+
function redactTopoloUrlSessionResult(response, uploadUrlPath) {
|
|
762
|
+
const redacted = cloneUploadValue(response);
|
|
763
|
+
let target = redacted;
|
|
764
|
+
const segments = uploadUrlPath.split(".");
|
|
765
|
+
for (const segment of segments.slice(0, -1)) {
|
|
766
|
+
target = isRecord2(target) ? target[segment] : void 0;
|
|
767
|
+
}
|
|
768
|
+
const finalSegment = segments.at(-1);
|
|
769
|
+
if (finalSegment && isRecord2(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
|
|
770
|
+
redactUploadHeaders(redacted);
|
|
771
|
+
return redacted;
|
|
772
|
+
}
|
|
761
773
|
function decodeTopoloActionBase64(contentBase64) {
|
|
762
774
|
if (!isCanonicalBase64(contentBase64)) {
|
|
763
775
|
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
@@ -776,6 +788,28 @@ function mapUploadInput(contract, contentBase64, input, options) {
|
|
|
776
788
|
[contract.fileNameField]: fileName
|
|
777
789
|
};
|
|
778
790
|
}
|
|
791
|
+
function cloneUploadValue(value) {
|
|
792
|
+
if (Array.isArray(value)) return value.map(cloneUploadValue);
|
|
793
|
+
if (!isRecord2(value)) return value;
|
|
794
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
|
|
795
|
+
}
|
|
796
|
+
function redactUploadHeaders(value) {
|
|
797
|
+
if (Array.isArray(value)) {
|
|
798
|
+
value.forEach(redactUploadHeaders);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (!isRecord2(value)) return;
|
|
802
|
+
for (const [key, item] of Object.entries(value)) {
|
|
803
|
+
if (key.toLowerCase() === "headers" && isRecord2(item)) {
|
|
804
|
+
for (const header of Object.keys(item)) {
|
|
805
|
+
if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
|
|
806
|
+
item[header] = "[REDACTED]";
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
redactUploadHeaders(item);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
779
813
|
function validateUploadMetadata(contract, byteLength, options) {
|
|
780
814
|
const fileName = options.fileName.trim();
|
|
781
815
|
const contentType = options.contentType.trim().toLowerCase();
|
|
@@ -1417,7 +1451,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
1417
1451
|
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
|
|
1418
1452
|
}
|
|
1419
1453
|
await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
|
|
1420
|
-
if (!contract.complete) return initiation;
|
|
1454
|
+
if (!contract.complete) return redactTopoloUrlSessionResult(initiation, contract.uploadUrlPath);
|
|
1421
1455
|
const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
|
|
1422
1456
|
if (typeof completionValue !== "string" || !completionValue.trim()) {
|
|
1423
1457
|
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
|
|
@@ -2169,8 +2203,8 @@ function createTopolo(options) {
|
|
|
2169
2203
|
};
|
|
2170
2204
|
}
|
|
2171
2205
|
|
|
2172
|
-
// ../../../
|
|
2173
|
-
import { mkdir, readFile, writeFile, chmod } from "fs/promises";
|
|
2206
|
+
// ../../../package-topolo-sdk-0.11.2/packages/topolo-sdk/dist/node/credential-store.js
|
|
2207
|
+
import { mkdir, readFile, writeFile, chmod, rm, stat } from "fs/promises";
|
|
2174
2208
|
import { existsSync } from "fs";
|
|
2175
2209
|
import { homedir, platform } from "os";
|
|
2176
2210
|
import { dirname, join } from "path";
|
|
@@ -2633,6 +2667,41 @@ async function resolveCredential(environment = selectedEnvironment()) {
|
|
|
2633
2667
|
return profileForEnvironment(config, environment).credential ?? null;
|
|
2634
2668
|
}
|
|
2635
2669
|
var REFRESH_SKEW_SECONDS = 60;
|
|
2670
|
+
var CONFIG_LOCK_RETRY_MS = 25;
|
|
2671
|
+
var CONFIG_LOCK_TIMEOUT_MS = 5e3;
|
|
2672
|
+
var CONFIG_LOCK_STALE_MS = 12e4;
|
|
2673
|
+
async function withConfigWriteLock(operation) {
|
|
2674
|
+
const lockPath = `${configPath()}.lock`;
|
|
2675
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
2676
|
+
const startedAt = Date.now();
|
|
2677
|
+
while (true) {
|
|
2678
|
+
try {
|
|
2679
|
+
await mkdir(lockPath);
|
|
2680
|
+
break;
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
if (error.code !== "EEXIST") throw error;
|
|
2683
|
+
try {
|
|
2684
|
+
const lock = await stat(lockPath);
|
|
2685
|
+
if (Date.now() - lock.mtimeMs > CONFIG_LOCK_STALE_MS) {
|
|
2686
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
2687
|
+
continue;
|
|
2688
|
+
}
|
|
2689
|
+
} catch (inspectionError) {
|
|
2690
|
+
if (inspectionError.code !== "ENOENT") throw inspectionError;
|
|
2691
|
+
continue;
|
|
2692
|
+
}
|
|
2693
|
+
if (Date.now() - startedAt >= CONFIG_LOCK_TIMEOUT_MS) {
|
|
2694
|
+
throw new Error(`Timed out waiting for credential configuration lock at ${lockPath}.`);
|
|
2695
|
+
}
|
|
2696
|
+
await new Promise((resolve2) => setTimeout(resolve2, CONFIG_LOCK_RETRY_MS));
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
try {
|
|
2700
|
+
return await operation();
|
|
2701
|
+
} finally {
|
|
2702
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2636
2705
|
async function refreshCredentialIfNeeded(environment = selectedEnvironment(), options = {}) {
|
|
2637
2706
|
const prefix = envVarPrefix(environment);
|
|
2638
2707
|
if (process.env[`${prefix}_API_KEY`] || process.env[`${prefix}_ACCESS_TOKEN`] || environment === "production" && process.env.TOPOLO_API_KEY || environment === "production" && process.env.TOPOLO_ACCESS_TOKEN) {
|
|
@@ -2686,51 +2755,47 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
|
|
|
2686
2755
|
}
|
|
2687
2756
|
}
|
|
2688
2757
|
async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
serviceUrls
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
[key]: {
|
|
2724
|
-
...context,
|
|
2725
|
-
credential: refreshed,
|
|
2726
|
-
oauthClientId
|
|
2758
|
+
return withConfigWriteLock(async () => {
|
|
2759
|
+
const config = await readConfig();
|
|
2760
|
+
const profile = profileForEnvironment(config, environment);
|
|
2761
|
+
const context = profile.contexts?.[key];
|
|
2762
|
+
const credential = context?.credential;
|
|
2763
|
+
if (!credential || credential.kind !== "access_token") return credential ?? null;
|
|
2764
|
+
const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
|
|
2765
|
+
if (!credential.refreshToken || !oauthClientId) return credential;
|
|
2766
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2767
|
+
if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
|
|
2768
|
+
const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
|
|
2769
|
+
if (environment === "local" && !serviceUrls?.auth) {
|
|
2770
|
+
throw new TopoloSdkError2(
|
|
2771
|
+
"environment_service_url_required",
|
|
2772
|
+
"Local Topolo context refresh requires an explicit local Auth service origin."
|
|
2773
|
+
);
|
|
2774
|
+
}
|
|
2775
|
+
try {
|
|
2776
|
+
const oauth = new TopoloOAuth({ serviceUrls });
|
|
2777
|
+
const tokens = await oauth.refreshToken({
|
|
2778
|
+
clientId: oauthClientId,
|
|
2779
|
+
refreshToken: credential.refreshToken
|
|
2780
|
+
});
|
|
2781
|
+
const refreshed = {
|
|
2782
|
+
kind: "access_token",
|
|
2783
|
+
accessToken: tokens.access_token,
|
|
2784
|
+
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
|
|
2785
|
+
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
2786
|
+
};
|
|
2787
|
+
await writeConfig(writeProfileConfig(config, environment, {
|
|
2788
|
+
...profile.activeContext === key ? { credential: refreshed } : {},
|
|
2789
|
+
contexts: {
|
|
2790
|
+
...profile.contexts ?? {},
|
|
2791
|
+
[key]: { ...context, credential: refreshed, oauthClientId }
|
|
2727
2792
|
}
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
}
|
|
2793
|
+
}));
|
|
2794
|
+
return refreshed;
|
|
2795
|
+
} catch {
|
|
2796
|
+
return credential;
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2734
2799
|
}
|
|
2735
2800
|
function resolveAgentName(configAgentName) {
|
|
2736
2801
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/mcp",
|
|
3
|
-
"version": "0.11.
|
|
4
|
-
"topoloPublishedVersion": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
|
+
"topoloPublishedVersion": "0.11.2",
|
|
5
5
|
"description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"author": "Topolo",
|
|
8
|
+
"homepage": "https://docs.topolo.app/guides/agents-and-automation",
|
|
6
9
|
"type": "module",
|
|
7
10
|
"bin": {
|
|
8
11
|
"topolo-mcp": "dist/index.js"
|
|
@@ -26,13 +29,13 @@
|
|
|
26
29
|
"dev": "tsup --watch",
|
|
27
30
|
"typecheck": "tsc --noEmit",
|
|
28
31
|
"start": "node dist/index.js",
|
|
29
|
-
"test": "vitest run",
|
|
32
|
+
"test": "if test \"$TOPOLO_BUILD_READY\" = 1; then vitest run; else python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- sh -c 'pnpm --dir ../topolo-sdk build && exec vitest run'; fi",
|
|
30
33
|
"test:watch": "vitest",
|
|
31
|
-
"coverage": "vitest run --coverage"
|
|
34
|
+
"coverage": "if test \"$TOPOLO_BUILD_READY\" = 1; then vitest run --coverage; else python3 ../../tooling/with-resource-lock.py ../topolo-sdk -- sh -c 'pnpm --dir ../topolo-sdk build && exec vitest run --coverage'; fi"
|
|
32
35
|
},
|
|
33
36
|
"dependencies": {
|
|
34
37
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
35
|
-
"@topolo/sdk": "0.11.
|
|
38
|
+
"@topolo/sdk": "0.11.2"
|
|
36
39
|
},
|
|
37
40
|
"devDependencies": {
|
|
38
41
|
"@types/node": "^20.12.0",
|