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.
On this page
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
adminrole 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
- Choose your extension domain based on use case
- Create the extension crate under
extensions/ - Implement required traits (
Extensionfor library, standalonemain.rsfor MCP/CLI) - Register with
register_extension!(library extensions only) - Link in the template's
src/lib.rs - Build and test:
just build - Configure access control rules for your new extension
Related Documentation
Domains
- Library Extensions -- Compiled into main binary
- Web Extensions -- Content and rendering
- MCP Extensions -- AI agent tools
- CLI Extensions -- Command-line tools
Core Traits
- Extension Trait -- Complete 30+ method reference
- Schema Extension -- Database schemas
- API Extension -- HTTP routes
- Job Extension -- Background jobs
- Provider Extension -- LLM/Tool providers
- Config Extension -- Configuration
Web Traits
- PageDataProvider -- Template variables
- ComponentRenderer -- HTML fragments
- ContentDataProvider -- Content enrichment
- TemplateDataExtender -- Final modifications
- PagePrerenderer -- Static pages
- FrontmatterProcessor -- Frontmatter parsing
- RSS & Sitemap -- Feeds
- Asset Declaration -- CSS/JS/Fonts
Lifecycle
- Registration -- inventory crate, macros
- Discovery -- ExtensionRegistry
- Dependencies -- Dependency management
- Initialization -- AppContext integration
Internals
- Typed Extensions -- Compile-time safety
- Extension Builder -- Type-safe registration
- Error Handling -- LoaderError, ConfigError
MCP Deep Dives
- Tool Structure -- Modular tool organization
- Resources -- MCP resources and templates
- Skills -- Skill integration for AI tools
- Responses -- Response formatting patterns
- AI Integration -- Full AI service guide
Security
- Authentication -- OAuth2, WebAuthn, scopes
- Security Configuration -- Auth secrets, token expiry
- Credentials -- API keys, client secrets