Skip to content

ToolContext

The invocation context handed to every tool handler — a small capability object with no ambient authority.

Each member maps to a Trove-provided callback the SDK closes over the short-TTL ctxToken; the blast radius of a handler is exactly its declared secrets ∪ scopes ∪ egress.

PropertyModifierTypeDescriptionInherited from
cache?publicExtensionCacheA best-effort key/value cache that outlives a single run. Optional, and absent is normal — a host may provide no cache at all, so read it as ctx.cache?.get(...) and treat a miss and an absent cache the same way. Never the source of truth: anything here must be re-derivable from the upstream, because it can vanish between runs.ExtensionContext.cache
configreadonlyReadonly<Record<string, unknown>>The user’s settings for this toolkit, keyed by the field names its manifest declares in config. {} for a toolkit that declares no settings, or whose owner has not filled any in — the manifest’s declared defaults are what apply. Preferences only, never credentials: a credential comes from ToolContext.secret, where it is encrypted at rest and redacted out of logs. Trove refuses a settings write that looks like a secret, so a toolkit should not go looking for one here. The same shape a source gets from ctx.config in @ontrove/extend/source.-
fetchpublicFetchLikeBehaves like the standard fetch, and is the ONLY way out. In Trove’s cloud every request is routed through an egress worker that permits https alone, matches the manifest’s declared hosts exactly, and refuses private and link-local addresses even when allowlisted. On the Mac it adds per-source timeouts, retry and rate-limit handling. Prefer it over global fetch everywhere: the global one is unguarded where it exists at all, and absent where it does not.ExtensionContext.fetch
logpublicLogChannelWhere an extension says what it is doing. Surfaced in the run transcript.ExtensionContext.log
trove?readonlyTroveClientA scoped client over the caller’s own knowledge base — present only if the manifest scopes requested trove:search and/or trove:ingest.-
userIdreadonlystringThe authenticated Clerk user id of the caller — identity, not a credential.-
fetchJson<S>(url: string | URL, opts: FetchJsonOpts & {
schema: S;
}): Promise<output<S>>;

Fetch JSON with batteries included: routes through fetch (so the default UA and any declarative auth apply), maps a non-2xx status to a ToolError (4xx≠429 → non-retryable; 429/5xx/network → retryable), guards malformed JSON, and — when opts.schema is supplied — validates the body and returns the typed result. Omit schema to receive parsed unknown. Keep the schema lenient (.default()/.nullish()); it is for parsing the upstream shape, not the tool’s strict output contract.

Type Parameter
S extends ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>
ParameterType
urlstring | URL
optsFetchJsonOpts & { schema: S; }

Promise<output<S>>

fetchJson(url: string | URL, opts?: FetchJsonOpts): Promise<unknown>;
ParameterType
urlstring | URL
opts?FetchJsonOpts

Promise<unknown>


now(): Date;

The current wall-clock time.

Injected rather than read from a global so a run is deterministic under test, and so a replayed fixture means the same thing tomorrow.

Date

ExtensionContext.now


requireSecret(name: string): Promise<string>;

secret, for a credential the extension genuinely cannot proceed without: rejects, naming the credential, rather than resolving undefined.

Prefer it to if (!(await ctx.secret(n))) throw — the failure message is the useful half, and the host writes a better one than each extension will.

ParameterType
namestring

Promise<string>

ExtensionContext.requireSecret


secret(name: string): Promise<string | undefined>;

A credential the manifest declared, by name — undefined when it is not set.

Resolves whether the value was pasted by the user or is a token Trove refreshed a moment ago — the extension cannot tell, and must not need to. That is what keeps delegated authorization out of every author’s code. Async for the same reason: resolving may be a vault read or a refresh.

This is the reader for a credential the extension can work without — an optional API key that raises a rate limit, an OAuth client secret a public client does not have. When the extension cannot proceed without the value, use requireSecret and let it raise.

Until 3.3.0 this rejected, and requireSecret was documented as behaving “identically; the name is the documentation” — so there were two names for one behaviour and no way at all to read an optional credential. The two sources that had one reached around the whole mechanism into the legacy ctx.credentials bag instead.

ParameterType
namestring

Promise<string | undefined>

ExtensionContext.secret