Morphos & Kosmesis are here.Explore
PraxisJS

MCP Server

@praxisjs/mcp — Model Context Protocol server for PraxisJS. Gives AI assistants the docs, an API reference, project inspection, validation, scaffolding and a versioned project memory.

MCP Server

@praxisjs/mcp is a Model Context Protocol server for PraxisJS. It works with any MCP-compatible AI assistant — Claude Code, Codex, Cursor, VS Code/Copilot, Windsurf, and others — and gives it:

  • The docs: PraxisJS, Morphos and Kosmesis — search by topic, read pages, look up any export with the signature of the version you have installed.
  • UI building blocks: the Kosmesis registry (including your custom registries) and the Morphos primitives and Kosmesis components your project already uses, with their versions and local edits.
  • Your project: installed versions, pages, components, stores, routes and folder layout, read by static analysis.
  • Checks: a validator for the PraxisJS rules assistants most often break, and scaffolding plans that follow your folder structure.
  • A project memory: .praxis/, a versioned catalog of your reusable code, business rules, decisions and conventions that assistants read and update only through the server.
npm install -D @praxisjs/mcp
pnpm add -D @praxisjs/mcp
yarn add -D @praxisjs/mcp
bun add -d @praxisjs/mcp

No install required. Run directly:

npx -y @praxisjs/mcp

Setup

npx praxisjs ai add configures the server for Claude Code and Codex together with the AI skills. To set it up by hand, pick your AI client:

Add it to .mcp.json at the project root (commit it so the whole team gets it):

{
  "mcpServers": {
    "praxisjs": {
      "command": "npx",
      "args": ["-y", "@praxisjs/mcp"]
    }
  }
}

or run claude mcp add --scope project praxisjs -- npx -y @praxisjs/mcp. Claude Code asks you to approve project servers the first time; "enabledMcpjsonServers": ["praxisjs"] in .claude/settings.json approves it ahead of time. Check it with /mcp.

Claude Code reads MCP servers from .mcp.json (project) or ~/.claude.json (user), not from an mcpServers key in .claude/settings.json. Earlier versions of praxisjs ai add wrote it there, where the server never started: run ai add again to fix it.

Add it to .codex/config.toml at the project root (Codex loads project config in trusted projects), or to ~/.codex/config.toml for every project:

[mcp_servers.praxisjs]
command = "npx"
args = ["-y", "@praxisjs/mcp"]

or run codex mcp add praxisjs -- npx -y @praxisjs/mcp.

Add to .cursor/mcp.json at the project root (or ~/.cursor/mcp.json for global):

{
  "mcpServers": {
    "praxisjs": {
      "command": "npx",
      "args": ["-y", "@praxisjs/mcp"]
    }
  }
}

Then enable it in Cursor Settings → MCP.

Add to .vscode/mcp.json at the project root:

{
  "servers": {
    "praxisjs": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@praxisjs/mcp"]
    }
  }
}

Click Start in the config file editor, or use the MCP panel in the GitHub Copilot Chat sidebar.

Add to ~/.codeium/windsurf/mcp_config.json, and set the project with PRAXISJS_PROJECT_ROOT (see below), since the config is global:

{
  "mcpServers": {
    "praxisjs": {
      "command": "npx",
      "args": ["-y", "@praxisjs/mcp"],
      "env": { "PRAXISJS_PROJECT_ROOT": "/path/to/your/project" }
    }
  }
}

If you prefer a local install over npx, run npm install -D @praxisjs/mcp and replace "command": "npx", "args": ["-y", "@praxisjs/mcp"] with "command": "./node_modules/.bin/praxisjs-mcp".

Which project it works on

The project tools and the project memory work on one folder, chosen in this order:

  1. PRAXISJS_PROJECT_ROOT, if set in the server's environment.
  2. CLAUDE_PROJECT_DIR, which Claude Code sets for the servers it starts.
  3. The first folder the client announces through MCP roots (VS Code, Claude Code and others do).
  4. The server's working directory.

From there it walks up to the nearest folder with a package.json, so starting it from src/ still serves the whole project. praxisjs_inspect_project prints the root in use.


Tools

GroupToolWhat it does
Docspraxisjs_overviewThe docs index (llms.txt)
praxisjs_get_pageOne docs page as Markdown
praxisjs_full_docsEvery page concatenated (large)
praxisjs_search_docsDocs sections matching a topic, with snippets
praxisjs_api_referenceOne export: package, kind, installed signature, docs section
praxisjs_list_apisThe public API by kind and package
praxisjs_get_install_commandInstall command for @praxisjs/* / @morphos/* packages, without versions
Projectpraxisjs_inspect_projectVersions, configuration, pages, components, stores, routes, layout, UI libraries
praxisjs_validate_filePraxisJS rules check for a file or folder
praxisjs_scaffoldWhere a new file goes and how it starts
praxisjs_ui_componentsKosmesis registry components and their install status
Memorypraxisjs_memory_initCreates .praxis/ and records the project structure
praxisjs_memory_list / _get / _searchRead entries
praxisjs_memory_add / _update / _removeChange entries
praxisjs_memory_upsertAdd or update many entries at once, with a dry run
praxisjs_memory_bootstrap_planThe first full analysis of the project, split into parallel passes
praxisjs_memory_mapCompares the code with the memory: unrecorded code, stale entries
praxisjs_memory_check_duplicate"Is there already something that does X?"
praxisjs_memory_migrateBrings an older .praxis/ to the current format

Every tool declares MCP annotations (read-only, destructive, idempotent), so clients can skip confirmations for the read-only ones.

Docs tools

Every docs tool takes an optional library: "praxisjs" (default), "morphos" or "kosmesis". The three sites publish the same llms.txt, llms-full.txt and per-page Markdown, so search, pages and snippets work the same for Morphos and Kosmesis. The docs tools need network access; offline, the signatures of installed packages are still available through praxisjs_api_reference.

Docs responses are kept in memory for 10 minutes, so repeated calls in a session don't hit praxisjs.org again, and calls for the same page at the same time share one request. A failed request is not cached, and a request that takes longer than 15 seconds fails with an error.

praxisjs_get_page takes a slug relative to /docs (essentials/components, decorators/performance, advanced/custom-decorators…). The /docs/… links listed by praxisjs_overview and full https://praxisjs.org/docs/… URLs work too, with or without a trailing slash or #anchor.

praxisjs_search_docs takes a query in plain words ("debounce an input handler"), and optionally limit (default 8) and includeChangelog (default false). It splits the documentation into sections and ranks them by how many of the query's words they contain (names and headings weigh more), returning slug#anchor — Page › Heading and the best matching line of each.

praxisjs_api_reference takes a symbol ("Debounce", "@State", "For", "createFieldDecorator"). It answers with the package, the kind (field decorator, composable, component…), whether it is reactive or StatefulComponent-only, the import line, the docs page, the docs sections about it and the TypeScript signature read from your installed package (node_modules/<package>, following its .d.ts re-exports), so the signature always matches your version. A name PraxisJS doesn't export is looked up in the installed @morphos/* packages; an unknown name gets close suggestions.

praxisjs_list_apis lists the API by kind (field-decorator, method-decorator, class-decorator, getter-decorator, decorator-builder, composable, component, base-class, function, class) and package; signatures: true adds the installed signatures.

praxisjs_get_install_command generates the command to install @praxisjs/* and @morphos/* packages (and the praxisjs, create-praxisjs and kosmesis CLIs) without pinning versions — never write version constraints directly into package.json.

FieldTypeDefaultDescription
packagesstring[]—One or more @praxisjs/* or @morphos/* package names (praxisjs, create-praxisjs and kosmesis are accepted too). Duplicates are dropped.
manager"npm" | "pnpm" | "yarn" | "bun""npm"Package manager to use
devbooleantrueInstall as devDependency. Pass false for packages the app imports at runtime (core, decorators, jsx, runtime, router, …).
packages: ["@praxisjs/router", "@praxisjs/store"], manager: "pnpm", dev: false
→  pnpm add @praxisjs/router @praxisjs/store

Project tools

The project tools read your source files (.ts, .tsx, .js, .jsx, skipping node_modules, build output, dot-folders and declaration files) with a small scanner built into the server: no TypeScript compiler or other dependency is loaded, and a file in the middle of an edit is read up to where it breaks.

praxisjs_inspect_project reports the @praxisjs/* packages (declared range and installed version), tsconfig options PraxisJS needs, installed AI integrations, whether the project memory exists, and the code grouped as pages, components (stateful/stateless, props, route), stores, composables, services, custom decorators, stylesheets and exported helpers, plus the routes from @Router([...]) tables and @Route decorators, the folder each kind of file lives in (or the features of a feature-slice project), and a UI libraries section: installed @morphos/* packages and which primitives each file imports; the Kosmesis setup from components.json (style system, components folder, registries) and every installed component with its version from components.lock.json, whether a newer one is in the registry and whether its files were edited locally. Components installed before Kosmesis kept a lock are listed by file name as untracked.

praxisjs_validate_file takes a path (a file, or a folder to check every source file in it) or source text with a filename. Rules:

RuleSeverityFlags
missing-componenterrorA class extending StatefulComponent/StatelessComponent without @Component()
component-ordererror@Component() not the innermost class decorator
missing-rendererrorA component without render()
accessor-keyworderror / warningA decorated accessor field (field decorators throw on it) / any accessor field
stateless-decoratorerror@State, @Prop, @Watch, @Emit, @Slot… on a StatelessComponent
stateful-propserrorthis.props in a StatefulComponent
core-exporterrorImporting a name @praxisjs/core doesn't export (signal, effect…)
compiler-helper-importerrorImporting the _$ helpers the Vite plugin emits
decorator-orderwarning@State() / @Prop() not closest to the field
unknown-memberwarning@Watch('x'), @Selector('x'), @Task('x')… naming a member the class doesn't declare
in-place-mutationwarningthis.items.push(…) or this.form.name = … on a @State/@Persisted/@Synced/@History field (no update)
static-readwarning / infoA reactive field read in render() outside an arrow ({this.count} is a snapshot); props are only hinted
internal-importwarning@praxisjs/*/internal in application code (composables and custom decorators may use it)
feature-deep-importwarningIn a feature-slice project, importing another feature past its index.ts
shared-imports-featurewarningsrc/shared/ importing from src/features/

praxisjs_scaffold takes a kind (page, component, store, composable, service, decorator, function, stylesheet), a name, for pages and components route and stateful, and in a feature-slice project the feature (without it, shared code goes to src/shared/ui or src/shared/lib). It writes nothing: it returns the path the file should have (the folder the project already uses for that kind, or the default layout), whether it already exists, starter code with the current APIs, a test file when the project writes tests (and where it keeps them), the docs to read and similar code that may already cover it. It follows .praxisjs-ai.json (css, tests).

praxisjs_ui_components lists the components of every Kosmesis registry the project uses — the default one and each @namespace registry in components.json, served over HTTP or from a local folder — with name, description, version, npm and registry dependencies, and, for installed ones, their version, whether an update is available and whether they were edited. query filters by words, installed: true shows only installed components. Without components.json it lists the public registry. It reads each registry's index.json; a registry that can't be reached is reported instead of failing the call.


Project memory

The project memory is a .praxis/ folder at the project root, versioned with your code. It records what the project already has and how it is built, so assistants reuse existing code, respect your business rules and keep files where they belong — and so do people, since it is plain Markdown you review in pull requests.

Assistants read and change it only through the server's tools: the AI skills tell them never to touch .praxis/ files directly, and every file starts with a "Managed by the PraxisJS MCP server" header.

Layout

.praxis/
  README.md                       what the folder is, generated
  index.md                        every entry by category, rebuilt on each change
  components/<id>.md              reusable UI components
  utilities/<id>.md               composables, stores, services, custom decorators, helpers, stylesheets
  business-rules/<id>.md          domain rules and where they are enforced
  architecture/0001-<id>.md       decisions (ADR): context, decision, consequences
  conventions/<id>.md             folder layout and coding rules; project-structure.md says where each kind of file lives,
                                  ui-libraries.md which Morphos primitives and Kosmesis components the project uses
  glossary/<id>.md                domain vocabulary

Entry format

One Markdown file per entry, named after its id (kebab-case). Short fields go in the frontmatter, longer text in fixed ## sections:

---
# Managed by the PraxisJS MCP server (@praxisjs/mcp). Change it through the praxisjs_memory_* tools, not by hand.
id: date-picker
title: DatePicker
summary: "Calendar popover for picking a date, with min/max bounds."
location: src/components/date-picker.tsx
exports: [DatePicker, DatePickerProps]
tags: [form, input]
related: [utilities/format-date]
---

## Purpose

Every date field in forms. Use DateRangePicker for ranges.

## API

Props: `value`, `min`, `max`, `onChange(date)`.

## Usage

<DatePicker value={() => this.due} onChange={(d) => (this.due = d)} />
CategoryFieldsSections
componentslocation, exportsPurpose, API, Usage, Notes
utilitieskind (composable, store, service, decorator, function, class, stylesheet, type, constant), location, exportsPurpose, API, Usage, Notes
business-ruleslocationsRule, Rationale, Examples
architecturestatus (proposed, accepted, superseded, deprecated), date, supersedesContext, Decision, Consequences
conventionsscope (structure, naming, code, testing, styling, workflow, other), pathsRule, Rationale, Examples
glossaryaliasesDefinition

Every entry also has title, a one-line summary, and optional tags and related ("category/id").

The format is chosen for small, readable diffs: keys and sections always come in the same order, lists are sorted and deduplicated, whitespace is normalized and there are no timestamps besides an architecture decision's date, so rewriting an unchanged entry changes nothing and editing one field changes one line.

Format versions

index.md is the memory's manifest: its frontmatter records the format version (schema: 1). The tools refuse to write to a memory in an older format until praxisjs_memory_migrate brought it up to date, and to a newer one until you update the server. praxisjs_memory_migrate is a dry run by default; with dryRun: false it validates every entry, writes it again in the canonical form (renaming files whose name doesn't match their id), regenerates README.md and index.md, builds all of it in a sibling folder and swaps it in with two renames, so the project never sees half a migration. Invalid entries are reported and kept as they are.

Safe writes

  • Every entry is validated against its category's schema; unknown fields, multi-line summaries, absolute paths or paths with .. are refused with a message naming the field.
  • Values that look like secrets (private keys, provider tokens, JWTs, credentials in URLs, password = …) are refused: .praxis/ is committed.
  • Files are written to a temporary file and renamed, so a crash never leaves half an entry, and index.md is rebuilt from the entries after every change.
  • A file that no longer parses is reported by praxisjs_memory_list instead of breaking the other tools.

Memory tools

ToolInputNotes
praxisjs_memory_init—Creates README.md and index.md, records conventions/project-structure from the folders the project uses (or the default layout), and lists reusable code not recorded yet. Safe to run again.
praxisjs_memory_listcategory?, tag?
praxisjs_memory_getcategory, id
praxisjs_memory_searchquery, category?, limit?Ranked by title, tags, summary and body
praxisjs_memory_addcategory, entryThe id comes from the title when not given; architecture ids are numbered (0001-…) and dated
praxisjs_memory_updatecategory, id, changesnull clears an optional field; ids never change
praxisjs_memory_removecategory, idReports entries whose related still points at it
praxisjs_memory_maprecord?Lists exported components (installed Kosmesis components with their docs link and update workflow), stores, composables, services, decorators, helpers and stylesheets with no entry (pages and the app root are left out), with prefilled entries; record: true adds them, tagging undocumented ones unreviewed, and records or refreshes conventions/ui-libraries. Also lists stale entries whose file or export is gone, and where the export lives now when it moved.
praxisjs_memory_check_duplicatedescription, name?Searches PraxisJS's own API, the memory's components and utilities, and the code
praxisjs_memory_upsertentries ([{ category, entry }]), dryRun?, confirmRewrite?Updates the matching entry (same id, same location and export, or same title) or adds a new one. Lists are merged; rewriting the title, summary or a non-empty section of a reviewed entry returns needs-confirmation until confirmRewrite: true. Entries tagged unreviewed are completed freely.
praxisjs_memory_bootstrap_plan—Eight focused passes — UI components, utilities and stores, routes and pages, business rules, architecture, conventions, dependencies and UI libraries, tests and tooling — each with its goal, the files to read and the categories it writes, ready for parallel subagents
praxisjs_memory_migratedryRun? (default true)See Format versions

The memory tools other than praxisjs_memory_init and praxisjs_memory_check_duplicate ask you to run praxisjs_memory_init first when .praxis/ doesn't exist.


Pairing with an AI skill

The MCP server provides the tools. The AI skills tell the assistant when to use them — docs before code, duplicate checks before new code, validation after every change, the memory kept current — together with project memory files (CLAUDE.md / AGENTS.md), one-time project config and PraxisJS conventions:

# New project — select the integration during scaffolding
pnpm create praxisjs@latest

# Existing project
npx praxisjs ai add

See the praxisjs CLI reference for ai remove, doctor, and upgrade.

On this page