@stage-labs/metro 0.1.0-beta.40 → 0.1.0-beta.41
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/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.41",
|
|
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": {
|
|
@@ -45,11 +45,16 @@ function credential(file: Record<string, unknown> | null): HostedCredential | nu
|
|
|
45
45
|
return { token: file.token, url: typeof file.url === 'string' && file.url !== '' ? file.url : metroBaseUrl() };
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function
|
|
49
|
-
const
|
|
50
|
-
if (existsSync(runtime)) return credential(readJsonFile(runtime));
|
|
48
|
+
export function hostedCredentialsFor(agentId: string, dir = configDir()): HostedCredential[] {
|
|
49
|
+
const out: HostedCredential[] = [];
|
|
51
50
|
const login = credential(readJsonFile(join(dir, 'credentials.json')));
|
|
52
|
-
|
|
51
|
+
if (login !== null && agentOfToken(login.token) === agentId) out.push(login);
|
|
52
|
+
const runtime = join(dir, `runtime-${agentId}.json`);
|
|
53
|
+
if (existsSync(runtime)) {
|
|
54
|
+
const cred = credential(readJsonFile(runtime));
|
|
55
|
+
if (cred !== null) out.push(cred);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
const isConnector = (v: unknown): v is HostedConnector =>
|
|
@@ -58,26 +63,48 @@ const isConnector = (v: unknown): v is HostedConnector =>
|
|
|
58
63
|
typeof (v as { id?: unknown }).id === 'string' &&
|
|
59
64
|
typeof (v as { name?: unknown }).name === 'string';
|
|
60
65
|
|
|
66
|
+
class Refused extends Error {}
|
|
67
|
+
|
|
61
68
|
async function fetchHosted(cred: HostedCredential): Promise<HostedConnector[]> {
|
|
62
69
|
const res = await fetch(`${cred.url}/api/cli/connectors`, {
|
|
63
70
|
headers: { authorization: `Bearer ${cred.token}` },
|
|
64
71
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
65
72
|
redirect: 'manual',
|
|
66
73
|
});
|
|
74
|
+
if (res.status === 401 || res.status === 403 || res.status === 409) {
|
|
75
|
+
await res.arrayBuffer().catch(() => undefined);
|
|
76
|
+
throw new Refused(`metro no longer accepts this credential (${String(res.status)})`);
|
|
77
|
+
}
|
|
67
78
|
if (!res.ok) throw new Error(`metro answered ${String(res.status)} for the connectors`);
|
|
68
79
|
const body = (await res.json()) as { connectors?: unknown };
|
|
69
80
|
return Array.isArray(body.connectors) ? body.connectors.filter(isConnector) : [];
|
|
70
81
|
}
|
|
71
82
|
|
|
83
|
+
async function firstAccepted(agentId: string, creds: HostedCredential[]): Promise<HostedConnector[] | null> {
|
|
84
|
+
for (const cred of creds) {
|
|
85
|
+
try {
|
|
86
|
+
return await fetchHosted(cred);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (!(err instanceof Refused)) throw err;
|
|
89
|
+
log.warn({ agent: agentId, reason: errMsg(err) }, 'hosted connectors: credential refused, trying the next');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
const cache = new Map<string, { at: number; list: HostedConnector[] }>();
|
|
73
96
|
|
|
74
97
|
export async function hostedConnectorsFor(agentId: string, dir = configDir()): Promise<HostedConnector[]> {
|
|
75
98
|
const hit = cache.get(agentId);
|
|
76
99
|
if (hit !== undefined && Date.now() - hit.at < CACHE_MS) return hit.list;
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
100
|
+
const creds = hostedCredentialsFor(agentId, dir);
|
|
101
|
+
if (creds.length === 0) return [];
|
|
79
102
|
try {
|
|
80
|
-
const list = await
|
|
103
|
+
const list = await firstAccepted(agentId, creds);
|
|
104
|
+
if (list === null) {
|
|
105
|
+
log.warn({ agent: agentId }, 'hosted connectors: every stored credential is stale; metro login again');
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
81
108
|
cache.set(agentId, { at: Date.now(), list });
|
|
82
109
|
return list;
|
|
83
110
|
} catch (err) {
|
package/runtime/runtime.json
CHANGED