@mcp-z/oauth 1.0.0

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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -0
  3. package/dist/cjs/account-utils.d.cts +107 -0
  4. package/dist/cjs/account-utils.d.ts +107 -0
  5. package/dist/cjs/account-utils.js +481 -0
  6. package/dist/cjs/account-utils.js.map +1 -0
  7. package/dist/cjs/index.d.cts +19 -0
  8. package/dist/cjs/index.d.ts +19 -0
  9. package/dist/cjs/index.js +149 -0
  10. package/dist/cjs/index.js.map +1 -0
  11. package/dist/cjs/jwt-auth.d.cts +53 -0
  12. package/dist/cjs/jwt-auth.d.ts +53 -0
  13. package/dist/cjs/jwt-auth.js +417 -0
  14. package/dist/cjs/jwt-auth.js.map +1 -0
  15. package/dist/cjs/key-utils.d.cts +131 -0
  16. package/dist/cjs/key-utils.d.ts +131 -0
  17. package/dist/cjs/key-utils.js +421 -0
  18. package/dist/cjs/key-utils.js.map +1 -0
  19. package/dist/cjs/lib/account-server/index.d.cts +45 -0
  20. package/dist/cjs/lib/account-server/index.d.ts +45 -0
  21. package/dist/cjs/lib/account-server/index.js +67 -0
  22. package/dist/cjs/lib/account-server/index.js.map +1 -0
  23. package/dist/cjs/lib/account-server/loopback.d.cts +22 -0
  24. package/dist/cjs/lib/account-server/loopback.d.ts +22 -0
  25. package/dist/cjs/lib/account-server/loopback.js +778 -0
  26. package/dist/cjs/lib/account-server/loopback.js.map +1 -0
  27. package/dist/cjs/lib/account-server/me.d.cts +23 -0
  28. package/dist/cjs/lib/account-server/me.d.ts +23 -0
  29. package/dist/cjs/lib/account-server/me.js +412 -0
  30. package/dist/cjs/lib/account-server/me.js.map +1 -0
  31. package/dist/cjs/lib/account-server/shared-utils.d.cts +6 -0
  32. package/dist/cjs/lib/account-server/shared-utils.d.ts +6 -0
  33. package/dist/cjs/lib/account-server/shared-utils.js +235 -0
  34. package/dist/cjs/lib/account-server/shared-utils.js.map +1 -0
  35. package/dist/cjs/lib/account-server/stateless.d.cts +20 -0
  36. package/dist/cjs/lib/account-server/stateless.d.ts +20 -0
  37. package/dist/cjs/lib/account-server/stateless.js +32 -0
  38. package/dist/cjs/lib/account-server/stateless.js.map +1 -0
  39. package/dist/cjs/lib/account-server/types.d.cts +32 -0
  40. package/dist/cjs/lib/account-server/types.d.ts +32 -0
  41. package/dist/cjs/lib/account-server/types.js +7 -0
  42. package/dist/cjs/lib/account-server/types.js.map +1 -0
  43. package/dist/cjs/lib/dcr-types.d.cts +126 -0
  44. package/dist/cjs/lib/dcr-types.d.ts +126 -0
  45. package/dist/cjs/lib/dcr-types.js +12 -0
  46. package/dist/cjs/lib/dcr-types.js.map +1 -0
  47. package/dist/cjs/lib/rfc-metadata-types.d.cts +46 -0
  48. package/dist/cjs/lib/rfc-metadata-types.d.ts +46 -0
  49. package/dist/cjs/lib/rfc-metadata-types.js +8 -0
  50. package/dist/cjs/lib/rfc-metadata-types.js.map +1 -0
  51. package/dist/cjs/package.json +1 -0
  52. package/dist/cjs/pkce.d.cts +36 -0
  53. package/dist/cjs/pkce.d.ts +36 -0
  54. package/dist/cjs/pkce.js +25 -0
  55. package/dist/cjs/pkce.js.map +1 -0
  56. package/dist/cjs/sanitizer.d.cts +37 -0
  57. package/dist/cjs/sanitizer.d.ts +37 -0
  58. package/dist/cjs/sanitizer.js +407 -0
  59. package/dist/cjs/sanitizer.js.map +1 -0
  60. package/dist/cjs/schemas/index.d.cts +36 -0
  61. package/dist/cjs/schemas/index.d.ts +36 -0
  62. package/dist/cjs/schemas/index.js +28 -0
  63. package/dist/cjs/schemas/index.js.map +1 -0
  64. package/dist/cjs/session-auth.d.cts +79 -0
  65. package/dist/cjs/session-auth.d.ts +79 -0
  66. package/dist/cjs/session-auth.js +354 -0
  67. package/dist/cjs/session-auth.js.map +1 -0
  68. package/dist/cjs/templates.d.cts +18 -0
  69. package/dist/cjs/templates.d.ts +18 -0
  70. package/dist/cjs/templates.js +38 -0
  71. package/dist/cjs/templates.js.map +1 -0
  72. package/dist/cjs/types.d.cts +343 -0
  73. package/dist/cjs/types.d.ts +343 -0
  74. package/dist/cjs/types.js +210 -0
  75. package/dist/cjs/types.js.map +1 -0
  76. package/dist/esm/account-utils.d.ts +107 -0
  77. package/dist/esm/account-utils.js +179 -0
  78. package/dist/esm/account-utils.js.map +1 -0
  79. package/dist/esm/index.d.ts +19 -0
  80. package/dist/esm/index.js +23 -0
  81. package/dist/esm/index.js.map +1 -0
  82. package/dist/esm/jwt-auth.d.ts +53 -0
  83. package/dist/esm/jwt-auth.js +164 -0
  84. package/dist/esm/jwt-auth.js.map +1 -0
  85. package/dist/esm/key-utils.d.ts +131 -0
  86. package/dist/esm/key-utils.js +143 -0
  87. package/dist/esm/key-utils.js.map +1 -0
  88. package/dist/esm/lib/account-server/index.d.ts +45 -0
  89. package/dist/esm/lib/account-server/index.js +41 -0
  90. package/dist/esm/lib/account-server/index.js.map +1 -0
  91. package/dist/esm/lib/account-server/loopback.d.ts +22 -0
  92. package/dist/esm/lib/account-server/loopback.js +372 -0
  93. package/dist/esm/lib/account-server/loopback.js.map +1 -0
  94. package/dist/esm/lib/account-server/me.d.ts +23 -0
  95. package/dist/esm/lib/account-server/me.js +170 -0
  96. package/dist/esm/lib/account-server/me.js.map +1 -0
  97. package/dist/esm/lib/account-server/shared-utils.d.ts +6 -0
  98. package/dist/esm/lib/account-server/shared-utils.js +24 -0
  99. package/dist/esm/lib/account-server/shared-utils.js.map +1 -0
  100. package/dist/esm/lib/account-server/stateless.d.ts +20 -0
  101. package/dist/esm/lib/account-server/stateless.js +25 -0
  102. package/dist/esm/lib/account-server/stateless.js.map +1 -0
  103. package/dist/esm/lib/account-server/types.d.ts +32 -0
  104. package/dist/esm/lib/account-server/types.js +6 -0
  105. package/dist/esm/lib/account-server/types.js.map +1 -0
  106. package/dist/esm/lib/dcr-types.d.ts +126 -0
  107. package/dist/esm/lib/dcr-types.js +13 -0
  108. package/dist/esm/lib/dcr-types.js.map +1 -0
  109. package/dist/esm/lib/rfc-metadata-types.d.ts +46 -0
  110. package/dist/esm/lib/rfc-metadata-types.js +7 -0
  111. package/dist/esm/lib/rfc-metadata-types.js.map +1 -0
  112. package/dist/esm/package.json +1 -0
  113. package/dist/esm/pkce.d.ts +36 -0
  114. package/dist/esm/pkce.js +33 -0
  115. package/dist/esm/pkce.js.map +1 -0
  116. package/dist/esm/sanitizer.d.ts +37 -0
  117. package/dist/esm/sanitizer.js +256 -0
  118. package/dist/esm/sanitizer.js.map +1 -0
  119. package/dist/esm/schemas/index.d.ts +36 -0
  120. package/dist/esm/schemas/index.js +19 -0
  121. package/dist/esm/schemas/index.js.map +1 -0
  122. package/dist/esm/session-auth.d.ts +79 -0
  123. package/dist/esm/session-auth.js +141 -0
  124. package/dist/esm/session-auth.js.map +1 -0
  125. package/dist/esm/templates.d.ts +18 -0
  126. package/dist/esm/templates.js +132 -0
  127. package/dist/esm/templates.js.map +1 -0
  128. package/dist/esm/types.d.ts +343 -0
  129. package/dist/esm/types.js +34 -0
  130. package/dist/esm/types.js.map +1 -0
  131. package/package.json +82 -0
@@ -0,0 +1,372 @@
1
+ /**
2
+ * Loopback OAuth account management tools.
3
+ *
4
+ * Provides account management for LoopbackOAuthProvider (server-managed multi-account).
5
+ * Users can add multiple accounts, switch between them, and manage identities.
6
+ *
7
+ * Tools:
8
+ * - account-me: Show current user identity (email, alias, session expiry)
9
+ * - account-switch: Use account (add if needed, switch if already linked)
10
+ * - account-remove: Remove account and delete tokens
11
+ * - account-list: Show all linked accounts (returns empty array if none)
12
+ */ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
13
+ import { z } from 'zod';
14
+ import { addAccount, getAccountInfo, getActiveAccount, getLinkedAccounts, removeAccount, setAccountInfo, setActiveAccount } from '../../account-utils.js';
15
+ import { createAccountMe } from './me.js';
16
+ import { findAccountByEmailOrAlias } from './shared-utils.js';
17
+ /**
18
+ * Create loopback OAuth account management tools.
19
+ * Returns 4 tools: account-me, account-switch, account-remove, account-list.
20
+ */ export function createLoopback(config) {
21
+ const { service, store, logger, auth } = config;
22
+ // Create account-me tool
23
+ const meTools = createAccountMe({
24
+ service,
25
+ store,
26
+ logger,
27
+ mode: 'loopback'
28
+ });
29
+ const tools = [
30
+ ...meTools.tools,
31
+ // account-switch
32
+ {
33
+ name: 'account-switch',
34
+ config: {
35
+ description: `Use ${service} account (smart mode). If email/alias provided and already linked, switches to it without triggering OAuth. If not linked or no email provided, triggers OAuth browser flow to add account. Returns account email, whether it was newly added, and total account count.`,
36
+ inputSchema: {
37
+ email: z.string().optional().describe('Email address to link (if already linked, switches without OAuth)'),
38
+ alias: z.string().optional().describe('Optional alias for easy identification')
39
+ },
40
+ outputSchema: {
41
+ result: z.discriminatedUnion('type', [
42
+ z.object({
43
+ type: z.literal('success'),
44
+ email: z.string(),
45
+ isNew: z.boolean(),
46
+ totalAccounts: z.number(),
47
+ message: z.string()
48
+ })
49
+ ])
50
+ }
51
+ },
52
+ handler: async (args)=>{
53
+ const params = args;
54
+ try {
55
+ var _ref;
56
+ logger.info(`Starting account switch for ${service}`, {
57
+ email: params.email,
58
+ alias: params.alias
59
+ });
60
+ // Get existing accounts
61
+ const existingAccounts = await getLinkedAccounts(store, {
62
+ service
63
+ });
64
+ let email;
65
+ let isNew;
66
+ // Smart behavior: check if email provided and already linked
67
+ if (params.email) {
68
+ // Find account by email or alias
69
+ const accountId = await findAccountByEmailOrAlias(store, service, params.email);
70
+ if (accountId) {
71
+ // Account already linked - just switch to it
72
+ email = accountId;
73
+ isNew = false;
74
+ logger.info(`Account already linked: ${email}, switching without OAuth`);
75
+ // Set as active account
76
+ await setActiveAccount(store, {
77
+ service,
78
+ accountId: email
79
+ });
80
+ // Update alias if provided
81
+ if (params.alias) {
82
+ var _ref1;
83
+ const existingInfo = await getAccountInfo(store, {
84
+ accountId: email,
85
+ service
86
+ });
87
+ const accountInfo = {
88
+ email,
89
+ alias: params.alias,
90
+ addedAt: (_ref1 = existingInfo === null || existingInfo === void 0 ? void 0 : existingInfo.addedAt) !== null && _ref1 !== void 0 ? _ref1 : new Date().toISOString()
91
+ };
92
+ await setAccountInfo(store, {
93
+ accountId: email,
94
+ service
95
+ }, accountInfo);
96
+ }
97
+ const result = {
98
+ type: 'success',
99
+ email,
100
+ isNew: false,
101
+ totalAccounts: existingAccounts.length,
102
+ message: `Account already linked: ${email}. Set as active account (no OAuth needed).`
103
+ };
104
+ return {
105
+ content: [
106
+ {
107
+ type: 'text',
108
+ text: JSON.stringify(result, null, 2)
109
+ }
110
+ ],
111
+ structuredContent: {
112
+ result
113
+ }
114
+ };
115
+ }
116
+ }
117
+ // Not linked or no email provided - trigger OAuth flow for new account
118
+ // Check if provider supports interactive authentication
119
+ if (!auth.authenticateNewAccount) {
120
+ throw new Error('Account switching requires interactive authentication. ' + 'The current auth provider does not support authenticateNewAccount().');
121
+ }
122
+ // Trigger new authentication with account selection
123
+ email = await auth.authenticateNewAccount();
124
+ // Check if account already exists (in case OAuth returned different email than requested)
125
+ isNew = !existingAccounts.includes(email);
126
+ if (isNew) {
127
+ // Add new account
128
+ await addAccount(store, {
129
+ service,
130
+ accountId: email
131
+ });
132
+ logger.info(`Added new ${service} account`, {
133
+ email
134
+ });
135
+ } else {
136
+ logger.info(`Account already linked: ${email}`);
137
+ }
138
+ // Set/update account info
139
+ const existingInfo = await getAccountInfo(store, {
140
+ accountId: email,
141
+ service
142
+ });
143
+ const accountInfo = {
144
+ email,
145
+ ...params.alias ? {
146
+ alias: params.alias
147
+ } : {},
148
+ addedAt: isNew ? new Date().toISOString() : (_ref = existingInfo === null || existingInfo === void 0 ? void 0 : existingInfo.addedAt) !== null && _ref !== void 0 ? _ref : new Date().toISOString()
149
+ };
150
+ await setAccountInfo(store, {
151
+ accountId: email,
152
+ service
153
+ }, accountInfo);
154
+ // Set as active account
155
+ await setActiveAccount(store, {
156
+ service,
157
+ accountId: email
158
+ });
159
+ const totalAccounts = isNew ? existingAccounts.length + 1 : existingAccounts.length;
160
+ const result = {
161
+ type: 'success',
162
+ email,
163
+ isNew,
164
+ totalAccounts,
165
+ message: isNew ? `Successfully added ${service} account: ${email} (${totalAccounts} total)` : `Account already linked: ${email}. Set as active account.`
166
+ };
167
+ return {
168
+ content: [
169
+ {
170
+ type: 'text',
171
+ text: JSON.stringify(result, null, 2)
172
+ }
173
+ ],
174
+ structuredContent: {
175
+ result
176
+ }
177
+ };
178
+ } catch (error) {
179
+ const message = error instanceof Error ? error.message : String(error);
180
+ throw new McpError(ErrorCode.InternalError, `Error switching ${service} account: ${message}`, {
181
+ stack: error instanceof Error ? error.stack : undefined
182
+ });
183
+ }
184
+ }
185
+ },
186
+ // account-remove
187
+ {
188
+ name: 'account-remove',
189
+ config: {
190
+ description: `Remove ${service} account and delete stored tokens permanently. If removing the active account, the first remaining account becomes active. Requires email or alias parameter.`,
191
+ inputSchema: {
192
+ accountId: z.string().min(1).describe('Email address or alias of account to remove')
193
+ },
194
+ outputSchema: {
195
+ result: z.discriminatedUnion('type', [
196
+ z.object({
197
+ type: z.literal('success'),
198
+ service: z.string(),
199
+ removed: z.string(),
200
+ remainingAccounts: z.number(),
201
+ newActiveAccount: z.string().optional(),
202
+ message: z.string()
203
+ })
204
+ ])
205
+ }
206
+ },
207
+ handler: async (args)=>{
208
+ const params = args;
209
+ try {
210
+ const linkedAccounts = await getLinkedAccounts(store, {
211
+ service
212
+ });
213
+ if (linkedAccounts.length === 0) {
214
+ throw new Error(`No ${service} accounts to remove`);
215
+ }
216
+ // Find account by email or alias
217
+ const accountId = await findAccountByEmailOrAlias(store, service, params.accountId);
218
+ if (!accountId) {
219
+ throw new Error(`Account not found: ${params.accountId}`);
220
+ }
221
+ // Get current active account
222
+ const activeAccount = await getActiveAccount(store, {
223
+ service
224
+ });
225
+ const removingActive = activeAccount === accountId;
226
+ // Remove the account
227
+ await removeAccount(store, {
228
+ service,
229
+ accountId
230
+ });
231
+ const remainingAccounts = linkedAccounts.filter((id)=>id !== accountId);
232
+ // If we removed the active account, set first remaining as active
233
+ let newActiveAccount;
234
+ if (removingActive && remainingAccounts.length > 0) {
235
+ const firstRemaining = remainingAccounts[0];
236
+ if (firstRemaining) {
237
+ newActiveAccount = firstRemaining;
238
+ await setActiveAccount(store, {
239
+ service,
240
+ accountId: newActiveAccount
241
+ });
242
+ }
243
+ }
244
+ logger.info(`Successfully removed ${service} account`, {
245
+ accountId,
246
+ remainingAccounts: remainingAccounts.length
247
+ });
248
+ const result = {
249
+ type: 'success',
250
+ service,
251
+ removed: accountId,
252
+ remainingAccounts: remainingAccounts.length,
253
+ ...newActiveAccount && {
254
+ newActiveAccount
255
+ },
256
+ message: `Removed ${service} account: ${accountId}${newActiveAccount ? `. Active account is now: ${newActiveAccount}` : ''}`
257
+ };
258
+ return {
259
+ content: [
260
+ {
261
+ type: 'text',
262
+ text: JSON.stringify(result, null, 2)
263
+ }
264
+ ],
265
+ structuredContent: {
266
+ result
267
+ }
268
+ };
269
+ } catch (error) {
270
+ const message = error instanceof Error ? error.message : String(error);
271
+ throw new McpError(ErrorCode.InternalError, `Error removing ${service} account: ${message}`, {
272
+ stack: error instanceof Error ? error.stack : undefined
273
+ });
274
+ }
275
+ }
276
+ },
277
+ // account-list
278
+ {
279
+ name: 'account-list',
280
+ config: {
281
+ description: `List all linked ${service} accounts with their aliases and active status.`,
282
+ inputSchema: {},
283
+ outputSchema: {
284
+ result: z.discriminatedUnion('type', [
285
+ z.object({
286
+ type: z.literal('success'),
287
+ service: z.string(),
288
+ accounts: z.array(z.object({
289
+ email: z.string(),
290
+ alias: z.string().optional(),
291
+ isActive: z.boolean()
292
+ })),
293
+ totalAccounts: z.number(),
294
+ message: z.string()
295
+ })
296
+ ])
297
+ }
298
+ },
299
+ handler: async ()=>{
300
+ try {
301
+ const linkedAccounts = await getLinkedAccounts(store, {
302
+ service
303
+ });
304
+ // Return empty array gracefully (no error when no accounts)
305
+ if (linkedAccounts.length === 0) {
306
+ const result = {
307
+ type: 'success',
308
+ service,
309
+ accounts: [],
310
+ totalAccounts: 0,
311
+ message: `No ${service} accounts linked. Use account-switch to add an account.`
312
+ };
313
+ return {
314
+ content: [
315
+ {
316
+ type: 'text',
317
+ text: JSON.stringify(result, null, 2)
318
+ }
319
+ ],
320
+ structuredContent: {
321
+ result
322
+ }
323
+ };
324
+ }
325
+ const activeAccountId = await getActiveAccount(store, {
326
+ service
327
+ });
328
+ // Get account info for each linked account
329
+ const accounts = await Promise.all(linkedAccounts.map(async (email)=>{
330
+ const accountInfo = await getAccountInfo(store, {
331
+ accountId: email,
332
+ service
333
+ });
334
+ return {
335
+ email,
336
+ alias: accountInfo === null || accountInfo === void 0 ? void 0 : accountInfo.alias,
337
+ isActive: email === activeAccountId
338
+ };
339
+ }));
340
+ const result = {
341
+ type: 'success',
342
+ service,
343
+ accounts,
344
+ totalAccounts: linkedAccounts.length,
345
+ message: `Found ${linkedAccounts.length} ${service} account(s)`
346
+ };
347
+ return {
348
+ content: [
349
+ {
350
+ type: 'text',
351
+ text: JSON.stringify(result, null, 2)
352
+ }
353
+ ],
354
+ structuredContent: {
355
+ result
356
+ }
357
+ };
358
+ } catch (error) {
359
+ const message = error instanceof Error ? error.message : String(error);
360
+ throw new McpError(ErrorCode.InternalError, `Error listing ${service} accounts: ${message}`, {
361
+ stack: error instanceof Error ? error.stack : undefined
362
+ });
363
+ }
364
+ }
365
+ }
366
+ ];
367
+ const prompts = [];
368
+ return {
369
+ tools,
370
+ prompts
371
+ };
372
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/lib/account-server/loopback.ts"],"sourcesContent":["/**\n * Loopback OAuth account management tools.\n *\n * Provides account management for LoopbackOAuthProvider (server-managed multi-account).\n * Users can add multiple accounts, switch between them, and manage identities.\n *\n * Tools:\n * - account-me: Show current user identity (email, alias, session expiry)\n * - account-switch: Use account (add if needed, switch if already linked)\n * - account-remove: Remove account and delete tokens\n * - account-list: Show all linked accounts (returns empty array if none)\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { addAccount, getAccountInfo, getActiveAccount, getLinkedAccounts, removeAccount, setAccountInfo, setActiveAccount } from '../../account-utils.ts';\nimport type { AccountInfo, McpPrompt, McpTool } from '../../types.ts';\nimport { createAccountMe } from './me.ts';\nimport { findAccountByEmailOrAlias } from './shared-utils.ts';\nimport type { AccountLoopbackConfig } from './types.ts';\n\n/**\n * Create loopback OAuth account management tools.\n * Returns 4 tools: account-me, account-switch, account-remove, account-list.\n */\nexport function createLoopback(config: AccountLoopbackConfig): { tools: McpTool[]; prompts: McpPrompt[] } {\n const { service, store, logger, auth } = config;\n\n // Create account-me tool\n const meTools = createAccountMe({ service, store, logger, mode: 'loopback' });\n\n const tools: McpTool[] = [\n ...meTools.tools,\n // account-switch\n {\n name: 'account-switch',\n config: {\n description: `Use ${service} account (smart mode). If email/alias provided and already linked, switches to it without triggering OAuth. If not linked or no email provided, triggers OAuth browser flow to add account. Returns account email, whether it was newly added, and total account count.`,\n inputSchema: {\n email: z.string().optional().describe('Email address to link (if already linked, switches without OAuth)'),\n alias: z.string().optional().describe('Optional alias for easy identification'),\n } as const,\n outputSchema: {\n result: z.discriminatedUnion('type', [\n z.object({\n type: z.literal('success'),\n email: z.string(),\n isNew: z.boolean(),\n totalAccounts: z.number(),\n message: z.string(),\n }),\n ]),\n } as const,\n },\n handler: async (args: unknown): Promise<CallToolResult> => {\n const params = args as { email?: string; alias?: string };\n try {\n logger.info(`Starting account switch for ${service}`, { email: params.email, alias: params.alias });\n\n // Get existing accounts\n const existingAccounts = await getLinkedAccounts(store, { service });\n\n let email: string;\n let isNew: boolean;\n\n // Smart behavior: check if email provided and already linked\n if (params.email) {\n // Find account by email or alias\n const accountId = await findAccountByEmailOrAlias(store, service, params.email);\n\n if (accountId) {\n // Account already linked - just switch to it\n email = accountId;\n isNew = false;\n logger.info(`Account already linked: ${email}, switching without OAuth`);\n\n // Set as active account\n await setActiveAccount(store, { service, accountId: email });\n\n // Update alias if provided\n if (params.alias) {\n const existingInfo = await getAccountInfo(store, { accountId: email, service });\n const accountInfo: AccountInfo = {\n email,\n alias: params.alias,\n addedAt: existingInfo?.addedAt ?? new Date().toISOString(),\n };\n await setAccountInfo(store, { accountId: email, service }, accountInfo);\n }\n\n const result = {\n type: 'success' as const,\n email,\n isNew: false,\n totalAccounts: existingAccounts.length,\n message: `Account already linked: ${email}. Set as active account (no OAuth needed).`,\n };\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n structuredContent: { result },\n };\n }\n }\n\n // Not linked or no email provided - trigger OAuth flow for new account\n // Check if provider supports interactive authentication\n if (!auth.authenticateNewAccount) {\n throw new Error('Account switching requires interactive authentication. ' + 'The current auth provider does not support authenticateNewAccount().');\n }\n\n // Trigger new authentication with account selection\n email = await auth.authenticateNewAccount();\n\n // Check if account already exists (in case OAuth returned different email than requested)\n isNew = !existingAccounts.includes(email);\n\n if (isNew) {\n // Add new account\n await addAccount(store, { service, accountId: email });\n logger.info(`Added new ${service} account`, { email });\n } else {\n logger.info(`Account already linked: ${email}`);\n }\n\n // Set/update account info\n const existingInfo = await getAccountInfo(store, { accountId: email, service });\n const accountInfo: AccountInfo = {\n email,\n ...(params.alias ? { alias: params.alias } : {}),\n addedAt: isNew ? new Date().toISOString() : (existingInfo?.addedAt ?? new Date().toISOString()),\n };\n await setAccountInfo(store, { accountId: email, service }, accountInfo);\n\n // Set as active account\n await setActiveAccount(store, { service, accountId: email });\n\n const totalAccounts = isNew ? existingAccounts.length + 1 : existingAccounts.length;\n\n const result = {\n type: 'success' as const,\n email,\n isNew,\n totalAccounts,\n message: isNew ? `Successfully added ${service} account: ${email} (${totalAccounts} total)` : `Account already linked: ${email}. Set as active account.`,\n };\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n structuredContent: { result },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new McpError(ErrorCode.InternalError, `Error switching ${service} account: ${message}`, {\n stack: error instanceof Error ? error.stack : undefined,\n });\n }\n },\n },\n\n // account-remove\n {\n name: 'account-remove',\n config: {\n description: `Remove ${service} account and delete stored tokens permanently. If removing the active account, the first remaining account becomes active. Requires email or alias parameter.`,\n inputSchema: {\n accountId: z.string().min(1).describe('Email address or alias of account to remove'),\n } as const,\n outputSchema: {\n result: z.discriminatedUnion('type', [\n z.object({\n type: z.literal('success'),\n service: z.string(),\n removed: z.string(),\n remainingAccounts: z.number(),\n newActiveAccount: z.string().optional(),\n message: z.string(),\n }),\n ]),\n } as const,\n },\n handler: async (args: unknown): Promise<CallToolResult> => {\n const params = args as { accountId: string };\n try {\n const linkedAccounts = await getLinkedAccounts(store, { service });\n if (linkedAccounts.length === 0) {\n throw new Error(`No ${service} accounts to remove`);\n }\n\n // Find account by email or alias\n const accountId = await findAccountByEmailOrAlias(store, service, params.accountId);\n\n if (!accountId) {\n throw new Error(`Account not found: ${params.accountId}`);\n }\n\n // Get current active account\n const activeAccount = await getActiveAccount(store, { service });\n const removingActive = activeAccount === accountId;\n\n // Remove the account\n await removeAccount(store, { service, accountId });\n const remainingAccounts = linkedAccounts.filter((id) => id !== accountId);\n\n // If we removed the active account, set first remaining as active\n let newActiveAccount: string | undefined;\n if (removingActive && remainingAccounts.length > 0) {\n const firstRemaining = remainingAccounts[0];\n if (firstRemaining) {\n newActiveAccount = firstRemaining;\n await setActiveAccount(store, { service, accountId: newActiveAccount });\n }\n }\n\n logger.info(`Successfully removed ${service} account`, { accountId, remainingAccounts: remainingAccounts.length });\n\n const result = {\n type: 'success' as const,\n service,\n removed: accountId,\n remainingAccounts: remainingAccounts.length,\n ...(newActiveAccount && { newActiveAccount }),\n message: `Removed ${service} account: ${accountId}${newActiveAccount ? `. Active account is now: ${newActiveAccount}` : ''}`,\n };\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n structuredContent: { result },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new McpError(ErrorCode.InternalError, `Error removing ${service} account: ${message}`, {\n stack: error instanceof Error ? error.stack : undefined,\n });\n }\n },\n },\n\n // account-list\n {\n name: 'account-list',\n config: {\n description: `List all linked ${service} accounts with their aliases and active status.`,\n inputSchema: {} as const,\n outputSchema: {\n result: z.discriminatedUnion('type', [\n z.object({\n type: z.literal('success'),\n service: z.string(),\n accounts: z.array(\n z.object({\n email: z.string(),\n alias: z.string().optional(),\n isActive: z.boolean(),\n })\n ),\n totalAccounts: z.number(),\n message: z.string(),\n }),\n ]),\n } as const,\n },\n handler: async (): Promise<CallToolResult> => {\n try {\n const linkedAccounts = await getLinkedAccounts(store, { service });\n\n // Return empty array gracefully (no error when no accounts)\n if (linkedAccounts.length === 0) {\n const result = {\n type: 'success' as const,\n service,\n accounts: [],\n totalAccounts: 0,\n message: `No ${service} accounts linked. Use account-switch to add an account.`,\n };\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n structuredContent: { result },\n };\n }\n\n const activeAccountId = await getActiveAccount(store, { service });\n\n // Get account info for each linked account\n const accounts = await Promise.all(\n linkedAccounts.map(async (email) => {\n const accountInfo = await getAccountInfo(store, { accountId: email, service });\n return {\n email,\n alias: accountInfo?.alias,\n isActive: email === activeAccountId,\n };\n })\n );\n\n const result = {\n type: 'success' as const,\n service,\n accounts,\n totalAccounts: linkedAccounts.length,\n message: `Found ${linkedAccounts.length} ${service} account(s)`,\n };\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n structuredContent: { result },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new McpError(ErrorCode.InternalError, `Error listing ${service} accounts: ${message}`, {\n stack: error instanceof Error ? error.stack : undefined,\n });\n }\n },\n },\n ];\n\n const prompts: McpPrompt[] = [];\n\n return { tools, prompts };\n}\n"],"names":["ErrorCode","McpError","z","addAccount","getAccountInfo","getActiveAccount","getLinkedAccounts","removeAccount","setAccountInfo","setActiveAccount","createAccountMe","findAccountByEmailOrAlias","createLoopback","config","service","store","logger","auth","meTools","mode","tools","name","description","inputSchema","email","string","optional","describe","alias","outputSchema","result","discriminatedUnion","object","type","literal","isNew","boolean","totalAccounts","number","message","handler","args","params","info","existingAccounts","accountId","existingInfo","accountInfo","addedAt","Date","toISOString","length","content","text","JSON","stringify","structuredContent","authenticateNewAccount","Error","includes","error","String","InternalError","stack","undefined","min","removed","remainingAccounts","newActiveAccount","linkedAccounts","activeAccount","removingActive","filter","id","firstRemaining","accounts","array","isActive","activeAccountId","Promise","all","map","prompts"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Account "me" tool - Who am I currently authenticated as?
3
+ *
4
+ * Provides current user identity across all auth modes:
5
+ * - Loopback: email, alias, sessionExpiresIn from stored tokens
6
+ * - DCR/Stateless: email from bearer token context, sessionExpiresIn=null
7
+ * - Device Code: email, sessionExpiresIn from stored tokens
8
+ * - Service Account: email, sessionExpiresIn="never" (JWT-based)
9
+ *
10
+ * Tool: {service}-account-me
11
+ */
12
+ import type { McpPrompt, McpTool } from '../../types.js';
13
+ import type { AccountMeConfig } from './types.js';
14
+ /**
15
+ * Create account-me tool for current user identity.
16
+ *
17
+ * Returns email, optional alias (loopback only), and session expiry info.
18
+ * Throws error if no active account in loopback mode.
19
+ */
20
+ export declare function createAccountMe(config: AccountMeConfig): {
21
+ tools: McpTool[];
22
+ prompts: McpPrompt[];
23
+ };
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Account "me" tool - Who am I currently authenticated as?
3
+ *
4
+ * Provides current user identity across all auth modes:
5
+ * - Loopback: email, alias, sessionExpiresIn from stored tokens
6
+ * - DCR/Stateless: email from bearer token context, sessionExpiresIn=null
7
+ * - Device Code: email, sessionExpiresIn from stored tokens
8
+ * - Service Account: email, sessionExpiresIn="never" (JWT-based)
9
+ *
10
+ * Tool: {service}-account-me
11
+ */ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
12
+ import { z } from 'zod';
13
+ import { getAccountInfo, getActiveAccount, getToken } from '../../account-utils.js';
14
+ /**
15
+ * Format milliseconds as human-readable duration
16
+ * Examples: "2h 15m", "45m", "30s"
17
+ */ function formatDuration(ms) {
18
+ const totalSeconds = Math.floor(ms / 1000);
19
+ if (totalSeconds < 60) {
20
+ return `${totalSeconds}s`;
21
+ }
22
+ const minutes = Math.floor(totalSeconds / 60);
23
+ if (minutes < 60) {
24
+ return `${minutes}m`;
25
+ }
26
+ const hours = Math.floor(minutes / 60);
27
+ const remainingMinutes = minutes % 60;
28
+ if (remainingMinutes === 0) {
29
+ return `${hours}h`;
30
+ }
31
+ return `${hours}h ${remainingMinutes}m`;
32
+ }
33
+ /**
34
+ * Create account-me tool for current user identity.
35
+ *
36
+ * Returns email, optional alias (loopback only), and session expiry info.
37
+ * Throws error if no active account in loopback mode.
38
+ */ export function createAccountMe(config) {
39
+ const { service, store, logger, mode } = config;
40
+ const tools = [
41
+ {
42
+ name: 'account-me',
43
+ config: {
44
+ description: `Show current ${service} user identity. Returns email, alias (if set), and session expiry information.`,
45
+ inputSchema: {},
46
+ outputSchema: {
47
+ result: z.discriminatedUnion('type', [
48
+ z.object({
49
+ type: z.literal('success'),
50
+ service: z.string(),
51
+ email: z.string(),
52
+ alias: z.string().optional(),
53
+ sessionExpiresIn: z.string().nullable().optional(),
54
+ message: z.string()
55
+ })
56
+ ])
57
+ }
58
+ },
59
+ handler: async (_args, extra)=>{
60
+ try {
61
+ var _ref;
62
+ // Mode-specific implementation
63
+ if (mode === 'stateless') {
64
+ // DCR/Stateless: Extract email from auth context
65
+ const authContext = extra === null || extra === void 0 ? void 0 : extra.authContext;
66
+ if (!(authContext === null || authContext === void 0 ? void 0 : authContext.accountId)) {
67
+ throw new Error('No authentication context available. DCR mode requires bearer token.');
68
+ }
69
+ const result = {
70
+ type: 'success',
71
+ service,
72
+ email: authContext.accountId,
73
+ sessionExpiresIn: null,
74
+ message: `Authenticated as ${authContext.accountId}. Session managed by MCP client.`
75
+ };
76
+ return {
77
+ content: [
78
+ {
79
+ type: 'text',
80
+ text: JSON.stringify(result, null, 2)
81
+ }
82
+ ],
83
+ structuredContent: {
84
+ result
85
+ }
86
+ };
87
+ }
88
+ // Loopback/Device Code/Service Account: Use store
89
+ if (!store) {
90
+ throw new Error('Store is required for non-stateless mode');
91
+ }
92
+ // Get active account
93
+ const activeAccountId = await getActiveAccount(store, {
94
+ service
95
+ });
96
+ if (!activeAccountId) {
97
+ throw new Error(`No active ${service} account found. Use account-switch to add an account.`);
98
+ }
99
+ // Get account info (email, alias)
100
+ const accountInfo = await getAccountInfo(store, {
101
+ accountId: activeAccountId,
102
+ service
103
+ });
104
+ const email = (_ref = accountInfo === null || accountInfo === void 0 ? void 0 : accountInfo.email) !== null && _ref !== void 0 ? _ref : activeAccountId;
105
+ const alias = accountInfo === null || accountInfo === void 0 ? void 0 : accountInfo.alias;
106
+ // Calculate session expiry
107
+ let sessionExpiresIn = null;
108
+ try {
109
+ const token = await getToken(store, {
110
+ accountId: activeAccountId,
111
+ service
112
+ });
113
+ if (token === null || token === void 0 ? void 0 : token.expiresAt) {
114
+ const now = Date.now();
115
+ if (token.expiresAt > now) {
116
+ sessionExpiresIn = formatDuration(token.expiresAt - now);
117
+ } else {
118
+ sessionExpiresIn = 'expired';
119
+ }
120
+ } else {
121
+ // No expiry = JWT-based service account or no token info
122
+ sessionExpiresIn = 'never';
123
+ }
124
+ } catch {
125
+ // Token not found or error reading - treat as "never" (service account pattern)
126
+ sessionExpiresIn = 'never';
127
+ }
128
+ const result = {
129
+ type: 'success',
130
+ service,
131
+ email,
132
+ ...alias && {
133
+ alias
134
+ },
135
+ ...sessionExpiresIn && {
136
+ sessionExpiresIn
137
+ },
138
+ message: `Authenticated as ${email}${alias ? ` (${alias})` : ''}${sessionExpiresIn ? `. Session expires in ${sessionExpiresIn}` : ''}.`
139
+ };
140
+ return {
141
+ content: [
142
+ {
143
+ type: 'text',
144
+ text: JSON.stringify(result, null, 2)
145
+ }
146
+ ],
147
+ structuredContent: {
148
+ result
149
+ }
150
+ };
151
+ } catch (error) {
152
+ var _logger_error;
153
+ const message = error instanceof Error ? error.message : String(error);
154
+ logger === null || logger === void 0 ? void 0 : (_logger_error = logger.error) === null || _logger_error === void 0 ? void 0 : _logger_error.call(logger, 'account-me.error', {
155
+ service,
156
+ error: message
157
+ });
158
+ throw new McpError(ErrorCode.InternalError, `Error getting ${service} account info: ${message}`, {
159
+ stack: error instanceof Error ? error.stack : undefined
160
+ });
161
+ }
162
+ }
163
+ }
164
+ ];
165
+ const prompts = [];
166
+ return {
167
+ tools,
168
+ prompts
169
+ };
170
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth/src/lib/account-server/me.ts"],"sourcesContent":["/**\n * Account \"me\" tool - Who am I currently authenticated as?\n *\n * Provides current user identity across all auth modes:\n * - Loopback: email, alias, sessionExpiresIn from stored tokens\n * - DCR/Stateless: email from bearer token context, sessionExpiresIn=null\n * - Device Code: email, sessionExpiresIn from stored tokens\n * - Service Account: email, sessionExpiresIn=\"never\" (JWT-based)\n *\n * Tool: {service}-account-me\n */\n\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\nimport { getAccountInfo, getActiveAccount, getToken } from '../../account-utils.ts';\nimport type { CachedToken, McpPrompt, McpTool } from '../../types.ts';\nimport type { AccountMeConfig } from './types.ts';\n\n/**\n * Format milliseconds as human-readable duration\n * Examples: \"2h 15m\", \"45m\", \"30s\"\n */\nfunction formatDuration(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n\n if (totalSeconds < 60) {\n return `${totalSeconds}s`;\n }\n\n const minutes = Math.floor(totalSeconds / 60);\n if (minutes < 60) {\n return `${minutes}m`;\n }\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n if (remainingMinutes === 0) {\n return `${hours}h`;\n }\n\n return `${hours}h ${remainingMinutes}m`;\n}\n\n/**\n * Create account-me tool for current user identity.\n *\n * Returns email, optional alias (loopback only), and session expiry info.\n * Throws error if no active account in loopback mode.\n */\nexport function createAccountMe(config: AccountMeConfig): { tools: McpTool[]; prompts: McpPrompt[] } {\n const { service, store, logger, mode } = config;\n\n const tools: McpTool[] = [\n {\n name: 'account-me',\n config: {\n description: `Show current ${service} user identity. Returns email, alias (if set), and session expiry information.`,\n inputSchema: {} as const,\n outputSchema: {\n result: z.discriminatedUnion('type', [\n z.object({\n type: z.literal('success'),\n service: z.string(),\n email: z.string(),\n alias: z.string().optional(),\n sessionExpiresIn: z.string().nullable().optional(),\n message: z.string(),\n }),\n ]),\n } as const,\n },\n handler: async (_args: unknown, extra?: unknown): Promise<CallToolResult> => {\n try {\n // Mode-specific implementation\n if (mode === 'stateless') {\n // DCR/Stateless: Extract email from auth context\n const authContext = (extra as { authContext?: { accountId?: string } })?.authContext;\n\n if (!authContext?.accountId) {\n throw new Error('No authentication context available. DCR mode requires bearer token.');\n }\n\n const result = {\n type: 'success' as const,\n service,\n email: authContext.accountId,\n sessionExpiresIn: null, // Client-managed\n message: `Authenticated as ${authContext.accountId}. Session managed by MCP client.`,\n };\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n structuredContent: { result },\n };\n }\n\n // Loopback/Device Code/Service Account: Use store\n if (!store) {\n throw new Error('Store is required for non-stateless mode');\n }\n\n // Get active account\n const activeAccountId = await getActiveAccount(store, { service });\n if (!activeAccountId) {\n throw new Error(`No active ${service} account found. Use account-switch to add an account.`);\n }\n\n // Get account info (email, alias)\n const accountInfo = await getAccountInfo(store, { accountId: activeAccountId, service });\n const email = accountInfo?.email ?? activeAccountId;\n const alias = accountInfo?.alias;\n\n // Calculate session expiry\n let sessionExpiresIn: string | null = null;\n try {\n const token = await getToken<CachedToken>(store, { accountId: activeAccountId, service });\n if (token?.expiresAt) {\n const now = Date.now();\n if (token.expiresAt > now) {\n sessionExpiresIn = formatDuration(token.expiresAt - now);\n } else {\n sessionExpiresIn = 'expired';\n }\n } else {\n // No expiry = JWT-based service account or no token info\n sessionExpiresIn = 'never';\n }\n } catch {\n // Token not found or error reading - treat as \"never\" (service account pattern)\n sessionExpiresIn = 'never';\n }\n\n const result = {\n type: 'success' as const,\n service,\n email,\n ...(alias && { alias }),\n ...(sessionExpiresIn && { sessionExpiresIn }),\n message: `Authenticated as ${email}${alias ? ` (${alias})` : ''}${sessionExpiresIn ? `. Session expires in ${sessionExpiresIn}` : ''}.`,\n };\n\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n structuredContent: { result },\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logger?.error?.('account-me.error', { service, error: message });\n\n throw new McpError(ErrorCode.InternalError, `Error getting ${service} account info: ${message}`, {\n stack: error instanceof Error ? error.stack : undefined,\n });\n }\n },\n },\n ];\n\n const prompts: McpPrompt[] = [];\n\n return { tools, prompts };\n}\n"],"names":["ErrorCode","McpError","z","getAccountInfo","getActiveAccount","getToken","formatDuration","ms","totalSeconds","Math","floor","minutes","hours","remainingMinutes","createAccountMe","config","service","store","logger","mode","tools","name","description","inputSchema","outputSchema","result","discriminatedUnion","object","type","literal","string","email","alias","optional","sessionExpiresIn","nullable","message","handler","_args","extra","authContext","accountId","Error","content","text","JSON","stringify","structuredContent","activeAccountId","accountInfo","token","expiresAt","now","Date","error","String","InternalError","stack","undefined","prompts"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Keyv } from 'keyv';
2
+ /**
3
+ * Find account ID by email or alias lookup.
4
+ * Returns accountId if found, otherwise null.
5
+ */
6
+ export declare function findAccountByEmailOrAlias(store: Keyv, service: string, emailOrAlias: string): Promise<string | null>;