mcp-google-multi 6.0.0-alpha.44 → 6.0.0-alpha.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compose.d.ts +5 -0
- package/dist/compose.js +67 -0
- package/dist/index.js +10 -59
- package/dist/registry-transfer.js +40 -3
- package/dist/tools/account-wizard.d.ts +9 -0
- package/dist/tools/account-wizard.js +42 -0
- package/package.json +19 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { ToolRegistry, type DiscoveryMode } from './registry.js';
|
|
3
|
+
import type { IdentityContext } from './identity.js';
|
|
4
|
+
import type { Metrics } from './usage-metrics.js';
|
|
5
|
+
export declare function buildRegistry(server: McpServer, ctx: IdentityContext, mode?: DiscoveryMode, metrics?: Metrics | null): ToolRegistry;
|
package/dist/compose.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// S1.12: registry composition, extracted from index.ts so the tenancy module
|
|
2
|
+
// (and tests) can build registries without touching the CLI entrypoint. This
|
|
3
|
+
// module must stay free of import-time side effects of its own: no argv
|
|
4
|
+
// parsing, no process.exit, no prints (accounts.js module-load resolution is
|
|
5
|
+
// the one inherited, pre-existing exception on the import chain).
|
|
6
|
+
import { GENERATED_SERVICES } from './tools/generated/index.js';
|
|
7
|
+
import { GENERATED_GATES, SERVICES } from './services.js';
|
|
8
|
+
import { ToolRegistry } from './registry.js';
|
|
9
|
+
import { registerDiscoverTools } from './discover.js';
|
|
10
|
+
import { registerEscapeTools } from './tools/google-api.js';
|
|
11
|
+
import { registerAccountTools } from './tools/accounts-tool.js';
|
|
12
|
+
import { registerDiagnoseTool } from './doctor.js';
|
|
13
|
+
import { registerAccountWizardTools } from './tools/account-wizard.js';
|
|
14
|
+
import { getToolsets, toolsetEnabled } from './toolsets.js';
|
|
15
|
+
export function buildRegistry(server, ctx, mode, metrics = null) {
|
|
16
|
+
const policy = ctx.policy;
|
|
17
|
+
// The registry's account view IS the context's: fan-out expansion,
|
|
18
|
+
// selector validation and default-account injection all follow this
|
|
19
|
+
// context, not the process global (identical for the free core's one ctx).
|
|
20
|
+
const registry = new ToolRegistry(server, policy, mode, metrics, () => ctx.accounts);
|
|
21
|
+
const toolsets = getToolsets();
|
|
22
|
+
if (toolsets !== 'all') {
|
|
23
|
+
const known = new Set([...SERVICES.map((s) => s.name), ...GENERATED_SERVICES.map((s) => s.name)]);
|
|
24
|
+
for (const requested of toolsets) {
|
|
25
|
+
if (!known.has(requested)) {
|
|
26
|
+
process.stderr.write(`GOOGLE_TOOLSETS: unknown service "${requested}" ignored\n`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const svc of SERVICES) {
|
|
31
|
+
if (!toolsetEnabled(toolsets, svc.name))
|
|
32
|
+
continue;
|
|
33
|
+
if (svc.enabled && !svc.enabled(ctx.accounts)) {
|
|
34
|
+
if (toolsets !== 'all') {
|
|
35
|
+
const hint = svc.name === 'admin' ? 'set admin on an account/profile (or GOOGLE_ADMIN_ACCOUNTS)' : `add "${svc.name}" to an account's scope profile (or legacy GOOGLE_OPTIONAL_SCOPES)`;
|
|
36
|
+
process.stderr.write(`GOOGLE_TOOLSETS: "${svc.name}" requested but not enabled — ${hint}\n`);
|
|
37
|
+
}
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
svc.register(registry);
|
|
41
|
+
}
|
|
42
|
+
for (const gen of GENERATED_SERVICES) {
|
|
43
|
+
if (!toolsetEnabled(toolsets, gen.name))
|
|
44
|
+
continue;
|
|
45
|
+
const curated = SERVICES.find((s) => s.name === gen.name);
|
|
46
|
+
const gate = curated?.enabled ?? GENERATED_GATES[gen.name]?.enabled;
|
|
47
|
+
if (gate && !gate(ctx.accounts)) {
|
|
48
|
+
if (!curated && toolsets !== 'all') {
|
|
49
|
+
process.stderr.write(`GOOGLE_TOOLSETS: "${gen.name}" requested but not enabled — ${GENERATED_GATES[gen.name].hint}\n`);
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
gen.register(registry);
|
|
54
|
+
}
|
|
55
|
+
if (registry.services().length === 0) {
|
|
56
|
+
const known = [...new Set([...SERVICES.map((s) => s.name), ...GENERATED_SERVICES.map((s) => s.name)])].sort();
|
|
57
|
+
throw new Error(`GOOGLE_TOOLSETS="${process.env.GOOGLE_TOOLSETS ?? ''}" selected no enabled services. ` +
|
|
58
|
+
`Known services: ${known.join(', ')}. ` +
|
|
59
|
+
`Note: optional services need their bundle in an account's scope profile (or legacy GOOGLE_OPTIONAL_SCOPES); admin needs an admin account/profile.`);
|
|
60
|
+
}
|
|
61
|
+
registerDiscoverTools(registry, policy);
|
|
62
|
+
registerEscapeTools(registry, policy);
|
|
63
|
+
registerAccountTools(registry);
|
|
64
|
+
registerDiagnoseTool(registry, ctx);
|
|
65
|
+
registerAccountWizardTools(registry, server);
|
|
66
|
+
return registry;
|
|
67
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -8,17 +8,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
10
10
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { ToolRegistry, resolveDiscoveryMode } from './registry.js';
|
|
14
|
-
import { registerDiscoverTools } from './discover.js';
|
|
15
|
-
import { registerEscapeTools } from './tools/google-api.js';
|
|
16
|
-
import { registerAccountTools } from './tools/accounts-tool.js';
|
|
17
|
-
import { registerDiagnoseTool } from './doctor.js';
|
|
18
|
-
import { registerAccountWizardTools } from './tools/account-wizard.js';
|
|
19
|
-
import { getToolsets, toolsetEnabled } from './toolsets.js';
|
|
11
|
+
import { unknownToolMessage } from './services.js';
|
|
12
|
+
import { resolveDiscoveryMode } from './registry.js';
|
|
20
13
|
import { isAllowed, describePolicy } from './write-control.js';
|
|
21
14
|
import { buildIdentityContext } from './identity.js';
|
|
15
|
+
import { buildRegistry } from './compose.js';
|
|
22
16
|
import { registerSetupPrompt } from './setup-prompt.js';
|
|
23
17
|
import { applyNetTuning } from './net-tuning.js';
|
|
24
18
|
import { argNormalizationEnabled, withArgNormalization, withValidationEnvelope } from './arg-normalize.js';
|
|
@@ -43,56 +37,6 @@ function strictArgOptions(registry, metrics) {
|
|
|
43
37
|
onDrop: metrics ? (tool, keys) => metrics.recordArgDrop(tool, keys) : undefined,
|
|
44
38
|
};
|
|
45
39
|
}
|
|
46
|
-
function buildRegistry(server, ctx, mode, metrics = null) {
|
|
47
|
-
const policy = ctx.policy;
|
|
48
|
-
const registry = new ToolRegistry(server, policy, mode, metrics);
|
|
49
|
-
const toolsets = getToolsets();
|
|
50
|
-
if (toolsets !== 'all') {
|
|
51
|
-
const known = new Set([...SERVICES.map((s) => s.name), ...GENERATED_SERVICES.map((s) => s.name)]);
|
|
52
|
-
for (const requested of toolsets) {
|
|
53
|
-
if (!known.has(requested)) {
|
|
54
|
-
process.stderr.write(`GOOGLE_TOOLSETS: unknown service "${requested}" ignored\n`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
for (const svc of SERVICES) {
|
|
59
|
-
if (!toolsetEnabled(toolsets, svc.name))
|
|
60
|
-
continue;
|
|
61
|
-
if (svc.enabled && !svc.enabled(ctx.accounts)) {
|
|
62
|
-
if (toolsets !== 'all') {
|
|
63
|
-
const hint = svc.name === 'admin' ? 'set admin on an account/profile (or GOOGLE_ADMIN_ACCOUNTS)' : `add "${svc.name}" to an account's scope profile (or legacy GOOGLE_OPTIONAL_SCOPES)`;
|
|
64
|
-
process.stderr.write(`GOOGLE_TOOLSETS: "${svc.name}" requested but not enabled — ${hint}\n`);
|
|
65
|
-
}
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
svc.register(registry);
|
|
69
|
-
}
|
|
70
|
-
for (const gen of GENERATED_SERVICES) {
|
|
71
|
-
if (!toolsetEnabled(toolsets, gen.name))
|
|
72
|
-
continue;
|
|
73
|
-
const curated = SERVICES.find((s) => s.name === gen.name);
|
|
74
|
-
const gate = curated?.enabled ?? GENERATED_GATES[gen.name]?.enabled;
|
|
75
|
-
if (gate && !gate(ctx.accounts)) {
|
|
76
|
-
if (!curated && toolsets !== 'all') {
|
|
77
|
-
process.stderr.write(`GOOGLE_TOOLSETS: "${gen.name}" requested but not enabled — ${GENERATED_GATES[gen.name].hint}\n`);
|
|
78
|
-
}
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
gen.register(registry);
|
|
82
|
-
}
|
|
83
|
-
if (registry.services().length === 0) {
|
|
84
|
-
const known = [...new Set([...SERVICES.map((s) => s.name), ...GENERATED_SERVICES.map((s) => s.name)])].sort();
|
|
85
|
-
throw new Error(`GOOGLE_TOOLSETS="${process.env.GOOGLE_TOOLSETS ?? ''}" selected no enabled services. ` +
|
|
86
|
-
`Known services: ${known.join(', ')}. ` +
|
|
87
|
-
`Note: optional services need their bundle in an account's scope profile (or legacy GOOGLE_OPTIONAL_SCOPES); admin needs an admin account/profile.`);
|
|
88
|
-
}
|
|
89
|
-
registerDiscoverTools(registry, policy);
|
|
90
|
-
registerEscapeTools(registry, policy);
|
|
91
|
-
registerAccountTools(registry);
|
|
92
|
-
registerDiagnoseTool(registry, ctx);
|
|
93
|
-
registerAccountWizardTools(registry, server);
|
|
94
|
-
return registry;
|
|
95
|
-
}
|
|
96
40
|
async function main() {
|
|
97
41
|
if (process.argv.includes('auth')) {
|
|
98
42
|
const { runAuthFlow } = await import('./auth.js');
|
|
@@ -381,6 +325,13 @@ async function main() {
|
|
|
381
325
|
// re-auth link into the AS's alias_reauth flow instead of a stdio CLI hint.
|
|
382
326
|
const { setHttpReauthBase } = await import('./reauth-hint.js');
|
|
383
327
|
setHttpReauthBase(httpCfg.publicUrl);
|
|
328
|
+
// Wizard consent over HTTP: hand out the clientless AS link instead of
|
|
329
|
+
// binding a loopback listener (single-owner flow; a tenancy host installs
|
|
330
|
+
// its own signed-mint context here).
|
|
331
|
+
const { setWizardHttpConsent } = await import('./tools/account-wizard.js');
|
|
332
|
+
setWizardHttpConsent({
|
|
333
|
+
mintConsentUrl: (alias) => `${httpCfg.publicUrl}/authorize?flow=alias_reauth&alias=${encodeURIComponent(alias)}`,
|
|
334
|
+
});
|
|
384
335
|
let httpTap;
|
|
385
336
|
if (httpMetrics) {
|
|
386
337
|
const { tapUsageMetrics } = await import('./metrics-tap.js');
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
8
8
|
import * as readline from 'node:readline';
|
|
9
9
|
import path from 'node:path';
|
|
10
|
-
import {
|
|
10
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
|
|
11
11
|
import { atomicWriteFileSync, atomicWriteWithLock } from './fs-atomic.js';
|
|
12
12
|
import { ALIAS_RE, configFilePath, CONFIG_VERSION, tenantsDir } from './config-file.js';
|
|
13
13
|
import { getTokenDir } from './accounts.js';
|
|
@@ -61,15 +61,52 @@ export function buildTransferBundle(exportedAt, deps = {}) {
|
|
|
61
61
|
}
|
|
62
62
|
return { manifest: { v: 1, exportedAt, aliases: [...aliasSet].sort() }, config, tokens };
|
|
63
63
|
}
|
|
64
|
+
// Bundle envelope v2: a REAL passphrase KDF. The v1 envelope reused the
|
|
65
|
+
// token store's deriveKey, whose passphrase branch is one unsalted sha256 —
|
|
66
|
+
// an exported bundle could be brute-forced offline (CodeQL
|
|
67
|
+
// js/insufficient-password-hash). scrypt with a per-bundle random salt fixes
|
|
68
|
+
// that; v1 bundles (a prerelease-only format, never produced by any stable
|
|
69
|
+
// release) are refused with a re-export hint rather than kept decryptable.
|
|
70
|
+
const BUNDLE_VERSION = 2;
|
|
71
|
+
const SCRYPT_PARAMS = { N: 1 << 15, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
|
|
72
|
+
function bundleKey(passphrase, salt) {
|
|
73
|
+
return scryptSync(passphrase, salt, 32, SCRYPT_PARAMS);
|
|
74
|
+
}
|
|
64
75
|
export function encryptBundle(bundle, passphrase) {
|
|
65
|
-
|
|
76
|
+
const salt = randomBytes(16);
|
|
77
|
+
const iv = randomBytes(12);
|
|
78
|
+
const cipher = createCipheriv('aes-256-gcm', bundleKey(passphrase, salt), iv);
|
|
79
|
+
const data = Buffer.concat([cipher.update(Buffer.from(JSON.stringify(bundle), 'utf8')), cipher.final()]);
|
|
80
|
+
const file = {
|
|
81
|
+
bv: BUNDLE_VERSION,
|
|
82
|
+
salt: salt.toString('base64'),
|
|
83
|
+
iv: iv.toString('base64'),
|
|
84
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
85
|
+
data: data.toString('base64'),
|
|
86
|
+
};
|
|
87
|
+
return JSON.stringify(file);
|
|
66
88
|
}
|
|
67
89
|
export class BundleDecryptError extends Error {
|
|
68
90
|
}
|
|
69
91
|
export function decryptBundle(contents, passphrase) {
|
|
92
|
+
let file;
|
|
93
|
+
try {
|
|
94
|
+
file = JSON.parse(contents);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
throw new BundleDecryptError('The file is not a registry export bundle.');
|
|
98
|
+
}
|
|
99
|
+
if (file.v !== undefined && file.bv === undefined) {
|
|
100
|
+
throw new BundleDecryptError('This bundle uses the retired v1 prerelease format (weak passphrase derivation); re-export it from the source machine with the current version.');
|
|
101
|
+
}
|
|
102
|
+
if (file.bv !== BUNDLE_VERSION || !file.salt || !file.iv || !file.tag || !file.data) {
|
|
103
|
+
throw new BundleDecryptError('The file is not a valid registry export (bad envelope).');
|
|
104
|
+
}
|
|
70
105
|
let obj;
|
|
71
106
|
try {
|
|
72
|
-
|
|
107
|
+
const decipher = createDecipheriv('aes-256-gcm', bundleKey(passphrase, Buffer.from(file.salt, 'base64')), Buffer.from(file.iv, 'base64'));
|
|
108
|
+
decipher.setAuthTag(Buffer.from(file.tag, 'base64'));
|
|
109
|
+
obj = JSON.parse(Buffer.concat([decipher.update(Buffer.from(file.data, 'base64')), decipher.final()]).toString('utf8'));
|
|
73
110
|
}
|
|
74
111
|
catch {
|
|
75
112
|
throw new BundleDecryptError('Could not decrypt the bundle — wrong passphrase or the file is corrupt.');
|
|
@@ -53,4 +53,13 @@ export declare function argsToAddForm(a: {
|
|
|
53
53
|
/** Scopes requested by the profile but NOT granted at consent (granular
|
|
54
54
|
* consent / unchecked bundles). Pure. */
|
|
55
55
|
export declare function scopeGrantDiff(requested: string[], grantedScope: string | undefined): string[];
|
|
56
|
+
/** HTTP-transport consent context (S1.20): set by the HTTP bootstrap. When
|
|
57
|
+
* present, runConsent NEVER binds a server-side loopback listener — the
|
|
58
|
+
* user's browser cannot reach the daemon's loopback — and instead hands out
|
|
59
|
+
* a clientless AS consent URL. The injector decides the flow (single-owner =
|
|
60
|
+
* the legacy alias_reauth link; a tenancy host mints a signed alias_add URL). */
|
|
61
|
+
export interface WizardHttpConsent {
|
|
62
|
+
mintConsentUrl: (alias: string) => Promise<string> | string;
|
|
63
|
+
}
|
|
64
|
+
export declare function setWizardHttpConsent(ctx: WizardHttpConsent | null): void;
|
|
56
65
|
export declare function registerAccountWizardTools(registry: ToolRegistry, server: McpServer): void;
|
|
@@ -136,6 +136,15 @@ function errorResult(slug, message, hint, account) {
|
|
|
136
136
|
isError: true,
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
+
let httpConsent = null;
|
|
140
|
+
export function setWizardHttpConsent(ctx) {
|
|
141
|
+
httpConsent = ctx;
|
|
142
|
+
}
|
|
143
|
+
function pendingText(alias, url) {
|
|
144
|
+
return (`Account "${alias}" is ready to authorize. Open this link in a browser (it belongs to whoever owns the Google account):\n${url}\n` +
|
|
145
|
+
`On completion the token is stored server-side and the account is usable immediately — no restart. ` +
|
|
146
|
+
`If the link expires, run account_reauth for a fresh one.\n${TESTING_MODE_WARNING}`);
|
|
147
|
+
}
|
|
139
148
|
/** Run Google consent for `alias` and persist the token. Opens the browser via
|
|
140
149
|
* URL-mode elicitation when the client supports it, else server-side + prints
|
|
141
150
|
* the URL. Returns the granted-scope diff for the S4 report. */
|
|
@@ -150,6 +159,29 @@ async function runConsent(server, alias) {
|
|
|
150
159
|
hint: 'Accounts sourced from GOOGLE_ACCOUNTS are not editable here. Run `mcp-google-multi migrate-config` to move them into config.json.',
|
|
151
160
|
};
|
|
152
161
|
}
|
|
162
|
+
// HTTP transport: consent completes OUT OF BAND at the AS /callback. Never
|
|
163
|
+
// bind a loopback listener here; try URL-mode elicitation as a convenience
|
|
164
|
+
// but surface the URL UP FRONT either way (not only on failure).
|
|
165
|
+
const http = httpConsent;
|
|
166
|
+
if (http) {
|
|
167
|
+
let url;
|
|
168
|
+
try {
|
|
169
|
+
url = await http.mintConsentUrl(alias);
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
return { ok: false, slug: 'internal', message: safeMessage(e) };
|
|
173
|
+
}
|
|
174
|
+
const caps = server.server.getClientCapabilities?.();
|
|
175
|
+
if (caps?.elicitation?.url !== undefined) {
|
|
176
|
+
try {
|
|
177
|
+
await server.server.elicitInput(urlElicitationParams(alias, url, randomBytes(16).toString('hex')));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// the URL in the pending text is the recovery
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { ok: 'pending', url };
|
|
184
|
+
}
|
|
153
185
|
// Bind the ephemeral loopback listener BEFORE building the auth URL: the
|
|
154
186
|
// redirect URI needs the assigned port, and listening first means the
|
|
155
187
|
// callback can't race the browser.
|
|
@@ -267,6 +299,8 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
267
299
|
invalidateAccountSet();
|
|
268
300
|
// S3 + S4: consent + validate.
|
|
269
301
|
const consent = await runConsent(server, validated.alias);
|
|
302
|
+
if (consent.ok === 'pending')
|
|
303
|
+
return textResult(pendingText(validated.alias, consent.url));
|
|
270
304
|
if (!consent.ok)
|
|
271
305
|
return errorResult(consent.slug, consent.message, consent.hint, validated.alias);
|
|
272
306
|
// B15: offer to register the server with another MCP client.
|
|
@@ -303,6 +337,8 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
303
337
|
return errorResult('validation_error', `Unknown account "${alias}".`, `Pass one of the configured aliases: ${getAccountSet().aliases.join(', ')}. Use account_add to create a new one.`, alias);
|
|
304
338
|
}
|
|
305
339
|
const consent = await runConsent(server, alias);
|
|
340
|
+
if (consent.ok === 'pending')
|
|
341
|
+
return textResult(pendingText(alias, consent.url));
|
|
306
342
|
if (!consent.ok)
|
|
307
343
|
return errorResult(consent.slug, consent.message, consent.hint, alias);
|
|
308
344
|
return textResult(s4Text(alias, consent.missing));
|
|
@@ -353,6 +389,12 @@ export function registerAccountWizardTools(registry, server) {
|
|
|
353
389
|
if (client.managed === 'cli') {
|
|
354
390
|
blocks.push(`${client.label}: run\n ${instr.text}`);
|
|
355
391
|
}
|
|
392
|
+
else if (a.write && mode === 'http') {
|
|
393
|
+
// Over HTTP this tool runs on the DAEMON, whose homedir is not the
|
|
394
|
+
// caller's machine: write:true would edit the operator's own client
|
|
395
|
+
// configs (a cross-tenant hazard under tenancy). Snippet only.
|
|
396
|
+
blocks.push(`${client.label}: write:true is ignored over HTTP — this tool runs on the server, not your machine. Add to ${instr.path}\n${instr.text}`);
|
|
397
|
+
}
|
|
356
398
|
else if (a.write) {
|
|
357
399
|
const res = applyFileEntry(client, name, entry);
|
|
358
400
|
blocks.push(res.ok
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-multi",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.46",
|
|
4
4
|
"mcpName": "io.github.bakissation/mcp-google-multi",
|
|
5
5
|
"description": "Local MCP server for Google Workspace (Gmail, Drive, Calendar, Sheets, Docs, Contacts, Tasks, Meet, Search Console, +Forms/Chat/Admin) across multiple accounts — OAuth-only, encrypted token storage, deny-by-default writes.",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,24 @@
|
|
|
9
9
|
"mcp-google-multi": "dist/index.js"
|
|
10
10
|
},
|
|
11
11
|
"main": "dist/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./identity": "./dist/identity.js",
|
|
15
|
+
"./compose": "./dist/compose.js",
|
|
16
|
+
"./registry": "./dist/registry.js",
|
|
17
|
+
"./oauth-as": "./dist/oauth-as.js",
|
|
18
|
+
"./accounts": "./dist/accounts.js",
|
|
19
|
+
"./token-store": "./dist/token-store.js",
|
|
20
|
+
"./http-transport": "./dist/http-transport.js",
|
|
21
|
+
"./http-config": "./dist/http-config.js",
|
|
22
|
+
"./client": "./dist/client.js",
|
|
23
|
+
"./config-file": "./dist/config-file.js",
|
|
24
|
+
"./mcp-token": "./dist/mcp-token.js",
|
|
25
|
+
"./boot-gates": "./dist/boot-gates.js",
|
|
26
|
+
"./tenant-purge": "./dist/tenant-purge.js",
|
|
27
|
+
"./doctor": "./dist/doctor.js",
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
12
30
|
"files": [
|
|
13
31
|
"dist"
|
|
14
32
|
],
|