import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js"; import type { ConnectionConfig, DirectConnectionConfig } from "../../config/models.js"; import type { BaseClientManager } from "../../confluent/base-client-manager.js"; import { CallToolResult } from "../../confluent/schema.js"; import { ConnectionPredicate, PredicateResult } from "../../confluent/tools/connection-predicates.js"; import { ToolName } from "../../confluent/tools/tool-name.js"; import { ServerRuntime } from "../../server-runtime.js"; import { z, ZodRawShape } from "zod"; /** * Standard MCP tool annotations. * Tools should reference these constants rather than creating ad-hoc instances. */ export declare const READ_ONLY: ToolAnnotations; export declare const CREATE_UPDATE: ToolAnnotations; export declare const DESTRUCTIVE: ToolAnnotations; /** * Opening clause of the injected `connectionId` parameter description, before the * quoted, comma-separated list of viable connection ids (see * {@linkcode quoteJoinIds}) is appended. */ export declare const CONNECTION_ID_DESCRIPTION_PREFIX = "Which configured connection to target. One of: "; /** * Sentence appended to the `connectionId` description that steers the agent at the * discovery tool. Emitted only when that tool is itself reachable; the joining space * is added at the call site so a blocked tool leaves no dangling trailing space. */ export declare const LIST_CONFIGURED_CONNECTIONS_POINTER = "Discover connections and learn what tools are supported by each connection by invoking 'list-configured-connections'."; /** * Operator-facing taxonomy of tool kinds. * * Answers "what kind of tool is this?" — orthogonal to the * {@linkcode ConnectionPredicate}-based "is this tool enabled?" question that * lives next door. Predicates gate advertisement; categories classify intent. */ export declare enum ToolCategory { Billing = "billing", Catalog = "catalog", ConfluentCloud = "confluent-cloud", Connect = "connect", Docs = "docs", Flink = "flink", Kafka = "kafka", McpServerDiagnostics = "mcp-server-diagnostics", Metrics = "metrics", SchemaRegistry = "schema-registry", Tableflow = "tableflow" } export interface ToolHandler { /** Handle a tool invocation. */ handle(runtime: ServerRuntime, toolArguments: Record | undefined, sessionId?: string): Promise | CallToolResult; /** * Produce the actual config registered with MCP for this tool, injecting a * required `connectionId` parameter when the server holds more than one * connection. See {@linkcode BaseToolHandler.getRegisteredToolConfig} for the * rules. * * This method is called when setting up the MCP server, and we ourselves make * the downcall to the handler-authored `getToolConfig()`. */ getRegisteredToolConfig(runtime: ServerRuntime): RegisteredToolConfig; /** * The connection predicate that gates this tool's enablement. Lifted onto * the interface so the MCP tool-call wrapper can compare it against * `alwaysEnabled` to skip the OAuth login gate for tools that don't touch * the client manager (currently the doc-lookup tools). See * {@linkcode BaseToolHandler.predicate} for the rules around setting it. */ readonly predicate: ConnectionPredicate; /** * The {@linkcode ToolCategory} this tool belongs to — operator-facing * taxonomy, orthogonal to {@linkcode predicate}. See * {@linkcode BaseToolHandler.category} for the rules around setting it. */ readonly category: ToolCategory; /** * IDs of connections that satisfy this tool's service requirements. A * non-empty result enables the tool; an empty result disables it. Always a * subset of `runtime.config.connections` keys. * * Implementations that extend {@linkcode BaseToolHandler} (the standard * path for every tool in this codebase) must not override this — declare * a {@linkcode BaseToolHandler.predicate} property and let the base class * derive the result. */ enabledConnectionIds(runtime: ServerRuntime): string[]; /** * Per-connection verdict map for this tool: `{ enabled: true }` for * connections that satisfy its requirements, `{ enabled: false; reason }` * for those that don't. Powers startup-log grouping and the * `describe-tool-availability` diagnostic tool. */ connectionVerdicts(runtime: ServerRuntime): Map; /** * Whether this tool is enabled irrespective of the configured connections. * See {@linkcode BaseToolHandler.isConnectionIndependent}. */ readonly isConnectionIndependent: boolean; /** * The connection id this invocation routes to, or `undefined` when it routes * to none. See {@linkcode BaseToolHandler.resolvedTargetConnectionId}. */ resolvedTargetConnectionId(runtime: ServerRuntime, toolArguments: Record | undefined): string | undefined; } export interface ToolConfig { name: ToolName; description: string; inputSchema: ZodRawShape; annotations: ToolAnnotations; } /** * The config actually registered with MCP, produced by * {@linkcode BaseToolHandler.getRegisteredToolConfig}. Differs from the * handler-authored {@linkcode ToolConfig} in that `inputSchema` is a resolved, * strict Zod object rather than a bare shape: strict so unknown parameters are * rejected (not silently stripped) before `handle()` runs, and resolved because * a multi-connection server injects the required `connectionId` enum into it. */ export interface RegisteredToolConfig extends Omit { inputSchema: z.ZodObject; } /** * What the connection-resolution helpers hand back: the chosen connection id, * its resolved config, and the client manager bound to it. * * Generic over the connection arm so the type-neutral resolvers * ({@linkcode BaseToolHandler.resolveConnection}) and the direct-narrowing one * ({@linkcode BaseToolHandler.resolveDirectConnection}) share a single name — * the latter instantiates it via the {@link ResolvedDirectConnection} alias. */ export interface ResolvedConnection { connId: string; conn: C; clientManager: BaseClientManager; } /** * A {@link ResolvedConnection} pre-narrowed to the direct arm — the return type * of {@linkcode BaseToolHandler.resolveDirectConnection}. */ export type ResolvedDirectConnection = ResolvedConnection; export declare abstract class BaseToolHandler implements ToolHandler { abstract handle(runtime: ServerRuntime, toolArguments: Record | undefined, sessionId?: string): Promise | CallToolResult; protected abstract getToolConfig(): ToolConfig; /** * Produce the actual config registered with MCP for this tool. * * * If the tool is connection-oriented and the server is configured * with multiple connections, injects a required `connectionId` parameter * indicating which connection the tool call should route to. * See {@linkcode registeredInputShape} for more details. * * * Wrap the input schema in `z.object(...).strict()` to uniformly reject * unknown parameters before `handle()` runs. * * This method is called for each active tool when setting up the MCP server. * * @final — concrete on `BaseToolHandler`; subclasses must not override. */ getRegisteredToolConfig(runtime: ServerRuntime): RegisteredToolConfig; /** * The input shape to register, before strict-wrapping: the authored shape, * plus a required `connectionId` enum when the server holds more than one * connection AND this tool is connection-oriented. A single-element enum (when * only one connection is viable) is still injected on a multi-connection server, * so that an explicit request for a non-viable connection (e.g. to a disabled * tool because it is read_only) is rejected by the enum rather than silently * auto-routed. */ private registeredInputShape; /** * The connection predicate that gates this tool. The single customization * seam for tool enablement — declared as a one-line readonly property: * * readonly predicate = hasKafka; // base predicate * readonly predicate = kafkaBootstrapOrOAuth; // named composite * readonly predicate = alwaysEnabled; // no requirement * * **Must reference a named export from `connection-predicates.ts`.** Do * not compose with `allOf(...)` or `widenForOAuth(...)` at the use site. * If no existing named export expresses the gate you need, add one — * with a per-predicate test in `connection-predicates.test.ts` matching * the depth of the existing `hasKafka` / `flinkWithTelemetry` blocks — * and reference that. Enforced mechanically: the `predicate property` * block in `tool-registry.test.ts` maintains an explicit * `Readonly>` (`EXPECTED_PREDICATES`) * pinning every tool to its expected predicate. The `Record` over * `ToolName` makes exhaustiveness a compile-time check (a missing tool * is a `tsc` error, not a runtime one), and the value type rejects * combinators at compile time. The `it.each` over the record fails with * the offending tool name in the row label whenever a handler's * `predicate` drifts from the table. New tools or rewires are a one-line * table edit. * * Both {@linkcode enabledConnectionIds} and {@linkcode connectionVerdicts} * are derived from this property and are marked `@final`; never override * either method. The retired iteration helpers `connectionIdsWhere` and * `connectionReasonsWhere` are no longer exported — the base class walks * `runtime.config.connections` for you. */ abstract readonly predicate: ConnectionPredicate; /** * The {@linkcode ToolCategory} this tool belongs to — operator-facing * taxonomy answering "what kind of tool is this?" Orthogonal to * {@linkcode predicate} (which gates advertisement); category classifies * intent for grouping in diagnostic surfaces and AI-client UX. */ abstract readonly category: ToolCategory; /** * IDs of connections that satisfy this tool's {@linkcode predicate} and are * not blocked by the read-only overlay. A non-empty result enables the tool; * an empty result disables it. Derived from {@linkcode connectionVerdicts} so * the two stay in lockstep. * * Customize tool gating by declaring {@linkcode predicate}, never by * replacing this derivation. * * @final — concrete on `BaseToolHandler`; subclasses must not override. */ enabledConnectionIds(runtime: ServerRuntime): string[]; /** * Whether this tool is enabled regardless of the configured connections — * true exactly when its {@linkcode predicate} is `alwaysEnabled`, the only * predicate that returns enabled without inspecting a connection. Such tools * (docs lookup, server diagnostics) never route to a connection, so they * register even on a zero-connection config, where the per-connection verdict * map driving {@linkcode enabledConnectionIds} is empty. */ get isConnectionIndependent(): boolean; /** * The connection id this invocation routes to — the OAuth-login gate uses it * to launch the browser flow only when a call actually targets the OAuth * connection, not merely because one exists somewhere in the config. * * Returns `undefined` for a connection-independent tool — it routes to no * connection, so there is nothing to pre-launch a login for. * * Otherwise delegates to {@linkcode resolveConnection} so routing is decided * in one place rather than duplicated in the gate, and propagates its throw. * Every throw path there is a `"Wacky --"` invariant violation: the injected * schema (see {@linkcode getRegisteredToolConfig}) makes `connectionId` * required for multi-connection tools and strips it for single-connection * ones, so a call that can't resolve means that contract broke. Surfacing the * error rather than swallowing it to `undefined` lets the broken invariant * reach the caller instead of being silently re-discovered inside `handle()`. * * @final — concrete on `BaseToolHandler`; subclasses must not override. */ resolvedTargetConnectionId(runtime: ServerRuntime, toolArguments: Record | undefined): string | undefined; /** * Per-connection verdict map for this tool — the single source of truth for * enablement. Each verdict composes the {@linkcode predicate} with the * read-only overlay (see {@linkcode connectionVerdict}). Powers grouped * startup logging, the diagnostic-tool surface, and * {@linkcode enabledConnectionIds}. * * Customize tool gating by declaring {@linkcode predicate}, never by * replacing this derivation. * * @final — concrete on `BaseToolHandler`; subclasses must not override. */ connectionVerdicts(runtime: ServerRuntime): Map; /** * Single-connection verdict composing the two gating layers: the * {@linkcode predicate} (service reachability) and the read-only overlay. * * A connection the predicate already disables keeps that verdict unchanged — * service reachability is the more fundamental reason and wins, so a * connection missing the service block is never reported as "read-only * blocked". Otherwise, a `read_only` connection disables any tool that * mutates state (`annotations.readOnlyHint !== true`). */ private connectionVerdict; /** * Resolves which connection a tool call targets: the explicit `connectionId` * argument when present, else the sole connection enabled for this tool. * Throws a listing error when the id is unknown/not-enabled, when it is * present but not a string, or when omitted while two or more connections are * candidates (the augmented schema makes all three unreachable in normal MCP * flow — these are defensive backstops against internal call sites). * * Reads `connectionId` off the **raw** `toolArguments` rather than off the * handler's locally-parsed object: the framework already validated it against * the registered schema (see {@linkcode getRegisteredToolConfig}), and a * handler's own `z.object` re-`parse()` would strip the key it never declared. * * @final — concrete on `BaseToolHandler`; subclasses must not override. */ protected resolveConnection(runtime: ServerRuntime, toolArguments: Record | undefined): ResolvedConnection; /** * Like {@linkcode resolveConnection}, but narrows the resolved connection to * a {@link DirectConnectionConfig} — throwing if the addressed connection is * OAuth-typed. The throw is a defensive backstop: a tool's `connectionId` * enum only offers connections its predicate enables, and the direct-block * predicates exclude OAuth, so an OAuth connection is unreachable here via * normal MCP flow. * * @final — concrete on `BaseToolHandler`; subclasses must not override. */ protected resolveDirectConnection(runtime: ServerRuntime, toolArguments: Record | undefined): ResolvedDirectConnection; /** * Resolves a required string from an explicit tool argument, falling back to * a connection-config value. Throws if neither is present. * `label` is the human-readable field name (e.g. `"Organization ID"`); * the thrown message is `"${label} is required"`. * The returned value is always trimmed. */ protected resolveParam(argValue: string | undefined, configValue: string | undefined, label: string): string; /** Like resolveParam but returns undefined instead of throwing when both are absent or blank. The returned value is always trimmed. */ protected resolveOptionalParam(argValue: string | undefined, configValue: string | undefined): string | undefined; createResponse(message: string, isError?: boolean, _meta?: Record): CallToolResult; /** * Variant of {@link createResponse} that surfaces the response's * machine-readable payload via MCP's `structuredContent` channel (per the * [2025-11-25 spec](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)). * The `message` carries the human-readable summary on `content`; the * `structuredContent` field carries the typed JSON payload callers (or * `outputSchema` validators) can consume directly. */ createStructuredResponse(message: string, structuredContent: Record): CallToolResult; } //# sourceMappingURL=base-tools.d.ts.map