@rubytech/create-maxy 1.0.763 → 1.0.765
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/package-lock.json +56 -1
- package/payload/platform/package.json +1 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +69 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +48 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +94 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js +31 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/log.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +213 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +130 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js +261 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/pkce-flow.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +61 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +170 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.d.ts +18 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +152 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +60 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +189 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts +23 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.js +53 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/log.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts +26 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js +50 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/account-register.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts +12 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js +32 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-event.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts +59 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js +54 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/calendar-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts +14 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js +45 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/contacts-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts +15 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js +48 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts +8 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js +49 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-search.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts +19 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js +58 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mailbox-info.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/package.json +20 -0
- package/payload/platform/plugins/outlook/mcp/scripts/verify-doc-impl.sh +109 -0
- package/payload/platform/plugins/outlook/references/auth.md +118 -0
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +114 -0
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +65 -0
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/chunk-EIQT6QDH.js +9562 -0
- package/payload/server/chunk-IO2WQEY4.js +9562 -0
- package/payload/server/chunk-TKYZ7AEB.js +3142 -0
- package/payload/server/client-pool-CX2MFW75.js +28 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{admin-V6NDkEoR.js → admin-g-Fjko8R.js} +5 -5
- package/payload/server/public/assets/{graph-BHcUKzXh.js → graph-DHBGJFDX.js} +1 -1
- package/payload/server/public/assets/page-BscgOyqp.js +50 -0
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +146 -39
- package/payload/server/public/assets/page-DxH_Opxt.js +0 -50
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-account encrypted token store for Outlook OAuth credentials.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from nsakki55/outlook-mcp@e49d8e68ac8d69db653c25803127dc3b98626cda
|
|
5
|
+
* (MIT). Adaptations:
|
|
6
|
+
* - Per-account paths under {ACCOUNTS_DIR}/{accountId}/secrets/outlook/
|
|
7
|
+
* - keytar fallback removed (Pi runs headless, no Secret Service)
|
|
8
|
+
* - node-persist removed; single JSON blob written atomically (temp + rename)
|
|
9
|
+
*
|
|
10
|
+
* tokens.enc AES-256-CBC ciphertext "<iv-hex>:<cipher-hex>" of the JSON blob
|
|
11
|
+
* .key 32 random bytes, base64, mode 0600 (the AES key)
|
|
12
|
+
*
|
|
13
|
+
* The JSON blob shape is {accessToken, refreshToken, accessTokenExpiry,
|
|
14
|
+
* refreshTokenExpiry, lastRefresh, graphUserId, scopes}.
|
|
15
|
+
*
|
|
16
|
+
* Rationale: per CLAUDE.md doctrine, account scope is absolute. Each account
|
|
17
|
+
* holds its own AES key — compromising account A's keyfile cannot decrypt
|
|
18
|
+
* account B's tokens.
|
|
19
|
+
*/
|
|
20
|
+
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
21
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
const REFRESH_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
|
24
|
+
const REFRESH_TOKEN_LIFETIME_MS = 90 * 24 * 60 * 60 * 1000; // 90 days
|
|
25
|
+
export class TokenStore {
|
|
26
|
+
accountId;
|
|
27
|
+
cachedBlob = null;
|
|
28
|
+
encryptionKey = null;
|
|
29
|
+
secretsDir;
|
|
30
|
+
tokensPath;
|
|
31
|
+
keyPath;
|
|
32
|
+
constructor(accountId, accountsDir) {
|
|
33
|
+
this.accountId = accountId;
|
|
34
|
+
this.secretsDir = join(accountsDir, accountId, "secrets", "outlook");
|
|
35
|
+
this.tokensPath = join(this.secretsDir, "tokens.enc");
|
|
36
|
+
this.keyPath = join(this.secretsDir, ".key");
|
|
37
|
+
}
|
|
38
|
+
/** Load (or create) the AES key. Idempotent and TOCTOU-safe via wx flag. */
|
|
39
|
+
loadKey() {
|
|
40
|
+
if (this.encryptionKey)
|
|
41
|
+
return this.encryptionKey;
|
|
42
|
+
mkdirSync(this.secretsDir, { recursive: true, mode: 0o700 });
|
|
43
|
+
chmodSync(this.secretsDir, 0o700);
|
|
44
|
+
if (existsSync(this.keyPath)) {
|
|
45
|
+
const stat = statSync(this.keyPath);
|
|
46
|
+
if ((stat.mode & 0o777) !== 0o600)
|
|
47
|
+
chmodSync(this.keyPath, 0o600);
|
|
48
|
+
const raw = readFileSync(this.keyPath, "utf-8").trim();
|
|
49
|
+
const key = Buffer.from(raw, "base64");
|
|
50
|
+
if (key.length !== 32) {
|
|
51
|
+
throw new Error(`Outlook key for account=${this.accountId} is corrupt (length=${key.length}); delete ${this.keyPath} and re-run outlook-account-register.`);
|
|
52
|
+
}
|
|
53
|
+
this.encryptionKey = key;
|
|
54
|
+
return key;
|
|
55
|
+
}
|
|
56
|
+
const newKey = randomBytes(32);
|
|
57
|
+
try {
|
|
58
|
+
writeFileSync(this.keyPath, newKey.toString("base64"), {
|
|
59
|
+
flag: "wx",
|
|
60
|
+
mode: 0o600,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// Concurrent flow created the key first — read theirs.
|
|
65
|
+
if (err.code === "EEXIST") {
|
|
66
|
+
const raw = readFileSync(this.keyPath, "utf-8").trim();
|
|
67
|
+
this.encryptionKey = Buffer.from(raw, "base64");
|
|
68
|
+
return this.encryptionKey;
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
this.encryptionKey = newKey;
|
|
73
|
+
return newKey;
|
|
74
|
+
}
|
|
75
|
+
encrypt(plaintext) {
|
|
76
|
+
const key = this.loadKey();
|
|
77
|
+
const iv = randomBytes(16);
|
|
78
|
+
const cipher = createCipheriv("aes-256-cbc", key, iv);
|
|
79
|
+
let encrypted = cipher.update(plaintext, "utf8", "hex");
|
|
80
|
+
encrypted += cipher.final("hex");
|
|
81
|
+
return `${iv.toString("hex")}:${encrypted}`;
|
|
82
|
+
}
|
|
83
|
+
decrypt(payload) {
|
|
84
|
+
const key = this.loadKey();
|
|
85
|
+
const [ivHex, ...rest] = payload.split(":");
|
|
86
|
+
if (!ivHex || rest.length === 0) {
|
|
87
|
+
throw new Error("token-decrypt-failed: malformed cipher payload");
|
|
88
|
+
}
|
|
89
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
90
|
+
const decipher = createDecipheriv("aes-256-cbc", key, iv);
|
|
91
|
+
let decrypted = decipher.update(rest.join(":"), "hex", "utf8");
|
|
92
|
+
decrypted += decipher.final("utf8");
|
|
93
|
+
return decrypted;
|
|
94
|
+
}
|
|
95
|
+
/** Atomic write — temp + rename. Either old or new tokens, never partial. */
|
|
96
|
+
writeBlob(blob) {
|
|
97
|
+
const ciphertext = this.encrypt(JSON.stringify(blob));
|
|
98
|
+
const temp = `${this.tokensPath}.tmp`;
|
|
99
|
+
writeFileSync(temp, ciphertext, { mode: 0o600 });
|
|
100
|
+
chmodSync(temp, 0o600);
|
|
101
|
+
renameSync(temp, this.tokensPath);
|
|
102
|
+
this.cachedBlob = blob;
|
|
103
|
+
}
|
|
104
|
+
/** Read and decrypt the blob from disk; cached after first read. */
|
|
105
|
+
read() {
|
|
106
|
+
if (this.cachedBlob)
|
|
107
|
+
return this.cachedBlob;
|
|
108
|
+
if (!existsSync(this.tokensPath))
|
|
109
|
+
return null;
|
|
110
|
+
const raw = readFileSync(this.tokensPath, "utf-8").trim();
|
|
111
|
+
if (!raw)
|
|
112
|
+
return null;
|
|
113
|
+
const json = this.decrypt(raw);
|
|
114
|
+
const blob = JSON.parse(json);
|
|
115
|
+
this.cachedBlob = blob;
|
|
116
|
+
return blob;
|
|
117
|
+
}
|
|
118
|
+
store(accessToken, refreshToken, expiresInSeconds, extras = {}) {
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
const previous = this.cachedBlob ?? this.tryReadSilent();
|
|
121
|
+
const blob = {
|
|
122
|
+
accessToken,
|
|
123
|
+
refreshToken: refreshToken ?? previous?.refreshToken ?? null,
|
|
124
|
+
accessTokenExpiry: now + expiresInSeconds * 1000,
|
|
125
|
+
refreshTokenExpiry: refreshToken
|
|
126
|
+
? now + REFRESH_TOKEN_LIFETIME_MS
|
|
127
|
+
: (previous?.refreshTokenExpiry ?? now + REFRESH_TOKEN_LIFETIME_MS),
|
|
128
|
+
lastRefresh: now,
|
|
129
|
+
graphUserId: extras.graphUserId ?? previous?.graphUserId ?? null,
|
|
130
|
+
scopes: extras.scopes ?? previous?.scopes ?? [],
|
|
131
|
+
};
|
|
132
|
+
this.writeBlob(blob);
|
|
133
|
+
return blob;
|
|
134
|
+
}
|
|
135
|
+
tryReadSilent() {
|
|
136
|
+
try {
|
|
137
|
+
return this.read();
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether the access token is within the refresh threshold of expiry. Caller
|
|
145
|
+
* should refresh before next Graph call when this is true.
|
|
146
|
+
*/
|
|
147
|
+
needsRefresh() {
|
|
148
|
+
const blob = this.read();
|
|
149
|
+
if (!blob)
|
|
150
|
+
return false;
|
|
151
|
+
return Date.now() > blob.accessTokenExpiry - REFRESH_THRESHOLD_MS;
|
|
152
|
+
}
|
|
153
|
+
hasTokens() {
|
|
154
|
+
return this.read() !== null;
|
|
155
|
+
}
|
|
156
|
+
refreshTokenExpired() {
|
|
157
|
+
const blob = this.read();
|
|
158
|
+
if (!blob)
|
|
159
|
+
return false;
|
|
160
|
+
return Date.now() > blob.refreshTokenExpiry;
|
|
161
|
+
}
|
|
162
|
+
clear() {
|
|
163
|
+
this.cachedBlob = null;
|
|
164
|
+
if (existsSync(this.tokensPath))
|
|
165
|
+
unlinkSync(this.tokensPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Exported for tests + the mailbox-info tool. */
|
|
169
|
+
export const refreshThresholdMs = REFRESH_THRESHOLD_MS;
|
|
170
|
+
//# sourceMappingURL=token-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @maxy/outlook MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Read-only Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes.
|
|
6
|
+
* Admin agent only. Public-agent surface is explicitly excluded.
|
|
7
|
+
*
|
|
8
|
+
* Auth: per-account OAuth Auth Code + PKCE. Tokens persist encrypted to
|
|
9
|
+
* {ACCOUNTS_DIR}/{accountId}/secrets/outlook/{tokens.enc, .key}
|
|
10
|
+
*
|
|
11
|
+
* Required environment:
|
|
12
|
+
* ACCOUNT_ID — provided by getMcpServers when spawned per account.
|
|
13
|
+
* PLATFORM_ROOT — repo root; used to derive ACCOUNTS_DIR.
|
|
14
|
+
* OUTLOOK_CLIENT_ID — Azure AD app (client) ID. Operator-set per install.
|
|
15
|
+
* OUTLOOK_TENANT_ID — Azure tenant ("common" for multi-tenant). Default "common".
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @maxy/outlook MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Read-only Microsoft Graph access for Outlook.com / Microsoft 365 mailboxes.
|
|
6
|
+
* Admin agent only. Public-agent surface is explicitly excluded.
|
|
7
|
+
*
|
|
8
|
+
* Auth: per-account OAuth Auth Code + PKCE. Tokens persist encrypted to
|
|
9
|
+
* {ACCOUNTS_DIR}/{accountId}/secrets/outlook/{tokens.enc, .key}
|
|
10
|
+
*
|
|
11
|
+
* Required environment:
|
|
12
|
+
* ACCOUNT_ID — provided by getMcpServers when spawned per account.
|
|
13
|
+
* PLATFORM_ROOT — repo root; used to derive ACCOUNTS_DIR.
|
|
14
|
+
* OUTLOOK_CLIENT_ID — Azure AD app (client) ID. Operator-set per install.
|
|
15
|
+
* OUTLOOK_TENANT_ID — Azure tenant ("common" for multi-tenant). Default "common".
|
|
16
|
+
*/
|
|
17
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
18
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
|
+
import { resolve } from "node:path";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { TokenStore } from "./auth/token-store.js";
|
|
22
|
+
import { runAccountRegister } from "./tools/account-register.js";
|
|
23
|
+
import { runMailList } from "./tools/mail-list.js";
|
|
24
|
+
import { runMailSearch } from "./tools/mail-search.js";
|
|
25
|
+
import { runCalendarList } from "./tools/calendar-list.js";
|
|
26
|
+
import { runCalendarEvent } from "./tools/calendar-event.js";
|
|
27
|
+
import { runContactsList } from "./tools/contacts-list.js";
|
|
28
|
+
import { runMailboxInfo } from "./tools/mailbox-info.js";
|
|
29
|
+
const ACCOUNT_ID = process.env.ACCOUNT_ID;
|
|
30
|
+
const PLATFORM_ROOT = process.env.PLATFORM_ROOT ?? process.cwd();
|
|
31
|
+
const ACCOUNTS_DIR = resolve(PLATFORM_ROOT, "..", "data/accounts");
|
|
32
|
+
const OUTLOOK_CLIENT_ID = process.env.OUTLOOK_CLIENT_ID ?? "";
|
|
33
|
+
const OUTLOOK_TENANT_ID = process.env.OUTLOOK_TENANT_ID ?? "common";
|
|
34
|
+
if (!ACCOUNT_ID) {
|
|
35
|
+
process.stderr.write("[outlook-mcp] FATAL: ACCOUNT_ID env var is required\n");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
function getStore() {
|
|
39
|
+
return new TokenStore(ACCOUNT_ID, ACCOUNTS_DIR);
|
|
40
|
+
}
|
|
41
|
+
function getConfig() {
|
|
42
|
+
if (!OUTLOOK_CLIENT_ID) {
|
|
43
|
+
throw new Error("OUTLOOK_CLIENT_ID is not configured. Set it in the operator's Maxy install before using outlook tools.");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
clientId: OUTLOOK_CLIENT_ID,
|
|
47
|
+
tenantId: OUTLOOK_TENANT_ID,
|
|
48
|
+
accountId: ACCOUNT_ID,
|
|
49
|
+
tokenStore: getStore(),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function ok(payload) {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{ type: "text", text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2) },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function fail(message) {
|
|
60
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
61
|
+
}
|
|
62
|
+
const server = new McpServer({
|
|
63
|
+
name: "outlook",
|
|
64
|
+
version: "0.1.0",
|
|
65
|
+
});
|
|
66
|
+
server.tool("outlook-account-register", "Initiate the OAuth Auth Code + PKCE flow for this account. Returns an authorization URL the operator opens in the VNC browser. Tool blocks until the operator completes consent (5-minute timeout) or fails loudly.", {}, async () => {
|
|
67
|
+
try {
|
|
68
|
+
const config = getConfig();
|
|
69
|
+
const result = await runAccountRegister({
|
|
70
|
+
accountId: ACCOUNT_ID,
|
|
71
|
+
clientId: config.clientId,
|
|
72
|
+
tenantId: config.tenantId,
|
|
73
|
+
tokenStore: config.tokenStore,
|
|
74
|
+
});
|
|
75
|
+
return ok(result);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return fail(`outlook-account-register failed: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// @ts-expect-error — MCP SDK server.tool() type inference exceeds depth limit with Zod v4
|
|
82
|
+
server.tool("outlook-mail-list", "List recent messages in the mailbox. Defaults to top=25 ordered by receivedDateTime DESC. Folder defaults to Inbox; pass a folder displayName or id for other folders. Returns id, subject, from, receivedDateTime, bodyPreview, isRead.", {
|
|
83
|
+
top: z.number().int().min(1).max(250).optional(),
|
|
84
|
+
folder: z.string().optional(),
|
|
85
|
+
}, async ({ top, folder }) => {
|
|
86
|
+
try {
|
|
87
|
+
const items = await runMailList(getConfig(), { top, folder });
|
|
88
|
+
return ok(items);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return fail(`outlook-mail-list failed: ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
server.tool("outlook-mail-search", "Search mail with Microsoft Graph $search. The query is enclosed in quotes and applied with ConsistencyLevel=eventual. Returns the same shape as outlook-mail-list.", {
|
|
95
|
+
query: z.string().min(1),
|
|
96
|
+
top: z.number().int().min(1).max(250).optional(),
|
|
97
|
+
}, async ({ query, top }) => {
|
|
98
|
+
try {
|
|
99
|
+
const items = await runMailSearch(getConfig(), { query, top });
|
|
100
|
+
return ok(items);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
return fail(`outlook-mail-search failed: ${err.message}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
server.tool("outlook-calendar-list", "List calendar events in the next rangeDays days (default 7, max 365). Returns id, subject, start, end, location, attendees, isAllDay, organizer.", {
|
|
107
|
+
rangeDays: z.number().int().min(1).max(365).optional(),
|
|
108
|
+
top: z.number().int().min(1).max(500).optional(),
|
|
109
|
+
}, async ({ rangeDays, top }) => {
|
|
110
|
+
try {
|
|
111
|
+
const events = await runCalendarList(getConfig(), { rangeDays, top });
|
|
112
|
+
return ok(events);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return fail(`outlook-calendar-list failed: ${err.message}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
server.tool("outlook-calendar-event", "Fetch a single calendar event by id, including the full body, attendees, and webLink.", {
|
|
119
|
+
eventId: z.string().min(1),
|
|
120
|
+
}, async ({ eventId }) => {
|
|
121
|
+
try {
|
|
122
|
+
const detail = await runCalendarEvent(getConfig(), { eventId });
|
|
123
|
+
return ok(detail);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
return fail(`outlook-calendar-event failed: ${err.message}`);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
server.tool("outlook-contacts-list", "List contacts. Defaults to top=50, max 250. Returns id, displayName, emailAddresses, phones, jobTitle, companyName.", {
|
|
130
|
+
top: z.number().int().min(1).max(250).optional(),
|
|
131
|
+
}, async ({ top }) => {
|
|
132
|
+
try {
|
|
133
|
+
const items = await runContactsList(getConfig(), { top });
|
|
134
|
+
return ok(items);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
return fail(`outlook-contacts-list failed: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
server.tool("outlook-mailbox-info", "Health probe — reports auth state, refresh-window status, and top-level folder count for this account. Useful for answering 'did account X auth?' without grepping logs.", {}, async () => {
|
|
141
|
+
try {
|
|
142
|
+
const info = await runMailboxInfo(getConfig());
|
|
143
|
+
return ok(info);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
return fail(`outlook-mailbox-info failed: ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
const transport = new StdioServerTransport();
|
|
150
|
+
await server.connect(transport);
|
|
151
|
+
process.stderr.write(`[outlook-mcp] ready account=${ACCOUNT_ID}\n`);
|
|
152
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Graph client wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Builds a `Client` from `@microsoft/microsoft-graph-client` whose auth provider
|
|
5
|
+
* pulls from the per-account TokenStore, with auto-refresh when the access
|
|
6
|
+
* token is within the refresh threshold of expiry.
|
|
7
|
+
*
|
|
8
|
+
* Centralises error classification — every Graph error decision keys off the
|
|
9
|
+
* status code and the structured `error.code` field, never the prose body.
|
|
10
|
+
* Aligns with `feedback_no_stdout_parsing_for_control_flow.md`.
|
|
11
|
+
*/
|
|
12
|
+
import { Client } from "@microsoft/microsoft-graph-client";
|
|
13
|
+
import type { TokenStore } from "../auth/token-store.js";
|
|
14
|
+
export interface GraphClientConfig {
|
|
15
|
+
clientId: string;
|
|
16
|
+
tenantId: string;
|
|
17
|
+
accountId: string;
|
|
18
|
+
tokenStore: TokenStore;
|
|
19
|
+
}
|
|
20
|
+
export type GraphErrorClass = {
|
|
21
|
+
kind: "auth";
|
|
22
|
+
statusCode: number;
|
|
23
|
+
code: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "rate-limit-recoverable";
|
|
26
|
+
statusCode: number;
|
|
27
|
+
retryAfterMs: number;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "rate-limit-terminal";
|
|
30
|
+
statusCode: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "on-prem";
|
|
33
|
+
statusCode: number;
|
|
34
|
+
mailServer?: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "5xx";
|
|
37
|
+
statusCode: number;
|
|
38
|
+
code: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "other";
|
|
41
|
+
statusCode: number;
|
|
42
|
+
code: string;
|
|
43
|
+
};
|
|
44
|
+
export declare function classifyGraphError(err: unknown): GraphErrorClass;
|
|
45
|
+
/**
|
|
46
|
+
* Factory wrapping `Client.init`. Exported as a named function so tests can
|
|
47
|
+
* `vi.spyOn(graphClient, "createGraphClient")` reliably (avoids the
|
|
48
|
+
* destructured-ESM mock no-op pitfall).
|
|
49
|
+
*/
|
|
50
|
+
export declare function createGraphClient(config: GraphClientConfig): Client;
|
|
51
|
+
export declare function getOrRefreshAccessToken(config: GraphClientConfig): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Run a Graph call with classification, single retry on `auth` (refresh +
|
|
54
|
+
* retry once), and respect for 429 Retry-After. All terminal classes throw
|
|
55
|
+
* with operator-readable messages.
|
|
56
|
+
*/
|
|
57
|
+
export declare function callGraph<T>(config: GraphClientConfig, invoke: (client: Client) => Promise<T>, opts?: {
|
|
58
|
+
tool: string;
|
|
59
|
+
}): Promise<T>;
|
|
60
|
+
//# sourceMappingURL=graph-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-client.d.ts","sourceRoot":"","sources":["../../src/lib/graph-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAGzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAqDxD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CA2BhE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAWnE;AAED,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDxF;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,MAAM,EAAE,iBAAiB,EACzB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EACtC,IAAI,GAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAsB,GACzC,OAAO,CAAC,CAAC,CAAC,CAsDZ"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Graph client wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Builds a `Client` from `@microsoft/microsoft-graph-client` whose auth provider
|
|
5
|
+
* pulls from the per-account TokenStore, with auto-refresh when the access
|
|
6
|
+
* token is within the refresh threshold of expiry.
|
|
7
|
+
*
|
|
8
|
+
* Centralises error classification — every Graph error decision keys off the
|
|
9
|
+
* status code and the structured `error.code` field, never the prose body.
|
|
10
|
+
* Aligns with `feedback_no_stdout_parsing_for_control_flow.md`.
|
|
11
|
+
*/
|
|
12
|
+
import { Client } from "@microsoft/microsoft-graph-client";
|
|
13
|
+
import { refreshAccessToken } from "../auth/pkce-flow.js";
|
|
14
|
+
import { log } from "./log.js";
|
|
15
|
+
/** Pull (statusCode, code, headers) from whatever shape the SDK threw. */
|
|
16
|
+
function normalizeGraphError(err) {
|
|
17
|
+
const e = err;
|
|
18
|
+
const statusCode = e.statusCode ?? e.status ?? 0;
|
|
19
|
+
let code = e.code ?? "";
|
|
20
|
+
if (!code && typeof e.body === "string") {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(e.body);
|
|
23
|
+
code = parsed.error?.code ?? "";
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// body wasn't JSON; leave code empty
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (!code && e.body && typeof e.body === "object") {
|
|
30
|
+
code = e.body.error?.code ?? "";
|
|
31
|
+
}
|
|
32
|
+
let retryAfterHeader = null;
|
|
33
|
+
let mailServerHeader = null;
|
|
34
|
+
const fetchHeaders = e.response?.headers;
|
|
35
|
+
if (fetchHeaders && typeof fetchHeaders.get === "function") {
|
|
36
|
+
retryAfterHeader = fetchHeaders.get("retry-after") ?? null;
|
|
37
|
+
mailServerHeader = fetchHeaders.get("x-mailbox-server") ?? null;
|
|
38
|
+
}
|
|
39
|
+
else if (e.headers instanceof Map) {
|
|
40
|
+
retryAfterHeader = e.headers.get("retry-after") ?? e.headers.get("Retry-After") ?? null;
|
|
41
|
+
mailServerHeader = e.headers.get("x-mailbox-server") ?? null;
|
|
42
|
+
}
|
|
43
|
+
else if (e.headers && typeof e.headers === "object") {
|
|
44
|
+
const h = e.headers;
|
|
45
|
+
retryAfterHeader =
|
|
46
|
+
h["retry-after"] ?? h["Retry-After"] ?? h["RETRY-AFTER"] ?? null;
|
|
47
|
+
mailServerHeader = h["x-mailbox-server"] ?? h["X-MailboxServer"] ?? null;
|
|
48
|
+
}
|
|
49
|
+
return { statusCode, code, retryAfterHeader, mailServerHeader };
|
|
50
|
+
}
|
|
51
|
+
export function classifyGraphError(err) {
|
|
52
|
+
const norm = normalizeGraphError(err);
|
|
53
|
+
const { statusCode, code, retryAfterHeader, mailServerHeader } = norm;
|
|
54
|
+
if (statusCode === 401) {
|
|
55
|
+
return { kind: "auth", statusCode, code: code || "Unauthenticated" };
|
|
56
|
+
}
|
|
57
|
+
if (statusCode === 429) {
|
|
58
|
+
if (retryAfterHeader) {
|
|
59
|
+
const seconds = Number(retryAfterHeader);
|
|
60
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
61
|
+
return {
|
|
62
|
+
kind: "rate-limit-recoverable",
|
|
63
|
+
statusCode,
|
|
64
|
+
retryAfterMs: seconds * 1000,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { kind: "rate-limit-terminal", statusCode };
|
|
69
|
+
}
|
|
70
|
+
if (statusCode === 503 && code === "MailboxNotEnabledForRESTAPI") {
|
|
71
|
+
return { kind: "on-prem", statusCode, mailServer: mailServerHeader ?? undefined };
|
|
72
|
+
}
|
|
73
|
+
if (statusCode >= 500 && statusCode < 600) {
|
|
74
|
+
return { kind: "5xx", statusCode, code: code || "ServerError" };
|
|
75
|
+
}
|
|
76
|
+
return { kind: "other", statusCode, code: code || "Unknown" };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Factory wrapping `Client.init`. Exported as a named function so tests can
|
|
80
|
+
* `vi.spyOn(graphClient, "createGraphClient")` reliably (avoids the
|
|
81
|
+
* destructured-ESM mock no-op pitfall).
|
|
82
|
+
*/
|
|
83
|
+
export function createGraphClient(config) {
|
|
84
|
+
return Client.init({
|
|
85
|
+
authProvider: async (done) => {
|
|
86
|
+
try {
|
|
87
|
+
const token = await getOrRefreshAccessToken(config);
|
|
88
|
+
done(null, token);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
done(err, null);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export async function getOrRefreshAccessToken(config) {
|
|
97
|
+
const { tokenStore, clientId, tenantId, accountId } = config;
|
|
98
|
+
const blob = tokenStore.read();
|
|
99
|
+
if (!blob) {
|
|
100
|
+
throw new Error(`auth-required: Outlook not connected for account=${accountId}; run outlook-account-register.`);
|
|
101
|
+
}
|
|
102
|
+
if (tokenStore.refreshTokenExpired() || !blob.refreshToken) {
|
|
103
|
+
throw new Error(`auth-required: Outlook refresh token expired for account=${accountId}; run outlook-account-register.`);
|
|
104
|
+
}
|
|
105
|
+
if (!tokenStore.needsRefresh()) {
|
|
106
|
+
return blob.accessToken;
|
|
107
|
+
}
|
|
108
|
+
const oldExpSec = Math.floor(blob.accessTokenExpiry / 1000);
|
|
109
|
+
try {
|
|
110
|
+
const refreshed = await refreshAccessToken({
|
|
111
|
+
clientId,
|
|
112
|
+
tenantId,
|
|
113
|
+
refreshToken: blob.refreshToken,
|
|
114
|
+
});
|
|
115
|
+
const updated = tokenStore.store(refreshed.access_token, refreshed.refresh_token ?? blob.refreshToken, refreshed.expires_in, { graphUserId: blob.graphUserId, scopes: blob.scopes });
|
|
116
|
+
log({
|
|
117
|
+
event: "token-refreshed",
|
|
118
|
+
account: accountId,
|
|
119
|
+
oldExpSec,
|
|
120
|
+
newExpSec: Math.floor(updated.accessTokenExpiry / 1000),
|
|
121
|
+
});
|
|
122
|
+
return updated.accessToken;
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
log({
|
|
126
|
+
event: "token-refresh-failed",
|
|
127
|
+
account: accountId,
|
|
128
|
+
reason: err.message,
|
|
129
|
+
});
|
|
130
|
+
tokenStore.clear();
|
|
131
|
+
throw new Error(`Outlook token refresh failed for account=${accountId}; re-auth required.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Run a Graph call with classification, single retry on `auth` (refresh +
|
|
136
|
+
* retry once), and respect for 429 Retry-After. All terminal classes throw
|
|
137
|
+
* with operator-readable messages.
|
|
138
|
+
*/
|
|
139
|
+
export async function callGraph(config, invoke, opts = { tool: "graph" }) {
|
|
140
|
+
let attempts = 0;
|
|
141
|
+
// Up to 3 attempts: original, one for auth-refresh, one for 429-recoverable.
|
|
142
|
+
while (true) {
|
|
143
|
+
attempts += 1;
|
|
144
|
+
const client = createGraphClient(config);
|
|
145
|
+
try {
|
|
146
|
+
return await invoke(client);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const cls = classifyGraphError(err);
|
|
150
|
+
log({
|
|
151
|
+
event: "graph-error",
|
|
152
|
+
account: config.accountId,
|
|
153
|
+
status: cls.statusCode,
|
|
154
|
+
code: "code" in cls ? cls.code : "",
|
|
155
|
+
retryAfterMs: cls.kind === "rate-limit-recoverable" ? cls.retryAfterMs : null,
|
|
156
|
+
});
|
|
157
|
+
if (cls.kind === "on-prem") {
|
|
158
|
+
log({
|
|
159
|
+
event: "on-prem-rejected",
|
|
160
|
+
account: config.accountId,
|
|
161
|
+
mailServer: cls.mailServer ?? "unknown",
|
|
162
|
+
});
|
|
163
|
+
throw new Error("Microsoft Graph does not support on-premises Exchange. Use Task 769 (IMAP).");
|
|
164
|
+
}
|
|
165
|
+
if (cls.kind === "rate-limit-terminal") {
|
|
166
|
+
throw new Error(`Outlook rate-limited without Retry-After hint (account=${config.accountId}, tool=${opts.tool}).`);
|
|
167
|
+
}
|
|
168
|
+
if (cls.kind === "auth" && attempts === 1) {
|
|
169
|
+
// Force a refresh on the next iteration by clearing the local cache
|
|
170
|
+
// and explicitly refreshing, then retry.
|
|
171
|
+
await getOrRefreshAccessToken({ ...config });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (cls.kind === "auth") {
|
|
175
|
+
throw new Error(`Outlook auth expired for account=${config.accountId}; run outlook-account-register.`);
|
|
176
|
+
}
|
|
177
|
+
if (cls.kind === "rate-limit-recoverable" && attempts === 1) {
|
|
178
|
+
await new Promise((resolve) => setTimeout(resolve, cls.retryAfterMs));
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (cls.kind === "5xx" && attempts === 1) {
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=graph-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-client.js","sourceRoot":"","sources":["../../src/lib/graph-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [outlook-mcp] structured logger.
|
|
3
|
+
*
|
|
4
|
+
* Every log line is prefixed `[outlook-mcp]` and rendered as `key=value` pairs
|
|
5
|
+
* (Pino-compatible shape). Bearer tokens and `access_token=` query strings are
|
|
6
|
+
* redacted before writing — defense in depth even though tools never log them.
|
|
7
|
+
*/
|
|
8
|
+
export declare function redact(value: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Log an event line. The first key in `fields` (typically `event`) becomes the
|
|
11
|
+
* top-level event tag; remaining keys are rendered as `k=v` pairs.
|
|
12
|
+
*
|
|
13
|
+
* log({ event: "mail-list", account: "abc", count: 25, elapsedMs: 320 })
|
|
14
|
+
* → [outlook-mcp] mail-list account=abc count=25 elapsedMs=320
|
|
15
|
+
*/
|
|
16
|
+
export declare function log(fields: {
|
|
17
|
+
event: string;
|
|
18
|
+
} & Record<string, unknown>): void;
|
|
19
|
+
/** Truncate a string to N chars, replacing the tail with `…` if it overflowed. */
|
|
20
|
+
export declare function trunc(value: string, max: number): string;
|
|
21
|
+
/** Hex-prefix of a SHA-256 challenge for log identification (8 chars). */
|
|
22
|
+
export declare function challengePrefix(challenge: string): string;
|
|
23
|
+
//# sourceMappingURL=log.d.ts.map
|