@stackable-labs/mcp-app-extension 0.4.0 → 0.4.1

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 ADDED
@@ -0,0 +1,93 @@
1
+ # `@stackable-labs/mcp-app-extension`
2
+
3
+ MCP server exposing Stackable Labs extension-platform tools to AI agents (Claude Code, etc.). Supports both **stdio** (local process, SDK self-auth via OAuth) and **streamable HTTP** (deployed Lambda, Claude Code native OAuth) transports.
4
+
5
+ ## Transports
6
+
7
+ | Transport | Home | Auth flow | Best for |
8
+ |-----------|------|-----------|----------|
9
+ | **HTTP** (streamable) | Deployed at `api-{stage}-use1.stackablelabs.io/mcp/app-extension` | Claude Code native OAuth — SDK discovers `.well-known/oauth-authorization-server` via `WWW-Authenticate` header, stores token in keychain | Most users. Simplest to configure. |
10
+ | **stdio** | Local process via `pnpm dlx @stackable-labs/mcp-app-extension@latest` | Package-level OAuth flow — spins up localhost callback listener, caches token to `~/.stackable/mcp-auth.json` | Local dev, agent orchestration outside a Claude Code keychain |
11
+
12
+ Both transports expose the same tool surface: `list_apps`, `list_extensions`, `get_extension`, `list_instances`, plus SDK-only tools (`list_skills`, `lookup_skill`, `validate_manifest`, `validate_permissions`).
13
+
14
+ ## Claude Code `.mcp.json` config
15
+
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
+
18
+ **Dev (against deployed `api-dev-use1.stackablelabs.io`):**
19
+
20
+ ```json
21
+ {
22
+ "mcpServers": {
23
+ "stackable-dev-http": {
24
+ "type": "http",
25
+ "url": "https://api-dev-use1.stackablelabs.io/mcp/app-extension"
26
+ },
27
+ "stackable-dev-stdio": {
28
+ "type": "stdio",
29
+ "command": "pnpm",
30
+ "args": [
31
+ "--config.dlx-cache-max-age=0",
32
+ "dlx",
33
+ "@stackable-labs/mcp-app-extension@latest"
34
+ ],
35
+ "env": {
36
+ "MCP_API_BASE_URL": "https://api-dev-use1.stackablelabs.io/mcp",
37
+ "ADMIN_API_BASE_URL": "https://api-dev-use1.stackablelabs.io/admin"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ **Prod (defaults, no env vars needed):**
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "stackable-http": {
50
+ "type": "http",
51
+ "url": "https://api-use1.stackablelabs.io/mcp/app-extension"
52
+ },
53
+ "stackable-stdio": {
54
+ "type": "stdio",
55
+ "command": "pnpm",
56
+ "args": ["--config.dlx-cache-max-age=0", "dlx", "@stackable-labs/mcp-app-extension@latest"]
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Env vars (stdio only)
63
+
64
+ Both optional — defaults point at prod. Override per-stage as needed.
65
+
66
+ | Env var | Default | Purpose |
67
+ |---------|---------|---------|
68
+ | `MCP_API_BASE_URL` | `https://api-use1.stackablelabs.io/mcp` | 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.). |
70
+
71
+ HTTP transport reads URLs from the deployed Lambda's env (set via CDK + `packages/infra/cdk/.env.{stage}` — not client-controlled).
72
+
73
+ ## Auth isolation
74
+
75
+ Stdio transport stores its token at `~/.stackable/mcp-auth.json`. CLI (`@stackable-labs/cli-app-extension`) stores its token at `~/.stackable/auth.json`. The two are independent — authenticating via stdio MCP does not affect CLI auth and vice versa. HTTP transport stores its token in the Claude Code keychain, completely separate from both file-based caches.
76
+
77
+ ## Development
78
+
79
+ ```bash
80
+ pnpm --filter @stackable-labs/mcp-app-extension build # tsup bundle
81
+ pnpm --filter @stackable-labs/mcp-app-extension test # vitest
82
+ pnpm --filter @stackable-labs/mcp-app-extension typecheck
83
+ ```
84
+
85
+ To test a local build via stdio, point the `.mcp.json` `command`/`args` at the built dist:
86
+
87
+ ```json
88
+ {
89
+ "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
+ }
93
+ ```
package/dist/index.js CHANGED
@@ -3759,7 +3759,7 @@ var package_default = {
3759
3759
  // src/server.ts
3760
3760
  var MCP_CLIENT_NAME = STANDALONE_CLIENT.MCP;
3761
3761
  var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
3762
- var DEFAULT_MCP_DISCOVERY_URL = "https://api-use1.stackablelabs.io/mcp/.well-known/oauth-authorization-server";
3762
+ var DEFAULT_MCP_API_BASE_URL = "https://api-use1.stackablelabs.io/mcp";
3763
3763
  var textContent = (text) => ({ content: [{ type: "text", text }] });
3764
3764
  var errorContent = (text) => ({ content: [{ type: "text", text }], isError: true });
3765
3765
  var createMcpServer = (options = {}) => {
@@ -3776,8 +3776,8 @@ var createMcpServer = (options = {}) => {
3776
3776
  } catch {
3777
3777
  }
3778
3778
  const { performOAuthFlow } = await import('./auth-AJDGAQSE.js');
3779
- const discoveryUrl = process.env.MCP_DISCOVERY_URL ?? DEFAULT_MCP_DISCOVERY_URL;
3780
- return performOAuthFlow(discoveryUrl);
3779
+ const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL;
3780
+ return performOAuthFlow(`${MCP_API_BASE_URL}/.well-known/oauth-authorization-server`);
3781
3781
  };
3782
3782
  const authHeaders = (token) => ({
3783
3783
  authorization: `Bearer ${token}`,
@@ -3786,10 +3786,18 @@ var createMcpServer = (options = {}) => {
3786
3786
  });
3787
3787
  const callApi = async (path) => {
3788
3788
  const token = await getAuthToken();
3789
- const baseUrl = process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
3790
- const res = await fetch(`${baseUrl}${path}`, { headers: authHeaders(token) });
3789
+ const ADMIN_API_BASE_URL = process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
3790
+ const url = `${ADMIN_API_BASE_URL}${path}`;
3791
+ let res;
3792
+ try {
3793
+ res = await fetch(url, { headers: authHeaders(token) });
3794
+ } catch (err) {
3795
+ return errorContent(
3796
+ `Network error calling ${url}: ${err.message}. Check ADMIN_API_BASE_URL env var (currently "${ADMIN_API_BASE_URL}").`
3797
+ );
3798
+ }
3791
3799
  if (!res.ok) {
3792
- return errorContent(`API error: ${res.status} ${res.statusText}`);
3800
+ return errorContent(`API error: ${res.status} ${res.statusText} (${url})`);
3793
3801
  }
3794
3802
  const data = await res.json();
3795
3803
  return textContent(JSON.stringify(data, null, 2));
@@ -3798,7 +3806,14 @@ var createMcpServer = (options = {}) => {
3798
3806
  try {
3799
3807
  return await fn();
3800
3808
  } catch (err) {
3801
- return errorContent(err.message);
3809
+ const e = err;
3810
+ if (e.message.includes("fetch")) {
3811
+ const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL;
3812
+ return errorContent(
3813
+ `MCP auth flow failed: ${e.message}. Check MCP_API_BASE_URL env var (currently "${MCP_API_BASE_URL}"). To re-auth from scratch: rm ~/.stackable/mcp-auth.json`
3814
+ );
3815
+ }
3816
+ return errorContent(e.message);
3802
3817
  }
3803
3818
  };
3804
3819
  const registrySkills = listSkills("registry");
package/dist/server.js CHANGED
@@ -3757,7 +3757,7 @@ var package_default = {
3757
3757
  // src/server.ts
3758
3758
  var MCP_CLIENT_NAME = STANDALONE_CLIENT.MCP;
3759
3759
  var DEFAULT_ADMIN_API_URL = "https://api-use1.stackablelabs.io/admin";
3760
- var DEFAULT_MCP_DISCOVERY_URL = "https://api-use1.stackablelabs.io/mcp/.well-known/oauth-authorization-server";
3760
+ var DEFAULT_MCP_API_BASE_URL = "https://api-use1.stackablelabs.io/mcp";
3761
3761
  var textContent = (text) => ({ content: [{ type: "text", text }] });
3762
3762
  var errorContent = (text) => ({ content: [{ type: "text", text }], isError: true });
3763
3763
  var createMcpServer = (options = {}) => {
@@ -3774,8 +3774,8 @@ var createMcpServer = (options = {}) => {
3774
3774
  } catch {
3775
3775
  }
3776
3776
  const { performOAuthFlow } = await import('./auth-JRYNP3PL.js');
3777
- const discoveryUrl = process.env.MCP_DISCOVERY_URL ?? DEFAULT_MCP_DISCOVERY_URL;
3778
- return performOAuthFlow(discoveryUrl);
3777
+ const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL;
3778
+ return performOAuthFlow(`${MCP_API_BASE_URL}/.well-known/oauth-authorization-server`);
3779
3779
  };
3780
3780
  const authHeaders = (token) => ({
3781
3781
  authorization: `Bearer ${token}`,
@@ -3784,10 +3784,18 @@ var createMcpServer = (options = {}) => {
3784
3784
  });
3785
3785
  const callApi = async (path) => {
3786
3786
  const token = await getAuthToken();
3787
- const baseUrl = process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
3788
- const res = await fetch(`${baseUrl}${path}`, { headers: authHeaders(token) });
3787
+ const ADMIN_API_BASE_URL = process.env.ADMIN_API_BASE_URL ?? DEFAULT_ADMIN_API_URL;
3788
+ const url = `${ADMIN_API_BASE_URL}${path}`;
3789
+ let res;
3790
+ try {
3791
+ res = await fetch(url, { headers: authHeaders(token) });
3792
+ } catch (err) {
3793
+ return errorContent(
3794
+ `Network error calling ${url}: ${err.message}. Check ADMIN_API_BASE_URL env var (currently "${ADMIN_API_BASE_URL}").`
3795
+ );
3796
+ }
3789
3797
  if (!res.ok) {
3790
- return errorContent(`API error: ${res.status} ${res.statusText}`);
3798
+ return errorContent(`API error: ${res.status} ${res.statusText} (${url})`);
3791
3799
  }
3792
3800
  const data = await res.json();
3793
3801
  return textContent(JSON.stringify(data, null, 2));
@@ -3796,7 +3804,14 @@ var createMcpServer = (options = {}) => {
3796
3804
  try {
3797
3805
  return await fn();
3798
3806
  } catch (err) {
3799
- return errorContent(err.message);
3807
+ const e = err;
3808
+ if (e.message.includes("fetch")) {
3809
+ const MCP_API_BASE_URL = process.env.MCP_API_BASE_URL ?? DEFAULT_MCP_API_BASE_URL;
3810
+ return errorContent(
3811
+ `MCP auth flow failed: ${e.message}. Check MCP_API_BASE_URL env var (currently "${MCP_API_BASE_URL}"). To re-auth from scratch: rm ~/.stackable/mcp-auth.json`
3812
+ );
3813
+ }
3814
+ return errorContent(e.message);
3800
3815
  }
3801
3816
  };
3802
3817
  const registrySkills = listSkills("registry");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/mcp-app-extension",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mcp-app-extension": "./dist/index.js"