backend-manager 5.6.4 → 5.7.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/CHANGELOG.md +35 -0
- package/CLAUDE.md +4 -3
- package/PROGRESS.md +34 -0
- package/docs/ai-library.md +62 -11
- package/docs/cdp-debugging.md +44 -0
- package/docs/cli-output.md +22 -10
- package/docs/mcp.md +166 -43
- package/package.json +1 -1
- package/plans/mcp2.md +247 -0
- package/src/cli/commands/mcp.js +8 -2
- package/src/cli/commands/serve.js +155 -29
- package/src/cli/commands/setup-tests/base-test.js +8 -0
- package/src/cli/commands/setup-tests/firebase-auth.js +26 -0
- package/src/cli/commands/setup-tests/firebase-cli.js +9 -13
- package/src/cli/commands/setup-tests/index.js +4 -0
- package/src/cli/commands/setup-tests/java-installed.js +26 -0
- package/src/cli/commands/setup.js +2 -1
- package/src/cli/commands/test.js +8 -0
- package/src/cli/index.js +14 -0
- package/src/cli/utils/ui.js +27 -5
- package/src/manager/index.js +8 -3
- package/src/manager/libraries/ai/index.js +45 -1
- package/src/manager/libraries/ai/providers/anthropic-format.js +234 -0
- package/src/manager/libraries/ai/providers/anthropic.js +28 -49
- package/src/manager/libraries/ai/providers/claude-code.js +21 -47
- package/src/manager/libraries/ai/providers/openai.js +154 -19
- package/src/manager/libraries/ai/providers/test.js +242 -0
- package/src/manager/libraries/email/data/disposable-domains.json +465 -0
- package/src/mcp/client.js +48 -13
- package/src/mcp/handler.js +222 -69
- package/src/mcp/index.js +48 -18
- package/src/mcp/tools.js +150 -0
- package/src/mcp/utils.js +108 -0
- package/src/test/fixtures/firebase-project/firebase.json +1 -1
- package/test/ai/tools-live.js +170 -0
- package/test/helpers/ai-test-provider.js +202 -0
- package/test/helpers/ai-tools-format.js +350 -0
- package/test/mcp/discovery.js +53 -0
- package/test/mcp/oauth.js +161 -0
- package/test/mcp/protocol.js +268 -0
- package/test/mcp/roles.js +168 -0
- package/test/mcp/utils.js +245 -0
- package/.claude/settings.local.json +0 -12
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test: MCP utility functions
|
|
3
|
+
* Tests resolveAuthInfo, filterToolsByRole, loadConsumerTools, buildToolMap
|
|
4
|
+
*
|
|
5
|
+
* Run: npx mgr test bem:mcp/utils
|
|
6
|
+
*/
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
description: 'MCP utility functions',
|
|
11
|
+
type: 'group',
|
|
12
|
+
|
|
13
|
+
tests: [
|
|
14
|
+
// --- resolveAuthInfo ---
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
name: 'resolveAuthInfo: admin key returns admin role',
|
|
18
|
+
async run({ assert }) {
|
|
19
|
+
const { resolveAuthInfo } = require('../../src/mcp/utils.js');
|
|
20
|
+
const saved = process.env.BACKEND_MANAGER_KEY;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
process.env.BACKEND_MANAGER_KEY = 'test-admin-key';
|
|
24
|
+
const result = resolveAuthInfo('test-admin-key');
|
|
25
|
+
|
|
26
|
+
assert.equal(result.role, 'admin', 'Should be admin');
|
|
27
|
+
assert.equal(result.authType, 'adminKey', 'Should be adminKey type');
|
|
28
|
+
assert.equal(result.token, 'test-admin-key', 'Token should match');
|
|
29
|
+
} finally {
|
|
30
|
+
process.env.BACKEND_MANAGER_KEY = saved;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
name: 'resolveAuthInfo: non-admin token returns user role',
|
|
37
|
+
async run({ assert }) {
|
|
38
|
+
const { resolveAuthInfo } = require('../../src/mcp/utils.js');
|
|
39
|
+
const result = resolveAuthInfo('some-user-api-key');
|
|
40
|
+
|
|
41
|
+
assert.equal(result.role, 'user', 'Should be user');
|
|
42
|
+
assert.equal(result.authType, 'userToken', 'Should be userToken type');
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
name: 'resolveAuthInfo: empty token returns public role',
|
|
48
|
+
async run({ assert }) {
|
|
49
|
+
const { resolveAuthInfo } = require('../../src/mcp/utils.js');
|
|
50
|
+
const result = resolveAuthInfo('');
|
|
51
|
+
|
|
52
|
+
assert.equal(result.role, 'public', 'Should be public');
|
|
53
|
+
assert.equal(result.authType, 'none', 'Should be none type');
|
|
54
|
+
assert.equal(result.token, '', 'Token should be empty');
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
{
|
|
59
|
+
name: 'resolveAuthInfo: null/undefined token returns public role',
|
|
60
|
+
async run({ assert }) {
|
|
61
|
+
const { resolveAuthInfo } = require('../../src/mcp/utils.js');
|
|
62
|
+
|
|
63
|
+
assert.equal(resolveAuthInfo(null).role, 'public', 'null should be public');
|
|
64
|
+
assert.equal(resolveAuthInfo(undefined).role, 'public', 'undefined should be public');
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
name: 'resolveAuthInfo: returns public when BACKEND_MANAGER_KEY is not set',
|
|
70
|
+
async run({ assert }) {
|
|
71
|
+
const { resolveAuthInfo } = require('../../src/mcp/utils.js');
|
|
72
|
+
const saved = process.env.BACKEND_MANAGER_KEY;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
delete process.env.BACKEND_MANAGER_KEY;
|
|
76
|
+
const result = resolveAuthInfo('any-token');
|
|
77
|
+
|
|
78
|
+
assert.equal(result.role, 'user', 'Non-empty token with no config key should be user');
|
|
79
|
+
} finally {
|
|
80
|
+
process.env.BACKEND_MANAGER_KEY = saved;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// --- filterToolsByRole ---
|
|
86
|
+
|
|
87
|
+
{
|
|
88
|
+
name: 'filterToolsByRole: admin sees all roles',
|
|
89
|
+
async run({ assert }) {
|
|
90
|
+
const { filterToolsByRole } = require('../../src/mcp/utils.js');
|
|
91
|
+
const tools = [
|
|
92
|
+
{ name: 'a', role: 'admin' },
|
|
93
|
+
{ name: 'b', role: 'user' },
|
|
94
|
+
{ name: 'c', role: 'public' },
|
|
95
|
+
];
|
|
96
|
+
const result = filterToolsByRole(tools, 'admin');
|
|
97
|
+
|
|
98
|
+
assert.equal(result.length, 3, 'Admin should see all 3');
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
{
|
|
103
|
+
name: 'filterToolsByRole: user sees user + public only',
|
|
104
|
+
async run({ assert }) {
|
|
105
|
+
const { filterToolsByRole } = require('../../src/mcp/utils.js');
|
|
106
|
+
const tools = [
|
|
107
|
+
{ name: 'a', role: 'admin' },
|
|
108
|
+
{ name: 'b', role: 'user' },
|
|
109
|
+
{ name: 'c', role: 'public' },
|
|
110
|
+
];
|
|
111
|
+
const result = filterToolsByRole(tools, 'user');
|
|
112
|
+
|
|
113
|
+
assert.equal(result.length, 2, 'User should see 2');
|
|
114
|
+
assert.ok(result.some((t) => t.name === 'b'), 'Should include user tool');
|
|
115
|
+
assert.ok(result.some((t) => t.name === 'c'), 'Should include public tool');
|
|
116
|
+
assert.ok(!result.some((t) => t.name === 'a'), 'Should exclude admin tool');
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
{
|
|
121
|
+
name: 'filterToolsByRole: public sees public only',
|
|
122
|
+
async run({ assert }) {
|
|
123
|
+
const { filterToolsByRole } = require('../../src/mcp/utils.js');
|
|
124
|
+
const tools = [
|
|
125
|
+
{ name: 'a', role: 'admin' },
|
|
126
|
+
{ name: 'b', role: 'user' },
|
|
127
|
+
{ name: 'c', role: 'public' },
|
|
128
|
+
];
|
|
129
|
+
const result = filterToolsByRole(tools, 'public');
|
|
130
|
+
|
|
131
|
+
assert.equal(result.length, 1, 'Public should see 1');
|
|
132
|
+
assert.equal(result[0].name, 'c', 'Should be the public tool');
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
name: 'filterToolsByRole: tools without role default to admin',
|
|
138
|
+
async run({ assert }) {
|
|
139
|
+
const { filterToolsByRole } = require('../../src/mcp/utils.js');
|
|
140
|
+
const tools = [{ name: 'no-role' }];
|
|
141
|
+
|
|
142
|
+
assert.equal(filterToolsByRole(tools, 'admin').length, 1, 'Admin should see role-less tool');
|
|
143
|
+
assert.equal(filterToolsByRole(tools, 'user').length, 0, 'User should not see role-less tool');
|
|
144
|
+
assert.equal(filterToolsByRole(tools, 'public').length, 0, 'Public should not see role-less tool');
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
name: 'filterToolsByRole: unknown role treated as public',
|
|
150
|
+
async run({ assert }) {
|
|
151
|
+
const { filterToolsByRole } = require('../../src/mcp/utils.js');
|
|
152
|
+
const tools = [
|
|
153
|
+
{ name: 'a', role: 'admin' },
|
|
154
|
+
{ name: 'b', role: 'user' },
|
|
155
|
+
{ name: 'c', role: 'public' },
|
|
156
|
+
];
|
|
157
|
+
const result = filterToolsByRole(tools, 'garbage');
|
|
158
|
+
|
|
159
|
+
assert.equal(result.length, 1, 'Unknown role should see public only');
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// --- loadConsumerTools ---
|
|
164
|
+
|
|
165
|
+
{
|
|
166
|
+
name: 'loadConsumerTools: returns empty array when no cwd',
|
|
167
|
+
async run({ assert }) {
|
|
168
|
+
const { loadConsumerTools } = require('../../src/mcp/utils.js');
|
|
169
|
+
|
|
170
|
+
assert.equal(loadConsumerTools(null).length, 0, 'null cwd');
|
|
171
|
+
assert.equal(loadConsumerTools('').length, 0, 'empty cwd');
|
|
172
|
+
assert.equal(loadConsumerTools(undefined).length, 0, 'undefined cwd');
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
{
|
|
177
|
+
name: 'loadConsumerTools: returns empty array for non-existent directory',
|
|
178
|
+
async run({ assert }) {
|
|
179
|
+
const { loadConsumerTools } = require('../../src/mcp/utils.js');
|
|
180
|
+
const result = loadConsumerTools('/tmp/does-not-exist-12345');
|
|
181
|
+
|
|
182
|
+
assert.equal(result.length, 0, 'Should return empty array');
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// --- buildToolMap ---
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
name: 'buildToolMap: consumer tools override built-ins with same name',
|
|
190
|
+
async run({ assert }) {
|
|
191
|
+
const { buildToolMap } = require('../../src/mcp/utils.js');
|
|
192
|
+
const builtin = [{ name: 'tool_a', description: 'original' }];
|
|
193
|
+
const consumer = [{ name: 'tool_a', description: 'override', _consumer: true }];
|
|
194
|
+
|
|
195
|
+
const map = buildToolMap(builtin, consumer);
|
|
196
|
+
assert.equal(map.get('tool_a').description, 'override', 'Consumer should override');
|
|
197
|
+
assert.equal(map.get('tool_a')._consumer, true, 'Should be marked as consumer');
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
{
|
|
202
|
+
name: 'buildToolMap: merges non-overlapping tools',
|
|
203
|
+
async run({ assert }) {
|
|
204
|
+
const { buildToolMap } = require('../../src/mcp/utils.js');
|
|
205
|
+
const builtin = [{ name: 'a' }, { name: 'b' }];
|
|
206
|
+
const consumer = [{ name: 'c' }];
|
|
207
|
+
|
|
208
|
+
const map = buildToolMap(builtin, consumer);
|
|
209
|
+
assert.equal(map.size, 3, 'Should have 3 tools total');
|
|
210
|
+
assert.ok(map.has('a'), 'Should have a');
|
|
211
|
+
assert.ok(map.has('b'), 'Should have b');
|
|
212
|
+
assert.ok(map.has('c'), 'Should have c');
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
// --- Real tools verification ---
|
|
217
|
+
|
|
218
|
+
{
|
|
219
|
+
name: 'all 19 built-in tools have a role assigned',
|
|
220
|
+
async run({ assert }) {
|
|
221
|
+
const tools = require('../../src/mcp/tools.js');
|
|
222
|
+
|
|
223
|
+
assert.equal(tools.length, 25, 'Should have 25 tools');
|
|
224
|
+
|
|
225
|
+
const missing = tools.filter((t) => !t.role);
|
|
226
|
+
assert.equal(missing.length, 0, `All tools should have roles, missing: ${missing.map((t) => t.name).join(', ')}`);
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
{
|
|
231
|
+
name: 'role distribution matches expected counts',
|
|
232
|
+
async run({ assert }) {
|
|
233
|
+
const tools = require('../../src/mcp/tools.js');
|
|
234
|
+
|
|
235
|
+
const admin = tools.filter((t) => t.role === 'admin');
|
|
236
|
+
const user = tools.filter((t) => t.role === 'user');
|
|
237
|
+
const pub = tools.filter((t) => t.role === 'public');
|
|
238
|
+
|
|
239
|
+
assert.equal(admin.length, 22, `Should have 22 admin tools, got ${admin.length}`);
|
|
240
|
+
assert.equal(user.length, 2, `Should have 2 user tools, got ${user.length}`);
|
|
241
|
+
assert.equal(pub.length, 1, `Should have 1 public tool, got ${pub.length}`);
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(grep -B2 -A20 \"Generate a newsletter preview\" /tmp/bem-test-run-3.log | head -50)",
|
|
5
|
-
"Bash(sed 's/\\\\x1b\\\\[[0-9;]*m//g')",
|
|
6
|
-
"Read(//tmp/**)",
|
|
7
|
-
"Bash(TEST_EXTENDED_MODE=1 npx mgr test 2>&1 | sed 's/\\\\x1b\\\\[[0-9;]*m//g' > /tmp/bem-run-6.log; grep -E \"passing|failing|skipped\" /tmp/bem-run-6.log | tail -5)",
|
|
8
|
-
"Bash(awk -F'`' '{print $2}')",
|
|
9
|
-
"Bash(TEST_EXTENDED_MODE=1 npx mgr test 2>&1 | sed 's/\\\\x1b\\\\[[0-9;]*m//g' > /tmp/bem-cleanup-run2.log; grep -E \"\\(passing|failing|skipped\\)\" /tmp/bem-cleanup-run2.log | tail -5)"
|
|
10
|
-
]
|
|
11
|
-
}
|
|
12
|
-
}
|