@topolo/mcp 0.9.4 → 0.9.6

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
@@ -301,7 +301,9 @@ var APPLICATIONS = {
301
301
  "domains": [
302
302
  "bugfix.topolo.app"
303
303
  ],
304
- "routes": [],
304
+ "routes": [
305
+ "bugfix.topolo.app"
306
+ ],
305
307
  "appId": "bugfix"
306
308
  }
307
309
  ]
@@ -417,7 +419,9 @@ var APPLICATIONS = {
417
419
  "domains": [
418
420
  "capacity.topolo.app"
419
421
  ],
420
- "routes": [],
422
+ "routes": [
423
+ "capacity.topolo.app"
424
+ ],
421
425
  "appId": "capacity"
422
426
  }
423
427
  ]
@@ -1159,7 +1163,9 @@ var APPLICATIONS = {
1159
1163
  "domains": [
1160
1164
  "observability-watch.topolo.app"
1161
1165
  ],
1162
- "routes": [],
1166
+ "routes": [
1167
+ "observability-watch.topolo.app"
1168
+ ],
1163
1169
  "appId": "observability_watch"
1164
1170
  }
1165
1171
  ]
@@ -3899,6 +3905,17 @@ function splitService(servicePart) {
3899
3905
  return [servicePart.slice(0, idx), servicePart.slice(idx + 1)];
3900
3906
  }
3901
3907
 
3908
+ // src/errors.ts
3909
+ var TopoloMcpPublicError = class extends Error {
3910
+ constructor(message) {
3911
+ super(message);
3912
+ this.name = "TopoloMcpPublicError";
3913
+ }
3914
+ };
3915
+ function publicMcpErrorMessage(error, fallback) {
3916
+ return error instanceof TopoloMcpPublicError ? error.message : fallback;
3917
+ }
3918
+
3902
3919
  // src/tools.ts
3903
3920
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
3904
3921
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
@@ -3907,7 +3924,7 @@ function optionalResourceContext(args) {
3907
3924
  const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
3908
3925
  if (!resourceType && !resourceId) return void 0;
3909
3926
  if (!resourceType || !resourceId) {
3910
- throw new Error("resourceType and resourceId must be provided together.");
3927
+ throw new TopoloMcpPublicError("resourceType and resourceId must be provided together.");
3911
3928
  }
3912
3929
  return { resourceType, resourceId };
3913
3930
  }
@@ -3989,7 +4006,7 @@ var TOOLS = [
3989
4006
  },
3990
4007
  handler: async (topolo, args) => {
3991
4008
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
3992
- if (!service) throw new Error("service is required for bounded action discovery.");
4009
+ if (!service) throw new TopoloMcpPublicError("service is required for bounded action discovery.");
3993
4010
  return topolo.client.searchActions({
3994
4011
  service,
3995
4012
  query: typeof args["query"] === "string" ? args["query"] : void 0,
@@ -4018,7 +4035,7 @@ var TOOLS = [
4018
4035
  handler: async (topolo, args) => {
4019
4036
  const application = args["application"];
4020
4037
  if (typeof application !== "string" || !application.trim()) {
4021
- throw new Error("`application` must be a non-empty string.");
4038
+ throw new TopoloMcpPublicError("`application` must be a non-empty string.");
4022
4039
  }
4023
4040
  return { application: await topolo.client.getService(application) };
4024
4041
  }
@@ -4037,7 +4054,7 @@ var TOOLS = [
4037
4054
  },
4038
4055
  handler: async (topolo, args) => {
4039
4056
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4040
- if (!actionId) throw new Error("actionId is required.");
4057
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4041
4058
  return { action: await topolo.client.getAction(actionId) };
4042
4059
  }
4043
4060
  },
@@ -4060,10 +4077,10 @@ var TOOLS = [
4060
4077
  },
4061
4078
  handler: async (topolo, args) => {
4062
4079
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4063
- if (!actionId) throw new Error("actionId is required.");
4080
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4064
4081
  const input = args["input"];
4065
4082
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4066
- throw new Error("input must be an object.");
4083
+ throw new TopoloMcpPublicError("input must be an object.");
4067
4084
  }
4068
4085
  const resource = optionalResourceContext(args);
4069
4086
  return topolo.client.readAction(
@@ -4093,10 +4110,10 @@ var TOOLS = [
4093
4110
  },
4094
4111
  handler: async (topolo, args) => {
4095
4112
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4096
- if (!actionId) throw new Error("actionId is required.");
4113
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4097
4114
  const input = args["input"];
4098
4115
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4099
- throw new Error("input must be an object.");
4116
+ throw new TopoloMcpPublicError("input must be an object.");
4100
4117
  }
4101
4118
  const resource = optionalResourceContext(args);
4102
4119
  return topolo.client.callAction(
@@ -4126,10 +4143,10 @@ var TOOLS = [
4126
4143
  },
4127
4144
  handler: async (topolo, args) => {
4128
4145
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4129
- if (!actionId) throw new Error("actionId is required.");
4146
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4130
4147
  const input = args["input"];
4131
4148
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4132
- throw new Error("input must be an object.");
4149
+ throw new TopoloMcpPublicError("input must be an object.");
4133
4150
  }
4134
4151
  return topolo.client.validateActionInput(actionId, input ?? {});
4135
4152
  }
@@ -4154,10 +4171,10 @@ var TOOLS = [
4154
4171
  },
4155
4172
  handler: async (topolo, args) => {
4156
4173
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4157
- if (!actionId) throw new Error("actionId is required.");
4174
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4158
4175
  const input = args["input"];
4159
4176
  if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4160
- throw new Error("input must be an object.");
4177
+ throw new TopoloMcpPublicError("input must be an object.");
4161
4178
  }
4162
4179
  const resource = optionalResourceContext(args);
4163
4180
  return topolo.client.planAction(
@@ -4184,7 +4201,7 @@ var TOOLS = [
4184
4201
  },
4185
4202
  handler: async (topolo, args) => {
4186
4203
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4187
- if (!actionId) throw new Error("actionId is required.");
4204
+ if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4188
4205
  const action = await topolo.client.getAction(actionId);
4189
4206
  return {
4190
4207
  actionId: action.actionId,
@@ -4207,7 +4224,7 @@ var TOOLS = [
4207
4224
  },
4208
4225
  handler: async (topolo, args) => {
4209
4226
  const service = typeof args["service"] === "string" ? args["service"].trim() : "";
4210
- if (!service) throw new Error("service is required.");
4227
+ if (!service) throw new TopoloMcpPublicError("service is required.");
4211
4228
  return topolo.client.searchActions({ service, query: "capabilities", limit: 100 });
4212
4229
  }
4213
4230
  },
@@ -4225,7 +4242,7 @@ var TOOLS = [
4225
4242
  },
4226
4243
  handler: async (topolo, args) => {
4227
4244
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
4228
- if (!application) throw new Error("application is required.");
4245
+ if (!application) throw new TopoloMcpPublicError("application is required.");
4229
4246
  const entry = await topolo.client.getService(application);
4230
4247
  return {
4231
4248
  application: { appId: entry.appId, slug: entry.slug, name: entry.name },
@@ -4247,7 +4264,7 @@ var TOOLS = [
4247
4264
  },
4248
4265
  handler: async (topolo, args) => {
4249
4266
  const application = typeof args["application"] === "string" ? args["application"].trim() : "";
4250
- if (!application) throw new Error("application is required.");
4267
+ if (!application) throw new TopoloMcpPublicError("application is required.");
4251
4268
  return topolo.client.listBindableResources(application);
4252
4269
  }
4253
4270
  },
@@ -4278,7 +4295,7 @@ var TOOLS = [
4278
4295
  };
4279
4296
  }
4280
4297
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
4281
- throw new Error(
4298
+ throw new TopoloMcpPublicError(
4282
4299
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
4283
4300
  );
4284
4301
  }
@@ -4316,7 +4333,7 @@ var TOOLS = [
4316
4333
  const application = args["application"];
4317
4334
  const failOn = args["failOn"];
4318
4335
  if (failOn !== void 0 && typeof failOn !== "string") {
4319
- throw new Error("failOn must be one of: missing, needs_review, partial");
4336
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
4320
4337
  }
4321
4338
  const gate = failOn ? normalizeFailOn(failOn) : null;
4322
4339
  let report;
@@ -4325,7 +4342,7 @@ var TOOLS = [
4325
4342
  return gate ? { ...report, conformanceGate: evaluateApplicationAuditGate(report, gate) } : report;
4326
4343
  }
4327
4344
  if (typeof application !== "string" || !APPLICATION_IDS.includes(application)) {
4328
- throw new Error(
4345
+ throw new TopoloMcpPublicError(
4329
4346
  `Unknown application "${String(application)}". ${formatKnownValues(APPLICATION_IDS, "applications")}`
4330
4347
  );
4331
4348
  }
@@ -4385,14 +4402,14 @@ var TOOLS = [
4385
4402
  const service = args["service"];
4386
4403
  const path = args["path"];
4387
4404
  if (typeof service !== "string" || !service.trim()) {
4388
- throw new Error("service is required. Call topolo_search_applications first to discover callable services.");
4405
+ throw new TopoloMcpPublicError("service is required. Call topolo_search_applications first to discover callable services.");
4389
4406
  }
4390
4407
  if (typeof path !== "string" || !path) {
4391
- throw new Error("path is required");
4408
+ throw new TopoloMcpPublicError("path is required");
4392
4409
  }
4393
4410
  const methodRaw = args["method"] ?? "GET";
4394
4411
  if (typeof methodRaw !== "string" || !HTTP_METHODS.includes(methodRaw)) {
4395
- throw new Error(`Unknown HTTP method "${String(methodRaw)}"`);
4412
+ throw new TopoloMcpPublicError(`Unknown HTTP method "${String(methodRaw)}"`);
4396
4413
  }
4397
4414
  const method = methodRaw;
4398
4415
  const query = args["query"];
@@ -4448,7 +4465,7 @@ function normalizeFailOn(value) {
4448
4465
  if (normalized === "missing" || normalized === "needs_review" || normalized === "partial") {
4449
4466
  return normalized;
4450
4467
  }
4451
- throw new Error("failOn must be one of: missing, needs_review, partial");
4468
+ throw new TopoloMcpPublicError("failOn must be one of: missing, needs_review, partial");
4452
4469
  }
4453
4470
  function statusesAtOrWorse(threshold) {
4454
4471
  if (threshold === "missing") return ["missing"];
@@ -4552,9 +4569,9 @@ function buildServer(topolo, scopes, tools = TOOLS) {
4552
4569
  return toolError(`Permission denied. Required: ${err.required.join(", ") || "(unknown)"}`);
4553
4570
  }
4554
4571
  if (err instanceof TopoloAuthError) {
4555
- return toolError(`Auth error: ${err.message}`);
4572
+ return toolError("Authentication failed.");
4556
4573
  }
4557
- return toolError(err instanceof Error ? err.message : String(err));
4574
+ return toolError(publicMcpErrorMessage(err, "Tool execution failed."));
4558
4575
  }
4559
4576
  });
4560
4577
  return server;
@@ -4588,7 +4605,7 @@ var DEFAULT_DEPENDENCIES = {
4588
4605
  function resolvePinnedContextKey(profile, selector) {
4589
4606
  const normalized = selector.trim().toLowerCase();
4590
4607
  if (!normalized) {
4591
- throw new Error("TOPOLO_CONTEXT must not be empty");
4608
+ throw new TopoloMcpPublicError("TOPOLO_CONTEXT must not be empty");
4592
4609
  }
4593
4610
  const contexts = Object.entries(profile.contexts ?? {});
4594
4611
  const exact = contexts.find(([key]) => key.toLowerCase() === normalized);
@@ -4600,16 +4617,16 @@ function resolvePinnedContextKey(profile, selector) {
4600
4617
  ].some((value) => String(value || "").trim().toLowerCase() === normalized));
4601
4618
  if (matches.length === 1) return matches[0][0];
4602
4619
  if (matches.length > 1) {
4603
- throw new Error(`TOPOLO_CONTEXT "${selector}" is ambiguous; use an exact context key`);
4620
+ throw new TopoloMcpPublicError(`TOPOLO_CONTEXT "${selector}" is ambiguous; use an exact context key`);
4604
4621
  }
4605
- throw new Error(`TOPOLO_CONTEXT "${selector}" is not signed in for this environment`);
4622
+ throw new TopoloMcpPublicError(`TOPOLO_CONTEXT "${selector}" is not signed in for this environment`);
4606
4623
  }
4607
4624
  async function resolveMcpCredential(environment, selector = process.env.TOPOLO_CONTEXT, dependencies = DEFAULT_DEPENDENCIES) {
4608
4625
  const environmentCredential = dependencies.resolveCredentialFromEnv();
4609
4626
  const requestedSelector = selector?.trim();
4610
4627
  if (environmentCredential) {
4611
4628
  if (requestedSelector) {
4612
- throw new Error("TOPOLO_CONTEXT cannot be combined with an environment credential");
4629
+ throw new TopoloMcpPublicError("TOPOLO_CONTEXT cannot be combined with an environment credential");
4613
4630
  }
4614
4631
  return { credential: environmentCredential, contextKey: null };
4615
4632
  }
@@ -4643,21 +4660,49 @@ async function resolveMcpCredential(environment, selector = process.env.TOPOLO_C
4643
4660
  }
4644
4661
 
4645
4662
  // src/index.ts
4646
- async function main() {
4647
- const environment = normalizeEnvironment(process.env.TOPOLO_ENV);
4648
- const resolvedCredential = await resolveMcpCredential(environment);
4663
+ import { fileURLToPath as fileURLToPath2 } from "url";
4664
+ import { resolve } from "path";
4665
+ function readStdioEnvironment() {
4666
+ return process.env.TOPOLO_ENV;
4667
+ }
4668
+ function createStdioTransport() {
4669
+ return new StdioServerTransport();
4670
+ }
4671
+ function writeStdioError(message) {
4672
+ process.stderr.write(message);
4673
+ }
4674
+ function exitStdioProcess(code) {
4675
+ return process.exit(code);
4676
+ }
4677
+ var DEFAULT_DEPENDENCIES2 = {
4678
+ normalizeEnvironment,
4679
+ environmentValue: readStdioEnvironment,
4680
+ resolveCredential: resolveMcpCredential,
4681
+ readConfig,
4682
+ profileForEnvironment,
4683
+ resolveAgentName,
4684
+ resolveServiceUrls,
4685
+ createTopolo,
4686
+ buildServer,
4687
+ createTransport: createStdioTransport,
4688
+ advertisedToolNames,
4689
+ writeStderr: writeStdioError,
4690
+ exit: exitStdioProcess
4691
+ };
4692
+ async function runStdioMcp(dependencies = DEFAULT_DEPENDENCIES2) {
4693
+ const environment = dependencies.normalizeEnvironment(dependencies.environmentValue());
4694
+ const resolvedCredential = await dependencies.resolveCredential(environment);
4649
4695
  const credential = resolvedCredential.credential;
4650
4696
  if (!credential) {
4651
- process.stderr.write(
4652
- "TopoloMCP: no Topolo credential. Run `topolo auth login`, or set TOPOLO_API_KEY / TOPOLO_ACCESS_TOKEN.\n"
4697
+ throw new TopoloMcpPublicError(
4698
+ "no Topolo credential. Run `topolo auth login`, or set TOPOLO_API_KEY / TOPOLO_ACCESS_TOKEN."
4653
4699
  );
4654
- process.exit(1);
4655
4700
  }
4656
- const config = await readConfig();
4657
- const profile = profileForEnvironment(config, environment);
4658
- const agentName = resolveAgentName(profile.agentName);
4659
- const serviceUrls = resolveServiceUrls(profile.serviceUrls, environment);
4660
- const topolo = createTopolo({
4701
+ const config = await dependencies.readConfig();
4702
+ const profile = dependencies.profileForEnvironment(config, environment);
4703
+ const agentName = dependencies.resolveAgentName(profile.agentName);
4704
+ const serviceUrls = dependencies.resolveServiceUrls(profile.serviceUrls, environment);
4705
+ const topolo = dependencies.createTopolo({
4661
4706
  credential,
4662
4707
  agent: {
4663
4708
  clientName: "topolo-mcp",
@@ -4670,11 +4715,11 @@ async function main() {
4670
4715
  ...resolvedCredential.refreshCredential ? { refreshCredential: resolvedCredential.refreshCredential } : {}
4671
4716
  });
4672
4717
  const scopes = await loadScopes(topolo);
4673
- const server = buildServer(topolo, scopes);
4674
- const transport = new StdioServerTransport();
4718
+ const server = dependencies.buildServer(topolo, scopes);
4719
+ const transport = dependencies.createTransport();
4675
4720
  await server.connect(transport);
4676
- const toolNames = await advertisedToolNames(topolo, scopes);
4677
- process.stderr.write(
4721
+ const toolNames = await dependencies.advertisedToolNames(topolo, scopes);
4722
+ dependencies.writeStderr(
4678
4723
  `TopoloMCP v${MCP_VERSION} connected (${environment}${resolvedCredential.contextKey ? `, ${resolvedCredential.contextKey}` : ""}). Tools available: ${toolNames.join(", ") || "(none)"}
4679
4724
  `
4680
4725
  );
@@ -4690,15 +4735,31 @@ async function loadScopes(topolo) {
4690
4735
  };
4691
4736
  } catch (err) {
4692
4737
  if (err instanceof TopoloAuthError) {
4693
- process.stderr.write(`TopoloMCP: credential rejected \u2014 ${err.message}
4694
- `);
4695
- process.exit(1);
4738
+ throw new TopoloMcpPublicError("credential rejected.");
4696
4739
  }
4697
4740
  throw err;
4698
4741
  }
4699
4742
  }
4700
- main().catch((err) => {
4701
- process.stderr.write(`TopoloMCP fatal: ${err instanceof Error ? err.message : String(err)}
4743
+ function terminateStdioMcp(error, dependencies = DEFAULT_DEPENDENCIES2) {
4744
+ const isPublic = error instanceof TopoloMcpPublicError;
4745
+ const message = publicMcpErrorMessage(error, "Unexpected startup failure.");
4746
+ dependencies.writeStderr(`TopoloMCP${isPublic ? "" : " fatal"}: ${message}
4702
4747
  `);
4703
- process.exit(1);
4704
- });
4748
+ return dependencies.exit(1);
4749
+ }
4750
+ function isDirectExecution(moduleUrl, argvEntry) {
4751
+ return Boolean(argvEntry && fileURLToPath2(moduleUrl) === resolve(argvEntry));
4752
+ }
4753
+ if (isDirectExecution(import.meta.url, process.argv[1])) {
4754
+ void runStdioMcp().catch((error) => terminateStdioMcp(error));
4755
+ }
4756
+ export {
4757
+ createStdioTransport,
4758
+ exitStdioProcess,
4759
+ isDirectExecution,
4760
+ loadScopes,
4761
+ readStdioEnvironment,
4762
+ runStdioMcp,
4763
+ terminateStdioMcp,
4764
+ writeStdioError
4765
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/mcp",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
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": {
@@ -36,7 +36,7 @@
36
36
  "@modelcontextprotocol/sdk": "^1.0.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "@topolo/sdk": "^0.9.5"
39
+ "@topolo/sdk": "^0.9.6"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@topolo/sdk": "file:../topolo-sdk",