@topolo/mcp 0.9.10 → 0.9.11

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
@@ -153,6 +153,10 @@ deletion, encryption, and audit storage for the data they own.
153
153
  | `topolo_get_action` | (credential-scoped) | no |
154
154
  | `topolo_read_action` | catalog or target policy | no |
155
155
  | `topolo_call_action` | catalog or target policy | yes |
156
+ | `topolo_list_workspaces` | target app runtime policy | no |
157
+ | `topolo_create_workspace` | target app runtime policy | yes |
158
+ | `topolo_rename_workspace` | target app runtime policy | yes |
159
+ | `topolo_set_default_workspace` | target app runtime policy | yes |
156
160
  | `topolo_api_call` | (service-enforced) | yes |
157
161
 
158
162
  Applications publish machine-action definitions through Topolo Developers.
@@ -171,6 +175,12 @@ capability without inventing a global permission, and the target service checks
171
175
  the current database-backed policy for the supplied organization/application on
172
176
  every call.
173
177
 
178
+ Workspace lifecycle is first-class so agents do not have to know that Auth is
179
+ the canonical owner. The tools accept the target application, derive the
180
+ organization from the credential, and retain Auth's per-app runtime decision.
181
+ `topolo_list_workspaces` exposes `policy.canCreate` and `policy.reason`; the
182
+ write tools never infer access from organization ownership.
183
+
174
184
  `topolo_api_call` is the escape hatch for endpoints without typed MCP tools
175
185
  yet. GET requests work without confirmation; mutating HTTP methods must pass
176
186
  `confirm: true` or the SDK rejects the call. Backing services still enforce
package/dist/dispatch.js CHANGED
@@ -64,6 +64,11 @@ function publicMcpErrorMessage(error, fallback) {
64
64
  // src/tools.ts
65
65
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
66
66
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
67
+ function requiredString(args, name) {
68
+ const value = typeof args[name] === "string" ? args[name].trim() : "";
69
+ if (!value) throw new TopoloMcpPublicError(`${name} is required.`);
70
+ return value;
71
+ }
67
72
  function optionalResourceContext(args) {
68
73
  const resourceType = typeof args["resourceType"] === "string" ? args["resourceType"].trim() : "";
69
74
  const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
@@ -73,20 +78,6 @@ function optionalResourceContext(args) {
73
78
  }
74
79
  return { resourceType, resourceId };
75
80
  }
76
- function coerceObjectParam(value, name) {
77
- let candidate = value;
78
- if (typeof candidate === "string" && candidate.trim().startsWith("{")) {
79
- try {
80
- candidate = JSON.parse(candidate);
81
- } catch {
82
- }
83
- }
84
- if (candidate === void 0) return {};
85
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
86
- throw new TopoloMcpPublicError(`${name} must be an object.`);
87
- }
88
- return candidate;
89
- }
90
81
  var TOOLS = [
91
82
  {
92
83
  name: "topolo_whoami",
@@ -237,11 +228,14 @@ var TOOLS = [
237
228
  handler: async (topolo, args) => {
238
229
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
239
230
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
240
- const input = coerceObjectParam(args["input"], "input");
231
+ const input = args["input"];
232
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
233
+ throw new TopoloMcpPublicError("input must be an object.");
234
+ }
241
235
  const resource = optionalResourceContext(args);
242
236
  return topolo.client.readAction(
243
237
  actionId,
244
- input,
238
+ input ?? {},
245
239
  resource ? { resource } : {}
246
240
  );
247
241
  }
@@ -267,11 +261,14 @@ var TOOLS = [
267
261
  handler: async (topolo, args) => {
268
262
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
269
263
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
270
- const input = coerceObjectParam(args["input"], "input");
264
+ const input = args["input"];
265
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
266
+ throw new TopoloMcpPublicError("input must be an object.");
267
+ }
271
268
  const resource = optionalResourceContext(args);
272
269
  return topolo.client.callAction(
273
270
  actionId,
274
- input,
271
+ input ?? {},
275
272
  {
276
273
  confirm: args["confirm"] === true,
277
274
  ...resource ? { resource } : {}
@@ -310,17 +307,23 @@ var TOOLS = [
310
307
  if (!isCanonicalBase64(contentBase64)) throw new TopoloMcpPublicError("contentBase64 must be canonical base64.");
311
308
  if (!contentType) throw new TopoloMcpPublicError("contentType is required.");
312
309
  if (!fileName) throw new TopoloMcpPublicError("fileName is required.");
313
- const input = coerceObjectParam(args["input"], "input");
314
- const completionInput = coerceObjectParam(args["completionInput"], "completionInput");
310
+ const input = args["input"];
311
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
312
+ throw new TopoloMcpPublicError("input must be an object.");
313
+ }
314
+ const completionInput = args["completionInput"];
315
+ if (completionInput !== void 0 && (!completionInput || typeof completionInput !== "object" || Array.isArray(completionInput))) {
316
+ throw new TopoloMcpPublicError("completionInput must be an object.");
317
+ }
315
318
  const resource = optionalResourceContext(args);
316
319
  return topolo.client.uploadActionBase64(
317
320
  actionId,
318
321
  contentBase64,
319
- input,
322
+ input ?? {},
320
323
  {
321
324
  fileName,
322
325
  contentType,
323
- completionInput,
326
+ completionInput: completionInput ?? {},
324
327
  confirm: args["confirm"] === true,
325
328
  ...resource ? { resource } : {}
326
329
  }
@@ -345,8 +348,11 @@ var TOOLS = [
345
348
  handler: async (topolo, args) => {
346
349
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
347
350
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
348
- const input = coerceObjectParam(args["input"], "input");
349
- return topolo.client.validateActionInput(actionId, input);
351
+ const input = args["input"];
352
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
353
+ throw new TopoloMcpPublicError("input must be an object.");
354
+ }
355
+ return topolo.client.validateActionInput(actionId, input ?? {});
350
356
  }
351
357
  },
352
358
  {
@@ -370,11 +376,14 @@ var TOOLS = [
370
376
  handler: async (topolo, args) => {
371
377
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
372
378
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
373
- const input = coerceObjectParam(args["input"], "input");
379
+ const input = args["input"];
380
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
381
+ throw new TopoloMcpPublicError("input must be an object.");
382
+ }
374
383
  const resource = optionalResourceContext(args);
375
384
  return topolo.client.planAction(
376
385
  actionId,
377
- input,
386
+ input ?? {},
378
387
  {
379
388
  confirm: args["confirm"] === true,
380
389
  ...resource ? { resource } : {}
@@ -463,6 +472,78 @@ var TOOLS = [
463
472
  return topolo.client.listBindableResources(application);
464
473
  }
465
474
  },
475
+ {
476
+ name: "topolo_list_workspaces",
477
+ title: "List workspaces for one Topolo application",
478
+ description: "Lists caller-visible workspaces and the caller-specific creation policy for one target application. Use this before creating a workspace; organization ownership alone does not imply creation rights for every app.",
479
+ requiredScopes: [],
480
+ destructive: false,
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: { application: { type: "string", minLength: 1 } },
484
+ required: ["application"],
485
+ additionalProperties: false
486
+ },
487
+ handler: async (topolo, args) => {
488
+ const application = requiredString(args, "application");
489
+ return topolo.workspaces.list(application);
490
+ }
491
+ },
492
+ {
493
+ name: "topolo_create_workspace",
494
+ title: "Create a workspace for one Topolo application",
495
+ description: "Creates a workspace only after reading the target application policy and confirming it permits this caller. The canonical Auth runtime still authorizes the concrete app and organization. Set confirm:true only after approval.",
496
+ requiredScopes: [],
497
+ destructive: true,
498
+ inputSchema: {
499
+ type: "object",
500
+ properties: { application: { type: "string", minLength: 1 }, name: { type: "string", minLength: 1, maxLength: 120 }, isDefault: { type: "boolean" }, confirm: { type: "boolean" } },
501
+ required: ["application", "name"],
502
+ additionalProperties: false
503
+ },
504
+ handler: async (topolo, args) => topolo.workspaces.create(requiredString(args, "application"), {
505
+ name: requiredString(args, "name"),
506
+ ...typeof args["isDefault"] === "boolean" ? { isDefault: args["isDefault"] } : {},
507
+ confirm: args["confirm"] === true
508
+ })
509
+ },
510
+ {
511
+ name: "topolo_rename_workspace",
512
+ title: "Rename one Topolo application workspace",
513
+ description: "Renames a caller-authorized workspace without changing its stable id, slug, ownership, membership, or app data.",
514
+ requiredScopes: [],
515
+ destructive: true,
516
+ inputSchema: {
517
+ type: "object",
518
+ properties: { application: { type: "string", minLength: 1 }, workspaceId: { type: "string", minLength: 1 }, name: { type: "string", minLength: 1, maxLength: 120 }, confirm: { type: "boolean" } },
519
+ required: ["application", "workspaceId", "name"],
520
+ additionalProperties: false
521
+ },
522
+ handler: async (topolo, args) => topolo.workspaces.rename(
523
+ requiredString(args, "application"),
524
+ requiredString(args, "workspaceId"),
525
+ requiredString(args, "name"),
526
+ args["confirm"] === true
527
+ )
528
+ },
529
+ {
530
+ name: "topolo_set_default_workspace",
531
+ title: "Set an application default workspace",
532
+ description: "Sets exactly one caller-authorized workspace as the default for the target application and current organization.",
533
+ requiredScopes: [],
534
+ destructive: true,
535
+ inputSchema: {
536
+ type: "object",
537
+ properties: { application: { type: "string", minLength: 1 }, workspaceId: { type: "string", minLength: 1 }, confirm: { type: "boolean" } },
538
+ required: ["application", "workspaceId"],
539
+ additionalProperties: false
540
+ },
541
+ handler: async (topolo, args) => topolo.workspaces.setDefault(
542
+ requiredString(args, "application"),
543
+ requiredString(args, "workspaceId"),
544
+ args["confirm"] === true
545
+ )
546
+ },
466
547
  {
467
548
  name: "topolo_list_application_requirements",
468
549
  title: "List Topolo application build requirements",
package/dist/http.js CHANGED
@@ -15878,6 +15878,11 @@ function splitService(servicePart) {
15878
15878
  // src/tools.ts
15879
15879
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
15880
15880
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
15881
+ function requiredString(args, name) {
15882
+ const value = typeof args[name] === "string" ? args[name].trim() : "";
15883
+ if (!value) throw new TopoloMcpPublicError(`${name} is required.`);
15884
+ return value;
15885
+ }
15881
15886
  function optionalResourceContext(args) {
15882
15887
  const resourceType = typeof args["resourceType"] === "string" ? args["resourceType"].trim() : "";
15883
15888
  const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
@@ -15887,20 +15892,6 @@ function optionalResourceContext(args) {
15887
15892
  }
15888
15893
  return { resourceType, resourceId };
15889
15894
  }
15890
- function coerceObjectParam(value, name) {
15891
- let candidate = value;
15892
- if (typeof candidate === "string" && candidate.trim().startsWith("{")) {
15893
- try {
15894
- candidate = JSON.parse(candidate);
15895
- } catch {
15896
- }
15897
- }
15898
- if (candidate === void 0) return {};
15899
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
15900
- throw new TopoloMcpPublicError(`${name} must be an object.`);
15901
- }
15902
- return candidate;
15903
- }
15904
15895
  var TOOLS = [
15905
15896
  {
15906
15897
  name: "topolo_whoami",
@@ -16051,11 +16042,14 @@ var TOOLS = [
16051
16042
  handler: async (topolo, args) => {
16052
16043
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16053
16044
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16054
- const input = coerceObjectParam(args["input"], "input");
16045
+ const input = args["input"];
16046
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16047
+ throw new TopoloMcpPublicError("input must be an object.");
16048
+ }
16055
16049
  const resource = optionalResourceContext(args);
16056
16050
  return topolo.client.readAction(
16057
16051
  actionId,
16058
- input,
16052
+ input ?? {},
16059
16053
  resource ? { resource } : {}
16060
16054
  );
16061
16055
  }
@@ -16081,11 +16075,14 @@ var TOOLS = [
16081
16075
  handler: async (topolo, args) => {
16082
16076
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16083
16077
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16084
- const input = coerceObjectParam(args["input"], "input");
16078
+ const input = args["input"];
16079
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16080
+ throw new TopoloMcpPublicError("input must be an object.");
16081
+ }
16085
16082
  const resource = optionalResourceContext(args);
16086
16083
  return topolo.client.callAction(
16087
16084
  actionId,
16088
- input,
16085
+ input ?? {},
16089
16086
  {
16090
16087
  confirm: args["confirm"] === true,
16091
16088
  ...resource ? { resource } : {}
@@ -16124,17 +16121,23 @@ var TOOLS = [
16124
16121
  if (!isCanonicalBase64(contentBase64)) throw new TopoloMcpPublicError("contentBase64 must be canonical base64.");
16125
16122
  if (!contentType) throw new TopoloMcpPublicError("contentType is required.");
16126
16123
  if (!fileName) throw new TopoloMcpPublicError("fileName is required.");
16127
- const input = coerceObjectParam(args["input"], "input");
16128
- const completionInput = coerceObjectParam(args["completionInput"], "completionInput");
16124
+ const input = args["input"];
16125
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16126
+ throw new TopoloMcpPublicError("input must be an object.");
16127
+ }
16128
+ const completionInput = args["completionInput"];
16129
+ if (completionInput !== void 0 && (!completionInput || typeof completionInput !== "object" || Array.isArray(completionInput))) {
16130
+ throw new TopoloMcpPublicError("completionInput must be an object.");
16131
+ }
16129
16132
  const resource = optionalResourceContext(args);
16130
16133
  return topolo.client.uploadActionBase64(
16131
16134
  actionId,
16132
16135
  contentBase64,
16133
- input,
16136
+ input ?? {},
16134
16137
  {
16135
16138
  fileName,
16136
16139
  contentType,
16137
- completionInput,
16140
+ completionInput: completionInput ?? {},
16138
16141
  confirm: args["confirm"] === true,
16139
16142
  ...resource ? { resource } : {}
16140
16143
  }
@@ -16159,8 +16162,11 @@ var TOOLS = [
16159
16162
  handler: async (topolo, args) => {
16160
16163
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16161
16164
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16162
- const input = coerceObjectParam(args["input"], "input");
16163
- return topolo.client.validateActionInput(actionId, input);
16165
+ const input = args["input"];
16166
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16167
+ throw new TopoloMcpPublicError("input must be an object.");
16168
+ }
16169
+ return topolo.client.validateActionInput(actionId, input ?? {});
16164
16170
  }
16165
16171
  },
16166
16172
  {
@@ -16184,11 +16190,14 @@ var TOOLS = [
16184
16190
  handler: async (topolo, args) => {
16185
16191
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
16186
16192
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
16187
- const input = coerceObjectParam(args["input"], "input");
16193
+ const input = args["input"];
16194
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
16195
+ throw new TopoloMcpPublicError("input must be an object.");
16196
+ }
16188
16197
  const resource = optionalResourceContext(args);
16189
16198
  return topolo.client.planAction(
16190
16199
  actionId,
16191
- input,
16200
+ input ?? {},
16192
16201
  {
16193
16202
  confirm: args["confirm"] === true,
16194
16203
  ...resource ? { resource } : {}
@@ -16277,6 +16286,78 @@ var TOOLS = [
16277
16286
  return topolo.client.listBindableResources(application);
16278
16287
  }
16279
16288
  },
16289
+ {
16290
+ name: "topolo_list_workspaces",
16291
+ title: "List workspaces for one Topolo application",
16292
+ description: "Lists caller-visible workspaces and the caller-specific creation policy for one target application. Use this before creating a workspace; organization ownership alone does not imply creation rights for every app.",
16293
+ requiredScopes: [],
16294
+ destructive: false,
16295
+ inputSchema: {
16296
+ type: "object",
16297
+ properties: { application: { type: "string", minLength: 1 } },
16298
+ required: ["application"],
16299
+ additionalProperties: false
16300
+ },
16301
+ handler: async (topolo, args) => {
16302
+ const application = requiredString(args, "application");
16303
+ return topolo.workspaces.list(application);
16304
+ }
16305
+ },
16306
+ {
16307
+ name: "topolo_create_workspace",
16308
+ title: "Create a workspace for one Topolo application",
16309
+ description: "Creates a workspace only after reading the target application policy and confirming it permits this caller. The canonical Auth runtime still authorizes the concrete app and organization. Set confirm:true only after approval.",
16310
+ requiredScopes: [],
16311
+ destructive: true,
16312
+ inputSchema: {
16313
+ type: "object",
16314
+ properties: { application: { type: "string", minLength: 1 }, name: { type: "string", minLength: 1, maxLength: 120 }, isDefault: { type: "boolean" }, confirm: { type: "boolean" } },
16315
+ required: ["application", "name"],
16316
+ additionalProperties: false
16317
+ },
16318
+ handler: async (topolo, args) => topolo.workspaces.create(requiredString(args, "application"), {
16319
+ name: requiredString(args, "name"),
16320
+ ...typeof args["isDefault"] === "boolean" ? { isDefault: args["isDefault"] } : {},
16321
+ confirm: args["confirm"] === true
16322
+ })
16323
+ },
16324
+ {
16325
+ name: "topolo_rename_workspace",
16326
+ title: "Rename one Topolo application workspace",
16327
+ description: "Renames a caller-authorized workspace without changing its stable id, slug, ownership, membership, or app data.",
16328
+ requiredScopes: [],
16329
+ destructive: true,
16330
+ inputSchema: {
16331
+ type: "object",
16332
+ properties: { application: { type: "string", minLength: 1 }, workspaceId: { type: "string", minLength: 1 }, name: { type: "string", minLength: 1, maxLength: 120 }, confirm: { type: "boolean" } },
16333
+ required: ["application", "workspaceId", "name"],
16334
+ additionalProperties: false
16335
+ },
16336
+ handler: async (topolo, args) => topolo.workspaces.rename(
16337
+ requiredString(args, "application"),
16338
+ requiredString(args, "workspaceId"),
16339
+ requiredString(args, "name"),
16340
+ args["confirm"] === true
16341
+ )
16342
+ },
16343
+ {
16344
+ name: "topolo_set_default_workspace",
16345
+ title: "Set an application default workspace",
16346
+ description: "Sets exactly one caller-authorized workspace as the default for the target application and current organization.",
16347
+ requiredScopes: [],
16348
+ destructive: true,
16349
+ inputSchema: {
16350
+ type: "object",
16351
+ properties: { application: { type: "string", minLength: 1 }, workspaceId: { type: "string", minLength: 1 }, confirm: { type: "boolean" } },
16352
+ required: ["application", "workspaceId"],
16353
+ additionalProperties: false
16354
+ },
16355
+ handler: async (topolo, args) => topolo.workspaces.setDefault(
16356
+ requiredString(args, "application"),
16357
+ requiredString(args, "workspaceId"),
16358
+ args["confirm"] === true
16359
+ )
16360
+ },
16280
16361
  {
16281
16362
  name: "topolo_list_application_requirements",
16282
16363
  title: "List Topolo application build requirements",
package/dist/index.js CHANGED
@@ -3865,11 +3865,71 @@ var IdentityModule = class {
3865
3865
  return this.client.introspect();
3866
3866
  }
3867
3867
  };
3868
+ var ACTIONS = {
3869
+ list: "app_topolo_auth.workspaces.list",
3870
+ create: "app_topolo_auth.workspaces.create",
3871
+ rename: "app_topolo_auth.workspaces.rename",
3872
+ setDefault: "app_topolo_auth.workspaces.default.set"
3873
+ };
3874
+ var WorkspacesModule = class {
3875
+ constructor(client) {
3876
+ this.client = client;
3877
+ }
3878
+ client;
3879
+ async list(application) {
3880
+ const context = await this.resolveContext(application);
3881
+ return this.client.callAction(ACTIONS.list, {
3882
+ org_id: context.organizationId,
3883
+ app_id: context.appId
3884
+ });
3885
+ }
3886
+ async create(application, input) {
3887
+ const current = await this.list(application);
3888
+ if (!current.data.policy.canCreate) {
3889
+ throw new TopoloSdkError(
3890
+ "workspace_creation_not_allowed",
3891
+ `Workspace creation is not allowed for ${current.data.appId}: ${current.data.policy.reason}`
3892
+ );
3893
+ }
3894
+ return this.client.callAction(ACTIONS.create, {
3895
+ organizationId: current.data.organizationId,
3896
+ appId: current.data.appId,
3897
+ name: input.name,
3898
+ ...input.isDefault === void 0 ? {} : { isDefault: input.isDefault }
3899
+ }, { confirm: input.confirm === true });
3900
+ }
3901
+ async rename(application, workspaceId, name, confirm = false) {
3902
+ const context = await this.resolveContext(application);
3903
+ return this.client.callAction(ACTIONS.rename, {
3904
+ workspaceId,
3905
+ organizationId: context.organizationId,
3906
+ appId: context.appId,
3907
+ name
3908
+ }, { confirm });
3909
+ }
3910
+ async setDefault(application, workspaceId, confirm = false) {
3911
+ const context = await this.resolveContext(application);
3912
+ return this.client.callAction(ACTIONS.setDefault, {
3913
+ workspaceId,
3914
+ organizationId: context.organizationId,
3915
+ appId: context.appId
3916
+ }, { confirm });
3917
+ }
3918
+ async resolveContext(application) {
3919
+ const identity = await this.client.introspect();
3920
+ if (!identity.organization?.id) {
3921
+ throw new TopoloSdkError("organization_required", "Workspace lifecycle requires an organization-scoped credential.");
3922
+ }
3923
+ const service = await this.client.getService(application);
3924
+ return { organizationId: identity.organization.id, appId: service.appId };
3925
+ }
3926
+ };
3868
3927
  function createTopolo(options) {
3869
3928
  const client = new TopoloClient(options);
3870
3929
  return {
3871
3930
  client,
3872
3931
  identity: new IdentityModule(client),
3932
+ workspaces: new WorkspacesModule(client),
3873
3933
  crm: new CrmModule(client)
3874
3934
  };
3875
3935
  }
@@ -4354,6 +4414,11 @@ function publicMcpErrorMessage(error, fallback) {
4354
4414
  // src/tools.ts
4355
4415
  var APPLICATION_IDS = Object.keys(APPLICATIONS).sort();
4356
4416
  var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
4417
+ function requiredString(args, name) {
4418
+ const value = typeof args[name] === "string" ? args[name].trim() : "";
4419
+ if (!value) throw new TopoloMcpPublicError(`${name} is required.`);
4420
+ return value;
4421
+ }
4357
4422
  function optionalResourceContext(args) {
4358
4423
  const resourceType = typeof args["resourceType"] === "string" ? args["resourceType"].trim() : "";
4359
4424
  const resourceId = typeof args["resourceId"] === "string" ? args["resourceId"].trim() : "";
@@ -4363,20 +4428,6 @@ function optionalResourceContext(args) {
4363
4428
  }
4364
4429
  return { resourceType, resourceId };
4365
4430
  }
4366
- function coerceObjectParam(value, name) {
4367
- let candidate = value;
4368
- if (typeof candidate === "string" && candidate.trim().startsWith("{")) {
4369
- try {
4370
- candidate = JSON.parse(candidate);
4371
- } catch {
4372
- }
4373
- }
4374
- if (candidate === void 0) return {};
4375
- if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
4376
- throw new TopoloMcpPublicError(`${name} must be an object.`);
4377
- }
4378
- return candidate;
4379
- }
4380
4431
  var TOOLS = [
4381
4432
  {
4382
4433
  name: "topolo_whoami",
@@ -4527,11 +4578,14 @@ var TOOLS = [
4527
4578
  handler: async (topolo, args) => {
4528
4579
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4529
4580
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4530
- const input = coerceObjectParam(args["input"], "input");
4581
+ const input = args["input"];
4582
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4583
+ throw new TopoloMcpPublicError("input must be an object.");
4584
+ }
4531
4585
  const resource = optionalResourceContext(args);
4532
4586
  return topolo.client.readAction(
4533
4587
  actionId,
4534
- input,
4588
+ input ?? {},
4535
4589
  resource ? { resource } : {}
4536
4590
  );
4537
4591
  }
@@ -4557,11 +4611,14 @@ var TOOLS = [
4557
4611
  handler: async (topolo, args) => {
4558
4612
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4559
4613
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4560
- const input = coerceObjectParam(args["input"], "input");
4614
+ const input = args["input"];
4615
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4616
+ throw new TopoloMcpPublicError("input must be an object.");
4617
+ }
4561
4618
  const resource = optionalResourceContext(args);
4562
4619
  return topolo.client.callAction(
4563
4620
  actionId,
4564
- input,
4621
+ input ?? {},
4565
4622
  {
4566
4623
  confirm: args["confirm"] === true,
4567
4624
  ...resource ? { resource } : {}
@@ -4600,17 +4657,23 @@ var TOOLS = [
4600
4657
  if (!isCanonicalBase642(contentBase64)) throw new TopoloMcpPublicError("contentBase64 must be canonical base64.");
4601
4658
  if (!contentType) throw new TopoloMcpPublicError("contentType is required.");
4602
4659
  if (!fileName) throw new TopoloMcpPublicError("fileName is required.");
4603
- const input = coerceObjectParam(args["input"], "input");
4604
- const completionInput = coerceObjectParam(args["completionInput"], "completionInput");
4660
+ const input = args["input"];
4661
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4662
+ throw new TopoloMcpPublicError("input must be an object.");
4663
+ }
4664
+ const completionInput = args["completionInput"];
4665
+ if (completionInput !== void 0 && (!completionInput || typeof completionInput !== "object" || Array.isArray(completionInput))) {
4666
+ throw new TopoloMcpPublicError("completionInput must be an object.");
4667
+ }
4605
4668
  const resource = optionalResourceContext(args);
4606
4669
  return topolo.client.uploadActionBase64(
4607
4670
  actionId,
4608
4671
  contentBase64,
4609
- input,
4672
+ input ?? {},
4610
4673
  {
4611
4674
  fileName,
4612
4675
  contentType,
4613
- completionInput,
4676
+ completionInput: completionInput ?? {},
4614
4677
  confirm: args["confirm"] === true,
4615
4678
  ...resource ? { resource } : {}
4616
4679
  }
@@ -4635,8 +4698,11 @@ var TOOLS = [
4635
4698
  handler: async (topolo, args) => {
4636
4699
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4637
4700
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4638
- const input = coerceObjectParam(args["input"], "input");
4639
- return topolo.client.validateActionInput(actionId, input);
4701
+ const input = args["input"];
4702
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4703
+ throw new TopoloMcpPublicError("input must be an object.");
4704
+ }
4705
+ return topolo.client.validateActionInput(actionId, input ?? {});
4640
4706
  }
4641
4707
  },
4642
4708
  {
@@ -4660,11 +4726,14 @@ var TOOLS = [
4660
4726
  handler: async (topolo, args) => {
4661
4727
  const actionId = typeof args["actionId"] === "string" ? args["actionId"].trim() : "";
4662
4728
  if (!actionId) throw new TopoloMcpPublicError("actionId is required.");
4663
- const input = coerceObjectParam(args["input"], "input");
4729
+ const input = args["input"];
4730
+ if (input !== void 0 && (!input || typeof input !== "object" || Array.isArray(input))) {
4731
+ throw new TopoloMcpPublicError("input must be an object.");
4732
+ }
4664
4733
  const resource = optionalResourceContext(args);
4665
4734
  return topolo.client.planAction(
4666
4735
  actionId,
4667
- input,
4736
+ input ?? {},
4668
4737
  {
4669
4738
  confirm: args["confirm"] === true,
4670
4739
  ...resource ? { resource } : {}
@@ -4753,6 +4822,78 @@ var TOOLS = [
4753
4822
  return topolo.client.listBindableResources(application);
4754
4823
  }
4755
4824
  },
4825
+ {
4826
+ name: "topolo_list_workspaces",
4827
+ title: "List workspaces for one Topolo application",
4828
+ description: "Lists caller-visible workspaces and the caller-specific creation policy for one target application. Use this before creating a workspace; organization ownership alone does not imply creation rights for every app.",
4829
+ requiredScopes: [],
4830
+ destructive: false,
4831
+ inputSchema: {
4832
+ type: "object",
4833
+ properties: { application: { type: "string", minLength: 1 } },
4834
+ required: ["application"],
4835
+ additionalProperties: false
4836
+ },
4837
+ handler: async (topolo, args) => {
4838
+ const application = requiredString(args, "application");
4839
+ return topolo.workspaces.list(application);
4840
+ }
4841
+ },
4842
+ {
4843
+ name: "topolo_create_workspace",
4844
+ title: "Create a workspace for one Topolo application",
4845
+ description: "Creates a workspace only after reading the target application policy and confirming it permits this caller. The canonical Auth runtime still authorizes the concrete app and organization. Set confirm:true only after approval.",
4846
+ requiredScopes: [],
4847
+ destructive: true,
4848
+ inputSchema: {
4849
+ type: "object",
4850
+ properties: { application: { type: "string", minLength: 1 }, name: { type: "string", minLength: 1, maxLength: 120 }, isDefault: { type: "boolean" }, confirm: { type: "boolean" } },
4851
+ required: ["application", "name"],
4852
+ additionalProperties: false
4853
+ },
4854
+ handler: async (topolo, args) => topolo.workspaces.create(requiredString(args, "application"), {
4855
+ name: requiredString(args, "name"),
4856
+ ...typeof args["isDefault"] === "boolean" ? { isDefault: args["isDefault"] } : {},
4857
+ confirm: args["confirm"] === true
4858
+ })
4859
+ },
4860
+ {
4861
+ name: "topolo_rename_workspace",
4862
+ title: "Rename one Topolo application workspace",
4863
+ description: "Renames a caller-authorized workspace without changing its stable id, slug, ownership, membership, or app data.",
4864
+ requiredScopes: [],
4865
+ destructive: true,
4866
+ inputSchema: {
4867
+ type: "object",
4868
+ properties: { application: { type: "string", minLength: 1 }, workspaceId: { type: "string", minLength: 1 }, name: { type: "string", minLength: 1, maxLength: 120 }, confirm: { type: "boolean" } },
4869
+ required: ["application", "workspaceId", "name"],
4870
+ additionalProperties: false
4871
+ },
4872
+ handler: async (topolo, args) => topolo.workspaces.rename(
4873
+ requiredString(args, "application"),
4874
+ requiredString(args, "workspaceId"),
4875
+ requiredString(args, "name"),
4876
+ args["confirm"] === true
4877
+ )
4878
+ },
4879
+ {
4880
+ name: "topolo_set_default_workspace",
4881
+ title: "Set an application default workspace",
4882
+ description: "Sets exactly one caller-authorized workspace as the default for the target application and current organization.",
4883
+ requiredScopes: [],
4884
+ destructive: true,
4885
+ inputSchema: {
4886
+ type: "object",
4887
+ properties: { application: { type: "string", minLength: 1 }, workspaceId: { type: "string", minLength: 1 }, confirm: { type: "boolean" } },
4888
+ required: ["application", "workspaceId"],
4889
+ additionalProperties: false
4890
+ },
4891
+ handler: async (topolo, args) => topolo.workspaces.setDefault(
4892
+ requiredString(args, "application"),
4893
+ requiredString(args, "workspaceId"),
4894
+ args["confirm"] === true
4895
+ )
4896
+ },
4756
4897
  {
4757
4898
  name: "topolo_list_application_requirements",
4758
4899
  title: "List Topolo application build requirements",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@topolo/mcp",
3
- "version": "0.9.10",
4
- "topoloPublishedVersion": "0.9.10",
3
+ "version": "0.9.11",
4
+ "topoloPublishedVersion": "0.9.11",
5
5
  "description": "Model Context Protocol server for the Topolo platform. Exposes scope-gated tools that third-party agents (Claude, Codex, etc.) can call natively.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "@modelcontextprotocol/sdk": "^1.0.0"
38
38
  },
39
39
  "peerDependencies": {
40
- "@topolo/sdk": "^0.9.9"
40
+ "@topolo/sdk": "^0.9.10"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@topolo/sdk": "file:../topolo-sdk",