# Build a devtool once. Mount it anywhere

> Source: <https://devfra.me>
> Published: 2026-09-23 15:47:20+00:00

# Build a devtool once. Mount it anywhere.

``` js
import { defineDevframe } from 'devframe'
import { initDevframe } from 'devframe/initiate'

const myDevframe = defineDevframe({
  id: 'my-tool',
  name: 'My Tool',
  rpc: [getModules],
  view: { type: 'spa', distDir: './dist/client' },
})

// one Request → Response handler, any host framework
const { handler } = initDevframe(myDevframe, {
  base: '/__my-tool/',
})
```

// Foundation //

## One definition, every entry point

`defineDevframe()` describes a tool once. `initDevframe()` turns it into a Web Standard `Request → Response` handler, and adapters reshape that same definition into whatever your package ships.

```
One Standard Handler
Adapters as Conveniences
Type-safe RPC & Shared State
Visual and Agentic
```

// Portability //

## The same handler, mounted natively

A devframe's boundary is simply the Web Standard `Request` and `Response`. Any framework that speaks that (or connect-style middleware) mounts the same tool and inherits the whole ecosystem. Only the host-framework-facing glue changes. [See all adapters](https://devfra.me/adapters/initiate).

``` js
import { Hono } from 'hono'
import { devtools } from './devtools'

const app = new Hono()

app.all(`${devtools.base}*`, c => devtools.handler(c.req.raw))
```

// Adapters //

## Package it the way your tool ships

The handler is the smallest common denominator. Higher-level adapters package that same definition into familiar forms; pick the entry points your package needs. [Browse the adapters](https://devfra.me/adapters).

``` python
import { createCac } from 'devframe/adapters/cac'
import myDevframe from './my-tool'

createCac(myDevframe).parse()
```

// Interfaces //

## One capability, two interfaces

RPC functions stay private by default and opt into agent exposure explicitly. The [MCP adapter](https://devfra.me/adapters/mcp) translates those functions, resources, and selected shared state into an agent-consumable interface: the presentation changes, the source of truth stays the same.

``` js
import { defineRpcFunction } from 'devframe'

export const inspectBuild = defineRpcFunction({
  name: 'inspect-build',
  type: 'query',
  handler: () => readBuildGraph(),
})
```

// Hub //

## From one devframe to a devtools hub

When several devtools run at once, discovery becomes the problem. `@devframes/hub` is a headless composition layer: many devframes register docks, commands, terminals, and shared state, and appear through one consistent entry. [Learn about the hub](https://devfra.me/guide/hub).

``` js
import { initHub } from '@devframes/hub/initiate'
import { createTerminalsDevframe } from '@devframes/plugin-terminals'

const hub = initHub({
  base: '/__devframes/',
  devframes: [
    createTerminalsDevframe(),
    // ...more devframes
  ],
  ui: await import('@devframes/hub-ui').then(m => m.createUi()),
})
```

## Ship your devtool everywhere

`DevframeDefinition` and pick the entry points your package ships: hosted, standalone, embedded, or agentic.
