@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.
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +12 -0
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/toolConfirmation.d.ts +11 -0
- package/dist/toolConfirmation.d.ts.map +1 -1
- package/dist/toolConfirmation.js +23 -1
- package/dist/toolLint.d.ts +35 -0
- package/dist/toolLint.d.ts.map +1 -0
- package/dist/toolLint.js +240 -0
- package/package.json +6 -1
- package/src/constants.ts +13 -0
- package/src/index.ts +25 -1
- package/src/toolConfirmation.ts +30 -0
- package/src/toolLint.ts +316 -0
package/dist/constants.d.ts
CHANGED
|
@@ -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;
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AA6JA,QAAA,MAAM,yBAAyB,EAAiB,UAAU,CAAC;AAC3D,QAAA,MAAM,+BAA+B,EAAuB,gBAAgB,CAAC;AAC7E,QAAA,MAAM,8BAA8B,EAAsB,eAAe,CAAC;AAC1E,QAAA,MAAM,wBAAwB,EAAgB,SAAS,CAAC;AAuCxD,QAAA,MAAM,sBAAsB,EAAiB,UAAU,CAAC;AACxD,QAAA,MAAM,4BAA4B,EAAuB,gBAAgB,CAAC;AAC1E,QAAA,MAAM,oBAAoB,EAAe,QAAQ,CAAC;AAClD,QAAA,MAAM,oBAAoB,EAAe,QAAQ,CAAC;AAoFlD,QAAA,MAAM,4BAA4B,EAAc,OAAO,CAAC;AACxD,QAAA,MAAM,6BAA6B,EAAe,QAAQ,CAAC;AAmV3D,QAAA,MAAM,sBAAsB,EAAkB,WAAW,CAAC;AAC1D,QAAA,MAAM,mBAAmB,EAAe,QAAQ,CAAC;AAGjD,QAAA,MAAM,mBAAmB,EAAe,QAAQ,CAAC;
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,wBAAwB,EACxB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACzB,MAAM,UAAU,CAAC;AAElB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAyCvD,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,8BAA8B,EAC/B,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,gCAAgC,EACjC,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,UAAU,EAGX,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAU9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAKhE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAG5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAM3D,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAW5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,cAAc,CAAC;AAmBtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AASvD,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,OAAO,EACR,MAAM,eAAe,CAAC;AAiBvB,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAI1E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAErD,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAChB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACV,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAO1E,OAAO,KAAK,EACV,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EACnB,MAAM,mBAAmB,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"}
|
package/dist/toolConfirmation.js
CHANGED
|
@@ -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"}
|
package/dist/toolLint.js
ADDED
|
@@ -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.
|
|
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,
|
package/src/toolConfirmation.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/toolLint.ts
ADDED
|
@@ -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
|
+
};
|