sync: update from private repo (f811f38)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-09 14:21:52 +00:00
parent 4601e8d5c3
commit b3747add6b
5 changed files with 88 additions and 690 deletions
+35
View File
@@ -310,6 +310,41 @@ describe('mcp-api', () => {
expect(post.status).toBe(409);
});
it('user can update their own server (edit); blank token preserves the existing one', async () => {
const { app, reg } = makeApp({ currentRole: 'user', userId: 'u1' });
const create = await request(app).post('/api/mcp/user-servers').send({
id: 'mine', name: 'Mine', url: 'http://127.0.0.1:9/mcp',
authKind: 'api_key', staticToken: 'sk-original',
});
expect(create.status).toBe(200);
// Edit: rename + add a custom header, leave the token blank (= keep existing).
const edit = await request(app).post('/api/mcp/user-servers').send({
id: 'mine', name: 'Mine Renamed', url: 'http://127.0.0.1:9/mcp',
authKind: 'api_key', staticToken: '', authHeaderName: 'xc-mcp-token',
});
expect(edit.status).toBe(200);
const full = reg.getDecrypted('mine');
expect(full?.staticToken).toBe('sk-original'); // preserved, not wiped
expect(full?.authHeaderName).toBe('xc-mcp-token'); // updated
expect(reg.listEnabledForUser('u1').find((s) => s.id === 'mine')?.name).toBe('Mine Renamed');
});
it('admin edit of a global api_key server preserves the token when left blank', async () => {
const { app, reg } = makeApp({ currentRole: 'admin' });
await request(app).post('/api/mcp/servers').send({
id: 'g', name: 'G', url: 'http://127.0.0.1:1/mcp',
authKind: 'api_key', staticToken: 'sk-keep',
});
const edit = await request(app).post('/api/mcp/servers').send({
id: 'g', name: 'G2', url: 'http://127.0.0.1:1/mcp',
authKind: 'api_key', staticToken: '',
});
expect(edit.status).toBe(200);
expect(reg.getDecrypted('g')?.staticToken).toBe('sk-keep');
});
it('DELETE /api/mcp/connections returns 400 for api_key global server', async () => {
const { app, reg } = makeApp({ currentRole: 'user', userId: 'u1' });
reg.upsert({
+32 -19
View File
@@ -56,15 +56,22 @@ export function createAdminRouter(deps: McpApiDeps): Router {
return;
}
const authKind = (body.authKind ?? 'oauth') as 'oauth' | 'api_key';
// Editing a global server re-POSTs the same id. Preserve write-only secrets
// when left blank, and keep the original authKind (the form locks it on edit).
const existing = deps.registry.getDecrypted(body.id);
const isEdit = existing !== null;
const authKind = (isEdit ? existing!.authKind : (body.authKind ?? 'oauth')) as 'oauth' | 'api_key';
const staticToken = body.staticToken || (isEdit ? existing!.staticToken ?? undefined : undefined);
const oauthClientSecret = body.oauthClientSecret || (isEdit ? existing!.oauthClientSecret || undefined : undefined);
const oauthClientId = body.oauthClientId || (isEdit ? existing!.oauthClientId || undefined : undefined);
if (authKind === 'oauth') {
if (!body.oauthClientId || !body.oauthClientSecret) {
if (!oauthClientId || !oauthClientSecret) {
res.status(400).json({ error: 'authKind oauth requires oauthClientId and oauthClientSecret' });
return;
}
} else if (authKind === 'api_key') {
if (!body.staticToken) {
if (!staticToken) {
res.status(400).json({ error: 'authKind api_key requires staticToken' });
return;
}
@@ -80,10 +87,10 @@ export function createAdminRouter(deps: McpApiDeps): Router {
url: body.url,
authKind,
ownerId: null,
oauthClientId: body.oauthClientId,
oauthClientSecret: body.oauthClientSecret,
oauthClientId,
oauthClientSecret,
oauthScopes: body.oauthScopes ?? null,
staticToken: body.staticToken,
staticToken,
authHeaderName: body.authHeaderName ?? null,
enabled: body.enabled !== false,
createdBy: adminId,
@@ -278,15 +285,28 @@ export function createUserServersRouter(deps: McpApiDeps): Router {
return;
}
const authKind = (body.authKind ?? 'oauth') as 'oauth' | 'api_key';
// Editing re-POSTs the same id. Collide only with a server you do NOT own
// (a global or another user's server); your own server is an update.
const existing = deps.registry.getDecrypted(body.id);
if (existing && existing.ownerId !== userId) {
res.status(409).json({ error: `server id '${body.id}' already exists` });
return;
}
const isEdit = existing !== null;
// authKind cannot change on edit (the form disables the selector).
const authKind = (isEdit ? existing!.authKind : (body.authKind ?? 'oauth')) as 'oauth' | 'api_key';
// Secrets are write-only: a blank value on edit means "keep the existing one".
const staticToken = body.staticToken || (isEdit ? existing!.staticToken ?? undefined : undefined);
const oauthClientSecret = body.oauthClientSecret || (isEdit ? existing!.oauthClientSecret || undefined : undefined);
const oauthClientId = body.oauthClientId || (isEdit ? existing!.oauthClientId || undefined : undefined);
if (authKind === 'api_key') {
if (!body.staticToken) {
if (!staticToken) {
res.status(400).json({ error: 'authKind api_key requires staticToken' });
return;
}
} else if (authKind === 'oauth') {
if (!body.oauthClientId || !body.oauthClientSecret) {
if (!oauthClientId || !oauthClientSecret) {
res.status(400).json({ error: 'authKind oauth requires oauthClientId and oauthClientSecret' });
return;
}
@@ -295,23 +315,16 @@ export function createUserServersRouter(deps: McpApiDeps): Router {
return;
}
// Check for id collision with any existing server (global or other-user-owned)
const existing = deps.registry.getDecrypted(body.id);
if (existing) {
res.status(409).json({ error: `server id '${body.id}' already exists` });
return;
}
deps.registry.upsert({
id: body.id,
name: body.name,
url: body.url,
authKind,
ownerId: userId,
oauthClientId: body.oauthClientId,
oauthClientSecret: body.oauthClientSecret,
oauthClientId,
oauthClientSecret,
oauthScopes: body.oauthScopes ?? null,
staticToken: body.staticToken,
staticToken,
authHeaderName: body.authHeaderName ?? null,
enabled: body.enabled !== false,
createdBy: userId,