mcp-google-multi 6.0.0-alpha.30 → 6.0.0-alpha.31
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/arg-normalize.d.ts +1 -2
- package/dist/arg-normalize.js +8 -1
- package/dist/http-transport.d.ts +1 -2
- package/dist/http-transport.js +2 -3
- package/dist/index.js +4 -4
- package/dist/metrics-tap.d.ts +10 -2
- package/dist/metrics-tap.js +31 -11
- package/dist/registry.d.ts +5 -1
- package/dist/registry.js +11 -2
- package/dist/setup-prompt.d.ts +1 -1
- package/dist/setup-prompt.js +1 -1
- package/dist/tools/account-wizard.d.ts +1 -1
- package/dist/usage-metrics.js +4 -0
- package/package.json +3 -3
package/dist/arg-normalize.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { Transport } from
|
|
2
|
-
import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
1
|
+
import type { Transport, JSONRPCMessage } from "@modelcontextprotocol/server";
|
|
3
2
|
export declare function argNormalizationEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
4
3
|
/** Declared scalar kind per schema key; drives value coercion on RENAMED keys
|
|
5
4
|
* only. Clients string-encode values for keys absent from the advertised
|
package/dist/arg-normalize.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Wire-level tools/call argument normalization. Clients (LLMs) recurringly
|
|
2
2
|
// snake_case a camelCase parameter (thread_id for threadId) and burn a retry
|
|
3
|
-
// on the -32602. A schema-level fix is off the table: SDK
|
|
3
|
+
// on the -32602. A schema-level fix is off the table: the SDK advertises an
|
|
4
4
|
// EMPTY input schema for any non-object wrapper (pipe/preprocess), so the
|
|
5
5
|
// only seam that keeps tools/list intact is the JSON-RPC message itself —
|
|
6
6
|
// which is versioned MCP spec, stabler than any SDK internal. The rename is
|
|
@@ -90,5 +90,12 @@ export function withArgNormalization(transport, shapeFor, log, onRename) {
|
|
|
90
90
|
if (transport.setProtocolVersion) {
|
|
91
91
|
wrapper.setProtocolVersion = (v) => transport.setProtocolVersion(v);
|
|
92
92
|
}
|
|
93
|
+
// v2-only, called by Protocol.connect() before start(). A no-op today (both
|
|
94
|
+
// sides default to the same exported constant), but a proxy that silently
|
|
95
|
+
// eats a member the SDK calls is a bug waiting for the first caller that
|
|
96
|
+
// passes supportedProtocolVersions explicitly.
|
|
97
|
+
if (transport.setSupportedProtocolVersions) {
|
|
98
|
+
wrapper.setSupportedProtocolVersions = (v) => transport.setSupportedProtocolVersions(v);
|
|
99
|
+
}
|
|
93
100
|
return wrapper;
|
|
94
101
|
}
|
package/dist/http-transport.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import type { McpServer, Transport } from "@modelcontextprotocol/server";
|
|
1
2
|
import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
2
|
-
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import type { HttpConfig } from './http-config.js';
|
|
4
4
|
import { type ArgShape } from './arg-normalize.js';
|
|
5
|
-
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
6
5
|
export type AuthOutcome = {
|
|
7
6
|
ok: true;
|
|
8
7
|
} | {
|
package/dist/http-transport.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
|
2
2
|
// node:http server, the route table, the front guard (Host / Origin / DNS-rebind),
|
|
3
3
|
// and the stateless per-request /mcp dispatch. The OAuth AS endpoints and Bearer
|
|
4
4
|
// verification are a seam filled by B13 (oauth-authorization-server); B12 ships a
|
|
5
5
|
// loopback-owner authenticator so the local-HTTP model works before the AS lands.
|
|
6
6
|
import { createServer } from 'node:http';
|
|
7
|
-
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
8
7
|
import { withArgNormalization } from './arg-normalize.js';
|
|
9
8
|
// A hung handler that keeps the connection open would otherwise hold the global
|
|
10
9
|
// serialize() lock forever. Generous by default so slow-but-valid calls (large
|
|
@@ -161,7 +160,7 @@ export class HttpTransportHost {
|
|
|
161
160
|
// the mounted AS routes), so the SDK's own DNS-rebind guard is disabled: its
|
|
162
161
|
// exact-Host match is stricter than the front guard and would 403 valid
|
|
163
162
|
// Hosts (double enforcement, differing rules).
|
|
164
|
-
const transport = new
|
|
163
|
+
const transport = new NodeStreamableHTTPServerTransport({
|
|
165
164
|
sessionIdGenerator: undefined,
|
|
166
165
|
enableJsonResponse: true,
|
|
167
166
|
enableDnsRebindingProtection: false,
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { assertServerAccountsConfigured } from './accounts.js';
|
|
|
5
5
|
import { readFileSync } from 'node:fs';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
10
10
|
import { GENERATED_SERVICES } from './tools/generated/index.js';
|
|
11
11
|
import { GENERATED_GATES, SERVICES } from './services.js';
|
|
12
12
|
import { ToolRegistry, resolveDiscoveryMode } from './registry.js';
|
|
@@ -247,7 +247,7 @@ async function main() {
|
|
|
247
247
|
let transport = new StdioServerTransport();
|
|
248
248
|
if (stdioMetrics) {
|
|
249
249
|
const { tapUsageMetrics } = await import('./metrics-tap.js');
|
|
250
|
-
transport = tapUsageMetrics(transport, stdioMetrics);
|
|
250
|
+
transport = tapUsageMetrics(transport, stdioMetrics, (n) => registry.hasTool(n));
|
|
251
251
|
}
|
|
252
252
|
await server.connect(argNormalizationEnabled()
|
|
253
253
|
? withArgNormalization(transport, (n) => registry.argShape(n), undefined, stdioMetrics ? (tool, n) => stdioMetrics.recordArgFix(tool, n) : undefined)
|
|
@@ -338,7 +338,7 @@ async function main() {
|
|
|
338
338
|
let httpTap;
|
|
339
339
|
if (httpMetrics) {
|
|
340
340
|
const { tapUsageMetrics } = await import('./metrics-tap.js');
|
|
341
|
-
httpTap = (t) => tapUsageMetrics(t, httpMetrics);
|
|
341
|
+
httpTap = (t) => tapUsageMetrics(t, httpMetrics, (n) => registry.hasTool(n));
|
|
342
342
|
}
|
|
343
343
|
const host = new HttpTransportHost({
|
|
344
344
|
server: httpServer,
|
package/dist/metrics-tap.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import type { Transport } from
|
|
1
|
+
import type { Transport } from "@modelcontextprotocol/server";
|
|
2
2
|
import type { Metrics } from './usage-metrics.js';
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function isSchemaValidationText(text: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* @param isKnownTool membership test against the REGISTERED tool set. The
|
|
6
|
+
* name on a `tools/call` frame is client-supplied, so a shape check alone
|
|
7
|
+
* would let a shape-valid but invented name reach disk and break the
|
|
8
|
+
* closed-vocabulary guarantee. A validation failure against a name the server
|
|
9
|
+
* never registered is a not-found, and is counted as one.
|
|
10
|
+
*/
|
|
11
|
+
export declare function tapUsageMetrics(transport: Transport, metrics: Metrics, isKnownTool?: (name: string) => boolean): Transport;
|
package/dist/metrics-tap.js
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
const PENDING_CAP = 1_000;
|
|
2
|
-
|
|
2
|
+
// The SDK synthesizes input-validation failures as isError RESULTS, skipping
|
|
3
|
+
// the handler entirely, so neither the registry wrapper nor the error-frame
|
|
4
|
+
// path sees them. The text is prose in both SDK eras (v1 prefixed it with
|
|
5
|
+
// "MCP error -32602: ", v2 dropped that prefix), while handler envelopes are
|
|
6
|
+
// always JSON starting with "{" — so this can never double count one.
|
|
7
|
+
const SCHEMA_VALIDATION_TEXT = /^(MCP error -32602: )?Input validation error:/;
|
|
8
|
+
export function isSchemaValidationText(text) {
|
|
9
|
+
return SCHEMA_VALIDATION_TEXT.test(text);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @param isKnownTool membership test against the REGISTERED tool set. The
|
|
13
|
+
* name on a `tools/call` frame is client-supplied, so a shape check alone
|
|
14
|
+
* would let a shape-valid but invented name reach disk and break the
|
|
15
|
+
* closed-vocabulary guarantee. A validation failure against a name the server
|
|
16
|
+
* never registered is a not-found, and is counted as one.
|
|
17
|
+
*/
|
|
18
|
+
export function tapUsageMetrics(transport, metrics, isKnownTool = () => false) {
|
|
3
19
|
const pending = new Map();
|
|
20
|
+
const recordValidation = (tool) => {
|
|
21
|
+
if (tool !== undefined && isKnownTool(tool))
|
|
22
|
+
metrics.recordRpc('schema_validation', tool);
|
|
23
|
+
else
|
|
24
|
+
metrics.recordRpc('tool_not_found');
|
|
25
|
+
};
|
|
4
26
|
const wrapper = {
|
|
5
27
|
start: () => transport.start(),
|
|
6
28
|
send: (message, options) => {
|
|
@@ -14,23 +36,16 @@ export function tapUsageMetrics(transport, metrics) {
|
|
|
14
36
|
if (/unknown tool|not found/i.test(m.error.message ?? ''))
|
|
15
37
|
metrics.recordRpc('tool_not_found');
|
|
16
38
|
else
|
|
17
|
-
|
|
39
|
+
recordValidation(tool);
|
|
18
40
|
}
|
|
19
41
|
else {
|
|
20
42
|
metrics.recordRpc(m.error.code);
|
|
21
43
|
}
|
|
22
44
|
}
|
|
23
45
|
else if (m.result?.isError === true) {
|
|
24
|
-
// SDK 1.x synthesizes input-validation failures as isError RESULTS
|
|
25
|
-
// ("MCP error -32602: ..."), skipping the handler entirely, so the
|
|
26
|
-
// registry wrapper never sees them either. Handler envelopes are
|
|
27
|
-
// JSON text and never carry this prefix, so nothing double counts.
|
|
28
46
|
const first = m.result.content?.[0]?.text;
|
|
29
|
-
if (typeof first === 'string' && first
|
|
30
|
-
|
|
31
|
-
metrics.recordRpc('tool_not_found');
|
|
32
|
-
else
|
|
33
|
-
metrics.recordRpc('schema_validation', tool);
|
|
47
|
+
if (typeof first === 'string' && isSchemaValidationText(first)) {
|
|
48
|
+
recordValidation(tool);
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
51
|
}
|
|
@@ -71,5 +86,10 @@ export function tapUsageMetrics(transport, metrics) {
|
|
|
71
86
|
if (transport.setProtocolVersion) {
|
|
72
87
|
wrapper.setProtocolVersion = (v) => transport.setProtocolVersion(v);
|
|
73
88
|
}
|
|
89
|
+
// v2-only; see the same forward in arg-normalize.ts. Both proxies compose,
|
|
90
|
+
// so a member dropped by either layer never reaches the real transport.
|
|
91
|
+
if (transport.setSupportedProtocolVersions) {
|
|
92
|
+
wrapper.setSupportedProtocolVersions = (v) => transport.setSupportedProtocolVersions(v);
|
|
93
|
+
}
|
|
74
94
|
return wrapper;
|
|
75
95
|
}
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { type Policy } from './write-control.js';
|
|
4
4
|
import type { ArgShape } from './arg-normalize.js';
|
|
@@ -58,6 +58,10 @@ export declare class ToolRegistry {
|
|
|
58
58
|
catalog(service: string, query?: string): CatalogOperation[];
|
|
59
59
|
/** The metrics recorder, for hook sites (escape hatch); null when off. */
|
|
60
60
|
get usageMetrics(): Metrics | null;
|
|
61
|
+
/** Membership test against the REGISTERED tool set (hidden tools included:
|
|
62
|
+
* they stay callable). The metrics tap uses this so a client-supplied name
|
|
63
|
+
* can never enter the closed vocabulary. */
|
|
64
|
+
hasTool(name: string): boolean;
|
|
61
65
|
/** Op-name vocabulary for a service, split by provenance so the capped
|
|
62
66
|
* discover descriptions can list curated ops and only summarize the
|
|
63
67
|
* generated long tail. */
|
package/dist/registry.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
1
|
import { z } from 'zod';
|
|
3
2
|
import { isAllowed, writeDisabledResult, IRREVERSIBLE_TOOLS } from './write-control.js';
|
|
4
3
|
import { getAccountSet, refreshAccountSetIfStale } from './accounts.js';
|
|
@@ -256,6 +255,12 @@ export class ToolRegistry {
|
|
|
256
255
|
get usageMetrics() {
|
|
257
256
|
return this.metrics;
|
|
258
257
|
}
|
|
258
|
+
/** Membership test against the REGISTERED tool set (hidden tools included:
|
|
259
|
+
* they stay callable). The metrics tap uses this so a client-supplied name
|
|
260
|
+
* can never enter the closed vocabulary. */
|
|
261
|
+
hasTool(name) {
|
|
262
|
+
return this.tools.some((t) => t.name === name);
|
|
263
|
+
}
|
|
259
264
|
/** Op-name vocabulary for a service, split by provenance so the capped
|
|
260
265
|
* discover descriptions can list curated ops and only summarize the
|
|
261
266
|
* generated long tail. */
|
|
@@ -320,7 +325,11 @@ export class ToolRegistry {
|
|
|
320
325
|
if (this.tools.length === 0) {
|
|
321
326
|
throw new Error('installListHandler() requires at least one registered tool');
|
|
322
327
|
}
|
|
323
|
-
this.server.server.setRequestHandler(
|
|
328
|
+
this.server.server.setRequestHandler('tools/list', async () => ({
|
|
329
|
+
// The wire Tool is hand-built because the SDK's own types drop the
|
|
330
|
+
// anthropic/* _meta keys honoring clients read. z.toJSONSchema emits a
|
|
331
|
+
// valid draft-7 object schema by construction, which the SDK's recursive
|
|
332
|
+
// JSON-Schema type cannot infer from our cached `unknown`.
|
|
324
333
|
tools: this.tools.filter((t) => this.isVisible(t)).map((t) => this.toToolJson(t)),
|
|
325
334
|
}));
|
|
326
335
|
}
|
package/dist/setup-prompt.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { McpServer } from
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
export interface SetupOptions {
|
|
3
3
|
/** Public base URL when serving over HTTP; used for the Web client redirect. */
|
|
4
4
|
publicUrl?: string;
|
package/dist/setup-prompt.js
CHANGED
|
@@ -47,7 +47,7 @@ export function registerSetupPrompt(server) {
|
|
|
47
47
|
server.registerPrompt('setup', {
|
|
48
48
|
title: 'Set up mcp-google-multi',
|
|
49
49
|
description: 'Guided Google Cloud Console prelude: project, APIs, consent screen, OAuth client, and credentials. The one-time browser setup that has no API.',
|
|
50
|
-
argsSchema: { publicUrl: z.string().optional().describe('Public base URL when serving over HTTP (for the Web OAuth client redirect). Omit for stdio.') },
|
|
50
|
+
argsSchema: z.object({ publicUrl: z.string().optional().describe('Public base URL when serving over HTTP (for the Web OAuth client redirect). Omit for stdio.') }),
|
|
51
51
|
}, (args) => ({
|
|
52
52
|
messages: [
|
|
53
53
|
{
|
package/dist/usage-metrics.js
CHANGED
|
@@ -18,6 +18,7 @@ const RETRY_WINDOW_MS = 10 * 60_000;
|
|
|
18
18
|
const BIGRAM_GAP_MS = 5 * 60_000;
|
|
19
19
|
const BIGRAM_CAP = 1_500;
|
|
20
20
|
const ERR_SLUG_CAP = 32;
|
|
21
|
+
const VALIDATION_CAP = 200;
|
|
21
22
|
const ESCAPE_CAP = 500;
|
|
22
23
|
const EVENTS_ROTATE_BYTES = 4 * 1024 * 1024;
|
|
23
24
|
const RETENTION_DAYS = 180;
|
|
@@ -184,6 +185,9 @@ function applyCaps(day) {
|
|
|
184
185
|
};
|
|
185
186
|
for (const t of Object.values(day.tools))
|
|
186
187
|
t.err = cap(t.err, ERR_SLUG_CAP);
|
|
188
|
+
// Bounded by the registered tool set via the tap's membership test, but
|
|
189
|
+
// capped anyway: this map is the only one fed by a wire-supplied key.
|
|
190
|
+
day.validation = cap(day.validation, VALIDATION_CAP);
|
|
187
191
|
day.escape.methods = cap(day.escape.methods, ESCAPE_CAP, (n) => { day.escape._overflow += n; });
|
|
188
192
|
day.bigrams = cap(day.bigrams, BIGRAM_CAP, (n) => { day.bigrams._overflow = (day.bigrams._overflow ?? 0) + n; });
|
|
189
193
|
return day;
|
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.31",
|
|
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",
|
|
@@ -81,7 +81,8 @@
|
|
|
81
81
|
"@googleapis/slides": "^10.0.0",
|
|
82
82
|
"@googleapis/tasks": "^17.0.0",
|
|
83
83
|
"@googleapis/webmasters": "^9.0.0",
|
|
84
|
-
"@modelcontextprotocol/
|
|
84
|
+
"@modelcontextprotocol/node": "2.0.0",
|
|
85
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
85
86
|
"googleapis-common": "^9.0.0",
|
|
86
87
|
"jose": "^6.2.8",
|
|
87
88
|
"markdown-it": "15.0.0",
|
|
@@ -115,7 +116,6 @@
|
|
|
115
116
|
},
|
|
116
117
|
"overrides": {
|
|
117
118
|
"tmp": "^0.2.4",
|
|
118
|
-
"fast-uri": "^3.1.6",
|
|
119
119
|
"hono": "^4.13.5",
|
|
120
120
|
"qs": "^6.16.0"
|
|
121
121
|
}
|