@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 +93 -0
- package/dist/index.js +22 -7
- package/dist/server.js +22 -7
- package/package.json +1 -1
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
|
|
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
|
|
3780
|
-
return performOAuthFlow(
|
|
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
|
|
3790
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
|
3778
|
-
return performOAuthFlow(
|
|
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
|
|
3788
|
-
const
|
|
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
|
-
|
|
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");
|