saasfunnels 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,8 @@ Hosted interactive MCP uses Clerk OAuth at the application `/mcp` URL and does n
73
73
 
74
74
  The application imports the exact versioned MCP registry through the server-side `saasfunnels/library` export. This keeps hosted Streamable HTTP and local stdio MCP on one package owner; it is not a browser API.
75
75
 
76
+ In hosted OAuth, `list_workspaces` discovers accessible workspace names and IDs. Live read, inspect, and plan tools accept an optional `workspace_id`. The hosted application must select the sole accessible workspace automatically, require an explicit ID when several are accessible, recheck authorization for every call, and include the selected workspace name and ID in each live response. The application passes the selection through to its developer API as the `workspace_id` query parameter. Local stdio API-key tools do not expose workspace selection; their key stays pinned to its workspace.
77
+
76
78
  ## Development
77
79
 
78
80
  ```bash
package/dist/library.js CHANGED
@@ -24260,7 +24260,7 @@ function validateFeatureCatalogManifest(value) {
24260
24260
 
24261
24261
  // src/identity.ts
24262
24262
  var SAASFUNNELS_CLI_NAME = "saasfunnels";
24263
- var SAASFUNNELS_CLI_VERSION = "0.2.1";
24263
+ var SAASFUNNELS_CLI_VERSION = "0.2.2";
24264
24264
  var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
24265
24265
  var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
24266
24266
  var SAASFUNNELS_ENV = Object.freeze({
@@ -24299,6 +24299,7 @@ var saasFunnelsMcpToolCapabilityIds = {
24299
24299
  get_integration_health: "developer.workspace.read",
24300
24300
  get_signal_payload_preview: "developer.workspace.read",
24301
24301
  get_workspace_readiness: "developer.workspace.read",
24302
+ list_workspaces: "developer.workspace.read",
24302
24303
  inspect_funnel_entry: "funnel.entry.inspect",
24303
24304
  list_mapping_gaps: "developer.workspace.read",
24304
24305
  list_recent_signals: "developer.workspace.read",
@@ -24399,7 +24400,7 @@ function objectArg(args, name) {
24399
24400
  function sanitizeString(value) {
24400
24401
  const withoutSecrets = value.replace(
24401
24402
  tokenPattern,
24402
- (candidate) => /^[A-Z0-9_]+$/.test(candidate) ? candidate : "[redacted-token]"
24403
+ (candidate) => /^[A-Z0-9_]+$/.test(candidate) || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(candidate) ? candidate : "[redacted-token]"
24403
24404
  ).replace(emailPattern3, "[redacted-email]");
24404
24405
  try {
24405
24406
  const parsed = new URL(
@@ -24668,6 +24669,25 @@ function emptyLiveInputSchema() {
24668
24669
  type: "object"
24669
24670
  };
24670
24671
  }
24672
+ var hostedWorkspaceToolNames = /* @__PURE__ */ new Set([
24673
+ "get_integration_health",
24674
+ "get_workspace_readiness",
24675
+ "list_mapping_gaps",
24676
+ "list_recent_signals",
24677
+ "get_signal_payload_preview",
24678
+ "inspect_funnel_entry",
24679
+ "propose_funnel_entry_installation",
24680
+ "get_account_strategy",
24681
+ "simulate_account_funnel_fit"
24682
+ ]);
24683
+ function developerPathForWorkspace(path, args, options) {
24684
+ if (!options.hostedMode) return path;
24685
+ if ("workspace_id" in args && !stringArg(args, "workspace_id")) {
24686
+ throw new McpProtocolError(-32602, "workspace_id must be a non-empty string.");
24687
+ }
24688
+ const workspaceId = stringArg(args, "workspace_id");
24689
+ return workspaceId ? `${path}${path.includes("?") ? "&" : "?"}workspace_id=${encodeURIComponent(workspaceId)}` : path;
24690
+ }
24671
24691
  function saasFunnelsMcpToolDefinitions({
24672
24692
  allowFunnelTests = false,
24673
24693
  allowFunnelWrites = false,
@@ -25030,6 +25050,29 @@ function hostedSaaSFunnelsMcpToolDefinitions() {
25030
25050
  capability2 && agentCapabilityAllowedAtBoundary(capability2.id, "hosted_mcp")
25031
25051
  );
25032
25052
  });
25053
+ for (const tool of tools) {
25054
+ if (!hostedWorkspaceToolNames.has(tool.name)) continue;
25055
+ const properties = tool.inputSchema.properties;
25056
+ properties.workspace_id = {
25057
+ description: "Workspace ID from list_workspaces. Required when your OAuth identity can access multiple workspaces; optional when it can access one. The server verifies access for every call.",
25058
+ type: "string"
25059
+ };
25060
+ }
25061
+ tools.push({
25062
+ annotations: {
25063
+ idempotentHint: true,
25064
+ openWorldHint: true,
25065
+ readOnlyHint: true
25066
+ },
25067
+ description: "List the names and IDs of workspaces accessible to the OAuth user. Use a workspace ID from this list to target live tools when more than one workspace is accessible.",
25068
+ inputSchema: {
25069
+ additionalProperties: false,
25070
+ properties: {},
25071
+ type: "object"
25072
+ },
25073
+ name: "list_workspaces",
25074
+ title: "List Workspaces"
25075
+ });
25033
25076
  assertMcpToolCapabilityContract(tools, "hosted_mcp");
25034
25077
  return tools;
25035
25078
  }
@@ -25055,7 +25098,7 @@ async function developerGet(path, args, options) {
25055
25098
  });
25056
25099
  }
25057
25100
  const response = await (options.fetch ?? fetch)(
25058
- `${apiBaseUrl(options, args)}${path}`,
25101
+ `${apiBaseUrl(options, args)}${developerPathForWorkspace(path, args, options)}`,
25059
25102
  {
25060
25103
  headers: {
25061
25104
  authorization
@@ -25091,7 +25134,7 @@ async function developerPost(path, args, body, options, requiredScope) {
25091
25134
  });
25092
25135
  }
25093
25136
  const response = await (options.fetch ?? fetch)(
25094
- `${apiBaseUrl(options, args)}${path}`,
25137
+ `${apiBaseUrl(options, args)}${developerPathForWorkspace(path, args, options)}`,
25095
25138
  {
25096
25139
  body: JSON.stringify(body),
25097
25140
  headers: {
@@ -25161,11 +25204,17 @@ async function sendTestEvent(args, options) {
25161
25204
  }
25162
25205
  async function callSaaSFunnelsMcpTool(name, args, options = {}) {
25163
25206
  try {
25164
- if ((name === "get_account_strategy" || name === "simulate_account_funnel_fit") && !options.hostedMode) {
25207
+ if (!options.hostedMode && "workspace_id" in args && hostedWorkspaceToolNames.has(name)) {
25208
+ throw new McpProtocolError(
25209
+ -32602,
25210
+ "workspace_id is available only with hosted OAuth; an API key is pinned to its workspace."
25211
+ );
25212
+ }
25213
+ if ((name === "get_account_strategy" || name === "simulate_account_funnel_fit" || name === "list_workspaces") && !options.hostedMode) {
25165
25214
  throw new McpToolExecutionError(
25166
- "This account tool requires hosted OAuth.",
25215
+ "This tool requires hosted OAuth.",
25167
25216
  {
25168
- error: "This account tool requires hosted OAuth.",
25217
+ error: "This tool requires hosted OAuth.",
25169
25218
  ok: false,
25170
25219
  required_boundary: "hosted_mcp"
25171
25220
  }
@@ -25208,6 +25257,10 @@ async function callSaaSFunnelsMcpTool(name, args, options = {}) {
25208
25257
  options
25209
25258
  )
25210
25259
  );
25260
+ if (name === "list_workspaces")
25261
+ return structuredToolResult(
25262
+ await developerGet("/api/developer-tools/workspaces", {}, options)
25263
+ );
25211
25264
  if (name === "get_workspace_readiness")
25212
25265
  return structuredToolResult(
25213
25266
  await developerGet(
@@ -24802,7 +24802,7 @@ Applied reviewed edits. Change record: ${result3.changeRecordPath}` : result3.id
24802
24802
 
24803
24803
  // src/identity.ts
24804
24804
  var SAASFUNNELS_CLI_NAME = "saasfunnels";
24805
- var SAASFUNNELS_CLI_VERSION = "0.2.1";
24805
+ var SAASFUNNELS_CLI_VERSION = "0.2.2";
24806
24806
  var SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
24807
24807
  var SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
24808
24808
  var SAASFUNNELS_ENV = Object.freeze({
@@ -25382,6 +25382,7 @@ var saasFunnelsMcpToolCapabilityIds = {
25382
25382
  get_integration_health: "developer.workspace.read",
25383
25383
  get_signal_payload_preview: "developer.workspace.read",
25384
25384
  get_workspace_readiness: "developer.workspace.read",
25385
+ list_workspaces: "developer.workspace.read",
25385
25386
  inspect_funnel_entry: "funnel.entry.inspect",
25386
25387
  list_mapping_gaps: "developer.workspace.read",
25387
25388
  list_recent_signals: "developer.workspace.read",
@@ -25482,7 +25483,7 @@ function objectArg(args, name) {
25482
25483
  function sanitizeString(value) {
25483
25484
  const withoutSecrets = value.replace(
25484
25485
  tokenPattern,
25485
- (candidate) => /^[A-Z0-9_]+$/.test(candidate) ? candidate : "[redacted-token]"
25486
+ (candidate) => /^[A-Z0-9_]+$/.test(candidate) || /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(candidate) ? candidate : "[redacted-token]"
25486
25487
  ).replace(emailPattern3, "[redacted-email]");
25487
25488
  try {
25488
25489
  const parsed = new URL(
@@ -25751,6 +25752,25 @@ function emptyLiveInputSchema() {
25751
25752
  type: "object"
25752
25753
  };
25753
25754
  }
25755
+ var hostedWorkspaceToolNames = /* @__PURE__ */ new Set([
25756
+ "get_integration_health",
25757
+ "get_workspace_readiness",
25758
+ "list_mapping_gaps",
25759
+ "list_recent_signals",
25760
+ "get_signal_payload_preview",
25761
+ "inspect_funnel_entry",
25762
+ "propose_funnel_entry_installation",
25763
+ "get_account_strategy",
25764
+ "simulate_account_funnel_fit"
25765
+ ]);
25766
+ function developerPathForWorkspace(path, args, options) {
25767
+ if (!options.hostedMode) return path;
25768
+ if ("workspace_id" in args && !stringArg(args, "workspace_id")) {
25769
+ throw new McpProtocolError(-32602, "workspace_id must be a non-empty string.");
25770
+ }
25771
+ const workspaceId = stringArg(args, "workspace_id");
25772
+ return workspaceId ? `${path}${path.includes("?") ? "&" : "?"}workspace_id=${encodeURIComponent(workspaceId)}` : path;
25773
+ }
25754
25774
  function saasFunnelsMcpToolDefinitions({
25755
25775
  allowFunnelTests = false,
25756
25776
  allowFunnelWrites = false,
@@ -26128,7 +26148,7 @@ async function developerGet(path, args, options) {
26128
26148
  });
26129
26149
  }
26130
26150
  const response = await (options.fetch ?? fetch)(
26131
- `${apiBaseUrl(options, args)}${path}`,
26151
+ `${apiBaseUrl(options, args)}${developerPathForWorkspace(path, args, options)}`,
26132
26152
  {
26133
26153
  headers: {
26134
26154
  authorization
@@ -26164,7 +26184,7 @@ async function developerPost(path, args, body, options, requiredScope) {
26164
26184
  });
26165
26185
  }
26166
26186
  const response = await (options.fetch ?? fetch)(
26167
- `${apiBaseUrl(options, args)}${path}`,
26187
+ `${apiBaseUrl(options, args)}${developerPathForWorkspace(path, args, options)}`,
26168
26188
  {
26169
26189
  body: JSON.stringify(body),
26170
26190
  headers: {
@@ -26234,11 +26254,17 @@ async function sendTestEvent(args, options) {
26234
26254
  }
26235
26255
  async function callSaaSFunnelsMcpTool(name, args, options = {}) {
26236
26256
  try {
26237
- if ((name === "get_account_strategy" || name === "simulate_account_funnel_fit") && !options.hostedMode) {
26257
+ if (!options.hostedMode && "workspace_id" in args && hostedWorkspaceToolNames.has(name)) {
26258
+ throw new McpProtocolError(
26259
+ -32602,
26260
+ "workspace_id is available only with hosted OAuth; an API key is pinned to its workspace."
26261
+ );
26262
+ }
26263
+ if ((name === "get_account_strategy" || name === "simulate_account_funnel_fit" || name === "list_workspaces") && !options.hostedMode) {
26238
26264
  throw new McpToolExecutionError(
26239
- "This account tool requires hosted OAuth.",
26265
+ "This tool requires hosted OAuth.",
26240
26266
  {
26241
- error: "This account tool requires hosted OAuth.",
26267
+ error: "This tool requires hosted OAuth.",
26242
26268
  ok: false,
26243
26269
  required_boundary: "hosted_mcp"
26244
26270
  }
@@ -26281,6 +26307,10 @@ async function callSaaSFunnelsMcpTool(name, args, options = {}) {
26281
26307
  options
26282
26308
  )
26283
26309
  );
26310
+ if (name === "list_workspaces")
26311
+ return structuredToolResult(
26312
+ await developerGet("/api/developer-tools/workspaces", {}, options)
26313
+ );
26284
26314
  if (name === "get_workspace_readiness")
26285
26315
  return structuredToolResult(
26286
26316
  await developerGet(
@@ -1,5 +1,5 @@
1
1
  export declare const SAASFUNNELS_CLI_NAME = "saasfunnels";
2
- export declare const SAASFUNNELS_CLI_VERSION = "0.2.1";
2
+ export declare const SAASFUNNELS_CLI_VERSION = "0.2.2";
3
3
  export declare const SAASFUNNELS_PRODUCT_NAME = "SaaSFunnels";
4
4
  export declare const SAASFUNNELS_DEFAULT_API_BASE_URL = "https://app.saasfunnels.ai";
5
5
  export declare const SAASFUNNELS_ENV: Readonly<{
@@ -37,6 +37,7 @@ export declare const saasFunnelsMcpToolCapabilityIds: {
37
37
  readonly get_integration_health: "developer.workspace.read";
38
38
  readonly get_signal_payload_preview: "developer.workspace.read";
39
39
  readonly get_workspace_readiness: "developer.workspace.read";
40
+ readonly list_workspaces: "developer.workspace.read";
40
41
  readonly inspect_funnel_entry: "funnel.entry.inspect";
41
42
  readonly list_mapping_gaps: "developer.workspace.read";
42
43
  readonly list_recent_signals: "developer.workspace.read";
@@ -68,7 +69,7 @@ export type SaaSFunnelsMcpOptions = {
68
69
  output?: Pick<NodeJS.WritableStream, "write">;
69
70
  };
70
71
  declare const mcpProtocolVersion = "2025-06-18";
71
- declare const mcpServerVersion = "0.2.1";
72
+ declare const mcpServerVersion = "0.2.2";
72
73
  export declare function saasFunnelsMcpResources(): McpResourceDefinition[];
73
74
  export declare function readSaaSFunnelsMcpResource(uri: string, options?: SaaSFunnelsMcpOptions): string;
74
75
  export declare function saasFunnelsMcpToolDefinitions({ allowFunnelTests, allowFunnelWrites, allowTestWrites, includeHostedAccountTools, }?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saasfunnels",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "SaaSFunnels CLI and local stdio MCP server",
6
6
  "author": "SaaSFunnels",