@extrovert.dev/mcp 0.1.0-pre.3 → 0.1.0-pre.5

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 (51) hide show
  1. package/README.md +74 -31
  2. package/dist/auth.d.ts +39 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +201 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/bin.d.ts +5 -7
  7. package/dist/bin.d.ts.map +1 -1
  8. package/dist/bin.js +63 -52
  9. package/dist/bin.js.map +1 -1
  10. package/dist/cli.d.ts +14 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +459 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/client.d.ts +20 -1
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +41 -8
  17. package/dist/client.js.map +1 -1
  18. package/dist/config.d.ts +1 -1
  19. package/dist/config.js +1 -1
  20. package/dist/contract.d.ts +2 -2
  21. package/dist/contract.js +2 -2
  22. package/dist/credentials.d.ts +41 -0
  23. package/dist/credentials.d.ts.map +1 -0
  24. package/dist/credentials.js +203 -0
  25. package/dist/credentials.js.map +1 -0
  26. package/dist/fixtures.d.ts.map +1 -1
  27. package/dist/fixtures.js +23 -2
  28. package/dist/fixtures.js.map +1 -1
  29. package/dist/http.d.ts +23 -9
  30. package/dist/http.d.ts.map +1 -1
  31. package/dist/http.js +106 -104
  32. package/dist/http.js.map +1 -1
  33. package/dist/index.d.ts +4 -2
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +3 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/server.d.ts +1 -1
  38. package/dist/server.d.ts.map +1 -1
  39. package/dist/server.js +8 -5
  40. package/dist/server.js.map +1 -1
  41. package/dist/stdio.d.ts.map +1 -1
  42. package/dist/stdio.js +25 -3
  43. package/dist/stdio.js.map +1 -1
  44. package/dist/tools.d.ts +1 -1
  45. package/dist/tools.d.ts.map +1 -1
  46. package/dist/tools.js +37 -16
  47. package/dist/tools.js.map +1 -1
  48. package/dist/types.d.ts +18 -1
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/package.json +10 -4
package/README.md CHANGED
@@ -10,9 +10,10 @@ persistent inbox on a domain Extrovert owns: created in one tool call, sends and
10
10
  **scoped key that expires and revokes on its own**. The key is bound to a fixed org + project (call
11
11
  `whoami` to see them) — there is no project selector to manage.
12
12
 
13
- > **Prerelease status:** the package is published on npm under the `next` dist-tag. Extrovert does
14
- > not currently operate a hosted MCP endpoint; use the packaged stdio server or an HTTP deployment
15
- > you operate yourself.
13
+ > **Prerelease status:** the package is published on npm under the `next` dist-tag. Extrovert also
14
+ > operates `https://mcp.extrovert.dev/mcp` as a stateless Streamable HTTP endpoint with browser OAuth.
15
+ > The same package installs `extrovert-mcp` for transports and `extrovert` for supported setup,
16
+ > authentication, mailbox, review-status, and reviewed-send commands.
16
17
 
17
18
  The standout tool is **`wait_for_email`** — a blocking call that returns the next matching message
18
19
  with the **OTP code and verification link already extracted**. Trigger a sign-in elsewhere, then act
@@ -66,6 +67,21 @@ ever reaches the model host. Keep every model host on the narrowest scoped key i
66
67
 
67
68
  ---
68
69
 
70
+ ## Connect with hosted OAuth
71
+
72
+ Give an OAuth-capable MCP client this URL:
73
+
74
+ ```text
75
+ https://mcp.extrovert.dev/mcp
76
+ ```
77
+
78
+ The endpoint publishes RFC 9728 protected-resource metadata and Clerk authorization-server
79
+ discovery. Compatible clients open the browser sign-in and consent flow, then store and refresh the
80
+ grant. Existing scoped `pk_agent_…` bearer keys also work when a client is configured explicitly.
81
+
82
+ The hosted service runs MCP SDK v2's fresh-server-per-request handler: no process-local session map,
83
+ sticky routing, or session teardown is required.
84
+
69
85
  ## Install and run the prerelease
70
86
 
71
87
  Use the explicit prerelease tag:
@@ -76,14 +92,39 @@ npx -y @extrovert.dev/mcp@next
76
92
 
77
93
  # inspect the packaged CLI
78
94
  npx -y @extrovert.dev/mcp@next --help
95
+
96
+ # register the stdio server in Codex or Claude Code
97
+ npx -y @extrovert.dev/mcp@next setup --host codex
98
+ npx -y @extrovert.dev/mcp@next setup --host claude
99
+ ```
100
+
101
+ Pin `@extrovert.dev/mcp@0.1.0-pre.5` for a reproducible dogfood environment. The package installs the
102
+ `extrovert-mcp` and `extrovert` aliases over one entrypoint; there is no second package or transport
103
+ implementation to keep in sync.
104
+
105
+ ### CLI fallback
106
+
107
+ The CLI uses the same typed client as MCP and prints ordinary message text without a `curl | jq`
108
+ pipeline:
109
+
110
+ ```bash
111
+ extrovert signup --human-email you@example.com --username support
112
+ extrovert verify # prompts for the emailed code
113
+ extrovert whoami
114
+ extrovert inbox list
115
+ extrovert message list --inbox support@smtp.extrovert.dev
116
+ extrovert message get msg_…
117
+ extrovert review status rr_…
79
118
  ```
80
119
 
81
- Pin `@extrovert.dev/mcp@0.1.0-pre.3` for a reproducible dogfood environment. The package installs the
82
- `extrovert-mcp` binary; this is the only Extrovert CLI currently shipped.
120
+ `signup` stores the limited key only in `pending-signup.json`. `verify` atomically writes the full
121
+ replacement to `credentials.json` and deletes the pending file. On Unix the directory is mode `0700`
122
+ and both files are mode `0600`. The default is `~/.config/extrovert/`; use
123
+ `EXTROVERT_CONFIG_DIR` for an isolated or managed runtime. `EXTROVERT_API_KEY` always wins when set.
83
124
 
84
125
  ## Build and run from source
85
126
 
86
- Requires Node ≥ 18.18. From this directory:
127
+ Requires Node ≥ 20. From this directory:
87
128
 
88
129
  ```bash
89
130
  pnpm install --frozen-lockfile
@@ -96,7 +137,7 @@ Two transports, one binary:
96
137
  # stdio — for local hosts (Claude Desktop, Claude Code, Cursor)
97
138
  node /absolute/path/to/extrovert/mcp/dist/bin.js
98
139
 
99
- # self-hosted Streamable HTTP at POST /mcp (default :8787)
140
+ # self-hosted stateless Streamable HTTP at /mcp (default :8787)
100
141
  node /absolute/path/to/extrovert/mcp/dist/bin.js --http --port 8787
101
142
  ```
102
143
 
@@ -113,13 +154,18 @@ pnpm run dev -- --http # tsx watch, HTTP
113
154
  |---|---|---|
114
155
  | `EXTROVERT_API_BASE_URL` | `https://api.extrovert.dev` | Base URL of the Extrovert REST API. |
115
156
  | `EXTROVERT_API_KEY` | *(empty)* | Scoped agent key (`pk_agent_…`) or enrollment key (`pk_enroll_…`). |
157
+ | `EXTROVERT_CONFIG_DIR` | platform config directory | Override the local credential directory. |
116
158
  | `EXTROVERT_MOCK` | *(off)* | Set `1` to force offline fixtures. |
117
159
  | `EXTROVERT_REQUEST_TIMEOUT_MS` | `30000` | Per-request timeout for non-blocking calls. |
118
160
  | `EXTROVERT_MAX_WAIT_MS` | `300000` | Upper bound the server allows `wait_for_email` to block. |
161
+ | `EXTROVERT_MCP_OAUTH_ENABLED` | *(off)* | Require Clerk OAuth or an introspected agent key on HTTP. |
162
+ | `EXTROVERT_MCP_OAUTH_ISSUER` | `https://clerk.extrovert.dev` | Clerk OAuth authorization-server issuer. |
163
+ | `EXTROVERT_MCP_PUBLIC_URL` | `https://mcp.extrovert.dev/mcp` | Public RFC 9728 resource identifier. |
119
164
  | `PORT` / `HOST` | `8787` / `0.0.0.0` | `--http` bind. |
120
165
 
121
166
  > **Offline fixtures are opt-in.** With no `EXTROVERT_API_KEY`, the server still talks to the live
122
- > API so an agent can start with `sign_up` and receive a limited key in-session. Set
167
+ > API so an agent can start with `sign_up` and receive a short-lived limited key in-session. The
168
+ > key expires with the OTP and is revoked when `verify_signup` returns its replacement. Set
123
169
  > `EXTROVERT_MOCK=1` to use deterministic in-memory fixtures; `create_inbox`, `send_email`, and
124
170
  > `wait_for_email` then operate on one coherent offline dataset.
125
171
 
@@ -134,23 +180,17 @@ Point any stdio-capable host at the prerelease package:
134
180
  "mcpServers": {
135
181
  "extrovert": {
136
182
  "command": "npx",
137
- "args": ["-y", "@extrovert.dev/mcp@next"],
138
- "env": {
139
- "EXTROVERT_API_BASE_URL": "https://api.extrovert.dev",
140
- "EXTROVERT_API_KEY": "pk_agent_…"
141
- }
183
+ "args": ["-y", "@extrovert.dev/mcp@next"]
142
184
  }
143
185
  }
144
186
  }
145
187
  ```
146
188
 
147
- For Claude Code, register that same local entrypoint:
189
+ For Codex or Claude Code, register that same local entrypoint:
148
190
 
149
191
  ```bash
150
- claude mcp add extrovert \
151
- --env EXTROVERT_API_BASE_URL=https://api.extrovert.dev \
152
- --env EXTROVERT_API_KEY=pk_agent_… \
153
- -- npx -y @extrovert.dev/mcp@next
192
+ npx -y @extrovert.dev/mcp@next setup --host codex
193
+ npx -y @extrovert.dev/mcp@next setup --host claude
154
194
  ```
155
195
 
156
196
  > **Offline:** omit `EXTROVERT_API_KEY` and set `EXTROVERT_MOCK=1`; the packaged server uses
@@ -225,23 +265,23 @@ continue. No polling, no separate "check the inbox" round-trips.
225
265
 
226
266
  ---
227
267
 
228
- ## Self-hosted HTTP notes
268
+ ## Stateless HTTP notes
229
269
 
230
270
  `extrovert-mcp --http` speaks MCP Streamable HTTP:
231
271
 
232
- - `POST /mcp` — client→server messages (the first must be `initialize`); the SDK assigns an
233
- `mcp-session-id` returned on the response.
234
- - `GET /mcp` — server→client SSE stream for an established session.
235
- - `DELETE /mcp` — tear a session down.
236
- - `GET /healthz` — liveness + mode (`mock`/`live`) + active session count.
272
+ - `POST /mcp` — one authenticated client→server request, served by a fresh MCP server instance.
273
+ - `GET /mcp` and `DELETE /mcp` — legacy stateless compatibility responses; no session is retained.
274
+ - `GET /healthz` — liveness, version, transport, and authentication mode.
275
+ - `GET /.well-known/oauth-protected-resource/mcp` — RFC 9728 protected-resource metadata when
276
+ OAuth is enabled.
237
277
 
238
- Each session gets an isolated server + client. A **per-request scoped key** may be supplied via
239
- `Authorization: Bearer …` (or `x-extrovert-api-key`), so one hosted deployment can serve many agents,
240
- each with their own scoped key falling back to `EXTROVERT_API_KEY` when no header is present. This
241
- is how a single hosted endpoint stays multi-tenant without ever holding an org-wide key.
278
+ Each request gets an isolated server + client and emits no `mcp-session-id`, so requests can land on
279
+ any cluster node. Production requires `Authorization: Bearer …` with either a Clerk OAuth access
280
+ token or an existing scoped `pk_agent_…` key. The OAuth principal and its fixed safe capabilities
281
+ are enforced again by the Extrovert API; the raw bearer token is never persisted by MCP or API.
242
282
 
243
- Extrovert does not operate this transport as a public hosted endpoint. These notes apply only to a
244
- deployment you control.
283
+ `EXTROVERT_API_KEY` and unauthenticated fixture mode remain local/self-hosting conveniences only;
284
+ the production service refuses to start without OAuth enabled.
245
285
 
246
286
  ---
247
287
 
@@ -250,9 +290,12 @@ deployment you control.
250
290
  ```text
251
291
  src/
252
292
  bin.ts CLI entrypoint (--http | stdio)
293
+ cli.ts setup/auth/signup/mailbox/review CLI using the typed client
294
+ credentials.ts permission-restricted, atomic local credential persistence
253
295
  server.ts McpServer factory + instructions
254
296
  stdio.ts stdio transport
255
- http.ts Streamable HTTP transport (Express, per-session, bearer key)
297
+ http.ts stateless Streamable HTTP transport (Express, OAuth + scoped keys)
298
+ auth.ts Clerk OAuth verification, RFC discovery, and agent-key introspection
256
299
  tools.ts manifest-driven tools — zod schemas, annotations, handlers, registration
257
300
  client.ts thin typed Extrovert REST client (one method per /v1 endpoint)
258
301
  config.ts env-driven configuration
package/dist/auth.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { type AuthInfo, type OAuthMetadata, type OAuthTokenVerifier } from "@modelcontextprotocol/server";
2
+ interface ClerkRequestState {
3
+ isAuthenticated: boolean;
4
+ toAuth(): unknown;
5
+ }
6
+ interface ClerkOAuthClient {
7
+ authenticateRequest(request: Request, options: {
8
+ acceptsToken: "oauth_token";
9
+ }): Promise<ClerkRequestState>;
10
+ }
11
+ export interface HostedAuthConfig {
12
+ issuer: URL;
13
+ resourceUrl: URL;
14
+ serviceDocumentationUrl: URL;
15
+ scopesSupported: string[];
16
+ clerkSecretKey: string;
17
+ clerkPublishableKey: string;
18
+ }
19
+ export interface ExtrovertTokenVerifierOptions {
20
+ clerk: ClerkOAuthClient;
21
+ resourceUrl: URL;
22
+ apiBaseUrl: string;
23
+ fetch?: typeof fetch;
24
+ }
25
+ /** Verify Clerk OAuth tokens and existing scoped Extrovert agent keys. */
26
+ export declare class ExtrovertTokenVerifier implements OAuthTokenVerifier {
27
+ private readonly clerk;
28
+ private readonly resourceUrl;
29
+ private readonly apiBaseUrl;
30
+ private readonly doFetch;
31
+ constructor(options: ExtrovertTokenVerifierOptions);
32
+ verifyAccessToken(token: string): Promise<AuthInfo>;
33
+ private verifyAgentKey;
34
+ }
35
+ export declare function loadHostedAuthConfig(env?: NodeJS.ProcessEnv): HostedAuthConfig;
36
+ export declare function createHostedTokenVerifier(config: HostedAuthConfig, apiBaseUrl: string): ExtrovertTokenVerifier;
37
+ export declare function discoverOAuthMetadata(issuer: URL, doFetch?: typeof fetch): Promise<OAuthMetadata>;
38
+ export {};
39
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACxB,MAAM,8BAA8B,CAAC;AAkBtC,UAAU,iBAAiB;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,IAAI,OAAO,CAAC;CACnB;AAED,UAAU,gBAAgB;IACxB,mBAAmB,CACjB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE;QAAE,YAAY,EAAE,aAAa,CAAA;KAAE,GACvC,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,GAAG,CAAC;IACZ,WAAW,EAAE,GAAG,CAAC;IACjB,uBAAuB,EAAE,GAAG,CAAC;IAC7B,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,EAAE,gBAAgB,CAAC;IACxB,WAAW,EAAE,GAAG,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,0EAA0E;AAC1E,qBAAa,sBAAuB,YAAW,kBAAkB;IAC/D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAM;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;gBAE3B,OAAO,EAAE,6BAA6B;IAO5C,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;YA0C3C,cAAc;CAiC7B;AAED,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,gBAAgB,CA8B3F;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,MAAM,GACjB,sBAAsB,CASxB;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,GAAG,EACX,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,aAAa,CAAC,CAwBxB"}
package/dist/auth.js ADDED
@@ -0,0 +1,201 @@
1
+ import { createClerkClient } from "@clerk/backend";
2
+ import { OAuthError, OAuthErrorCode, } from "@modelcontextprotocol/server";
3
+ const DEFAULT_ISSUER = "https://clerk.extrovert.dev";
4
+ const DEFAULT_PUBLIC_URL = "https://mcp.extrovert.dev/mcp";
5
+ const DISCOVERY_TIMEOUT_MS = 10_000;
6
+ const AGENT_KEY_ATTESTATION_SECONDS = 300;
7
+ /** Verify Clerk OAuth tokens and existing scoped Extrovert agent keys. */
8
+ export class ExtrovertTokenVerifier {
9
+ clerk;
10
+ resourceUrl;
11
+ apiBaseUrl;
12
+ doFetch;
13
+ constructor(options) {
14
+ this.clerk = options.clerk;
15
+ this.resourceUrl = options.resourceUrl;
16
+ this.apiBaseUrl = options.apiBaseUrl.replace(/\/+$/, "");
17
+ this.doFetch = options.fetch ?? fetch;
18
+ }
19
+ async verifyAccessToken(token) {
20
+ if (token.startsWith("pk_agent_"))
21
+ return this.verifyAgentKey(token);
22
+ if (token.startsWith("pk_enroll_")) {
23
+ throw invalidToken("Enrollment tokens cannot authenticate the hosted MCP endpoint");
24
+ }
25
+ const request = new Request(this.resourceUrl, {
26
+ headers: { authorization: `Bearer ${token}` },
27
+ });
28
+ let state;
29
+ try {
30
+ state = await this.clerk.authenticateRequest(request, {
31
+ acceptsToken: "oauth_token",
32
+ });
33
+ }
34
+ catch {
35
+ throw invalidToken("OAuth access token verification failed");
36
+ }
37
+ if (!state.isAuthenticated)
38
+ throw invalidToken("OAuth access token is invalid or expired");
39
+ const auth = state.toAuth();
40
+ if (!isClerkOAuthAuth(auth)) {
41
+ throw invalidToken("Bearer token is not a Clerk OAuth access token");
42
+ }
43
+ const expiresAt = jwtExpiry(token);
44
+ if (expiresAt === undefined) {
45
+ // Production is deliberately configured for Clerk JWT access tokens. The
46
+ // MCP bearer gate requires a concrete expiry and must not invent one.
47
+ throw invalidToken("OAuth access token has no verifiable expiration");
48
+ }
49
+ return {
50
+ token,
51
+ clientId: auth.oauth_token.clientId,
52
+ scopes: auth.scopes,
53
+ expiresAt,
54
+ resource: this.resourceUrl,
55
+ extra: {
56
+ tokenType: "oauth_token",
57
+ userId: auth.oauth_token.userId,
58
+ subject: auth.subject,
59
+ },
60
+ };
61
+ }
62
+ async verifyAgentKey(token) {
63
+ const controller = new AbortController();
64
+ const timeout = setTimeout(() => controller.abort(), DISCOVERY_TIMEOUT_MS);
65
+ try {
66
+ const response = await this.doFetch(`${this.apiBaseUrl}/v1/auth/me`, {
67
+ headers: { authorization: `Bearer ${token}` },
68
+ signal: controller.signal,
69
+ });
70
+ if (!response.ok)
71
+ throw invalidToken("Extrovert agent key is invalid or revoked");
72
+ const body = (await response.json());
73
+ const keyId = requiredString(body.key_id);
74
+ const agentId = requiredString(body.agent_id);
75
+ const scopes = stringArray(body.scopes);
76
+ if (!keyId || !agentId) {
77
+ throw invalidToken("Extrovert agent-key introspection returned an invalid principal");
78
+ }
79
+ return {
80
+ token,
81
+ clientId: `extrovert-agent:${agentId}`,
82
+ scopes,
83
+ // This is the lifetime of the successful introspection result. Because
84
+ // HTTP is stateless, every later request validates the key again.
85
+ expiresAt: Math.floor(Date.now() / 1000) + AGENT_KEY_ATTESTATION_SECONDS,
86
+ resource: this.resourceUrl,
87
+ extra: { tokenType: "agent_key", keyId, agentId },
88
+ };
89
+ }
90
+ catch (error) {
91
+ if (OAuthError.isInstance(error))
92
+ throw error;
93
+ throw invalidToken("Extrovert agent-key verification failed");
94
+ }
95
+ finally {
96
+ clearTimeout(timeout);
97
+ }
98
+ }
99
+ }
100
+ export function loadHostedAuthConfig(env = process.env) {
101
+ const issuer = new URL((env.EXTROVERT_MCP_OAUTH_ISSUER ?? DEFAULT_ISSUER).trim());
102
+ const resourceUrl = new URL((env.EXTROVERT_MCP_PUBLIC_URL ?? DEFAULT_PUBLIC_URL).trim());
103
+ const serviceDocumentationUrl = new URL((env.EXTROVERT_MCP_DOCUMENTATION_URL ?? "https://docs.extrovert.dev/mcp/overview/").trim());
104
+ const clerkSecretKey = (env.CLERK_SECRET_KEY ?? "").trim();
105
+ const clerkPublishableKey = (env.CLERK_PUBLISHABLE_KEY ??
106
+ env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY ??
107
+ "").trim();
108
+ if (!clerkSecretKey || !clerkPublishableKey) {
109
+ throw new Error("hosted MCP OAuth requires CLERK_SECRET_KEY and CLERK_PUBLISHABLE_KEY");
110
+ }
111
+ if (issuer.protocol !== "https:" || resourceUrl.protocol !== "https:") {
112
+ throw new Error("hosted MCP OAuth issuer and resource URL must use HTTPS");
113
+ }
114
+ const scopesSupported = (env.EXTROVERT_MCP_OAUTH_SCOPES ?? "openid profile email")
115
+ .split(/[\s,]+/)
116
+ .map((scope) => scope.trim())
117
+ .filter(Boolean);
118
+ return {
119
+ issuer,
120
+ resourceUrl,
121
+ serviceDocumentationUrl,
122
+ scopesSupported,
123
+ clerkSecretKey,
124
+ clerkPublishableKey,
125
+ };
126
+ }
127
+ export function createHostedTokenVerifier(config, apiBaseUrl) {
128
+ return new ExtrovertTokenVerifier({
129
+ clerk: createClerkClient({
130
+ secretKey: config.clerkSecretKey,
131
+ publishableKey: config.clerkPublishableKey,
132
+ }),
133
+ resourceUrl: config.resourceUrl,
134
+ apiBaseUrl,
135
+ });
136
+ }
137
+ export async function discoverOAuthMetadata(issuer, doFetch = fetch) {
138
+ const url = new URL("/.well-known/oauth-authorization-server", issuer);
139
+ const controller = new AbortController();
140
+ const timeout = setTimeout(() => controller.abort(), DISCOVERY_TIMEOUT_MS);
141
+ try {
142
+ const response = await doFetch(url, {
143
+ headers: { accept: "application/json" },
144
+ signal: controller.signal,
145
+ });
146
+ if (!response.ok)
147
+ throw new Error(`OAuth discovery returned HTTP ${response.status}`);
148
+ const metadata = (await response.json());
149
+ if (metadata.issuer !== issuer.toString().replace(/\/$/, "")) {
150
+ throw new Error("OAuth discovery issuer does not match EXTROVERT_MCP_OAUTH_ISSUER");
151
+ }
152
+ for (const field of ["authorization_endpoint", "token_endpoint"]) {
153
+ const value = metadata[field];
154
+ if (typeof value !== "string" || new URL(value).protocol !== "https:") {
155
+ throw new Error(`OAuth discovery is missing a secure ${field}`);
156
+ }
157
+ }
158
+ return metadata;
159
+ }
160
+ finally {
161
+ clearTimeout(timeout);
162
+ }
163
+ }
164
+ function isClerkOAuthAuth(value) {
165
+ if (!value || typeof value !== "object")
166
+ return false;
167
+ const candidate = value;
168
+ return (candidate.isAuthenticated === true &&
169
+ candidate.tokenType === "oauth_token" &&
170
+ typeof candidate.subject === "string" &&
171
+ Array.isArray(candidate.scopes) &&
172
+ !!candidate.oauth_token &&
173
+ typeof candidate.oauth_token.userId === "string" &&
174
+ typeof candidate.oauth_token.clientId === "string");
175
+ }
176
+ function jwtExpiry(token) {
177
+ const payload = token.split(".")[1];
178
+ if (!payload)
179
+ return undefined;
180
+ try {
181
+ const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
182
+ return typeof decoded.exp === "number" && Number.isSafeInteger(decoded.exp)
183
+ ? decoded.exp
184
+ : undefined;
185
+ }
186
+ catch {
187
+ return undefined;
188
+ }
189
+ }
190
+ function requiredString(value) {
191
+ return typeof value === "string" ? value.trim() : "";
192
+ }
193
+ function stringArray(value) {
194
+ return Array.isArray(value)
195
+ ? value.filter((item) => typeof item === "string" && item.trim() !== "")
196
+ : [];
197
+ }
198
+ function invalidToken(message) {
199
+ return new OAuthError(OAuthErrorCode.InvalidToken, message);
200
+ }
201
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/bin.d.ts CHANGED
@@ -1,13 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Extrovert MCP server CLI entrypoint.
3
+ * Extrovert MCP + CLI entrypoint.
4
4
  *
5
- * extrovert-mcp # stdio transport (default; for local MCP hosts)
6
- * extrovert-mcp --http # hosted Streamable HTTP transport
7
- * extrovert-mcp --http --port 9000
8
- *
9
- * Configuration is read from the environment (see config.ts):
10
- * EXTROVERT_API_BASE_URL, EXTROVERT_API_KEY, EXTROVERT_MOCK, ...
5
+ * extrovert-mcp # stdio transport for MCP hosts
6
+ * extrovert-mcp --http --port 9000 # hosted Streamable HTTP
7
+ * extrovert setup --host codex # register the packaged stdio server
8
+ * extrovert signup / verify / message… # supported CLI fallback
11
9
  */
12
10
  export {};
13
11
  //# sourceMappingURL=bin.d.ts.map
package/dist/bin.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG"}
package/dist/bin.js CHANGED
@@ -1,20 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Extrovert MCP server CLI entrypoint.
3
+ * Extrovert MCP + CLI entrypoint.
4
4
  *
5
- * extrovert-mcp # stdio transport (default; for local MCP hosts)
6
- * extrovert-mcp --http # hosted Streamable HTTP transport
7
- * extrovert-mcp --http --port 9000
8
- *
9
- * Configuration is read from the environment (see config.ts):
10
- * EXTROVERT_API_BASE_URL, EXTROVERT_API_KEY, EXTROVERT_MOCK, ...
5
+ * extrovert-mcp # stdio transport for MCP hosts
6
+ * extrovert-mcp --http --port 9000 # hosted Streamable HTTP
7
+ * extrovert setup --host codex # register the packaged stdio server
8
+ * extrovert signup / verify / message… # supported CLI fallback
11
9
  */
10
+ import { CLI_HELP, runCli } from "./cli.js";
12
11
  import { runHttp } from "./http.js";
13
12
  import { runStdio } from "./stdio.js";
14
- function parseArgs(argv) {
15
- const args = { http: false, help: false };
16
- for (let i = 0; i < argv.length; i++) {
17
- const arg = argv[i];
13
+ const CLI_COMMANDS = new Set([
14
+ "setup",
15
+ "auth",
16
+ "signup",
17
+ "verify",
18
+ "whoami",
19
+ "inbox",
20
+ "message",
21
+ "review",
22
+ "send",
23
+ "help",
24
+ ]);
25
+ const TRANSPORT_HELP = `
26
+ MCP transports:
27
+ extrovert-mcp stdio (default for local hosts)
28
+ extrovert-mcp --http Streamable HTTP at /mcp (default port 8787)
29
+ extrovert-mcp --http --port N override the HTTP port
30
+
31
+ Environment:
32
+ EXTROVERT_API_BASE_URL API URL (default https://api.extrovert.dev)
33
+ EXTROVERT_API_KEY scoped agent key; overrides the stored credential
34
+ EXTROVERT_CONFIG_DIR override the permission-restricted credential directory
35
+ EXTROVERT_MOCK set to 1 for offline fixtures
36
+ `;
37
+ function parseTransportArgs(argv) {
38
+ const args = { http: false };
39
+ for (let index = 0; index < argv.length; index++) {
40
+ const arg = argv[index];
18
41
  switch (arg) {
19
42
  case "--http":
20
43
  case "--hosted":
@@ -24,66 +47,54 @@ function parseArgs(argv) {
24
47
  args.http = false;
25
48
  break;
26
49
  case "--port": {
27
- const next = argv[++i];
28
- if (next)
29
- args.port = Number.parseInt(next, 10);
50
+ const next = argv[++index];
51
+ if (!next)
52
+ throw new Error("--port requires a value");
53
+ const port = Number(next);
54
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535)
55
+ throw new Error("--port must be from 1 to 65535");
56
+ args.port = port;
30
57
  break;
31
58
  }
32
59
  case "--host": {
33
- const next = argv[++i];
34
- if (next)
35
- args.host = next;
60
+ const next = argv[++index];
61
+ if (!next)
62
+ throw new Error("--host requires a value");
63
+ args.host = next;
36
64
  break;
37
65
  }
38
- case "-h":
39
- case "--help":
40
- args.help = true;
41
- break;
42
66
  default:
43
- // Ignore unknown flags so MCP hosts can pass extras harmlessly.
67
+ // Retain compatibility with MCP hosts that append harmless flags.
44
68
  break;
45
69
  }
46
70
  }
47
71
  return args;
48
72
  }
49
- const HELP = `extrovert-mcp — MCP server for Extrovert (a real mailbox for your agent, in one call)
50
-
51
- Usage:
52
- extrovert-mcp [--http] [--port <n>] [--host <addr>]
53
-
54
- Transports:
55
- (default) stdio — for Claude Desktop, Claude Code, Cursor, and other local hosts
56
- --http hosted Streamable HTTP transport at POST /mcp (default port 8787)
57
-
58
- Environment:
59
- EXTROVERT_API_BASE_URL Base URL of the Extrovert REST API (default https://api.extrovert.dev)
60
- EXTROVERT_API_KEY Scoped agent key (pk_agent_…) or enrollment key (pk_enroll_…)
61
- EXTROVERT_MOCK Set to 1 to force offline fixture mode
62
- PORT / HOST Override --http bind (also honored by hosting platforms)
63
-
64
- Tools: redeem_enrollment, create_inbox, list_inboxes, get_inbox, update_inbox, delete_inbox,
65
- send_email, reply_email, read_messages, list_threads, search, wait_for_email
66
- `;
67
73
  async function main() {
68
- const args = parseArgs(process.argv.slice(2));
69
- if (args.help) {
70
- process.stdout.write(HELP);
74
+ const argv = process.argv.slice(2);
75
+ const first = argv[0];
76
+ if (first === "--help" || first === "-h") {
77
+ process.stdout.write(`${CLI_HELP}${TRANSPORT_HELP}`);
71
78
  return;
72
79
  }
80
+ if (first && CLI_COMMANDS.has(first)) {
81
+ process.exitCode = await runCli(argv);
82
+ return;
83
+ }
84
+ const args = parseTransportArgs(argv);
73
85
  if (args.http) {
74
- const opts = {};
86
+ const options = {};
75
87
  if (args.port !== undefined)
76
- opts.port = args.port;
88
+ options.port = args.port;
77
89
  if (args.host !== undefined)
78
- opts.host = args.host;
79
- await runHttp(opts);
80
- }
81
- else {
82
- await runStdio();
90
+ options.host = args.host;
91
+ await runHttp(options);
92
+ return;
83
93
  }
94
+ await runStdio();
84
95
  }
85
- main().catch((err) => {
86
- process.stderr.write(`extrovert-mcp: fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
96
+ main().catch((error) => {
97
+ process.stderr.write(`extrovert: fatal: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
87
98
  process.exit(1);
88
99
  });
89
100
  //# sourceMappingURL=bin.js.map
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAStC,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,IAAI,GAAY,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,QAAQ,CAAC;YACd,KAAK,UAAU;gBACb,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACjB,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;gBAClB,MAAM;YACR,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACvB,IAAI,IAAI;oBAAE,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAChD,MAAM;YACR,CAAC;YACD,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACvB,IAAI,IAAI;oBAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBAC3B,MAAM;YACR,CAAC;YACD,KAAK,IAAI,CAAC;YACV,KAAK,QAAQ;gBACX,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;gBACjB,MAAM;YACR;gBACE,gEAAgE;gBAChE,MAAM;QACV,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;CAiBZ,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,IAAI,GAAqC,EAAE,CAAC;QAClD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACnD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACnD,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;SAAM,CAAC;QACN,MAAM,QAAQ,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { type SpawnSyncReturns } from "node:child_process";
2
+ import type { Readable, Writable } from "node:stream";
3
+ import { type CredentialStore } from "./credentials.js";
4
+ export declare const CLI_HELP = "extrovert \u2014 setup, authenticate, and use Extrovert without custom transport code\n\nUsage:\n extrovert setup [--host codex|claude]\n extrovert auth login --with-token\n extrovert auth status\n extrovert auth logout\n extrovert signup --human-email <email> [--username <name>]\n extrovert verify [--otp <code>]\n extrovert whoami [--json]\n extrovert inbox list [--limit <n>] [--json]\n extrovert message list --inbox <address> [--unread] [--limit <n>] [--json]\n extrovert message get <message-id> [--source] [--json]\n extrovert review list [--state <state>] [--limit <n>] [--json]\n extrovert review status <review-id> [--json]\n extrovert send --inbox <address> --to <email> --subject <text> --text <body>\n --summary <reviewer-intent> [--client-id <id>] [--rules-reviewed]\n\nAuthentication:\n signup stores only a short-lived pending key; verify atomically replaces it with\n the full key in a permission-restricted local credential file. EXTROVERT_API_KEY\n always takes precedence over the stored credential.\n\nMCP transport:\n Running extrovert-mcp with no command starts stdio. The official plugin and\n 'extrovert setup' configure that published server directly.\n";
5
+ export interface CliOptions {
6
+ env?: NodeJS.ProcessEnv;
7
+ stdin?: NodeJS.ReadStream | Readable;
8
+ stdout?: NodeJS.WriteStream | Writable;
9
+ stderr?: NodeJS.WriteStream | Writable;
10
+ credentialStore?: CredentialStore;
11
+ runCommand?: (command: string, args: string[]) => SpawnSyncReturns<string>;
12
+ }
13
+ export declare function runCli(argv: string[], options?: CliOptions): Promise<number>;
14
+ //# sourceMappingURL=cli.d.ts.map