@1mcp/agent 0.34.4 → 0.35.0-beta.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.
Files changed (183) hide show
  1. package/build/admin/assets/admin-console-CX5GzncA.css +1 -0
  2. package/build/admin/assets/admin-console-CkZ5lBKg.js +61 -0
  3. package/build/admin/index.html +13 -0
  4. package/build/commands/admin/admin.d.ts +90 -0
  5. package/build/commands/admin/admin.d.ts.map +1 -0
  6. package/build/commands/admin/admin.js +545 -0
  7. package/build/commands/admin/admin.js.map +1 -0
  8. package/build/commands/admin/index.d.ts +3 -0
  9. package/build/commands/admin/index.d.ts.map +1 -0
  10. package/build/commands/admin/index.js +99 -0
  11. package/build/commands/admin/index.js.map +1 -0
  12. package/build/commands/auth/index.d.ts.map +1 -1
  13. package/build/commands/auth/index.js +24 -5
  14. package/build/commands/auth/index.js.map +1 -1
  15. package/build/commands/auth/login.d.ts +1 -0
  16. package/build/commands/auth/login.d.ts.map +1 -1
  17. package/build/commands/auth/login.js +6 -10
  18. package/build/commands/auth/login.js.map +1 -1
  19. package/build/commands/auth/logout.d.ts +3 -0
  20. package/build/commands/auth/logout.d.ts.map +1 -1
  21. package/build/commands/auth/logout.js +19 -32
  22. package/build/commands/auth/logout.js.map +1 -1
  23. package/build/commands/auth/runtimeTargetContext.d.ts +25 -0
  24. package/build/commands/auth/runtimeTargetContext.d.ts.map +1 -0
  25. package/build/commands/auth/runtimeTargetContext.js +69 -0
  26. package/build/commands/auth/runtimeTargetContext.js.map +1 -0
  27. package/build/commands/auth/status.d.ts +1 -0
  28. package/build/commands/auth/status.d.ts.map +1 -1
  29. package/build/commands/auth/status.js +10 -33
  30. package/build/commands/auth/status.js.map +1 -1
  31. package/build/commands/inspect/index.d.ts.map +1 -1
  32. package/build/commands/inspect/index.js +4 -0
  33. package/build/commands/inspect/index.js.map +1 -1
  34. package/build/commands/inspect/inspect.d.ts +1 -0
  35. package/build/commands/inspect/inspect.d.ts.map +1 -1
  36. package/build/commands/inspect/inspect.js +2 -2
  37. package/build/commands/inspect/inspect.js.map +1 -1
  38. package/build/commands/instructions/index.d.ts.map +1 -1
  39. package/build/commands/instructions/index.js +4 -0
  40. package/build/commands/instructions/index.js.map +1 -1
  41. package/build/commands/instructions/instructions.d.ts +1 -0
  42. package/build/commands/instructions/instructions.d.ts.map +1 -1
  43. package/build/commands/instructions/instructions.js.map +1 -1
  44. package/build/commands/instructions/instructionsUtils.js +1 -1
  45. package/build/commands/instructions/instructionsUtils.js.map +1 -1
  46. package/build/commands/mcp/enable.d.ts +89 -2
  47. package/build/commands/mcp/enable.d.ts.map +1 -1
  48. package/build/commands/mcp/enable.js +555 -35
  49. package/build/commands/mcp/enable.js.map +1 -1
  50. package/build/commands/proxy/index.d.ts +1 -0
  51. package/build/commands/proxy/index.d.ts.map +1 -1
  52. package/build/commands/proxy/index.js +4 -0
  53. package/build/commands/proxy/index.js.map +1 -1
  54. package/build/commands/run/index.d.ts.map +1 -1
  55. package/build/commands/run/index.js +4 -0
  56. package/build/commands/run/index.js.map +1 -1
  57. package/build/commands/run/run.d.ts +1 -0
  58. package/build/commands/run/run.d.ts.map +1 -1
  59. package/build/commands/run/run.js +2 -2
  60. package/build/commands/run/run.js.map +1 -1
  61. package/build/commands/serve/serve.d.ts.map +1 -1
  62. package/build/commands/serve/serve.js +4 -0
  63. package/build/commands/serve/serve.js.map +1 -1
  64. package/build/commands/shared/apiClient.d.ts +5 -1
  65. package/build/commands/shared/apiClient.d.ts.map +1 -1
  66. package/build/commands/shared/apiClient.js +17 -9
  67. package/build/commands/shared/apiClient.js.map +1 -1
  68. package/build/commands/shared/clientSurfaceAttachment.d.ts +14 -0
  69. package/build/commands/shared/clientSurfaceAttachment.d.ts.map +1 -1
  70. package/build/commands/shared/clientSurfaceAttachment.js +51 -9
  71. package/build/commands/shared/clientSurfaceAttachment.js.map +1 -1
  72. package/build/commands/shared/commandRunner.d.ts +3 -1
  73. package/build/commands/shared/commandRunner.d.ts.map +1 -1
  74. package/build/commands/shared/commandRunner.js +59 -2
  75. package/build/commands/shared/commandRunner.js.map +1 -1
  76. package/build/commands/shared/serveTargetResolver.d.ts +14 -1
  77. package/build/commands/shared/serveTargetResolver.d.ts.map +1 -1
  78. package/build/commands/shared/serveTargetResolver.js +145 -3
  79. package/build/commands/shared/serveTargetResolver.js.map +1 -1
  80. package/build/commands/target/index.d.ts +3 -0
  81. package/build/commands/target/index.d.ts.map +1 -0
  82. package/build/commands/target/index.js +171 -0
  83. package/build/commands/target/index.js.map +1 -0
  84. package/build/commands/target/target.d.ts +78 -0
  85. package/build/commands/target/target.d.ts.map +1 -0
  86. package/build/commands/target/target.js +471 -0
  87. package/build/commands/target/target.js.map +1 -0
  88. package/build/constants/mcp.d.ts +1 -1
  89. package/build/constants/mcp.d.ts.map +1 -1
  90. package/build/constants/mcp.js +1 -1
  91. package/build/constants/mcp.js.map +1 -1
  92. package/build/core/runtime/runtimeIdentityService.d.ts +30 -0
  93. package/build/core/runtime/runtimeIdentityService.d.ts.map +1 -0
  94. package/build/core/runtime/runtimeIdentityService.js +63 -0
  95. package/build/core/runtime/runtimeIdentityService.js.map +1 -0
  96. package/build/core/server/agentConfig.d.ts +4 -0
  97. package/build/core/server/agentConfig.d.ts.map +1 -1
  98. package/build/core/server/agentConfig.js +3 -0
  99. package/build/core/server/agentConfig.js.map +1 -1
  100. package/build/core/tools/handlers/serverManagementHandler.d.ts +7 -7
  101. package/build/core/tools/internal/adapters/helpers/configTransformers.d.ts +2 -2
  102. package/build/core/tools/internal/schemas/discovery.d.ts +1 -1
  103. package/build/core/tools/internal/schemas/index.d.ts +13 -13
  104. package/build/core/tools/internal/schemas/installation.d.ts +7 -7
  105. package/build/core/tools/internal/schemas/management.d.ts +5 -5
  106. package/build/core/types/transport.d.ts +9 -3
  107. package/build/core/types/transport.d.ts.map +1 -1
  108. package/build/core/types/transport.js +5 -0
  109. package/build/core/types/transport.js.map +1 -1
  110. package/build/domains/admin/adminConfiguredServerService.d.ts +275 -0
  111. package/build/domains/admin/adminConfiguredServerService.d.ts.map +1 -0
  112. package/build/domains/admin/adminConfiguredServerService.js +1926 -0
  113. package/build/domains/admin/adminConfiguredServerService.js.map +1 -0
  114. package/build/domains/admin/adminConnectivityChecker.d.ts +7 -0
  115. package/build/domains/admin/adminConnectivityChecker.d.ts.map +1 -0
  116. package/build/domains/admin/adminConnectivityChecker.js +42 -0
  117. package/build/domains/admin/adminConnectivityChecker.js.map +1 -0
  118. package/build/domains/admin/adminDomain.d.ts +29 -0
  119. package/build/domains/admin/adminDomain.d.ts.map +1 -0
  120. package/build/domains/admin/adminDomain.js +39 -0
  121. package/build/domains/admin/adminDomain.js.map +1 -0
  122. package/build/domains/admin/adminIdentityService.d.ts +75 -0
  123. package/build/domains/admin/adminIdentityService.d.ts.map +1 -0
  124. package/build/domains/admin/adminIdentityService.js +321 -0
  125. package/build/domains/admin/adminIdentityService.js.map +1 -0
  126. package/build/domains/admin/adminOperationService.d.ts +202 -0
  127. package/build/domains/admin/adminOperationService.d.ts.map +1 -0
  128. package/build/domains/admin/adminOperationService.js +724 -0
  129. package/build/domains/admin/adminOperationService.js.map +1 -0
  130. package/build/domains/admin/adminPresetService.d.ts +140 -0
  131. package/build/domains/admin/adminPresetService.d.ts.map +1 -0
  132. package/build/domains/admin/adminPresetService.js +248 -0
  133. package/build/domains/admin/adminPresetService.js.map +1 -0
  134. package/build/domains/admin/runtimeScopeAdminLock.d.ts +22 -0
  135. package/build/domains/admin/runtimeScopeAdminLock.d.ts.map +1 -0
  136. package/build/domains/admin/runtimeScopeAdminLock.js +121 -0
  137. package/build/domains/admin/runtimeScopeAdminLock.js.map +1 -0
  138. package/build/domains/config-change/configChange.d.ts +6 -1
  139. package/build/domains/config-change/configChange.d.ts.map +1 -1
  140. package/build/domains/config-change/configChange.js +344 -0
  141. package/build/domains/config-change/configChange.js.map +1 -1
  142. package/build/domains/config-change/types.d.ts +18 -3
  143. package/build/domains/config-change/types.d.ts.map +1 -1
  144. package/build/domains/preset/manager/presetManager.d.ts +1 -0
  145. package/build/domains/preset/manager/presetManager.d.ts.map +1 -1
  146. package/build/domains/preset/manager/presetManager.js +3 -0
  147. package/build/domains/preset/manager/presetManager.js.map +1 -1
  148. package/build/domains/preset/tagAuthoring.d.ts +10 -0
  149. package/build/domains/preset/tagAuthoring.d.ts.map +1 -0
  150. package/build/domains/preset/tagAuthoring.js +94 -0
  151. package/build/domains/preset/tagAuthoring.js.map +1 -0
  152. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts +51 -0
  153. package/build/domains/runtime-targets/runtimeIdentityVerification.d.ts.map +1 -0
  154. package/build/domains/runtime-targets/runtimeIdentityVerification.js +258 -0
  155. package/build/domains/runtime-targets/runtimeIdentityVerification.js.map +1 -0
  156. package/build/domains/runtime-targets/runtimeTargetStore.d.ts +222 -0
  157. package/build/domains/runtime-targets/runtimeTargetStore.d.ts.map +1 -0
  158. package/build/domains/runtime-targets/runtimeTargetStore.js +1456 -0
  159. package/build/domains/runtime-targets/runtimeTargetStore.js.map +1 -0
  160. package/build/index.d.ts.map +1 -1
  161. package/build/index.js +7 -1
  162. package/build/index.js.map +1 -1
  163. package/build/transport/http/routes/adminRoutes.d.ts +34 -0
  164. package/build/transport/http/routes/adminRoutes.d.ts.map +1 -0
  165. package/build/transport/http/routes/adminRoutes.js +1217 -0
  166. package/build/transport/http/routes/adminRoutes.js.map +1 -0
  167. package/build/transport/http/routes/oauthRoutes.d.ts +2 -0
  168. package/build/transport/http/routes/oauthRoutes.d.ts.map +1 -1
  169. package/build/transport/http/routes/oauthRoutes.js +5 -185
  170. package/build/transport/http/routes/oauthRoutes.js.map +1 -1
  171. package/build/transport/http/routes/runtimeIdentityRoutes.d.ts +7 -0
  172. package/build/transport/http/routes/runtimeIdentityRoutes.d.ts.map +1 -0
  173. package/build/transport/http/routes/runtimeIdentityRoutes.js +9 -0
  174. package/build/transport/http/routes/runtimeIdentityRoutes.js.map +1 -0
  175. package/build/transport/http/server.d.ts +3 -0
  176. package/build/transport/http/server.d.ts.map +1 -1
  177. package/build/transport/http/server.js +92 -3
  178. package/build/transport/http/server.js.map +1 -1
  179. package/build/utils/validation/urlDetection.d.ts +13 -2
  180. package/build/utils/validation/urlDetection.d.ts.map +1 -1
  181. package/build/utils/validation/urlDetection.js +37 -6
  182. package/build/utils/validation/urlDetection.js.map +1 -1
  183. package/package.json +22 -9
@@ -0,0 +1,1217 @@
1
+ import { createHash } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { AdminConfiguredServerApplyError, AdminConfiguredServerNotFoundError, } from '../../../domains/admin/adminConfiguredServerService.js';
6
+ import { ADMIN_SESSION_COOKIE_NAME, AdminIdentityError, } from '../../../domains/admin/adminIdentityService.js';
7
+ import { AdminPresetConflictError, AdminPresetNotFoundError, } from '../../../domains/admin/adminPresetService.js';
8
+ import { sanitizeErrorMessage } from '../../../utils/validation/sanitization.js';
9
+ import express, { Router } from 'express';
10
+ import { createRequire } from 'node:module';
11
+ import { z } from 'zod';
12
+ const FAILED_LOGIN_LIMIT = 5;
13
+ const FAILED_LOGIN_WINDOW_MS = 15 * 60 * 1000;
14
+ const FAILED_LOGIN_MAX_ACTIVE_KEYS = 10_000;
15
+ const ADMIN_USERNAME_MAX_LENGTH = 256;
16
+ const ADMIN_PASSWORD_MAX_LENGTH = 4096;
17
+ const CLI_ADMIN_PROTOCOL_VERSION = '1';
18
+ const CLI_ADMIN_RESPONSE_MAX_BYTES = 256 * 1024;
19
+ const CLI_ADMIN_RESPONSE_TOO_LARGE_MESSAGE = 'CLI Admin response exceeded the maximum supported size; use a narrower or paginated request.';
20
+ const CLI_SESSION_OPERATIONS = ['admin.login', 'admin.status', 'admin.logout'];
21
+ const ADMIN_API_PROTOCOL_VERSION = '1';
22
+ const packageMetadata = createRequire(import.meta.url)('../../../../package.json');
23
+ const CLI_MCP_OPERATIONS = ['mcp.enable', 'mcp.disable'];
24
+ const adminLoginBodySchema = z.object({
25
+ username: z.string().trim().min(1).max(ADMIN_USERNAME_MAX_LENGTH),
26
+ password: z.string().min(1).max(ADMIN_PASSWORD_MAX_LENGTH),
27
+ });
28
+ const cliConfiguredServerMutationBodySchema = z.object({
29
+ targetName: z.string().trim().min(1).max(256),
30
+ dryRun: z.boolean().optional(),
31
+ });
32
+ export function createAdminRoutes(options) {
33
+ if (!options.adminEnabled) {
34
+ options.adminService.revokeAllSessions();
35
+ return null;
36
+ }
37
+ const router = Router();
38
+ const failedLoginLimiter = new FailedLoginLimiter();
39
+ const adminConsoleAssets = resolveAdminConsoleAssets(options.adminConsoleAssetsDir);
40
+ options.adminService.bootstrapFirstAdminFromEnvironment();
41
+ router.get('/cli/v1/capabilities', (req, res) => {
42
+ const identity = options.getRuntimeIdentity();
43
+ const setupRequired = !options.adminService.hasAdminAccount();
44
+ const mutationAvailability = cliMutationAvailability(options, setupRequired);
45
+ const configuredServerOperationsSupported = Boolean(options.configuredServerService);
46
+ const mcpMutationsReady = configuredServerOperationsSupported && mutationAvailability.available;
47
+ const mcpOperations = configuredServerOperationsSupported ? [...CLI_MCP_OPERATIONS] : [];
48
+ const mcpMutationOperations = mcpMutationsReady ? ['enable', 'disable'] : [];
49
+ const mcpReadinessStatus = cliMcpReadinessStatus(mutationAvailability);
50
+ sendCliSuccess(req, res, {
51
+ runtime: toCliRuntimeIdentity(identity),
52
+ supportedOperations: [...CLI_SESSION_OPERATIONS, ...mcpOperations],
53
+ adminSurface: {
54
+ enabled: true,
55
+ status: setupRequired ? 'setupRequired' : 'loginRequired',
56
+ },
57
+ mutationReadiness: {
58
+ mcp: {
59
+ enabled: mcpMutationsReady,
60
+ status: mcpReadinessStatus,
61
+ operations: mcpMutationOperations,
62
+ },
63
+ },
64
+ adminMutationsAvailable: mutationAvailability.available,
65
+ ...(mutationAvailability.reason ? { adminMutationsUnavailableReason: mutationAvailability.reason } : {}),
66
+ features: {
67
+ adminSessions: true,
68
+ bearerSessionAuth: true,
69
+ csrfTokens: true,
70
+ mcpEnableDisable: mcpMutationsReady,
71
+ },
72
+ });
73
+ });
74
+ router.post('/cli/v1/session/login', async (req, res) => {
75
+ const parsedBody = adminLoginBodySchema.safeParse(req.body);
76
+ if (!parsedBody.success) {
77
+ sendCliError(req, res, {
78
+ status: 400,
79
+ code: 'admin_login_request_invalid',
80
+ message: 'Admin username and password are required and must be valid strings',
81
+ retryable: false,
82
+ });
83
+ return;
84
+ }
85
+ const { username, password } = parsedBody.data;
86
+ const source = getLoginSource(req);
87
+ if (failedLoginLimiter.isLimited(username, source)) {
88
+ sendCliError(req, res, {
89
+ status: 429,
90
+ code: 'admin_login_rate_limited',
91
+ message: 'Too many failed admin login attempts',
92
+ retryable: true,
93
+ });
94
+ return;
95
+ }
96
+ try {
97
+ const login = await options.adminService.login({
98
+ username,
99
+ password,
100
+ });
101
+ failedLoginLimiter.reset(username, source);
102
+ sendCliSuccess(req, res, {
103
+ sessionToken: login.sessionToken,
104
+ csrfToken: login.csrfToken,
105
+ expiresAt: login.expiresAt,
106
+ account: toCliAdminAccount(login.account),
107
+ });
108
+ }
109
+ catch (error) {
110
+ failedLoginLimiter.recordFailure(username, source);
111
+ sendCliAdminError(req, res, error);
112
+ }
113
+ });
114
+ router.get('/cli/v1/session/status', (req, res) => {
115
+ const sessionToken = getBearerSessionToken(req);
116
+ const session = options.adminService.validateSession(sessionToken);
117
+ const runtime = toCliRuntimeIdentity(options.getRuntimeIdentity());
118
+ if (!session) {
119
+ sendCliSuccess(req, res, {
120
+ authenticated: false,
121
+ runtime,
122
+ });
123
+ return;
124
+ }
125
+ sendCliSuccess(req, res, {
126
+ authenticated: true,
127
+ runtime,
128
+ account: toCliAdminAccount(session.account),
129
+ expiresAt: session.expiresAt,
130
+ });
131
+ });
132
+ router.post('/cli/v1/session/logout', (req, res) => {
133
+ const sessionToken = getBearerSessionToken(req);
134
+ const session = options.adminService.validateSession(sessionToken);
135
+ options.adminService.revokeSession(sessionToken);
136
+ sendCliSuccess(req, res, {
137
+ revoked: Boolean(session),
138
+ });
139
+ });
140
+ router.post('/cli/v1/operations/enable-server', async (req, res) => {
141
+ await handleCliConfiguredServerMutation(req, res, options, 'enableConfiguredServer');
142
+ });
143
+ router.post('/cli/v1/operations/disable-server', async (req, res) => {
144
+ await handleCliConfiguredServerMutation(req, res, options, 'disableConfiguredServer');
145
+ });
146
+ router.post('/api/session/login', async (req, res) => {
147
+ const parsedBody = adminLoginBodySchema.safeParse(req.body);
148
+ if (!parsedBody.success) {
149
+ res.status(400).json({ error: 'admin_login_request_invalid' });
150
+ return;
151
+ }
152
+ const { username, password } = parsedBody.data;
153
+ const source = getLoginSource(req);
154
+ if (failedLoginLimiter.isLimited(username, source)) {
155
+ res.status(429).json({ error: 'admin_login_rate_limited' });
156
+ return;
157
+ }
158
+ try {
159
+ const login = await options.adminService.login({
160
+ username,
161
+ password,
162
+ });
163
+ failedLoginLimiter.reset(username, source);
164
+ setAdminSessionCookie(res, options.getRuntimeIdentity().externalUrl, login.sessionToken, login.expiresAt);
165
+ res.status(200).json({
166
+ authenticated: true,
167
+ account: login.account,
168
+ csrfToken: login.csrfToken,
169
+ expiresAt: login.expiresAt,
170
+ });
171
+ }
172
+ catch (error) {
173
+ failedLoginLimiter.recordFailure(username, source);
174
+ sendAdminError(res, error);
175
+ }
176
+ });
177
+ router.use('/api', (req, res, next) => {
178
+ const sessionToken = getAdminSessionCookie(req);
179
+ const session = options.adminService.validateSession(sessionToken);
180
+ if (!session) {
181
+ res.status(401).json(unauthenticatedAdminApiResponse(options));
182
+ return;
183
+ }
184
+ if (isUnsafeMethod(req.method) && !options.adminService.validateCsrf(sessionToken, req.header('X-CSRF-Token'))) {
185
+ res.status(403).json({ error: 'csrf_required' });
186
+ return;
187
+ }
188
+ next();
189
+ });
190
+ router.get('/api/session', (req, res) => {
191
+ const session = options.adminService.validateSession(getAdminSessionCookie(req));
192
+ if (!session) {
193
+ res.status(401).json({ authenticated: false });
194
+ return;
195
+ }
196
+ res.status(200).json({
197
+ authenticated: true,
198
+ account: session.account,
199
+ csrfToken: session.csrfToken,
200
+ expiresAt: session.expiresAt,
201
+ });
202
+ });
203
+ router.post('/api/session/logout', (req, res) => {
204
+ options.adminService.revokeSession(getAdminSessionCookie(req));
205
+ clearAdminSessionCookie(res, options.getRuntimeIdentity().externalUrl);
206
+ res.status(200).json({ ok: true });
207
+ });
208
+ router.get('/api/status', (req, res) => {
209
+ const session = options.adminService.validateSession(getAdminSessionCookie(req));
210
+ if (!session) {
211
+ res.status(401).json({ authenticated: false });
212
+ return;
213
+ }
214
+ res.status(200).json({
215
+ ok: true,
216
+ runtime: options.getRuntimeIdentity(),
217
+ session: {
218
+ authenticated: true,
219
+ account: toAdminConsoleAccount(session.account),
220
+ expiresAt: session.expiresAt,
221
+ },
222
+ oauth: sanitizeOAuthDashboard(options.getOAuthDashboard?.() ?? { status: 'ready', services: [] }),
223
+ audit: {
224
+ facts: options.configuredServerService?.getRecentAuditFacts({ limit: 10 }) ??
225
+ options.presetService?.getRecentAuditFacts({ limit: 10 }) ??
226
+ [],
227
+ },
228
+ about: buildAboutMetadata(options.getRuntimeIdentity(), {
229
+ buildVersion: req.header('X-Admin-UI-Build-Version'),
230
+ protocolVersion: req.header('X-Admin-UI-Protocol-Version'),
231
+ }),
232
+ });
233
+ });
234
+ router.get('/api/presets', async (req, res) => {
235
+ if (!options.presetService)
236
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
237
+ const result = await options.presetService.listPresets({
238
+ context: buildAdminOperationContext(req, options, { type: 'preset_collection' }),
239
+ });
240
+ sendAdminOperationResult(res, result);
241
+ });
242
+ router.get('/api/presets/:name', async (req, res) => {
243
+ if (!options.presetService)
244
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
245
+ try {
246
+ sendAdminOperationResult(res, await options.presetService.getPreset({
247
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name }),
248
+ name: req.params.name,
249
+ }));
250
+ }
251
+ catch (error) {
252
+ sendPresetError(res, error);
253
+ }
254
+ });
255
+ router.post('/api/presets/preview', async (req, res) => {
256
+ if (!options.presetService)
257
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
258
+ try {
259
+ sendAdminOperationResult(res, await options.presetService.previewPreset({
260
+ context: buildAdminOperationContext(req, options, {
261
+ type: 'preset',
262
+ id: getBodyString(req.body, 'sourceName') || getBodyString(getBodyValue(req.body, 'draft'), 'name'),
263
+ }),
264
+ draft: getPresetDraft(req.body),
265
+ sourceName: getBodyString(req.body, 'sourceName') || undefined,
266
+ }));
267
+ }
268
+ catch (error) {
269
+ sendPresetError(res, error);
270
+ }
271
+ });
272
+ router.post('/api/presets', async (req, res) => {
273
+ await handlePresetMutation(req, res, options, 'create');
274
+ });
275
+ router.post('/api/presets/:name/update', async (req, res) => {
276
+ await handlePresetMutation(req, res, options, 'update');
277
+ });
278
+ router.post('/api/presets/:name/duplicate', async (req, res) => {
279
+ await handlePresetMutation(req, res, options, 'duplicate');
280
+ });
281
+ router.post('/api/presets/:name/delete-preview', async (req, res) => {
282
+ if (!options.presetService)
283
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
284
+ try {
285
+ sendAdminOperationResult(res, await options.presetService.previewDeletePreset({
286
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name }),
287
+ name: req.params.name,
288
+ revision: getBodyString(req.body, 'revision'),
289
+ }));
290
+ }
291
+ catch (error) {
292
+ sendPresetError(res, error);
293
+ }
294
+ });
295
+ router.delete('/api/presets/:name', async (req, res) => {
296
+ if (!options.presetService)
297
+ return void res.status(404).json({ error: 'admin_presets_unavailable' });
298
+ try {
299
+ sendAdminOperationResult(res, await options.presetService.deletePreset({
300
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name }),
301
+ name: req.params.name,
302
+ revision: getBodyString(req.body, 'revision'),
303
+ previewFingerprint: getBodyString(req.body, 'previewFingerprint'),
304
+ }));
305
+ }
306
+ catch (error) {
307
+ sendPresetError(res, error);
308
+ }
309
+ });
310
+ router.get('/api/configured-servers', async (req, res) => {
311
+ if (!options.configuredServerService) {
312
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
313
+ return;
314
+ }
315
+ const result = await options.configuredServerService.listConfiguredServers({
316
+ context: buildAdminOperationContext(req, options, { type: 'configured_server_collection' }),
317
+ });
318
+ if (!result.ok) {
319
+ sendAdminOperationResult(res, result);
320
+ return;
321
+ }
322
+ res.status(200).json({
323
+ ok: true,
324
+ operationId: result.operationId,
325
+ servers: result.result.servers,
326
+ });
327
+ });
328
+ router.get('/api/configured-servers/:name', async (req, res) => {
329
+ await handleConfiguredServerDetail(req, res, options);
330
+ });
331
+ router.post('/api/configured-servers/:name/preview', async (req, res) => {
332
+ await handleConfiguredServerPreview(req, res, options);
333
+ });
334
+ router.post('/api/configured-servers/:name/apply', async (req, res) => {
335
+ await handleConfiguredServerApply(req, res, options);
336
+ });
337
+ router.post('/api/configured-servers/:name/enable', async (req, res) => {
338
+ await handleConfiguredServerMutation(req, res, options, 'enableConfiguredServer');
339
+ });
340
+ router.post('/api/configured-servers/:name/disable', async (req, res) => {
341
+ await handleConfiguredServerMutation(req, res, options, 'disableConfiguredServer');
342
+ });
343
+ router.use('/assets', express.static(path.join(adminConsoleAssets.rootDir, 'assets'), {
344
+ immutable: true,
345
+ maxAge: '1y',
346
+ }));
347
+ router.use('/assets', (_req, res) => {
348
+ res.status(404).type('text/plain').send('Admin Console asset not found');
349
+ });
350
+ router.get(['/', '/*splat'], (req, res, next) => {
351
+ if (isAdminApiPath(req.path)) {
352
+ next();
353
+ return;
354
+ }
355
+ sendAdminConsoleIndex(res, adminConsoleAssets.indexPath);
356
+ });
357
+ return router;
358
+ }
359
+ function resolveAdminConsoleAssets(configuredDir) {
360
+ const rootDir = configuredDir ?? resolveDefaultAdminConsoleAssetsDir();
361
+ return {
362
+ rootDir,
363
+ indexPath: path.join(rootDir, 'index.html'),
364
+ };
365
+ }
366
+ export function resolveDefaultAdminConsoleAssetsDir() {
367
+ return fileURLToPath(new URL('../../../admin', import.meta.url));
368
+ }
369
+ function isAdminApiPath(pathname) {
370
+ return (pathname === '/api' || pathname.startsWith('/api/') || pathname === '/cli/v1' || pathname.startsWith('/cli/v1/'));
371
+ }
372
+ function sendAdminConsoleIndex(res, indexPath) {
373
+ if (!fs.existsSync(indexPath)) {
374
+ res.status(503).type('text/plain').send('Admin Console assets are not available. Run the package build first.');
375
+ return;
376
+ }
377
+ res.status(200).sendFile(indexPath);
378
+ }
379
+ function unauthenticatedAdminApiResponse(options) {
380
+ return options.adminService.hasAdminAccount()
381
+ ? { authenticated: false }
382
+ : { authenticated: false, adminStatus: 'setupRequired' };
383
+ }
384
+ async function handleCliConfiguredServerMutation(req, res, options, operationName) {
385
+ if (!options.configuredServerService) {
386
+ sendCliError(req, res, {
387
+ status: 404,
388
+ code: 'admin_configured_servers_unavailable',
389
+ message: 'Configured server administration is unavailable',
390
+ retryable: false,
391
+ });
392
+ return;
393
+ }
394
+ if (isMutationLocked(options)) {
395
+ sendCliError(req, res, {
396
+ status: 409,
397
+ code: 'runtime_scope_locked',
398
+ message: 'Runtime scope admin mutations are locked by another writer',
399
+ retryable: true,
400
+ details: {
401
+ operationName,
402
+ reason: 'writer_lock_unavailable',
403
+ },
404
+ });
405
+ return;
406
+ }
407
+ const sessionToken = getBearerSessionToken(req);
408
+ if (!sessionToken) {
409
+ sendCliError(req, res, {
410
+ status: 401,
411
+ code: 'admin_session_required',
412
+ message: 'A valid admin session bearer token is required',
413
+ retryable: false,
414
+ });
415
+ return;
416
+ }
417
+ const session = options.adminService.validateSession(sessionToken);
418
+ if (!session) {
419
+ sendCliError(req, res, {
420
+ status: 401,
421
+ code: 'admin_session_required',
422
+ message: 'A valid admin session bearer token is required',
423
+ retryable: false,
424
+ });
425
+ return;
426
+ }
427
+ const parsedBody = cliConfiguredServerMutationBodySchema.safeParse(req.body);
428
+ if (!parsedBody.success) {
429
+ sendCliError(req, res, {
430
+ status: 400,
431
+ code: 'validation_request_invalid',
432
+ message: 'Configured server targetName and dryRun must have valid types',
433
+ retryable: false,
434
+ });
435
+ return;
436
+ }
437
+ const { targetName, dryRun = false } = parsedBody.data;
438
+ const context = buildCliAdminOperationContext(req, options, session.account, sessionToken, {
439
+ type: 'configured_server',
440
+ id: targetName,
441
+ });
442
+ const input = {
443
+ context,
444
+ targetName,
445
+ ...(dryRun ? { dryRun: true } : {}),
446
+ confirmationRequirements: dryRun
447
+ ? []
448
+ : cliConfiguredServerConfirmationRequirements(options, targetName, operationName),
449
+ };
450
+ const result = operationName === 'enableConfiguredServer'
451
+ ? await options.configuredServerService.enableConfiguredServer(input)
452
+ : await options.configuredServerService.disableConfiguredServer(input);
453
+ sendCliAdminOperationResult(req, res, result);
454
+ }
455
+ async function handleConfiguredServerDetail(req, res, options) {
456
+ if (!options.configuredServerService) {
457
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
458
+ return;
459
+ }
460
+ const targetName = req.params.name;
461
+ try {
462
+ const result = await options.configuredServerService.getConfiguredServerDetail({
463
+ context: buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName }),
464
+ targetName,
465
+ });
466
+ if (!result.ok) {
467
+ sendAdminOperationResult(res, result);
468
+ return;
469
+ }
470
+ res.status(200).json({
471
+ ok: true,
472
+ operationId: result.operationId,
473
+ server: result.result.server,
474
+ editContract: result.result.editContract,
475
+ });
476
+ }
477
+ catch (error) {
478
+ if (error instanceof AdminConfiguredServerNotFoundError) {
479
+ res.status(404).json({
480
+ ok: false,
481
+ error: error.code,
482
+ code: error.code,
483
+ message: 'Configured server target was not found',
484
+ target: { type: 'configured_server', id: error.targetName },
485
+ });
486
+ return;
487
+ }
488
+ throw error;
489
+ }
490
+ }
491
+ async function handleConfiguredServerPreview(req, res, options) {
492
+ if (!options.configuredServerService) {
493
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
494
+ return;
495
+ }
496
+ const targetName = req.params.name;
497
+ const edit = getBodyValue(req.body, 'edit');
498
+ try {
499
+ const result = await options.configuredServerService.previewConfiguredServerEdit({
500
+ context: buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName }),
501
+ targetName,
502
+ edit: edit === undefined ? {} : edit,
503
+ connectivityCheck: getBodyString(req.body, 'connectivityCheck') === 'manual' ? 'manual' : 'auto',
504
+ });
505
+ if (!result.ok) {
506
+ sendAdminOperationResult(res, result);
507
+ return;
508
+ }
509
+ res.status(200).json({
510
+ ok: true,
511
+ operationId: result.operationId,
512
+ preview: result.result,
513
+ });
514
+ }
515
+ catch (error) {
516
+ if (error instanceof AdminConfiguredServerNotFoundError) {
517
+ res.status(404).json({
518
+ ok: false,
519
+ error: error.code,
520
+ code: error.code,
521
+ message: 'Configured server target was not found',
522
+ target: { type: 'configured_server', id: error.targetName },
523
+ });
524
+ return;
525
+ }
526
+ throw error;
527
+ }
528
+ }
529
+ async function handleConfiguredServerApply(req, res, options) {
530
+ if (!options.configuredServerService) {
531
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
532
+ return;
533
+ }
534
+ if (isMutationLocked(options)) {
535
+ sendAdminOperationResult(res, {
536
+ ok: false,
537
+ status: 'runtime_scope_locked',
538
+ code: 'runtime_scope_locked',
539
+ retryable: true,
540
+ operationName: 'applyConfiguredServerEdit',
541
+ reason: 'writer_lock_unavailable',
542
+ });
543
+ return;
544
+ }
545
+ const targetName = req.params.name;
546
+ try {
547
+ const result = await options.configuredServerService.applyConfiguredServerEdit({
548
+ context: buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName }),
549
+ targetName,
550
+ edit: getBodyValue(req.body, 'edit') ?? {},
551
+ previewFingerprint: getBodyString(req.body, 'previewFingerprint'),
552
+ });
553
+ if (!result.ok && result.status === 'mutation_failed' && isConfiguredServerApplyErrorCode(result.error)) {
554
+ sendConfiguredServerApplyError(res, result.error);
555
+ return;
556
+ }
557
+ sendAdminOperationResult(res, result);
558
+ }
559
+ catch (error) {
560
+ if (error instanceof AdminConfiguredServerNotFoundError) {
561
+ sendConfiguredServerApplyError(res, error.code);
562
+ return;
563
+ }
564
+ if (error instanceof AdminConfiguredServerApplyError) {
565
+ sendConfiguredServerApplyError(res, error.code);
566
+ return;
567
+ }
568
+ throw error;
569
+ }
570
+ }
571
+ function isConfiguredServerApplyErrorCode(value) {
572
+ return value.startsWith('configured_server_');
573
+ }
574
+ function sendConfiguredServerApplyError(res, code) {
575
+ const status = code === 'configured_server_not_found' ? 404 : 409;
576
+ res.status(status).json({
577
+ ok: false,
578
+ error: code,
579
+ code,
580
+ message: configuredServerApplyErrorMessage(code),
581
+ });
582
+ }
583
+ function configuredServerApplyErrorMessage(code) {
584
+ switch (code) {
585
+ case 'configured_server_stale_preview':
586
+ return 'The configured server changed after preview. Preview the edit again.';
587
+ case 'configured_server_destination_conflict':
588
+ return 'The requested configured server target name is already in use.';
589
+ case 'configured_server_connectivity_blocked':
590
+ return 'Connectivity validation did not pass for the enabled remote server.';
591
+ case 'configured_server_edit_invalid':
592
+ return 'The configured server edit is invalid.';
593
+ case 'configured_server_edit_unchanged':
594
+ return 'The configured server edit does not contain any changes.';
595
+ case 'configured_server_not_found':
596
+ return 'Configured server target was not found.';
597
+ default:
598
+ return 'The configured server edit could not be applied.';
599
+ }
600
+ }
601
+ async function handleConfiguredServerMutation(req, res, options, operationName) {
602
+ if (!options.configuredServerService) {
603
+ res.status(404).json({ error: 'admin_configured_servers_unavailable' });
604
+ return;
605
+ }
606
+ if (isMutationLocked(options)) {
607
+ sendAdminOperationResult(res, {
608
+ ok: false,
609
+ status: 'runtime_scope_locked',
610
+ code: 'runtime_scope_locked',
611
+ retryable: true,
612
+ operationName,
613
+ reason: 'writer_lock_unavailable',
614
+ });
615
+ return;
616
+ }
617
+ const targetName = req.params.name;
618
+ const context = buildAdminOperationContext(req, options, { type: 'configured_server', id: targetName });
619
+ const input = { context, targetName };
620
+ const result = operationName === 'enableConfiguredServer'
621
+ ? await options.configuredServerService.enableConfiguredServer(input)
622
+ : await options.configuredServerService.disableConfiguredServer(input);
623
+ sendAdminOperationResult(res, result);
624
+ }
625
+ function cliMutationAvailability(options, setupRequired) {
626
+ if (!options.configuredServerService) {
627
+ return {
628
+ available: false,
629
+ reason: 'mutation_service_unavailable',
630
+ };
631
+ }
632
+ if (setupRequired) {
633
+ return {
634
+ available: false,
635
+ reason: 'setup_required',
636
+ };
637
+ }
638
+ return options.adminMutationAvailability ?? { available: true };
639
+ }
640
+ function isMutationLocked(options) {
641
+ return (options.adminMutationAvailability?.available === false &&
642
+ options.adminMutationAvailability.reason === 'writer_lock_unavailable');
643
+ }
644
+ function cliMcpReadinessStatus(mutationAvailability) {
645
+ if (mutationAvailability.available) {
646
+ return 'ready';
647
+ }
648
+ return mutationAvailability.reason === 'mutation_service_unavailable'
649
+ ? 'unavailable'
650
+ : (mutationAvailability.reason ?? 'unavailable');
651
+ }
652
+ export class FailedLoginLimiter {
653
+ now;
654
+ maxActiveKeys;
655
+ windowMs;
656
+ attempts = new Map();
657
+ constructor(now = Date.now, maxActiveKeys = FAILED_LOGIN_MAX_ACTIVE_KEYS, windowMs = FAILED_LOGIN_WINDOW_MS) {
658
+ this.now = now;
659
+ this.maxActiveKeys = maxActiveKeys;
660
+ this.windowMs = windowMs;
661
+ }
662
+ isLimited(username, origin) {
663
+ this.pruneExpired();
664
+ const attempt = this.getAttempt(username, origin);
665
+ if (attempt) {
666
+ return attempt.count >= FAILED_LOGIN_LIMIT;
667
+ }
668
+ return this.attempts.size >= this.maxActiveKeys;
669
+ }
670
+ recordFailure(username, origin) {
671
+ const key = this.key(username, origin);
672
+ this.pruneExpired();
673
+ const now = this.now();
674
+ const attempt = this.getAttempt(username, origin);
675
+ if (!attempt && this.attempts.size >= this.maxActiveKeys) {
676
+ return;
677
+ }
678
+ this.attempts.set(key, attempt ? { ...attempt, count: attempt.count + 1 } : { count: 1, firstFailureAt: now });
679
+ }
680
+ reset(username, origin) {
681
+ this.attempts.delete(this.key(username, origin));
682
+ }
683
+ getAttempt(username, origin) {
684
+ const key = this.key(username, origin);
685
+ const attempt = this.attempts.get(key);
686
+ if (!attempt) {
687
+ return null;
688
+ }
689
+ if (this.now() - attempt.firstFailureAt > this.windowMs) {
690
+ this.attempts.delete(key);
691
+ return null;
692
+ }
693
+ return attempt;
694
+ }
695
+ key(username, origin) {
696
+ return `${username.trim() || '<missing>'}\0${origin}`;
697
+ }
698
+ pruneExpired() {
699
+ const now = this.now();
700
+ for (const [key, attempt] of this.attempts) {
701
+ if (now - attempt.firstFailureAt > this.windowMs) {
702
+ this.attempts.delete(key);
703
+ }
704
+ }
705
+ }
706
+ }
707
+ function isUnsafeMethod(method) {
708
+ return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS';
709
+ }
710
+ function getLoginSource(req) {
711
+ return req.ip ?? req.socket.remoteAddress ?? req.header('origin') ?? 'unknown';
712
+ }
713
+ function setAdminSessionCookie(res, externalUrl, sessionToken, expiresAt) {
714
+ res.cookie(ADMIN_SESSION_COOKIE_NAME, sessionToken, {
715
+ httpOnly: true,
716
+ sameSite: 'strict',
717
+ secure: isHttpsRuntime(externalUrl),
718
+ path: '/admin',
719
+ expires: new Date(expiresAt),
720
+ });
721
+ }
722
+ function clearAdminSessionCookie(res, externalUrl) {
723
+ res.clearCookie(ADMIN_SESSION_COOKIE_NAME, {
724
+ httpOnly: true,
725
+ sameSite: 'strict',
726
+ secure: isHttpsRuntime(externalUrl),
727
+ path: '/admin',
728
+ });
729
+ }
730
+ function getAdminSessionCookie(req) {
731
+ const cookieHeader = req.header('cookie');
732
+ if (!cookieHeader) {
733
+ return undefined;
734
+ }
735
+ for (const cookie of cookieHeader.split(';')) {
736
+ const [name, ...valueParts] = cookie.trim().split('=');
737
+ if (name === ADMIN_SESSION_COOKIE_NAME) {
738
+ return valueParts.join('=');
739
+ }
740
+ }
741
+ return undefined;
742
+ }
743
+ function getBearerSessionToken(req) {
744
+ const authorization = req.header('authorization')?.trim();
745
+ if (!authorization) {
746
+ return undefined;
747
+ }
748
+ const match = authorization.match(/^Bearer\s+(.+)$/i);
749
+ const token = match?.[1]?.trim();
750
+ return token || undefined;
751
+ }
752
+ function isHttpsRuntime(externalUrl) {
753
+ try {
754
+ return new URL(externalUrl).protocol === 'https:';
755
+ }
756
+ catch {
757
+ return false;
758
+ }
759
+ }
760
+ function sendCliSuccess(req, res, result) {
761
+ const requestId = getRequestId(req);
762
+ sendBoundedCliEnvelope(res, 200, {
763
+ ok: true,
764
+ cliProtocolVersion: CLI_ADMIN_PROTOCOL_VERSION,
765
+ requestId,
766
+ warnings: [],
767
+ result,
768
+ });
769
+ }
770
+ function sendCliAdminError(req, res, error) {
771
+ if (error instanceof AdminIdentityError) {
772
+ const status = error.code === 'invalid_credentials' ? 401 : error.code === 'admin_account_not_found' ? 404 : 400;
773
+ sendCliError(req, res, {
774
+ status,
775
+ code: error.code,
776
+ message: error.message,
777
+ retryable: false,
778
+ });
779
+ return;
780
+ }
781
+ sendCliError(req, res, {
782
+ status: 500,
783
+ code: 'admin_cli_request_failed',
784
+ message: 'Admin CLI request failed',
785
+ retryable: false,
786
+ });
787
+ }
788
+ function sendCliError(req, res, error) {
789
+ const requestId = getRequestId(req);
790
+ sendBoundedCliEnvelope(res, error.status, {
791
+ ok: false,
792
+ cliProtocolVersion: CLI_ADMIN_PROTOCOL_VERSION,
793
+ requestId,
794
+ error: {
795
+ code: error.code,
796
+ message: error.message,
797
+ retryable: error.retryable,
798
+ requestId,
799
+ ...(error.retryAfterMs === undefined ? {} : { retryAfterMs: error.retryAfterMs }),
800
+ ...(error.recoveryCommand ? { recoveryCommand: error.recoveryCommand } : {}),
801
+ ...(error.details === undefined ? {} : { details: error.details }),
802
+ },
803
+ warnings: [],
804
+ });
805
+ }
806
+ function sendBoundedCliEnvelope(res, status, envelope) {
807
+ if (Buffer.byteLength(JSON.stringify(envelope), 'utf8') > CLI_ADMIN_RESPONSE_MAX_BYTES) {
808
+ res.status(422).json({
809
+ ok: false,
810
+ cliProtocolVersion: CLI_ADMIN_PROTOCOL_VERSION,
811
+ requestId: envelope.requestId,
812
+ error: {
813
+ code: 'validation_response_too_large',
814
+ message: CLI_ADMIN_RESPONSE_TOO_LARGE_MESSAGE,
815
+ retryable: false,
816
+ requestId: envelope.requestId,
817
+ details: {
818
+ maxBytes: CLI_ADMIN_RESPONSE_MAX_BYTES,
819
+ },
820
+ },
821
+ warnings: [],
822
+ });
823
+ return;
824
+ }
825
+ res.status(status).json(envelope);
826
+ }
827
+ function sendCliAdminOperationResult(req, res, result) {
828
+ if (result.ok) {
829
+ sendCliSuccess(req, res, {
830
+ operationId: result.operationId,
831
+ operationName: result.operationName,
832
+ replayed: result.replayed,
833
+ ...(typeof result.result === 'object' && result.result !== null ? result.result : { value: result.result }),
834
+ });
835
+ return;
836
+ }
837
+ sendCliError(req, res, {
838
+ status: cliOperationErrorStatus(result.status),
839
+ code: result.code,
840
+ message: cliOperationErrorMessage(result.status),
841
+ retryable: result.retryable,
842
+ retryAfterMs: result.status === 'operation_in_progress' ? result.retryAfterMs : undefined,
843
+ details: cliOperationErrorDetails(result),
844
+ });
845
+ }
846
+ function sendAdminError(res, error) {
847
+ if (error instanceof AdminIdentityError) {
848
+ const status = error.code === 'invalid_credentials' ? 401 : error.code === 'admin_account_not_found' ? 404 : 400;
849
+ res.status(status).json({ error: error.code });
850
+ return;
851
+ }
852
+ throw error;
853
+ }
854
+ function sendAdminOperationResult(res, result) {
855
+ if (result.ok) {
856
+ res.status(200).json({
857
+ ok: true,
858
+ operationId: result.operationId,
859
+ replayed: result.replayed,
860
+ result: result.result,
861
+ });
862
+ return;
863
+ }
864
+ const status = result.status === 'idempotency_key_required' ? 400 : result.status === 'mutation_failed' ? 409 : 409;
865
+ res.status(status).json(result);
866
+ }
867
+ async function handlePresetMutation(req, res, options, action) {
868
+ if (!options.presetService) {
869
+ res.status(404).json({ error: 'admin_presets_unavailable' });
870
+ return;
871
+ }
872
+ const draft = getPresetDraft(req.body);
873
+ const common = {
874
+ context: buildAdminOperationContext(req, options, { type: 'preset', id: req.params.name || draft.name }),
875
+ draft,
876
+ revision: getBodyString(req.body, 'revision'),
877
+ previewFingerprint: getBodyString(req.body, 'previewFingerprint'),
878
+ };
879
+ try {
880
+ const result = action === 'create'
881
+ ? await options.presetService.createPreset(common)
882
+ : action === 'update'
883
+ ? await options.presetService.updatePreset({ ...common, sourceName: req.params.name })
884
+ : await options.presetService.duplicatePreset({ ...common, sourceName: req.params.name });
885
+ if (!result.ok && result.status === 'mutation_failed' && result.error === 'preset_revision_conflict') {
886
+ res.status(409).json({ error: 'preset_revision_conflict' });
887
+ return;
888
+ }
889
+ sendAdminOperationResult(res, result);
890
+ }
891
+ catch (error) {
892
+ sendPresetError(res, error);
893
+ }
894
+ }
895
+ function sendPresetError(res, error) {
896
+ if (error instanceof AdminPresetNotFoundError) {
897
+ res.status(404).json({ error: error.code });
898
+ return;
899
+ }
900
+ if (error instanceof AdminPresetConflictError) {
901
+ res.status(409).json({ error: error.code });
902
+ return;
903
+ }
904
+ if (error instanceof Error) {
905
+ res.status(400).json({ error: 'preset_validation_failed', message: sanitizeErrorMessage(error.message) });
906
+ return;
907
+ }
908
+ throw error;
909
+ }
910
+ function getPresetDraft(body) {
911
+ const value = getBodyValue(body, 'draft');
912
+ const record = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
913
+ const strategy = record.strategy;
914
+ return {
915
+ name: typeof record.name === 'string' ? record.name : '',
916
+ description: typeof record.description === 'string' ? record.description : undefined,
917
+ strategy: strategy === 'and' || strategy === 'advanced' ? strategy : 'or',
918
+ tagQuery: record.tagQuery && typeof record.tagQuery === 'object' && !Array.isArray(record.tagQuery)
919
+ ? record.tagQuery
920
+ : {},
921
+ };
922
+ }
923
+ function buildAboutMetadata(runtime, ui) {
924
+ return {
925
+ productName: '1MCP Agent',
926
+ runtimeVersion: runtime.runtimeVersion,
927
+ adminUiBuildVersion: ui.buildVersion || undefined,
928
+ adminApiProtocolVersion: ADMIN_API_PROTOCOL_VERSION,
929
+ adminUiProtocolVersion: ui.protocolVersion || undefined,
930
+ protocolCompatible: ui.protocolVersion === ADMIN_API_PROTOCOL_VERSION,
931
+ runtime: {
932
+ runtimeScopeId: runtime.runtimeScopeId,
933
+ externalUrl: runtime.externalUrl || undefined,
934
+ },
935
+ build: {
936
+ commit: process.env.ADMIN_UI_BUILD_COMMIT || undefined,
937
+ timestamp: process.env.ADMIN_UI_BUILD_TIMESTAMP || undefined,
938
+ },
939
+ project: {
940
+ repository: normalizeRepositoryUrl(packageMetadata.repository?.url) ?? packageMetadata.homepage,
941
+ documentation: packageMetadata.documentation,
942
+ issues: packageMetadata.bugs?.url,
943
+ license: packageMetadata.license,
944
+ },
945
+ };
946
+ }
947
+ function normalizeRepositoryUrl(value) {
948
+ return value?.replace(/^git\+/u, '').replace(/\.git$/u, '');
949
+ }
950
+ function buildAdminOperationContext(req, options, target) {
951
+ const sessionToken = getAdminSessionCookie(req);
952
+ const session = options.adminService.validateSession(sessionToken);
953
+ if (!session) {
954
+ throw new Error('Admin operation context requested without a valid session');
955
+ }
956
+ const runtimeIdentity = options.getRuntimeIdentity();
957
+ const operationName = operationNameForRequest(req);
958
+ return {
959
+ actor: {
960
+ type: 'admin_session',
961
+ accountId: session.account.id,
962
+ sessionId: sessionToken,
963
+ },
964
+ origin: 'browser',
965
+ target,
966
+ runtimeIdentity: {
967
+ runtimeScopeId: runtimeIdentity.runtimeScopeId,
968
+ runtimeVersion: runtimeIdentity.runtimeVersion,
969
+ },
970
+ request: {
971
+ requestId: getRequestId(req),
972
+ jsonMode: true,
973
+ },
974
+ idempotencyKey: req.header('Idempotency-Key'),
975
+ requestFingerprint: configuredServerRequestFingerprint(operationName, target.id, req.body),
976
+ confirmationFacts: getBodyRecord(req.body, 'confirmationFacts'),
977
+ };
978
+ }
979
+ function cliConfiguredServerConfirmationRequirements(options, targetName, operationName) {
980
+ const identity = options.getRuntimeIdentity();
981
+ if (isLoopbackRuntimeUrl(identity.externalUrl)) {
982
+ return [];
983
+ }
984
+ return [
985
+ {
986
+ code: 'confirm_non_loopback_runtime',
987
+ expected: true,
988
+ target: {
989
+ type: 'configured_server',
990
+ id: targetName,
991
+ },
992
+ },
993
+ {
994
+ code: 'confirmedOperation',
995
+ expected: operationName === 'enableConfiguredServer' ? 'mcp.enable' : 'mcp.disable',
996
+ target: {
997
+ type: 'configured_server',
998
+ id: targetName,
999
+ },
1000
+ },
1001
+ {
1002
+ code: 'confirmedRuntimeScopeId',
1003
+ expected: identity.runtimeScopeId,
1004
+ target: {
1005
+ type: 'configured_server',
1006
+ id: targetName,
1007
+ },
1008
+ },
1009
+ {
1010
+ code: 'confirmationSource',
1011
+ expected: 'cli_flag',
1012
+ target: {
1013
+ type: 'configured_server',
1014
+ id: targetName,
1015
+ },
1016
+ },
1017
+ ];
1018
+ }
1019
+ function buildCliAdminOperationContext(req, options, account, sessionToken, target) {
1020
+ const runtimeIdentity = options.getRuntimeIdentity();
1021
+ const operationName = operationNameForRequest(req);
1022
+ return {
1023
+ actor: {
1024
+ type: 'admin_session',
1025
+ accountId: account.id,
1026
+ sessionId: sessionToken,
1027
+ },
1028
+ origin: 'cli',
1029
+ target,
1030
+ runtimeIdentity: {
1031
+ runtimeScopeId: runtimeIdentity.runtimeScopeId,
1032
+ runtimeVersion: runtimeIdentity.runtimeVersion,
1033
+ },
1034
+ request: {
1035
+ requestId: getRequestId(req),
1036
+ jsonMode: true,
1037
+ },
1038
+ idempotencyKey: req.header('Idempotency-Key'),
1039
+ requestFingerprint: configuredServerRequestFingerprint(operationName, target.id),
1040
+ confirmationFacts: getBodyRecord(req.body, 'confirmationFacts'),
1041
+ };
1042
+ }
1043
+ function configuredServerRequestFingerprint(operationName, targetName, body) {
1044
+ const normalized = stableJsonStringify({
1045
+ schemaVersion: 1,
1046
+ operationName,
1047
+ target: {
1048
+ type: 'configured_server',
1049
+ id: targetName ?? '',
1050
+ },
1051
+ ...(operationName === 'applyConfiguredServerEdit'
1052
+ ? {
1053
+ previewFingerprint: getBodyString(body, 'previewFingerprint'),
1054
+ editDigest: createHash('sha256')
1055
+ .update(stableJsonStringify(getBodyValue(body, 'edit') ?? {}))
1056
+ .digest('hex'),
1057
+ }
1058
+ : {}),
1059
+ });
1060
+ return operationName === 'applyConfiguredServerEdit'
1061
+ ? `configured_server_apply_${createHash('sha256').update(normalized).digest('hex')}`
1062
+ : normalized;
1063
+ }
1064
+ function stableJsonStringify(value) {
1065
+ if (Array.isArray(value)) {
1066
+ return `[${value.map((item) => stableJsonStringify(item)).join(',')}]`;
1067
+ }
1068
+ if (value && typeof value === 'object') {
1069
+ const entries = Object.entries(value)
1070
+ .filter(([, entryValue]) => entryValue !== undefined)
1071
+ .sort(([left], [right]) => left.localeCompare(right));
1072
+ return `{${entries
1073
+ .map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJsonStringify(entryValue)}`)
1074
+ .join(',')}}`;
1075
+ }
1076
+ return JSON.stringify(value);
1077
+ }
1078
+ function cliOperationErrorStatus(status) {
1079
+ switch (status) {
1080
+ case 'idempotency_key_required':
1081
+ return 400;
1082
+ case 'admin_operation_journal_unavailable':
1083
+ return 503;
1084
+ default:
1085
+ return 409;
1086
+ }
1087
+ }
1088
+ function cliOperationErrorMessage(status) {
1089
+ switch (status) {
1090
+ case 'idempotency_key_required':
1091
+ return 'Idempotency key is required';
1092
+ case 'idempotency_conflict':
1093
+ return 'Idempotency key conflicts with another request';
1094
+ case 'operation_in_progress':
1095
+ return 'Admin operation is still in progress';
1096
+ case 'operation_state_unknown':
1097
+ return 'Admin operation state is unknown';
1098
+ case 'mutation_confirmation_required':
1099
+ return 'Additional mutation confirmation is required';
1100
+ case 'mutation_failed':
1101
+ return 'Configured server mutation failed';
1102
+ case 'admin_operation_journal_unavailable':
1103
+ return 'Admin operation journal is unavailable';
1104
+ case 'runtime_scope_mismatch':
1105
+ return 'Runtime scope mismatch';
1106
+ case 'runtime_scope_locked':
1107
+ return 'Runtime scope admin mutations are locked by another writer';
1108
+ default:
1109
+ return 'Admin operation failed';
1110
+ }
1111
+ }
1112
+ function cliOperationErrorDetails(result) {
1113
+ const details = {
1114
+ operationName: result.operationName,
1115
+ };
1116
+ if (result.status === 'operation_state_unknown') {
1117
+ details.target = result.target;
1118
+ details.reservedAt = result.reservedAt;
1119
+ details.recovery = result.recovery;
1120
+ }
1121
+ if (result.status === 'runtime_scope_locked') {
1122
+ details.reason = result.reason;
1123
+ }
1124
+ if (result.status === 'mutation_confirmation_required') {
1125
+ details.confirmationRequirements = result.confirmationRequirements;
1126
+ }
1127
+ if (result.status === 'mutation_failed') {
1128
+ details.error = result.error;
1129
+ }
1130
+ return details;
1131
+ }
1132
+ function operationNameForRequest(req) {
1133
+ if (req.path.endsWith('/enable') || req.path.endsWith('/enable-server')) {
1134
+ return 'enableConfiguredServer';
1135
+ }
1136
+ if (req.path.endsWith('/disable') || req.path.endsWith('/disable-server')) {
1137
+ return 'disableConfiguredServer';
1138
+ }
1139
+ if (req.path.endsWith('/preview')) {
1140
+ return 'previewConfiguredServerEdit';
1141
+ }
1142
+ if (req.path.endsWith('/apply')) {
1143
+ return 'applyConfiguredServerEdit';
1144
+ }
1145
+ if (req.method === 'GET' && req.path.startsWith('/api/configured-servers/')) {
1146
+ return 'getConfiguredServerDetail';
1147
+ }
1148
+ return 'listConfiguredServers';
1149
+ }
1150
+ function getRequestId(req) {
1151
+ const requestId = req.header('X-Request-Id');
1152
+ return requestId?.trim() || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
1153
+ }
1154
+ function toAdminConsoleAccount(account) {
1155
+ return {
1156
+ id: account.id,
1157
+ username: account.username,
1158
+ role: account.role,
1159
+ };
1160
+ }
1161
+ function toCliAdminAccount(account) {
1162
+ return {
1163
+ username: account.username,
1164
+ role: account.role,
1165
+ };
1166
+ }
1167
+ function toCliRuntimeIdentity(identity) {
1168
+ return {
1169
+ identityProtocolVersion: identity.identityProtocolVersion,
1170
+ runtimeScopeId: identity.runtimeScopeId,
1171
+ runtimeVersion: identity.runtimeVersion,
1172
+ };
1173
+ }
1174
+ function sanitizeOAuthDashboard(dashboard) {
1175
+ if (dashboard.status !== 'ready') {
1176
+ return dashboard;
1177
+ }
1178
+ return {
1179
+ ...dashboard,
1180
+ services: dashboard.services.map((service) => ({
1181
+ ...service,
1182
+ lastError: service.lastError ? sanitizeErrorMessage(service.lastError) : undefined,
1183
+ })),
1184
+ };
1185
+ }
1186
+ function getBodyValue(body, key) {
1187
+ if (!body || typeof body !== 'object') {
1188
+ return undefined;
1189
+ }
1190
+ return body[key];
1191
+ }
1192
+ function getBodyString(body, key) {
1193
+ if (!body || typeof body !== 'object') {
1194
+ return '';
1195
+ }
1196
+ const value = body[key];
1197
+ return typeof value === 'string' ? value : '';
1198
+ }
1199
+ function getBodyRecord(body, key) {
1200
+ if (!body || typeof body !== 'object') {
1201
+ return undefined;
1202
+ }
1203
+ const value = body[key];
1204
+ return value && typeof value === 'object' && !Array.isArray(value)
1205
+ ? { ...value }
1206
+ : undefined;
1207
+ }
1208
+ function isLoopbackRuntimeUrl(value) {
1209
+ try {
1210
+ const hostname = new URL(value).hostname.toLowerCase();
1211
+ return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '[::1]';
1212
+ }
1213
+ catch {
1214
+ return false;
1215
+ }
1216
+ }
1217
+ //# sourceMappingURL=adminRoutes.js.map