Skip to content

Manifest Reference

NimbleBrain reads host metadata from the _meta["ai.nimblebrain/host"] key in your MCPB manifest.json. This is where you declare your app’s name, icon, placements (sidebar items, main views, and settings sections), and briefing facets.

manifest.json
{
"manifest_version": "0.4",
"name": "@myorg/my-app",
"version": "1.0.0",
"description": "My NimbleBrain app",
"author": {
"name": "My Org",
"email": "dev@myorg.com",
"url": "https://myorg.com"
},
"server": {
"type": "python",
"mcp_config": {
"command": "python",
"args": ["-m", "my_app.server"]
}
},
"_meta": {
"ai.nimblebrain/host": {
"host_version": "1.0",
"name": "My App",
"icon": "database",
"placements": [
{
"slot": "main",
"resourceUri": "ui://dashboard",
"priority": 50,
"label": "My App",
"icon": "database",
"route": "my-app",
"size": "full"
},
{
"slot": "settings",
"resourceUri": "ui://settings",
"label": "My App",
"icon": "settings"
}
]
}
}
}

These fields are defined by the MCPB specification. NimbleBrain supports both v0.3 and v0.4 formats.

FieldTypeRequiredDescription
manifest_versionstringNoMCPB spec version ("0.3" or "0.4").
namestringYesScoped package name (e.g., @myorg/my-app).
versionstringYesSemver version string.
descriptionstringNoHuman-readable description.
authorobjectNoAuthor info: name, email, url.
server.typestringYesRuntime type: "python", "node", "binary", or "uv".
server.entry_pointstringNoEntry point file path.
server.mcp_configMcpConfigYesSpawn configuration (see below).
_metaobjectNoExtension metadata. NimbleBrain reads _meta["ai.nimblebrain/host"].
FieldTypeRequiredDescription
commandstringYesExecutable to run ("python", "node", etc.).
argsstring[]NoCommand-line arguments. Use ${__dirname} for the bundle directory.
envRecord<string, string>NoEnvironment variables passed to the process.

Host metadata: _meta["ai.nimblebrain/host"]

Section titled “Host metadata: _meta["ai.nimblebrain/host"]”
FieldTypeRequiredDescription
host_versionstringYesHost extension contract version: "1.0" or "1.1". 1.1 is the version that adds the host_capabilities block — set it when you declare that block.
namestringRecommendedDisplay name shown in the sidebar and app list. Without a name the host surfaces no UI for the bundle, so it’s required in practice for any app with a presence in the shell.
iconstringNoLucide icon name in kebab-case (e.g., "database", "message-square", "hand"). Defaults to "".
categorystringNoReserved, free-form string. Not currently consumed by the host — declare it only as forward-looking metadata.
placementsPlacementDeclaration[]NoShell layout placement declarations — sidebar items, main views, and settings sections. See Placements.
briefingobjectNoDaily briefing contribution. See briefing below.
host_capabilitiesobjectNoNimbleBrain host capabilities this bundle requires or prefers. Declare host_version: "1.1" alongside it. See Host Capabilities.

A settings section is a placement with slot: "settings", not a separate top-level object. Add an entry to placements and the host renders it as a tab under Settings → This Workspace → Apps → <your app>, served from the placement’s resourceUri:

{
"slot": "settings",
"resourceUri": "ui://settings",
"label": "My App",
"icon": "settings"
}

The resourceUri points at the ui:// page that renders the settings HTML; label and icon set the tab’s appearance. See Placements for the full PlacementDeclaration shape.

Apps can contribute to the daily briefing on the Home dashboard by declaring a briefing block. The platform reads entity data from disk and presents a business-language summary.

FieldTypeRequiredDescription
briefing.priority"high" | "medium" | "low"NoRelative ordering in the briefing. High-priority apps appear first. Default: "medium".
briefing.facetsBriefingFacet[]YesSummary dimensions this app contributes.

Each facet describes one piece of data to surface in the briefing:

FieldTypeRequiredDescription
namestringYesUnique identifier (kebab-case).
labelstringYesHuman-readable label (e.g., “Pipeline”, “Blocked tasks”).
typestringYesHow this facet is treated: "attention" (needs action now), "upcoming" (coming soon), "activity" (something happened), "delta" (state changed), "kpi" (headline number).
entitystringNoUpjack entity to query. Reads JSON files from the entity data directory.
queryobjectNoMongoDB-style filter (powered by sift.js). Supports $eq, $ne, $gt, $gte, $lt, $lte, $in, $exists. String values like "${today}" are resolved at runtime.
toolstringNoMCP tool to call at briefing time (e.g., "newsapi__search_news").
tool_inputobjectNoArguments passed to the tool.
resourcestringNobriefing:// URI — the app computes its own summary via MCP resource.
metric"count" | "sum" | "list"NoAggregation hint.
descriptionstringNoFallback description if data can’t be resolved.

Each facet resolves via one of entity, tool, or resource.

Query variables resolved at runtime:

VariableValue
${today}Current date as YYYY-MM-DD
${now}Current ISO timestamp
${period.since}Start of the briefing period (24 hours ago)

Example:

"briefing": {
"priority": "medium",
"facets": [
{
"name": "due_today",
"label": "Due today",
"type": "upcoming",
"entity": "task",
"query": { "due_date": "${today}", "completed_at": null },
"metric": "count",
"description": "Tasks due today that aren't done"
},
{
"name": "blocked_tasks",
"label": "Blocked",
"type": "attention",
"entity": "task",
"query": { "column": "blocked", "status": "active" },
"metric": "count",
"description": "Tasks stuck in blocked column"
}
]
}

Each entry in the placements array:

FieldTypeRequiredDefaultDescription
slotstringYesShell slot to fill: "sidebar", "sidebar.apps", "sidebar.bottom", "main", or "settings". See Placements.
resourceUristringYesui:// URI served by this MCP server.
prioritynumberNo100Sort order within the slot. Lower values appear first.
labelstringNoHuman-readable label for sidebar items and tabs.
iconstringNoLucide icon name (kebab-case). Falls back to CircleDot if unset or unrecognized.
routestringNoRoute path for "main" slot placements. Registers as /app/<route>.
size"compact" | "full" | "auto"NoSize hint for the slot renderer.

placements is what the shell renders and routes from. To give your app a navigable main view, declare a "main" slot placement with a route:

"placements": [
{
"slot": "main",
"resourceUri": "ui://dashboard",
"route": "my-app",
"label": "My App",
"icon": "database"
}
]

The virtual primary resource path resolves to the first declared placement’s resourceUri, so the shell can load your main view without knowing its URI ahead of time. See UI Resources for how primary resolves.

The schema also accepts a primaryView object for back-compat, but the host does not consume it — placements are the source of truth for what is registered and routed. Declare a "main" placement, not primaryView.

Icons use Lucide names. The resolver accepts both kebab-case and PascalCase:

  • "message-square" resolves to the MessageSquare component
  • "database" resolves to Database
  • "hand" resolves to Hand

If the icon name is not recognized, CircleDot is used as the default fallback.

The smallest manifest that registers a UI view:

manifest.json
{
"name": "@myorg/hello",
"version": "0.1.0",
"server": {
"type": "node",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/dist/index.js"]
}
},
"_meta": {
"ai.nimblebrain/host": {
"host_version": "1.0",
"name": "Hello",
"icon": "hand",
"placements": [
{
"slot": "main",
"resourceUri": "ui://index",
"route": "hello",
"label": "Hello"
}
]
}
}
}

No _meta needed for tools-only bundles:

manifest.json
{
"name": "@myorg/calculator",
"version": "1.0.0",
"server": {
"type": "python",
"mcp_config": {
"command": "python",
"args": ["-m", "calculator.server"]
}
}
}