@topolo/mcp 0.9.4 → 0.9.5

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 CHANGED
@@ -118,6 +118,7 @@ credentials already carry their own immutable context.
118
118
  active stored credential. **Refuse to start** if none is available.
119
119
  2. Call `GET /api/me` to load the user's granted scopes + role.
120
120
  3. Connect the stdio transport and begin serving MCP requests.
121
+
121
122
  4. Advertise a fixed, bounded discovery/invocation surface. Agents search one
122
123
  application at a time and call exact action IDs; the server never expands
123
124
  one MCP tool per action or materializes the global catalog.
@@ -126,6 +127,19 @@ Startup is synchronous and fast (< 500 ms typical). If the credential is
126
127
  rejected, the process logs the error to stderr and exits non-zero — host
127
128
  clients surface that as a failed server launch.
128
129
 
130
+ ## Data privacy boundary
131
+
132
+ TopoloMCP is a transport adapter and does not persist customer data. It has no
133
+ database, KV, R2, Durable Object, filesystem-write, analytics, or log-storage
134
+ binding. Credentials remain in the caller-owned environment or the shared CLI
135
+ credential store, and every request is authorized by the target Topolo service.
136
+
137
+ Only messages raised by TopoloMCP's own input validators are returned verbatim.
138
+ Authentication, SDK, provider, session-construction, and unexpected runtime
139
+ errors are replaced with stable non-sensitive messages before they reach MCP
140
+ responses or stderr. Target services remain responsible for retention, export,
141
+ deletion, encryption, and audit storage for the data they own.
142
+
129
143
  ## Tools
130
144
 
131
145
  | Tool | Required scopes | Destructive hint |
package/dist/dispatch.js CHANGED
@@ -50,6 +50,17 @@ function splitService(servicePart) {
50
50
  return [servicePart.slice(0, idx), servicePart.slice(idx + 1)];
51
51
  }
52
52
 
53
+ // src/errors.ts
54
+ var TopoloMcpPublicError = class extends Error {
55
+ constructor(message) {
56
+ super(message);
57
+ this.name = "TopoloMcpPublicError";
58
+ }
59
+ };
60
+ function publicMcpErrorMessage(error, fallback) {
61
+ return error instanceof TopoloMcpPublicError ? error.message : fallback;
62
+ }
63
+
53
64
  // src/tools.ts
54
65
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
55
66
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
@@ -58,7 +69,7 @@ function optionalResourceContext(args) {
58
69
  const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
59
70
  if (!resourceType && !resourceId) return void 0;
60
71
  if (!resourceType || !resourceId) {
61
- throw new Error("resourceType and resourceId must be provided together.");
72
+ throw new TopoloMcpPublicError("resourceType and resourceId must be provided together.");
62
73
  }
63
74
  return { resourceType, resourceId };
64
75
  }
@@ -140,7 +151,7 @@ var TOOLS = [
140
151
  },
141
152
  handler: async (topolo, args) => {
142
153
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
143
- if (!service) throw new Error("service is required for bounded action discovery.");
154
+ if (!service) throw new TopoloMcpPublicError("service is required for bounded action discovery.");
144
155
  return topolo.client.searchActions({
145
156
  service,
146
157
  query: typeof args["query"] === "string" ? args["query"] : void 0,
@@ -169,7 +180,7 @@ var TOOLS = [
169
180
  handler: async (topolo, args) => {
170
181
  const application = args["application"];
171
182
  if (typeof application !== "string" || !application.trim()) {
172
- throw new Error("`application` must be a non-empty string.");
183
+ throw new TopoloMcpPublicError("`application` must be a non-empty string.");
173
184
  }
174
185
  return { application: await topolo.client.getService(application) };
175
186
  }
@@ -188,7 +199,7 @@ var TOOLS = [
188
199
  },
189
200
  handler: async (topolo, args) => {
190
201
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
191
- if (!actionId) throw new Error("actionId is required.");
202
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
192
203
  return { action: await topolo.client.getAction(actionId) };
193
204
  }
194
205
  },
@@ -211,10 +222,10 @@ var TOOLS = [
211
222
  },
212
223
  handler: async (topolo, args) => {
213
224
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
214
- if (!actionId) throw new Error("actionId is required.");
225
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
215
226
  const input = args["input"];
216
227
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
217
- throw new Error("input must be an object.");
228
+ throw new TopoloMcpPublicError("input must be an object.");
218
229
  }
219
230
  const resource = optionalResourceContext(args);
220
231
  return topolo.client.readAction(
@@ -244,10 +255,10 @@ var TOOLS = [
244
255
  },
245
256
  handler: async (topolo, args) => {
246
257
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
247
- if (!actionId) throw new Error("actionId is required.");
258
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
248
259
  const input = args["input"];
249
260
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
250
- throw new Error("input must be an object.");
261
+ throw new TopoloMcpPublicError("input must be an object.");
251
262
  }
252
263
  const resource = optionalResourceContext(args);
253
264
  return topolo.client.callAction(
@@ -277,10 +288,10 @@ var TOOLS = [
277
288
  },
278
289
  handler: async (topolo, args) => {
279
290
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
280
- if (!actionId) throw new Error("actionId is required.");
291
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
281
292
  const input = args["input"];
282
293
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
283
- throw new Error("input must be an object.");
294
+ throw new TopoloMcpPublicError("input must be an object.");
284
295
  }
285
296
  return topolo.client.validateActionInput(actionId, input ?? {});
286
297
  }
@@ -305,10 +316,10 @@ var TOOLS = [
305
316
  },
306
317
  handler: async (topolo, args) => {
307
318
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
308
- if (!actionId) throw new Error("actionId is required.");
319
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
309
320
  const input = args["input"];
310
321
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
311
- throw new Error("input must be an object.");
322
+ throw new TopoloMcpPublicError("input must be an object.");
312
323
  }
313
324
  const resource = optionalResourceContext(args);
314
325
  return topolo.client.planAction(
@@ -335,7 +346,7 @@ var TOOLS = [
335
346
  },
336
347
  handler: async (topolo, args) => {
337
348
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
338
- if (!actionId) throw new Error("actionId is required.");
349
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
339
350
  const action = await topolo.client.getAction(actionId);
340
351
  return {
341
352
  actionId: action.actionId,
@@ -358,7 +369,7 @@ var TOOLS = [
358
369
  },
359
370
  handler: async (topolo, args) => {
360
371
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
361
- if (!service) throw new Error("service is required.");
372
+ if (!service) throw new TopoloMcpPublicError("service is required.");
362
373
  return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
363
374
  }
364
375
  },
@@ -376,7 +387,7 @@ var TOOLS = [
376
387
  },
377
388
  handler: async (topolo, args) => {
378
389
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
379
- if (!application) throw new Error("application is required.");
390
+ if (!application) throw new TopoloMcpPublicError("application is required.");
380
391
  const entry = await topolo.client.getService(application);
381
392
  return {
382
393
  application: { appId: entry.appId, slug: entry.slug, name: entry.name },
@@ -398,7 +409,7 @@ var TOOLS = [
398
409
  },
399
410
  handler: async (topolo, args) => {
400
411
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
401
- if (!application) throw new Error("application is required.");
412
+ if (!application) throw new TopoloMcpPublicError("application is required.");
402
413
  return topolo.client.listBindableResources(application);
403
414
  }
404
415
  },
@@ -429,7 +440,7 @@ var TOOLS = [
429
440
  };
430
441
  }
431
442
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
432
- throw new Error(
443
+ throw new TopoloMcpPublicError(
433
444
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
434
445
  );
435
446
  }
@@ -467,7 +478,7 @@ var TOOLS = [
467
478
  const application = args["application"];
468
479
  const failOn = args["failOn"];
469
480
  if (failOn !== void 0 && typeof failOn !== "string") {
470
- throw new Error("failOn must be one of: missing, needs_review, partial");
481
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
471
482
  }
472
483
  const gate = failOn ? normalizeFailOn(failOn) : null;
473
484
  let report;
@@ -476,7 +487,7 @@ var TOOLS = [
476
487
  return gate ? { ...report, conformanceGate: evaluateApplicationAuditGate(report, gate) } : report;
477
488
  }
478
489
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
479
- throw new Error(
490
+ throw new TopoloMcpPublicError(
480
491
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
481
492
  );
482
493
  }
@@ -536,14 +547,14 @@ var TOOLS = [
536
547
  const service = args["service"];
537
548
  const path = args["path"];
538
549
  if (typeof service !== "string" || !service.trim()) {
539
- throw new Error("service is required. Call topolo_search_applications first to discover callable services.");
550
+ throw new TopoloMcpPublicError("service is required. Call topolo_search_applications first to discover callable services.");
540
551
  }
541
552
  if (typeof path !== "string" || !path) {
542
- throw new Error("path is required");
553
+ throw new TopoloMcpPublicError("path is required");
543
554
  }
544
555
  const methodRaw = args["method"] ?? "GET";
545
556
  if (typeof methodRaw !== "string" || !HTTP_METHODS.includes(methodRaw)) {
546
- throw new Error(`Unknown HTTP method "${String(methodRaw)}"`);
557
+ throw new TopoloMcpPublicError(`Unknown HTTP method "${String(methodRaw)}"`);
547
558
  }
548
559
  const method = methodRaw;
549
560
  const query = args["query"];
@@ -599,7 +610,7 @@ function normalizeFailOn(value) {
599
610
  if (normalized === "missing" || normalized === "needs_review" || normalized === "partial") {
600
611
  return normalized;
601
612
  }
602
- throw new Error("failOn must be one of: missing, needs_review, partial");
613
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
603
614
  }
604
615
  function statusesAtOrWorse(threshold) {
605
616
  if (threshold === "missing") return ["missing"];
@@ -694,14 +705,14 @@ async function dispatchTopoloTool(input) {
694
705
  return {
695
706
  ok: false,
696
707
  reason: "auth",
697
- error: `Auth error: ${err.message}`,
708
+ error: "Authentication failed.",
698
709
  durationMs: Date.now() - started
699
710
  };
700
711
  }
701
712
  return {
702
713
  ok: false,
703
714
  reason: "handler",
704
- error: err instanceof Error ? err.message : String(err),
715
+ error: publicMcpErrorMessage(err, "Tool execution failed."),
705
716
  durationMs: Date.now() - started
706
717
  };
707
718
  }
package/dist/http.js CHANGED
@@ -14217,6 +14217,17 @@ function credentialFingerprint(credential) {
14217
14217
  return (hash >>> 0).toString(16).padStart(8, "0");
14218
14218
  }
14219
14219
 
14220
+ // src/errors.ts
14221
+ var TopoloMcpPublicError = class extends Error {
14222
+ constructor(message) {
14223
+ super(message);
14224
+ this.name = "TopoloMcpPublicError";
14225
+ }
14226
+ };
14227
+ function publicMcpErrorMessage(error2, fallback) {
14228
+ return error2 instanceof TopoloMcpPublicError ? error2.message : fallback;
14229
+ }
14230
+
14220
14231
  // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.26.0_@cfworker+json-schema@4.1.1_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
14221
14232
  function isZ4Schema(s) {
14222
14233
  const schema = s;
@@ -15872,7 +15883,7 @@ function optionalResourceContext(args) {
15872
15883
  const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
15873
15884
  if (!resourceType && !resourceId) return void 0;
15874
15885
  if (!resourceType || !resourceId) {
15875
- throw new Error("resourceType and resourceId must be provided together.");
15886
+ throw new TopoloMcpPublicError("resourceType and resourceId must be provided together.");
15876
15887
  }
15877
15888
  return { resourceType, resourceId };
15878
15889
  }
@@ -15954,7 +15965,7 @@ var TOOLS = [
15954
15965
  },
15955
15966
  handler: async (topolo, args) => {
15956
15967
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
15957
- if (!service) throw new Error("service is required for bounded action discovery.");
15968
+ if (!service) throw new TopoloMcpPublicError("service is required for bounded action discovery.");
15958
15969
  return topolo.client.searchActions({
15959
15970
  service,
15960
15971
  query: typeof args["query"] === "string" ? args["query"] : void 0,
@@ -15983,7 +15994,7 @@ var TOOLS = [
15983
15994
  handler: async (topolo, args) => {
15984
15995
  const application = args["application"];
15985
15996
  if (typeof application !== "string" || !application.trim()) {
15986
- throw new Error("`application` must be a non-empty string.");
15997
+ throw new TopoloMcpPublicError("`application` must be a non-empty string.");
15987
15998
  }
15988
15999
  return { application: await topolo.client.getService(application) };
15989
16000
  }
@@ -16002,7 +16013,7 @@ var TOOLS = [
16002
16013
  },
16003
16014
  handler: async (topolo, args) => {
16004
16015
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16005
- if (!actionId) throw new Error("actionId is required.");
16016
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16006
16017
  return { action: await topolo.client.getAction(actionId) };
16007
16018
  }
16008
16019
  },
@@ -16025,10 +16036,10 @@ var TOOLS = [
16025
16036
  },
16026
16037
  handler: async (topolo, args) => {
16027
16038
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16028
- if (!actionId) throw new Error("actionId is required.");
16039
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16029
16040
  const input = args["input"];
16030
16041
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16031
- throw new Error("input must be an object.");
16042
+ throw new TopoloMcpPublicError("input must be an object.");
16032
16043
  }
16033
16044
  const resource = optionalResourceContext(args);
16034
16045
  return topolo.client.readAction(
@@ -16058,10 +16069,10 @@ var TOOLS = [
16058
16069
  },
16059
16070
  handler: async (topolo, args) => {
16060
16071
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16061
- if (!actionId) throw new Error("actionId is required.");
16072
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16062
16073
  const input = args["input"];
16063
16074
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16064
- throw new Error("input must be an object.");
16075
+ throw new TopoloMcpPublicError("input must be an object.");
16065
16076
  }
16066
16077
  const resource = optionalResourceContext(args);
16067
16078
  return topolo.client.callAction(
@@ -16091,10 +16102,10 @@ var TOOLS = [
16091
16102
  },
16092
16103
  handler: async (topolo, args) => {
16093
16104
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16094
- if (!actionId) throw new Error("actionId is required.");
16105
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16095
16106
  const input = args["input"];
16096
16107
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16097
- throw new Error("input must be an object.");
16108
+ throw new TopoloMcpPublicError("input must be an object.");
16098
16109
  }
16099
16110
  return topolo.client.validateActionInput(actionId, input ?? {});
16100
16111
  }
@@ -16119,10 +16130,10 @@ var TOOLS = [
16119
16130
  },
16120
16131
  handler: async (topolo, args) => {
16121
16132
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16122
- if (!actionId) throw new Error("actionId is required.");
16133
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16123
16134
  const input = args["input"];
16124
16135
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16125
- throw new Error("input must be an object.");
16136
+ throw new TopoloMcpPublicError("input must be an object.");
16126
16137
  }
16127
16138
  const resource = optionalResourceContext(args);
16128
16139
  return topolo.client.planAction(
@@ -16149,7 +16160,7 @@ var TOOLS = [
16149
16160
  },
16150
16161
  handler: async (topolo, args) => {
16151
16162
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16152
- if (!actionId) throw new Error("actionId is required.");
16163
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16153
16164
  const action = await topolo.client.getAction(actionId);
16154
16165
  return {
16155
16166
  actionId: action.actionId,
@@ -16172,7 +16183,7 @@ var TOOLS = [
16172
16183
  },
16173
16184
  handler: async (topolo, args) => {
16174
16185
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
16175
- if (!service) throw new Error("service is required.");
16186
+ if (!service) throw new TopoloMcpPublicError("service is required.");
16176
16187
  return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
16177
16188
  }
16178
16189
  },
@@ -16190,7 +16201,7 @@ var TOOLS = [
16190
16201
  },
16191
16202
  handler: async (topolo, args) => {
16192
16203
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
16193
- if (!application) throw new Error("application is required.");
16204
+ if (!application) throw new TopoloMcpPublicError("application is required.");
16194
16205
  const entry = await topolo.client.getService(application);
16195
16206
  return {
16196
16207
  application: { appId: entry.appId, slug: entry.slug, name: entry.name },
@@ -16212,7 +16223,7 @@ var TOOLS = [
16212
16223
  },
16213
16224
  handler: async (topolo, args) => {
16214
16225
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
16215
- if (!application) throw new Error("application is required.");
16226
+ if (!application) throw new TopoloMcpPublicError("application is required.");
16216
16227
  return topolo.client.listBindableResources(application);
16217
16228
  }
16218
16229
  },
@@ -16243,7 +16254,7 @@ var TOOLS = [
16243
16254
  };
16244
16255
  }
16245
16256
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
16246
- throw new Error(
16257
+ throw new TopoloMcpPublicError(
16247
16258
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
16248
16259
  );
16249
16260
  }
@@ -16281,7 +16292,7 @@ var TOOLS = [
16281
16292
  const application = args["application"];
16282
16293
  const failOn = args["failOn"];
16283
16294
  if (failOn !== void 0 && typeof failOn !== "string") {
16284
- throw new Error("failOn must be one of: missing, needs_review, partial");
16295
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
16285
16296
  }
16286
16297
  const gate = failOn ? normalizeFailOn(failOn) : null;
16287
16298
  let report;
@@ -16290,7 +16301,7 @@ var TOOLS = [
16290
16301
  return gate ? { ...report, conformanceGate: evaluateApplicationAuditGate(report, gate) } : report;
16291
16302
  }
16292
16303
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
16293
- throw new Error(
16304
+ throw new TopoloMcpPublicError(
16294
16305
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
16295
16306
  );
16296
16307
  }
@@ -16350,14 +16361,14 @@ var TOOLS = [
16350
16361
  const service = args["service"];
16351
16362
  const path = args["path"];
16352
16363
  if (typeof service !== "string" || !service.trim()) {
16353
- throw new Error("service is required. Call topolo_search_applications first to discover callable services.");
16364
+ throw new TopoloMcpPublicError("service is required. Call topolo_search_applications first to discover callable services.");
16354
16365
  }
16355
16366
  if (typeof path !== "string" || !path) {
16356
- throw new Error("path is required");
16367
+ throw new TopoloMcpPublicError("path is required");
16357
16368
  }
16358
16369
  const methodRaw = args["method"] ?? "GET";
16359
16370
  if (typeof methodRaw !== "string" || !HTTP_METHODS.includes(methodRaw)) {
16360
- throw new Error(`Unknown HTTP method "${String(methodRaw)}"`);
16371
+ throw new TopoloMcpPublicError(`Unknown HTTP method "${String(methodRaw)}"`);
16361
16372
  }
16362
16373
  const method = methodRaw;
16363
16374
  const query = args["query"];
@@ -16413,7 +16424,7 @@ function normalizeFailOn(value) {
16413
16424
  if (normalized === "missing" || normalized === "needs_review" || normalized === "partial") {
16414
16425
  return normalized;
16415
16426
  }
16416
- throw new Error("failOn must be one of: missing, needs_review, partial");
16427
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
16417
16428
  }
16418
16429
  function statusesAtOrWorse(threshold) {
16419
16430
  if (threshold === "missing") return ["missing"];
@@ -16517,9 +16528,9 @@ function buildServer(topolo, scopes, tools = TOOLS) {
16517
16528
  return toolError(`Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`);
16518
16529
  }
16519
16530
  if (err instanceof TopoloAuthError) {
16520
- return toolError(`Auth error: ${err.message}`);
16531
+ return toolError("Authentication failed.");
16521
16532
  }
16522
- return toolError(err instanceof Error ? err.message : String(err));
16533
+ return toolError(publicMcpErrorMessage(err, "Tool execution failed."));
16523
16534
  }
16524
16535
  });
16525
16536
  return server;
@@ -16622,11 +16633,11 @@ function createTopoloMcpHttpHandler(options = {}) {
16622
16633
  session = await buildSessionForRequest(credential);
16623
16634
  } catch (err) {
16624
16635
  if (err instanceof TopoloAuthError2) {
16625
- return jsonRpcError(401, `Credential rejected: ${err.message}`, -32001);
16636
+ return jsonRpcError(401, "Credential rejected.", -32001);
16626
16637
  }
16627
16638
  return jsonRpcError(
16628
16639
  500,
16629
- `Failed to initialize MCP session: ${err instanceof Error ? err.message : String(err)}`
16640
+ publicMcpErrorMessage(err, "Failed to initialize MCP session.")
16630
16641
  );
16631
16642
  }
16632
16643
  return session.transport.handleRequest(request);
package/dist/index.js CHANGED
@@ -3899,6 +3899,17 @@ function splitService(servicePart) {
3899
3899
  return [servicePart.slice(0, idx), servicePart.slice(idx + 1)];
3900
3900
  }
3901
3901
 
3902
+ // src/errors.ts
3903
+ var TopoloMcpPublicError = class extends Error {
3904
+ constructor(message) {
3905
+ super(message);
3906
+ this.name = "TopoloMcpPublicError";
3907
+ }
3908
+ };
3909
+ function publicMcpErrorMessage(error, fallback) {
3910
+ return error instanceof TopoloMcpPublicError ? error.message : fallback;
3911
+ }
3912
+
3902
3913
  // src/tools.ts
3903
3914
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
3904
3915
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
@@ -3907,7 +3918,7 @@ function optionalResourceContext(args) {
3907
3918
  const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
3908
3919
  if (!resourceType && !resourceId) return void 0;
3909
3920
  if (!resourceType || !resourceId) {
3910
- throw new Error("resourceType and resourceId must be provided together.");
3921
+ throw new TopoloMcpPublicError("resourceType and resourceId must be provided together.");
3911
3922
  }
3912
3923
  return { resourceType, resourceId };
3913
3924
  }
@@ -3989,7 +4000,7 @@ var TOOLS = [
3989
4000
  },
3990
4001
  handler: async (topolo, args) => {
3991
4002
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
3992
- if (!service) throw new Error("service is required for bounded action discovery.");
4003
+ if (!service) throw new TopoloMcpPublicError("service is required for bounded action discovery.");
3993
4004
  return topolo.client.searchActions({
3994
4005
  service,
3995
4006
  query: typeof args["query"] === "string" ? args["query"] : void 0,
@@ -4018,7 +4029,7 @@ var TOOLS = [
4018
4029
  handler: async (topolo, args) => {
4019
4030
  const application = args["application"];
4020
4031
  if (typeof application !== "string" || !application.trim()) {
4021
- throw new Error("`application` must be a non-empty string.");
4032
+ throw new TopoloMcpPublicError("`application` must be a non-empty string.");
4022
4033
  }
4023
4034
  return { application: await topolo.client.getService(application) };
4024
4035
  }
@@ -4037,7 +4048,7 @@ var TOOLS = [
4037
4048
  },
4038
4049
  handler: async (topolo, args) => {
4039
4050
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4040
- if (!actionId) throw new Error("actionId is required.");
4051
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4041
4052
  return { action: await topolo.client.getAction(actionId) };
4042
4053
  }
4043
4054
  },
@@ -4060,10 +4071,10 @@ var TOOLS = [
4060
4071
  },
4061
4072
  handler: async (topolo, args) => {
4062
4073
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4063
- if (!actionId) throw new Error("actionId is required.");
4074
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4064
4075
  const input = args["input"];
4065
4076
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4066
- throw new Error("input must be an object.");
4077
+ throw new TopoloMcpPublicError("input must be an object.");
4067
4078
  }
4068
4079
  const resource = optionalResourceContext(args);
4069
4080
  return topolo.client.readAction(
@@ -4093,10 +4104,10 @@ var TOOLS = [
4093
4104
  },
4094
4105
  handler: async (topolo, args) => {
4095
4106
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4096
- if (!actionId) throw new Error("actionId is required.");
4107
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4097
4108
  const input = args["input"];
4098
4109
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4099
- throw new Error("input must be an object.");
4110
+ throw new TopoloMcpPublicError("input must be an object.");
4100
4111
  }
4101
4112
  const resource = optionalResourceContext(args);
4102
4113
  return topolo.client.callAction(
@@ -4126,10 +4137,10 @@ var TOOLS = [
4126
4137
  },
4127
4138
  handler: async (topolo, args) => {
4128
4139
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4129
- if (!actionId) throw new Error("actionId is required.");
4140
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4130
4141
  const input = args["input"];
4131
4142
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4132
- throw new Error("input must be an object.");
4143
+ throw new TopoloMcpPublicError("input must be an object.");
4133
4144
  }
4134
4145
  return topolo.client.validateActionInput(actionId, input ?? {});
4135
4146
  }
@@ -4154,10 +4165,10 @@ var TOOLS = [
4154
4165
  },
4155
4166
  handler: async (topolo, args) => {
4156
4167
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4157
- if (!actionId) throw new Error("actionId is required.");
4168
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4158
4169
  const input = args["input"];
4159
4170
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4160
- throw new Error("input must be an object.");
4171
+ throw new TopoloMcpPublicError("input must be an object.");
4161
4172
  }
4162
4173
  const resource = optionalResourceContext(args);
4163
4174
  return topolo.client.planAction(
@@ -4184,7 +4195,7 @@ var TOOLS = [
4184
4195
  },
4185
4196
  handler: async (topolo, args) => {
4186
4197
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4187
- if (!actionId) throw new Error("actionId is required.");
4198
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4188
4199
  const action = await topolo.client.getAction(actionId);
4189
4200
  return {
4190
4201
  actionId: action.actionId,
@@ -4207,7 +4218,7 @@ var TOOLS = [
4207
4218
  },
4208
4219
  handler: async (topolo, args) => {
4209
4220
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
4210
- if (!service) throw new Error("service is required.");
4221
+ if (!service) throw new TopoloMcpPublicError("service is required.");
4211
4222
  return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
4212
4223
  }
4213
4224
  },
@@ -4225,7 +4236,7 @@ var TOOLS = [
4225
4236
  },
4226
4237
  handler: async (topolo, args) => {
4227
4238
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
4228
- if (!application) throw new Error("application is required.");
4239
+ if (!application) throw new TopoloMcpPublicError("application is required.");
4229
4240
  const entry = await topolo.client.getService(application);
4230
4241
  return {
4231
4242
  application: { appId: entry.appId, slug: entry.slug, name: entry.name },
@@ -4247,7 +4258,7 @@ var TOOLS = [
4247
4258
  },
4248
4259
  handler: async (topolo, args) => {
4249
4260
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
4250
- if (!application) throw new Error("application is required.");
4261
+ if (!application) throw new TopoloMcpPublicError("application is required.");
4251
4262
  return topolo.client.listBindableResources(application);
4252
4263
  }
4253
4264
  },
@@ -4278,7 +4289,7 @@ var TOOLS = [
4278
4289
  };
4279
4290
  }
4280
4291
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
4281
- throw new Error(
4292
+ throw new TopoloMcpPublicError(
4282
4293
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
4283
4294
  );
4284
4295
  }
@@ -4316,7 +4327,7 @@ var TOOLS = [
4316
4327
  const application = args["application"];
4317
4328
  const failOn = args["failOn"];
4318
4329
  if (failOn !== void 0 && typeof failOn !== "string") {
4319
- throw new Error("failOn must be one of: missing, needs_review, partial");
4330
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
4320
4331
  }
4321
4332
  const gate = failOn ? normalizeFailOn(failOn) : null;
4322
4333
  let report;
@@ -4325,7 +4336,7 @@ var TOOLS = [
4325
4336
  return gate ? { ...report, conformanceGate: evaluateApplicationAuditGate(report, gate) } : report;
4326
4337
  }
4327
4338
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
4328
- throw new Error(
4339
+ throw new TopoloMcpPublicError(
4329
4340
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
4330
4341
  );
4331
4342
  }
@@ -4385,14 +4396,14 @@ var TOOLS = [
4385
4396
  const service = args["service"];
4386
4397
  const path = args["path"];
4387
4398
  if (typeof service !== "string" || !service.trim()) {
4388
- throw new Error("service is required. Call topolo_search_applications first to discover callable services.");
4399
+ throw new TopoloMcpPublicError("service is required. Call topolo_search_applications first to discover callable services.");
4389
4400
  }
4390
4401
  if (typeof path !== "string" || !path) {
4391
- throw new Error("path is required");
4402
+ throw new TopoloMcpPublicError("path is required");
4392
4403
  }
4393
4404
  const methodRaw = args["method"] ?? "GET";
4394
4405
  if (typeof methodRaw !== "string" || !HTTP_METHODS.includes(methodRaw)) {
4395
- throw new Error(`Unknown HTTP method "${String(methodRaw)}"`);
4406
+ throw new TopoloMcpPublicError(`Unknown HTTP method "${String(methodRaw)}"`);
4396
4407
  }
4397
4408
  const method = methodRaw;
4398
4409
  const query = args["query"];
@@ -4448,7 +4459,7 @@ function normalizeFailOn(value) {
4448
4459
  if (normalized === "missing" || normalized === "needs_review" || normalized === "partial") {
4449
4460
  return normalized;
4450
4461
  }
4451
- throw new Error("failOn must be one of: missing, needs_review, partial");
4462
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
4452
4463
  }
4453
4464
  function statusesAtOrWorse(threshold) {
4454
4465
  if (threshold === "missing") return ["missing"];
@@ -4552,9 +4563,9 @@ function buildServer(topolo, scopes, tools = TOOLS) {
4552
4563
  return toolError(`Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`);
4553
4564
  }
4554
4565
  if (err instanceof TopoloAuthError) {
4555
- return toolError(`Auth error: ${err.message}`);
4566
+ return toolError("Authentication failed.");
4556
4567
  }
4557
- return toolError(err instanceof Error ? err.message : String(err));
4568
+ return toolError(publicMcpErrorMessage(err, "Tool execution failed."));
4558
4569
  }
4559
4570
  });
4560
4571
  return server;
@@ -4588,7 +4599,7 @@ var DEFAULT_DEPENDENCIES = {
4588
4599
  function resolvePinnedContextKey(profile, selector) {
4589
4600
  const normalized = selector.trim().toLowerCase();
4590
4601
  if (!normalized) {
4591
- throw new Error("TOPOLO_CONTEXT must not be empty");
4602
+ throw new TopoloMcpPublicError("TOPOLO_CONTEXT must not be empty");
4592
4603
  }
4593
4604
  const contexts = Object.entries(profile.contexts ?? {});
4594
4605
  const exact = contexts.find(([key]) => key.toLowerCase() === normalized);
@@ -4600,16 +4611,16 @@ function resolvePinnedContextKey(profile, selector) {
4600
4611
  ].some((value) => String(value || "").trim().toLowerCase() === normalized));
4601
4612
  if (matches.length === 1) return matches[0][0];
4602
4613
  if (matches.length > 1) {
4603
- throw new Error(`TOPOLO_CONTEXT "${selector}" is ambiguous; use an exact context key`);
4614
+ throw new TopoloMcpPublicError(`TOPOLO_CONTEXT "${selector}" is ambiguous; use an exact context key`);
4604
4615
  }
4605
- throw new Error(`TOPOLO_CONTEXT "${selector}" is not signed in for this environment`);
4616
+ throw new TopoloMcpPublicError(`TOPOLO_CONTEXT "${selector}" is not signed in for this environment`);
4606
4617
  }
4607
4618
  async function resolveMcpCredential(environment, selector = process.env.TOPOLO_CONTEXT, dependencies = DEFAULT_DEPENDENCIES) {
4608
4619
  const environmentCredential = dependencies.resolveCredentialFromEnv();
4609
4620
  const requestedSelector = selector?.trim();
4610
4621
  if (environmentCredential) {
4611
4622
  if (requestedSelector) {
4612
- throw new Error("TOPOLO_CONTEXT cannot be combined with an environment credential");
4623
+ throw new TopoloMcpPublicError("TOPOLO_CONTEXT cannot be combined with an environment credential");
4613
4624
  }
4614
4625
  return { credential: environmentCredential, contextKey: null };
4615
4626
  }
@@ -4690,15 +4701,14 @@ async function loadScopes(topolo) {
4690
4701
  };
4691
4702
  } catch (err) {
4692
4703
  if (err instanceof TopoloAuthError) {
4693
- process.stderr.write(`TopoloMCP: credential rejected \u2014 ${err.message}
4694
- `);
4704
+ process.stderr.write("TopoloMCP: credential rejected.\n");
4695
4705
  process.exit(1);
4696
4706
  }
4697
4707
  throw err;
4698
4708
  }
4699
4709
  }
4700
4710
  main().catch((err) => {
4701
- process.stderr.write(`TopoloMCP fatal: ${err instanceof Error ? err.message : String(err)}
4711
+ process.stderr.write(`TopoloMCP fatal: ${publicMcpErrorMessage(err, "Unexpected startup failure.")}
4702
4712
  `);
4703
4713
  process.exit(1);
4704
4714
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/mcp",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
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": {