JUHE API Marketplace
vibase-ai avatar
MCP Server

vibase

Easily query and mutate Postgres data using MCP. This is an open source project.

0
GitHub Stars
8/23/2025
Last Updated
No Configuration
Please check the documentation below.

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.

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

  1. 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 || '%';
  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 object
  • options: Optional server configuration options

Returns:

  • server: The MCP server instance
  • cleanup: Async function to clean up database connections
  • plugins: 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 instance
  • config: Validated configuration object

Returns:

  • cleanup: Async function to clean up database connections
  • plugins: 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

  1. Plugin Structure: Plugins are objects with a name and callbacks for different hooks
  2. Plugin Registration: Use plugins.register(plugin) to register a complete plugin
  3. Hook Execution: Plugin callbacks run before each query (more hooks coming soon)
  4. Context Access: Callbacks receive context including tool name, parsed arguments, and request metadata
  5. Session Variables: Plugins return pgSettings Maps that become PostgreSQL session variables
  6. Automatic Transactions: When pgSettings are present, queries run in transactions with variables applied
  7. 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 executed
  • toolConfig: Tool configuration from YAML
  • pool: Database connection pool
  • parsedArgs: Validated tool arguments
  • extra: 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

Quick Actions

Key Features

Model Context Protocol
Secure Communication
Real-time Updates
Open Source