Composable JSX for prompts. Type-safe tools and skills, defined once, used in code or served as CLI help, docs, SKILL.md and an MCP server.
bun add contextjsx # or npm i contextjsxIncludes the contextjsx command line; contextjsx --help lists it.
- Core. Prompts composed like UI in JSX, commands, namespaces and skills typed in code. 0.8 kB
- Emit. Export as MCP instructions, CLI help, argv parsing, Markdown docs and skill files. 4.4 kB
- Serve. Your commands and skills as an MCP server. 4.7 kB
Compose prompts from components, like UI.
import { render, Data } from 'contextjsx';
const Access = ({ mode }: { mode: 'read' | 'write' }) => (
<access>{mode === 'write' ? 'You may edit and push.' : 'Read only.'}</access>
);
export const system = render(
<system>
<Access mode={access} />
<event source="slack">
<Data>{message}</Data>
</event>
</system>,
);Plain JSX, no React. Use any lowercase tag for markup, capitalized names are for components.
Built-in components:
Dataescapes text from users or tools, so it cannot break the markup of your prompt.<Data value={obj} />prints an object as JSON.
Setup, two lines in tsconfig:
{ "jsx": "react-jsx", "jsxImportSource": "contextjsx" }Or per file, next to another JSX runtime:
/** @jsxImportSource contextjsx */Describe what your product can do, once.
import * as z from 'zod';
import { defineApp, defineCommand, defineSkill } from 'contextjsx';
// The CLI command `dispatch` and the MCP tool `dispatch`.
const dispatch = defineCommand({
name: 'dispatch',
description: 'Dispatch a task',
// The schema of the command's arguments and the tool's input.
args: { prompt: z.string().describe('what to do') },
// `read` becomes readOnlyHint.
access: 'write',
run: async ({ prompt }) => start(prompt),
});
const review = defineSkill({
name: 'review',
description: 'Review a change before merging',
body: <>Read the diff, run the tests, list what would break.</>,
});
// One root: commands for the CLI, instructions and skills for models.
export const app = defineApp({
name: 'app',
description: 'Delegate coding tasks',
commands: [dispatch],
skills: [review],
instructions: 'Start work with dispatch, then read results.',
});defineNamespace groups commands and skills under a word (task dispatch, tool task/dispatch) and nests. A skill can also be a markdown file: defineSkill({ file }), its frontmatter giving name and description. mcp: false keeps a command off MCP.
Generate the text your users and agents read, from that definition.
contextjsx emit ./app.ts --all --out .Or in code:
import {
instructions,
cli,
parse,
run,
markdown,
skills,
} from 'contextjsx/emit';
// What your MCP server tells the model: your text, then its tools and skills.
const text = instructions(app);
// Help text for `dispatch --help`.
const help = cli(app, dispatch);
// Typed args from argv; throws ParseError naming the bad arg.
const { prompt } = parse(dispatch, ['--prompt', 'fix the build']);
// index.md plus a page per namespace.
markdown(app, { outDir: './docs' });
// One SKILL.md per skill.
skills(app, { outDir: './skills' });Without outDir, markdown and skills return the files as strings instead of writing them.
One call can be your whole CLI: help, parsing, the command, its output. The contextjsx bin is this line; its reference is docs/cli.md.
process.exitCode = await run(app, process.argv.slice(2));Expose your commands and skills to other people's agents over MCP.
contextjsx serve ./app.tsOr in code:
import { serve } from 'contextjsx/serve';
// The agent spawns you.
await serve(app, 'stdio');
// Or the agent connects to you.
const { url, token } = await serve(app, { http: {} });Each command becomes a tool, dispatch or task/dispatch; your instructions and skills come along, loaded by the agent when it needs them. The app's description, and its optional title, version, websiteUrl and icons, are the server's serverInfo; instructions say how to use it.
One command runs everything.
bun run gate # build, tsc, lint, tests, benchmarkLicensed under MIT with ❤️