README Documentation
@vibase/core
Easily read and mutate Postgres data using MCP.
Overview
@vibase/core
provides the foundational components for creating MCP (Model Context Protocol) servers on top of Postgres using simple configuration files.
- Run on your own machine with STDIO
- Deploy on lambda or Cloudflare workers
- Package in a Claude Desktop Extension
Features
- YAML Configuration: Define MCP tools using simple YAML configuration files
- PostgreSQL Support: Direct SQL execution against PostgreSQL databases with connection pooling
- Safe SQL Generation: Uses parameterized queries for safe SQL query construction
- Type Safety: Full TypeScript support with Zod validation
- Connection Management: Automatic connection pooling and cleanup
Installation
npm install -g @vibase/core
Quick Start
- Create a YAML configuration file (
tools.yaml
):
sources:
todo_db:
kind: postgres
connection_string: postgres://user:password@localhost:5432/todo_management
tools:
get_boards:
kind: postgres
source: todo_db
description: Retrieve all todo boards
statement: SELECT id, name, description FROM boards ORDER BY created_at DESC;
parameters: []
search_tasks:
kind: postgres
source: todo_db
description: Search tasks by title or description
parameters:
- name: search_term
type: string
description: Search term to match against task title or description
required: true
statement: |
SELECT t.id, t.title, t.priority, s.name as stage_name
FROM tasks t
JOIN stages s ON t.stage_id = s.id
WHERE t.title ILIKE '%' || $1 || '%' OR t.description ILIKE '%' || $1 || '%';
- Create and run the MCP server:
npx @vibase/core tools.yaml --http 5555
Your MCP server now runs over streamable HTTP on http://localhost:5555
You can view options with --help
API Reference
Use Vibase with any TypeScript MCP server.
Configuration
loadConfigFromYaml(configPath: string): ToolboxConfig
Loads and validates configuration from a YAML file.
Parameters:
configPath
: Path to the YAML configuration file
Returns:
ToolboxConfig
: Validated configuration object
Throws:
- Configuration validation errors
- File not found errors
- YAML parsing errors
loadConfigFromYamlString(yamlContent: string): ToolboxConfig
Loads and validates configuration from a YAML string.
Parameters:
yamlContent
: YAML configuration as a string
Returns:
ToolboxConfig
: Validated configuration object
Throws:
- Configuration validation errors
- YAML parsing errors
Server Creation
createMcpServerFromConfig(config: ToolboxConfig, options?: ServerOptions): { server: McpServer; cleanup: () => Promise<void>; plugins: PluginRegistry }
Creates an MCP server from a validated configuration object.
Parameters:
config
: Validated configuration objectoptions
: Optional server configuration options
Returns:
server
: The MCP server instancecleanup
: Async function to clean up database connectionsplugins
: Plugin registry for registering custom plugins
addToolsToMcpServer(server: McpServer, config: ToolboxConfig): { cleanup: () => Promise<void>; plugins: PluginRegistry }
Adds tools from configuration to an existing MCP server. This is useful when you want to add tools to a server that was created separately.
Parameters:
server
: Existing MCP server instanceconfig
: Validated configuration object
Returns:
cleanup
: Async function to clean up database connectionsplugins
: Plugin registry for registering custom plugins
Example:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { addToolsToMcpServer, loadConfigFromYaml } from "@vibase/core";
// Create server manually
const server = new McpServer({
name: "My Custom Server",
version: "1.0.0",
});
// Load configuration and add tools
const config = loadConfigFromYaml("./config.yaml");
const { cleanup, plugins } = addToolsToMcpServer(server, config);
// Register plugins if needed
const authPlugin = {
name: "auth",
callbacks: {
beforeQuery: (context) => {
return { pgSettings: new Map([["role", "authenticated"]]) };
}
}
};
plugins.register(authPlugin);
// Connect transport and handle cleanup
process.on("SIGINT", async () => {
await cleanup();
process.exit(0);
});
Configuration Format
Sources
Define database connections:
sources:
source-name:
kind: postgres
connection_string: postgres://user:password@host:port/database
Tools
Define SQL-based tools:
tools:
tool-name:
kind: postgres
source: source-name # References a source
description: Human-readable description
parameters:
- name: param_name
type: string | number | boolean
description: Parameter description
required: true | false # Optional, defaults to true
statement: SELECT * FROM table WHERE column = $1;
Plugins API
@vibase/core
includes a plugin system that allows you to modify query execution behavior. The most common use case is implementing authentication with JWT tokens and PostgreSQL Row Level Security (RLS).
Plugin Registration
import { createMcpServerFromConfig, loadConfigFromYaml } from "@vibase/core";
// Create server and get plugins registry
const config = loadConfigFromYaml("./config.yaml");
const { server, plugins } = createMcpServerFromConfig(config);
// Register a plugin
const myPlugin = {
name: "my-plugin",
callbacks: {
beforeQuery: (context) => {
// Plugin logic here
return { pgSettings: new Map([["role", "authenticated"]]) };
}
}
};
plugins.register(myPlugin);
JWT Authentication Plugin Example
Here's how you can implement JWT bearer token authentication:
import jwt from "jsonwebtoken";
function createBearerAuthPlugin(jwtSecret: string) {
return {
name: "bearer-auth",
callbacks: {
beforeQuery: ({ extra }) => {
const pgSettings = new Map();
try {
// Extract JWT from authInfo.token or Authorization header
let token = extra?.authInfo?.token;
if (!token) {
const authHeader = extra?.requestInfo?.headers?.authorization;
if (authHeader?.startsWith('Bearer ')) {
token = authHeader.replace(/^Bearer\s+/i, '');
}
}
if (!token) {
// No token - set anonymous role
pgSettings.set('role', 'anonymous');
return { pgSettings };
}
// Verify and decode JWT
const decoded = jwt.verify(token, jwtSecret);
// Set PostgreSQL session variables for RLS
pgSettings.set('role', 'authenticated');
pgSettings.set('jwt.claims.sub', decoded.sub);
pgSettings.set('jwt.claims.role', decoded.role);
return { pgSettings };
} catch (error) {
// JWT verification failed - set anonymous role
pgSettings.set('role', 'anonymous');
return { pgSettings };
}
}
}
};
}
// Register the plugin
const authPlugin = createBearerAuthPlugin(process.env.JWT_SECRET);
plugins.register(authPlugin);
How Plugins Work
- Plugin Structure: Plugins are objects with a
name
andcallbacks
for different hooks - Plugin Registration: Use
plugins.register(plugin)
to register a complete plugin - Hook Execution: Plugin callbacks run before each query (more hooks coming soon)
- Context Access: Callbacks receive context including tool name, parsed arguments, and request metadata
- Session Variables: Plugins return
pgSettings
Maps that become PostgreSQL session variables - Automatic Transactions: When
pgSettings
are present, queries run in transactions with variables applied - RLS Integration: SQL queries can access variables via
current_setting('jwt.claims.sub')
Plugin Management
// Get all registered plugins
const allPlugins = plugins.getRegisteredPlugins();
// Get specific plugin by name
const authPlugin = plugins.getPlugin("bearer-auth");
Row Level Security Integration
With the authentication plugin, your SQL queries can reference JWT claims:
-- Users can only see their own todos
CREATE POLICY user_todos ON todos
FOR ALL USING (user_id = current_setting('jwt.claims.sub')::uuid);
-- Query automatically filtered by RLS
SELECT * FROM todos; -- Only returns current user's todos
Plugin Context
Plugin callbacks receive a context object with:
toolName
: Name of the tool being executedtoolConfig
: Tool configuration from YAMLpool
: Database connection poolparsedArgs
: Validated tool argumentsextra
: MCP request metadata (authInfo, requestInfo, etc.)query
: The SQL query being executed
Security Considerations
- Always use parameterized queries (enforced by this package)
- Store database credentials securely (consider environment variables)
- Limit database user permissions to only what's needed
- Use SSL connections for production databases
- Validate and sanitize user input before using in queries
Contributing
Contributions are welcome!
License
Apache 2.0