import { type Token } from '@frontmcp/di'; import { type ServerCapabilities } from '@frontmcp/protocol'; import { type EntryOwnerRef, type ToolEntry, type ToolRecord, type ToolType } from '../common'; import type ProviderRegistry from '../provider/provider.registry'; import { RegistryAbstract, type RegistryBuildMapResult } from '../regsitry'; import { type ToolChangeEvent } from './tool.events'; import { ToolInstance } from './tool.instance'; import { type ExportNameOptions } from './tool.types'; export default class ToolRegistry extends RegistryAbstract { /** Who owns this registry (used for provenance). Optional. */ owner: EntryOwnerRef; /** Tools truly owned/constructed by THIS registry (with lineage applied) */ private localRows; /** Adopted tool rows from each child registry (references to the same instances) */ private adopted; /** Children registries that we track */ private children; /** Remote app tools tracked by app ID for efficient cleanup during re-adoption */ private remoteAppTools; private byQualifiedId; private byName; private byOwnerAndName; private byProviderAndName; private byOwner; private version; private emitter; constructor(providers: ProviderRegistry, list: ToolType[], owner: EntryOwnerRef); protected buildMap(list: ToolType[]): RegistryBuildMapResult; protected buildGraph(): void; protected initialize(): Promise; /** * Adopt tools from a child registry. Parent runs after children are ready. * We *reference* the child's tool instances; no duplicates are created. * * IMPORTANT: * - Child rows already include the child's own lineage (e.g., adapter:openapi). * - Here we only prepend the **parent's** owner, to avoid double-prefixing the child. */ adoptFromChild(child: ToolRegistry, _childOwner: EntryOwnerRef): void; /** * Type guard to check if an object has the ToolRegistry subscribe interface. * Used for duck-typing remote registries that may not be full ToolRegistry instances. */ private hasSubscribeInterface; /** * Adopt tools directly from a remote app's tools registry. * Remote apps expose tools via proxy entries that forward execution to the remote server. * This also subscribes to updates from the remote app's registry for lazy-loaded tools. */ private adoptToolsFromRemoteApp; /** * Adopt tools from a local app's child ToolRegistry instances. * Local apps use the hierarchical registry pattern for tool discovery. */ private adoptToolsFromLocalApp; getTools(includeHidden?: boolean): ToolEntry[]; /** * Get tools appropriate for MCP listing based on client elicitation support. * * Filters applied: * - Excludes `visibility !== 'public'` tools (hidden + internal). Hidden tools * remain externally callable by name; internal tools do not (gate enforced * in the call-tool flow). * - Adds the sendElicitationResult fallback tool when the client doesn't * support standard elicitation. The fallback is itself `visibility: 'hidden'` * (it must remain externally callable), so the elicitation lookup uses * `getTools(true)` to bypass the visibility filter for that one case. * * @param supportsElicitation - Whether the client supports MCP elicitation (from session payload) * @returns Tools appropriate for this client */ getToolsForListing(supportsElicitation?: boolean): ToolEntry[]; getInlineTools(): ToolEntry[]; /** Internal snapshot of effective indexed rows (locals + adopted). */ private listAllIndexed; private reindex; /** List all instances (locals + adopted). */ listAllInstances(): readonly ToolEntry[]; /** List instances by owner path (e.g. "app:Portal/plugin:Okta") */ listByOwner(ownerPath: string): readonly ToolEntry[]; /** * Produce unique, MCP-valid exported names. * - Base (standardized) = metadata.name cased (snake/camel/kebab/dot) * - If conflicting: * - Locals keep bare base (unless >1 locals conflict → they get owner prefixes) * - Children with same base get prefixed by providerId (or owner path) */ exportResolvedNames(opts?: ExportNameOptions): Array<{ name: string; instance: ToolEntry; }>; /** Lookup by the exported (resolved) name. */ getExported(name: string, opts?: ExportNameOptions): ToolEntry | undefined; subscribe(opts: { immediate?: boolean; filter?: (i: ToolEntry) => boolean; }, cb: (evt: ToolChangeEvent) => void): () => void; private bump; /** Build an IndexedTool row */ private makeRow; /** Clone a child row and prepend lineage (with adjacent de-dup to avoid double prefixes). */ private relineage; /** Best-effort provider id used for prefixing (inspects class metadata if present). */ private providerIdOf; /** True if this registry (or adopted children) has any tools. */ hasAny(): boolean; /** * Replace all tools owned by the given owner. * Clears local rows, rebuilds from new list, reindexes, and emits 'reset'. * Used by adapter polling to hot-swap tools when specs change. */ replaceAll(list: ToolType[], owner: EntryOwnerRef): void; /** * Type guard to check if a ToolEntry has the required properties for registration. * Validates that the tool has record with provide, kind, metadata, and a valid name. */ private isRegisterableTool; /** * Register an existing ToolInstance directly (for agent-scoped or remote tools). * This allows pre-constructed tool instances to be added without going through * the standard token-based initialization flow. * * **IMPORTANT: Scope Binding** * The ToolInstance captures its scope and providers at construction time. * The tool will use the scope from its original `providers` argument, NOT this registry's scope. * Therefore, you must create the ToolInstance with the correct providers for the target scope: * * ```typescript * // Correct: Create with agent's providers * const tool = new ToolInstance(record, agentProviders, owner); * agentTools.registerToolInstance(tool); * * // For remote tools: Use RemoteToolInstance * const remoteTool = new RemoteToolInstance(record, mcpClient, providers, owner); * remoteTools.registerToolInstance(remoteTool); * * // Wrong: Reusing app-scoped tool in agent context * // The tool will still use app's scope/hooks, not agent's! * agentTools.registerToolInstance(existingAppTool); * ``` * * @param tool - The tool instance to register (ToolInstance or RemoteToolInstance) * @throws Error if tool is not a valid instance */ registerToolInstance(tool: ToolEntry): void; /** * Unregister a tool instance previously added via `registerToolInstance`. * Returns true if the token was found and removed, false otherwise. * * Used by dynamic registrants (e.g. the OpenAPI internal-tool adapter) when * a bundle swap drops or replaces operations. The instance map and indexed * rows are kept in sync; downstream consumers see the change after `reindex`. */ unregisterToolInstance(token: Token): boolean; /** * Get the MCP capabilities for tools. * These are reported to clients during initialization. * * Issue #407: we always advertise the `tools` capability so the MCP SDK * accepts the always-registered `tools/list` handler (see * `createMcpHandlers` — registering a handler without the matching * capability throws `Server does not support tools (required for tools/list)`). * `listChanged` still reflects whether anything is actually registered. */ getCapabilities(): Partial; } //# sourceMappingURL=tool.registry.d.ts.map