Skip to content

Toolkit Manifest Reference

A toolkit declares itself in TypeScript. You write one defineToolkit({ … }) call in extension.ts holding both what the toolkit is and the tools it serves, and manifest.json is generated from it.

extension.ts
import { defineToolkit, tool, z } from '@ontrove/extend/toolkit';
export default defineToolkit({
id: 'acme-orders',
name: 'Acme Orders',
description: 'Query Acme order status and line items.',
icon: '📦',
version: '1.0.0',
author: 'Hollyburn Analytics Inc.',
secrets: ['ORDERS_API_TOKEN'],
egress: ['orders.acme.com'],
scopes: [],
visibility: 'private',
tools: [
tool({
name: 'lookup_order',
title: 'Look up order',
description: "Look up an Acme order's status and line items by ID.",
input: z.object({ orderId: z.string().describe("e.g. 'ORD-10423'.") }),
async handler({ orderId }, ctx) {
const token = await ctx.requireSecret('ORDERS_API_TOKEN');
// …
return { text: `Order ${orderId}: shipped.` };
},
}),
],
});

A hand-written manifest is a declaration nothing compiles. Every one of ours had drifted — toolkit manifests carried an sdk field naming a version range of a package that had long since moved past it, because no validator, no backend and no client ever read that field. Declaring the identity next to the tools makes the compiler the thing that notices.

Two consequences worth knowing before you start.

Validation is eager. defineToolkit validates the declaration at definition time, so a missing icon, a version that is not semver, a lowercase secret name, or an egress entry written as a URL throws when the module is imported. trove toolkit dev and trove toolkit deploy both load your module, so you meet the error at your desk rather than on a tool call.

manifest.json is still committed, and you never edit it. trove toolkit deploy reads the file off disk — it takes the toolkit’s name and slug from it, and it is the file’s secrets, egress and scopes that become the deployed toolkit’s policy. Deploy does not execute your defineToolkit call to derive them. So the file exists, carries "generated": true, and is written from the same object as your extension.ts.

FieldTypeRequiredNotes
idstringYesPattern ^[a-z0-9-]+$. The toolkit’s namespace: its tools appear to Claude as {id}__{tool}, and its standalone endpoint is https://api.ontrove.sh/mcp/s/{id}.
namestringYesDisplay name. Short and descriptive — Acme Orders, not Acme Internal Order Status Lookup System v2.
descriptionstringYesOne line, in the directory listing. Not model-visible — the model reads each tool’s own description.
iconstringYesA single emoji, or an HTTPS URL to a square icon.
versionstringYesSemver. Recorded on every deployment and shown in rollback history, so bump it on each meaningful deploy.
authorstringNoAttribution in the directory.

string[] — optional, and an empty list denies everything.

The hosts this toolkit may reach. Outbound HTTP is denied by default: ctx.fetch to a host absent from this list fails, and so does a redirect that resolves to one. Each entry is a bare hostname with an optional :port — no scheme, no path, no wildcard. A URL here is rejected at definition time rather than deployed and then silently unmatched.

egress: ['orders.acme.com', 'metrics.acme.com'],

Public-internet hosts only. Even a host you list is refused when it resolves to a loopback, private, link-local or otherwise reserved address — checked on the resolved address of every request and every redirect, so a name that rebinds to an internal IP is still blocked. A toolkit runs in Trove’s cloud and cannot reach your own machine: no localhost, no LAN, no local files, no Mac apps. See What toolkits can and can’t reach, and Secrets and Auth for the security model.

Keep the list tight. Every entry is a path a compromised dependency could send a decrypted secret down.

string[] — optional.

The credential names this toolkit reads through ctx.secret(name) / ctx.requireSecret(name). Names must be UPPER_SNAKE_CASE (^[A-Z][A-Z0-9_]*$) — a rule that exists because a manifest is a file in a repository, and the shape of the field is what stands between that and a pasted value.

secrets: ['ORDERS_API_TOKEN', 'METRICS_READ_KEY'],

Values are set separately with trove secret set <toolkit> <NAME> and live in the encrypted vault. A name that is not declared here is unreadable at run time. See Secrets and Auth.

string[] — optional, default [].

The Trove capabilities the toolkit requests. Without one, ctx.trove is undefined and the toolkit can only call external APIs.

ScopeWhat it unlocks
trove:searchctx.trove.search(…) and ctx.trove.getDocument(…) — read the calling user’s knowledge base
trove:ingestctx.trove.ingest(…) — write documents into the calling user’s knowledge base

Declaring trove:ingest also says the toolkit writes, so the SDK derives readOnlyHint: false for its tools by default — see Annotations. Most toolkits declare no scopes, which is the right default.

'shared' | 'private' — optional, default private.

private keeps the toolkit to its owner. shared marks it for listing in the toolkit directory. Any other value is rejected at definition time.

Record<string, ManifestConfigField> — optional.

The preference fields shown in the toolkit’s settings. Each key becomes a property on ctx.config; each value describes the input, taking label, type, placeholder, pattern, hint and default. Field-by-field types are in the generated API reference.

Preferences only — never credentials. Settings are stored as user data and Trove refuses a write whose values look credential-shaped; a credential belongs in secrets, where it is encrypted at rest and redacted out of logs.

OAuth2ClientCredentials — optional.

Declarative OAuth2 client-credentials auth: the SDK mints, caches and attaches the Bearer to egress, so handlers issue plain ctx.fetch calls. It is the one part of the declaration that is not copied into manifest.json — it names vault secrets and belongs with the code that uses them. Both credential names must appear in secrets, and both the token host and apiHost must appear in egress. See auth.

ToolDefinition[] — required, at least one.

The tools this toolkit serves. Like auth, they are not part of manifest.json: a manifest says what the toolkit is, and the tool list is read from the deployed toolkit itself. Wrap each definition in tool() so its handler’s arguments are typed from its own input schema. The full tool contract — input, output, annotations, handler — is in the SDK Reference.

Every required field, and nothing else:

extension.ts
import { defineToolkit, tool, z } from '@ontrove/extend/toolkit';
export default defineToolkit({
id: 'my-toolkit',
name: 'My Toolkit',
description: 'What it does, in one line.',
icon: '🧰',
version: '1.0.0',
tools: [
tool({
name: 'echo',
description: 'Echo a message back to the caller.',
input: z.object({ message: z.string().describe('The message to echo.') }),
async handler({ message }) {
return { text: message };
},
}),
],
});

The manifest.json beside it:

manifest.json
{
"id": "my-toolkit",
"name": "My Toolkit",
"description": "What it does, in one line.",
"icon": "🧰",
"version": "1.0.0",
"generated": true
}