@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.
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/cjs/account-utils.d.cts +107 -0
- package/dist/cjs/account-utils.d.ts +107 -0
- package/dist/cjs/account-utils.js +481 -0
- package/dist/cjs/account-utils.js.map +1 -0
- package/dist/cjs/index.d.cts +19 -0
- package/dist/cjs/index.d.ts +19 -0
- package/dist/cjs/index.js +149 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/jwt-auth.d.cts +53 -0
- package/dist/cjs/jwt-auth.d.ts +53 -0
- package/dist/cjs/jwt-auth.js +417 -0
- package/dist/cjs/jwt-auth.js.map +1 -0
- package/dist/cjs/key-utils.d.cts +131 -0
- package/dist/cjs/key-utils.d.ts +131 -0
- package/dist/cjs/key-utils.js +421 -0
- package/dist/cjs/key-utils.js.map +1 -0
- package/dist/cjs/lib/account-server/index.d.cts +45 -0
- package/dist/cjs/lib/account-server/index.d.ts +45 -0
- package/dist/cjs/lib/account-server/index.js +67 -0
- package/dist/cjs/lib/account-server/index.js.map +1 -0
- package/dist/cjs/lib/account-server/loopback.d.cts +22 -0
- package/dist/cjs/lib/account-server/loopback.d.ts +22 -0
- package/dist/cjs/lib/account-server/loopback.js +778 -0
- package/dist/cjs/lib/account-server/loopback.js.map +1 -0
- package/dist/cjs/lib/account-server/me.d.cts +23 -0
- package/dist/cjs/lib/account-server/me.d.ts +23 -0
- package/dist/cjs/lib/account-server/me.js +412 -0
- package/dist/cjs/lib/account-server/me.js.map +1 -0
- package/dist/cjs/lib/account-server/shared-utils.d.cts +6 -0
- package/dist/cjs/lib/account-server/shared-utils.d.ts +6 -0
- package/dist/cjs/lib/account-server/shared-utils.js +235 -0
- package/dist/cjs/lib/account-server/shared-utils.js.map +1 -0
- package/dist/cjs/lib/account-server/stateless.d.cts +20 -0
- package/dist/cjs/lib/account-server/stateless.d.ts +20 -0
- package/dist/cjs/lib/account-server/stateless.js +32 -0
- package/dist/cjs/lib/account-server/stateless.js.map +1 -0
- package/dist/cjs/lib/account-server/types.d.cts +32 -0
- package/dist/cjs/lib/account-server/types.d.ts +32 -0
- package/dist/cjs/lib/account-server/types.js +7 -0
- package/dist/cjs/lib/account-server/types.js.map +1 -0
- package/dist/cjs/lib/dcr-types.d.cts +126 -0
- package/dist/cjs/lib/dcr-types.d.ts +126 -0
- package/dist/cjs/lib/dcr-types.js +12 -0
- package/dist/cjs/lib/dcr-types.js.map +1 -0
- package/dist/cjs/lib/rfc-metadata-types.d.cts +46 -0
- package/dist/cjs/lib/rfc-metadata-types.d.ts +46 -0
- package/dist/cjs/lib/rfc-metadata-types.js +8 -0
- package/dist/cjs/lib/rfc-metadata-types.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pkce.d.cts +36 -0
- package/dist/cjs/pkce.d.ts +36 -0
- package/dist/cjs/pkce.js +25 -0
- package/dist/cjs/pkce.js.map +1 -0
- package/dist/cjs/sanitizer.d.cts +37 -0
- package/dist/cjs/sanitizer.d.ts +37 -0
- package/dist/cjs/sanitizer.js +407 -0
- package/dist/cjs/sanitizer.js.map +1 -0
- package/dist/cjs/schemas/index.d.cts +36 -0
- package/dist/cjs/schemas/index.d.ts +36 -0
- package/dist/cjs/schemas/index.js +28 -0
- package/dist/cjs/schemas/index.js.map +1 -0
- package/dist/cjs/session-auth.d.cts +79 -0
- package/dist/cjs/session-auth.d.ts +79 -0
- package/dist/cjs/session-auth.js +354 -0
- package/dist/cjs/session-auth.js.map +1 -0
- package/dist/cjs/templates.d.cts +18 -0
- package/dist/cjs/templates.d.ts +18 -0
- package/dist/cjs/templates.js +38 -0
- package/dist/cjs/templates.js.map +1 -0
- package/dist/cjs/types.d.cts +343 -0
- package/dist/cjs/types.d.ts +343 -0
- package/dist/cjs/types.js +210 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/account-utils.d.ts +107 -0
- package/dist/esm/account-utils.js +179 -0
- package/dist/esm/account-utils.js.map +1 -0
- package/dist/esm/index.d.ts +19 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/jwt-auth.d.ts +53 -0
- package/dist/esm/jwt-auth.js +164 -0
- package/dist/esm/jwt-auth.js.map +1 -0
- package/dist/esm/key-utils.d.ts +131 -0
- package/dist/esm/key-utils.js +143 -0
- package/dist/esm/key-utils.js.map +1 -0
- package/dist/esm/lib/account-server/index.d.ts +45 -0
- package/dist/esm/lib/account-server/index.js +41 -0
- package/dist/esm/lib/account-server/index.js.map +1 -0
- package/dist/esm/lib/account-server/loopback.d.ts +22 -0
- package/dist/esm/lib/account-server/loopback.js +372 -0
- package/dist/esm/lib/account-server/loopback.js.map +1 -0
- package/dist/esm/lib/account-server/me.d.ts +23 -0
- package/dist/esm/lib/account-server/me.js +170 -0
- package/dist/esm/lib/account-server/me.js.map +1 -0
- package/dist/esm/lib/account-server/shared-utils.d.ts +6 -0
- package/dist/esm/lib/account-server/shared-utils.js +24 -0
- package/dist/esm/lib/account-server/shared-utils.js.map +1 -0
- package/dist/esm/lib/account-server/stateless.d.ts +20 -0
- package/dist/esm/lib/account-server/stateless.js +25 -0
- package/dist/esm/lib/account-server/stateless.js.map +1 -0
- package/dist/esm/lib/account-server/types.d.ts +32 -0
- package/dist/esm/lib/account-server/types.js +6 -0
- package/dist/esm/lib/account-server/types.js.map +1 -0
- package/dist/esm/lib/dcr-types.d.ts +126 -0
- package/dist/esm/lib/dcr-types.js +13 -0
- package/dist/esm/lib/dcr-types.js.map +1 -0
- package/dist/esm/lib/rfc-metadata-types.d.ts +46 -0
- package/dist/esm/lib/rfc-metadata-types.js +7 -0
- package/dist/esm/lib/rfc-metadata-types.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/pkce.d.ts +36 -0
- package/dist/esm/pkce.js +33 -0
- package/dist/esm/pkce.js.map +1 -0
- package/dist/esm/sanitizer.d.ts +37 -0
- package/dist/esm/sanitizer.js +256 -0
- package/dist/esm/sanitizer.js.map +1 -0
- package/dist/esm/schemas/index.d.ts +36 -0
- package/dist/esm/schemas/index.js +19 -0
- package/dist/esm/schemas/index.js.map +1 -0
- package/dist/esm/session-auth.d.ts +79 -0
- package/dist/esm/session-auth.js +141 -0
- package/dist/esm/session-auth.js.map +1 -0
- package/dist/esm/templates.d.ts +18 -0
- package/dist/esm/templates.js +132 -0
- package/dist/esm/templates.js.map +1 -0
- package/dist/esm/types.d.ts +343 -0
- package/dist/esm/types.js +34 -0
- package/dist/esm/types.js.map +1 -0
- 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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"}
|