From bodhi-js-sdk
Integrate apps with Bodhi App using bodhi-js-sdk. Covers BodhiProvider, useBodhi, access request flow, MCP tool calling, agentic chat, streaming, authentication, and troubleshooting. Always use this skill when a project imports @bodhiapp packages or builds apps connecting to Bodhi App, local LLMs, or MCP servers.
How this skill is triggered — by the user, by Claude, or both
Slash command
/bodhi-js-sdk:bodhi-sdkThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Bodhi App is a platform providing access to local and cloud LLM services and MCP tool servers through an OpenAI-compatible API. Third-party apps integrate via bodhi-js-sdk, which handles connectivity, resource consent, authentication, and API access.
Bodhi App is a platform providing access to local and cloud LLM services and MCP tool servers through an OpenAI-compatible API. Third-party apps integrate via bodhi-js-sdk, which handles connectivity, resource consent, authentication, and API access.
This is the central concept for any Bodhi App integration. Understanding this is essential — without it, API calls for MCPs will return empty results.
Apps don't get automatic access to a user's LLMs or MCPs. Instead:
login(), the app specifies which resources it requires (MCP servers, user role)If your app uses MCPs, you must request them during login(). Without requesting, client.mcps.list() returns nothing.
| Package | Use Case |
|---|---|
@bodhiapp/bodhi-js-react | React web apps (recommended, single install) |
@bodhiapp/bodhi-js | Vanilla JS/TS web apps |
@bodhiapp/bodhi-js-react-ext | React Chrome extensions |
@bodhiapp/bodhi-js-ext | Vanilla JS Chrome extensions |
@bodhiapp/bodhi-js-cli | CLI/headless Node.js apps |
For extension development, see extension-sdk.md.
npm install @bodhiapp/bodhi-js-react
Register at https://developer.getbodhi.app to get a clientId.
https://main-id.getbodhi.app/realms/bodhihttps://id.getbodhi.app/realms/bodhi (SDK default)import { BodhiProvider } from '@bodhiapp/bodhi-js-react';
function App() {
return (
<BodhiProvider
authClientId="your-client-id"
clientConfig={{
authServerUrl: 'https://main-id.getbodhi.app/realms/bodhi', // dev server
}}
>
<MainContent />
</BodhiProvider>
);
}
import { useBodhi, LoginOptionsBuilder } from '@bodhiapp/bodhi-js-react';
function MainContent() {
const { isOverallReady, isAuthenticated, login, showSetup } = useBodhi();
if (!isOverallReady) return <button onClick={showSetup}>Setup Required</button>;
if (!isAuthenticated) {
const loginOpts = new LoginOptionsBuilder().addMcpServer('https://mcp.exa.ai/mcp').build();
return <button onClick={() => login(loginOpts)}>Login</button>;
}
return <ChatInterface />;
}
The requested field tells Bodhi App what resources your app needs. The user sees a consent popup listing these and can approve, modify, or deny each one. You can also construct LoginOptions directly:
login({
requested: {
mcp_servers: [{ url: 'https://mcp.exa.ai/mcp' }],
},
});
// Only returns MCPs the user approved for your app
const { mcps } = await client.mcps.list();
// Chat works without resource requests (uses LLM directly)
const stream = client.chat.completions.create({
model: 'gemma-3n-e4b-it',
messages: [{ role: 'user', content: 'Hello!' }],
stream: true,
});
<BodhiProvider
authClientId="your-client-id" // Required (or provide client prop)
clientConfig={{ // Optional
authServerUrl: '...', // Auth server (default: prod)
redirectUri: '...', // OAuth callback URL (auto-computed)
basePath: '/', // App base path
logLevel: 'warn',
}}
basePath="/" // For sub-path deployments (GitHub Pages)
callbackPath="/callback" // OAuth callback route (auto-computed from basePath)
handleCallback={true} // Auto-handle OAuth redirect (default: true)
logLevel="warn"
>
const {
client, // SDK client — all API calls go through this
isOverallReady, // Client + server ready (use as main gate) — see caveat below
isReady, // Client connected (extension or direct)
isServerReady, // Server responding with status 'ready'
isInitializing, // client.init() in progress
isAuthenticated, // Valid OAuth token
isAuthLoading, // Auth operation in progress
canLogin, // isReady && !isAuthLoading
isExtension, // Connected via Bodhi Browser extension
isDirect, // Connected via direct HTTP
login, // (options?: LoginOptions) => Promise<AuthState | void>
logout, // () => Promise<void>
showSetup, // Opens setup wizard modal
hideSetup,
clientState, // { status, mode, extensionId, url, server, error }
auth, // { status, user, accessToken, error }
setupState, // 'ready' | 'loading' | 'loaded'
} = useBodhi();
Every app that uses MCPs should request them during login:
await login({
// What resources your app needs
requested: {
mcp_servers: [
{ url: 'https://mcp.exa.ai/mcp' }, // Web search
{ url: 'http://localhost:3001' }, // Local MCP server
],
},
// Optional overrides
userRole: 'scope_user_power_user', // Default: 'scope_user_user'
flowType: 'popup', // Default: 'popup' (alternative: 'redirect')
// Progress tracking
onProgress: stage => {
// 'requesting' → 'reviewing' → 'authenticating'
setLoginStage(stage);
},
});
requesting — SDK posts to /bodhi/v1/apps/request-access with your app's client ID, requested resources, and flow type. This endpoint is anonymous — any app can request.reviewing — A popup opens at the returned review_url. The user sees what resources your app is requesting and can approve all, approve some, or deny. SDK polls for the decision.authenticating — Once approved, SDK performs OAuth 2.0 + PKCE with the granted scope. The token carries claims for approved resources.isAuthenticated becomes true. Approved resources are now available via client.mcps.list().| Field | Type | Default | Description |
|---|---|---|---|
requested | RequestedResourcesV1 | none | MCPs your app needs (version auto-injected) |
userRole | UserScope | 'scope_user_user' | 'scope_user_user' or 'scope_user_power_user' |
flowType | FlowType | 'popup' | 'popup' or 'redirect' |
redirectUrl | string | client redirectUri | Return URL for redirect flow |
onProgress | (stage) => void | none | Progress callback |
pollIntervalMs | number | 2000 | Polling interval (popup flow) |
pollTimeoutMs | number | 300000 | Polling timeout (popup flow, 5min) |
Fluent builder for constructing LoginOptions:
import { LoginOptionsBuilder } from '@bodhiapp/bodhi-js-react';
const opts = new LoginOptionsBuilder()
.setRole('scope_user_power_user')
.addMcpServer('https://mcp.exa.ai/mcp')
.addMcpServer('http://localhost:3001')
.setFlowType('popup')
.setOnProgress(stage => console.log(stage))
.build();
await login(opts);
Since requested must be passed per-login call, create a wrapper for consistent behavior:
// hooks/useBodhiApp.ts
import { useBodhi, type LoginOptions } from '@bodhiapp/bodhi-js-react';
const APP_RESOURCES = {
mcp_servers: [{ url: 'https://mcp.exa.ai/mcp' }],
} as const;
export function useBodhiApp() {
const bodhi = useBodhi();
return {
...bodhi,
login: (options?: LoginOptions) =>
bodhi.login({
requested: APP_RESOURCES,
...options,
}),
};
}
const stream = client.chat.completions.create({
model: 'gemma-3n-e4b-it',
messages: [{ role: 'user', content: prompt }],
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices?.[0]?.delta?.content || '';
setResponse(prev => prev + content);
}
For apps that use MCP tools in chat (tool calling + agent loop), see agentic-patterns.md for the complete implementation guide. Quick overview:
// 1. Request MCP access during login
await login({ requested: { mcp_servers: [{ url: 'https://mcp.exa.ai/mcp' }] } });
// 2. List approved MCPs — each has a path for proxy connection
const { mcps } = await client.mcps.list();
// 3. Create MCP client using createMcpClient(client, mcp.path) for tool discovery and execution
import { createMcpClient } from '@bodhiapp/bodhi-js-react/mcp';
const mcpClient = await createMcpClient(client, mcps[0].path);
const tools = await mcpClient.listTools();
// See agentic-patterns.md for the full agent loop implementation
import { CliClient } from '@bodhiapp/bodhi-js-cli';
import { createMcpClient } from '@bodhiapp/bodhi-js-cli/mcp';
const client = new CliClient({ authClientId, authServerUrl, serverUrl });
await client.login({
requested: { mcp_servers: [{ url: 'https://mcp.exa.ai/mcp' }] },
onReviewUrl: url => console.log(url),
});
const mcps = await client.mcps.list();
for (const mcp of mcps.mcps) {
const mcpClient = await createMcpClient(client, mcp.path);
const tools = await mcpClient.listTools();
const result = await mcpClient.callTool({ name: 'search', arguments: { query: 'AI news' } });
await mcpClient.close();
}
client.models.list() returns an AsyncGenerator:
const models: string[] = [];
for await (const model of client.models.list()) {
models.push(model.id);
}
Generate vector embeddings from text:
const response = await client.embeddings.create({
model: 'nomic-embed-text-v1.5',
input: 'text to embed',
});
const embedding = response.data[0].embedding; // number[]
import { isApiResultSuccess, isApiResultOperationError } from '@bodhiapp/bodhi-js-react';
const result = await client.sendApiRequest('GET', '/v1/models');
if (isApiResultSuccess(result)) {
console.log(result.body);
} else if (isApiResultOperationError(result)) {
console.error(result.error.message, result.error.type);
}
For streaming, errors are thrown — use try/catch:
try {
for await (const chunk of stream) {
/* ... */
}
} catch (err) {
console.error('Stream error:', err instanceof Error ? err.message : err);
}
function App() {
const { isOverallReady, isAuthenticated, showSetup, login } = useBodhi();
if (!isOverallReady) return <button onClick={showSetup}>Setup Required</button>;
if (!isAuthenticated) return <button onClick={() => login({ requested: APP_RESOURCES })}>Login</button>;
return <YourAppContent />;
}
window.bodhiext.http://localhost:1135. Requires Chrome 130+ LNA.showSetup()) guides installation.bodhi-js-sdk/core/src/interface.ts — UIClient interface definitionbodhi-js-sdk/core/src/openai-client-compat.ts — Chat, Models, Embeddings, Mcps (list only)bodhi-js-sdk/core/src/mcp.ts — createMcpClient factory, McpTransportProvider interfacebodhi-js-sdk/cli/src/cli-client.ts — CliClient with login(), createMcpTransportConfig()bodhi-js-sdk/core/src/access-request.ts — AccessRequestBuilder, LoginOptionsBuilder, polling logicbodhi-js-sdk/web/src/direct-client.ts — login() implementation with access request flowbodhi-js-sdk/core/src/types/index.ts — LoginOptions, LoginProgressStagebodhi-js-sdk/react-core/src/BodhiProvider.tsx — React provider, callback handlingsdk-test-app/web/src/ — Reference app with full integrationCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub bodhisearch/bodhi-js --plugin bodhi-js-sdk