@stage-labs/metro 0.1.0-beta.31 → 0.1.0-beta.32
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/dist/cli.js +5 -0
- package/dist/runtime.js +4 -1
- package/dist/serve.js +57 -0
- package/package.json +1 -1
- package/runtime/node_modules/@metro-labs/mcp/package.json +1 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +5 -5
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +16 -15
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-connector-api.ts +9 -9
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-pair.ts +1 -1
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +8 -7
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +5 -3
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +109 -71
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +16 -7
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +14 -14
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +8 -8
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +3 -3
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +12 -2
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +11 -4
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +158 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-secret.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +31 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +1 -1
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +10 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +17 -17
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +1 -1
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +4 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-config.ts +27 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +9 -9
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-auth.ts +82 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-nonces.ts +43 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-routes.ts +89 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +2 -5
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +15 -16
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-connectors.ts +9 -9
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +31 -31
- package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +250 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +152 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +16 -7
- package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +48 -39
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +7 -7
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +2 -1
- package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +22 -12
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +4 -4
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +1 -1
- package/runtime/runtime.json +1 -1
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
|
@@ -4,7 +4,13 @@ import { type Server } from 'node:http';
|
|
|
4
4
|
import { selfLine, userSelf } from './events.js';
|
|
5
5
|
import { setTrainCallBackend } from './train-call.js';
|
|
6
6
|
import { errMsg, log, logFatalSync } from './log.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
acquireLock,
|
|
9
|
+
isLocalMode,
|
|
10
|
+
loadMetroEnv,
|
|
11
|
+
STATE_DIR,
|
|
12
|
+
trainsDir,
|
|
13
|
+
} from './paths.js';
|
|
8
14
|
import { installCrashGuard, markDaemonReady } from './crash-guard.js';
|
|
9
15
|
import {
|
|
10
16
|
loadTunnelConfig,
|
|
@@ -12,7 +18,7 @@ import {
|
|
|
12
18
|
warnOnLegacyWebhooks,
|
|
13
19
|
webhookPort,
|
|
14
20
|
} from './tunnel.js';
|
|
15
|
-
import { TrainSupervisor
|
|
21
|
+
import { TrainSupervisor } from './supervisor.js';
|
|
16
22
|
import {
|
|
17
23
|
makeEmit,
|
|
18
24
|
startWebhookServer,
|
|
@@ -27,10 +33,15 @@ import {
|
|
|
27
33
|
} from '../db/runtimes.js';
|
|
28
34
|
import {
|
|
29
35
|
blockedStationsFor,
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
mintAgentCodeForUser,
|
|
37
|
+
releaseRuntimeForUser,
|
|
32
38
|
} from '../db/runtime-admin.js';
|
|
33
39
|
import { loadAgentForRuntime, localAgentKey } from '../db/materialize.js';
|
|
40
|
+
import { fileSource } from '../db/file-source.js';
|
|
41
|
+
import { ensureLocalSessionSecret } from './local-secret.js';
|
|
42
|
+
import { localConnectHint, localSessionApis } from './local-mode.js';
|
|
43
|
+
import type { ModeInfo } from './mode-api.js';
|
|
44
|
+
import type { SessionApis } from './session-apis.js';
|
|
34
45
|
import {
|
|
35
46
|
agentLiveness,
|
|
36
47
|
closeAgentSession,
|
|
@@ -48,45 +59,46 @@ import {
|
|
|
48
59
|
materializeFromDb,
|
|
49
60
|
reloadAccountsFromDb,
|
|
50
61
|
reloadFrom,
|
|
62
|
+
type ReloadedStations,
|
|
51
63
|
} from '../db/materialize.js';
|
|
52
64
|
import {
|
|
53
65
|
httpSource,
|
|
54
66
|
runtimeConfigFromEnv,
|
|
55
67
|
startRuntimePoller,
|
|
56
68
|
} from './runtime-source.js';
|
|
57
|
-
import {
|
|
69
|
+
import { createAgentForUser, deleteAgentForUser, listAgentsForUser, ownedAgentOrThrow, resetAgentKeyForUser, type ResetAgentKey,} from '../db/agent-admin.js';
|
|
58
70
|
import {
|
|
59
71
|
attachAccountToAgent,
|
|
60
72
|
detachAccountFromAgent,
|
|
61
73
|
} from '../db/account-attach.js';
|
|
62
74
|
import {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
createConnectorForUser,
|
|
76
|
+
createPendingConnectorForUser,
|
|
77
|
+
deleteConnectorForUser,
|
|
78
|
+
disconnectConnectorForUser,
|
|
67
79
|
connectorNamesByIds,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
getConnectorForUser,
|
|
81
|
+
listConnectorsForUser,
|
|
82
|
+
renameConnectorForUser,
|
|
83
|
+
reconnectConnectorForUser,
|
|
84
|
+
verifyConnectorForUser,
|
|
73
85
|
} from '../db/connectors.js';
|
|
74
86
|
import { relayTarget } from '../db/connector-relay.js';
|
|
75
87
|
import {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
addMemberForUser,
|
|
89
|
+
createProjectForUser,
|
|
90
|
+
deleteProjectForUser,
|
|
91
|
+
listMembersForUser,
|
|
92
|
+
listProjectsForUser,
|
|
93
|
+
removeMemberForUser,
|
|
94
|
+
renameProjectForUser,
|
|
95
|
+
setMemberRoleForUser,
|
|
84
96
|
} from '../db/projects.js';
|
|
85
97
|
import {
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
addConnectorToAgentForUser,
|
|
99
|
+
agentConnectorsForUser,
|
|
88
100
|
connectorIdsByAgent,
|
|
89
|
-
|
|
101
|
+
removeConnectorFromAgentForUser,
|
|
90
102
|
} from '../db/agent-connectors.js';
|
|
91
103
|
import type { AgentConnectorApiDeps } from './agent-connector-api.js';
|
|
92
104
|
import type { StationName } from '../db/schema.js';
|
|
@@ -124,12 +136,15 @@ setTrainCallBackend((train, action, args) =>
|
|
|
124
136
|
);
|
|
125
137
|
|
|
126
138
|
const runtime = runtimeConfigFromEnv();
|
|
127
|
-
const
|
|
139
|
+
const linkedSource = runtime === null ? null : httpSource(runtime);
|
|
140
|
+
const localSource = linkedSource ?? (isLocalMode() ? fileSource : null);
|
|
128
141
|
|
|
129
142
|
function announceLocalEndpoint(): void {
|
|
143
|
+
if (isLocalMode()) process.stderr.write(`\n${localConnectHint(webhookPort())}`);
|
|
130
144
|
const key = localAgentKey();
|
|
131
145
|
if (key === null) {
|
|
132
|
-
log.
|
|
146
|
+
if (isLocalMode()) log.info('no agent on this machine yet');
|
|
147
|
+
else log.warn('this agent has no key — reset it in the web UI to connect an agent');
|
|
133
148
|
return;
|
|
134
149
|
}
|
|
135
150
|
const url = `http://127.0.0.1:${String(webhookPort())}/mcp?token=${key}`;
|
|
@@ -153,8 +168,11 @@ async function stopLocalStations(): Promise<void> {
|
|
|
153
168
|
for (const station of supervisor.running()) await supervisor.stopTrain(station);
|
|
154
169
|
}
|
|
155
170
|
|
|
171
|
+
const reloadStations = (): Promise<ReloadedStations> =>
|
|
172
|
+
localSource === null ? reloadAccountsFromDb() : reloadFrom(localSource);
|
|
173
|
+
|
|
156
174
|
async function syncStations(station: StationName): Promise<void> {
|
|
157
|
-
const { removed } = await
|
|
175
|
+
const { removed } = await reloadStations();
|
|
158
176
|
if (stationByName(station)?.hasTrain === false) return;
|
|
159
177
|
if (removed.includes(station)) await supervisor.stopTrain(station);
|
|
160
178
|
else supervisor.requestReload(station);
|
|
@@ -162,11 +180,11 @@ async function syncStations(station: StationName): Promise<void> {
|
|
|
162
180
|
|
|
163
181
|
const attachSessions = new AttachSessions({
|
|
164
182
|
authorize: async (owner) => {
|
|
165
|
-
await ownedAgentOrThrow(owner.
|
|
183
|
+
await ownedAgentOrThrow(owner.subject, owner.agentId);
|
|
166
184
|
},
|
|
167
185
|
complete: async (owner, station, config) => {
|
|
168
186
|
const ref = await attachAccountToAgent(
|
|
169
|
-
owner.
|
|
187
|
+
owner.subject,
|
|
170
188
|
owner.agentId,
|
|
171
189
|
station,
|
|
172
190
|
config,
|
|
@@ -186,10 +204,10 @@ const attachSessions = new AttachSessions({
|
|
|
186
204
|
});
|
|
187
205
|
|
|
188
206
|
async function resetAgentKey(
|
|
189
|
-
|
|
207
|
+
subject: string,
|
|
190
208
|
id: string,
|
|
191
209
|
): Promise<ResetAgentKey> {
|
|
192
|
-
const reset = await
|
|
210
|
+
const reset = await resetAgentKeyForUser(subject, id);
|
|
193
211
|
const closed = await closeAgentSession(id);
|
|
194
212
|
log.info(
|
|
195
213
|
{ agent: reset.name, id: reset.id, sessionClosed: closed },
|
|
@@ -200,14 +218,14 @@ async function resetAgentKey(
|
|
|
200
218
|
|
|
201
219
|
const agentApi: AgentApiDeps = {
|
|
202
220
|
attachSessions,
|
|
203
|
-
listAgents:
|
|
204
|
-
createAgent:
|
|
205
|
-
deleteAgent:
|
|
221
|
+
listAgents: listAgentsForUser,
|
|
222
|
+
createAgent: createAgentForUser,
|
|
223
|
+
deleteAgent: deleteAgentForUser,
|
|
206
224
|
resetKey: resetAgentKey,
|
|
207
225
|
gatherAccounts: gatherAccountsForAgents,
|
|
208
226
|
capabilities: accountStationCapabilities,
|
|
209
227
|
liveness: agentLiveness,
|
|
210
|
-
releaseRuntime:
|
|
228
|
+
releaseRuntime: releaseRuntimeForUser,
|
|
211
229
|
connectorIds: connectorIdsByAgent,
|
|
212
230
|
runtimes: runtimeLabels,
|
|
213
231
|
prepareAccount,
|
|
@@ -225,66 +243,86 @@ const runApi: RunApiDeps = {
|
|
|
225
243
|
};
|
|
226
244
|
|
|
227
245
|
const agentConnectorApi: AgentConnectorApiDeps = {
|
|
228
|
-
agentConnectors:
|
|
229
|
-
addConnector:
|
|
230
|
-
removeConnector:
|
|
231
|
-
mintCode:
|
|
246
|
+
agentConnectors: agentConnectorsForUser,
|
|
247
|
+
addConnector: addConnectorToAgentForUser,
|
|
248
|
+
removeConnector: removeConnectorFromAgentForUser,
|
|
249
|
+
mintCode: mintAgentCodeForUser,
|
|
232
250
|
};
|
|
233
251
|
|
|
234
252
|
const projectApi: ProjectApiDeps = {
|
|
235
|
-
listProjects:
|
|
236
|
-
createProject:
|
|
237
|
-
renameProject:
|
|
238
|
-
deleteProject:
|
|
239
|
-
listMembers:
|
|
240
|
-
addMember:
|
|
241
|
-
setMemberRole:
|
|
242
|
-
removeMember:
|
|
253
|
+
listProjects: listProjectsForUser,
|
|
254
|
+
createProject: createProjectForUser,
|
|
255
|
+
renameProject: renameProjectForUser,
|
|
256
|
+
deleteProject: deleteProjectForUser,
|
|
257
|
+
listMembers: listMembersForUser,
|
|
258
|
+
addMember: addMemberForUser,
|
|
259
|
+
setMemberRole: setMemberRoleForUser,
|
|
260
|
+
removeMember: removeMemberForUser,
|
|
243
261
|
};
|
|
244
262
|
|
|
245
263
|
const connectorApi: ConnectorApiDeps = {
|
|
246
|
-
listConnectors:
|
|
264
|
+
listConnectors: listConnectorsForUser,
|
|
247
265
|
connectorNamesByIds,
|
|
248
|
-
agentConnectors:
|
|
266
|
+
agentConnectors: agentConnectorsForUser,
|
|
249
267
|
fenceRuntime,
|
|
250
|
-
renameConnector:
|
|
251
|
-
createConnector:
|
|
252
|
-
createPendingConnector:
|
|
253
|
-
reconnectConnector:
|
|
254
|
-
getConnector:
|
|
255
|
-
verifyConnector:
|
|
256
|
-
disconnectConnector:
|
|
257
|
-
deleteConnector:
|
|
268
|
+
renameConnector: renameConnectorForUser,
|
|
269
|
+
createConnector: createConnectorForUser,
|
|
270
|
+
createPendingConnector: createPendingConnectorForUser,
|
|
271
|
+
reconnectConnector: reconnectConnectorForUser,
|
|
272
|
+
getConnector: getConnectorForUser,
|
|
273
|
+
verifyConnector: verifyConnectorForUser,
|
|
274
|
+
disconnectConnector: disconnectConnectorForUser,
|
|
275
|
+
deleteConnector: deleteConnectorForUser,
|
|
258
276
|
};
|
|
259
277
|
|
|
278
|
+
const hostedMode = (): ModeInfo => ({
|
|
279
|
+
mode: linkedSource === null ? 'hosted' : 'linked',
|
|
280
|
+
owner: null,
|
|
281
|
+
project: null,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
function sessionApis(): SessionApis {
|
|
285
|
+
if (isLocalMode())
|
|
286
|
+
return localSessionApis({
|
|
287
|
+
syncStations,
|
|
288
|
+
closeAgentSession,
|
|
289
|
+
gatherAccounts: gatherAccountsForAgents,
|
|
290
|
+
capabilities: accountStationCapabilities,
|
|
291
|
+
liveness: agentLiveness,
|
|
292
|
+
prepareAccount,
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
agentApi,
|
|
296
|
+
agentConnectorApi,
|
|
297
|
+
connectorApi,
|
|
298
|
+
projectApi,
|
|
299
|
+
runApi,
|
|
300
|
+
relayApi: { target: relayTarget, fence: fenceRuntime },
|
|
301
|
+
mode: hostedMode,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
260
305
|
async function main(): Promise<void> {
|
|
306
|
+
if (isLocalMode()) ensureLocalSessionSecret();
|
|
261
307
|
if (localSource === null) await materializeFromDb();
|
|
262
|
-
else await materializeFrom(localSource);
|
|
308
|
+
else await materializeFrom(localSource, { allowEmpty: linkedSource === null });
|
|
263
309
|
warnOnLegacyWebhooks();
|
|
264
310
|
supervisor.start();
|
|
265
311
|
const metroMcp = await createMetroMcp();
|
|
266
312
|
webhookServer = await startWebhookServer(
|
|
267
313
|
emit,
|
|
268
|
-
|
|
269
|
-
agentApi,
|
|
270
|
-
agentConnectorApi,
|
|
271
|
-
connectorApi,
|
|
272
|
-
projectApi,
|
|
273
|
-
runApi,
|
|
274
|
-
relayApi: { target: relayTarget, fence: fenceRuntime },
|
|
275
|
-
},
|
|
314
|
+
sessionApis(),
|
|
276
315
|
metroMcp.httpHandler,
|
|
277
316
|
metroCall,
|
|
278
317
|
);
|
|
279
318
|
metroMcp.startInbound();
|
|
280
319
|
startUploadReaper();
|
|
281
|
-
if (
|
|
320
|
+
if (linkedSource !== null)
|
|
282
321
|
startRuntimePoller({ sync: syncLocal, stopAll: stopLocalStations });
|
|
283
|
-
|
|
284
|
-
}
|
|
322
|
+
if (localSource !== null) announceLocalEndpoint();
|
|
285
323
|
tunnel?.start();
|
|
286
324
|
log.info(
|
|
287
|
-
{ tunnel: !!tunnel, trainsDir:
|
|
325
|
+
{ tunnel: !!tunnel, trainsDir: trainsDir(), mcp: '/' },
|
|
288
326
|
'dispatcher ready',
|
|
289
327
|
);
|
|
290
328
|
markDaemonReady();
|
|
@@ -13,7 +13,7 @@ import { extractToken } from '../mcp/request-identity.js';
|
|
|
13
13
|
import { publicBaseOrDefault } from './attach-serve.js';
|
|
14
14
|
import { AGENT_CODE_RE, takeAgentCode } from './agent-pair.js';
|
|
15
15
|
import { relayServersJson } from './connector-json.js';
|
|
16
|
-
import { sessionTtlFromEnv } from './
|
|
16
|
+
import { sessionTtlFromEnv } from './session-config.js';
|
|
17
17
|
import { signAgentToken } from './session.js';
|
|
18
18
|
import type { ConnectorApiDeps } from './connector-api.js';
|
|
19
19
|
|
|
@@ -58,14 +58,19 @@ async function handleClaim(
|
|
|
58
58
|
});
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
const agent = await deps.agentConnectors(taken.
|
|
61
|
+
const agent = await deps.agentConnectors(taken.subject, taken.agentId);
|
|
62
62
|
const token = signAgentToken(
|
|
63
|
-
{
|
|
63
|
+
{ subject: taken.subject, agentId: agent.id },
|
|
64
64
|
secret,
|
|
65
65
|
{ ttlSec: sessionTtlFromEnv() },
|
|
66
66
|
);
|
|
67
|
-
log.info({
|
|
68
|
-
sendJson(req, res, 200, {
|
|
67
|
+
log.info({ subject: taken.subject, agent: agent.id }, 'cli: code claimed');
|
|
68
|
+
sendJson(req, res, 200, {
|
|
69
|
+
token,
|
|
70
|
+
email: taken.subject,
|
|
71
|
+
subject: taken.subject,
|
|
72
|
+
agent: agent.name,
|
|
73
|
+
});
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
async function handleRead(
|
|
@@ -80,9 +85,13 @@ async function handleRead(
|
|
|
80
85
|
return;
|
|
81
86
|
}
|
|
82
87
|
await assertLease(who, deps.fenceRuntime);
|
|
83
|
-
const agent = await deps.agentConnectors(who.
|
|
88
|
+
const agent = await deps.agentConnectors(who.subject, who.agentId);
|
|
84
89
|
if (path === SESSION_PATH) {
|
|
85
|
-
sendJson(req, res, 200, {
|
|
90
|
+
sendJson(req, res, 200, {
|
|
91
|
+
email: who.subject,
|
|
92
|
+
subject: who.subject,
|
|
93
|
+
agent: agent.name,
|
|
94
|
+
});
|
|
86
95
|
return;
|
|
87
96
|
}
|
|
88
97
|
const entries = await deps.connectorNamesByIds(agent.connectorIds);
|
|
@@ -35,23 +35,23 @@ const asText = (value: unknown): string =>
|
|
|
35
35
|
typeof value === 'string' ? value : '';
|
|
36
36
|
|
|
37
37
|
export interface ConnectorApiDeps extends OAuthRouteDeps {
|
|
38
|
-
listConnectors: (
|
|
38
|
+
listConnectors: (subject: string, project: string) => Promise<Connector[]>;
|
|
39
39
|
connectorNamesByIds: (ids: string[]) => Promise<RelayServerEntry[]>;
|
|
40
|
-
agentConnectors: (
|
|
40
|
+
agentConnectors: (subject: string, agentId: string) => Promise<AgentConnectors>;
|
|
41
41
|
fenceRuntime: Fence;
|
|
42
42
|
createConnector: (
|
|
43
|
-
|
|
43
|
+
subject: string,
|
|
44
44
|
project: string,
|
|
45
45
|
input: ConnectorInput,
|
|
46
46
|
) => Promise<Connector>;
|
|
47
|
-
verifyConnector: (
|
|
48
|
-
disconnectConnector: (
|
|
47
|
+
verifyConnector: (subject: string, id: string) => Promise<ConnectorCheck>;
|
|
48
|
+
disconnectConnector: (subject: string, id: string) => Promise<Connector>;
|
|
49
49
|
renameConnector: (
|
|
50
|
-
|
|
50
|
+
subject: string,
|
|
51
51
|
id: string,
|
|
52
52
|
name: string,
|
|
53
53
|
) => Promise<Connector>;
|
|
54
|
-
deleteConnector: (
|
|
54
|
+
deleteConnector: (subject: string, id: string) => Promise<DeletedConnector>;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
type Routable =
|
|
@@ -115,7 +115,7 @@ async function handleList(
|
|
|
115
115
|
sendJson(req, res, 400, { error: 'a project is required' });
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
|
-
const rows = await deps.listConnectors(session.
|
|
118
|
+
const rows = await deps.listConnectors(session.subject, project);
|
|
119
119
|
sendJson(req, res, 200, {
|
|
120
120
|
connectors: rows.map((row) => connectorPayload(row)),
|
|
121
121
|
});
|
|
@@ -135,7 +135,7 @@ async function handleCreate(
|
|
|
135
135
|
}
|
|
136
136
|
const offered = asText(bodyField(body, 'value')).trim() !== '';
|
|
137
137
|
try {
|
|
138
|
-
const created = await deps.createConnector(session.
|
|
138
|
+
const created = await deps.createConnector(session.subject, project, {
|
|
139
139
|
name: bodyField(body, 'name'),
|
|
140
140
|
url: bodyField(body, 'url'),
|
|
141
141
|
header: bodyField(body, 'header'),
|
|
@@ -161,7 +161,7 @@ async function handleVerify(
|
|
|
161
161
|
session: ApiSession,
|
|
162
162
|
id: string,
|
|
163
163
|
): Promise<void> {
|
|
164
|
-
const check = await deps.verifyConnector(session.
|
|
164
|
+
const check = await deps.verifyConnector(session.subject, id);
|
|
165
165
|
log.info(
|
|
166
166
|
{ id: check.id, name: check.name, ok: check.ok },
|
|
167
167
|
'connector-api: re-verified connector',
|
|
@@ -176,7 +176,7 @@ async function handleDisconnect(
|
|
|
176
176
|
session: ApiSession,
|
|
177
177
|
id: string,
|
|
178
178
|
): Promise<void> {
|
|
179
|
-
const row = await deps.disconnectConnector(session.
|
|
179
|
+
const row = await deps.disconnectConnector(session.subject, id);
|
|
180
180
|
log.info(
|
|
181
181
|
{ id: row.id, name: row.name },
|
|
182
182
|
'connector-api: signed the connector out',
|
|
@@ -191,7 +191,7 @@ async function handleDelete(
|
|
|
191
191
|
session: ApiSession,
|
|
192
192
|
id: string,
|
|
193
193
|
): Promise<void> {
|
|
194
|
-
const gone = await deps.deleteConnector(session.
|
|
194
|
+
const gone = await deps.deleteConnector(session.subject, id);
|
|
195
195
|
log.info(
|
|
196
196
|
{ id: gone.id, name: gone.name },
|
|
197
197
|
'connector-api: deleted connector',
|
|
@@ -210,7 +210,7 @@ async function handleConnector(
|
|
|
210
210
|
await handleDelete(req, res, deps, session, id);
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
|
-
const row = await deps.getConnector(session.
|
|
213
|
+
const row = await deps.getConnector(session.subject, id);
|
|
214
214
|
sendJson(req, res, 200, connectorPayload(row, true));
|
|
215
215
|
}
|
|
216
216
|
|
|
@@ -226,7 +226,7 @@ async function handleRename(
|
|
|
226
226
|
sendJson(req, res, 400, { error: 'name is required' });
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
|
-
const row = await deps.renameConnector(session.
|
|
229
|
+
const row = await deps.renameConnector(session.subject, id, name);
|
|
230
230
|
log.info({ id: row.id, name: row.name }, 'connector-api: renamed connector');
|
|
231
231
|
sendJson(req, res, 200, connectorPayload(row));
|
|
232
232
|
}
|
|
@@ -24,16 +24,16 @@ export function hostOf(url: string): string {
|
|
|
24
24
|
|
|
25
25
|
export interface OAuthRouteDeps {
|
|
26
26
|
createPendingConnector: (
|
|
27
|
-
|
|
27
|
+
subject: string,
|
|
28
28
|
project: string,
|
|
29
29
|
input: { name: unknown; url: unknown },
|
|
30
30
|
) => Promise<Connector>;
|
|
31
31
|
reconnectConnector: (
|
|
32
|
-
|
|
32
|
+
subject: string,
|
|
33
33
|
id: string,
|
|
34
34
|
auth: OAuthAuth,
|
|
35
35
|
) => Promise<Connector>;
|
|
36
|
-
getConnector: (
|
|
36
|
+
getConnector: (subject: string, id: string) => Promise<Connector>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export async function startOAuth(
|
|
@@ -46,12 +46,12 @@ export async function startOAuth(
|
|
|
46
46
|
payload: (row: Connector) => Record<string, unknown>,
|
|
47
47
|
): Promise<void> {
|
|
48
48
|
const url = parseConnectorUrl(bodyField(body, 'url'));
|
|
49
|
-
const row = await deps.createPendingConnector(session.
|
|
49
|
+
const row = await deps.createPendingConnector(session.subject, project, {
|
|
50
50
|
name: bodyField(body, 'name'),
|
|
51
51
|
url: bodyField(body, 'url'),
|
|
52
52
|
});
|
|
53
53
|
const authorize = await beginOAuth({
|
|
54
|
-
|
|
54
|
+
subject: session.subject,
|
|
55
55
|
name: row.name,
|
|
56
56
|
url,
|
|
57
57
|
returnTo: asText(bodyField(body, 'returnTo')),
|
|
@@ -71,11 +71,11 @@ export async function handleConnect(
|
|
|
71
71
|
session: ApiSession,
|
|
72
72
|
id: string,
|
|
73
73
|
): Promise<void> {
|
|
74
|
-
const row = await deps.getConnector(session.
|
|
74
|
+
const row = await deps.getConnector(session.subject, id);
|
|
75
75
|
const url = parseConnectorUrl(row.url);
|
|
76
76
|
const body = await readJsonBody(req);
|
|
77
77
|
const authorize = await beginOAuth({
|
|
78
|
-
|
|
78
|
+
subject: session.subject,
|
|
79
79
|
name: row.name,
|
|
80
80
|
url,
|
|
81
81
|
returnTo: asText(bodyField(body, 'returnTo')),
|
|
@@ -110,7 +110,7 @@ async function saveOAuth(
|
|
|
110
110
|
entry: PendingAuth,
|
|
111
111
|
auth: OAuthAuth,
|
|
112
112
|
): Promise<Connector> {
|
|
113
|
-
return deps.reconnectConnector(entry.
|
|
113
|
+
return deps.reconnectConnector(entry.subject, entry.connectorId, auth);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
async function settleCallback(
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type OAuthAuth,
|
|
5
5
|
type OAuthTokens,
|
|
6
6
|
} from './connector-verify.js';
|
|
7
|
-
import { validateReturnTo } from './
|
|
7
|
+
import { validateReturnTo } from './session-config.js';
|
|
8
8
|
import {
|
|
9
9
|
authorizeUrl,
|
|
10
10
|
exchangeCode,
|
|
@@ -32,7 +32,7 @@ const resourceOf = (url: URL): string => url.toString();
|
|
|
32
32
|
|
|
33
33
|
export interface BeginInput {
|
|
34
34
|
connectorId: string;
|
|
35
|
-
|
|
35
|
+
subject: string;
|
|
36
36
|
name: string;
|
|
37
37
|
url: URL;
|
|
38
38
|
returnTo: string;
|
|
@@ -47,7 +47,7 @@ export async function beginOAuth(input: BeginInput): Promise<string> {
|
|
|
47
47
|
const verifier = newVerifier();
|
|
48
48
|
const resource = resourceOf(input.url);
|
|
49
49
|
const state = startPending({
|
|
50
|
-
|
|
50
|
+
subject: input.subject,
|
|
51
51
|
name: input.name,
|
|
52
52
|
url: input.url.toString(),
|
|
53
53
|
resource,
|
|
@@ -27,7 +27,8 @@ import {
|
|
|
27
27
|
} from './tunnel.js';
|
|
28
28
|
import { attachmentEventUrl, handleAttachRequest } from './attach-serve.js';
|
|
29
29
|
import { webhookEntry } from '@metro-labs/webhook';
|
|
30
|
-
import {
|
|
30
|
+
import { handleSiweAuthRequest } from './siwe-routes.js';
|
|
31
|
+
import { handleModeRequest } from './mode-api.js';
|
|
31
32
|
import { handleUploadRequest } from './upload-api.js';
|
|
32
33
|
import {
|
|
33
34
|
handleMonitorRequest,
|
|
@@ -323,6 +324,15 @@ async function handleWebhookRoute(
|
|
|
323
324
|
return true;
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
function handleSignInRoutes(
|
|
328
|
+
req: IncomingMessage,
|
|
329
|
+
res: ServerResponse,
|
|
330
|
+
apis: SessionApis,
|
|
331
|
+
): boolean {
|
|
332
|
+
if (handleSiweAuthRequest(req, res, apis.siwe)) return true;
|
|
333
|
+
return apis.mode !== undefined && handleModeRequest(req, res, apis.mode);
|
|
334
|
+
}
|
|
335
|
+
|
|
326
336
|
async function handlePreMcpRoutes(
|
|
327
337
|
req: IncomingMessage,
|
|
328
338
|
res: ServerResponse,
|
|
@@ -331,7 +341,7 @@ async function handlePreMcpRoutes(
|
|
|
331
341
|
monitorCall?: MonitorCall,
|
|
332
342
|
): Promise<boolean> {
|
|
333
343
|
if (handleHealth(req, res)) return true;
|
|
334
|
-
if (
|
|
344
|
+
if (handleSignInRoutes(req, res, apis)) return true;
|
|
335
345
|
if (handleSessionApis(req, res, apis)) return true;
|
|
336
346
|
if (handleUploadRequest(req, res)) return true;
|
|
337
347
|
if (handleAttachRequest(req, res)) return true;
|
|
@@ -71,9 +71,16 @@ export function daemonSelf(): Line {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export function selfLine(): Line | null {
|
|
74
|
-
if (process.env.CLAUDECODE)
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
if (!process.env.CLAUDECODE) return null;
|
|
75
|
+
const s = claudeSessionId();
|
|
76
|
+
if (!s) return null;
|
|
77
|
+
try {
|
|
78
|
+
return Line.claude(claudeUserId(), s);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
log.warn(
|
|
81
|
+
{ reason: errMsg(e) },
|
|
82
|
+
'could not resolve a Claude account id; relaying with no self line',
|
|
83
|
+
);
|
|
84
|
+
return null;
|
|
77
85
|
}
|
|
78
|
-
return null;
|
|
79
86
|
}
|