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, category, sidebar placements, briefing facets, and settings section.

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",
"category": "custom",
"primaryView": {
"resourceUri": "ui://dashboard"
},
"placements": [
{
"slot": "sidebar",
"resourceUri": "ui://nav",
"priority": 50,
"label": "My App",
"icon": "database",
"route": "my-app",
"size": "compact"
}
],
"settings": {
"id": "my-app",
"label": "My App",
"icon": "settings",
"resourceUri": "ui://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_versionstringYesSchema version. Currently "1.0".
namestringYesDisplay name shown in the sidebar and app list.
iconstringYesLucide icon name in kebab-case (e.g., "database", "message-square", "hand").
categorystringNoApp category: "sales", "marketing", "operations", "research", "finance", "hr", "engineering", "support", or "custom".
primaryViewobjectNoThe main view loaded when a user navigates to your app.
primaryView.resourceUristringYes (if primaryView set)ui:// URI for the main view (e.g., "ui://dashboard").
placementsPlacementDeclaration[]NoExplicit shell layout placement declarations. See Placements.
briefingobjectNoDaily briefing contribution. See briefing below.
FieldTypeRequiredDescription
settings.idstringYesUnique section ID across all bundles.
settings.labelstringYesTab label in the settings UI.
settings.iconstringYesLucide icon name for the settings tab.
settings.resourceUristringYesui:// URI that renders the settings section HTML.

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.conversations", "sidebar.bottom", "main", "toolbar.right".
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.

If your manifest has placements, those are used directly. If it only has primaryView (no placements array), NimbleBrain converts it to a single "main" slot placement automatically:

// Legacy conversion (from PlacementRegistry.registerLegacy)
{
slot: "main",
resourceUri: uiMeta.primaryView.resourceUri,
label: uiMeta.name,
icon: uiMeta.icon,
route: bundleName // e.g., "@myorg/my-app"
}

The route for legacy bundles uses the scoped bundle name as the path. For @myorg/my-app, the URL becomes /app/@myorg/my-app.

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",
"primaryView": {
"resourceUri": "ui://index"
}
}
}
}

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"]
}
}
}