@ganju/utils 0.0.11 → 0.0.13

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.
@@ -392,6 +392,9 @@ export declare const constants: {
392
392
  TOOL_CONFIRMATION_FALLBACK_QUESTION: string;
393
393
  TOOL_CONFIRMATION_ASK_INSTRUCTION: string;
394
394
  TOOL_CONFIRMATION_SYSTEM_NOTE: string;
395
+ MCP_TOOL_CONFIRMATION_TTL_MS: number;
396
+ MCP_TOOL_CONFIRMATION_ARG: string;
397
+ MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION: string;
395
398
  CHANNEL_DEBOUNCE_DEFAULT_MS: number;
396
399
  CHANNEL_DEBOUNCE_MIN_MS: number;
397
400
  CHANNEL_DEBOUNCE_MAX_MS: number;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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"}
package/dist/constants.js CHANGED
@@ -666,6 +666,15 @@ const TOOL_CONFIRMATION_ASK_INSTRUCTION = 'IMPORTANT: these actions have NOT bee
666
666
  'waiting for the user to confirm them. Your reply must ask the user to ' +
667
667
  'confirm, and must not say or suggest that they were done, nor describe ' +
668
668
  'or make up any result from them.';
669
+ // For MCP clients, whose chat the server can't see: a sensitive call without a
670
+ // valid confirmation isn't run, and the model is told to ask the user and call
671
+ // again with a token that binds the second call to these exact arguments.
672
+ // How long that token stays good, and the argument that carries it.
673
+ const MCP_TOOL_CONFIRMATION_TTL_MS = 10 * 60 * 1000;
674
+ const MCP_TOOL_CONFIRMATION_ARG = 'confirmation';
675
+ const MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION = "Leave out on the first call. This action needs the user's yes: the " +
676
+ 'first call returns a token; after the user says yes, call again with the ' +
677
+ 'same arguments and that token here.';
669
678
  // The question when the model wrote none, above the runner's own summary.
670
679
  const TOOL_CONFIRMATION_FALLBACK_QUESTION = 'Before I do this, please confirm. Reply yes to go ahead, or no to cancel.';
671
680
  const TELEGRAM_SECRET_HEADER = 'x-telegram-bot-api-secret-token';
@@ -2297,6 +2306,9 @@ exports.constants = {
2297
2306
  TOOL_CONFIRMATION_FALLBACK_QUESTION,
2298
2307
  TOOL_CONFIRMATION_ASK_INSTRUCTION,
2299
2308
  TOOL_CONFIRMATION_SYSTEM_NOTE,
2309
+ MCP_TOOL_CONFIRMATION_TTL_MS,
2310
+ MCP_TOOL_CONFIRMATION_ARG,
2311
+ MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION,
2300
2312
  CHANNEL_DEBOUNCE_DEFAULT_MS,
2301
2313
  CHANNEL_DEBOUNCE_MIN_MS,
2302
2314
  CHANNEL_DEBOUNCE_MAX_MS,
package/dist/index.d.ts CHANGED
@@ -27,6 +27,7 @@ import type { ExtractedDocument, ExtractedDocumentMetadata, ExtractedDocumentSou
27
27
  import type { ChannelNotifier, ToolStatusEvent } from './channelNotifier';
28
28
  import type { BufferedChannelMessage, BufferedChannelCommand, ChannelBufferEnvelope, ChannelBufferFlush } from './channelDebounce';
29
29
  import type { PendingToolConfirmation, PendingToolConfirmations, ToolEffect } from './toolConfirmation';
30
+ import type { LintableTool, ToolLintFinding, ToolLintOptions, ToolLintRule } from './toolLint';
30
31
  import type { Source, ResourceUrlContext, SourceButton } from './sources';
31
32
  import { OAuthReauthRequiredError } from './oauth';
32
33
  import type { RefreshOAuthTokenInput, RefreshedOAuthToken } from './oauth';
@@ -2157,6 +2158,9 @@ export declare const utils: {
2157
2158
  TOOL_CONFIRMATION_FALLBACK_QUESTION: string;
2158
2159
  TOOL_CONFIRMATION_ASK_INSTRUCTION: string;
2159
2160
  TOOL_CONFIRMATION_SYSTEM_NOTE: string;
2161
+ MCP_TOOL_CONFIRMATION_TTL_MS: number;
2162
+ MCP_TOOL_CONFIRMATION_ARG: string;
2163
+ MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION: string;
2160
2164
  CHANNEL_DEBOUNCE_DEFAULT_MS: number;
2161
2165
  CHANNEL_DEBOUNCE_MIN_MS: number;
2162
2166
  CHANNEL_DEBOUNCE_MAX_MS: number;
@@ -3071,6 +3075,15 @@ export declare const utils: {
3071
3075
  destructiveHint: boolean;
3072
3076
  };
3073
3077
  TOOL_EFFECTS: readonly ["read", "write", "sensitive"];
3078
+ isSensitiveTool: (annotations: {
3079
+ readOnlyHint?: boolean;
3080
+ destructiveHint?: boolean;
3081
+ } | null | undefined) => boolean;
3082
+ stableJson: (value: unknown) => string;
3083
+ lintTools: (tools: LintableTool[], options?: ToolLintOptions) => ToolLintFinding[];
3084
+ TOOL_LINT_RULES: readonly ["missing-description", "short-description", "no-usage-guidance", "missing-annotations", "undescribed-input", "overlapping-tools", "too-many-tools"];
3085
+ TOOL_LINT_MAX_TOOLS: number;
3086
+ TOOL_LINT_MIN_DESCRIPTION_LENGTH: number;
3074
3087
  isResourceSourceEnabled: (resource: {
3075
3088
  showSource?: string | null;
3076
3089
  } | null | undefined) => boolean;
@@ -3095,5 +3108,5 @@ export declare const utils: {
3095
3108
  PlanLimitError: typeof PlanLimitError;
3096
3109
  isPlanLimitError: (error: unknown) => error is PlanLimitError;
3097
3110
  };
3098
- export type { MintedAccessToken, CustomCodeProject, ProjectPathIssue, CatalogGroup, CatalogTool, CatalogToolDescriptor, ToolAnnotations, PendingToolConfirmation, PendingToolConfirmations, ToolEffect, ToolGroupKey, ToolKey, CalendarConfigField, JsonSchema, SchemaViolation, MimeAttachment, MimeMessageInput, GmailOperation, GmailSendRequest, GmailSendResponse, OutlookOperation, OutlookSendRequest, OutlookSendResponse, SlackOperation, SlackSendRequest, SlackSendResponse, SlackSendRemoteResourceRequest, TelegramSendRequest, TelegramSendResponse, TelegramSendRemoteResourceRequest, DiscordSendRequest, DiscordSendResponse, DiscordSendRemoteResourceRequest, WhatsappSendRequest, WhatsappSendResponse, WhatsappSendRemoteResourceRequest, EnvSource, Separator, ChunkMetadata, PreparedChunk, ExtractedDocument, ExtractedDocumentMetadata, ExtractedDocumentSource, RateLimitRetryOptions, RerankAiBinding, QueueBatchLike, QueueMessageLike, ProcessQueueBatchHandlers, ChannelNotifier, ToolStatusEvent, BufferedChannelMessage, BufferedChannelCommand, ChannelBufferEnvelope, ChannelBufferFlush, Source, ResourceUrlContext, SourceButton, RefreshOAuthTokenInput, RefreshedOAuthToken, HttpEndpointToolConfig, McpProxyToolConfig, McpProxyDiscoveredTool, McpProxyDiscoveredResource, McpProxyDiscoveredPrompt, McpProxyDiscovery, CustomCodeToolConfig, CustomCodeToolManifest, CustomCodeManifest, CustomCodeSendFile, CustomCodeCreateResource, CustomCodeTokenPayload, ContainmentTokenPayload, OAuthProviderConfig, AttachmentResource, ResolvedAttachment, ResolveAttachmentResult, ExposableResource, SyncableResource, ResourceSyncProvider, PlanLimitDetails, PlanLimits };
3111
+ export type { MintedAccessToken, CustomCodeProject, ProjectPathIssue, CatalogGroup, CatalogTool, CatalogToolDescriptor, ToolAnnotations, PendingToolConfirmation, PendingToolConfirmations, ToolEffect, LintableTool, ToolLintFinding, ToolLintOptions, ToolLintRule, ToolGroupKey, ToolKey, CalendarConfigField, JsonSchema, SchemaViolation, MimeAttachment, MimeMessageInput, GmailOperation, GmailSendRequest, GmailSendResponse, OutlookOperation, OutlookSendRequest, OutlookSendResponse, SlackOperation, SlackSendRequest, SlackSendResponse, SlackSendRemoteResourceRequest, TelegramSendRequest, TelegramSendResponse, TelegramSendRemoteResourceRequest, DiscordSendRequest, DiscordSendResponse, DiscordSendRemoteResourceRequest, WhatsappSendRequest, WhatsappSendResponse, WhatsappSendRemoteResourceRequest, EnvSource, Separator, ChunkMetadata, PreparedChunk, ExtractedDocument, ExtractedDocumentMetadata, ExtractedDocumentSource, RateLimitRetryOptions, RerankAiBinding, QueueBatchLike, QueueMessageLike, ProcessQueueBatchHandlers, ChannelNotifier, ToolStatusEvent, BufferedChannelMessage, BufferedChannelCommand, ChannelBufferEnvelope, ChannelBufferFlush, Source, ResourceUrlContext, SourceButton, RefreshOAuthTokenInput, RefreshedOAuthToken, HttpEndpointToolConfig, McpProxyToolConfig, McpProxyDiscoveredTool, McpProxyDiscoveredResource, McpProxyDiscoveredPrompt, McpProxyDiscovery, CustomCodeToolConfig, CustomCodeToolManifest, CustomCodeManifest, CustomCodeSendFile, CustomCodeCreateResource, CustomCodeTokenPayload, ContainmentTokenPayload, OAuthProviderConfig, AttachmentResource, ResolvedAttachment, ResolveAttachmentResult, ExposableResource, SyncableResource, ResourceSyncProvider, PlanLimitDetails, PlanLimits };
3099
3112
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAzJI,CAAC;;;;;;;;;;;;;;;qBA3BF,CAAC;;;;;;;;;;;;;;;;;;;;;;kBAvFD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAkGT,CAAC;oBACH,CAAC;;;;aAsIP,CAAC;mBACgB,CAAC;WACL,CAAC;gBAEd,CAAC;gBACF,CAAC;;;mBA2CK,CAAC;oBAET,CAAA;gBAUG,CAAC;gBACO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAnPZ,CAAC;;;;;;;sBAoCgB,CAAC;;;;;;;;;;;;;;;CA6TnB,CAAC;AAEF,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACvB,wBAAwB,EACxB,UAAU,EACV,YAAY,EACZ,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EAChC,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,SAAS,EACT,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,EACN,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACX,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAvKI,CAAC;;;;;;;;;;;;;;;qBA3BF,CAAC;;;;;;;;;;;;;;;;;;;;;;kBAvFD,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAkGT,CAAC;oBACH,CAAC;;;;aAuIV,CAAA;mBACC,CAAC;WACQ,CAAC;gBACA,CAAC;gBAEd,CAAC;;;mBA0CE,CAAC;oBACG,CAAC;gBAUK,CAAC;gBACY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBA+BzB,CAAD;uBAA0B,CAAC;;;;;;;;kBAjR1B,CAAC;;;;;;;sBAoCgB,CAAC;;;;;;;;;;;;;;;CAiVnB,CAAC;AAEF,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EACvB,wBAAwB,EACxB,UAAU,EACV,YAAY,EACZ,eAAe,EACf,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC9B,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EAChC,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,SAAS,EACT,SAAS,EACT,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,yBAAyB,EACzB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,MAAM,EACN,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,EACX,CAAC"}
package/dist/index.js CHANGED
@@ -48,6 +48,7 @@ const processQueueBatch_1 = require("./processQueueBatch");
48
48
  const channelNotifier_1 = require("./channelNotifier");
49
49
  const channelDebounce_1 = require("./channelDebounce");
50
50
  const toolConfirmation_1 = require("./toolConfirmation");
51
+ const toolLint_1 = require("./toolLint");
51
52
  const sources_1 = require("./sources");
52
53
  const oauth_1 = require("./oauth");
53
54
  const vendorTimeZone_1 = require("./vendorTimeZone");
@@ -170,6 +171,12 @@ exports.utils = {
170
171
  formatConfirmationSummary: toolConfirmation_1.formatConfirmationSummary,
171
172
  annotationsForEffect: toolConfirmation_1.annotationsForEffect,
172
173
  TOOL_EFFECTS: toolConfirmation_1.TOOL_EFFECTS,
174
+ isSensitiveTool: toolConfirmation_1.isSensitiveTool,
175
+ stableJson: toolConfirmation_1.stableJson,
176
+ lintTools: toolLint_1.lintTools,
177
+ TOOL_LINT_RULES: toolLint_1.TOOL_LINT_RULES,
178
+ TOOL_LINT_MAX_TOOLS: toolLint_1.TOOL_LINT_MAX_TOOLS,
179
+ TOOL_LINT_MIN_DESCRIPTION_LENGTH: toolLint_1.TOOL_LINT_MIN_DESCRIPTION_LENGTH,
173
180
  isResourceSourceEnabled: sources_1.isResourceSourceEnabled,
174
181
  isDownloadableSource: sources_1.isDownloadableSource,
175
182
  safeHostname: sources_1.safeHostname,
@@ -44,4 +44,15 @@ export declare const formatConfirmationSummary: (calls: Array<{
44
44
  name: string;
45
45
  arguments: Record<string, unknown>;
46
46
  }>, titleOf: (name: string) => string) => string;
47
+ /**
48
+ * Whether a tool needs a yes before it runs, by the MCP defaults: read-only
49
+ * only if it says so, and a tool that changes things is destructive unless it
50
+ * says it isn't. So an unannotated custom or proxied tool is confirmed — the
51
+ * safe reading for an organization that asked to be asked.
52
+ */
53
+ export declare const isSensitiveTool: (annotations: {
54
+ readOnlyHint?: boolean;
55
+ destructiveHint?: boolean;
56
+ } | null | undefined) => boolean;
57
+ export declare const stableJson: (value: unknown) => string;
47
58
  //# sourceMappingURL=toolConfirmation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"toolConfirmation.d.ts","sourceRoot":"","sources":["../src/toolConfirmation.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,uBAAuB;IAEtC,EAAE,EAAE,MAAM,CAAC;IAGX,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC,CAAC;CACJ;AAKD,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;AAO/E,eAAO,MAAM,YAAY,yCAA0C,CAAC;AAEpE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,UAAU,KACjB;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAKE,CAAC;AAmHvD,eAAO,MAAM,uBAAuB,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,IAOhE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,MAAM,GAAG,IAAI,KACrB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAG3B,CAAC;AAQF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,KAAK,GAAG,IAAI,EACpB,uBAAuB,MAAM,KAC5B,MAA4E,CAAC;AAEhF,eAAO,MAAM,uBAAuB,GAClC,IAAI,MAAM,KACT;IAAE,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAA;CAAE,GAAG,IAQ1D,CAAC;AAWF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,KAAG,OAIlD,CAAC;AAmBF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACpC,OAAO,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EAClE,SAAS,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,KAChC,MAcc,CAAC"}
1
+ {"version":3,"file":"toolConfirmation.d.ts","sourceRoot":"","sources":["../src/toolConfirmation.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,uBAAuB;IAEtC,EAAE,EAAE,MAAM,CAAC;IAGX,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,CAAC,CAAC;CACJ;AAKD,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;AAO/E,eAAO,MAAM,YAAY,yCAA0C,CAAC;AAEpE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,UAAU,KACjB;IAAE,YAAY,EAAE,OAAO,CAAC;IAAC,eAAe,EAAE,OAAO,CAAA;CAKE,CAAC;AAmHvD,eAAO,MAAM,uBAAuB,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,IAOhE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,MAAM,GAAG,IAAI,KACrB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAG3B,CAAC;AAQF,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,KAAK,GAAG,IAAI,EACpB,uBAAuB,MAAM,KAC5B,MAA4E,CAAC;AAEhF,eAAO,MAAM,uBAAuB,GAClC,IAAI,MAAM,KACT;IAAE,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC;IAAC,qBAAqB,EAAE,MAAM,CAAA;CAAE,GAAG,IAQ1D,CAAC;AAWF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,KAAG,OAIlD,CAAC;AAmBF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACpC,OAAO,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EAClE,SAAS,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,KAChC,MAcc,CAAC;AAElB;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAC1B,aACI;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD,IAAI,GACJ,SAAS,KACZ,OAC2E,CAAC;AAI/E,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,MAY3C,CAAC"}
@@ -4,7 +4,7 @@
4
4
  // participant's next message is a plain yes. These are the pieces of that rule
5
5
  // that aren't the runner's own bookkeeping.
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.formatConfirmationSummary = exports.isConfirmationReply = exports.parseConfirmationButton = exports.confirmationButtonId = exports.confirmationButtonLabels = exports.languageFromPhoneNumber = exports.annotationsForEffect = exports.TOOL_EFFECTS = void 0;
7
+ exports.stableJson = exports.isSensitiveTool = exports.formatConfirmationSummary = exports.isConfirmationReply = exports.parseConfirmationButton = exports.confirmationButtonId = exports.confirmationButtonLabels = exports.languageFromPhoneNumber = exports.annotationsForEffect = exports.TOOL_EFFECTS = void 0;
8
8
  // What an owner says a tool they wrote does, in the words the dashboard asks
9
9
  // it in, and the MCP annotations each answer stands for: `read` only looks
10
10
  // things up, `write` changes things that can be undone, `sensitive` sends,
@@ -215,3 +215,25 @@ const formatConfirmationSummary = (calls, titleOf) => calls
215
215
  })
216
216
  .join('\n\n');
217
217
  exports.formatConfirmationSummary = formatConfirmationSummary;
218
+ /**
219
+ * Whether a tool needs a yes before it runs, by the MCP defaults: read-only
220
+ * only if it says so, and a tool that changes things is destructive unless it
221
+ * says it isn't. So an unannotated custom or proxied tool is confirmed — the
222
+ * safe reading for an organization that asked to be asked.
223
+ */
224
+ const isSensitiveTool = (annotations) => annotations?.readOnlyHint !== true && annotations?.destructiveHint !== false;
225
+ exports.isSensitiveTool = isSensitiveTool;
226
+ // JSON with keys sorted at every depth, so two argument objects that say the
227
+ // same thing serialize the same whatever order the model emitted them in.
228
+ const stableJson = (value) => {
229
+ if (Array.isArray(value))
230
+ return `[${value.map(exports.stableJson).join(',')}]`;
231
+ if (value && typeof value === 'object') {
232
+ return `{${Object.keys(value)
233
+ .sort()
234
+ .map(key => `${JSON.stringify(key)}:${(0, exports.stableJson)(value[key])}`)
235
+ .join(',')}}`;
236
+ }
237
+ return JSON.stringify(value) ?? 'null';
238
+ };
239
+ exports.stableJson = stableJson;
@@ -0,0 +1,35 @@
1
+ export interface LintableTool {
2
+ name: string;
3
+ title?: string;
4
+ description?: string;
5
+ inputSchema?: unknown;
6
+ annotations?: {
7
+ readOnlyHint?: boolean;
8
+ destructiveHint?: boolean;
9
+ idempotentHint?: boolean;
10
+ openWorldHint?: boolean;
11
+ };
12
+ }
13
+ export declare const TOOL_LINT_RULES: readonly ["missing-description", "short-description", "no-usage-guidance", "missing-annotations", "undescribed-input", "overlapping-tools", "too-many-tools"];
14
+ export type ToolLintRule = (typeof TOOL_LINT_RULES)[number];
15
+ export interface ToolLintFinding {
16
+ rule: ToolLintRule;
17
+ tool?: string;
18
+ alike?: string[];
19
+ paths?: string[];
20
+ count?: number;
21
+ limit?: number;
22
+ message: string;
23
+ }
24
+ export interface ToolLintOptions {
25
+ others?: LintableTool[];
26
+ otherCount?: number;
27
+ }
28
+ export declare const TOOL_LINT_MIN_DESCRIPTION_LENGTH = 40;
29
+ export declare const TOOL_LINT_MAX_TOOLS = 25;
30
+ /**
31
+ * Check a set of tools for what makes them hard for a model to use. Findings
32
+ * come back in the order of `tools`, then the server-wide one.
33
+ */
34
+ export declare const lintTools: (tools: LintableTool[], options?: ToolLintOptions) => ToolLintFinding[];
35
+ //# sourceMappingURL=toolLint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolLint.d.ts","sourceRoot":"","sources":["../src/toolLint.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;CACH;AAED,eAAO,MAAM,eAAe,+JAQlB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,CAAC;IAGnB,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAI9B,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAGxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAKnD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AA2ItC;;;GAGG;AACH,eAAO,MAAM,SAAS,GACpB,OAAO,YAAY,EAAE,EACrB,UAAS,eAAoB,KAC5B,eAAe,EA8FjB,CAAC"}
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ // Checks on how a tool presents itself to a model: the things that make a
3
+ // model pick the wrong tool, pass the wrong arguments, or pay for tools it never
4
+ // uses. Every finding is a warning — a tool that fails one still works, it is
5
+ // just harder for a model to use well.
6
+ //
7
+ // One rule set, run in three places: `ganju build` (with `--strict` to fail on
8
+ // any finding), the API when a custom-code version is created (returned beside
9
+ // the version, with the artifact's other tools as context), and the dashboard's
10
+ // function dialog while it is being filled in.
11
+ //
12
+ // The rule for this file: it imports nothing. The published CLI bundles it, and
13
+ // anything it imported would ship inside the CLI with it (see cliConstants.ts).
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.lintTools = exports.TOOL_LINT_MAX_TOOLS = exports.TOOL_LINT_MIN_DESCRIPTION_LENGTH = exports.TOOL_LINT_RULES = void 0;
16
+ exports.TOOL_LINT_RULES = [
17
+ 'missing-description',
18
+ 'short-description',
19
+ 'no-usage-guidance',
20
+ 'missing-annotations',
21
+ 'undescribed-input',
22
+ 'overlapping-tools',
23
+ 'too-many-tools'
24
+ ];
25
+ // Shorter than this and a description rarely says both what a tool does and
26
+ // when to call it.
27
+ exports.TOOL_LINT_MIN_DESCRIPTION_LENGTH = 40;
28
+ // Past this many tools every call carries a long list of schemas, and models
29
+ // start choosing among them badly. A channel turn hard-caps its list at 40
30
+ // (CHANNEL_MAX_TOOLS); this warns well before that.
31
+ exports.TOOL_LINT_MAX_TOOLS = 25;
32
+ // Two descriptions sharing at least this much of their vocabulary read as the
33
+ // same tool to a model. Words are weighted by how rare they are among the
34
+ // server's tools (see descriptionWeights), so tools written from one template
35
+ // — "Use when someone asks about X" — are told apart by their X.
36
+ const DESCRIPTION_OVERLAP = 0.6;
37
+ // Too few content words and two unrelated descriptions can share most of them.
38
+ const MIN_OVERLAP_WORDS = 5;
39
+ // Lowercased, accents off, so "Úsala cuando" and "usala cuando" are one text.
40
+ const fold = (text) => text
41
+ .toLowerCase()
42
+ .normalize('NFKD')
43
+ .replace(/[\u0300-\u036f]/g, '');
44
+ // Wording that tells a model WHEN to call a tool rather than only what it does
45
+ // — English, Spanish and Portuguese, the languages descriptions here are
46
+ // written in. Loose on purpose: a false "says when" costs nothing, a false
47
+ // "doesn't" nags someone whose description is fine.
48
+ const USAGE_GUIDANCE = /\b(use|uses|using|call|calls|when|whenever|before|after|instead|only if|if the user|if you|for questions|usa|usala|usalo|usar|llama|llamala|llamar|cuando|antes de|despues de|en lugar de|si el usuario|quando|use-a|utilize|chame)\b/;
49
+ const STOPWORDS = new Set([
50
+ 'the',
51
+ 'and',
52
+ 'for',
53
+ 'with',
54
+ 'from',
55
+ 'this',
56
+ 'that',
57
+ 'into',
58
+ 'your',
59
+ 'you',
60
+ 'use',
61
+ 'when',
62
+ 'call',
63
+ 'tool',
64
+ 'returns',
65
+ 'return',
66
+ 'given',
67
+ 'user',
68
+ 'about',
69
+ 'una',
70
+ 'uno',
71
+ 'los',
72
+ 'las',
73
+ 'del',
74
+ 'con',
75
+ 'por',
76
+ 'para',
77
+ 'que',
78
+ 'este',
79
+ 'esta',
80
+ 'como',
81
+ 'cuando',
82
+ 'usa',
83
+ 'herramienta',
84
+ 'devuelve',
85
+ 'usuario'
86
+ ]);
87
+ const contentWords = (text) => new Set(fold(text)
88
+ .split(/[^a-z0-9]+/)
89
+ .filter(word => word.length >= 3 && !STOPWORDS.has(word)));
90
+ // `lookupOrder`, `lookup-order` and `order_lookup` name the same thing.
91
+ const nameTokens = (name) => name
92
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
93
+ .toLowerCase()
94
+ .split(/[^a-z0-9]+/)
95
+ .filter(Boolean)
96
+ .sort()
97
+ .join(' ');
98
+ // How much each word says about which tool a description belongs to: a word
99
+ // in every description (the template, the domain) next to nothing, a word in
100
+ // one of them a lot. Smoothed, so with two tools a shared word still counts.
101
+ const descriptionWeights = (descriptions) => {
102
+ const frequency = new Map();
103
+ for (const words of descriptions) {
104
+ for (const word of words) {
105
+ frequency.set(word, (frequency.get(word) ?? 0) + 1);
106
+ }
107
+ }
108
+ const total = descriptions.length;
109
+ return word => Math.log(1 + total / (frequency.get(word) ?? 1));
110
+ };
111
+ // Jaccard, with each word counted by its weight.
112
+ const weightedJaccard = (a, b, weight) => {
113
+ let shared = 0;
114
+ let union = 0;
115
+ for (const word of a) {
116
+ union += weight(word);
117
+ if (b.has(word))
118
+ shared += weight(word);
119
+ }
120
+ for (const word of b)
121
+ if (!a.has(word))
122
+ union += weight(word);
123
+ return union === 0 ? 0 : shared / union;
124
+ };
125
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
126
+ // Every property the model may fill in that doesn't say what it is for, down
127
+ // through nested objects and arrays of objects.
128
+ const undescribedPaths = (schema, prefix = '') => {
129
+ if (!isObject(schema))
130
+ return [];
131
+ const paths = [];
132
+ if (isObject(schema.properties)) {
133
+ for (const [key, property] of Object.entries(schema.properties)) {
134
+ const path = prefix ? `${prefix}.${key}` : key;
135
+ if (!isObject(property))
136
+ continue;
137
+ const description = property.description;
138
+ if (typeof description !== 'string' || !description.trim()) {
139
+ paths.push(path);
140
+ }
141
+ paths.push(...undescribedPaths(property, path));
142
+ }
143
+ }
144
+ if (isObject(schema.items)) {
145
+ paths.push(...undescribedPaths(schema.items, `${prefix}[]`));
146
+ }
147
+ return paths;
148
+ };
149
+ const quoted = (name) => `"${name}"`;
150
+ /**
151
+ * Check a set of tools for what makes them hard for a model to use. Findings
152
+ * come back in the order of `tools`, then the server-wide one.
153
+ */
154
+ const lintTools = (tools, options = {}) => {
155
+ const others = options.others ?? [];
156
+ const findings = [];
157
+ const everyTool = [...tools, ...others];
158
+ const wordsOf = new Map(everyTool.map(tool => [tool, contentWords(tool.description ?? '')]));
159
+ const weight = descriptionWeights([...wordsOf.values()]);
160
+ const overlaps = (a, b) => {
161
+ if (nameTokens(a.name) === nameTokens(b.name))
162
+ return true;
163
+ const aWords = wordsOf.get(a);
164
+ const bWords = wordsOf.get(b);
165
+ if (aWords.size < MIN_OVERLAP_WORDS || bWords.size < MIN_OVERLAP_WORDS) {
166
+ return false;
167
+ }
168
+ return weightedJaccard(aWords, bWords, weight) >= DESCRIPTION_OVERLAP;
169
+ };
170
+ tools.forEach((tool, index) => {
171
+ const description = (tool.description ?? '').trim();
172
+ if (!description) {
173
+ findings.push({
174
+ rule: 'missing-description',
175
+ tool: tool.name,
176
+ message: `${quoted(tool.name)} has no description, so a model sees only its name when deciding whether to call it.`
177
+ });
178
+ }
179
+ else if (description.length < exports.TOOL_LINT_MIN_DESCRIPTION_LENGTH) {
180
+ findings.push({
181
+ rule: 'short-description',
182
+ tool: tool.name,
183
+ message: `${quoted(tool.name)} has a very short description. Say what it does and when to call it, in at least ${exports.TOOL_LINT_MIN_DESCRIPTION_LENGTH} characters.`
184
+ });
185
+ }
186
+ else if (!USAGE_GUIDANCE.test(fold(description))) {
187
+ findings.push({
188
+ rule: 'no-usage-guidance',
189
+ tool: tool.name,
190
+ message: `${quoted(tool.name)}'s description says what it does but not when to use it (e.g. "Use when the user asks about…").`
191
+ });
192
+ }
193
+ // Undeclared means "may change things" by the MCP defaults, so with the
194
+ // organization confirming sensitive actions every call asks first — a
195
+ // lookup included.
196
+ const hints = tool.annotations;
197
+ if (hints?.readOnlyHint === undefined &&
198
+ hints?.destructiveHint === undefined) {
199
+ findings.push({
200
+ rule: 'missing-annotations',
201
+ tool: tool.name,
202
+ message: `${quoted(tool.name)} declares no annotations, so it is treated as one that may change things and is confirmed before every call when the organization confirms sensitive actions. Set readOnlyHint: true if it only reads, or destructiveHint: false if its changes can be undone.`
203
+ });
204
+ }
205
+ const paths = undescribedPaths(tool.inputSchema);
206
+ if (paths.length > 0) {
207
+ findings.push({
208
+ rule: 'undescribed-input',
209
+ tool: tool.name,
210
+ paths,
211
+ message: `${quoted(tool.name)} has input properties with no description: ${paths.join(', ')}. A model has to guess what to pass.`
212
+ });
213
+ }
214
+ // Each pair once — against the tools after this one, then the rest of the
215
+ // server — and one finding per tool, so twenty look-alikes are nineteen
216
+ // lines rather than a hundred and ninety.
217
+ const alike = [...tools.slice(index + 1), ...others]
218
+ .filter(other => other.name !== tool.name && overlaps(tool, other))
219
+ .map(other => other.name);
220
+ if (alike.length > 0) {
221
+ findings.push({
222
+ rule: 'overlapping-tools',
223
+ tool: tool.name,
224
+ alike,
225
+ message: `${quoted(tool.name)} looks like ${alike.map(quoted).join(', ')} in name or description, so a model may call one for another. Make each description say what sets it apart.`
226
+ });
227
+ }
228
+ });
229
+ const count = tools.length + (options.otherCount ?? others.length);
230
+ if (count > exports.TOOL_LINT_MAX_TOOLS) {
231
+ findings.push({
232
+ rule: 'too-many-tools',
233
+ count,
234
+ limit: exports.TOOL_LINT_MAX_TOOLS,
235
+ message: `${count} tools are enabled on this server. Every one's schema is sent to the model on every call, and models choose worse past a few dozen — turn off the ones it doesn't need (more than ${exports.TOOL_LINT_MAX_TOOLS} is where this warns).`
236
+ });
237
+ }
238
+ return findings;
239
+ };
240
+ exports.lintTools = lintTools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganju/utils",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Shared constants, schemas and helpers for Ganju",
5
5
  "license": "Apache-2.0",
6
6
  "author": "MontoyaAndres <andresmontoyafcb@gmail.com>",
@@ -53,6 +53,11 @@
53
53
  "import": "./dist/cliConstants.js",
54
54
  "require": "./dist/cliConstants.js"
55
55
  },
56
+ "./toolLint": {
57
+ "types": "./dist/toolLint.d.ts",
58
+ "import": "./dist/toolLint.js",
59
+ "require": "./dist/toolLint.js"
60
+ },
56
61
  "./sdkConstants": {
57
62
  "types": "./dist/sdkConstants.d.ts",
58
63
  "import": "./dist/sdkConstants.js",
package/src/constants.ts CHANGED
@@ -807,6 +807,16 @@ const TOOL_CONFIRMATION_ASK_INSTRUCTION =
807
807
  'waiting for the user to confirm them. Your reply must ask the user to ' +
808
808
  'confirm, and must not say or suggest that they were done, nor describe ' +
809
809
  'or make up any result from them.';
810
+ // For MCP clients, whose chat the server can't see: a sensitive call without a
811
+ // valid confirmation isn't run, and the model is told to ask the user and call
812
+ // again with a token that binds the second call to these exact arguments.
813
+ // How long that token stays good, and the argument that carries it.
814
+ const MCP_TOOL_CONFIRMATION_TTL_MS = 10 * 60 * 1000;
815
+ const MCP_TOOL_CONFIRMATION_ARG = 'confirmation';
816
+ const MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION =
817
+ "Leave out on the first call. This action needs the user's yes: the " +
818
+ 'first call returns a token; after the user says yes, call again with the ' +
819
+ 'same arguments and that token here.';
810
820
  // The question when the model wrote none, above the runner's own summary.
811
821
  const TOOL_CONFIRMATION_FALLBACK_QUESTION =
812
822
  'Before I do this, please confirm. Reply yes to go ahead, or no to cancel.';
@@ -2669,6 +2679,9 @@ export const constants = {
2669
2679
  TOOL_CONFIRMATION_FALLBACK_QUESTION,
2670
2680
  TOOL_CONFIRMATION_ASK_INSTRUCTION,
2671
2681
  TOOL_CONFIRMATION_SYSTEM_NOTE,
2682
+ MCP_TOOL_CONFIRMATION_TTL_MS,
2683
+ MCP_TOOL_CONFIRMATION_ARG,
2684
+ MCP_TOOL_CONFIRMATION_ARG_DESCRIPTION,
2672
2685
  CHANNEL_DEBOUNCE_DEFAULT_MS,
2673
2686
  CHANNEL_DEBOUNCE_MIN_MS,
2674
2687
  CHANNEL_DEBOUNCE_MAX_MS,
package/src/index.ts CHANGED
@@ -233,13 +233,27 @@ import {
233
233
  languageFromPhoneNumber,
234
234
  formatConfirmationSummary,
235
235
  annotationsForEffect,
236
- TOOL_EFFECTS
236
+ TOOL_EFFECTS,
237
+ isSensitiveTool,
238
+ stableJson
237
239
  } from './toolConfirmation';
238
240
  import type {
239
241
  PendingToolConfirmation,
240
242
  PendingToolConfirmations,
241
243
  ToolEffect
242
244
  } from './toolConfirmation';
245
+ import {
246
+ lintTools,
247
+ TOOL_LINT_RULES,
248
+ TOOL_LINT_MAX_TOOLS,
249
+ TOOL_LINT_MIN_DESCRIPTION_LENGTH
250
+ } from './toolLint';
251
+ import type {
252
+ LintableTool,
253
+ ToolLintFinding,
254
+ ToolLintOptions,
255
+ ToolLintRule
256
+ } from './toolLint';
243
257
  import {
244
258
  isResourceSourceEnabled,
245
259
  isDownloadableSource,
@@ -387,6 +401,12 @@ export const utils = {
387
401
  formatConfirmationSummary,
388
402
  annotationsForEffect,
389
403
  TOOL_EFFECTS,
404
+ isSensitiveTool,
405
+ stableJson,
406
+ lintTools,
407
+ TOOL_LINT_RULES,
408
+ TOOL_LINT_MAX_TOOLS,
409
+ TOOL_LINT_MIN_DESCRIPTION_LENGTH,
390
410
  isResourceSourceEnabled,
391
411
  isDownloadableSource,
392
412
  safeHostname,
@@ -419,6 +439,10 @@ export type {
419
439
  PendingToolConfirmation,
420
440
  PendingToolConfirmations,
421
441
  ToolEffect,
442
+ LintableTool,
443
+ ToolLintFinding,
444
+ ToolLintOptions,
445
+ ToolLintRule,
422
446
  ToolGroupKey,
423
447
  ToolKey,
424
448
  CalendarConfigField,
@@ -259,3 +259,33 @@ export const formatConfirmationSummary = (
259
259
  return [`⏳ **${titleOf(call.name)}**`, ...lines].join('\n');
260
260
  })
261
261
  .join('\n\n');
262
+
263
+ /**
264
+ * Whether a tool needs a yes before it runs, by the MCP defaults: read-only
265
+ * only if it says so, and a tool that changes things is destructive unless it
266
+ * says it isn't. So an unannotated custom or proxied tool is confirmed — the
267
+ * safe reading for an organization that asked to be asked.
268
+ */
269
+ export const isSensitiveTool = (
270
+ annotations:
271
+ | { readOnlyHint?: boolean; destructiveHint?: boolean }
272
+ | null
273
+ | undefined
274
+ ): boolean =>
275
+ annotations?.readOnlyHint !== true && annotations?.destructiveHint !== false;
276
+
277
+ // JSON with keys sorted at every depth, so two argument objects that say the
278
+ // same thing serialize the same whatever order the model emitted them in.
279
+ export const stableJson = (value: unknown): string => {
280
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
281
+ if (value && typeof value === 'object') {
282
+ return `{${Object.keys(value)
283
+ .sort()
284
+ .map(
285
+ key =>
286
+ `${JSON.stringify(key)}:${stableJson((value as Record<string, unknown>)[key])}`
287
+ )
288
+ .join(',')}}`;
289
+ }
290
+ return JSON.stringify(value) ?? 'null';
291
+ };
@@ -0,0 +1,316 @@
1
+ // Checks on how a tool presents itself to a model: the things that make a
2
+ // model pick the wrong tool, pass the wrong arguments, or pay for tools it never
3
+ // uses. Every finding is a warning — a tool that fails one still works, it is
4
+ // just harder for a model to use well.
5
+ //
6
+ // One rule set, run in three places: `ganju build` (with `--strict` to fail on
7
+ // any finding), the API when a custom-code version is created (returned beside
8
+ // the version, with the artifact's other tools as context), and the dashboard's
9
+ // function dialog while it is being filled in.
10
+ //
11
+ // The rule for this file: it imports nothing. The published CLI bundles it, and
12
+ // anything it imported would ship inside the CLI with it (see cliConstants.ts).
13
+
14
+ // The shape all three callers can produce: a manifest entry, an http-endpoint's
15
+ // config, a proxied tool from discovery, a catalog tool.
16
+ export interface LintableTool {
17
+ name: string;
18
+ title?: string;
19
+ description?: string;
20
+ inputSchema?: unknown;
21
+ annotations?: {
22
+ readOnlyHint?: boolean;
23
+ destructiveHint?: boolean;
24
+ idempotentHint?: boolean;
25
+ openWorldHint?: boolean;
26
+ };
27
+ }
28
+
29
+ export const TOOL_LINT_RULES = [
30
+ 'missing-description',
31
+ 'short-description',
32
+ 'no-usage-guidance',
33
+ 'missing-annotations',
34
+ 'undescribed-input',
35
+ 'overlapping-tools',
36
+ 'too-many-tools'
37
+ ] as const;
38
+
39
+ export type ToolLintRule = (typeof TOOL_LINT_RULES)[number];
40
+
41
+ export interface ToolLintFinding {
42
+ rule: ToolLintRule;
43
+ // The tool the finding is about. Absent only for `too-many-tools`, which is
44
+ // about the server as a whole.
45
+ tool?: string;
46
+ // `overlapping-tools`: the tools it may be confused with. Each look-alike
47
+ // pair is reported once, on the first of the two.
48
+ alike?: string[];
49
+ // `undescribed-input`: the properties with no description, as dotted paths.
50
+ paths?: string[];
51
+ // `too-many-tools`: how many are enabled, and the number past which it warns.
52
+ count?: number;
53
+ limit?: number;
54
+ // The same finding in English, for the CLI and the API. The dashboard builds
55
+ // its own from the fields above, in the reader's language.
56
+ message: string;
57
+ }
58
+
59
+ export interface ToolLintOptions {
60
+ // The other tools on the same server — natives, http endpoints, proxied
61
+ // tools. They are checked against for overlap and counted, but never
62
+ // reported on themselves: the author of these tools can't change them.
63
+ others?: LintableTool[];
64
+ // How many tools the server exposes beside `tools`, when `others` doesn't
65
+ // list them all. Defaults to `others.length`.
66
+ otherCount?: number;
67
+ }
68
+
69
+ // Shorter than this and a description rarely says both what a tool does and
70
+ // when to call it.
71
+ export const TOOL_LINT_MIN_DESCRIPTION_LENGTH = 40;
72
+
73
+ // Past this many tools every call carries a long list of schemas, and models
74
+ // start choosing among them badly. A channel turn hard-caps its list at 40
75
+ // (CHANNEL_MAX_TOOLS); this warns well before that.
76
+ export const TOOL_LINT_MAX_TOOLS = 25;
77
+
78
+ // Two descriptions sharing at least this much of their vocabulary read as the
79
+ // same tool to a model. Words are weighted by how rare they are among the
80
+ // server's tools (see descriptionWeights), so tools written from one template
81
+ // — "Use when someone asks about X" — are told apart by their X.
82
+ const DESCRIPTION_OVERLAP = 0.6;
83
+ // Too few content words and two unrelated descriptions can share most of them.
84
+ const MIN_OVERLAP_WORDS = 5;
85
+
86
+ // Lowercased, accents off, so "Úsala cuando" and "usala cuando" are one text.
87
+ const fold = (text: string): string =>
88
+ text
89
+ .toLowerCase()
90
+ .normalize('NFKD')
91
+ .replace(/[\u0300-\u036f]/g, '');
92
+
93
+ // Wording that tells a model WHEN to call a tool rather than only what it does
94
+ // — English, Spanish and Portuguese, the languages descriptions here are
95
+ // written in. Loose on purpose: a false "says when" costs nothing, a false
96
+ // "doesn't" nags someone whose description is fine.
97
+ const USAGE_GUIDANCE =
98
+ /\b(use|uses|using|call|calls|when|whenever|before|after|instead|only if|if the user|if you|for questions|usa|usala|usalo|usar|llama|llamala|llamar|cuando|antes de|despues de|en lugar de|si el usuario|quando|use-a|utilize|chame)\b/;
99
+
100
+ const STOPWORDS = new Set([
101
+ 'the',
102
+ 'and',
103
+ 'for',
104
+ 'with',
105
+ 'from',
106
+ 'this',
107
+ 'that',
108
+ 'into',
109
+ 'your',
110
+ 'you',
111
+ 'use',
112
+ 'when',
113
+ 'call',
114
+ 'tool',
115
+ 'returns',
116
+ 'return',
117
+ 'given',
118
+ 'user',
119
+ 'about',
120
+ 'una',
121
+ 'uno',
122
+ 'los',
123
+ 'las',
124
+ 'del',
125
+ 'con',
126
+ 'por',
127
+ 'para',
128
+ 'que',
129
+ 'este',
130
+ 'esta',
131
+ 'como',
132
+ 'cuando',
133
+ 'usa',
134
+ 'herramienta',
135
+ 'devuelve',
136
+ 'usuario'
137
+ ]);
138
+
139
+ const contentWords = (text: string): Set<string> =>
140
+ new Set(
141
+ fold(text)
142
+ .split(/[^a-z0-9]+/)
143
+ .filter(word => word.length >= 3 && !STOPWORDS.has(word))
144
+ );
145
+
146
+ // `lookupOrder`, `lookup-order` and `order_lookup` name the same thing.
147
+ const nameTokens = (name: string): string =>
148
+ name
149
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
150
+ .toLowerCase()
151
+ .split(/[^a-z0-9]+/)
152
+ .filter(Boolean)
153
+ .sort()
154
+ .join(' ');
155
+
156
+ // How much each word says about which tool a description belongs to: a word
157
+ // in every description (the template, the domain) next to nothing, a word in
158
+ // one of them a lot. Smoothed, so with two tools a shared word still counts.
159
+ const descriptionWeights = (
160
+ descriptions: Set<string>[]
161
+ ): ((word: string) => number) => {
162
+ const frequency = new Map<string, number>();
163
+ for (const words of descriptions) {
164
+ for (const word of words) {
165
+ frequency.set(word, (frequency.get(word) ?? 0) + 1);
166
+ }
167
+ }
168
+ const total = descriptions.length;
169
+ return word => Math.log(1 + total / (frequency.get(word) ?? 1));
170
+ };
171
+
172
+ // Jaccard, with each word counted by its weight.
173
+ const weightedJaccard = (
174
+ a: Set<string>,
175
+ b: Set<string>,
176
+ weight: (word: string) => number
177
+ ): number => {
178
+ let shared = 0;
179
+ let union = 0;
180
+ for (const word of a) {
181
+ union += weight(word);
182
+ if (b.has(word)) shared += weight(word);
183
+ }
184
+ for (const word of b) if (!a.has(word)) union += weight(word);
185
+ return union === 0 ? 0 : shared / union;
186
+ };
187
+
188
+ const isObject = (value: unknown): value is Record<string, unknown> =>
189
+ typeof value === 'object' && value !== null && !Array.isArray(value);
190
+
191
+ // Every property the model may fill in that doesn't say what it is for, down
192
+ // through nested objects and arrays of objects.
193
+ const undescribedPaths = (schema: unknown, prefix = ''): string[] => {
194
+ if (!isObject(schema)) return [];
195
+ const paths: string[] = [];
196
+ if (isObject(schema.properties)) {
197
+ for (const [key, property] of Object.entries(schema.properties)) {
198
+ const path = prefix ? `${prefix}.${key}` : key;
199
+ if (!isObject(property)) continue;
200
+ const description = property.description;
201
+ if (typeof description !== 'string' || !description.trim()) {
202
+ paths.push(path);
203
+ }
204
+ paths.push(...undescribedPaths(property, path));
205
+ }
206
+ }
207
+ if (isObject(schema.items)) {
208
+ paths.push(...undescribedPaths(schema.items, `${prefix}[]`));
209
+ }
210
+ return paths;
211
+ };
212
+
213
+ const quoted = (name: string) => `"${name}"`;
214
+
215
+ /**
216
+ * Check a set of tools for what makes them hard for a model to use. Findings
217
+ * come back in the order of `tools`, then the server-wide one.
218
+ */
219
+ export const lintTools = (
220
+ tools: LintableTool[],
221
+ options: ToolLintOptions = {}
222
+ ): ToolLintFinding[] => {
223
+ const others = options.others ?? [];
224
+ const findings: ToolLintFinding[] = [];
225
+
226
+ const everyTool = [...tools, ...others];
227
+ const wordsOf = new Map(
228
+ everyTool.map(tool => [tool, contentWords(tool.description ?? '')])
229
+ );
230
+ const weight = descriptionWeights([...wordsOf.values()]);
231
+ const overlaps = (a: LintableTool, b: LintableTool): boolean => {
232
+ if (nameTokens(a.name) === nameTokens(b.name)) return true;
233
+ const aWords = wordsOf.get(a)!;
234
+ const bWords = wordsOf.get(b)!;
235
+ if (aWords.size < MIN_OVERLAP_WORDS || bWords.size < MIN_OVERLAP_WORDS) {
236
+ return false;
237
+ }
238
+ return weightedJaccard(aWords, bWords, weight) >= DESCRIPTION_OVERLAP;
239
+ };
240
+
241
+ tools.forEach((tool, index) => {
242
+ const description = (tool.description ?? '').trim();
243
+
244
+ if (!description) {
245
+ findings.push({
246
+ rule: 'missing-description',
247
+ tool: tool.name,
248
+ message: `${quoted(tool.name)} has no description, so a model sees only its name when deciding whether to call it.`
249
+ });
250
+ } else if (description.length < TOOL_LINT_MIN_DESCRIPTION_LENGTH) {
251
+ findings.push({
252
+ rule: 'short-description',
253
+ tool: tool.name,
254
+ message: `${quoted(tool.name)} has a very short description. Say what it does and when to call it, in at least ${TOOL_LINT_MIN_DESCRIPTION_LENGTH} characters.`
255
+ });
256
+ } else if (!USAGE_GUIDANCE.test(fold(description))) {
257
+ findings.push({
258
+ rule: 'no-usage-guidance',
259
+ tool: tool.name,
260
+ message: `${quoted(tool.name)}'s description says what it does but not when to use it (e.g. "Use when the user asks about…").`
261
+ });
262
+ }
263
+
264
+ // Undeclared means "may change things" by the MCP defaults, so with the
265
+ // organization confirming sensitive actions every call asks first — a
266
+ // lookup included.
267
+ const hints = tool.annotations;
268
+ if (
269
+ hints?.readOnlyHint === undefined &&
270
+ hints?.destructiveHint === undefined
271
+ ) {
272
+ findings.push({
273
+ rule: 'missing-annotations',
274
+ tool: tool.name,
275
+ message: `${quoted(tool.name)} declares no annotations, so it is treated as one that may change things and is confirmed before every call when the organization confirms sensitive actions. Set readOnlyHint: true if it only reads, or destructiveHint: false if its changes can be undone.`
276
+ });
277
+ }
278
+
279
+ const paths = undescribedPaths(tool.inputSchema);
280
+ if (paths.length > 0) {
281
+ findings.push({
282
+ rule: 'undescribed-input',
283
+ tool: tool.name,
284
+ paths,
285
+ message: `${quoted(tool.name)} has input properties with no description: ${paths.join(', ')}. A model has to guess what to pass.`
286
+ });
287
+ }
288
+
289
+ // Each pair once — against the tools after this one, then the rest of the
290
+ // server — and one finding per tool, so twenty look-alikes are nineteen
291
+ // lines rather than a hundred and ninety.
292
+ const alike = [...tools.slice(index + 1), ...others]
293
+ .filter(other => other.name !== tool.name && overlaps(tool, other))
294
+ .map(other => other.name);
295
+ if (alike.length > 0) {
296
+ findings.push({
297
+ rule: 'overlapping-tools',
298
+ tool: tool.name,
299
+ alike,
300
+ message: `${quoted(tool.name)} looks like ${alike.map(quoted).join(', ')} in name or description, so a model may call one for another. Make each description say what sets it apart.`
301
+ });
302
+ }
303
+ });
304
+
305
+ const count = tools.length + (options.otherCount ?? others.length);
306
+ if (count > TOOL_LINT_MAX_TOOLS) {
307
+ findings.push({
308
+ rule: 'too-many-tools',
309
+ count,
310
+ limit: TOOL_LINT_MAX_TOOLS,
311
+ message: `${count} tools are enabled on this server. Every one's schema is sent to the model on every call, and models choose worse past a few dozen — turn off the ones it doesn't need (more than ${TOOL_LINT_MAX_TOOLS} is where this warns).`
312
+ });
313
+ }
314
+
315
+ return findings;
316
+ };