@unifiedmemory/cli 1.3.15 → 1.3.17
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/.env.example +7 -7
- package/commands/init.js +0 -31
- package/lib/config.js +5 -3
- package/lib/mcp-proxy.js +3 -1
- package/lib/mcp-server.js +7 -8
- package/package.json +1 -1
- package/tests/unit/config.test.js +10 -5
- package/tests/unit/mcp-proxy.test.js +13 -2
package/.env.example
CHANGED
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
# ============================================
|
|
18
18
|
# Clerk OAuth Configuration
|
|
19
19
|
# ============================================
|
|
20
|
-
# Production defaults are included in the CLI
|
|
21
|
-
#
|
|
22
|
-
# CLERK_CLIENT_ID=
|
|
23
|
-
# CLERK_DOMAIN=
|
|
20
|
+
# Production defaults are included in the CLI.
|
|
21
|
+
# For local development, uncomment and use dev Clerk values:
|
|
22
|
+
# CLERK_CLIENT_ID=nULlnomaKB9rRGP2
|
|
23
|
+
# CLERK_DOMAIN=clear-caiman-45.clerk.accounts.dev
|
|
24
24
|
|
|
25
25
|
# ============================================
|
|
26
26
|
# API Configuration
|
|
27
27
|
# ============================================
|
|
28
|
-
# Production default is included in the CLI
|
|
29
|
-
#
|
|
30
|
-
# API_ENDPOINT=https://
|
|
28
|
+
# Production default is included in the CLI.
|
|
29
|
+
# For local development, uncomment and use dev gateway:
|
|
30
|
+
# API_ENDPOINT=https://rose-asp-main-1c0b114.d2.zuplo.dev
|
|
31
31
|
|
|
32
32
|
# ============================================
|
|
33
33
|
# OAuth Flow Configuration
|
package/commands/init.js
CHANGED
|
@@ -141,24 +141,6 @@ export async function init(options = {}) {
|
|
|
141
141
|
console.log(' 3. Run `um status` to verify configuration\n');
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
/**
|
|
145
|
-
* Validate that we have a real organization context (not fallback to user_id)
|
|
146
|
-
* @param {Object} authData - Auth data with user_id and org_id
|
|
147
|
-
* @returns {Object} { isValid, isPersonalContext, message }
|
|
148
|
-
*/
|
|
149
|
-
function validateOrganizationContext(authData) {
|
|
150
|
-
const isPersonalContext = authData.org_id === authData.user_id;
|
|
151
|
-
|
|
152
|
-
if (isPersonalContext) {
|
|
153
|
-
return {
|
|
154
|
-
isValid: false,
|
|
155
|
-
isPersonalContext: true,
|
|
156
|
-
message: 'You are in personal account context. Organization-scoped operations require an organization.',
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return { isValid: true, isPersonalContext: false, message: null };
|
|
161
|
-
}
|
|
162
144
|
|
|
163
145
|
async function ensureAuthenticated(options) {
|
|
164
146
|
// Try to load and refresh token if expired
|
|
@@ -213,19 +195,6 @@ async function ensureAuthenticated(options) {
|
|
|
213
195
|
async function selectOrCreateProject(authData, options) {
|
|
214
196
|
const apiUrl = authData.api_url || 'https://rose-asp-main-1c0b114.d2.zuplo.dev';
|
|
215
197
|
|
|
216
|
-
// Validate organization context BEFORE making API calls
|
|
217
|
-
const contextValidation = validateOrganizationContext(authData);
|
|
218
|
-
|
|
219
|
-
if (!contextValidation.isValid && contextValidation.isPersonalContext) {
|
|
220
|
-
console.log(chalk.yellow('\n⚠️ ' + contextValidation.message));
|
|
221
|
-
console.log(chalk.gray('\nOrganization-scoped projects require an organization context.'));
|
|
222
|
-
console.log(chalk.cyan('\nRecommended actions:'));
|
|
223
|
-
console.log(chalk.cyan(' 1. Switch to an organization: um org switch'));
|
|
224
|
-
console.log(chalk.cyan(' 2. Create an organization at: https://unifiedmemory.ai'));
|
|
225
|
-
console.log(chalk.cyan(' 3. Re-run: um init'));
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
198
|
// Fetch existing projects with enhanced error handling
|
|
230
199
|
console.log(chalk.gray('Fetching projects...'));
|
|
231
200
|
const result = await fetchProjects(authData, apiUrl);
|
package/lib/config.js
CHANGED
|
@@ -10,12 +10,14 @@ dotenvConfig({ path: join(__dirname, '..', '.env') });
|
|
|
10
10
|
|
|
11
11
|
export const config = {
|
|
12
12
|
// Clerk OAuth configuration (production defaults, can be overridden via env vars)
|
|
13
|
-
|
|
13
|
+
// TODO: Replace clerkClientId with production Clerk OAuth client ID once created
|
|
14
|
+
clerkClientId: process.env.CLERK_CLIENT_ID || 'bNpTWw0hP3V7ueqN',
|
|
14
15
|
clerkClientSecret: process.env.CLERK_CLIENT_SECRET, // Optional for PKCE flow
|
|
15
|
-
clerkDomain: process.env.CLERK_DOMAIN || '
|
|
16
|
+
clerkDomain: process.env.CLERK_DOMAIN || 'clerk.unifiedmemory.ai',
|
|
16
17
|
|
|
17
18
|
// API configuration (production default, can be overridden via env var)
|
|
18
|
-
|
|
19
|
+
// TODO: Replace with production Zuplo gateway URL once deployed
|
|
20
|
+
apiEndpoint: process.env.API_ENDPOINT || 'https://api.unifiedmemory.ai',
|
|
19
21
|
|
|
20
22
|
// OAuth flow configuration (localhost defaults for callback server)
|
|
21
23
|
redirectUri: process.env.REDIRECT_URI || 'http://localhost:3333/callback',
|
package/lib/mcp-proxy.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* MCP Proxy Client - Forwards MCP requests to the gateway HTTP endpoint
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import { config } from './config.js';
|
|
6
|
+
|
|
7
|
+
const GATEWAY_MCP_URL = `${config.apiEndpoint}/mcp`;
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Transform tool schema to hide context parameters (org, proj, user)
|
package/lib/mcp-server.js
CHANGED
|
@@ -218,15 +218,14 @@ function buildAuthHeaders(authData, projectContext) {
|
|
|
218
218
|
headers['X-User-Id'] = authData.decoded.sub;
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
// Add X-Org-Id - check JWT claims first, fall back to selectedOrgId
|
|
222
|
-
const orgId = authData.decoded?.o?.o_id || authData.selectedOrgId;
|
|
221
|
+
// Add X-Org-Id - check JWT claims first, fall back to selectedOrgId, then userId (personal account)
|
|
222
|
+
const orgId = authData.decoded?.o?.o_id || authData.selectedOrgId || authData.decoded?.sub;
|
|
223
223
|
const orgName = authData.decoded?.o?.o_name || authData.selectedOrgName;
|
|
224
224
|
|
|
225
225
|
if (orgId) {
|
|
226
226
|
headers['X-Org-Id'] = orgId;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
console.error(`✓ Using personal account context`);
|
|
227
|
+
const isPersonal = orgId === authData.decoded?.sub;
|
|
228
|
+
console.error(`✓ ${isPersonal ? 'Personal account' : 'Organization'}: ${orgName || orgId}`);
|
|
230
229
|
}
|
|
231
230
|
|
|
232
231
|
// Add project context if available
|
|
@@ -249,9 +248,9 @@ async function loadFreshAuth(projectContext) {
|
|
|
249
248
|
const authData = await loadAndRefreshToken();
|
|
250
249
|
const authHeaders = buildAuthHeaders(authData, projectContext);
|
|
251
250
|
|
|
252
|
-
// Build auth context - check JWT claims first, fall back to selectedOrgId
|
|
251
|
+
// Build auth context - check JWT claims first, fall back to selectedOrgId, then userId (personal account)
|
|
253
252
|
const jwtOrgId = authData.decoded?.o?.o_id;
|
|
254
|
-
const orgId = jwtOrgId || authData.selectedOrgId;
|
|
253
|
+
const orgId = jwtOrgId || authData.selectedOrgId || authData.decoded?.sub;
|
|
255
254
|
const orgName = authData.decoded?.o?.o_name || authData.selectedOrgName;
|
|
256
255
|
const orgRole = authData.decoded?.o?.o_role;
|
|
257
256
|
|
|
@@ -259,7 +258,7 @@ async function loadFreshAuth(projectContext) {
|
|
|
259
258
|
decoded: authData.decoded,
|
|
260
259
|
orgContext: orgId ? {
|
|
261
260
|
id: orgId,
|
|
262
|
-
name: orgName,
|
|
261
|
+
name: orgName || 'Personal',
|
|
263
262
|
role: orgRole,
|
|
264
263
|
} : null
|
|
265
264
|
};
|
package/package.json
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
10
10
|
|
|
11
|
+
// Mock dotenv so the local .env file doesn't interfere with unit tests
|
|
12
|
+
vi.mock('dotenv', () => ({
|
|
13
|
+
config: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
|
|
11
16
|
describe('config', () => {
|
|
12
17
|
// Store original env vars
|
|
13
18
|
const originalEnv = { ...process.env };
|
|
@@ -35,9 +40,9 @@ describe('config', () => {
|
|
|
35
40
|
// Fresh import to get defaults
|
|
36
41
|
const { config } = await import('../../lib/config.js');
|
|
37
42
|
|
|
38
|
-
expect(config.clerkClientId).toBe('
|
|
39
|
-
expect(config.clerkDomain).toBe('
|
|
40
|
-
expect(config.apiEndpoint).toBe('https://
|
|
43
|
+
expect(config.clerkClientId).toBe('bNpTWw0hP3V7ueqN');
|
|
44
|
+
expect(config.clerkDomain).toBe('clerk.unifiedmemory.ai');
|
|
45
|
+
expect(config.apiEndpoint).toBe('https://api.unifiedmemory.ai');
|
|
41
46
|
expect(config.redirectUri).toBe('http://localhost:3333/callback');
|
|
42
47
|
expect(config.port).toBe(3333);
|
|
43
48
|
});
|
|
@@ -123,7 +128,7 @@ describe('config', () => {
|
|
|
123
128
|
const { config, validateConfig } = await import('../../lib/config.js');
|
|
124
129
|
|
|
125
130
|
// Empty string is falsy, so it falls back to default
|
|
126
|
-
expect(config.clerkDomain).toBe('
|
|
131
|
+
expect(config.clerkDomain).toBe('clerk.unifiedmemory.ai');
|
|
127
132
|
expect(validateConfig()).toBe(true);
|
|
128
133
|
});
|
|
129
134
|
|
|
@@ -142,7 +147,7 @@ describe('config', () => {
|
|
|
142
147
|
const { config, validateConfig } = await import('../../lib/config.js');
|
|
143
148
|
|
|
144
149
|
// Empty string is falsy, so it falls back to default
|
|
145
|
-
expect(config.clerkClientId).toBe('
|
|
150
|
+
expect(config.clerkClientId).toBe('bNpTWw0hP3V7ueqN');
|
|
146
151
|
expect(validateConfig()).toBe(true);
|
|
147
152
|
});
|
|
148
153
|
|
|
@@ -8,11 +8,17 @@
|
|
|
8
8
|
* are tested indirectly through the exported functions.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { describe, it, expect, vi, beforeAll, afterAll, afterEach } from 'vitest';
|
|
11
|
+
import { describe, it, expect, vi, beforeAll, beforeEach, afterAll, afterEach } from 'vitest';
|
|
12
12
|
import { setupServer } from 'msw/node';
|
|
13
13
|
import { http, HttpResponse } from 'msw';
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// Mock dotenv so the local .env file doesn't interfere with unit tests
|
|
16
|
+
vi.mock('dotenv', () => ({
|
|
17
|
+
config: vi.fn()
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Must match the fallback default in lib/config.js (used when no .env is present, e.g. CI)
|
|
21
|
+
const GATEWAY_MCP_URL = 'https://api.unifiedmemory.ai/mcp';
|
|
16
22
|
|
|
17
23
|
// Sample tool with pathParams and headers that should be transformed
|
|
18
24
|
const sampleToolWithContext = {
|
|
@@ -59,6 +65,11 @@ const sampleToolNoContext = {
|
|
|
59
65
|
const server = setupServer();
|
|
60
66
|
|
|
61
67
|
beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' }));
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.resetModules();
|
|
70
|
+
// Clear API_ENDPOINT so config.js uses the hardcoded default matching GATEWAY_MCP_URL above
|
|
71
|
+
delete process.env.API_ENDPOINT;
|
|
72
|
+
});
|
|
62
73
|
afterEach(() => server.resetHandlers());
|
|
63
74
|
afterAll(() => server.close());
|
|
64
75
|
|