"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports._setResourcesForTest = _setResourcesForTest;
exports._setRequestTargetForTest = _setRequestTargetForTest;
exports._resetCustomToolWarningsForTest = _resetCustomToolWarningsForTest;
exports.registerApplicationTools = registerApplicationTools;
/**
 * Application-profile tool generation. Walks Harper's `Resources` registry
 * and registers verb tools (`get_*`, `search_*`, `create_*`, `update_*`,
 * `delete_*`) for each exported Resource that:
 *   1. Has a `'mcp'` exportType not explicitly set to `false`.
 *   2. Implements the corresponding verb on its prototype.
 *
 * Tool dispatch delegates to the static `Resource.get/post/put/delete/search`
 * methods, which wrap `transactional()` internally. That means
 * `allowRead/allowCreate/allowUpdate/allowDelete` per-record predicates run
 * unchanged, and restricted-attribute writes are rejected at runtime by
 * `Table.allowUpdate` even when the input schema permits them — defense in
 * depth.
 */
const node_crypto_1 = require("node:crypto");
const env = __importStar(require("../../../utility/environment/environmentManager.js"));
const hdbTerms_ts_1 = require("../../../utility/hdbTerms.js");
const harper_logger_ts_1 = __importDefault(require("../../../utility/logging/harper_logger.js"));
const toolRegistry_ts_1 = require("../toolRegistry.js");
const derive_ts_1 = require("./schemas/derive.js");
// Test seams: avoid Harper's eager graph init in unit tests.
let _resourcesOverride;
let _requestTargetCtorOverride;
function _setResourcesForTest(r) {
    _resourcesOverride = r;
}
function _setRequestTargetForTest(ctor) {
    _requestTargetCtorOverride = ctor;
}
function loadResources() {
    if (_resourcesOverride)
        return _resourcesOverride;
    try {
        return require('../../../resources/Resources').resources;
    }
    catch (err) {
        harper_logger_ts_1.default.trace(`MCP application tools: Resources registry unavailable (${err.message})`);
        return undefined;
    }
}
function loadRequestTarget() {
    if (_requestTargetCtorOverride)
        return _requestTargetCtorOverride;
    try {
        return require('../../../resources/RequestTarget').RequestTarget;
    }
    catch {
        // Tests that don't use the real RequestTarget can supply a fake.
        return undefined;
    }
}
const DEFAULT_SEARCH_LIMIT = 100;
const MAX_SEARCH_LIMIT = 1000;
function searchLimitFor(_profile) {
    const v = env.get(hdbTerms_ts_1.CONFIG_PARAMS.MCP_APPLICATION_MAXTOOLS); // existing knob; treat as a sane upper bound
    if (typeof v === 'number' && v > 0)
        return Math.min(v, MAX_SEARCH_LIMIT);
    return DEFAULT_SEARCH_LIMIT;
}
/**
 * Sanitize a Resource path into a valid MCP tool-name segment.
 * MCP tool names must be JSON-encodable identifiers; `/` and `.` are the
 * common offenders. Anything else non-identifier-friendly gets dropped.
 */
function sanitizePath(path) {
    return path.replace(/[/.]/g, '_').replace(/[^a-zA-Z0-9_]/g, '');
}
/**
 * Resolve a unique tool-name suffix for this Resource. Default is the
 * sanitized path; on collision we prefix with the database name; on second
 * collision we add a short hash. The collision recovery is deterministic
 * so two boots produce the same tool names.
 */
function uniqueSuffix(path, databaseName, claimed) {
    const base = sanitizePath(path);
    if (!claimed.has(base))
        return base;
    if (databaseName) {
        const dbPrefixed = `${sanitizePath(databaseName)}_${base}`;
        if (!claimed.has(dbPrefixed))
            return dbPrefixed;
    }
    // Final fallback: append a short stable hash of the full path.
    const hash = (0, node_crypto_1.createHash)('sha256').update(path).digest('hex').slice(0, 6);
    return `${base}_${hash}`;
}
function getUserTablePermissions(user, databaseName, tableName) {
    if (user?.role?.permission?.super_user === true) {
        return { read: true, insert: true, update: true, delete: true, describe: true };
    }
    if (!databaseName || !tableName)
        return null;
    const dbPerm = user?.role?.permission?.[databaseName];
    if (!dbPerm || typeof dbPerm !== 'object' || Array.isArray(dbPerm))
        return null;
    const tablePerm = dbPerm.tables?.[tableName];
    if (!tablePerm || typeof tablePerm !== 'object')
        return null;
    return tablePerm;
}
function buildContext(user) {
    return {
        user,
        authorize: true,
        checkPermission: user?.role?.permission ?? {},
    };
}
function makeTarget() {
    const Ctor = loadRequestTarget();
    if (!Ctor) {
        // Fallback target shape — exposes the writable fields Resource methods
        // read. Real production runs always have RequestTarget available.
        return {};
    }
    return new Ctor();
}
function wrapResult(data) {
    const text = typeof data === 'string' ? data : JSON.stringify(data ?? null);
    const result = { content: [{ type: 'text', text }] };
    if (data !== null && typeof data === 'object') {
        result.structuredContent = data;
    }
    return result;
}
function wrapError(toolName, err) {
    const e = err;
    const message = e?.http_resp_msg ?? e?.message ?? `${toolName} failed`;
    harper_logger_ts_1.default.trace(`MCP ${toolName} threw: ${err.stack ?? message}`);
    return {
        isError: true,
        content: [
            {
                type: 'text',
                text: JSON.stringify({ kind: 'harper_error', tool: toolName, message }),
            },
        ],
    };
}
function encodeCursor(offset) {
    return Buffer.from(JSON.stringify({ offset }), 'utf8').toString('base64url');
}
function decodeCursor(cursor) {
    if (!cursor)
        return 0;
    try {
        const decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
        const o = decoded?.offset;
        if (typeof o === 'number' && o >= 0 && Number.isInteger(o))
            return o;
        return 0;
    }
    catch {
        return 0;
    }
}
// ─── Verb-specific handler factories ───────────────────────────────────────
function makeGetHandler(toolName, ResourceClass) {
    return async function (args, context) {
        const a = (args ?? {});
        try {
            const target = makeTarget();
            target.id = a.id;
            if (Array.isArray(a.get_attributes))
                target.select = a.get_attributes;
            const data = await ResourceClass.get(target, buildContext(context.user));
            return wrapResult(data);
        }
        catch (err) {
            return wrapError(toolName, err);
        }
    };
}
function makeSearchHandler(toolName, ResourceClass) {
    return async function (args, context) {
        const a = (args ?? {});
        try {
            const target = makeTarget();
            if (Array.isArray(a.conditions))
                target.conditions = a.conditions;
            if (typeof a.operator === 'string')
                target.operator = a.operator;
            if (Array.isArray(a.get_attributes))
                target.select = a.get_attributes;
            const requestedLimit = typeof a.limit === 'number' && a.limit > 0 ? Math.floor(a.limit) : DEFAULT_SEARCH_LIMIT;
            const limit = Math.min(requestedLimit, searchLimitFor('application'));
            const offset = decodeCursor(typeof a.cursor === 'string' ? a.cursor : undefined);
            // Fetch one extra to know if there's a next page without a second round-trip.
            target.limit = limit + 1;
            target.offset = offset;
            const rawData = await ResourceClass.search(target, buildContext(context.user));
            const rows = await collectRows(rawData);
            const hasMore = rows.length > limit;
            const page = hasMore ? rows.slice(0, limit) : rows;
            const result = { rows: page };
            if (hasMore)
                result.nextCursor = encodeCursor(offset + limit);
            return wrapResult(result);
        }
        catch (err) {
            return wrapError(toolName, err);
        }
    };
}
async function collectRows(rawData) {
    if (rawData == null)
        return [];
    if (Array.isArray(rawData))
        return rawData;
    // AsyncIterable
    if (typeof rawData[Symbol.asyncIterator] === 'function') {
        const out = [];
        for await (const row of rawData)
            out.push(row);
        return out;
    }
    // Sync iterable
    if (typeof rawData[Symbol.iterator] === 'function') {
        return Array.from(rawData);
    }
    // Single value
    return [rawData];
}
function makeCreateHandler(toolName, ResourceClass) {
    return async function (args, context) {
        const a = (args ?? {});
        try {
            const target = makeTarget();
            const data = await ResourceClass.post(target, a, buildContext(context.user));
            return wrapResult(data ?? { ok: true });
        }
        catch (err) {
            return wrapError(toolName, err);
        }
    };
}
function makeUpdateHandler(toolName, ResourceClass, verb) {
    return async function (args, context) {
        const a = (args ?? {});
        const { id, ...rest } = a;
        try {
            const target = makeTarget();
            target.id = id;
            const fn = verb === 'put' ? ResourceClass.put : ResourceClass.patch;
            const data = await fn(target, rest, buildContext(context.user));
            return wrapResult(data ?? { ok: true });
        }
        catch (err) {
            return wrapError(toolName, err);
        }
    };
}
function makeDeleteHandler(toolName, ResourceClass) {
    return async function (args, context) {
        const a = (args ?? {});
        try {
            const target = makeTarget();
            target.id = a.id;
            const data = await ResourceClass.delete(target, buildContext(context.user));
            return wrapResult(data ?? { ok: true });
        }
        catch (err) {
            return wrapError(toolName, err);
        }
    };
}
function detectVerbs(ResourceClass) {
    const p = ResourceClass.prototype;
    if (!p || typeof p !== 'object') {
        return { get: false, search: false, create: false, updatePut: false, updatePatch: false, delete: false };
    }
    return {
        get: typeof p.get === 'function',
        search: typeof p.search === 'function',
        // Resource.post defaults to no-op on the base class, but `update()` is the
        // implicit override per the openApi pattern. Treat either as "has create".
        create: typeof p.post === 'function' || typeof p.update === 'function',
        updatePut: typeof p.put === 'function',
        updatePatch: typeof p.patch === 'function',
        delete: typeof p.delete === 'function',
    };
}
// ─── Visibility predicate ──────────────────────────────────────────────────
function makeVisibleTo(databaseName, tableName, mode) {
    return function visibleTo(user) {
        // Super-user sees everything.
        if (user?.role?.permission?.super_user === true)
            return true;
        // Non-table Resources have no static permission gate — runtime allow*
        // predicates enforce. Conservative default: don't list them for
        // non-super users; they can still call via tools/call if they know
        // the tool name (pass-through is documented behavior).
        if (!databaseName || !tableName)
            return false;
        const perm = getUserTablePermissions(user, databaseName, tableName);
        if (!perm)
            return false;
        if (mode === 'read')
            return perm.read === true || perm.describe === true;
        return perm[mode] === true;
    };
}
function shouldEnumerate(entry) {
    const types = entry.exportTypes;
    if (!types)
        return true; // no per-protocol controls → all enabled
    return types.mcp !== false;
}
/**
 * Per-verb sentence templates. Replaces the prior mechanical
 * `"${verb} on resource '/X'"` output with prose grounded in the actual verb.
 */
const VERB_SENTENCES = {
    get: ({ tableName, primaryKey, path }) => `Fetches a single ${tableName ?? path} record by ${primaryKey ?? 'id'}.`,
    search: ({ tableName, path }) => `Searches ${tableName ?? path} records by attribute conditions.`,
    create: ({ tableName, path }) => `Creates a new ${tableName ?? path} record.`,
    update: ({ tableName, primaryKey, path }) => `Replaces a ${tableName ?? path} record by ${primaryKey ?? 'id'} (PUT semantics).`,
    patch: ({ tableName, primaryKey, path }) => `Partially updates a ${tableName ?? path} record by ${primaryKey ?? 'id'}.`,
    delete: ({ tableName, primaryKey, path }) => `Deletes a ${tableName ?? path} record by ${primaryKey ?? 'id'}.`,
};
/**
 * Map each verb to the Resource RBAC method that gates it at runtime.
 * Harper exposes four predicates — allowRead / allowCreate / allowUpdate /
 * allowDelete — so multiple verbs share the same method (get + search both
 * gate on allowRead; update + patch both gate on allowUpdate).
 */
const VERB_TO_RBAC_METHOD = {
    get: 'allowRead',
    search: 'allowRead',
    create: 'allowCreate',
    update: 'allowUpdate',
    patch: 'allowUpdate',
    delete: 'allowDelete',
};
/**
 * Compose the per-verb tool description. When the Resource carries a
 * `static description` (or a `Table.description` from the GraphQL parser),
 * prefix it; then the verb-specific sentence; then the runtime-RBAC note.
 */
function verbDescription(verb, ctx) {
    const prefix = ctx.tableDoc ? `${ctx.tableDoc}\n\n` : '';
    const sentence = VERB_SENTENCES[verb](ctx);
    const allowMethod = VERB_TO_RBAC_METHOD[verb];
    return `${prefix}${sentence} Runtime RBAC (${allowMethod}) enforces per-record access at call time.`;
}
/**
 * Merge per-verb annotation overrides from `ResourceClass.mcp.annotations[verb]`
 * over the defaults. Used for narrow MCP-only knobs that don't fit JSON Schema
 * (e.g. `idempotentHint` overrides per verb).
 */
function mergeAnnotations(verb, defaults, ResourceClass) {
    const override = ResourceClass.mcp?.annotations?.[verb];
    return override ? { ...defaults, ...override } : defaults;
}
/**
 * Operations a Resource may opt into via `static mcp.annotations.<verb>.idempotentHint`.
 * Default-empty: under MCP semantics `idempotentHint: true` means same observable
 * outcome on repeat call; we under-annotate before mis-annotate. `update_*` (PUT
 * semantics) is the only built-in default; `patch_*`/`delete_*` annotations are
 * deferred until repeat-call behavior is verified end-to-end.
 */
function registerVerbTools(ctx) {
    let count = 0;
    const { suffix, ResourceClass, attributes, verbs, tableName, databaseName, path } = ctx;
    const tableDoc = ResourceClass.description;
    const primaryKey = ResourceClass.primaryKey;
    const ctxForVerb = { tableDoc, tableName, primaryKey, path };
    const overrideOutput = (verb) => ResourceClass.outputSchemas?.[verb];
    if (verbs.get) {
        const name = `get_${suffix}`;
        (0, toolRegistry_ts_1.addTool)({
            name,
            description: verbDescription('get', ctxForVerb),
            inputSchema: (0, derive_ts_1.deriveGetSchema)(attributes, undefined),
            outputSchema: overrideOutput('get') ?? (0, derive_ts_1.deriveGetOutputSchema)(attributes, undefined),
            profile: 'application',
            annotations: mergeAnnotations('get', { readOnlyHint: true }, ResourceClass),
            visibleTo: makeVisibleTo(databaseName, tableName, 'read'),
            handler: makeGetHandler(name, ResourceClass),
        });
        count++;
    }
    if (verbs.search) {
        const name = `search_${suffix}`;
        // search_* deliberately omits outputSchema — envelope shape (records vs
        // data, cursor vs nextCursor) is tracked in the sibling envelope issue.
        (0, toolRegistry_ts_1.addTool)({
            name,
            description: verbDescription('search', ctxForVerb),
            inputSchema: (0, derive_ts_1.deriveSearchSchema)(attributes, undefined),
            profile: 'application',
            annotations: mergeAnnotations('search', { readOnlyHint: true }, ResourceClass),
            visibleTo: makeVisibleTo(databaseName, tableName, 'read'),
            handler: makeSearchHandler(name, ResourceClass),
        });
        count++;
    }
    if (verbs.create) {
        const name = `create_${suffix}`;
        (0, toolRegistry_ts_1.addTool)({
            name,
            description: verbDescription('create', ctxForVerb),
            inputSchema: (0, derive_ts_1.deriveCreateSchema)(attributes, undefined),
            outputSchema: overrideOutput('create') ?? (0, derive_ts_1.deriveCreateOutputSchema)(attributes, undefined),
            profile: 'application',
            annotations: mergeAnnotations('create', {}, ResourceClass),
            visibleTo: makeVisibleTo(databaseName, tableName, 'insert'),
            handler: makeCreateHandler(name, ResourceClass),
        });
        count++;
    }
    if (verbs.updatePut) {
        const name = `update_${suffix}`;
        (0, toolRegistry_ts_1.addTool)({
            name,
            description: verbDescription('update', ctxForVerb),
            inputSchema: (0, derive_ts_1.deriveUpdateSchema)(attributes, undefined),
            outputSchema: overrideOutput('update') ?? (0, derive_ts_1.deriveUpdateOutputSchema)(attributes, undefined),
            profile: 'application',
            // PUT semantics: replacing with the same payload yields the same state,
            // so the observable outcome on repeat call is identical.
            annotations: mergeAnnotations('update', { idempotentHint: true }, ResourceClass),
            visibleTo: makeVisibleTo(databaseName, tableName, 'update'),
            handler: makeUpdateHandler(name, ResourceClass, 'put'),
        });
        count++;
    }
    else if (verbs.updatePatch) {
        const name = `patch_${suffix}`;
        (0, toolRegistry_ts_1.addTool)({
            name,
            description: verbDescription('patch', ctxForVerb),
            inputSchema: (0, derive_ts_1.deriveUpdateSchema)(attributes, undefined),
            outputSchema: overrideOutput('patch') ?? (0, derive_ts_1.derivePatchOutputSchema)(attributes, undefined),
            profile: 'application',
            // patch_* idempotency depends on partial-update semantics; default
            // omitted, opt-in via static mcp.annotations.patch.idempotentHint.
            annotations: mergeAnnotations('patch', {}, ResourceClass),
            visibleTo: makeVisibleTo(databaseName, tableName, 'update'),
            handler: makeUpdateHandler(name, ResourceClass, 'patch'),
        });
        count++;
    }
    if (verbs.delete) {
        const name = `delete_${suffix}`;
        // outputSchema deliberately omitted (matches the search_* precedent).
        // `Table.delete` returns Promise<boolean>; `makeDeleteHandler` wraps it as
        // `wrapResult(data ?? { ok: true })`, and wrapResult only sets
        // `structuredContent` when data is an object. So a boolean handler
        // return produces text content only — no structuredContent for an
        // outputSchema to validate. An author who returns a structured
        // envelope can opt in via `static outputSchemas.delete`.
        const deleteOverride = overrideOutput('delete');
        (0, toolRegistry_ts_1.addTool)({
            name,
            description: verbDescription('delete', ctxForVerb),
            inputSchema: (0, derive_ts_1.deriveDeleteSchema)(attributes, undefined),
            ...(deleteOverride ? { outputSchema: deleteOverride } : {}),
            profile: 'application',
            // delete_* idempotency depends on delete-of-deleted behavior; default
            // omits idempotentHint until verified end-to-end.
            annotations: mergeAnnotations('delete', { destructiveHint: true }, ResourceClass),
            visibleTo: makeVisibleTo(databaseName, tableName, 'delete'),
            handler: makeDeleteHandler(name, ResourceClass),
        });
        count++;
    }
    return count;
}
/**
 * #622 — Component-author opt-in. Walk `ResourceClass.mcpTools` and register
 * each entry as a standalone MCP tool that invokes the named instance method.
 *
 * Each invocation constructs an instance with `(undefined, context)` and
 * calls `instance[method](args)`. This mirrors what a custom user-defined
 * Harper Resource expects when its methods are reached outside of a REST
 * request — they receive arguments and rely on internal Harper calls to
 * enforce any RBAC they need.
 *
 * No `visibleTo` filter beyond "is the user authenticated" — per the design,
 * the Resource is responsible for its own ACLs. An LLM that calls a tool
 * it shouldn't gets the Resource's natural error back as `isError: true`.
 */
// Dedup keys for warn-once telemetry on missing description / inputSchema.
// Keyed by `${path}:${name}`. Survives across `registerApplicationTools()`
// re-invocations so re-registering the same Resource doesn't re-spam the log.
// Test seam below resets these.
const _warnedMissingDesc = new Set();
const _warnedMissingInput = new Set();
function _resetCustomToolWarningsForTest() {
    _warnedMissingDesc.clear();
    _warnedMissingInput.clear();
}
function registerCustomMcpTools(ResourceClass, path) {
    const tools = ResourceClass.mcpTools;
    if (!Array.isArray(tools) || tools.length === 0)
        return 0;
    let count = 0;
    for (const def of tools) {
        if (!def?.name || !def?.method) {
            harper_logger_ts_1.default.warn(`MCP application profile: skipping invalid mcpTools entry on '${path}' (missing name or method): ${JSON.stringify(def)}`);
            continue;
        }
        const methodName = def.method;
        if (typeof ResourceClass.prototype?.[methodName] !== 'function') {
            harper_logger_ts_1.default.warn(`MCP application profile: '${path}' declares mcpTool '${def.name}' for method '${methodName}', but no such instance method exists on the prototype`);
            continue;
        }
        // Nudge component authors toward shipping description + inputSchema. Both
        // materially affect LLM tool selection: description tells the model WHICH
        // tool to pick; inputSchema tells it HOW to fill the arguments. Falling
        // back to a generic description + `additionalProperties: true` makes the
        // tool callable but hard to invoke correctly.
        const dedupKey = `${path}:${def.name}`;
        let description = def.description;
        if (!description) {
            if (!_warnedMissingDesc.has(dedupKey)) {
                _warnedMissingDesc.add(dedupKey);
                harper_logger_ts_1.default.warn(`MCP application: Resource '${path}' exposes mcpTool '${def.name}' without a description. ` +
                    `LLM tool selection degrades without one; add { description: '...' } to the mcpTools entry.`);
            }
            description = `Custom MCP tool exposed by Resource '${path}' (method '${methodName}'). RBAC is enforced by the Resource itself.`;
        }
        let inputSchema = def.inputSchema;
        if (!inputSchema) {
            if (!_warnedMissingInput.has(dedupKey)) {
                _warnedMissingInput.add(dedupKey);
                harper_logger_ts_1.default.warn(`MCP application: Resource '${path}' exposes mcpTool '${def.name}' without an inputSchema. ` +
                    `LLM cannot construct typed arguments; add { inputSchema: { type: 'object', properties: {...}, required: [...] } } to the mcpTools entry.`);
            }
            inputSchema = { type: 'object', additionalProperties: true };
        }
        (0, toolRegistry_ts_1.addTool)({
            name: def.name,
            description,
            inputSchema,
            profile: 'application',
            ...(def.annotations ? { annotations: def.annotations } : {}),
            // Per the design: custom-tool RBAC is delegated to the Resource. No
            // visibleTo filter beyond "the user is authenticated" — the runtime
            // rejects unauthorized calls naturally.
            visibleTo: () => true,
            handler: makeCustomMethodHandler(def.name, ResourceClass, methodName),
        });
        count++;
    }
    return count;
}
function makeCustomMethodHandler(toolName, ResourceClass, methodName) {
    return async function (args, context) {
        try {
            // Instantiate per call. Component authors define custom methods on
            // the instance side; the Harper context carries the user so any
            // internal Resource calls the method makes pick up RBAC naturally.
            const Ctor = ResourceClass;
            const instance = new Ctor(undefined, buildContext(context.user));
            const method = instance[methodName];
            if (typeof method !== 'function') {
                throw new Error(`method '${methodName}' is not a function on the constructed Resource`);
            }
            const data = await method.call(instance, args ?? {});
            return wrapResult(data ?? { ok: true });
        }
        catch (err) {
            return wrapError(toolName, err);
        }
    };
}
/**
 * Idempotent registration — walk the Resources Map, register verb tools
 * for each entry that passes the exportTypes + verb-presence filters.
 * Re-invocation overwrites prior tool entries (Map.set semantics).
 */
function registerApplicationTools() {
    const resources = loadResources();
    if (!resources) {
        harper_logger_ts_1.default.warn('MCP application profile: Resources registry not available; no tools registered');
        return;
    }
    const claimedSuffixes = new Set();
    let toolsRegistered = 0;
    let resourcesConsidered = 0;
    for (const [path, entry] of resources) {
        resourcesConsidered++;
        if (!shouldEnumerate(entry))
            continue;
        const ResourceClass = entry.Resource;
        // @hidden type-level: suppress the Resource from MCP tool listing entirely.
        // Data remains accessible via direct query/RBAC; only descriptive surfaces drop it.
        if (ResourceClass?.hidden === true) {
            harper_logger_ts_1.default.trace(`MCP application: '/${path}' suppressed from tool listing (@hidden)`);
            continue;
        }
        const verbs = detectVerbs(ResourceClass);
        const hasVerbs = verbs.get || verbs.search || verbs.create || verbs.updatePut || verbs.updatePatch || verbs.delete;
        const hasCustomTools = Array.isArray(ResourceClass?.mcpTools) && ResourceClass.mcpTools.length > 0;
        if (!hasVerbs && !hasCustomTools)
            continue;
        const databaseName = ResourceClass?.databaseName;
        const tableName = ResourceClass?.tableName;
        const suffix = uniqueSuffix(path, databaseName, claimedSuffixes);
        claimedSuffixes.add(suffix);
        const attributes = (ResourceClass?.attributes ?? []);
        if (hasVerbs) {
            toolsRegistered += registerVerbTools({
                path,
                suffix,
                ResourceClass,
                databaseName,
                tableName,
                attributes,
                verbs,
            });
        }
        toolsRegistered += registerCustomMcpTools(ResourceClass, path);
    }
    harper_logger_ts_1.default.info(`MCP application profile: considered ${resourcesConsidered} resource(s), registered ${toolsRegistered} tool(s)`);
}
//# sourceMappingURL=application.js.map