@extenshi/mcp
Version:
Extenshi MCP server — bring the extension catalog (search, security, market research) and pre-publish scanning into your AI tools
285 lines • 11.9 kB
TypeScript
/**
* Thin tRPC client for the public catalog BFF (`bff.extenshi.io`).
*
* The BFF tRPC server is configured WITHOUT a data transformer (no superjson),
* so this client must match — plain JSON over the wire. We type the proxy as
* `any` on purpose: catalog-api emits no `.d.ts`, so the BFF↔API type chain
* can't be imported reliably across a published-package boundary. The runtime
* proxy builds the procedure path from property access regardless of types.
*
* Every call carries `Authorization: Bearer <ek_…>`. Today the BFF read
* procedures are anonymous, but they are moving to mandatory key-enforcement
* (no anonymous data access) — sending the key now is forward-compatible and
* needs no client change when enforcement lands.
*/
import { type PayBff } from './pay.js';
/** One credit pool as reported by `getApiCallerBalance`. */
export interface CreditPool {
/** Total calls this pool can still fund (free grant + purchased) — the single number that gates a call. */
remaining: number;
/** The slice of `remaining` still coming from the one-time signup grant (always ≤ remaining). */
freeRemaining: number;
freeGranted: number;
freeUsed: number;
}
/** Read/scan/icon/inventory balances for the `ek_` key or remote-MCP identity. */
export interface ApiCallerBalance {
read: CreditPool;
scan: CreditPool;
icon: CreditPool;
inventory: CreditPool;
billingUrl: string;
}
/** A resolved store→catalog reference (`resolveExtensionRef`). */
export interface ExtensionRef {
id: number;
store: 'CHROME' | 'FIREFOX' | 'EDGE';
}
export interface Bff extends PayBff {
/**
* Create a project owned by this identity — the same service as the Dojo
* wizard. Idempotent per `idempotencyKey`. Requires project.write.
*/
createProject(input: import('@extenshi/contracts').AgentCreateProject): Promise<unknown>;
/** Owner decisions with status, suggested defaults and what each blocks. */
getDecisions(input: {
projectId: string;
}): Promise<unknown>;
/** Record a PROPOSAL; only the owner decides, in Dojo. Requires project.write. */
proposeDecision(input: import('@extenshi/contracts').ProposeDecision): Promise<unknown>;
importManifest(input: import('@extenshi/contracts').ManifestImport): Promise<unknown>;
getProjectWorkspace(input: {
projectId: string;
}): Promise<unknown>;
diffProjectWorkspace(input: import('@extenshi/contracts').WorkspacePatch): Promise<unknown>;
patchProjectWorkspace(input: import('@extenshi/contracts').WorkspacePatch): Promise<unknown>;
recordProjectEvidence(input: Omit<import('@extenshi/contracts').Evidence, 'id' | 'recordedAt'>): Promise<unknown>;
getReleaseReadiness(input: {
projectId: string;
browser?: 'chrome' | 'firefox' | 'edge';
artifactDigest?: string;
}): Promise<unknown>;
connectionDiagnostics(): Promise<unknown>;
searchExtensions(input: Record<string, unknown>): Promise<unknown>;
getExtensionById(id: number): Promise<unknown>;
/** Paginated store user reviews for an extension (PII-free projection). */
getReviews(input: Record<string, unknown>): Promise<unknown>;
getSecurityData(extensionId: number): Promise<unknown>;
getRiskSummary(extensionId: number): Promise<unknown>;
getSearchFacets(input: Record<string, unknown>): Promise<unknown>;
getStats(): Promise<unknown>;
/** Catalog-wide extended facets (manifest / permissions / risk / freshness …), cached server-side. */
getExtendedFilterFacets(): Promise<unknown>;
/** Catalog-wide category tree with per-category counts. */
getCategoryTree(): Promise<unknown>;
/** This caller's credit balances across all pools. FREE — never spends a read credit. */
getApiCallerBalance(): Promise<ApiCallerBalance>;
/**
* Resolve a public STORE extension id (from the store URL) to the numeric
* catalog id the read tools take. FREE — the read that follows is what's
* metered. Returns null when no catalog listing exists for it yet.
*/
resolveExtensionRef(input: {
storeId: string;
store?: 'CHROME' | 'FIREFOX' | 'EDGE';
}): Promise<ExtensionRef | null>;
/**
* Risk for MANY extensions addressed by store id, in ONE metered read.
* Cluster-resolved server-side, so each row describes the same listing the
* extension page renders. Capped at 40 extensions per call for metered
* callers — the BFF rejects a larger batch with a message naming the limit.
*/
getSecuritySummaryBatch(input: {
extensions: Array<{
storeId: string;
store: 'CHROME' | 'FIREFOX' | 'EDGE';
}>;
}): Promise<unknown>;
/**
* The caller's OWN extension projects. FREE — credits pay for catalog data
* about other people's extensions, never for reading your own work.
*/
listMyProjects(): Promise<unknown>;
/**
* One own project as state an agent can act on: chosen extension types, the
* manifest.json that project produces per browser, live hosted URLs, and an
* index of saved tool state. FREE, same reasoning. `includeToolStates` inlines
* named payloads; without it the response lists what exists and how big.
*/
getMyProjectState(input: {
projectId: string;
includeToolStates?: string[];
}): Promise<unknown>;
/**
* The starter extension one own project produces, as files — the same set the
* site commits and packs into its download. FREE, same reasoning. One browser
* per call: the manifests differ between them and the rest does not.
*/
getMyProjectScaffold(input: {
projectId: string;
browser?: string;
}): Promise<unknown>;
listPrivacyPolicyVersions(input: {
projectId: string;
}): Promise<unknown>;
getPrivacyPolicyVersion(input: {
projectId: string;
versionNumber: number;
}): Promise<unknown>;
publishPrivacyPolicy(input: {
projectId: string;
bodyMarkdown?: string;
kind?: string;
}): Promise<unknown>;
updatePrivacyPolicyWithAi(input: {
projectId: string;
}): Promise<unknown>;
/**
* Create (or ROTATE) the per-project CI evidence ingest secret. The
* plaintext is shown once and belongs in the repository's Actions secrets.
*/
createCiIngestSecret(input: {
projectId: string;
}): Promise<unknown>;
revokeCiIngestSecret(input: {
projectId: string;
}): Promise<unknown>;
/** Register a project's homepage/support URL (HTTPS, reachable, hashed). */
upsertHostedPage(input: {
projectId: string;
kind: string;
url: string;
}): Promise<unknown>;
/** Re-fetch and compare: verified | changed | unreachable per kind. */
verifyHostedArtifact(input: {
projectId: string;
kind: string;
}): Promise<unknown>;
/** Forget a registration (a project moving off a URL needs a real delete). */
removeHostedPage(input: {
projectId: string;
kind: string;
}): Promise<unknown>;
listHostedPages(input: {
projectId: string;
}): Promise<unknown>;
/** Publish (create or update) the project's landing page on page.extenshi.io. */
publishLandingPage(input: {
projectId: string;
form: Record<string, unknown>;
registerAsHomepage?: boolean;
}): Promise<unknown>;
getLandingPage(input: {
projectId: string;
}): Promise<unknown>;
unpublishLandingPage(input: {
projectId: string;
}): Promise<unknown>;
/** Schema-v2 homepage drafted from project state: {form, sources, todos, problems, previewUrl}. Writes nothing. */
draftLandingPage(input: {
projectId: string;
storeUrls?: {
chrome?: string;
firefox?: string;
edge?: string;
};
}): Promise<unknown>;
/** Render exactly as publish would (validation, Pay offers, canonical) without writing. */
previewLandingPage(input: {
projectId: string;
form: Record<string, unknown>;
locale?: string;
}): Promise<unknown>;
listLandingVersions(input: {
projectId: string;
}): Promise<unknown>;
/** Publish an earlier version's form as a new version. */
rollbackLandingPage(input: {
projectId: string;
versionNumber: number;
registerAsHomepage?: boolean;
}): Promise<unknown>;
/** Custom domain: records to set, verification state, next step. */
getCustomDomain(input: {
projectId: string;
}): Promise<unknown>;
setCustomDomain(input: {
projectId: string;
hostname: string;
}): Promise<unknown>;
verifyCustomDomain(input: {
projectId: string;
}): Promise<unknown>;
removeCustomDomain(input: {
projectId: string;
}): Promise<unknown>;
/** The hosted install instructions page (it. 10 T8): state, URL and the saved form. */
getInstructionsPage(input: {
projectId: string;
}): Promise<unknown>;
/** Publish the install instructions (the saved form when `input` is omitted). */
publishInstructions(input: {
projectId: string;
input?: Record<string, unknown>;
}): Promise<unknown>;
setInstructionsEnabled(input: {
projectId: string;
enabled: boolean;
}): Promise<unknown>;
/** Every uninstall form and welcome page of the project: translatable fields and per-language status. */
getPageTranslations(input: {
projectId: string;
}): Promise<unknown>;
/** Save one language of one page (text by field path; the server records the source text). */
setPageTranslations(input: {
projectId: string;
surface: 'uninstall' | 'welcome' | 'instructions';
publicCode: string;
locale: string;
translations: Record<string, string>;
}): Promise<unknown>;
/**
* Translated copies of the hosted privacy policy / license terms (it. 13 T10
* L3): what the developer can translate (edited sections, own answers),
* languages, status per language and the translation URLs. The English
* version stays the binding text.
*/
getPrivacyPolicyTranslations(input: {
projectId: string;
}): Promise<unknown>;
setPrivacyPolicyTranslations(input: {
projectId: string;
locale: string;
translations: Record<string, string>;
sources?: Record<string, string>;
}): Promise<unknown>;
getLicenseTermsTranslations(input: {
projectId: string;
}): Promise<unknown>;
setLicenseTermsTranslations(input: {
projectId: string;
locale: string;
translations: Record<string, string>;
sources?: Record<string, string>;
}): Promise<unknown>;
/**
* Upload one PNG/JPEG/WebP to the project's PUBLIC media store (the same one
* Dojo uploads use). The BFF re-checks magic bytes, strips metadata, enforces
* size/dimension/quota limits and returns a stable content-addressed URL.
*/
uploadProjectMedia(input: {
projectId: string;
mime: 'image/png' | 'image/jpeg' | 'image/webp';
dataBase64: string;
}): Promise<unknown>;
}
/** Build a BFF client from a static `ek_…` key (stdio path). */
export declare function makeBff(bffUrl: string, apiKey: string): Bff;
/**
* Build a BFF client whose Authorization header is produced per request by
* `authHeader` (may be async). The remote OAuth server uses this to mint a
* fresh, short-lived genkan JWT for each call instead of holding a static key.
* The provider returns the FULL header value (e.g. `Bearer eyJ…`).
*/
export declare function makeBffWithAuth(bffUrl: string, authHeader: () => string | Promise<string>): Bff;
//# sourceMappingURL=bff.d.ts.map