solarbuild-mcp
MCP server for the Solar framework. Gives AI agents live access to your component registry — call manifest(), component(), and validate() tools to discover, inspect, and validate Solar components at runtime.
README
Solar
A runtime UI framework built for AI-generated code.
When an AI agent builds UI with React or Vue, it guesses. It guesses prop names, types, and valid values. When it gets something wrong, it gets a stack trace. Stack traces are not machine-readable feedback.
Solar gives agents a clear path. Call registry.manifest() and you get a JSON schema of every registered component, including what props it takes and what values are valid. Mount a component with wrong props and you get a ContractError, a structured object with expected, received, and fix fields. The agent reads fix and retries. No human required.
Core principles
- Explicit contracts over conventions: props are typed and validated at the component boundary, not inferred
- Component registry: models read a machine-readable manifest before generating any composition code
- Structured errors: validation failures return parseable JSON an agent can act on directly
- Declared side effects: effects declare what they depend on and what they touch; no implicit subscriptions
- Typed composition: slot props enforce which component can fill them, with runtime validation
- Runtime-based: no compiler required; debuggable in the browser as-is
- Small surface area: fewer primitives means fewer ways to generate something wrong
How it works
Defining a component
Every component declares its shape explicitly. Register it so other components and models can discover it.
import { createElement, defineComponent, registry } from './framework/index.js'
const Button = defineComponent({
name: 'Button',
props: {
label: { type: 'string', required: true },
onClick: { type: 'function', required: true },
variant: { type: 'string', enum: ['primary', 'secondary'], default: 'primary' },
},
render({ label, onClick, variant }) {
return createElement('button', { class: variant, onclick: onClick }, label)
}
})
registry.register(Button)
Structured errors
Pass the wrong type and you get a structured error, not a string. An agent can parse and self-correct without regex.
Button({ label: 42, onClick: () => {} })
// throws ContractError:
{
"error": "ContractError",
"component": "Button",
"prop": "label",
"expected": "string",
"received": "number",
"fix": "Pass a string value for \"label\""
}
Component registry
Before generating composition code, a model reads the full catalog:
registry.manifest()
// →
[
{
"name": "Button",
"props": {
"label": { "type": "string", "required": true },
"onClick": { "type": "function", "required": true },
"variant": { "type": "string", "enum": ["primary", "secondary"], "default": "primary" }
}
},
...
]
Typed slots (component composition)
A parent component declares which component type can fill a slot. Passing anything else throws a ContractError.
const Card = defineComponent({
name: 'Card',
props: {
title: { type: 'string', required: true },
action: { type: 'slot', accepts: 'Button', required: true },
},
render({ title, action }) {
return createElement('div', { class: 'card' },
createElement('h3', {}, title),
action,
)
}
})
// valid
Card({ title: 'Hello', action: Button({ label: 'Go', onClick: () => {} }) })
// throws: Card: prop "action": expected slot(Button), got vnode with no _source
Card({ title: 'Hello', action: createElement('button', {}, 'Go') })
Compact h() notation
h() parses a dense array format into a vnode tree. It's registry-aware, so component names resolve automatically.
import { h } from './framework/index.js'
// plain DOM nodes
h(['div', { class: 'row' },
['p', {}, 'Hello'],
['button', { class: 'btn' }, 'Click'],
])
// registered component by name, dispatches to Button()
h(['Button', { label: 'Save', onClick: handleSave, variant: 'primary' }])
Hooks and effects
State is useState. Side effects use three explicit primitives instead of a general useEffect:
useResource: async data fetching with automatic AbortController cancellation on key changeuseSubscription: event listener that attaches and detaches when source/event/handler changesonMount/onUnmount: lifecycle callbacks that run once, not on every render
const UserCard = defineComponent({
name: 'UserCard',
props: { userId: { type: 'number', required: true } },
render({ userId }) {
const [width, setWidth] = useState(window.innerWidth)
// cancels the previous fetch automatically when userId changes
const { data, loading, error } = useResource({
key: userId,
fetch: async (signal) => {
const res = await fetch(`/api/user/${userId}`, { signal })
return res.json()
},
})
// attaches once, re-attaches only if source/event/handler changes
useSubscription({ source: window, event: 'resize', handler: () => setWidth(window.innerWidth) })
onMount(() => analytics.track('UserCard mounted'))
onUnmount(() => analytics.track('UserCard unmounted'))
if (loading) return createElement('p', {}, 'Loading...')
return createElement('p', {}, `${data.name} - viewport: ${width}px`)
}
})
Architecture
framework/
├── core/
│ ├── createElement.js # vnode factory
│ ├── render.js # mount vnode tree to DOM
│ ├── diff.js # reconcile old and new vnode trees
│ ├── hooks.js # useState, useMemo, useResource, useSubscription, onMount, onUnmount
│ └── scheduler.js # batch and defer re-renders
├── contract/
│ ├── defineComponent.js # strict component definition with prop schema
│ ├── ContractError.js # structured error class with toJSON()
│ ├── validate.js # runtime prop validation
│ └── types.js # primitive type definitions + slot type
├── runtime/
│ ├── reconciler.js # component lifecycle and mount/unmount
│ └── events.js # delegated event handling
├── registry.js # component catalog and manifest
├── h.js # compact array notation parser
└── index.js # public API
Components live in components/, one per file, default export is the defineComponent call, self-registered on import. Rules a model can follow without inference.
Live demo
Open in StackBlitz — runs a 3-step agent loop in the browser: discover components via registry.manifest(), catch a ContractError on a bad mount, self-correct using the fix field.
Getting started
npm create solarbuild@latest my-app
cd my-app
npm run dev
Cursor plugin: cursor.directory/plugins/framework-solar — adds Solar rules to Cursor so your AI assistant knows the API out of the box.
Or use the CDN directly. No install, no build step:
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script type="module">
import {
defineComponent, mountComponent, registry,
useState, createElement
} from 'https://cdn.jsdelivr.net/npm/solarbuild/framework/index.js'
const Counter = defineComponent({
name: 'Counter',
props: {},
render() {
const [count, setCount] = useState(0)
return createElement('button', { onclick: () => setCount(n => n + 1) }, `Clicked ${count} times`)
}
})
registry.register(Counter)
mountComponent(Counter, {}, document.getElementById('app'))
</script>
</body>
</html>
Running the demo
npm run dev
Open http://localhost:3456/demo/ (trailing slash required; it's a static server quirk).
The demo covers all framework features: static rendering, diffing, hooks, contract validation, declared effects, batched updates, registry manifest, compact h() notation, and typed slots.
MCP Server
solarbuild-mcp is an MCP server that exposes your Solar component registry as callable tools. AI agents can discover components, validate props, and self-correct at runtime — without reading static docs.
npm install solarbuild-mcp
{
"mcpServers": {
"solarbuild": {
"command": "npx",
"args": ["solarbuild-mcp", "--components", "./components"]
}
}
}
Tools: manifest (full registry schema), component (single component schema), validate (props validation with fix instructions).
→ MCP Server docs · npm
Docs
Full documentation at docs.solarbuild.dev
What this is not
- Not a meta-framework. No SSR, no routing, no build pipeline.
- Not a React replacement. Narrower scope, different constraints.
- Not optimized for humans writing components by hand, though it works fine for that too.
The thesis: the next wave of frameworks won't be designed for developers. They'll be designed for the models developers use to write code. This is an early attempt at that.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.