@stackable-labs/mcp-app-extension 2.1.0 → 2.3.0

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
@@ -15,7 +15,7 @@ Both transports expose the same tool surface: `list_apps`, `list_extensions`, `g
15
15
 
16
16
  Drop into your project root (or `~/.claude.json` for user-scoped setup). Both entries can coexist — Claude Code routes tool calls per-server.
17
17
 
18
- **Dev (against deployed `api-dev-use1.stackablelabs.io`):**
18
+ **Dev (against deployed `api-x-dev.stackablelabs.io`):**
19
19
 
20
20
  ```json
21
21
  {
@@ -33,8 +33,8 @@ Drop into your project root (or `~/.claude.json` for user-scoped setup). Both en
33
33
  "@stackable-labs/mcp-app-extension@latest"
34
34
  ],
35
35
  "env": {
36
- "MCP_API_BASE_URL": "https://mcp-dev.stackablelabs.io",
37
- "ADMIN_API_BASE_URL": "https://api-dev-use1.stackablelabs.io/admin"
36
+ "MCP_LABS_API_BASE_URL": "https://mcp-dev.stackablelabs.io",
37
+ "ADMIN_LABS_API_BASE_URL": "https://api-x-dev.stackablelabs.io/admin"
38
38
  }
39
39
  }
40
40
  }
@@ -65,8 +65,10 @@ Both optional — defaults point at prod. Override per-stage as needed.
65
65
 
66
66
  | Env var | Default | Purpose |
67
67
  |---------|---------|---------|
68
- | `MCP_API_BASE_URL` | `https://mcp.stackablelabs.io` | MCP host root. OAuth discovery URL is derived as `${mcpBase}/.well-known/oauth-authorization-server`. |
69
- | `ADMIN_API_BASE_URL` | `https://api-use1.stackablelabs.io/admin` | Admin API endpoint for platform tool calls (`list_apps`, etc.). |
68
+ | `MCP_LABS_API_BASE_URL` | `https://mcp.stackablelabs.io` | MCP host root. OAuth discovery URL is derived as `${mcpBase}/.well-known/oauth-authorization-server`. |
69
+ | `ADMIN_LABS_API_BASE_URL` | `https://api-x.stackablelabs.io/admin` | Admin API endpoint for platform tool calls (`list_apps`, etc.). |
70
+
71
+ > **Renamed (2026-07):** `MCP_API_BASE_URL` → `MCP_LABS_API_BASE_URL`, `ADMIN_API_BASE_URL` → `ADMIN_LABS_API_BASE_URL`. The old names still work as a deprecated fallback — update your config; they'll be removed in a later release.
70
72
 
71
73
  HTTP transport reads URLs from the deployed Lambda's env (set via CDK + `packages/infra/cdk/.env.{stage}` — not client-controlled).
72
74
 
@@ -87,7 +89,7 @@ To test a local build via stdio, point the `.mcp.json` `command`/`args` at the b
87
89
  ```json
88
90
  {
89
91
  "command": "node",
90
- "args": ["/absolute/path/to/packages/mcp/app-extension/dist/index.js"],
91
- "env": { "MCP_API_BASE_URL": "...", "ADMIN_API_BASE_URL": "..." }
92
+ "args": ["/absolute/path/to/packages/mcp/labs-app-extension/dist/index.js"],
93
+ "env": { "MCP_LABS_API_BASE_URL": "...", "ADMIN_LABS_API_BASE_URL": "..." }
92
94
  }
93
95
  ```
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { IDENTITY_EVENT, ACTIVITY_EVENT, SURFACE_TARGET, TEMPLATE_FLAVORS, MESSAGING_ACTION_EXAMPLES, PERMISSIONS, CAPABILITY_PERMISSION_MAP, HOOK_PERMISSION_MAP, ALLOWED_ICONS, UI_TAGS, UI_TAG_CATEGORIES, UI_TAG_ATTRIBUTES, tagToComponentName, EVENT_HOOK_PERMISSION_MAP, MESSAGING_SEND_EXAMPLES } from '@stackable-labs/sdk-extension-contracts';
5
- import { validatePatternFormat } from '@stackable-labs/lib-contracts';
6
- import fs4, { readFile, mkdir, writeFile, constants } from 'fs/promises';
7
5
  import path, { join } from 'path';
6
+ import fs4, { readFile, mkdir, writeFile, constants } from 'fs/promises';
8
7
  import os, { homedir } from 'os';
8
+ import { validatePatternFormat } from '@stackable-labs/lib-contracts';
9
9
  import { z } from 'zod';
10
10
  import { createServer } from 'http';
11
11
  import process6 from 'process';
@@ -2912,7 +2912,7 @@ Both workflows produce the same output \u2014 a Stackable extension that runs in
2912
2912
  };
2913
2913
 
2914
2914
  // ../../sdk/extension/ai-docs/src/generators/ai-accelerated-development.ts
2915
- var DEFAULT_MCP_API_BASE_URL = "https://mcp.stackablelabs.io";
2915
+ var DEFAULT_MCP_LABS_API_BASE_URL = "https://mcp.stackablelabs.io";
2916
2916
  var generateAIAcceleratedDevelopment = () => {
2917
2917
  const fm = frontmatter({
2918
2918
  root: false,
@@ -2920,8 +2920,8 @@ var generateAIAcceleratedDevelopment = () => {
2920
2920
  description: "AI-Accelerated Development: AI Extension Studio, Agent Skills, the live MCP server, and the Claude Code plugin \u2014 and how to choose between them.",
2921
2921
  globs: ["*"]
2922
2922
  });
2923
- const mcpApiBaseUrl = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL;
2924
- const mcpEndpointUrl = `${mcpApiBaseUrl}/app-extension`;
2923
+ const mcpApiBaseUrl2 = process.env.MCP_LABS_API_BASE_URL ?? DEFAULT_MCP_LABS_API_BASE_URL;
2924
+ const mcpEndpointUrl = `${mcpApiBaseUrl2}/app-extension`;
2925
2925
  return `${fm}
2926
2926
 
2927
2927
  # AI-Accelerated Development
@@ -5678,23 +5678,25 @@ var getVerifier = () => getRandomBytes(32, "base64url");
5678
5678
 
5679
5679
  // ../../lib/utils-auth/src/constants.ts
5680
5680
  var STANDALONE_CLIENT_DATA = {
5681
- CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
5682
- MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
5681
+ STACKABLE_LABS: {
5682
+ CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
5683
+ MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
5684
+ },
5685
+ STACKABLE_OPS: {
5686
+ MCP: { name: "@stackable-ops/mcp", authFile: "mcp-ops-auth.json" }
5687
+ }
5683
5688
  };
5684
- var STANDALONE_CLIENT = Object.fromEntries(
5685
- Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.name])
5689
+ var projectField = (field) => Object.fromEntries(
5690
+ Object.entries(STANDALONE_CLIENT_DATA).map(([org, roles]) => [
5691
+ org,
5692
+ Object.fromEntries(Object.entries(roles).map(([role, entry]) => [role, entry[field]]))
5693
+ ])
5686
5694
  );
5687
- var STANDALONE_CLIENT_AUTH_FILE = Object.fromEntries(
5688
- Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.authFile])
5689
- );
5690
- Object.values(STANDALONE_CLIENT);
5691
-
5692
- // ../../lib/utils-auth/src/index.ts
5693
- var deriveClientId = async (clientName) => (await getDigest(clientName)).slice(0, 32);
5695
+ var STANDALONE_CLIENT = projectField("name");
5696
+ var STANDALONE_CLIENT_AUTH_FILE = projectField("authFile");
5697
+ Object.values(STANDALONE_CLIENT_DATA).flatMap((roles) => Object.values(roles).map((entry) => entry.name));
5694
5698
  var AUTH_DIR = join(homedir(), ".stackable");
5695
- join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.CLI);
5696
- var MCP_AUTH_FILE = join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.MCP);
5697
- var resolveAuthFile = (filename) => join(AUTH_DIR, filename ?? STANDALONE_CLIENT_AUTH_FILE.CLI);
5699
+ var resolveAuthFile = (filename) => join(AUTH_DIR, filename ?? STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
5698
5700
  var readAuthState = async (filename) => {
5699
5701
  try {
5700
5702
  const content = await readFile(resolveAuthFile(filename), "utf8");
@@ -5733,85 +5735,44 @@ var getToken = async (filename) => {
5733
5735
  return state.token;
5734
5736
  };
5735
5737
 
5738
+ // ../../lib/utils-auth/src/index.ts
5739
+ var deriveDcrClientId = async (clientName) => (await getDigest(clientName)).slice(0, 32);
5740
+ var deriveClientId = async (clientName) => deriveDcrClientId(clientName);
5741
+ join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
5742
+ var MCP_AUTH_FILE = join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.MCP);
5743
+
5744
+ // ../../lib/utils-mcp/src/index.ts
5745
+ var textContent = (text) => ({
5746
+ content: [{ type: "text", text }]
5747
+ });
5748
+ var errorContent = (text) => ({
5749
+ isError: true,
5750
+ content: [{ type: "text", text }]
5751
+ });
5752
+
5736
5753
  // package.json
5737
5754
  var package_default = {
5738
5755
  version: "0.0.0"};
5739
-
5740
- // src/server.ts
5741
- var MCP_CLIENT_NAME = STANDALONE_CLIENT.MCP;
5742
- var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
5743
- var DEFAULT_MCP_API_BASE_URL2 = "https://mcp.stackablelabs.io";
5744
- var textContent = (text) => ({ content: [{ type: "text", text }] });
5745
- var errorContent = (text) => ({ content: [{ type: "text", text }], isError: true });
5746
- var createMcpServer = (options = {}) => {
5747
- const server2 = new McpServer({
5748
- name: "stackable-extension-dev",
5749
- version: package_default.version
5750
- });
5751
- const getAuthToken = async () => {
5752
- if (options.authContext?.token) {
5753
- return options.authContext.token;
5754
- }
5755
- try {
5756
- return await getToken(STANDALONE_CLIENT_AUTH_FILE.MCP);
5757
- } catch {
5758
- }
5759
- if (!options.performOAuthFlow) {
5760
- throw new Error(
5761
- "No auth token available. Provide authContext (server) or performOAuthFlow (CLI)."
5762
- );
5763
- }
5764
- const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL2;
5765
- return options.performOAuthFlow(`${MCP_API_BASE_URL}/.well-known/oauth-authorization-server`);
5766
- };
5767
- const authHeaders = (token) => ({
5768
- authorization: `Bearer ${token}`,
5769
- "content-type": "application/json",
5770
- "x-client-name": MCP_CLIENT_NAME
5771
- });
5772
- const callApi = async (path2) => {
5773
- const token = await getAuthToken();
5774
- const ADMIN_API_BASE_URL = process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
5775
- const url = `${ADMIN_API_BASE_URL}${path2}`;
5776
- let res;
5777
- try {
5778
- res = await fetch(url, { headers: authHeaders(token) });
5779
- } catch (err) {
5780
- return errorContent(
5781
- `Network error calling ${url}: ${err.message}. Check ADMIN_API_BASE_URL env var (currently "${ADMIN_API_BASE_URL}").`
5782
- );
5783
- }
5784
- if (!res.ok) {
5785
- return errorContent(`API error: ${res.status} ${res.statusText} (${url})`);
5786
- }
5787
- const data = await res.json();
5788
- return textContent(JSON.stringify(data, null, 2));
5789
- };
5790
- const withAuth = async (fn) => {
5791
- try {
5792
- return await fn();
5793
- } catch (err) {
5794
- const e = err;
5795
- if (e.message.includes("fetch")) {
5796
- const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL2;
5797
- return errorContent(
5798
- `MCP auth flow failed: ${e.message}. Check MCP_API_BASE_URL env var (currently "${MCP_API_BASE_URL}"). To re-auth from scratch: rm ${MCP_AUTH_FILE}`
5799
- );
5800
- }
5801
- return errorContent(e.message);
5802
- }
5803
- };
5756
+ var registerAppTools = (server2, ctx) => {
5757
+ server2.registerTool("list_apps", {
5758
+ description: "List available apps for development with IDs, names, and targets. Requires CLI auth."
5759
+ }, async () => ctx.withAuth(() => ctx.callApi("/app-extension")));
5760
+ server2.registerTool("list_extensions", {
5761
+ description: "List extensions under an app with status, version, and bundle URL. Requires CLI auth.",
5762
+ inputSchema: { appId: z.string().describe("App ID") }
5763
+ }, async ({ appId }) => ctx.withAuth(() => ctx.callApi(`/app-extension/${appId}/extensions`)));
5764
+ server2.registerTool("get_extension", {
5765
+ description: "Get extension detail including manifest, permissions, and targets. Requires CLI auth.",
5766
+ inputSchema: { appId: z.string().describe("App ID"), extensionId: z.string().describe("Extension ID") }
5767
+ }, async ({ appId, extensionId }) => ctx.withAuth(() => ctx.callApi(`/app-extension/${appId}/extensions/${extensionId}`)));
5768
+ server2.registerTool("list_instances", {
5769
+ description: "List instances under an app. Requires CLI auth.",
5770
+ inputSchema: { appId: z.string().describe("App ID") }
5771
+ }, async ({ appId }) => ctx.withAuth(() => ctx.callApi(`/app-extension/${appId}/instances`)));
5772
+ };
5773
+ var apps_default = registerAppTools;
5774
+ var registerSkillTools = (server2) => {
5804
5775
  const registrySkills = listSkills("registry");
5805
- for (const skill of registrySkills) {
5806
- server2.registerResource(
5807
- skill.id,
5808
- `sdk://${skill.id}`,
5809
- { description: skill.description, mimeType: "text/markdown" },
5810
- async (uri) => ({
5811
- contents: [{ uri: uri.href, text: getSkillBody(skill), mimeType: "text/markdown" }]
5812
- })
5813
- );
5814
- }
5815
5776
  server2.registerTool("list_skills", {
5816
5777
  description: "List all available SDK skills with descriptions"
5817
5778
  }, async () => textContent(JSON.stringify(registrySkills.map(getSkillMetadata), null, 2)));
@@ -5835,6 +5796,9 @@ var createMcpServer = (options = {}) => {
5835
5796
  }
5836
5797
  return errorContent('Provide either "id" or "query" parameter.');
5837
5798
  });
5799
+ };
5800
+ var skills_default = registerSkillTools;
5801
+ var registerValidationTools = (server2) => {
5838
5802
  server2.registerTool("validate_manifest", {
5839
5803
  description: "Validate an extension manifest.json. Checks required fields, valid permissions, targets, and allowedDomains consistency with permissions.",
5840
5804
  inputSchema: { manifestContent: z.string().describe("The full manifest.json content as a string") }
@@ -5929,21 +5893,93 @@ ${errors.map((e) => `- ${e}`).join("\n")}`);
5929
5893
  return textContent(`Permission analysis:
5930
5894
  ${issues.map((i) => `- ${i}`).join("\n")}`);
5931
5895
  });
5932
- server2.registerTool("list_apps", {
5933
- description: "List available apps for development with IDs, names, and targets. Requires CLI auth."
5934
- }, async () => withAuth(() => callApi("/app-extension")));
5935
- server2.registerTool("list_extensions", {
5936
- description: "List extensions under an app with status, version, and bundle URL. Requires CLI auth.",
5937
- inputSchema: { appId: z.string().describe("App ID") }
5938
- }, async ({ appId }) => withAuth(() => callApi(`/app-extension/${appId}/extensions`)));
5939
- server2.registerTool("get_extension", {
5940
- description: "Get extension detail including manifest, permissions, and targets. Requires CLI auth.",
5941
- inputSchema: { appId: z.string().describe("App ID"), extensionId: z.string().describe("Extension ID") }
5942
- }, async ({ appId, extensionId }) => withAuth(() => callApi(`/app-extension/${appId}/extensions/${extensionId}`)));
5943
- server2.registerTool("list_instances", {
5944
- description: "List instances under an app. Requires CLI auth.",
5945
- inputSchema: { appId: z.string().describe("App ID") }
5946
- }, async ({ appId }) => withAuth(() => callApi(`/app-extension/${appId}/instances`)));
5896
+ };
5897
+ var validation_default = registerValidationTools;
5898
+
5899
+ // src/tools/index.ts
5900
+ var toolRegistrars = [
5901
+ skills_default,
5902
+ validation_default,
5903
+ apps_default
5904
+ ];
5905
+
5906
+ // src/server.ts
5907
+ var MCP_CLIENT_NAME = STANDALONE_CLIENT.STACKABLE_LABS.MCP;
5908
+ var DEFAULT_ADMIN_LABS_API_URL = "https://api-x.stackablelabs.io/admin";
5909
+ var DEFAULT_MCP_LABS_API_BASE_URL2 = "https://mcp.stackablelabs.io";
5910
+ var adminApiBaseUrl = () => process.env.ADMIN_LABS_API_BASE_URL ?? process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_LABS_API_URL;
5911
+ var mcpApiBaseUrl = () => process.env.MCP_LABS_API_BASE_URL ?? process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_LABS_API_BASE_URL2;
5912
+ var createMcpServer = (options = {}) => {
5913
+ const server2 = new McpServer({
5914
+ name: "stackable-extension-dev",
5915
+ version: package_default.version
5916
+ });
5917
+ const getAuthToken = async () => {
5918
+ if (options.authContext?.token) {
5919
+ return options.authContext.token;
5920
+ }
5921
+ try {
5922
+ return await getToken(STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.MCP);
5923
+ } catch {
5924
+ }
5925
+ if (!options.performOAuthFlow) {
5926
+ throw new Error(
5927
+ "No auth token available. Provide authContext (server) or performOAuthFlow (CLI)."
5928
+ );
5929
+ }
5930
+ return options.performOAuthFlow(`${mcpApiBaseUrl()}/.well-known/oauth-authorization-server`);
5931
+ };
5932
+ const authHeaders = (token) => ({
5933
+ authorization: `Bearer ${token}`,
5934
+ "content-type": "application/json",
5935
+ "x-client-name": MCP_CLIENT_NAME
5936
+ });
5937
+ const callApi = async (path2) => {
5938
+ const token = await getAuthToken();
5939
+ const adminBase = adminApiBaseUrl();
5940
+ const url = `${adminBase}${path2}`;
5941
+ let res;
5942
+ try {
5943
+ res = await fetch(url, { headers: authHeaders(token) });
5944
+ } catch (err) {
5945
+ return errorContent(
5946
+ `Network error calling ${url}: ${err.message}. Check ADMIN_LABS_API_BASE_URL env var (currently "${adminBase}").`
5947
+ );
5948
+ }
5949
+ if (!res.ok) {
5950
+ return errorContent(`API error: ${res.status} ${res.statusText} (${url})`);
5951
+ }
5952
+ const data = await res.json();
5953
+ return textContent(JSON.stringify(data, null, 2));
5954
+ };
5955
+ const withAuth = async (fn) => {
5956
+ try {
5957
+ return await fn();
5958
+ } catch (err) {
5959
+ const e = err;
5960
+ if (e.message.includes("fetch")) {
5961
+ return errorContent(
5962
+ `MCP auth flow failed: ${e.message}. Check MCP_LABS_API_BASE_URL env var (currently "${mcpApiBaseUrl()}"). To re-auth from scratch: rm ${MCP_AUTH_FILE}`
5963
+ );
5964
+ }
5965
+ return errorContent(e.message);
5966
+ }
5967
+ };
5968
+ const registrySkills = listSkills("registry");
5969
+ for (const skill of registrySkills) {
5970
+ server2.registerResource(
5971
+ skill.id,
5972
+ `sdk://${skill.id}`,
5973
+ { description: skill.description, mimeType: "text/markdown" },
5974
+ async (uri) => ({
5975
+ contents: [{ uri: uri.href, text: getSkillBody(skill), mimeType: "text/markdown" }]
5976
+ })
5977
+ );
5978
+ }
5979
+ const ctx = { withAuth, callApi };
5980
+ for (const register of toolRegistrars) {
5981
+ register(server2, ctx);
5982
+ }
5947
5983
  return server2;
5948
5984
  };
5949
5985
  var isDockerCached;
@@ -6437,7 +6473,7 @@ var registerClient = async (registrationEndpoint) => {
6437
6473
  method: "POST",
6438
6474
  headers: { "content-type": "application/json" },
6439
6475
  body: JSON.stringify({
6440
- client_name: STANDALONE_CLIENT.MCP,
6476
+ client_name: STANDALONE_CLIENT.STACKABLE_LABS.MCP,
6441
6477
  redirect_uris: ["http://127.0.0.1/callback"]
6442
6478
  })
6443
6479
  });
@@ -6474,7 +6510,7 @@ var decodeJwtPayload2 = (token) => {
6474
6510
  };
6475
6511
  var performOAuthFlow = async (discoveryUrl) => {
6476
6512
  const discovery = await fetchDiscovery(discoveryUrl);
6477
- let clientId = await deriveClientId(STANDALONE_CLIENT.MCP);
6513
+ let clientId = await deriveClientId(STANDALONE_CLIENT.STACKABLE_LABS.MCP);
6478
6514
  if (discovery.registration_endpoint) {
6479
6515
  clientId = await registerClient(discovery.registration_endpoint);
6480
6516
  }
@@ -6557,7 +6593,7 @@ var performOAuthFlow = async (discoveryUrl) => {
6557
6593
  orgId: payload.orgId,
6558
6594
  userId: payload.sub
6559
6595
  },
6560
- STANDALONE_CLIENT_AUTH_FILE.MCP
6596
+ STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.MCP
6561
6597
  );
6562
6598
  return tokenResponse.access_token;
6563
6599
  };
package/dist/server.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { IDENTITY_EVENT, ACTIVITY_EVENT, SURFACE_TARGET, TEMPLATE_FLAVORS, MESSAGING_ACTION_EXAMPLES, PERMISSIONS, CAPABILITY_PERMISSION_MAP, HOOK_PERMISSION_MAP, ALLOWED_ICONS, UI_TAGS, UI_TAG_CATEGORIES, UI_TAG_ATTRIBUTES, tagToComponentName, EVENT_HOOK_PERMISSION_MAP, MESSAGING_SEND_EXAMPLES } from '@stackable-labs/sdk-extension-contracts';
3
- import { validatePatternFormat } from '@stackable-labs/lib-contracts';
4
- import { readFile } from 'fs/promises';
5
3
  import { join } from 'path';
4
+ import { readFile } from 'fs/promises';
6
5
  import { homedir } from 'os';
6
+ import { validatePatternFormat } from '@stackable-labs/lib-contracts';
7
7
  import { z } from 'zod';
8
8
 
9
9
  // src/server.ts
@@ -2905,7 +2905,7 @@ Both workflows produce the same output \u2014 a Stackable extension that runs in
2905
2905
  };
2906
2906
 
2907
2907
  // ../../sdk/extension/ai-docs/src/generators/ai-accelerated-development.ts
2908
- var DEFAULT_MCP_API_BASE_URL = "https://mcp.stackablelabs.io";
2908
+ var DEFAULT_MCP_LABS_API_BASE_URL = "https://mcp.stackablelabs.io";
2909
2909
  var generateAIAcceleratedDevelopment = () => {
2910
2910
  const fm = frontmatter({
2911
2911
  root: false,
@@ -2913,8 +2913,8 @@ var generateAIAcceleratedDevelopment = () => {
2913
2913
  description: "AI-Accelerated Development: AI Extension Studio, Agent Skills, the live MCP server, and the Claude Code plugin \u2014 and how to choose between them.",
2914
2914
  globs: ["*"]
2915
2915
  });
2916
- const mcpApiBaseUrl = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL;
2917
- const mcpEndpointUrl = `${mcpApiBaseUrl}/app-extension`;
2916
+ const mcpApiBaseUrl2 = process.env.MCP_LABS_API_BASE_URL ?? DEFAULT_MCP_LABS_API_BASE_URL;
2917
+ const mcpEndpointUrl = `${mcpApiBaseUrl2}/app-extension`;
2918
2918
  return `${fm}
2919
2919
 
2920
2920
  # AI-Accelerated Development
@@ -5632,22 +5632,25 @@ pair(EVENT_SNIPPETS, EVENT_SNIPPETS2);
5632
5632
 
5633
5633
  // ../../lib/utils-auth/src/constants.ts
5634
5634
  var STANDALONE_CLIENT_DATA = {
5635
- CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
5636
- MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
5635
+ STACKABLE_LABS: {
5636
+ CLI: { name: "@stackable-labs/cli-app-extension", authFile: "cli-auth.json" },
5637
+ MCP: { name: "@stackable-labs/mcp-app-extension", authFile: "mcp-auth.json" }
5638
+ },
5639
+ STACKABLE_OPS: {
5640
+ MCP: { name: "@stackable-ops/mcp", authFile: "mcp-ops-auth.json" }
5641
+ }
5637
5642
  };
5638
- var STANDALONE_CLIENT = Object.fromEntries(
5639
- Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.name])
5643
+ var projectField = (field) => Object.fromEntries(
5644
+ Object.entries(STANDALONE_CLIENT_DATA).map(([org, roles]) => [
5645
+ org,
5646
+ Object.fromEntries(Object.entries(roles).map(([role, entry]) => [role, entry[field]]))
5647
+ ])
5640
5648
  );
5641
- var STANDALONE_CLIENT_AUTH_FILE = Object.fromEntries(
5642
- Object.entries(STANDALONE_CLIENT_DATA).map(([k, v]) => [k, v.authFile])
5643
- );
5644
- Object.values(STANDALONE_CLIENT);
5645
-
5646
- // ../../lib/utils-auth/src/index.ts
5649
+ var STANDALONE_CLIENT = projectField("name");
5650
+ var STANDALONE_CLIENT_AUTH_FILE = projectField("authFile");
5651
+ Object.values(STANDALONE_CLIENT_DATA).flatMap((roles) => Object.values(roles).map((entry) => entry.name));
5647
5652
  var AUTH_DIR = join(homedir(), ".stackable");
5648
- join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.CLI);
5649
- var MCP_AUTH_FILE = join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.MCP);
5650
- var resolveAuthFile = (filename) => join(AUTH_DIR, filename ?? STANDALONE_CLIENT_AUTH_FILE.CLI);
5653
+ var resolveAuthFile = (filename) => join(AUTH_DIR, filename ?? STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
5651
5654
  var readAuthState = async (filename) => {
5652
5655
  try {
5653
5656
  const content = await readFile(resolveAuthFile(filename), "utf8");
@@ -5682,85 +5685,42 @@ var getToken = async (filename) => {
5682
5685
  return state.token;
5683
5686
  };
5684
5687
 
5688
+ // ../../lib/utils-auth/src/index.ts
5689
+ join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.CLI);
5690
+ var MCP_AUTH_FILE = join(AUTH_DIR, STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.MCP);
5691
+
5692
+ // ../../lib/utils-mcp/src/index.ts
5693
+ var textContent = (text) => ({
5694
+ content: [{ type: "text", text }]
5695
+ });
5696
+ var errorContent = (text) => ({
5697
+ isError: true,
5698
+ content: [{ type: "text", text }]
5699
+ });
5700
+
5685
5701
  // package.json
5686
5702
  var package_default = {
5687
5703
  version: "0.0.0"};
5688
-
5689
- // src/server.ts
5690
- var MCP_CLIENT_NAME = STANDALONE_CLIENT.MCP;
5691
- var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
5692
- var DEFAULT_MCP_API_BASE_URL2 = "https://mcp.stackablelabs.io";
5693
- var textContent = (text) => ({ content: [{ type: "text", text }] });
5694
- var errorContent = (text) => ({ content: [{ type: "text", text }], isError: true });
5695
- var createMcpServer = (options = {}) => {
5696
- const server = new McpServer({
5697
- name: "stackable-extension-dev",
5698
- version: package_default.version
5699
- });
5700
- const getAuthToken = async () => {
5701
- if (options.authContext?.token) {
5702
- return options.authContext.token;
5703
- }
5704
- try {
5705
- return await getToken(STANDALONE_CLIENT_AUTH_FILE.MCP);
5706
- } catch {
5707
- }
5708
- if (!options.performOAuthFlow) {
5709
- throw new Error(
5710
- "No auth token available. Provide authContext (server) or performOAuthFlow (CLI)."
5711
- );
5712
- }
5713
- const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL2;
5714
- return options.performOAuthFlow(`${MCP_API_BASE_URL}/.well-known/oauth-authorization-server`);
5715
- };
5716
- const authHeaders = (token) => ({
5717
- authorization: `Bearer ${token}`,
5718
- "content-type": "application/json",
5719
- "x-client-name": MCP_CLIENT_NAME
5720
- });
5721
- const callApi = async (path) => {
5722
- const token = await getAuthToken();
5723
- const ADMIN_API_BASE_URL = process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
5724
- const url = `${ADMIN_API_BASE_URL}${path}`;
5725
- let res;
5726
- try {
5727
- res = await fetch(url, { headers: authHeaders(token) });
5728
- } catch (err) {
5729
- return errorContent(
5730
- `Network error calling ${url}: ${err.message}. Check ADMIN_API_BASE_URL env var (currently "${ADMIN_API_BASE_URL}").`
5731
- );
5732
- }
5733
- if (!res.ok) {
5734
- return errorContent(`API error: ${res.status} ${res.statusText} (${url})`);
5735
- }
5736
- const data = await res.json();
5737
- return textContent(JSON.stringify(data, null, 2));
5738
- };
5739
- const withAuth = async (fn) => {
5740
- try {
5741
- return await fn();
5742
- } catch (err) {
5743
- const e = err;
5744
- if (e.message.includes("fetch")) {
5745
- const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL2;
5746
- return errorContent(
5747
- `MCP auth flow failed: ${e.message}. Check MCP_API_BASE_URL env var (currently "${MCP_API_BASE_URL}"). To re-auth from scratch: rm ${MCP_AUTH_FILE}`
5748
- );
5749
- }
5750
- return errorContent(e.message);
5751
- }
5752
- };
5704
+ var registerAppTools = (server, ctx) => {
5705
+ server.registerTool("list_apps", {
5706
+ description: "List available apps for development with IDs, names, and targets. Requires CLI auth."
5707
+ }, async () => ctx.withAuth(() => ctx.callApi("/app-extension")));
5708
+ server.registerTool("list_extensions", {
5709
+ description: "List extensions under an app with status, version, and bundle URL. Requires CLI auth.",
5710
+ inputSchema: { appId: z.string().describe("App ID") }
5711
+ }, async ({ appId }) => ctx.withAuth(() => ctx.callApi(`/app-extension/${appId}/extensions`)));
5712
+ server.registerTool("get_extension", {
5713
+ description: "Get extension detail including manifest, permissions, and targets. Requires CLI auth.",
5714
+ inputSchema: { appId: z.string().describe("App ID"), extensionId: z.string().describe("Extension ID") }
5715
+ }, async ({ appId, extensionId }) => ctx.withAuth(() => ctx.callApi(`/app-extension/${appId}/extensions/${extensionId}`)));
5716
+ server.registerTool("list_instances", {
5717
+ description: "List instances under an app. Requires CLI auth.",
5718
+ inputSchema: { appId: z.string().describe("App ID") }
5719
+ }, async ({ appId }) => ctx.withAuth(() => ctx.callApi(`/app-extension/${appId}/instances`)));
5720
+ };
5721
+ var apps_default = registerAppTools;
5722
+ var registerSkillTools = (server) => {
5753
5723
  const registrySkills = listSkills("registry");
5754
- for (const skill of registrySkills) {
5755
- server.registerResource(
5756
- skill.id,
5757
- `sdk://${skill.id}`,
5758
- { description: skill.description, mimeType: "text/markdown" },
5759
- async (uri) => ({
5760
- contents: [{ uri: uri.href, text: getSkillBody(skill), mimeType: "text/markdown" }]
5761
- })
5762
- );
5763
- }
5764
5724
  server.registerTool("list_skills", {
5765
5725
  description: "List all available SDK skills with descriptions"
5766
5726
  }, async () => textContent(JSON.stringify(registrySkills.map(getSkillMetadata), null, 2)));
@@ -5784,6 +5744,9 @@ var createMcpServer = (options = {}) => {
5784
5744
  }
5785
5745
  return errorContent('Provide either "id" or "query" parameter.');
5786
5746
  });
5747
+ };
5748
+ var skills_default = registerSkillTools;
5749
+ var registerValidationTools = (server) => {
5787
5750
  server.registerTool("validate_manifest", {
5788
5751
  description: "Validate an extension manifest.json. Checks required fields, valid permissions, targets, and allowedDomains consistency with permissions.",
5789
5752
  inputSchema: { manifestContent: z.string().describe("The full manifest.json content as a string") }
@@ -5878,21 +5841,93 @@ ${errors.map((e) => `- ${e}`).join("\n")}`);
5878
5841
  return textContent(`Permission analysis:
5879
5842
  ${issues.map((i) => `- ${i}`).join("\n")}`);
5880
5843
  });
5881
- server.registerTool("list_apps", {
5882
- description: "List available apps for development with IDs, names, and targets. Requires CLI auth."
5883
- }, async () => withAuth(() => callApi("/app-extension")));
5884
- server.registerTool("list_extensions", {
5885
- description: "List extensions under an app with status, version, and bundle URL. Requires CLI auth.",
5886
- inputSchema: { appId: z.string().describe("App ID") }
5887
- }, async ({ appId }) => withAuth(() => callApi(`/app-extension/${appId}/extensions`)));
5888
- server.registerTool("get_extension", {
5889
- description: "Get extension detail including manifest, permissions, and targets. Requires CLI auth.",
5890
- inputSchema: { appId: z.string().describe("App ID"), extensionId: z.string().describe("Extension ID") }
5891
- }, async ({ appId, extensionId }) => withAuth(() => callApi(`/app-extension/${appId}/extensions/${extensionId}`)));
5892
- server.registerTool("list_instances", {
5893
- description: "List instances under an app. Requires CLI auth.",
5894
- inputSchema: { appId: z.string().describe("App ID") }
5895
- }, async ({ appId }) => withAuth(() => callApi(`/app-extension/${appId}/instances`)));
5844
+ };
5845
+ var validation_default = registerValidationTools;
5846
+
5847
+ // src/tools/index.ts
5848
+ var toolRegistrars = [
5849
+ skills_default,
5850
+ validation_default,
5851
+ apps_default
5852
+ ];
5853
+
5854
+ // src/server.ts
5855
+ var MCP_CLIENT_NAME = STANDALONE_CLIENT.STACKABLE_LABS.MCP;
5856
+ var DEFAULT_ADMIN_LABS_API_URL = "https://api-x.stackablelabs.io/admin";
5857
+ var DEFAULT_MCP_LABS_API_BASE_URL2 = "https://mcp.stackablelabs.io";
5858
+ var adminApiBaseUrl = () => process.env.ADMIN_LABS_API_BASE_URL ?? process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_LABS_API_URL;
5859
+ var mcpApiBaseUrl = () => process.env.MCP_LABS_API_BASE_URL ?? process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_LABS_API_BASE_URL2;
5860
+ var createMcpServer = (options = {}) => {
5861
+ const server = new McpServer({
5862
+ name: "stackable-extension-dev",
5863
+ version: package_default.version
5864
+ });
5865
+ const getAuthToken = async () => {
5866
+ if (options.authContext?.token) {
5867
+ return options.authContext.token;
5868
+ }
5869
+ try {
5870
+ return await getToken(STANDALONE_CLIENT_AUTH_FILE.STACKABLE_LABS.MCP);
5871
+ } catch {
5872
+ }
5873
+ if (!options.performOAuthFlow) {
5874
+ throw new Error(
5875
+ "No auth token available. Provide authContext (server) or performOAuthFlow (CLI)."
5876
+ );
5877
+ }
5878
+ return options.performOAuthFlow(`${mcpApiBaseUrl()}/.well-known/oauth-authorization-server`);
5879
+ };
5880
+ const authHeaders = (token) => ({
5881
+ authorization: `Bearer ${token}`,
5882
+ "content-type": "application/json",
5883
+ "x-client-name": MCP_CLIENT_NAME
5884
+ });
5885
+ const callApi = async (path) => {
5886
+ const token = await getAuthToken();
5887
+ const adminBase = adminApiBaseUrl();
5888
+ const url = `${adminBase}${path}`;
5889
+ let res;
5890
+ try {
5891
+ res = await fetch(url, { headers: authHeaders(token) });
5892
+ } catch (err) {
5893
+ return errorContent(
5894
+ `Network error calling ${url}: ${err.message}. Check ADMIN_LABS_API_BASE_URL env var (currently "${adminBase}").`
5895
+ );
5896
+ }
5897
+ if (!res.ok) {
5898
+ return errorContent(`API error: ${res.status} ${res.statusText} (${url})`);
5899
+ }
5900
+ const data = await res.json();
5901
+ return textContent(JSON.stringify(data, null, 2));
5902
+ };
5903
+ const withAuth = async (fn) => {
5904
+ try {
5905
+ return await fn();
5906
+ } catch (err) {
5907
+ const e = err;
5908
+ if (e.message.includes("fetch")) {
5909
+ return errorContent(
5910
+ `MCP auth flow failed: ${e.message}. Check MCP_LABS_API_BASE_URL env var (currently "${mcpApiBaseUrl()}"). To re-auth from scratch: rm ${MCP_AUTH_FILE}`
5911
+ );
5912
+ }
5913
+ return errorContent(e.message);
5914
+ }
5915
+ };
5916
+ const registrySkills = listSkills("registry");
5917
+ for (const skill of registrySkills) {
5918
+ server.registerResource(
5919
+ skill.id,
5920
+ `sdk://${skill.id}`,
5921
+ { description: skill.description, mimeType: "text/markdown" },
5922
+ async (uri) => ({
5923
+ contents: [{ uri: uri.href, text: getSkillBody(skill), mimeType: "text/markdown" }]
5924
+ })
5925
+ );
5926
+ }
5927
+ const ctx = { withAuth, callApi };
5928
+ for (const register of toolRegistrars) {
5929
+ register(server, ctx);
5930
+ }
5896
5931
  return server;
5897
5932
  };
5898
5933
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/mcp-app-extension",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mcp-app-extension": "./dist/index.js"