Skip to content

Resources

Resources provide local, deterministic data via SQLite. Unlike tools (which call remote REST APIs), resources query local databases. They are perfect for bulk-downloaded open data such as company registers, transit schedules, and sanctions lists.

A resource is a SQLite database bundled with a schema. The FlowMCP runtime loads the .db file via sql.js (a pure JavaScript/WASM SQLite implementation) and exposes each defined query as an MCP resource. No network calls, no API keys, no rate limits.

Resources are ideal when:

  • Data is large and rarely changes (company registers, geographic data)
  • Offline access is required
  • Latency must be near-zero
  • The dataset is publicly available as a bulk download
AspectToolsResources
Data sourceRemote REST APILocal SQLite database
LatencyNetwork-dependentInstant
AvailabilityRequires internetAlways available
Data freshnessReal-timeSnapshot (periodic refresh)
API key requiredUsually yesNo
Use caseLive prices, on-chain dataCompany registers, transit data

Resources are declared inside main.resources. Each resource points to a SQLite database and defines named queries with SQL and parameters:

export const main = {
namespace: 'offeneregister',
name: 'OffeneRegister',
version: '3.0.0',
root: '',
tools: {},
resources: {
companiesDb: {
source: 'sqlite',
database: 'companies.db',
origin: 'global',
description: 'German company register (OffeneRegister)',
queries: {
searchCompanies: {
sql: "SELECT * FROM companies WHERE name LIKE ? LIMIT ?",
description: 'Search companies by name',
parameters: {
searchTerm: { type: 'string', required: true },
limit: { type: 'number', required: false, default: 10 }
},
output: { columns: ['company_number', 'name', 'registered_address', 'status'] }
}
}
}
}
}

The origin field determines where the runtime looks for the .db file:

OriginPath ResolutionBest For
global~/.flowmcp/data/{database}Shared datasets used across projects
project.flowmcp/data/{database}Project-specific data
inlineRelative to the schema fileSelf-contained schemas with small databases

Complex queries can use Common Table Expressions (CTEs) for multi-step filtering:

WITH recent AS (
SELECT * FROM companies WHERE registered_date > ?
)
SELECT * FROM recent WHERE status = 'active' LIMIT ?

CTEs must still start with a read-only statement. The same SQL security rules apply: no INSERT, UPDATE, DELETE, or other write operations anywhere in the CTE chain.

ConstraintValueRationale
Max resources per schema2Resources are supplementary, not primary output
Max queries per resource87 defined + 1 auto-injected freeQuery
getSchema queryRequiredMust return the database table structure
SQL operationsSELECT onlyRead-only enforcement — no INSERT/UPDATE/DELETE
Parameter placeholders? onlyPrevents SQL injection
Source typesqlite onlyFuture versions may add other sources
Database file extension.dbStandard SQLite extension

Two queries are handled automatically by the runtime:

  • getSchema — You must define this query. It returns the database structure so AI agents can understand available tables and columns.
  • freeQuery — Auto-injected by the runtime. Allows AI agents to run arbitrary SELECT queries within the read-only sandbox. This counts toward the 8-query limit.

An OffeneRegister schema with a SQLite resource for querying the German company register:

export const main = {
namespace: 'offeneregister',
name: 'OffeneRegister',
description: 'German company register — local SQLite database',
version: '3.0.0',
tags: ['open-data', 'germany', 'companies'],
root: '',
tools: {},
resources: {
companiesDb: {
source: 'sqlite',
database: 'openregister.db',
origin: 'global',
description: 'OffeneRegister company database (2.5 GB)',
queries: {
getSchema: {
sql: "SELECT sql FROM sqlite_master WHERE type='table'",
description: 'Get database schema',
parameters: {},
output: { columns: ['sql'] }
},
searchCompanies: {
sql: "SELECT company_number, name, registered_address, status FROM companies WHERE name LIKE ? LIMIT ?",
description: 'Full-text search for companies by name',
parameters: {
searchTerm: { type: 'string', required: true, description: 'Company name (use % for wildcards)' },
limit: { type: 'number', required: false, default: 10, description: 'Max results' }
},
output: { columns: ['company_number', 'name', 'registered_address', 'status'] }
},
getByNumber: {
sql: "SELECT * FROM companies WHERE company_number = ?",
description: 'Look up a company by its registration number',
parameters: {
companyNumber: { type: 'string', required: true, description: 'Company registration number' }
},
output: { columns: ['company_number', 'name', 'registered_address', 'status', 'registered_date'] }
},
recentRegistrations: {
sql: "SELECT company_number, name, registered_date FROM companies ORDER BY registered_date DESC LIMIT ?",
description: 'List the most recently registered companies',
parameters: {
limit: { type: 'number', required: false, default: 20, description: 'Max results' }
},
output: { columns: ['company_number', 'name', 'registered_date'] }
}
}
}
}
}

This schema has no tools and no root URL — it operates entirely on local data. The AI agent can search companies, look up by number, or browse recent registrations without any network access.

Resources are validated by rules RES001-RES023. Key rules include:

CodeRule
RES003Maximum 2 resources per schema
RES005Source must be 'sqlite'
RES006Database path must end in .db
RES008Maximum 8 queries per resource (7 + freeQuery)
RES012SQL must start with SELECT (or With for CTEs)
RES013SQL must not contain blocked patterns
RES014SQL must use ? placeholders
RES015Placeholder count must match parameter count

See Validation Rules for the complete list.