@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
package/dist/cli.js
CHANGED
|
@@ -10,11 +10,15 @@ import { launchClaude } from './claude.js';
|
|
|
10
10
|
import { bedrock } from './bedrock.js';
|
|
11
11
|
import { installPlugin, syncPluginServers } from './plugin.js';
|
|
12
12
|
import { update } from './update.js';
|
|
13
|
+
import { serve } from './serve.js';
|
|
13
14
|
import { currentVersion } from './version.js';
|
|
14
15
|
const USAGE = `metro — the command line for your MCP connectors
|
|
15
16
|
|
|
16
17
|
metro start <agent-id> [--detach]
|
|
17
18
|
run that agent's stations on this machine
|
|
19
|
+
metro serve [--port <n>]
|
|
20
|
+
run a daemon of your own: its agents and stations live in ~/.metro/agents
|
|
21
|
+
here, and metro.box manages it through the link it prints
|
|
18
22
|
metro stop [agent-id]
|
|
19
23
|
stop every metro daemon on this machine, however it was started
|
|
20
24
|
metro status <agent-id>
|
|
@@ -145,6 +149,7 @@ const COMMANDS = {
|
|
|
145
149
|
return 0;
|
|
146
150
|
},
|
|
147
151
|
start: () => start(process.argv.slice(3)),
|
|
152
|
+
serve: () => serve(process.argv.slice(3)),
|
|
148
153
|
stop: () => stopDaemon(process.argv.slice(3)),
|
|
149
154
|
status: () => status(process.argv.slice(3)),
|
|
150
155
|
logs: () => logs(process.argv.slice(3)),
|
package/dist/runtime.js
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync } from 'node:fs';
|
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
7
|
import { metroUrl } from './store.js';
|
|
8
8
|
const AGENT_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{10}$/;
|
|
9
|
-
const SERVER_ENTRY = join('node_modules', '@metro-labs', 'mcp', 'src', 'server.ts');
|
|
9
|
+
export const SERVER_ENTRY = join('node_modules', '@metro-labs', 'mcp', 'src', 'server.ts');
|
|
10
10
|
export class MissingRuntime extends Error {
|
|
11
11
|
}
|
|
12
12
|
export function runtimeDir() {
|
|
@@ -76,6 +76,9 @@ export function daemonPlan(opts) {
|
|
|
76
76
|
}
|
|
77
77
|
export function runDaemon(agentId, plan) {
|
|
78
78
|
process.stderr.write(`Starting metro for ${agentId}\n`);
|
|
79
|
+
return spawnPlan(plan);
|
|
80
|
+
}
|
|
81
|
+
export function spawnPlan(plan) {
|
|
79
82
|
const child = spawn(plan.command, plan.args, {
|
|
80
83
|
cwd: plan.cwd,
|
|
81
84
|
env: plan.env,
|
package/dist/serve.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findBun, localPort, runtimeDir, SERVER_ENTRY, spawnPlan, } from './runtime.js';
|
|
4
|
+
const SCRUBBED = new Set(['METRO_RUN_TOKEN', 'METRO_AGENT', 'DATABASE_URL']);
|
|
5
|
+
const PORT_FLAG = /^--port=(.*)$/;
|
|
6
|
+
const USAGE = 'usage: metro serve [--port <n>]';
|
|
7
|
+
function portOf(raw) {
|
|
8
|
+
const n = Number(raw);
|
|
9
|
+
if (raw === undefined || raw === '' || !Number.isInteger(n) || n < 1 || n > 65535)
|
|
10
|
+
throw new Error(`'${raw ?? ''}' is not a port — ${USAGE}`);
|
|
11
|
+
return n;
|
|
12
|
+
}
|
|
13
|
+
export function parseServeArgs(argv) {
|
|
14
|
+
let port = localPort();
|
|
15
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
16
|
+
const arg = argv[i] ?? '';
|
|
17
|
+
const inline = PORT_FLAG.exec(arg);
|
|
18
|
+
if (inline) {
|
|
19
|
+
port = portOf(inline[1]);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (arg === '--port' || arg === '-p') {
|
|
23
|
+
port = portOf(argv[i + 1]);
|
|
24
|
+
i += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`unknown argument '${arg}' — ${USAGE}`);
|
|
28
|
+
}
|
|
29
|
+
return { port };
|
|
30
|
+
}
|
|
31
|
+
export function serveStateDir() {
|
|
32
|
+
const xdg = process.env.XDG_CACHE_HOME?.trim();
|
|
33
|
+
const base = xdg === undefined || xdg === '' ? join(homedir(), '.cache') : xdg;
|
|
34
|
+
return join(base, 'metro', 'serve');
|
|
35
|
+
}
|
|
36
|
+
export function servePlan(opts) {
|
|
37
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !SCRUBBED.has(key)));
|
|
38
|
+
return {
|
|
39
|
+
command: findBun(),
|
|
40
|
+
args: [SERVER_ENTRY],
|
|
41
|
+
cwd: opts.dir,
|
|
42
|
+
env: {
|
|
43
|
+
...env,
|
|
44
|
+
METRO_MODE: 'local',
|
|
45
|
+
METRO_WEBHOOK_PORT: String(opts.port),
|
|
46
|
+
METRO_HTTP_HOST: process.env.METRO_HTTP_HOST ?? '127.0.0.1',
|
|
47
|
+
METRO_TRAINS_DIR: join(opts.dir, 'trains'),
|
|
48
|
+
METRO_STATE_DIR: process.env.METRO_STATE_DIR ?? serveStateDir(),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function serve(argv) {
|
|
53
|
+
const { port } = parseServeArgs(argv);
|
|
54
|
+
const plan = servePlan({ dir: runtimeDir(), port });
|
|
55
|
+
process.stderr.write(`Starting a metro daemon of your own on http://127.0.0.1:${String(port)}\n`);
|
|
56
|
+
return spawnPlan(plan);
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stage-labs/metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.32",
|
|
4
4
|
"description": "The metro command line. Sign in once per machine, then hand your MCP connector list to Claude Code without the credentials touching disk, argv or shell history.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -51,13 +51,13 @@ export interface AccountApiDeps {
|
|
|
51
51
|
attachSessions: AttachSessionApi;
|
|
52
52
|
prepareAccount: (input: AttachInput) => Promise<PreparedAccount>;
|
|
53
53
|
attachAccount: (
|
|
54
|
-
|
|
54
|
+
subject: string,
|
|
55
55
|
agentId: string,
|
|
56
56
|
station: StationName,
|
|
57
57
|
config: Record<string, unknown>,
|
|
58
58
|
) => Promise<AccountRef>;
|
|
59
59
|
detachAccount: (
|
|
60
|
-
|
|
60
|
+
subject: string,
|
|
61
61
|
agentId: string,
|
|
62
62
|
station: StationName,
|
|
63
63
|
accountId: string,
|
|
@@ -146,7 +146,7 @@ async function storeAccount(
|
|
|
146
146
|
): Promise<AccountRef> {
|
|
147
147
|
try {
|
|
148
148
|
return await deps.attachAccount(
|
|
149
|
-
session.
|
|
149
|
+
session.subject,
|
|
150
150
|
agentId,
|
|
151
151
|
station,
|
|
152
152
|
prepared.config,
|
|
@@ -158,7 +158,7 @@ async function storeAccount(
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
function ownerOf(session: ApiSession, agentId: string): AttachOwner {
|
|
161
|
-
return {
|
|
161
|
+
return { subject: session.subject, agentId };
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
function asInput(body: unknown): Record<string, unknown> {
|
|
@@ -224,7 +224,7 @@ async function handleDetach(
|
|
|
224
224
|
target: { station: StationName; accountId: string },
|
|
225
225
|
): Promise<void> {
|
|
226
226
|
const ref = await deps.detachAccount(
|
|
227
|
-
session.
|
|
227
|
+
session.subject,
|
|
228
228
|
agentId,
|
|
229
229
|
target.station,
|
|
230
230
|
target.accountId,
|
|
@@ -30,18 +30,18 @@ const PREFIX = '/api/agents';
|
|
|
30
30
|
const SERVER_NAME = 'metro';
|
|
31
31
|
|
|
32
32
|
export interface AgentApiDeps extends AccountApiDeps {
|
|
33
|
-
listAgents: (
|
|
33
|
+
listAgents: (subject: string, project: string) => Promise<AgentSummary[]>;
|
|
34
34
|
createAgent: (
|
|
35
|
-
|
|
35
|
+
subject: string,
|
|
36
36
|
project: string,
|
|
37
37
|
name: string,
|
|
38
38
|
) => Promise<CreatedAgent>;
|
|
39
39
|
deleteAgent: (
|
|
40
|
-
|
|
40
|
+
subject: string,
|
|
41
41
|
id: string,
|
|
42
42
|
) => Promise<DeletedAgent>;
|
|
43
43
|
resetKey: (
|
|
44
|
-
|
|
44
|
+
subject: string,
|
|
45
45
|
id: string,
|
|
46
46
|
) => Promise<ResetAgentKey>;
|
|
47
47
|
gatherAccounts: (allowed: Set<string>) => Promise<{
|
|
@@ -49,8 +49,9 @@ export interface AgentApiDeps extends AccountApiDeps {
|
|
|
49
49
|
unavailable: string[];
|
|
50
50
|
}>;
|
|
51
51
|
capabilities: () => Record<string, string[]>;
|
|
52
|
+
attachable?: string[];
|
|
52
53
|
liveness: () => Map<string, { connected: boolean; lastSeenAt: number }>;
|
|
53
|
-
releaseRuntime: (
|
|
54
|
+
releaseRuntime: (subject: string, agentId: string) => Promise<void>;
|
|
54
55
|
runtimes: (agentIds: string[]) => Promise<Map<string, string>>;
|
|
55
56
|
connectorIds: (agentIds: string[]) => Promise<Map<string, string[]>>;
|
|
56
57
|
}
|
|
@@ -160,18 +161,18 @@ async function handleList(
|
|
|
160
161
|
sendJson(req, res, 400, { error: 'a project is required' });
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
163
|
-
const list = await deps.listAgents(session.
|
|
164
|
+
const list = await deps.listAgents(session.subject, project);
|
|
164
165
|
const live = deps.liveness();
|
|
165
166
|
const held = await deps.runtimes(
|
|
166
167
|
list.filter((a) => a.owned).map((a) => a.id),
|
|
167
168
|
);
|
|
168
169
|
const connectors = await deps.connectorIds(list.map((a) => a.id));
|
|
169
170
|
const base = {
|
|
170
|
-
|
|
171
|
+
subject: session.subject,
|
|
171
172
|
endpoint: mcpEndpoint(),
|
|
172
173
|
agents: list.map((a) => agentPayload(a, live, held, connectors)),
|
|
173
174
|
capabilities: deps.capabilities(),
|
|
174
|
-
attachable: ATTACHABLE,
|
|
175
|
+
attachable: deps.attachable ?? ATTACHABLE,
|
|
175
176
|
};
|
|
176
177
|
if (!wantsAccounts(req)) {
|
|
177
178
|
sendJson(req, res, 200, base);
|
|
@@ -190,7 +191,7 @@ async function handleRuntime(
|
|
|
190
191
|
session: ApiSession,
|
|
191
192
|
id: string,
|
|
192
193
|
): Promise<void> {
|
|
193
|
-
await deps.releaseRuntime(session.
|
|
194
|
+
await deps.releaseRuntime(session.subject, id);
|
|
194
195
|
log.info({ agent: id }, 'agent-api: runtime released');
|
|
195
196
|
sendJson(req, res, 200, { agent: id, runtime: null });
|
|
196
197
|
}
|
|
@@ -208,9 +209,9 @@ async function handleCreate(
|
|
|
208
209
|
sendJson(req, res, 400, { error: 'a project is required' });
|
|
209
210
|
return;
|
|
210
211
|
}
|
|
211
|
-
const created = await deps.createAgent(session.
|
|
212
|
+
const created = await deps.createAgent(session.subject, project, name as string);
|
|
212
213
|
log.info(
|
|
213
|
-
{ agent: created.name, id: created.id, owner: session.
|
|
214
|
+
{ agent: created.name, id: created.id, owner: session.subject },
|
|
214
215
|
'agent-api: created agent',
|
|
215
216
|
);
|
|
216
217
|
sendJson(req, res, 201, {
|
|
@@ -227,9 +228,9 @@ async function handleResetKey(
|
|
|
227
228
|
session: ApiSession,
|
|
228
229
|
id: string,
|
|
229
230
|
): Promise<void> {
|
|
230
|
-
const reset = await deps.resetKey(session.
|
|
231
|
+
const reset = await deps.resetKey(session.subject, id);
|
|
231
232
|
log.info(
|
|
232
|
-
{ agent: reset.name, id: reset.id, owner: session.
|
|
233
|
+
{ agent: reset.name, id: reset.id, owner: session.subject },
|
|
233
234
|
'agent-api: reset agent key',
|
|
234
235
|
);
|
|
235
236
|
sendJson(req, res, 200, {
|
|
@@ -247,9 +248,9 @@ async function handleDelete(
|
|
|
247
248
|
session: ApiSession,
|
|
248
249
|
id: string,
|
|
249
250
|
): Promise<void> {
|
|
250
|
-
const gone = await deps.deleteAgent(session.
|
|
251
|
+
const gone = await deps.deleteAgent(session.subject, id);
|
|
251
252
|
log.info(
|
|
252
|
-
{ agent: gone.name, id: gone.id, owner: session.
|
|
253
|
+
{ agent: gone.name, id: gone.id, owner: session.subject },
|
|
253
254
|
'agent-api: deleted agent',
|
|
254
255
|
);
|
|
255
256
|
sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
|
|
@@ -15,19 +15,19 @@ import type { AgentConnectors } from '../db/agent-connectors.js';
|
|
|
15
15
|
const PREFIX = '/api/agents';
|
|
16
16
|
|
|
17
17
|
export interface AgentConnectorApiDeps {
|
|
18
|
-
agentConnectors: (
|
|
18
|
+
agentConnectors: (subject: string, agentId: string) => Promise<AgentConnectors>;
|
|
19
19
|
addConnector: (
|
|
20
|
-
|
|
20
|
+
subject: string,
|
|
21
21
|
agentId: string,
|
|
22
22
|
connectorId: string,
|
|
23
23
|
) => Promise<AgentConnectors>;
|
|
24
24
|
removeConnector: (
|
|
25
|
-
|
|
25
|
+
subject: string,
|
|
26
26
|
agentId: string,
|
|
27
27
|
connectorId: string,
|
|
28
28
|
) => Promise<AgentConnectors>;
|
|
29
29
|
mintCode: (
|
|
30
|
-
|
|
30
|
+
subject: string,
|
|
31
31
|
agentId: string,
|
|
32
32
|
) => Promise<{ code: string; expiresAt: number; agent: string }>;
|
|
33
33
|
}
|
|
@@ -73,7 +73,7 @@ async function handleConnectors(
|
|
|
73
73
|
id: string,
|
|
74
74
|
): Promise<void> {
|
|
75
75
|
if (req.method === 'GET') {
|
|
76
|
-
sendJson(req, res, 200, await deps.agentConnectors(session.
|
|
76
|
+
sendJson(req, res, 200, await deps.agentConnectors(session.subject, id));
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
79
|
const connectorId = bodyField(await readJsonBody(req), 'connectorId');
|
|
@@ -81,7 +81,7 @@ async function handleConnectors(
|
|
|
81
81
|
sendJson(req, res, 400, { error: 'connectorId is required' });
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
|
-
sendJson(req, res, 200, await deps.addConnector(session.
|
|
84
|
+
sendJson(req, res, 200, await deps.addConnector(session.subject, id, connectorId));
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
async function handleCode(
|
|
@@ -91,8 +91,8 @@ async function handleCode(
|
|
|
91
91
|
session: ApiSession,
|
|
92
92
|
id: string,
|
|
93
93
|
): Promise<void> {
|
|
94
|
-
const minted = await deps.mintCode(session.
|
|
95
|
-
log.info({ agent: id,
|
|
94
|
+
const minted = await deps.mintCode(session.subject, id);
|
|
95
|
+
log.info({ agent: id, subject: session.subject }, 'agent-api: minted pairing code');
|
|
96
96
|
sendJson(req, res, 201, minted);
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -111,7 +111,7 @@ async function route(
|
|
|
111
111
|
req,
|
|
112
112
|
res,
|
|
113
113
|
200,
|
|
114
|
-
await deps.removeConnector(session.
|
|
114
|
+
await deps.removeConnector(session.subject, tgt.id, tgt.connectorId),
|
|
115
115
|
);
|
|
116
116
|
} catch (err) {
|
|
117
117
|
apiFailure(req, res, err, 'agent-api');
|
|
@@ -14,7 +14,7 @@ const BODY_MAX = 4 * 1024;
|
|
|
14
14
|
const DRAIN_MAX = 2 * BODY_MAX;
|
|
15
15
|
|
|
16
16
|
export interface ApiSession {
|
|
17
|
-
|
|
17
|
+
subject: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function cors(req: IncomingMessage): Record<string, string> {
|
|
@@ -22,6 +22,7 @@ export function cors(req: IncomingMessage): Record<string, string> {
|
|
|
22
22
|
'access-control-allow-origin': req.headers.origin ?? '*',
|
|
23
23
|
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
24
24
|
'access-control-allow-headers': 'Authorization, Content-Type',
|
|
25
|
+
'access-control-allow-private-network': 'true',
|
|
25
26
|
'access-control-max-age': '86400',
|
|
26
27
|
vary: 'Origin',
|
|
27
28
|
};
|
|
@@ -47,8 +48,8 @@ export function apiSession(req: IncomingMessage): ApiSession | null {
|
|
|
47
48
|
const token = extractToken(req) ?? '';
|
|
48
49
|
if (token === '') return null;
|
|
49
50
|
try {
|
|
50
|
-
const {
|
|
51
|
-
return {
|
|
51
|
+
const { subject } = verifySession(token, secret);
|
|
52
|
+
return { subject: subject.toLowerCase() };
|
|
52
53
|
} catch {
|
|
53
54
|
return null;
|
|
54
55
|
}
|
|
@@ -61,7 +62,7 @@ export function runIdentity(req: IncomingMessage): RunClaims | null {
|
|
|
61
62
|
if (token === '') return null;
|
|
62
63
|
try {
|
|
63
64
|
const claims = verifyRunToken(token, secret);
|
|
64
|
-
return { ...claims,
|
|
65
|
+
return { ...claims, subject: claims.subject.toLowerCase() };
|
|
65
66
|
} catch {
|
|
66
67
|
return null;
|
|
67
68
|
}
|
|
@@ -77,12 +78,12 @@ export function agentIdentity(req: IncomingMessage): AgentIdentity | null {
|
|
|
77
78
|
const token = extractToken(req) ?? '';
|
|
78
79
|
if (token === '') return null;
|
|
79
80
|
try {
|
|
80
|
-
const {
|
|
81
|
-
return {
|
|
81
|
+
const { subject, agentId } = verifyAgentToken(token, secret);
|
|
82
|
+
return { subject: subject.toLowerCase(), agentId };
|
|
82
83
|
} catch {
|
|
83
84
|
const run = runIdentity(req);
|
|
84
85
|
if (run === null) return null;
|
|
85
|
-
return {
|
|
86
|
+
return { subject: run.subject, agentId: run.agentId, runtimeId: run.runtimeId };
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -15,6 +15,7 @@ import { attachmentOwner, recordAttachmentOwner } from './attach-owner.js';
|
|
|
15
15
|
import { grantAllows, issueAttachmentGrant } from './attach-grant.js';
|
|
16
16
|
import { errMsg, log } from './log.js';
|
|
17
17
|
import { loadTunnelConfig, webhookPort } from './tunnel.js';
|
|
18
|
+
import { isLocalMode } from './paths.js';
|
|
18
19
|
|
|
19
20
|
function authorized(req: IncomingMessage, name: string): boolean {
|
|
20
21
|
const owner = attachmentOwner(name);
|
|
@@ -34,10 +35,11 @@ export function publicBaseUrl(): string | null {
|
|
|
34
35
|
return host ? `https://${host}` : null;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
const servesLocally = (): boolean =>
|
|
39
|
+
(process.env.METRO_RUN_TOKEN?.trim() ?? '') !== '' || isLocalMode();
|
|
40
|
+
|
|
37
41
|
const localBase = (): string | null =>
|
|
38
|
-
(
|
|
39
|
-
? null
|
|
40
|
-
: `http://127.0.0.1:${String(webhookPort())}`;
|
|
42
|
+
servesLocally() ? `http://127.0.0.1:${String(webhookPort())}` : null;
|
|
41
43
|
|
|
42
44
|
export const publicBaseOrDefault = (): string =>
|
|
43
45
|
publicBaseUrl() ?? localBase() ?? DEFAULT_PUBLIC_BASE;
|
|
@@ -28,7 +28,7 @@ const MAX_PER_AGENT = 2;
|
|
|
28
28
|
const MAX_TOTAL = 40;
|
|
29
29
|
|
|
30
30
|
export interface AttachOwner {
|
|
31
|
-
|
|
31
|
+
subject: string;
|
|
32
32
|
agentId: string;
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -109,7 +109,7 @@ export class AttachSessions {
|
|
|
109
109
|
private own(owner: AttachOwner, attachId: string): Session {
|
|
110
110
|
const session = this.sessions.get(attachId);
|
|
111
111
|
if (
|
|
112
|
-
session?.owner.
|
|
112
|
+
session?.owner.subject !== owner.subject ||
|
|
113
113
|
session.owner.agentId !== owner.agentId
|
|
114
114
|
)
|
|
115
115
|
throw missing();
|