@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/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
- // ../../../public-platform-tooling-boundary-20260813/packages/topolo-sdk/dist/index.js
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
- // ../../../public-platform-tooling-boundary-20260813/packages/topolo-sdk/dist/node/credential-store.js
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
- const config = await readConfig();
2690
- const profile = profileForEnvironment(config, environment);
2691
- const context = profile.contexts?.[key];
2692
- const credential = context?.credential;
2693
- if (!credential || credential.kind !== "access_token") return credential ?? null;
2694
- const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
2695
- if (!credential.refreshToken || !oauthClientId) return credential;
2696
- const now = Math.floor(Date.now() / 1e3);
2697
- if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
2698
- const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
2699
- if (environment === "local" && !serviceUrls?.auth) {
2700
- throw new TopoloSdkError2(
2701
- "environment_service_url_required",
2702
- "Local Topolo context refresh requires an explicit local Auth service origin."
2703
- );
2704
- }
2705
- try {
2706
- const oauth = new TopoloOAuth({
2707
- serviceUrls
2708
- });
2709
- const tokens = await oauth.refreshToken({
2710
- clientId: oauthClientId,
2711
- refreshToken: credential.refreshToken
2712
- });
2713
- const refreshed = {
2714
- kind: "access_token",
2715
- accessToken: tokens.access_token,
2716
- ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
2717
- expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
2718
- };
2719
- await writeConfig(writeProfileConfig(config, environment, {
2720
- ...profile.activeContext === key ? { credential: refreshed } : {},
2721
- contexts: {
2722
- ...profile.contexts ?? {},
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
- return refreshed;
2731
- } catch {
2732
- return credential;
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.0",
4
- "topoloPublishedVersion": "0.11.0",
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.0"
38
+ "@topolo/sdk": "0.11.2"
36
39
  },
37
40
  "devDependencies": {
38
41
  "@types/node": "^20.12.0",