@extenshi/mcp
Version:
Extenshi MCP server — bring the extension catalog (search, security, market research) and pre-publish scanning into your AI tools
106 lines • 5.51 kB
TypeScript
/**
* Output shaping for MCP tool results.
*
* MCP tool results land directly in the AI client's context window, so we keep
* them compact and curated. Field names below match the live BFF payloads
* (catalog router mounted as `catalog`, security as `security`):
* - search items wrap a cross-store cluster: `{ id, slug, snapshots[], availableStores, security }`
* where the human-facing fields (name, rating, users, store) live on each snapshot.
* - `getExtensionById` returns the cluster with aggregated `latest*` fields.
* - `getSecurityData` returns `{ riskAssessment, scanExecution, findings:{ total, groupTotal, bySeverity } }`.
* - `getRiskSummary` is already a compact summary (or null if never scanned).
*
* `compact()` is a defensive fallback that bounds array length / string length /
* depth so a tool can never blow up the context even if a shape drifts.
*/
interface CompactOpts {
maxArray?: number;
maxString?: number;
maxDepth?: number;
}
/** Recursively bound a value's size: cap arrays, truncate strings, limit depth. */
export declare function compact(value: unknown, opts?: CompactOpts, depth?: number): unknown;
type Obj = Record<string, unknown>;
/** Compact a search result down to a count + curated items. */
export declare function shapeSearch(result: unknown, limit: number): Obj;
/** Curate a single extension detail (`getExtensionById`). */
export declare function shapeExtension(result: unknown): unknown;
/**
* Curate a page of store user reviews (`get_reviews`). Passes through the
* keyset `nextCursor` so the model can page, a `count` for quick sizing, and the
* store-level `aggregate` (rating / count / snapshot date + reviews link) — the
* bare facts that stay public even for Chrome.
*/
export declare function shapeReviews(result: unknown, limit: number): Obj;
/**
* Curate the install-dialog preview — the consolidated permission prompt the
* browser shows at install (computed server-side by catalog-api from the
* manifest's required permissions; source of truth: shared-types/permission-warnings.ts).
* Reduces each browser's warnings to their human-readable lines for terseness.
*/
export declare function shapeInstallDialog(preview: unknown): Obj | undefined;
/** Capability phrase for a scanner key; unknown keys get a generic phrase, never the raw name. */
export declare function scannerPhrase(scanner: unknown): string | undefined;
/**
* Some rule ids carry the scanner key as a namespace (`jstap:document-cookie-access`);
* the same string is often the finding title. Drop a known scanner prefix so the
* tool name never leaks through the rule or title either.
*/
export declare function stripScannerPrefix(value: unknown): unknown;
/** Curate the security view: risk summary + grouped findings (top N per severity). */
export declare function shapeSecurity(security: unknown, riskSummary: unknown, installDialogPreview?: unknown): Obj;
/**
* Shape one row of `security.getSecuritySummaryBatch` for the risk-by-store-id
* tool.
*
* Two things this deliberately does NOT hide:
* - `scoredStore` / `scoredVariantId` — the batch resolves a store id through
* its cross-store cluster to the record the extension page renders, and
* cluster members are scanned independently (the same extension scored 78.55
* HIGH on its Chrome record and 80.47 CRITICAL on its Edge one). An agent
* reporting a number to a developer must be able to say which listing it
* describes.
* - a `null` score — "not scanned" is an expected state, not an error, and
* must never be flattened into a reassuring 100.
*
* `catalogUrl` uses the CANONICAL id so the link lands on the page the score
* came from without a redirect hop.
*/
export declare function shapeStoreRiskRow(row: unknown): Obj;
/**
* Shape a whole risk-by-store-id batch, and say which requested ids the catalog
* had no listing for — an agent that cannot tell "safe" from "unknown" will
* report the wrong thing.
*/
export declare function shapeStoreRiskBatch(rows: unknown, requested: Array<{
storeId: string;
store: string;
}>): Obj;
/** Facet groups `market_overview` can return, selectable via its `facets` argument. */
export declare const MARKET_FACETS: readonly ["stats", "stores", "categories", "extended", "questionnaire", "downloads"];
export type MarketFacet = (typeof MARKET_FACETS)[number];
/** Default number of entries kept per open-ended facet list (categories, permissions, sites). */
export declare const DEFAULT_MARKET_TOP_N = 10;
export declare const MAX_MARKET_TOP_N = 100;
export interface MarketOverviewInput {
scope: 'search' | 'catalog-wide';
stats?: unknown;
/** Raw facets as the BFF returns them (search facets, or the catalog-wide assembly). */
facets?: unknown;
note?: string;
}
export interface MarketShapeOpts {
facets?: readonly string[];
topN?: number;
}
/**
* Trim a market_overview payload for the model's context: pick the requested
* facet groups (default: all), cut open-ended lists to the `topN` largest
* entries, and reduce category nodes to name/slug/count. Counts are passed
* through unchanged — this shapes the response, it does not recompute data.
* A `truncated` map says how many entries each trimmed list dropped, so the
* caller knows when a larger `top_n` would reveal more.
*/
export declare function shapeMarketOverview(input: MarketOverviewInput, opts?: MarketShapeOpts): Obj;
export {};
//# sourceMappingURL=shape.d.ts.map