Skip to main content

Extensions and Scoped Permissions

How systemprompt.io extensions work across four domains, and how permission scoping enforces per-agent, per-tool authorization through OAuth2, roles, and access control rules.

TL;DR -- Extensions are Rust code you write, compile into your binary, and deploy wherever you want. Four domains cover every use case: library (database, API, jobs), web (pages, templates, assets), MCP (AI agent tools), and CLI (command-line utilities). Every extension is protected by scoped permissions -- OAuth2 scopes control what tokens can do, role-based access control determines who sees what, and access control rules enforce boundaries per-agent, per-tool, and per-user.

What Extensions Are

When you build with systemprompt.io, you own the binary. Not an account on a hosted service. A real Rust binary that compiles from source code you control, runs on infrastructure you choose, and executes functionality you define.

Extensions are how you add that functionality. Write Rust code that implements API routes, database schemas, background jobs, MCP tools, or custom CLI commands. Compile it into your binary. Deploy it wherever you want.

systemprompt.io is a library, not a framework. Extensions are embedded code you own and extend. There is no vendor lock-in, no runtime dependency on external services, no phone-home requirement. Your binary, your rules.

The Ownership Model

The systemprompt.io template is your project. When you clone it, you get full ownership over every aspect of the system:

  • Your binary -- Compile from source. Inspect every line of code that runs in production
  • Your release cycle -- Upgrade core when you choose, not when a vendor forces it
  • Your deployment -- Run on bare metal, VMs, containers, serverless -- anywhere Rust compiles
  • Your modifications -- Fork, customize, add features, remove what you don't need
  • Your distribution -- Ship to customers, embed in products, sell as SaaS

Extensions follow the same ownership model. The code lives in your repository under extensions/. You write it, you compile it, you deploy it, you maintain it.

Extension Domains

Extensions group into four domains based on how they compile and run:

Domain Binary Type Use Case
Library Compiles into main Database schemas, API routes, background jobs, providers
Web Compiles into main Page data, templates, assets, content rendering
MCP Standalone binary AI agent tools via Model Context Protocol
CLI Standalone binary Command-line utilities, automation scripts

Library Extensions

Library extensions compile directly into your main binary. They implement the Extension trait and provide database schemas, HTTP API routes, background jobs, LLM and tool providers, and configuration validation. This is where most server-side logic lives.

fn metadata(&self) -> ExtensionMetadata {
    ExtensionMetadata {
        id: "my-extension",
        name: "My Extension",
        version: env!("CARGO_PKG_VERSION"),
    }
}

Register your extension with register_extension!(MyExtension) and the runtime discovers it automatically at startup. See Library Extensions for the full guide.

Web Extensions

Web extensions handle content rendering and static site generation. They provide page data providers (template variables), component renderers (HTML fragments), content data providers (enrichment), template data extenders, page prerenderers (static pages), RSS and sitemap generators, and asset declarations. See Web Extensions.

MCP Extensions

MCP extensions are standalone binaries that expose tools for AI agents via the Model Context Protocol. They run as separate processes, listen on TCP ports, and serve tool requests over HTTP. This separation provides independent scaling, process isolation, and separate deployment cycles.

Each MCP server is declared in a manifest:

extension:
  type: mcp
  name: marketplace
  binary: systemprompt-mcp-marketplace
  port: 5050
  enabled: true

See MCP Extensions for the full guide.

CLI Extensions

CLI extensions are standalone binaries for automation: custom command-line tools, external integrations, utility commands, and agent-invokable scripts. See CLI Extensions.

Scoped Permissions

Every extension -- every API route, every MCP tool, every agent interaction -- is protected by scoped permissions. systemprompt.io enforces authorization at three levels: OAuth2 scopes on tokens, role-based access control on users, and access control rules on entities.

OAuth2 Scope Enforcement

OAuth2 scopes define what a token is allowed to do. Every protected operation requires specific scopes. When a request arrives, the authorization layer extracts scopes from the access token and compares them against the operation's requirements. If the token lacks the required scopes, the request is rejected with a 403 Forbidden response.

Common scopes:

Scope Permission
anonymous Public access, no authentication required
user Authenticated user operations
admin Administrative operations
tools:read Read MCP tool definitions
tools:execute Execute MCP tools
agents:read View agent configurations
agents:write Modify agent configurations
content:write Create or modify content

Scopes are enforced at multiple levels. API routes declare required scopes in their handlers. MCP tools specify scope requirements in their manifests. Agents define which scopes are needed to interact with them.

Agent-level scope configuration:

# services/agents/my_agent.yaml
securitySchemes:
  oauth2:
    type: oauth2
    flows:
      authorizationCode:
        authorizationUrl: "/api/v1/core/oauth/authorize"
        tokenUrl: "/api/v1/core/oauth/token"
        scopes:
          anonymous: "Public access"
          user: "Authenticated access"
          admin: "Administrative access"

security:
  - oauth2: ["user"]  # Minimum required scope

Per-tool scope enforcement:

Each MCP tool can require its own scope. The content:write tool requires the content:write scope. The admin:users tool requires admin. This means a token with user scope can read content but cannot modify it, even if it reaches the same MCP server.

Per-Agent, Per-Tool Access Control

Beyond OAuth2 scopes, systemprompt.io implements a unified access control system that governs visibility and execution rights across plugins, agents, and MCP servers.

Access control rules are stored in the database and evaluated at request time:

CREATE TABLE access_control_rules (
    entity_type TEXT NOT NULL
      CHECK (entity_type IN ('plugin', 'agent', 'mcp_server')),
    entity_id TEXT NOT NULL,
    rule_type TEXT NOT NULL
      CHECK (rule_type IN ('role')),
    rule_value TEXT NOT NULL,
    access TEXT NOT NULL DEFAULT 'allow'
      CHECK (access IN ('allow', 'deny'))
);

Each rule specifies an entity (plugin, agent, or MCP server), a matcher (role), and an access decision (allow or deny). This means you can:

  • Restrict a plugin to specific roles -- Only users with the admin role see administrative plugins
  • Control MCP server access -- Limit which MCP servers are available to which user groups
  • Bulk assign rules -- Apply the same access rules to multiple entities at once

Role-Based Plugin Access

Plugins bundle skills, agents, and MCP servers into deployable units. Each plugin declares which roles can access it:

# services/plugins/my-plugin/config.yaml
plugin:
  id: my-plugin
  name: "My Plugin"
  roles: ["admin", "engineering"]  # Empty array = everyone
  skills:
    source: explicit
    include:
      - skill_one
      - skill_two
  agents:
    source: explicit
    include:
      - my_agent

When roles is empty, every authenticated user can access the plugin. When roles are specified, only users with matching roles see the plugin and its contents. The admin dashboard provides a UI for managing these rules, and changes sync back to the YAML configuration.

User Isolation

User isolation is automatic. User A cannot access User B's agents, files, or data. This is enforced through user-scoped database queries and tenant boundaries embedded in access tokens. Every token includes a tenant_id claim, and every query filters by it.

Extension Traits Overview

The Extension trait is the core interface for library extensions. All methods have default implementations, so you only override what your extension needs. The only required method is metadata(), which returns the extension's identity.

Core traits:

Trait Purpose
Extension Base trait with 30+ methods for schemas, routes, jobs, providers
SchemaExtension Database schema migrations
ApiExtension HTTP route registration
JobExtension Background job definitions
ProviderExtension LLM and tool provider registration
ConfigExtension Configuration validation

Web traits:

Trait Purpose
PageDataProvider Inject template variables for specific routes
ComponentRenderer Render HTML fragments
ContentDataProvider Enrich content with additional data
TemplateDataExtender Modify template data before rendering
PagePrerenderer Generate static HTML pages
FrontmatterProcessor Parse and transform frontmatter
RssSitemapProvider Generate RSS feeds and sitemaps
AssetDeclaration Register CSS, JS, and font assets

Directory Structure

extensions/
├── marketplace/            # Library extension
│   ├── Cargo.toml
│   ├── src/
│   │   ├── lib.rs
│   │   ├── extension.rs    # implements Extension trait
│   │   ├── api/
│   │   ├── admin/
│   │   └── schemas.rs
│   └── schema/             # SQL migrations
├── web/                    # Web extension
│   ├── Cargo.toml
│   └── src/
│       ├── extension.rs    # implements Extension + web traits
│       ├── homepage/
│       └── products/
├── mcp/
│   └── marketplace/        # Standalone MCP server
│       ├── Cargo.toml
│       ├── manifest.yaml
│       └── src/
│           └── main.rs
└── cli/
    └── discord/            # CLI tool
        ├── Cargo.toml
        └── src/
            └── main.rs

Building Extensions

  1. Choose your extension domain based on use case
  2. Create the extension crate under extensions/
  3. Implement required traits (Extension for library, standalone main.rs for MCP/CLI)
  4. Register with register_extension! (library extensions only)
  5. Link in the template's src/lib.rs
  6. Build and test: just build
  7. Configure access control rules for your new extension

Domains

Core Traits

Web Traits

Lifecycle

Internals

MCP Deep Dives

Security