@topolo/mcp 0.10.2 → 0.10.3

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
- // ../topolo-sdk/dist/index.js
6
+ // ../../../development-workflow-acceleration/node_modules/.pnpm/@topolo+sdk@0.10.4/node_modules/@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);
@@ -30,6 +30,90 @@ var TopoloSdkError = class extends Error {
30
30
  this.code = code;
31
31
  }
32
32
  };
33
+ var PUBLIC_ERROR_CODES = /* @__PURE__ */ new Set([
34
+ "action_app_identity_unavailable",
35
+ "action_authentication_required",
36
+ "action_catalog_summary_unavailable",
37
+ "action_catalog_unavailable",
38
+ "action_contract_mismatch",
39
+ "action_permission_required",
40
+ "action_runtime_authorization_required",
41
+ "action_service_not_allowed",
42
+ "auth_error",
43
+ "authentication_required",
44
+ "authorization_pending",
45
+ "bad_sig",
46
+ "confirmation_required",
47
+ "dependency_unavailable",
48
+ "environment_mismatch",
49
+ "environment_service_url_required",
50
+ "expired_token",
51
+ "forbidden",
52
+ "http_error",
53
+ "immutable_drift",
54
+ "integration_dependency_unavailable",
55
+ "integration_misconfigured",
56
+ "invalid_action",
57
+ "invalid_action_input",
58
+ "invalid_grant",
59
+ "invalid_input",
60
+ "invalid_token",
61
+ "mcp_error",
62
+ "membership_required",
63
+ "network_error",
64
+ "organization_context_required",
65
+ "organization_enrollment_required",
66
+ "permission_denied",
67
+ "rate_limited",
68
+ "request_failed",
69
+ "service_catalog_unavailable",
70
+ "service_path_origin_mismatch",
71
+ "service_required",
72
+ "slow_down",
73
+ "state_conflict",
74
+ "unauthorized",
75
+ "unknown_action",
76
+ "unknown_service"
77
+ ]);
78
+ var ERROR_CODE_PATTERN = /^[a-z][a-z0-9_]{0,63}$/;
79
+ var TOPOLO_REQUEST_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
80
+ var CLOUDFLARE_RAY_ID_PATTERN = /^[0-9a-f]{16}-[A-Z]{3}$/;
81
+ var PERMISSION_PATTERN = /^[a-z][a-z0-9_-]{0,63}(?:\.[a-z][a-z0-9_-]{0,63})*:(?:[a-z][a-z0-9_-]{0,63}|\*)$/;
82
+ var RETRY_AFTER_DATE_PATTERN = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
83
+ var PUBLIC_DETAIL_SOURCES = /* @__PURE__ */ new Set([
84
+ "api_key_introspection",
85
+ "api_key_validation",
86
+ "auth_context",
87
+ "auth_validate",
88
+ "catalog",
89
+ "credential",
90
+ "runtime",
91
+ "service_token_provider",
92
+ "service_verify_key",
93
+ "token_validation",
94
+ "upstream"
95
+ ]);
96
+ function canonicalTopoloErrorCode(value) {
97
+ if (typeof value !== "string") return null;
98
+ const candidate = value.trim().toLowerCase();
99
+ if (!ERROR_CODE_PATTERN.test(candidate) || !PUBLIC_ERROR_CODES.has(candidate)) return null;
100
+ return candidate;
101
+ }
102
+ function canonicalTopoloRequestId(value) {
103
+ if (typeof value !== "string") return void 0;
104
+ const candidate = value.trim();
105
+ return TOPOLO_REQUEST_ID_PATTERN.test(candidate) || CLOUDFLARE_RAY_ID_PATTERN.test(candidate) ? candidate : void 0;
106
+ }
107
+ function canonicalTopoloRetryAfter(value) {
108
+ if (typeof value !== "string") return void 0;
109
+ const candidate = value.trim();
110
+ if (/^\d{1,10}$/.test(candidate) || RETRY_AFTER_DATE_PATTERN.test(candidate)) return candidate;
111
+ return void 0;
112
+ }
113
+ function canonicalTopoloRequired(value) {
114
+ if (!Array.isArray(value)) return [];
115
+ return [...new Set(value.filter((item) => typeof item === "string" && PERMISSION_PATTERN.test(item)))].slice(0, 32);
116
+ }
33
117
  var TopoloAuthError = class extends TopoloSdkError {
34
118
  status;
35
119
  body;
@@ -37,15 +121,19 @@ var TopoloAuthError = class extends TopoloSdkError {
37
121
  path;
38
122
  requestId;
39
123
  retryAfter;
124
+ details;
40
125
  constructor(message, code = "auth_error", options = {}) {
41
- super(code, message);
126
+ super(canonicalTopoloErrorCode(code) ?? "auth_error", message);
42
127
  this.name = "TopoloAuthError";
43
128
  this.status = options.status ?? 401;
44
129
  this.body = options.body;
45
130
  this.service = options.service;
46
131
  this.path = options.path;
47
- this.requestId = options.requestId;
48
- this.retryAfter = options.retryAfter;
132
+ this.requestId = canonicalTopoloRequestId(options.requestId);
133
+ this.retryAfter = canonicalTopoloRetryAfter(options.retryAfter);
134
+ this.details = extractTopoloServiceErrorDetails(
135
+ options.details === void 0 ? options.body : { details: options.details }
136
+ );
49
137
  }
50
138
  };
51
139
  var TopoloPermissionError = class extends TopoloSdkError {
@@ -56,28 +144,140 @@ var TopoloPermissionError = class extends TopoloSdkError {
56
144
  path;
57
145
  requestId;
58
146
  retryAfter;
147
+ details;
59
148
  constructor(message, required, options = {}) {
60
- super("permission_denied", message);
149
+ super(
150
+ canonicalTopoloErrorCode(options.code) ?? serviceErrorCode(options.body, "permission_denied"),
151
+ message
152
+ );
61
153
  this.name = "TopoloPermissionError";
62
- this.required = required;
154
+ this.required = canonicalTopoloRequired(required);
63
155
  this.status = options.status ?? 403;
64
156
  this.body = options.body;
65
157
  this.service = options.service;
66
158
  this.path = options.path;
67
- this.requestId = options.requestId;
68
- this.retryAfter = options.retryAfter;
159
+ this.requestId = canonicalTopoloRequestId(options.requestId);
160
+ this.retryAfter = canonicalTopoloRetryAfter(options.retryAfter);
161
+ this.details = extractTopoloServiceErrorDetails(
162
+ options.details === void 0 ? options.body : { details: options.details }
163
+ );
69
164
  }
70
165
  };
71
- function extractHttpErrorCode(body) {
72
- if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
166
+ function extractTopoloServiceErrorCode(body) {
167
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
73
168
  const record = body;
74
- if (typeof record.code === "string" && record.code.trim()) return record.code.trim();
169
+ const topLevel = canonicalTopoloErrorCode(record.code);
170
+ if (topLevel) return topLevel;
75
171
  const nested = record.error;
172
+ const nestedString = canonicalTopoloErrorCode(nested);
173
+ if (nestedString) return nestedString;
76
174
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
77
175
  const code = nested.code;
78
- if (typeof code === "string" && code.trim()) return code.trim();
176
+ const nestedCode = canonicalTopoloErrorCode(code);
177
+ if (nestedCode) return nestedCode;
79
178
  }
80
- return "http_error";
179
+ return null;
180
+ }
181
+ function serviceErrorCode(body, fallback) {
182
+ return extractTopoloServiceErrorCode(body) ?? fallback;
183
+ }
184
+ function extractTopoloServiceErrorDetails(body) {
185
+ if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
186
+ const record = body;
187
+ let candidate = record.details;
188
+ const nested = record.error;
189
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
190
+ const nestedRecord = nested;
191
+ if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
192
+ }
193
+ if (candidate === void 0) candidate = record;
194
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return void 0;
195
+ const source = candidate;
196
+ const safe = {};
197
+ for (const [key, value] of Object.entries(source)) {
198
+ if (/present$/i.test(key) && typeof value === "boolean") {
199
+ safe[key] = value;
200
+ continue;
201
+ }
202
+ if (key === "retryable" && typeof value === "boolean") {
203
+ safe.retryable = value;
204
+ continue;
205
+ }
206
+ if (key === "code") {
207
+ const sanitized = canonicalTopoloErrorCode(value);
208
+ if (sanitized) safe.code = sanitized;
209
+ continue;
210
+ }
211
+ if (key === "source") {
212
+ const sanitized = typeof value === "string" ? value.trim().toLowerCase() : "";
213
+ if (PUBLIC_DETAIL_SOURCES.has(sanitized)) safe.source = sanitized;
214
+ continue;
215
+ }
216
+ if (key === "dependency" || key === "platform") {
217
+ safe[`${key}Present`] = value !== null && value !== void 0;
218
+ continue;
219
+ }
220
+ if (key === "error" && !safe.code) {
221
+ const sanitized = canonicalTopoloErrorCode(value);
222
+ if (sanitized) safe.code = sanitized;
223
+ continue;
224
+ }
225
+ if (key === "required" && Array.isArray(value)) {
226
+ const required = canonicalTopoloRequired(value);
227
+ if (required.length) safe.required = required;
228
+ continue;
229
+ }
230
+ if (["missingFields", "fields", "paths"].includes(key) && Array.isArray(value)) {
231
+ const paths = value.filter((item) => typeof item === "string" && /^[$.a-z0-9_[\]-]{1,160}$/i.test(item));
232
+ if (paths.length) safe[key] = paths;
233
+ }
234
+ }
235
+ return Object.keys(safe).length ? safe : void 0;
236
+ }
237
+ function publicTopoloErrorMessage(error) {
238
+ const code = canonicalTopoloErrorCode(
239
+ typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
240
+ ) ?? "request_failed";
241
+ if (/(?:organization|org).*(?:enroll|membership|member|admission)/.test(code)) {
242
+ return "Organization or application enrollment is required.";
243
+ }
244
+ if (/(?:organization|org).*(?:context|required|missing|invalid|unknown|not_found)/.test(code)) {
245
+ return "Organization context is unavailable.";
246
+ }
247
+ if (["auth_error", "authentication_required", "unauthorized", "invalid_token", "expired_token"].includes(code)) {
248
+ return "Authentication failed.";
249
+ }
250
+ if (code === "permission_denied" || code === "forbidden") return "Permission denied.";
251
+ if (code === "confirmation_required") return "Confirmation is required.";
252
+ return `Topolo request failed (${code}).`;
253
+ }
254
+ function topoloErrorHint(error) {
255
+ const code = canonicalTopoloErrorCode(
256
+ typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
257
+ );
258
+ if (!code) return void 0;
259
+ if (code === "auth_error" || code === "authentication_required" || code === "unauthorized" || code === "invalid_token" || code === "expired_token") {
260
+ return "Run `topolo auth login`, then verify the effective Topolo identity and organization.";
261
+ }
262
+ if (code === "confirmation_required") {
263
+ return "Review the unchanged action plan, obtain approval, and retry once with --confirm or confirm:true.";
264
+ }
265
+ if (code === "enrollment_required" || code === "membership_required" || /(?:organization|org).*(?:enroll|membership|member|admission)|(?:enroll|membership).*(?:organization|org)/.test(code)) {
266
+ return "The current identity is not enrolled for this organization or application. Use an enrolled identity or ask an organization administrator to complete enrollment.";
267
+ }
268
+ if (/(?:organization|org).*(?:context|required|missing|invalid|unknown|not_found)/.test(code)) {
269
+ return "Select a signed-in organization context, then verify the effective identity and organization before retrying.";
270
+ }
271
+ if (code === "permission_denied" || code === "forbidden") {
272
+ return "The current identity or resource context is not authorized. Review the structured details and published authorization contract.";
273
+ }
274
+ if (code === "environment_mismatch") {
275
+ return "Use service origins and credentials from one explicit Topolo environment only.";
276
+ }
277
+ if (code === "invalid_action_input" || code === "invalid_input") {
278
+ return "Correct only the reported schema paths, then prepare the action again.";
279
+ }
280
+ return void 0;
81
281
  }
82
282
  var TopoloConfirmationRequiredError = class extends TopoloSdkError {
83
283
  constructor(message) {
@@ -92,17 +292,66 @@ var TopoloHttpError = class extends TopoloSdkError {
92
292
  path;
93
293
  requestId;
94
294
  retryAfter;
295
+ details;
95
296
  constructor(service, path, status, body, message, options = {}) {
96
- super(extractHttpErrorCode(body), message ?? `HTTP ${status} from ${service}${path}`);
297
+ super(serviceErrorCode(body, "http_error"), message ?? `HTTP ${status} from ${service}${path}`);
97
298
  this.name = "TopoloHttpError";
98
299
  this.status = status;
99
300
  this.body = body;
100
301
  this.service = service;
101
302
  this.path = path;
102
- this.requestId = options.requestId;
103
- this.retryAfter = options.retryAfter;
303
+ this.requestId = canonicalTopoloRequestId(options.requestId);
304
+ this.retryAfter = canonicalTopoloRetryAfter(options.retryAfter);
305
+ this.details = extractTopoloServiceErrorDetails(
306
+ options.details === void 0 ? body : { details: options.details }
307
+ );
104
308
  }
105
309
  };
310
+ function hostnameHasSuffix(hostname, suffix) {
311
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
312
+ }
313
+ function workersDevAccountMatches(hostname, account) {
314
+ if (!hostname.endsWith(".workers.dev")) return false;
315
+ const labels = hostname.slice(0, -".workers.dev".length).split(".");
316
+ return labels.length >= 2 && labels.at(-1) === account;
317
+ }
318
+ function originMatchesEnvironment(url, environment) {
319
+ const hostname = url.hostname.toLowerCase();
320
+ if (environment === "local") {
321
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
322
+ }
323
+ if (url.protocol !== "https:") return false;
324
+ if (environment === "production") {
325
+ return hostnameHasSuffix(hostname, "topolo.app") || hostnameHasSuffix(hostname, "topolo.io") || workersDevAccountMatches(hostname, "topolo");
326
+ }
327
+ if (environment === "staging") {
328
+ return hostnameHasSuffix(hostname, "stg.topolo.us") || workersDevAccountMatches(hostname, "topolo-staging");
329
+ }
330
+ return hostnameHasSuffix(hostname, "topolo.dev") || workersDevAccountMatches(hostname, "topolo-development");
331
+ }
332
+ function assertTopoloServiceUrlEnvironment(service, value, environment) {
333
+ let url;
334
+ try {
335
+ url = new URL(value);
336
+ } catch {
337
+ throw new TopoloSdkError("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
338
+ }
339
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment(url, environment)) {
340
+ throw new TopoloSdkError(
341
+ "environment_mismatch",
342
+ `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
343
+ );
344
+ }
345
+ return url.origin;
346
+ }
347
+ function topoloServiceOrigins(serviceUrls, environment) {
348
+ return Object.fromEntries(
349
+ Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
350
+ service,
351
+ assertTopoloServiceUrlEnvironment(service, value, environment)
352
+ ])
353
+ );
354
+ }
106
355
  var APP_API_URLS = {
107
356
  auth: "https://auth.topolo.app",
108
357
  developers: "https://developers.topolo.app"
@@ -2851,6 +3100,7 @@ var TopoloClient = class _TopoloClient {
2851
3100
  credential;
2852
3101
  agent;
2853
3102
  serviceUrls;
3103
+ environment;
2854
3104
  appIds;
2855
3105
  requireConfirmForWrites;
2856
3106
  timeoutMs;
@@ -2879,7 +3129,8 @@ var TopoloClient = class _TopoloClient {
2879
3129
  if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
2880
3130
  this.credential = options.credential;
2881
3131
  this.agent = options.agent;
2882
- this.serviceUrls = options.serviceUrls;
3132
+ this.environment = options.environment;
3133
+ this.serviceUrls = options.environment ? topoloServiceOrigins(options.serviceUrls, options.environment) : options.serviceUrls;
2883
3134
  this.appIds = options.appIds;
2884
3135
  this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
2885
3136
  this.timeoutMs = options.timeoutMs ?? 3e4;
@@ -2908,9 +3159,15 @@ var TopoloClient = class _TopoloClient {
2908
3159
  );
2909
3160
  }
2910
3161
  await this.loadCurrentCredential();
2911
- const resolvedService = opts.baseUrl ? { serviceKey: normalizeLookupKey(opts.service), baseUrl: requireHttpOrigin(opts.baseUrl) } : await this.resolveRequestService(opts.service);
3162
+ const resolvedService = opts.baseUrl ? { serviceKey: normalizeLookupKey(opts.service), baseUrl: this.validatedServiceOrigin(opts.service, opts.baseUrl) } : await this.resolveRequestService(opts.service);
2912
3163
  const baseUrl = resolvedService.baseUrl;
2913
3164
  const url = new URL(opts.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
3165
+ if (url.origin !== new URL(baseUrl).origin) {
3166
+ throw new TopoloSdkError(
3167
+ "service_path_origin_mismatch",
3168
+ `Refusing Topolo request path because it changes the resolved ${resolvedService.serviceKey || opts.service} service origin.`
3169
+ );
3170
+ }
2914
3171
  if (opts.query) {
2915
3172
  for (const [k, v] of Object.entries(opts.query)) {
2916
3173
  if (v !== void 0) url.searchParams.set(k, String(v));
@@ -2981,7 +3238,7 @@ var TopoloClient = class _TopoloClient {
2981
3238
  url: urlStr,
2982
3239
  requestId,
2983
3240
  durationMs: Date.now() - startedAt,
2984
- error: err instanceof Error ? err.message : String(err)
3241
+ error: "network_error"
2985
3242
  });
2986
3243
  throw err;
2987
3244
  }
@@ -2990,6 +3247,7 @@ var TopoloClient = class _TopoloClient {
2990
3247
  if (!res.ok) {
2991
3248
  const durationMs = Date.now() - startedAt;
2992
3249
  const message = describeError(parsed, `HTTP ${res.status}`);
3250
+ const publicCode = extractTopoloServiceErrorCode(parsed) ?? "http_error";
2993
3251
  this.emit({
2994
3252
  phase: "error",
2995
3253
  method,
@@ -2998,7 +3256,7 @@ var TopoloClient = class _TopoloClient {
2998
3256
  url: urlStr,
2999
3257
  requestId,
3000
3258
  durationMs,
3001
- error: message,
3259
+ error: publicCode,
3002
3260
  status: res.status
3003
3261
  });
3004
3262
  const errorContext = {
@@ -3006,17 +3264,21 @@ var TopoloClient = class _TopoloClient {
3006
3264
  body: parsed,
3007
3265
  service: resolvedService.serviceKey,
3008
3266
  path: opts.path,
3009
- requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
3010
- retryAfter: res.headers.get("Retry-After") || void 0
3267
+ requestId: canonicalTopoloRequestId(res.headers.get("X-Topolo-Request-Id")) ?? requestId,
3268
+ retryAfter: canonicalTopoloRetryAfter(res.headers.get("Retry-After"))
3011
3269
  };
3012
3270
  if (res.status === 401) {
3013
- throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
3271
+ throw new TopoloAuthError(
3272
+ describeError(parsed, "Unauthorized"),
3273
+ extractTopoloServiceErrorCode(parsed) ?? "auth_error",
3274
+ errorContext
3275
+ );
3014
3276
  }
3015
3277
  if (res.status === 403) {
3016
3278
  throw new TopoloPermissionError(
3017
3279
  describeError(parsed, "Permission denied"),
3018
3280
  extractRequired(parsed),
3019
- errorContext
3281
+ { ...errorContext, code: extractTopoloServiceErrorCode(parsed) ?? "permission_denied" }
3020
3282
  );
3021
3283
  }
3022
3284
  throw new TopoloHttpError(
@@ -3066,6 +3328,17 @@ var TopoloClient = class _TopoloClient {
3066
3328
  });
3067
3329
  return this.refreshInFlight;
3068
3330
  }
3331
+ validatedServiceOrigin(service, value) {
3332
+ const origin = requireHttpOrigin(value);
3333
+ return this.environment ? assertTopoloServiceUrlEnvironment(service, origin, this.environment) : origin;
3334
+ }
3335
+ validatedActionEntry(entry) {
3336
+ if (!entry.apiBaseUrl) return entry;
3337
+ return {
3338
+ ...entry,
3339
+ apiBaseUrl: this.validatedServiceOrigin(entry.appSlug || entry.appId, entry.apiBaseUrl)
3340
+ };
3341
+ }
3069
3342
  async resolveRequestService(service) {
3070
3343
  const serviceKey = normalizeLookupKey(service);
3071
3344
  if (!serviceKey) {
@@ -3073,17 +3346,20 @@ var TopoloClient = class _TopoloClient {
3073
3346
  }
3074
3347
  if (serviceKey === "auth" || serviceKey === "developers") {
3075
3348
  const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
3076
- if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
3349
+ if (overrideUrl) return { serviceKey, baseUrl: this.validatedServiceOrigin(serviceKey, overrideUrl) };
3077
3350
  if (serviceKey === "developers") {
3078
3351
  const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl("auth", this.serviceUrls));
3079
- if (derived) return { serviceKey, baseUrl: derived };
3352
+ if (derived) return { serviceKey, baseUrl: this.validatedServiceOrigin(serviceKey, derived) };
3080
3353
  }
3081
- return { serviceKey, baseUrl: DEFAULT_APP_URLS[serviceKey] };
3354
+ return {
3355
+ serviceKey,
3356
+ baseUrl: this.validatedServiceOrigin(serviceKey, DEFAULT_APP_URLS[serviceKey])
3357
+ };
3082
3358
  }
3083
3359
  const live = await this.resolveLiveCallableService(serviceKey);
3084
3360
  if (live) {
3085
3361
  const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
3086
- return overrideUrl ? { ...live, baseUrl: overrideUrl } : live;
3362
+ return overrideUrl ? { ...live, baseUrl: this.validatedServiceOrigin(serviceKey, overrideUrl) } : { ...live, baseUrl: this.validatedServiceOrigin(serviceKey, live.baseUrl) };
3087
3363
  }
3088
3364
  throw new TopoloSdkError(
3089
3365
  "unknown_service",
@@ -3097,10 +3373,10 @@ var TopoloClient = class _TopoloClient {
3097
3373
  serviceKey,
3098
3374
  overrides: this.serviceUrls
3099
3375
  });
3100
- if (baseUrl) return { serviceKey, baseUrl };
3376
+ if (baseUrl) return { serviceKey, baseUrl: this.validatedServiceOrigin(serviceKey, baseUrl) };
3101
3377
  const actionPage = await this.searchActions({ service: match.appId, limit: 1 }).catch(() => null);
3102
3378
  const actionBaseUrl = actionPage?.actions.find((action) => action.apiBaseUrl)?.apiBaseUrl ?? null;
3103
- return actionBaseUrl ? { serviceKey, baseUrl: requireHttpOrigin(actionBaseUrl) } : null;
3379
+ return actionBaseUrl ? { serviceKey, baseUrl: this.validatedServiceOrigin(serviceKey, actionBaseUrl) } : null;
3104
3380
  }
3105
3381
  /**
3106
3382
  * Resolve a service-slug → opaque app-id via caller config or TopoloAuth.
@@ -3120,7 +3396,7 @@ var TopoloClient = class _TopoloClient {
3120
3396
  if (!normalized) return null;
3121
3397
  const configuredAppId = resolveConfiguredAppId(normalized, this.appIds);
3122
3398
  if (configuredAppId) return configuredAppId;
3123
- const authBase = resolveServiceUrl("developers", this.serviceUrls);
3399
+ const authBase = this.validatedServiceOrigin("developers", resolveServiceUrl("developers", this.serviceUrls));
3124
3400
  const cacheKey = `${authBase}|${normalized}`;
3125
3401
  const now = Date.now();
3126
3402
  const cached = _TopoloClient.appIdCache.get(cacheKey);
@@ -3296,7 +3572,7 @@ var TopoloClient = class _TopoloClient {
3296
3572
  "TopoloAuth did not return an action catalog for the current credential."
3297
3573
  );
3298
3574
  }
3299
- const entries = raw.map((item) => normalizeActionCatalogEntry(item)).filter((entry) => Boolean(entry));
3575
+ const entries = raw.map((item) => normalizeActionCatalogEntry(item)).filter((entry) => Boolean(entry)).map((entry) => this.validatedActionEntry(entry));
3300
3576
  return {
3301
3577
  actions: entries,
3302
3578
  page: normalizeCatalogPage(
@@ -3361,7 +3637,7 @@ var TopoloClient = class _TopoloClient {
3361
3637
  const raw = res.data?.action ?? res.action;
3362
3638
  const match = normalizeActionCatalogEntry(raw);
3363
3639
  if (!match) throw new TopoloSdkError("unknown_action", `Unknown or unavailable Topolo action "${action}".`);
3364
- return match;
3640
+ return this.validatedActionEntry(match);
3365
3641
  }
3366
3642
  async callAction(action, input = {}, options = {}) {
3367
3643
  const entry = await this.getAction(action);
@@ -3438,6 +3714,25 @@ var TopoloClient = class _TopoloClient {
3438
3714
  }
3439
3715
  async planAction(action, input = {}, options = {}) {
3440
3716
  const entry = await this.getAction(action);
3717
+ return this.planResolvedAction(entry, input, options);
3718
+ }
3719
+ async prepareAction(action, input, options = {}) {
3720
+ const entry = await this.getAction(action);
3721
+ const example = (entry.agentContract?.examples ?? []).find((candidate) => Boolean(candidate?.input) && typeof candidate.input === "object" && !Array.isArray(candidate.input) && validateTopoloActionInput(entry.inputSchema, candidate.input).valid) ?? null;
3722
+ const selectedInput = input ?? example?.input ?? {};
3723
+ const plan = this.planResolvedAction(entry, selectedInput, options);
3724
+ return {
3725
+ action: entry,
3726
+ example,
3727
+ input: selectedInput,
3728
+ inputSource: input ? "provided" : example ? "example" : "empty",
3729
+ missingFields: [...new Set(
3730
+ plan.validation.errors.filter((issue) => issue.code === "required").map((issue) => issue.path)
3731
+ )],
3732
+ plan
3733
+ };
3734
+ }
3735
+ planResolvedAction(entry, input, options) {
3441
3736
  const validation = validateTopoloActionInput(entry.inputSchema, input);
3442
3737
  const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
3443
3738
  const confirmationRequired = agentAccess === "confirm" || entry.requiresConfirmation || entry.destructive;
@@ -3895,7 +4190,16 @@ function pickString(value) {
3895
4190
  function extractRequired(body) {
3896
4191
  if (body && typeof body === "object") {
3897
4192
  const maybe = body;
3898
- if (Array.isArray(maybe.required)) return maybe.required.map(String);
4193
+ if (Array.isArray(maybe.required)) return canonicalTopoloRequired(maybe.required);
4194
+ for (const nested of [maybe.details, maybe.error]) {
4195
+ if (!nested || typeof nested !== "object" || Array.isArray(nested)) continue;
4196
+ const record = nested;
4197
+ if (Array.isArray(record.required)) return canonicalTopoloRequired(record.required);
4198
+ if (record.details && typeof record.details === "object" && !Array.isArray(record.details)) {
4199
+ const required = record.details.required;
4200
+ if (Array.isArray(required)) return canonicalTopoloRequired(required);
4201
+ }
4202
+ }
3899
4203
  }
3900
4204
  return [];
3901
4205
  }
@@ -3943,6 +4247,72 @@ function compactActionPlan(plan) {
3943
4247
  nextActionIds: plan.nextActionIds
3944
4248
  };
3945
4249
  }
4250
+ function requiredContextText(value, field) {
4251
+ if (typeof value !== "string" || !value.trim() || value.trim().toLowerCase() === "unknown") {
4252
+ throw new TopoloSdkError("invalid_identity_context", `Effective Topolo ${field} is incomplete.`);
4253
+ }
4254
+ return value.trim();
4255
+ }
4256
+ function createTopoloAgentExecutionContext(input) {
4257
+ if (input.contextKey !== null && input.contextKey !== "personal" && !/^org:[^:]+$/.test(input.contextKey)) {
4258
+ throw new TopoloSdkError("invalid_identity_context", "Effective Topolo context key is invalid.");
4259
+ }
4260
+ const user = Object.freeze({
4261
+ id: requiredContextText(input.identity.user.id, "user id"),
4262
+ email: requiredContextText(input.identity.user.email, "user email"),
4263
+ name: input.identity.user.name?.trim() || null,
4264
+ role: requiredContextText(input.identity.user.role, "user role")
4265
+ });
4266
+ const organization = input.identity.organization ? Object.freeze({
4267
+ id: requiredContextText(input.identity.organization.id, "organization id"),
4268
+ slug: requiredContextText(input.identity.organization.slug, "organization slug"),
4269
+ name: requiredContextText(input.identity.organization.name, "organization name")
4270
+ }) : null;
4271
+ if (input.contextKey === "personal" && organization) {
4272
+ throw new TopoloSdkError("identity_context_mismatch", "Personal Topolo context resolved an organization.");
4273
+ }
4274
+ if (input.contextKey?.startsWith("org:")) {
4275
+ const expectedOrganizationId = input.contextKey.slice("org:".length);
4276
+ if (!organization || organization.id !== expectedOrganizationId) {
4277
+ throw new TopoloSdkError("identity_context_mismatch", "Pinned Topolo organization does not match the effective identity.");
4278
+ }
4279
+ }
4280
+ return Object.freeze({
4281
+ environment: input.environment,
4282
+ contextKey: input.contextKey,
4283
+ user,
4284
+ organization,
4285
+ serviceOrigins: Object.freeze(topoloServiceOrigins(input.serviceUrls, input.environment))
4286
+ });
4287
+ }
4288
+ function compactActionPreparationCore(preparation) {
4289
+ const example = preparation.example ? { title: preparation.example.title ?? null, input: preparation.example.input } : null;
4290
+ const effectiveInput = preparation.inputSource === "example" ? { source: "example" } : { source: preparation.inputSource, value: preparation.input };
4291
+ return {
4292
+ action: {
4293
+ id: preparation.action.actionId,
4294
+ title: preparation.action.title,
4295
+ docsUrl: preparation.action.agentContract?.docsUrl ?? null
4296
+ },
4297
+ example,
4298
+ input: effectiveInput,
4299
+ missingFields: preparation.missingFields,
4300
+ valid: preparation.plan.validation.valid,
4301
+ errors: preparation.plan.validation.errors,
4302
+ request: preparation.plan.request,
4303
+ confirmation: {
4304
+ required: preparation.plan.confirmationRequired,
4305
+ provided: preparation.plan.confirmationProvided
4306
+ },
4307
+ executable: preparation.plan.executable,
4308
+ verification: preparation.plan.verification,
4309
+ rollbackActionId: preparation.plan.rollbackActionId
4310
+ };
4311
+ }
4312
+ function compactActionPreparation(preparation, context) {
4313
+ const compact = compactActionPreparationCore(preparation);
4314
+ return context ? { context, ...compact } : compact;
4315
+ }
3946
4316
  function compactApplicationAudit(report) {
3947
4317
  return {
3948
4318
  version: report.version,
@@ -4081,7 +4451,7 @@ function createTopolo(options) {
4081
4451
  };
4082
4452
  }
4083
4453
 
4084
- // ../topolo-sdk/dist/node/credential-store.js
4454
+ // ../../../development-workflow-acceleration/node_modules/.pnpm/@topolo+sdk@0.10.4/node_modules/@topolo/sdk/dist/node/credential-store.js
4085
4455
  import { mkdir, readFile, writeFile, chmod } from "fs/promises";
4086
4456
  import { existsSync } from "fs";
4087
4457
  import { homedir, platform } from "os";
@@ -4094,6 +4464,90 @@ var TopoloSdkError2 = class extends Error {
4094
4464
  this.code = code;
4095
4465
  }
4096
4466
  };
4467
+ var PUBLIC_ERROR_CODES2 = /* @__PURE__ */ new Set([
4468
+ "action_app_identity_unavailable",
4469
+ "action_authentication_required",
4470
+ "action_catalog_summary_unavailable",
4471
+ "action_catalog_unavailable",
4472
+ "action_contract_mismatch",
4473
+ "action_permission_required",
4474
+ "action_runtime_authorization_required",
4475
+ "action_service_not_allowed",
4476
+ "auth_error",
4477
+ "authentication_required",
4478
+ "authorization_pending",
4479
+ "bad_sig",
4480
+ "confirmation_required",
4481
+ "dependency_unavailable",
4482
+ "environment_mismatch",
4483
+ "environment_service_url_required",
4484
+ "expired_token",
4485
+ "forbidden",
4486
+ "http_error",
4487
+ "immutable_drift",
4488
+ "integration_dependency_unavailable",
4489
+ "integration_misconfigured",
4490
+ "invalid_action",
4491
+ "invalid_action_input",
4492
+ "invalid_grant",
4493
+ "invalid_input",
4494
+ "invalid_token",
4495
+ "mcp_error",
4496
+ "membership_required",
4497
+ "network_error",
4498
+ "organization_context_required",
4499
+ "organization_enrollment_required",
4500
+ "permission_denied",
4501
+ "rate_limited",
4502
+ "request_failed",
4503
+ "service_catalog_unavailable",
4504
+ "service_path_origin_mismatch",
4505
+ "service_required",
4506
+ "slow_down",
4507
+ "state_conflict",
4508
+ "unauthorized",
4509
+ "unknown_action",
4510
+ "unknown_service"
4511
+ ]);
4512
+ var ERROR_CODE_PATTERN2 = /^[a-z][a-z0-9_]{0,63}$/;
4513
+ var TOPOLO_REQUEST_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4514
+ var CLOUDFLARE_RAY_ID_PATTERN2 = /^[0-9a-f]{16}-[A-Z]{3}$/;
4515
+ var PERMISSION_PATTERN2 = /^[a-z][a-z0-9_-]{0,63}(?:\.[a-z][a-z0-9_-]{0,63})*:(?:[a-z][a-z0-9_-]{0,63}|\*)$/;
4516
+ var RETRY_AFTER_DATE_PATTERN2 = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
4517
+ var PUBLIC_DETAIL_SOURCES2 = /* @__PURE__ */ new Set([
4518
+ "api_key_introspection",
4519
+ "api_key_validation",
4520
+ "auth_context",
4521
+ "auth_validate",
4522
+ "catalog",
4523
+ "credential",
4524
+ "runtime",
4525
+ "service_token_provider",
4526
+ "service_verify_key",
4527
+ "token_validation",
4528
+ "upstream"
4529
+ ]);
4530
+ function canonicalTopoloErrorCode2(value) {
4531
+ if (typeof value !== "string") return null;
4532
+ const candidate = value.trim().toLowerCase();
4533
+ if (!ERROR_CODE_PATTERN2.test(candidate) || !PUBLIC_ERROR_CODES2.has(candidate)) return null;
4534
+ return candidate;
4535
+ }
4536
+ function canonicalTopoloRequestId2(value) {
4537
+ if (typeof value !== "string") return void 0;
4538
+ const candidate = value.trim();
4539
+ return TOPOLO_REQUEST_ID_PATTERN2.test(candidate) || CLOUDFLARE_RAY_ID_PATTERN2.test(candidate) ? candidate : void 0;
4540
+ }
4541
+ function canonicalTopoloRetryAfter2(value) {
4542
+ if (typeof value !== "string") return void 0;
4543
+ const candidate = value.trim();
4544
+ if (/^\d{1,10}$/.test(candidate) || RETRY_AFTER_DATE_PATTERN2.test(candidate)) return candidate;
4545
+ return void 0;
4546
+ }
4547
+ function canonicalTopoloRequired2(value) {
4548
+ if (!Array.isArray(value)) return [];
4549
+ return [...new Set(value.filter((item) => typeof item === "string" && PERMISSION_PATTERN2.test(item)))].slice(0, 32);
4550
+ }
4097
4551
  var TopoloAuthError2 = class extends TopoloSdkError2 {
4098
4552
  status;
4099
4553
  body;
@@ -4101,27 +4555,91 @@ var TopoloAuthError2 = class extends TopoloSdkError2 {
4101
4555
  path;
4102
4556
  requestId;
4103
4557
  retryAfter;
4558
+ details;
4104
4559
  constructor(message, code = "auth_error", options = {}) {
4105
- super(code, message);
4560
+ super(canonicalTopoloErrorCode2(code) ?? "auth_error", message);
4106
4561
  this.name = "TopoloAuthError";
4107
4562
  this.status = options.status ?? 401;
4108
4563
  this.body = options.body;
4109
4564
  this.service = options.service;
4110
4565
  this.path = options.path;
4111
- this.requestId = options.requestId;
4112
- this.retryAfter = options.retryAfter;
4566
+ this.requestId = canonicalTopoloRequestId2(options.requestId);
4567
+ this.retryAfter = canonicalTopoloRetryAfter2(options.retryAfter);
4568
+ this.details = extractTopoloServiceErrorDetails2(
4569
+ options.details === void 0 ? options.body : { details: options.details }
4570
+ );
4113
4571
  }
4114
4572
  };
4115
- function extractHttpErrorCode2(body) {
4116
- if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
4573
+ function extractTopoloServiceErrorCode2(body) {
4574
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
4117
4575
  const record = body;
4118
- if (typeof record.code === "string" && record.code.trim()) return record.code.trim();
4576
+ const topLevel = canonicalTopoloErrorCode2(record.code);
4577
+ if (topLevel) return topLevel;
4119
4578
  const nested = record.error;
4579
+ const nestedString = canonicalTopoloErrorCode2(nested);
4580
+ if (nestedString) return nestedString;
4120
4581
  if (nested && typeof nested === "object" && !Array.isArray(nested)) {
4121
4582
  const code = nested.code;
4122
- if (typeof code === "string" && code.trim()) return code.trim();
4583
+ const nestedCode = canonicalTopoloErrorCode2(code);
4584
+ if (nestedCode) return nestedCode;
4123
4585
  }
4124
- return "http_error";
4586
+ return null;
4587
+ }
4588
+ function serviceErrorCode2(body, fallback) {
4589
+ return extractTopoloServiceErrorCode2(body) ?? fallback;
4590
+ }
4591
+ function extractTopoloServiceErrorDetails2(body) {
4592
+ if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
4593
+ const record = body;
4594
+ let candidate = record.details;
4595
+ const nested = record.error;
4596
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
4597
+ const nestedRecord = nested;
4598
+ if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
4599
+ }
4600
+ if (candidate === void 0) candidate = record;
4601
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return void 0;
4602
+ const source = candidate;
4603
+ const safe = {};
4604
+ for (const [key, value] of Object.entries(source)) {
4605
+ if (/present$/i.test(key) && typeof value === "boolean") {
4606
+ safe[key] = value;
4607
+ continue;
4608
+ }
4609
+ if (key === "retryable" && typeof value === "boolean") {
4610
+ safe.retryable = value;
4611
+ continue;
4612
+ }
4613
+ if (key === "code") {
4614
+ const sanitized = canonicalTopoloErrorCode2(value);
4615
+ if (sanitized) safe.code = sanitized;
4616
+ continue;
4617
+ }
4618
+ if (key === "source") {
4619
+ const sanitized = typeof value === "string" ? value.trim().toLowerCase() : "";
4620
+ if (PUBLIC_DETAIL_SOURCES2.has(sanitized)) safe.source = sanitized;
4621
+ continue;
4622
+ }
4623
+ if (key === "dependency" || key === "platform") {
4624
+ safe[`${key}Present`] = value !== null && value !== void 0;
4625
+ continue;
4626
+ }
4627
+ if (key === "error" && !safe.code) {
4628
+ const sanitized = canonicalTopoloErrorCode2(value);
4629
+ if (sanitized) safe.code = sanitized;
4630
+ continue;
4631
+ }
4632
+ if (key === "required" && Array.isArray(value)) {
4633
+ const required = canonicalTopoloRequired2(value);
4634
+ if (required.length) safe.required = required;
4635
+ continue;
4636
+ }
4637
+ if (["missingFields", "fields", "paths"].includes(key) && Array.isArray(value)) {
4638
+ const paths = value.filter((item) => typeof item === "string" && /^[$.a-z0-9_[\]-]{1,160}$/i.test(item));
4639
+ if (paths.length) safe[key] = paths;
4640
+ }
4641
+ }
4642
+ return Object.keys(safe).length ? safe : void 0;
4125
4643
  }
4126
4644
  var TopoloHttpError2 = class extends TopoloSdkError2 {
4127
4645
  status;
@@ -4130,15 +4648,19 @@ var TopoloHttpError2 = class extends TopoloSdkError2 {
4130
4648
  path;
4131
4649
  requestId;
4132
4650
  retryAfter;
4651
+ details;
4133
4652
  constructor(service, path, status, body, message, options = {}) {
4134
- super(extractHttpErrorCode2(body), message ?? `HTTP ${status} from ${service}${path}`);
4653
+ super(serviceErrorCode2(body, "http_error"), message ?? `HTTP ${status} from ${service}${path}`);
4135
4654
  this.name = "TopoloHttpError";
4136
4655
  this.status = status;
4137
4656
  this.body = body;
4138
4657
  this.service = service;
4139
4658
  this.path = path;
4140
- this.requestId = options.requestId;
4141
- this.retryAfter = options.retryAfter;
4659
+ this.requestId = canonicalTopoloRequestId2(options.requestId);
4660
+ this.retryAfter = canonicalTopoloRetryAfter2(options.retryAfter);
4661
+ this.details = extractTopoloServiceErrorDetails2(
4662
+ options.details === void 0 ? body : { details: options.details }
4663
+ );
4142
4664
  }
4143
4665
  };
4144
4666
  var APP_API_URLS2 = {
@@ -4244,8 +4766,9 @@ var TopoloOAuth = class {
4244
4766
  return parsed;
4245
4767
  }
4246
4768
  };
4247
- var ENVIRONMENT_DEFAULT_SERVICE_URLS = {
4769
+ var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
4248
4770
  production: {
4771
+ auth: "https://auth.topolo.app",
4249
4772
  developers: "https://developers.topolo.app"
4250
4773
  },
4251
4774
  staging: {
@@ -4259,6 +4782,52 @@ var ENVIRONMENT_DEFAULT_SERVICE_URLS = {
4259
4782
  },
4260
4783
  local: {}
4261
4784
  };
4785
+ function hostnameHasSuffix2(hostname, suffix) {
4786
+ return hostname === suffix || hostname.endsWith(`.${suffix}`);
4787
+ }
4788
+ function workersDevAccountMatches2(hostname, account) {
4789
+ if (!hostname.endsWith(".workers.dev")) return false;
4790
+ const labels = hostname.slice(0, -".workers.dev".length).split(".");
4791
+ return labels.length >= 2 && labels.at(-1) === account;
4792
+ }
4793
+ function originMatchesEnvironment2(url, environment) {
4794
+ const hostname = url.hostname.toLowerCase();
4795
+ if (environment === "local") {
4796
+ return hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "127.0.0.1" || hostname === "[::1]";
4797
+ }
4798
+ if (url.protocol !== "https:") return false;
4799
+ if (environment === "production") {
4800
+ return hostnameHasSuffix2(hostname, "topolo.app") || hostnameHasSuffix2(hostname, "topolo.io") || workersDevAccountMatches2(hostname, "topolo");
4801
+ }
4802
+ if (environment === "staging") {
4803
+ return hostnameHasSuffix2(hostname, "stg.topolo.us") || workersDevAccountMatches2(hostname, "topolo-staging");
4804
+ }
4805
+ return hostnameHasSuffix2(hostname, "topolo.dev") || workersDevAccountMatches2(hostname, "topolo-development");
4806
+ }
4807
+ function assertTopoloServiceUrlEnvironment2(service, value, environment) {
4808
+ let url;
4809
+ try {
4810
+ url = new URL(value);
4811
+ } catch {
4812
+ throw new TopoloSdkError2("invalid_service_url", `Invalid URL configured for Topolo service "${service}".`);
4813
+ }
4814
+ if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password || !originMatchesEnvironment2(url, environment)) {
4815
+ throw new TopoloSdkError2(
4816
+ "environment_mismatch",
4817
+ `Refusing Topolo service "${service}": ${url.origin} does not belong to ${environment}.`
4818
+ );
4819
+ }
4820
+ return url.origin;
4821
+ }
4822
+ function topoloServiceOrigins2(serviceUrls, environment) {
4823
+ return Object.fromEntries(
4824
+ Object.entries(serviceUrls ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([service, value]) => [
4825
+ service,
4826
+ assertTopoloServiceUrlEnvironment2(service, value, environment)
4827
+ ])
4828
+ );
4829
+ }
4830
+ var ENVIRONMENT_DEFAULT_SERVICE_URLS = TOPOLO_ENVIRONMENT_SERVICE_URLS;
4262
4831
  function configPath() {
4263
4832
  const override = process.env.TOPOLO_CONFIG_PATH;
4264
4833
  if (override) return override;
@@ -4339,9 +4908,9 @@ function writeProfileConfig(config, environment, profile) {
4339
4908
  }
4340
4909
  async function resolveCredential(environment = selectedEnvironment()) {
4341
4910
  const prefix = envVarPrefix(environment);
4342
- const apiKey = process.env[`${prefix}_API_KEY`] ?? process.env.TOPOLO_API_KEY;
4911
+ const apiKey = process.env[`${prefix}_API_KEY`] ?? (environment === "production" ? process.env.TOPOLO_API_KEY : void 0);
4343
4912
  if (apiKey) return { kind: "api_key", apiKey };
4344
- const accessToken = process.env[`${prefix}_ACCESS_TOKEN`] ?? process.env.TOPOLO_ACCESS_TOKEN;
4913
+ const accessToken = process.env[`${prefix}_ACCESS_TOKEN`] ?? (environment === "production" ? process.env.TOPOLO_ACCESS_TOKEN : void 0);
4345
4914
  if (accessToken) return { kind: "access_token", accessToken };
4346
4915
  const config = await readConfig();
4347
4916
  return profileForEnvironment(config, environment).credential ?? null;
@@ -4349,7 +4918,7 @@ async function resolveCredential(environment = selectedEnvironment()) {
4349
4918
  var REFRESH_SKEW_SECONDS = 60;
4350
4919
  async function refreshCredentialIfNeeded(environment = selectedEnvironment(), options = {}) {
4351
4920
  const prefix = envVarPrefix(environment);
4352
- if (process.env[`${prefix}_API_KEY`] || process.env[`${prefix}_ACCESS_TOKEN`] || process.env.TOPOLO_API_KEY || process.env.TOPOLO_ACCESS_TOKEN) {
4921
+ 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) {
4353
4922
  return resolveCredential(environment);
4354
4923
  }
4355
4924
  const config = await readConfig();
@@ -4359,9 +4928,16 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
4359
4928
  if (!cred.refreshToken || !profile.oauthClientId) return cred;
4360
4929
  const now = Math.floor(Date.now() / 1e3);
4361
4930
  if (!options.force && cred.expiresAt && cred.expiresAt - REFRESH_SKEW_SECONDS > now) return cred;
4931
+ const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
4932
+ if (environment === "local" && !serviceUrls?.auth) {
4933
+ throw new TopoloSdkError2(
4934
+ "environment_service_url_required",
4935
+ "Local Topolo credential refresh requires an explicit local Auth service origin."
4936
+ );
4937
+ }
4362
4938
  try {
4363
4939
  const oauth = new TopoloOAuth({
4364
- serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
4940
+ serviceUrls
4365
4941
  });
4366
4942
  const tokens = await oauth.refreshToken({
4367
4943
  clientId: profile.oauthClientId,
@@ -4402,9 +4978,16 @@ async function refreshContextCredentialIfNeeded(environment, key, options = {})
4402
4978
  if (!credential.refreshToken || !oauthClientId) return credential;
4403
4979
  const now = Math.floor(Date.now() / 1e3);
4404
4980
  if (!options.force && credential.expiresAt && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
4981
+ const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
4982
+ if (environment === "local" && !serviceUrls?.auth) {
4983
+ throw new TopoloSdkError2(
4984
+ "environment_service_url_required",
4985
+ "Local Topolo context refresh requires an explicit local Auth service origin."
4986
+ );
4987
+ }
4405
4988
  try {
4406
4989
  const oauth = new TopoloOAuth({
4407
- serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
4990
+ serviceUrls
4408
4991
  });
4409
4992
  const tokens = await oauth.refreshToken({
4410
4993
  clientId: oauthClientId,
@@ -4451,7 +5034,8 @@ function resolveServiceUrls(configUrls, environment = selectedEnvironment()) {
4451
5034
  merged[k.slice("TOPOLO_SERVICE_URL_".length).toLowerCase()] = v;
4452
5035
  }
4453
5036
  }
4454
- return Object.keys(merged).length > 0 ? merged : void 0;
5037
+ const origins = topoloServiceOrigins2(merged, environment);
5038
+ return Object.keys(origins).length > 0 ? origins : void 0;
4455
5039
  }
4456
5040
 
4457
5041
  // src/server.ts
@@ -4816,6 +5400,38 @@ var TOOLS = [
4816
5400
  return args["detail"] === true ? result : compactActionValidation(result.action.actionId, result.validation);
4817
5401
  }
4818
5402
  },
5403
+ {
5404
+ name: "topolo_prepare_action",
5405
+ title: "Prepare one Topolo action",
5406
+ description: "Fetches one exact live action contract once and returns one valid published example, missing fields, exact request, confirmation state, verification, and rollback without executing it.",
5407
+ requiredScopes: [],
5408
+ destructive: false,
5409
+ inputSchema: {
5410
+ type: "object",
5411
+ properties: {
5412
+ actionId: { type: "string", minLength: 1 },
5413
+ input: { type: "object", additionalProperties: true },
5414
+ resourceType: { type: "string" },
5415
+ resourceId: { type: "string" },
5416
+ confirm: { type: "boolean", description: "Include confirmation in the plan without executing the action." }
5417
+ },
5418
+ required: ["actionId"],
5419
+ additionalProperties: false
5420
+ },
5421
+ handler: async (topolo, args) => {
5422
+ const actionId = requiredString(args, "actionId");
5423
+ const resource = optionalResourceContext(args);
5424
+ const preparation = await topolo.client.prepareAction(
5425
+ actionId,
5426
+ args["input"] === void 0 ? void 0 : coerceObjectParam(args["input"], "input"),
5427
+ {
5428
+ confirm: args["confirm"] === true,
5429
+ ...resource ? { resource } : {}
5430
+ }
5431
+ );
5432
+ return compactActionPreparation(preparation);
5433
+ }
5434
+ },
4819
5435
  {
4820
5436
  name: "topolo_plan_action",
4821
5437
  title: "Plan one Topolo action",
@@ -5283,11 +5899,29 @@ function readMcpVersion() {
5283
5899
  }
5284
5900
  var MCP_VERSION = readMcpVersion();
5285
5901
 
5902
+ // src/context.ts
5903
+ function createMcpExecutionContext(input) {
5904
+ return createTopoloAgentExecutionContext(input);
5905
+ }
5906
+ function mcpServerInstructions(context) {
5907
+ return JSON.stringify({
5908
+ effectiveTopoloContext: context,
5909
+ rules: [
5910
+ "Use only this environment, identity, organization, context key, and service origins.",
5911
+ "Prepare mutations before execution and preserve the unchanged request for confirmation.",
5912
+ "Never infer undisclosed actions or switch organization context inside this MCP session."
5913
+ ]
5914
+ });
5915
+ }
5916
+ function mcpResultEnvelope(context, data) {
5917
+ return { context, data };
5918
+ }
5919
+
5286
5920
  // src/server.ts
5287
- function buildServer(topolo, scopes, tools = TOOLS) {
5921
+ function buildServer(topolo, scopes, context, tools = TOOLS) {
5288
5922
  const server = new Server(
5289
5923
  { name: "topolo", version: MCP_VERSION },
5290
- { capabilities: { tools: {} } }
5924
+ { capabilities: { tools: {} }, instructions: mcpServerInstructions(context) }
5291
5925
  );
5292
5926
  server.setRequestHandler(ListToolsRequestSchema, async () => {
5293
5927
  const availableTools = await buildTopoloTools(topolo, scopes, tools);
@@ -5310,66 +5944,74 @@ function buildServer(topolo, scopes, tools = TOOLS) {
5310
5944
  const tool = availableTools.find((t) => t.name === params.name);
5311
5945
  if (!tool) {
5312
5946
  return toolError(
5947
+ context,
5313
5948
  `Tool "${params.name}" is not available for this credential. Either the tool does not exist or the current scopes do not permit it.`
5314
5949
  );
5315
5950
  }
5316
5951
  if (!hasAnyScope(scopes, tool.requiredScopes)) {
5317
5952
  return toolError(
5953
+ context,
5318
5954
  `Tool "${tool.name}" requires one of: ${tool.requiredScopes.join(", ")}. Current credential does not grant any of these.`
5319
5955
  );
5320
5956
  }
5321
5957
  try {
5322
5958
  const result = await tool.handler(topolo, params.arguments ?? {});
5323
5959
  return {
5324
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
5960
+ content: [{ type: "text", text: JSON.stringify(mcpResultEnvelope(context, result)) }]
5325
5961
  };
5326
5962
  } catch (err) {
5327
5963
  if (err instanceof TopoloPermissionError) {
5328
- return toolStructuredError(err, `Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`, {
5964
+ return toolStructuredError(context, err, err.message || "Permission denied.", {
5329
5965
  required: err.required
5330
5966
  });
5331
5967
  }
5332
5968
  if (err instanceof TopoloAuthError) {
5333
- return toolStructuredError(err, "Authentication failed.");
5969
+ return toolStructuredError(context, err, "Authentication failed.");
5334
5970
  }
5335
5971
  if (err instanceof TopoloHttpError) {
5336
- return toolStructuredError(err, err.message);
5972
+ return toolStructuredError(context, err, err.message);
5337
5973
  }
5338
- return toolError(publicMcpErrorMessage(err, "Tool execution failed."));
5974
+ return toolError(context, publicMcpErrorMessage(err, "Tool execution failed."));
5339
5975
  }
5340
5976
  });
5341
5977
  return server;
5342
5978
  }
5343
- function toolStructuredError(error, message, extra = {}) {
5344
- return toolError(JSON.stringify({
5345
- error: {
5346
- code: error.code,
5347
- status: error.status,
5348
- message,
5349
- service: error.service,
5350
- path: error.path,
5351
- requestId: error.requestId,
5352
- retryAfter: error.retryAfter,
5353
- details: error.body,
5354
- ...extra
5355
- }
5356
- }, null, 2));
5979
+ function toolStructuredError(context, error, _message, extra = {}) {
5980
+ return toolError(context, {
5981
+ code: error.code,
5982
+ status: error.status,
5983
+ message: publicTopoloErrorMessage(error),
5984
+ service: error.service,
5985
+ path: error.path,
5986
+ requestId: error.requestId,
5987
+ retryAfter: error.retryAfter,
5988
+ details: error.details,
5989
+ hint: topoloErrorHint(error),
5990
+ ...extra
5991
+ });
5357
5992
  }
5358
5993
  async function advertisedToolNames(topolo, scopes, tools = TOOLS) {
5359
5994
  return (await buildTopoloTools(topolo, scopes, tools)).map((t) => t.name);
5360
5995
  }
5361
- function toolError(message) {
5996
+ function toolError(context, error) {
5362
5997
  return {
5363
5998
  isError: true,
5364
- content: [{ type: "text", text: message }]
5999
+ content: [{
6000
+ type: "text",
6001
+ text: JSON.stringify({
6002
+ context,
6003
+ error: typeof error === "string" ? { code: "mcp_error", message: error } : error
6004
+ })
6005
+ }]
5365
6006
  };
5366
6007
  }
5367
6008
 
5368
6009
  // src/auth.ts
5369
- function resolveCredentialFromEnv() {
5370
- const apiKey = process.env.TOPOLO_API_KEY;
6010
+ function resolveCredentialFromEnv(environment = "production") {
6011
+ const prefix = environment === "production" ? "TOPOLO_PRODUCTION" : `TOPOLO_${environment.toUpperCase()}`;
6012
+ const apiKey = process.env[`${prefix}_API_KEY`] ?? (environment === "production" ? process.env.TOPOLO_API_KEY : void 0);
5371
6013
  if (apiKey) return { kind: "api_key", apiKey };
5372
- const accessToken = process.env.TOPOLO_ACCESS_TOKEN;
6014
+ const accessToken = process.env[`${prefix}_ACCESS_TOKEN`] ?? (environment === "production" ? process.env.TOPOLO_ACCESS_TOKEN : void 0);
5373
6015
  if (accessToken) return { kind: "access_token", accessToken };
5374
6016
  return null;
5375
6017
  }
@@ -5401,7 +6043,7 @@ function resolvePinnedContextKey(profile, selector) {
5401
6043
  throw new TopoloMcpPublicError(`TOPOLO_CONTEXT "${selector}" is not signed in for this environment`);
5402
6044
  }
5403
6045
  async function resolveMcpCredential(environment, selector = process.env.TOPOLO_CONTEXT, dependencies = DEFAULT_DEPENDENCIES) {
5404
- const environmentCredential = dependencies.resolveCredentialFromEnv();
6046
+ const environmentCredential = dependencies.resolveCredentialFromEnv(environment);
5405
6047
  const requestedSelector = selector?.trim();
5406
6048
  if (environmentCredential) {
5407
6049
  if (requestedSelector) {
@@ -5410,15 +6052,16 @@ async function resolveMcpCredential(environment, selector = process.env.TOPOLO_C
5410
6052
  return { credential: environmentCredential, contextKey: null };
5411
6053
  }
5412
6054
  if (requestedSelector) {
5413
- const config = await dependencies.readConfig();
6055
+ const config2 = await dependencies.readConfig();
5414
6056
  const contextKey = resolvePinnedContextKey(
5415
- profileForEnvironment(config, environment),
6057
+ profileForEnvironment(config2, environment),
5416
6058
  requestedSelector
5417
6059
  );
5418
6060
  const credential = await dependencies.refreshContextCredentialIfNeeded(environment, contextKey);
5419
6061
  return {
5420
6062
  credential,
5421
6063
  contextKey,
6064
+ storedContext: profileForEnvironment(config2, environment).contexts?.[contextKey],
5422
6065
  credentialProvider: () => dependencies.refreshContextCredentialIfNeeded(
5423
6066
  environment,
5424
6067
  contextKey
@@ -5430,6 +6073,25 @@ async function resolveMcpCredential(environment, selector = process.env.TOPOLO_C
5430
6073
  )
5431
6074
  };
5432
6075
  }
6076
+ const config = await dependencies.readConfig();
6077
+ const profile = profileForEnvironment(config, environment);
6078
+ const activeContext = profile.activeContext;
6079
+ if (activeContext) {
6080
+ const storedContext = profile.contexts?.[activeContext];
6081
+ if (!storedContext) {
6082
+ throw new TopoloMcpPublicError(`Active Topolo context "${activeContext}" is not signed in for this environment`);
6083
+ }
6084
+ return {
6085
+ credential: await dependencies.refreshContextCredentialIfNeeded(environment, activeContext),
6086
+ contextKey: activeContext,
6087
+ storedContext,
6088
+ credentialProvider: () => dependencies.refreshContextCredentialIfNeeded(environment, activeContext),
6089
+ refreshCredential: () => dependencies.refreshContextCredentialIfNeeded(environment, activeContext, { force: true })
6090
+ };
6091
+ }
6092
+ if (Object.keys(profile.contexts ?? {}).length > 0) {
6093
+ throw new TopoloMcpPublicError("No active Topolo context is selected for this environment");
6094
+ }
5433
6095
  return {
5434
6096
  credential: await dependencies.refreshCredentialIfNeeded(environment),
5435
6097
  contextKey: null,
@@ -5484,6 +6146,7 @@ async function runStdioMcp(dependencies = DEFAULT_DEPENDENCIES2) {
5484
6146
  const serviceUrls = dependencies.resolveServiceUrls(profile.serviceUrls, environment);
5485
6147
  const topolo = dependencies.createTopolo({
5486
6148
  credential,
6149
+ environment,
5487
6150
  agent: {
5488
6151
  clientName: "topolo-mcp",
5489
6152
  clientVersion: MCP_VERSION,
@@ -5494,8 +6157,15 @@ async function runStdioMcp(dependencies = DEFAULT_DEPENDENCIES2) {
5494
6157
  ...resolvedCredential.credentialProvider ? { credentialProvider: resolvedCredential.credentialProvider } : {},
5495
6158
  ...resolvedCredential.refreshCredential ? { refreshCredential: resolvedCredential.refreshCredential } : {}
5496
6159
  });
5497
- const scopes = await loadScopes(topolo);
5498
- const server = dependencies.buildServer(topolo, scopes);
6160
+ const identity = await loadIdentity(topolo);
6161
+ const scopes = scopesForIdentity(identity);
6162
+ const executionContext = createMcpExecutionContext({
6163
+ environment,
6164
+ contextKey: resolvedCredential.contextKey,
6165
+ identity,
6166
+ serviceUrls
6167
+ });
6168
+ const server = dependencies.buildServer(topolo, scopes, executionContext);
5499
6169
  const transport = dependencies.createTransport();
5500
6170
  await server.connect(transport);
5501
6171
  const toolNames = await dependencies.advertisedToolNames(topolo, scopes);
@@ -5505,14 +6175,19 @@ async function runStdioMcp(dependencies = DEFAULT_DEPENDENCIES2) {
5505
6175
  );
5506
6176
  }
5507
6177
  async function loadScopes(topolo) {
6178
+ return scopesForIdentity(await loadIdentity(topolo));
6179
+ }
6180
+ function scopesForIdentity(info) {
6181
+ const userWithOrgSlug = info.user;
6182
+ return {
6183
+ permissions: info.user.permissions,
6184
+ role: info.user.role,
6185
+ orgSlug: info.organization?.slug || userWithOrgSlug.orgSlug || void 0
6186
+ };
6187
+ }
6188
+ async function loadIdentity(topolo) {
5508
6189
  try {
5509
- const info = await topolo.identity.whoami();
5510
- const userWithOrgSlug = info.user;
5511
- return {
5512
- permissions: info.user.permissions,
5513
- role: info.user.role,
5514
- orgSlug: info.organization?.slug || userWithOrgSlug.orgSlug || void 0
5515
- };
6190
+ return await topolo.identity.whoami();
5516
6191
  } catch (err) {
5517
6192
  if (err instanceof TopoloAuthError) {
5518
6193
  throw new TopoloMcpPublicError("credential rejected.");
@@ -5544,9 +6219,11 @@ export {
5544
6219
  createStdioTransport,
5545
6220
  exitStdioProcess,
5546
6221
  isDirectExecution,
6222
+ loadIdentity,
5547
6223
  loadScopes,
5548
6224
  readStdioEnvironment,
5549
6225
  runStdioMcp,
6226
+ scopesForIdentity,
5550
6227
  terminateStdioMcp,
5551
6228
  writeStdioError
5552
6229
  };