@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.21
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/api.js +103 -0
- package/dist/cli.js +134 -565
- package/dist/control.js +102 -0
- package/dist/prompt.js +10 -0
- package/dist/runtime.js +94 -0
- package/dist/store.js +70 -0
- package/dist/update.js +33 -0
- package/dist/version.js +97 -0
- package/package.json +34 -22
- package/runtime/node_modules/@metro-labs/discord/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord/src/accounts.ts +119 -0
- package/runtime/node_modules/@metro-labs/discord/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord/src/format.ts +210 -0
- package/runtime/node_modules/@metro-labs/discord/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/mcp/package.json +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +335 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +124 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +322 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +20 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +202 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +338 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +31 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +164 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +208 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +357 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +79 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +134 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +125 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +18 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +41 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +323 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +169 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +215 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +71 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +218 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +329 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +19 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +364 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +300 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +31 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +131 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +145 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
- package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +172 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +48 -0
- package/runtime/node_modules/@metro-labs/telegram/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +3 -0
- package/runtime/node_modules/@metro-labs/telegram-user/package.json +28 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +81 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +141 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
- package/runtime/runtime.json +3 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -78
- package/dist/channels/discord.js +0 -148
- package/dist/channels/telegram.js +0 -137
- package/dist/lib/address.js +0 -21
- package/dist/lib/codex-rc.js +0 -274
- package/dist/lib/dotenv.js +0 -31
- package/dist/log.js +0 -12
- package/dist/paths.js +0 -45
- package/dist/tail.js +0 -161
- package/skills/metro/SKILL.md +0 -122
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ApiError } from '../daemon/api-error.js';
|
|
2
|
+
import type {
|
|
3
|
+
ConnectorAuth,
|
|
4
|
+
VerifiedRecord,
|
|
5
|
+
VerifiedServer,
|
|
6
|
+
} from '../daemon/connector-verify.js';
|
|
7
|
+
import { readStoredTools } from '../daemon/connector-tools.js';
|
|
8
|
+
|
|
9
|
+
const CONNECTOR_NAME_MAX = 64;
|
|
10
|
+
const HEADER_NAME_RE = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$/;
|
|
11
|
+
const HEADER_VALUE_RE = /^[\x20-\x7e]{1,4096}$/;
|
|
12
|
+
const DEFAULT_HEADER = 'Authorization';
|
|
13
|
+
|
|
14
|
+
export class ConnectorError extends ApiError {}
|
|
15
|
+
|
|
16
|
+
export type ConnectorSignIn = 'connected' | 'disconnected' | null;
|
|
17
|
+
|
|
18
|
+
export interface ConnectorConfig {
|
|
19
|
+
auth: ConnectorAuth;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
verified: VerifiedRecord;
|
|
22
|
+
oauth: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
26
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function text(value: unknown): string {
|
|
30
|
+
return typeof value === 'string' ? value : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hasControlChar(value: string): boolean {
|
|
34
|
+
for (const ch of value) {
|
|
35
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
36
|
+
if (code < 0x20 || code === 0x7f) return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function connectorName(raw: unknown): string {
|
|
42
|
+
const name = typeof raw === 'string' ? raw.trim() : '';
|
|
43
|
+
if (name === '') throw new ConnectorError('a connector needs a name', 400);
|
|
44
|
+
if (name.length > CONNECTOR_NAME_MAX)
|
|
45
|
+
throw new ConnectorError(
|
|
46
|
+
`a connector name must be ${String(CONNECTOR_NAME_MAX)} characters or fewer`,
|
|
47
|
+
400,
|
|
48
|
+
);
|
|
49
|
+
if (hasControlChar(name))
|
|
50
|
+
throw new ConnectorError(
|
|
51
|
+
'a connector name must not contain control characters',
|
|
52
|
+
400,
|
|
53
|
+
);
|
|
54
|
+
return name;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function connectorAuth(
|
|
58
|
+
rawHeader: unknown,
|
|
59
|
+
rawValue: unknown,
|
|
60
|
+
): ConnectorAuth {
|
|
61
|
+
const header = text(rawHeader).trim();
|
|
62
|
+
const value = text(rawValue).trim();
|
|
63
|
+
if (header === '' && value === '') return { kind: 'none' };
|
|
64
|
+
if (value === '')
|
|
65
|
+
throw new ConnectorError(
|
|
66
|
+
'that header has no value — give both a header name and its value, or neither',
|
|
67
|
+
400,
|
|
68
|
+
);
|
|
69
|
+
const name = header === '' ? DEFAULT_HEADER : header;
|
|
70
|
+
if (!HEADER_NAME_RE.test(name))
|
|
71
|
+
throw new ConnectorError('that is not a valid HTTP header name', 400);
|
|
72
|
+
if (!HEADER_VALUE_RE.test(value))
|
|
73
|
+
throw new ConnectorError('that header value is not sendable', 400);
|
|
74
|
+
return { kind: 'header', name, value };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readOAuth(raw: Record<string, unknown>): ConnectorAuth {
|
|
78
|
+
const accessToken = text(raw.accessToken);
|
|
79
|
+
const clientId = text(raw.clientId);
|
|
80
|
+
const tokenEndpoint = text(raw.tokenEndpoint);
|
|
81
|
+
if (accessToken === '' || clientId === '' || tokenEndpoint === '')
|
|
82
|
+
return { kind: 'none' };
|
|
83
|
+
const refreshToken = text(raw.refreshToken);
|
|
84
|
+
const clientSecret = text(raw.clientSecret);
|
|
85
|
+
return {
|
|
86
|
+
kind: 'oauth',
|
|
87
|
+
accessToken,
|
|
88
|
+
clientId,
|
|
89
|
+
tokenEndpoint,
|
|
90
|
+
issuer: text(raw.issuer),
|
|
91
|
+
...(refreshToken === '' ? {} : { refreshToken }),
|
|
92
|
+
...(clientSecret === '' ? {} : { clientSecret }),
|
|
93
|
+
...(typeof raw.expiresAt === 'number' ? { expiresAt: raw.expiresAt } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readAuth(raw: unknown): ConnectorAuth {
|
|
98
|
+
if (!isRecord(raw)) return { kind: 'none' };
|
|
99
|
+
if (raw.kind === 'oauth') return readOAuth(raw);
|
|
100
|
+
if (raw.kind !== 'header') return { kind: 'none' };
|
|
101
|
+
const name = text(raw.name);
|
|
102
|
+
const value = text(raw.value);
|
|
103
|
+
if (name === '' || value === '') return { kind: 'none' };
|
|
104
|
+
return { kind: 'header', name, value };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function readVerified(raw: unknown): VerifiedRecord {
|
|
108
|
+
const record = isRecord(raw) ? raw : {};
|
|
109
|
+
const catalog = readStoredTools(record.catalog);
|
|
110
|
+
return {
|
|
111
|
+
at: text(record.at),
|
|
112
|
+
server: text(record.server),
|
|
113
|
+
version: text(record.version),
|
|
114
|
+
protocol: text(record.protocol),
|
|
115
|
+
icon: text(record.icon),
|
|
116
|
+
tools: typeof record.tools === 'number' ? record.tools : 0,
|
|
117
|
+
catalog,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function readConfig(raw: unknown): ConnectorConfig {
|
|
122
|
+
const record = isRecord(raw) ? raw : {};
|
|
123
|
+
const auth = readAuth(record.auth);
|
|
124
|
+
return {
|
|
125
|
+
auth,
|
|
126
|
+
createdAt: text(record.createdAt),
|
|
127
|
+
verified: readVerified(record.verified),
|
|
128
|
+
oauth: record.oauth === true || auth.kind === 'oauth',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function signInState(config: ConnectorConfig): ConnectorSignIn {
|
|
133
|
+
if (config.auth.kind === 'oauth') return 'connected';
|
|
134
|
+
return config.oauth ? 'disconnected' : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function stamp(server: VerifiedServer): VerifiedRecord {
|
|
138
|
+
return {
|
|
139
|
+
at: new Date().toISOString(),
|
|
140
|
+
server: server.server,
|
|
141
|
+
version: server.version,
|
|
142
|
+
protocol: server.protocol,
|
|
143
|
+
icon: server.icon,
|
|
144
|
+
tools: server.tools,
|
|
145
|
+
catalog: server.catalog,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { assertRenameFreeOfClash } from './connector-collections.js';
|
|
2
|
+
import { projectIdOrThrow } from './projects.js';
|
|
3
|
+
import { and, asc, eq, inArray } from 'drizzle-orm';
|
|
4
|
+
import {
|
|
5
|
+
connectorUrlText,
|
|
6
|
+
ConnectorVerifyError,
|
|
7
|
+
parseConnectorUrl,
|
|
8
|
+
verifyRemoteMcp,
|
|
9
|
+
type ConnectorAuth,
|
|
10
|
+
type OAuthAuth,
|
|
11
|
+
type VerifiedRecord,
|
|
12
|
+
} from '../daemon/connector-verify.js';
|
|
13
|
+
import { oauthExpired, refreshOAuth } from '../daemon/connector-oauth.js';
|
|
14
|
+
import { advertisesOAuth } from '../daemon/oauth-discovery.js';
|
|
15
|
+
import {
|
|
16
|
+
ConnectorError,
|
|
17
|
+
connectorAuth,
|
|
18
|
+
connectorName,
|
|
19
|
+
readConfig,
|
|
20
|
+
signInState,
|
|
21
|
+
stamp,
|
|
22
|
+
type ConnectorConfig,
|
|
23
|
+
type ConnectorSignIn,
|
|
24
|
+
} from './connector-config.js';
|
|
25
|
+
import { errMsg, log } from '../daemon/log.js';
|
|
26
|
+
import { newId } from './ids.js';
|
|
27
|
+
import { getDb } from './client.js';
|
|
28
|
+
import { connectors, type ConnectorTransport } from './schema.js';
|
|
29
|
+
|
|
30
|
+
export interface Connector {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
url: string;
|
|
34
|
+
transport: ConnectorTransport;
|
|
35
|
+
auth: ConnectorAuth['kind'];
|
|
36
|
+
header: string | null;
|
|
37
|
+
secret: string | null;
|
|
38
|
+
bearer: string | null;
|
|
39
|
+
expiresAt: number | null;
|
|
40
|
+
signIn: ConnectorSignIn;
|
|
41
|
+
verified: VerifiedRecord;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ConnectorInput {
|
|
45
|
+
name: unknown;
|
|
46
|
+
url: unknown;
|
|
47
|
+
header: unknown;
|
|
48
|
+
value: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ConnectorCheck =
|
|
52
|
+
| { id: string; name: string; ok: true; verified: VerifiedRecord }
|
|
53
|
+
| { id: string; name: string; ok: false; reason: string };
|
|
54
|
+
|
|
55
|
+
export interface DeletedConnector {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type ConnectorRow = typeof connectors.$inferSelect;
|
|
61
|
+
|
|
62
|
+
function toConnector(row: ConnectorRow): Connector {
|
|
63
|
+
const config = readConfig(row.config);
|
|
64
|
+
const auth = config.auth;
|
|
65
|
+
return {
|
|
66
|
+
id: row.id,
|
|
67
|
+
name: row.name,
|
|
68
|
+
url: row.url,
|
|
69
|
+
transport: row.transport,
|
|
70
|
+
auth: auth.kind,
|
|
71
|
+
header: auth.kind === 'header' ? auth.name : null,
|
|
72
|
+
secret: auth.kind === 'header' ? auth.value : null,
|
|
73
|
+
bearer: auth.kind === 'oauth' ? auth.accessToken : null,
|
|
74
|
+
expiresAt: auth.kind === 'oauth' ? (auth.expiresAt ?? null) : null,
|
|
75
|
+
signIn: signInState(config),
|
|
76
|
+
verified: config.verified,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const missing = (): ConnectorError =>
|
|
81
|
+
new ConnectorError('no such connector', 404);
|
|
82
|
+
|
|
83
|
+
async function ownedConnectorOrThrow(
|
|
84
|
+
email: string,
|
|
85
|
+
id: string,
|
|
86
|
+
): Promise<ConnectorRow> {
|
|
87
|
+
const rows = await getDb()
|
|
88
|
+
.select()
|
|
89
|
+
.from(connectors)
|
|
90
|
+
.where(eq(connectors.id, id));
|
|
91
|
+
const row = rows[0];
|
|
92
|
+
if (row === undefined) throw missing();
|
|
93
|
+
try {
|
|
94
|
+
await projectIdOrThrow(email, row.projectId);
|
|
95
|
+
} catch {
|
|
96
|
+
throw missing();
|
|
97
|
+
}
|
|
98
|
+
return row;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function saveConfig(
|
|
102
|
+
row: ConnectorRow,
|
|
103
|
+
config: ConnectorConfig,
|
|
104
|
+
): Promise<Connector> {
|
|
105
|
+
const rows = await getDb()
|
|
106
|
+
.update(connectors)
|
|
107
|
+
.set({ config })
|
|
108
|
+
.where(and(eq(connectors.id, row.id), eq(connectors.projectId, row.projectId)))
|
|
109
|
+
.returning();
|
|
110
|
+
const saved = rows[0];
|
|
111
|
+
if (saved === undefined) throw missing();
|
|
112
|
+
return toConnector(saved);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function connectorRowsFor(
|
|
116
|
+
email: string,
|
|
117
|
+
project: string,
|
|
118
|
+
): Promise<ConnectorRow[]> {
|
|
119
|
+
const projectId = await projectIdOrThrow(email, project);
|
|
120
|
+
return getDb()
|
|
121
|
+
.select()
|
|
122
|
+
.from(connectors)
|
|
123
|
+
.where(eq(connectors.projectId, projectId))
|
|
124
|
+
.orderBy(asc(connectors.id));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function listConnectorsForEmail(
|
|
128
|
+
email: string,
|
|
129
|
+
project: string,
|
|
130
|
+
): Promise<Connector[]> {
|
|
131
|
+
return (await connectorRowsFor(email, project)).map(toConnector);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function withFreshToken(row: ConnectorRow): Promise<Connector> {
|
|
135
|
+
const config = readConfig(row.config);
|
|
136
|
+
if (config.auth.kind !== 'oauth' || !oauthExpired(config.auth))
|
|
137
|
+
return toConnector(row);
|
|
138
|
+
const url = parseConnectorUrl(row.url);
|
|
139
|
+
const auth = await refreshOAuth(config.auth, url.toString());
|
|
140
|
+
return saveConfig(row, { ...config, auth });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function rowsByIds(ids: string[]): Promise<ConnectorRow[]> {
|
|
144
|
+
if (ids.length === 0) return [];
|
|
145
|
+
return getDb()
|
|
146
|
+
.select()
|
|
147
|
+
.from(connectors)
|
|
148
|
+
.where(inArray(connectors.id, ids))
|
|
149
|
+
.orderBy(asc(connectors.id));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function listFreshConnectorsByIds(
|
|
153
|
+
ids: string[],
|
|
154
|
+
): Promise<Connector[]> {
|
|
155
|
+
const rows = await rowsByIds(ids);
|
|
156
|
+
return Promise.all(
|
|
157
|
+
rows.map(async (row) =>
|
|
158
|
+
withFreshToken(row).catch((err: unknown) => {
|
|
159
|
+
log.warn(
|
|
160
|
+
{ id: row.id, err: errMsg(err) },
|
|
161
|
+
'connectors: could not refresh the access token',
|
|
162
|
+
);
|
|
163
|
+
return toConnector(row);
|
|
164
|
+
}),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function getConnectorForEmail(
|
|
170
|
+
email: string,
|
|
171
|
+
id: string,
|
|
172
|
+
): Promise<Connector> {
|
|
173
|
+
return toConnector(await ownedConnectorOrThrow(email, id));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function insertConnector(
|
|
177
|
+
projectId: string,
|
|
178
|
+
name: string,
|
|
179
|
+
url: URL,
|
|
180
|
+
config: ConnectorConfig,
|
|
181
|
+
): Promise<Connector> {
|
|
182
|
+
const rows = await getDb()
|
|
183
|
+
.insert(connectors)
|
|
184
|
+
.values({
|
|
185
|
+
id: newId(),
|
|
186
|
+
projectId,
|
|
187
|
+
name,
|
|
188
|
+
url: connectorUrlText(url),
|
|
189
|
+
transport: 'http',
|
|
190
|
+
config,
|
|
191
|
+
})
|
|
192
|
+
.returning();
|
|
193
|
+
const row = rows[0];
|
|
194
|
+
if (row === undefined)
|
|
195
|
+
throw new ConnectorError('connector insert returned no row', 500);
|
|
196
|
+
return toConnector(row);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const UNVERIFIED = {
|
|
200
|
+
at: '',
|
|
201
|
+
server: '',
|
|
202
|
+
version: '',
|
|
203
|
+
protocol: '',
|
|
204
|
+
icon: '',
|
|
205
|
+
tools: 0,
|
|
206
|
+
catalog: [],
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export async function createPendingConnectorForEmail(
|
|
210
|
+
email: string,
|
|
211
|
+
project: string,
|
|
212
|
+
input: { name: unknown; url: unknown },
|
|
213
|
+
): Promise<Connector> {
|
|
214
|
+
const name = connectorName(input.name);
|
|
215
|
+
const url = parseConnectorUrl(input.url);
|
|
216
|
+
const projectId = await projectIdOrThrow(email, project);
|
|
217
|
+
return insertConnector(projectId, name, url, {
|
|
218
|
+
auth: { kind: 'none' },
|
|
219
|
+
createdAt: new Date().toISOString(),
|
|
220
|
+
verified: UNVERIFIED,
|
|
221
|
+
oauth: true,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function createConnectorForEmail(
|
|
226
|
+
email: string,
|
|
227
|
+
project: string,
|
|
228
|
+
input: ConnectorInput,
|
|
229
|
+
): Promise<Connector> {
|
|
230
|
+
const name = connectorName(input.name);
|
|
231
|
+
const url = parseConnectorUrl(input.url);
|
|
232
|
+
const auth = connectorAuth(input.header, input.value);
|
|
233
|
+
const projectId = await projectIdOrThrow(email, project);
|
|
234
|
+
const verified = stamp(await verifyRemoteMcp(url, auth));
|
|
235
|
+
return insertConnector(projectId, name, url, {
|
|
236
|
+
auth,
|
|
237
|
+
createdAt: new Date().toISOString(),
|
|
238
|
+
verified,
|
|
239
|
+
oauth: await oauthCapable(url, auth),
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function oauthCapable(url: URL, auth: ConnectorAuth): Promise<boolean> {
|
|
244
|
+
if (auth.kind === 'oauth') return true;
|
|
245
|
+
if (auth.kind === 'header') return false;
|
|
246
|
+
return advertisesOAuth(url);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function freshAuth(
|
|
250
|
+
auth: ConnectorAuth,
|
|
251
|
+
resource: string,
|
|
252
|
+
): Promise<ConnectorAuth> {
|
|
253
|
+
if (auth.kind !== 'oauth' || !oauthExpired(auth)) return auth;
|
|
254
|
+
return refreshOAuth(auth, resource);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export async function reconnectConnectorForEmail(
|
|
258
|
+
email: string,
|
|
259
|
+
id: string,
|
|
260
|
+
auth: OAuthAuth,
|
|
261
|
+
): Promise<Connector> {
|
|
262
|
+
const row = await ownedConnectorOrThrow(email, id);
|
|
263
|
+
const config = readConfig(row.config);
|
|
264
|
+
const url = parseConnectorUrl(row.url);
|
|
265
|
+
const verified = stamp(await verifyRemoteMcp(url, auth));
|
|
266
|
+
return saveConfig(row, { ...config, auth, verified, oauth: true });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function disconnectConnectorForEmail(
|
|
270
|
+
email: string,
|
|
271
|
+
id: string,
|
|
272
|
+
): Promise<Connector> {
|
|
273
|
+
const row = await ownedConnectorOrThrow(email, id);
|
|
274
|
+
const config = readConfig(row.config);
|
|
275
|
+
if (config.auth.kind !== 'oauth')
|
|
276
|
+
throw new ConnectorError('that connector is not signed in', 400);
|
|
277
|
+
return saveConfig(row, { ...config, auth: { kind: 'none' }, oauth: true });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function verifyConnectorForEmail(
|
|
281
|
+
email: string,
|
|
282
|
+
id: string,
|
|
283
|
+
): Promise<ConnectorCheck> {
|
|
284
|
+
const row = await ownedConnectorOrThrow(email, id);
|
|
285
|
+
const config = readConfig(row.config);
|
|
286
|
+
try {
|
|
287
|
+
const url = parseConnectorUrl(row.url);
|
|
288
|
+
const auth = await freshAuth(config.auth, url.toString());
|
|
289
|
+
const verified = stamp(await verifyRemoteMcp(url, auth));
|
|
290
|
+
const oauth = config.oauth || (await oauthCapable(url, auth));
|
|
291
|
+
await saveConfig(row, { ...config, auth, verified, oauth });
|
|
292
|
+
return { id: row.id, name: row.name, ok: true, verified };
|
|
293
|
+
} catch (err) {
|
|
294
|
+
if (!(err instanceof ConnectorVerifyError)) throw err;
|
|
295
|
+
return { id: row.id, name: row.name, ok: false, reason: err.message };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export async function renameConnectorForEmail(
|
|
300
|
+
email: string,
|
|
301
|
+
id: string,
|
|
302
|
+
raw: string,
|
|
303
|
+
): Promise<Connector> {
|
|
304
|
+
const name = connectorName(raw);
|
|
305
|
+
const row = await ownedConnectorOrThrow(email, id);
|
|
306
|
+
await assertRenameFreeOfClash(row.id, name);
|
|
307
|
+
const rows = await getDb()
|
|
308
|
+
.update(connectors)
|
|
309
|
+
.set({ name })
|
|
310
|
+
.where(and(eq(connectors.id, row.id), eq(connectors.projectId, row.projectId)))
|
|
311
|
+
.returning();
|
|
312
|
+
const saved = rows[0];
|
|
313
|
+
if (saved === undefined) throw missing();
|
|
314
|
+
return toConnector(saved);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export async function deleteConnectorForEmail(
|
|
318
|
+
email: string,
|
|
319
|
+
id: string,
|
|
320
|
+
): Promise<DeletedConnector> {
|
|
321
|
+
const row = await ownedConnectorOrThrow(email, id);
|
|
322
|
+
const gone = await getDb()
|
|
323
|
+
.delete(connectors)
|
|
324
|
+
.where(eq(connectors.id, row.id))
|
|
325
|
+
.returning({ id: connectors.id, name: connectors.name });
|
|
326
|
+
const deleted = gone[0];
|
|
327
|
+
if (deleted === undefined) throw missing();
|
|
328
|
+
return deleted;
|
|
329
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const ID_BYTES = 8;
|
|
4
|
+
const REROLLS = 16;
|
|
5
|
+
|
|
6
|
+
export const ID_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{10}$/;
|
|
7
|
+
|
|
8
|
+
export function newId(): string {
|
|
9
|
+
for (let attempt = 0; attempt < REROLLS; attempt += 1) {
|
|
10
|
+
const id = randomBytes(ID_BYTES).toString('base64url');
|
|
11
|
+
if (ID_RE.test(id)) return id;
|
|
12
|
+
}
|
|
13
|
+
throw new Error('could not generate an id');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function parseId(raw: unknown): string | null {
|
|
17
|
+
if (typeof raw !== 'string') return null;
|
|
18
|
+
return ID_RE.test(raw) ? raw : null;
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { ID_RE } from './ids.js';
|
|
3
|
+
|
|
4
|
+
const digest = (token: string): string =>
|
|
5
|
+
createHash('sha256').update(token).digest('hex');
|
|
6
|
+
|
|
7
|
+
let agentIdByKeyHash = new Map<string, string>();
|
|
8
|
+
|
|
9
|
+
export interface KeyOwner {
|
|
10
|
+
key: string;
|
|
11
|
+
agentId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const usable = (e: KeyOwner): boolean =>
|
|
15
|
+
e.key !== '' && ID_RE.test(e.agentId);
|
|
16
|
+
|
|
17
|
+
export function setKeyMap(entries: KeyOwner[]): void {
|
|
18
|
+
agentIdByKeyHash = new Map(
|
|
19
|
+
entries.filter(usable).map((e) => [digest(e.key), e.agentId]),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function registerKey(key: string, agentId: string): void {
|
|
24
|
+
if (!usable({ key, agentId })) return;
|
|
25
|
+
agentIdByKeyHash.set(digest(key), agentId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function unregisterAgentKey(agentId: string): void {
|
|
29
|
+
for (const [hash, id] of agentIdByKeyHash)
|
|
30
|
+
if (id === agentId) agentIdByKeyHash.delete(hash);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function rotateAgentKey(agentId: string, key: string | null): void {
|
|
34
|
+
unregisterAgentKey(agentId);
|
|
35
|
+
if (key !== null) registerKey(key, agentId);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function agentIdForKey(token: string): string | undefined {
|
|
39
|
+
if (token === '') return undefined;
|
|
40
|
+
return agentIdByKeyHash.get(digest(token));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hasAnyKey(): boolean {
|
|
44
|
+
return agentIdByKeyHash.size > 0;
|
|
45
|
+
}
|