Skip to main content
Glama

selvans

selvans is a framework that makes any web application natively operable by AI agents through the Model Context Protocol (MCP).

Instead of using browser automation, AI agents connect directly to your application through a structured protocol — observing the UI semantic tree and calling typed tools to interact with it.


How It Works

 External AI (Claude Desktop, Cursor…)
          │  MCP / SSE
          ▼
  ┌──────────────────┐
  │  selvans-core │  ← Hub service (WebSocket + MCP server + admin UI)
  └──────┬─────┬─────┘
         │ WS  │ WS
    ┌────┘     └────┐
    ▼               ▼
Angular App    Python Backend
(selvans-   (selvans-
  angular)       python)
  1. selvans-core is the central hub. It exposes an MCP server (SSE) that external AI clients connect to, and a WebSocket endpoint that your frontend and backend connect to.

  2. selvans-angular integrates into your Angular app. It registers a semantic UI tree and built-in tools (click_element, form_input, get_page_state…) with the Core.

  3. selvans-python integrates into your Python/FastAPI backend. It registers typed service operations with the Core — tool schemas and argument validation are derived from your type hints by the official MCP SDK.

  4. The AI calls tools and operations through the Core, which routes them to the right app in real time.


Related MCP server: Playwright MCP

Repository Structure

selvans/
├── packages/
│   ├── core/                     # Hub service + Docker image (Python module: selvans_core)
│   ├── lib/                      # Client libraries
│   │   ├── selvans-angular/      # Angular library (npm)
│   │   ├── selvans-python/       # Python client library (PyPI)
│   │   └── selvans-java/         # Java library (planned)
│   ├── demo/                     # Demo stack
│   │   ├── selvans-angular-demo/ # Angular demo app
│   │   ├── selvans-python-demo/  # FastAPI demo backend
│   │   └── docker-compose.yml    # Demo Docker Compose
│   ├── setup/                    # npm installer for the Claude plugin (WIP)
│   └── claude-plugin/            # Claude plugin: agents/commands/skills (WIP)
└── .github/workflows/            # CI (docker-images.yml, release.yml)

There is no root Nx/pnpm workspace — packages are organized by shippable artifact and each one builds, tests and ships on its own.


Quick Start

The demo stack runs entirely in Docker (only Docker required — no Node/pnpm on the host). The Core is consumed as a published image; until it exists on Docker Hub, build it locally first:

# 1. Build the Core image locally (one-off, until it's published to Docker Hub)
docker build -t gabrieleconsonni/selvans-core:latest packages/core

# 2. Start the stack: Core (:8080) + Python demo (:8001) + Angular dev server (:4200, live-reload)
docker compose -f packages/demo/docker-compose.yml up -d --build

Open http://localhost:4200 — the selvans-panel should show "connected".

Live-reload: the Angular source is bind-mounted into the container. Saving a file in packages/demo/selvans-angular-demo/ or packages/lib/selvans-angular/ triggers an automatic browser update (~1 s latency due to polling on Docker Desktop Windows).

First startup: the Angular container build (in-container npm install + esbuild) takes ~90–180 s.

Prod-like (Angular built and served via nginx instead of the live-reload dev server):

docker compose -f packages/demo/docker-compose.yml --profile full up -d --build

Note: selvans-angular-dev (default profile, live-reload) and selvans-angular-demo (profile full, nginx) both bind port 4200 — never run both at once.

Teardown (stop + wipe volumes):

docker compose -f packages/demo/docker-compose.yml down -v

See [[Getting-started]] in the wiki for the full guide and troubleshooting.


Manual start

1. Start the Core

# Run the Core on its own (local build)
docker compose -f packages/core/docker-compose.yml up --build
# Admin UI → http://localhost:8080/ui
# MCP SSE  → http://localhost:8080/mcp/sse

2. Integrate the Angular Frontend

npm install selvans-angular
// app.module.ts
import { SelvansModule } from 'selvans-angular';

@NgModule({
  imports: [
    SelvansModule.forRoot({
      coreUrl: 'http://localhost:8080',
      appId: 'my-app'
    })
  ]
})
export class AppModule {}

Mark your components with semantic directives:

<main [SelvansNode]="{ id: 'main', template: 'layout', description: 'Main content area' }">
  <form [SelvansNode]="{ id: 'login-form', template: 'form', description: 'User login form' }">
    <input [SelvansTarget]="'email-input'" type="email" />
    <button [SelvansTarget]="'submit-btn'">Login</button>
  </form>
</main>

3. Integrate the Python Backend

pip install selvans
# main.py
from typing import Literal
from selvans import SelvansBeApp, SelvansBeConfig, SelvansService, operation

class TaskService(SelvansService):
    name = "tasks"
    description = "Task management"

    @operation("list", description="List tasks, optionally filtered by status")
    async def list_tasks(self, status: Literal["pending", "completed"] | None = None) -> list[dict]:
        return await db.list_tasks(status=status)

    @operation("create", description="Create a new task")
    async def create_task(self, title: str, priority: Literal["high", "medium", "low"] = "medium") -> dict:
        return await db.create_task(title, priority)

surface = SelvansBeApp(SelvansBeConfig(core_url="http://localhost:8080"))
surface.register(TaskService())
app = surface.create_app()

Type hints drive the AI-facing schema: Literal[...] becomes a JSON Schema enum, X | None an optional field, and out-of-range arguments are rejected before your method runs.

4. Connect an AI Client

Add the Core's MCP endpoint to your AI client (e.g. Claude Desktop claude_desktop_config.json):

{
  "mcpServers": {
    "selvans": {
      "url": "http://localhost:8080/mcp/sse"
    }
  }
}

The AI can now observe your app's UI and call your backend operations directly.

When the Core runs with authentication enabled (Selvans_AUTH_MODE=enforce), the client must present a bearer token ("headers": { "Authorization": "Bearer <token>" }) and only sees the tools it is authorized for — see Authentication.


Run the Full Demo

docker compose -f packages/demo/docker-compose.yml up -d --build
# → Core :8080 + Python demo :8001 + Angular dev container :4200 (live-reload)

Or prod-like, with Angular served via nginx:

docker compose -f packages/demo/docker-compose.yml --profile full up -d --build
# → Core :8080 + Python demo :8001 + Angular nginx :4200 (static bundle)

Service

URL

Mode

selvans-core

http://localhost:8080

both

Admin UI

http://localhost:8080/ui

both

MCP SSE endpoint

http://localhost:8080/mcp/sse

both

Python demo backend

http://localhost:8001

both

Angular demo frontend

http://localhost:4200

dev (live-reload) / prod-like (nginx)

Note: selvans-angular-dev (dev, live-reload) and selvans-angular-demo (profile full, nginx) both bind port 4200 — do not start them together.


Packages

Package

Description

Docs

selvans-angular

Angular library — semantic UI tree + built-in tools

docs

selvans-python

Python library — FastAPI integration + service operations

docs

selvans-core

Hub service — MCP server + WebSocket hub + admin UI

docs


Built-in Frontend Tools

The Angular library registers these tools automatically:

Tool

Description

get_page_state

Returns current URL, title, and visible text

navigate

Navigate to a route by path

get_elements

List all elements marked with [SelvansTarget]

click_element

Click an element by its target ID

form_input

Read or set the value of a form field by target ID


In-App AI Chat (built-in)

External MCP clients (Claude Desktop, Cursor) run the AI in a separate window. For a chat that lives next to the UI it drives, the Core ships a built-in agent loop — no AI vendor SDK, just plain HTTP to the provider you configure.

        ┌──────────────── selvans-core ────────────────┐
Browser │  WS /chat/ws →  ChatOrchestrator (agent loop) │ ──HTTP──▶ LLM
 chat   │                      │ router.dispatch()      │   (Anthropic /
panel / │                      ▼                        │    OpenAI / Ollama)
console │              connected apps (FE + BE) via WS   │
        └───────────────────────────────────────────────┘

The orchestrator asks the model for tool calls, runs them through the same ToolRouter that MCP clients use, and feeds results back until the model answers.

The browser talks to it over a stateful, streaming WebSocket (/chat/ws): the Core owns the conversation, so what the user types reaches the model and the loop streams back live — the assistant's narration, each tool call and its result, then the reply. The chat is a real-time mirror of the agent, and because the Core (not an external MCP client) drives the loop, the browser → model → browser round-trip is just a normal bidirectional socket. A stateless POST /chat (whole transcript in, final reply out) remains for simple callers.

Two surfaces are exposed over it:

  • Frontend panel — drop <selvans-chat /> into your Angular app. The chat is scoped to that app's tools, so the assistant operates the UI the user is looking at, live.

  • Multi-app consolehttp://localhost:8080/ui/console. One chat, an app-switcher across every registered app, and the selected app's UI embedded in an iframe beside it.

Configure the provider via env (see .env.example) — the Core reaches it over HTTP, so switching is only a matter of these values:

AI_PROVIDER=ollama          # ollama | openai | anthropic
OLLAMA_MODEL=qwen2.5:7b-instruct
# OPENAI_API_KEY=…   OPENAI_MODEL=gpt-4o-mini
# ANTHROPIC_API_KEY=…   ANTHROPIC_MODEL=claude-sonnet-4-20250514

With ollama (the default) no API key is needed — use a model that supports tool calling. If the selected provider is missing its key, the chat returns a clear, actionable error (an error frame over the socket, or an error body from POST /chat) and the rest of the Core keeps working.


Authentication

App-to-Core authentication is available as an opt-in first step (phase 0-1 of the auth design). Apps authenticate to the Core with a shared-secret token bound to their appId, and the Core refuses to let one app hijack another's live appId.

Configured via env on the Core:

# off (default) | permissive (verify + log, don't block) | enforce (reject)
Selvans_AUTH_MODE=enforce
# per-app shared secrets, keyed by appId
Selvans_APP_TOKENS='{"demo-backend":"change-me"}'

Each app then presents its token — SelvansBeConfig(token=…) (Python), SelvansModule.forRoot({ token: … }) (Angular). With AUTH_MODE=off behaviour is unchanged, so existing deployments are unaffected. Roll out with permissive to observe violations in the logs before switching to enforce.

The same AUTH_MODE also governs AI/MCP clients: each client is given a bearer token and an allow-list of apps, so it can only see and call the tools it is authorized for (tool calls are attributed to the real client id, not a shared constant):

Selvans_MCP_CLIENTS='{"claude-desktop":{"token":"change-me","allow":["demo-backend"]}}'

In enforce mode you must configure both Selvans_APP_TOKENS and Selvans_MCP_CLIENTS, otherwise apps / AI clients are rejected.

End-user identity

The AI can act on behalf of an end user: it presents the user's token in the X-Selvans-User header, the Core verifies it (HS256 JWT when Selvans_USER_JWT_SECRET is set; otherwise an opaque, unverified subject id), and propagates the identity to your app on every call. Every tool call is then attributable to a real user in the telemetry — closing the "we don't know which user made a request" gap.

Selvans_USER_JWT_SECRET=change-me   # verify the user JWT (omit for opaque dev mode)

Your backend reads the caller without changing operation signatures:

from selvans import operation, current_user_id

@operation("list", description="List the current user's tasks")
async def list_tasks(self) -> list[dict]:
    return await db.list_tasks(owner=current_user_id())   # None if no user propagated

Angular receives the same context on the toolCall$ stream. Identity is propagated and audited regardless of AUTH_MODE; it is about attribution, not access control, so an absent/invalid user token never blocks a call (it is logged).


License

Distributed under the Apache 2.0 license — see LICENSE and NOTICE.

F
license - not found
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
2Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gabrieleakeron/selvans'

If you have feedback or need assistance with the MCP directory API, please join our Discord server