@agentforge/tools
Standard tools library with 88 production-ready tools.
Installation
pnpm add @agentforge/toolsWeb Tools (22)
Web Search
import { webSearch } from '@agentforge/tools';
// Basic search (no API key needed - uses DuckDuckGo)
const result = await webSearch.invoke({
query: 'TypeScript programming language',
maxResults: 10
});
console.log(`Found ${result.results.length} results from ${result.source}`);
result.results.forEach(r => {
console.log(`${r.title}: ${r.link}`);
console.log(` ${r.snippet}`);
});
// Premium search with Serper API (requires SERPER_API_KEY env var)
// Get your API key at: https://serper.dev
const premiumResult = await webSearch.invoke({
query: 'Latest AI developments 2026',
maxResults: 5,
preferSerper: true // Use Serper for Google search results
});
// Check metadata
console.log(`Source: ${premiumResult.source}`);
console.log(`Fallback used: ${premiumResult.metadata?.fallbackUsed}`);
console.log(`Response time: ${premiumResult.metadata?.responseTime}ms`);Features:
- No API key required for basic searches (uses DuckDuckGo)
- Optional Serper API for premium Google search results
- Smart fallback: automatically switches providers when needed
- Returns structured results with titles, links, and snippets
- Tracks metadata (source, fallback usage, response time)
Environment Setup:
# Optional: Add to your .env file for premium Google search
SERPER_API_KEY=your-serper-api-key-hereHTTP Client
import { httpClient, httpGet, httpPost } from '@agentforge/tools';
// Generic HTTP request
const response = await httpClient.invoke({
url: 'https://api.example.com/data',
method: 'GET',
headers: { 'Authorization': 'Bearer token' }
});
// GET request
const data = await httpGet.invoke({
url: 'https://api.example.com/users'
});
// POST request
const result = await httpPost.invoke({
url: 'https://api.example.com/users',
body: { name: 'John' }
});HTTP and scraper factories apply a destination policy before every request. By default, localhost, metadata, link-local, and private-network destinations are blocked, including destinations reached through redirects or hostname resolution. Use the explicit opt-in flags only for trusted operator-controlled or internal-network workflows:
import { createHttpTools, createScraperTools } from '@agentforge/tools';
const [safeHttpClient] = createHttpTools({
destinationPolicy: { maxRedirects: 3 }
});
const [internalScraper] = createScraperTools({
destinationPolicy: {
allowPrivateNetwork: true,
allowLocalhost: true,
}
});Available policy flags are allowLocalhost, allowPrivateNetwork, allowLinkLocal, allowMetadata, allowRedirects, and maxRedirects. See ST-11002 web egress policy hardening for the security rationale and migration guidance.
Web Scraping
import { webScraper, htmlParser, extractLinks } from '@agentforge/tools';
// Scrape webpage
const content = await webScraper.invoke({
url: 'https://example.com',
selector: '.content'
});
// Parse HTML
const parsed = await htmlParser.invoke({
html: '<div>Content</div>',
selector: 'div'
});
// Extract links
const links = await extractLinks.invoke({
html: '<a href="/page">Link</a>'
});URL Utilities
import { urlValidator, urlBuilder, urlQueryParser } from '@agentforge/tools';
// Validate URL - returns detailed URL components
const result = await urlValidator.invoke({
url: 'https://example.com/path?query=value#hash'
});
console.log(result.data?.protocol); // 'https:'
console.log(result.data?.hostname); // 'example.com'
console.log(result.data?.pathname); // '/path'
// Build URL from components
const url = await urlBuilder.invoke({
protocol: 'https',
hostname: 'api.example.com',
pathname: '/users',
query: { page: '1', limit: '10' }
});
console.log(url.url); // 'https://api.example.com/users?page=1&limit=10'
// Parse query string - accepts URL or query string
const params = await urlQueryParser.invoke({
input: 'https://example.com?foo=bar&baz=qux'
});
console.log(params.params); // { foo: 'bar', baz: 'qux' }
console.log(params.count); // 2Slack Integration
import {
sendSlackMessage,
notifySlack,
getSlackChannels,
getSlackMessages,
createSlackTools
} from '@agentforge/tools';
// Send a message to a Slack channel
const result = await sendSlackMessage.invoke({
channel: 'general', // Channel name (no #) or ID
message: 'Hello from AgentForge! 👋'
});
// Send a notification with @mentions
const notification = await notifySlack.invoke({
channel: 'alerts', // Channel name (no #) or ID
message: 'Deployment completed successfully!',
mentions: ['john', 'jane'] // Usernames without @
});
// List available channels
const channels = await getSlackChannels.invoke({
include_private: true // Note: underscore, not camelCase
});
console.log(`Found ${channels.data?.count} channels`);
channels.data?.channels.forEach(ch => {
console.log(`${ch.name} (${ch.num_members} members)`);
});
// Read message history
const messages = await getSlackMessages.invoke({
channel: 'general', // Channel name (no #) or ID
limit: 10
});
console.log(`Retrieved ${messages.data?.count} messages`);
messages.data?.messages.forEach(msg => {
console.log(`[${msg.timestamp}] ${msg.user}: ${msg.text}`);
});
// Custom configuration with factory function
const customSlackTools = createSlackTools({
token: 'xoxb-your-custom-token'
});
// Use custom tools
const customResult = await customSlackTools.sendMessage.invoke({
channel: 'custom', // Channel name (no #) or ID
message: 'Using custom token configuration'
});Features:
- Send messages to any Slack channel
- Notify team members with @mentions
- List public and private channels
- Read message history with pagination
- Configurable via environment variables or programmatic configuration
- Full TypeScript support with Zod validation
- Structured logging for debugging
Environment Setup:
# Add to your .env file
# Use either a User Token or Bot Token
SLACK_USER_TOKEN=xoxp-your-user-token-here
# OR
SLACK_BOT_TOKEN=xoxb-your-bot-token-hereGetting a Slack Token:
- Go to Slack API Apps
- Create a new app or select an existing one
- Navigate to "OAuth & Permissions"
- Add required scopes:
- User Token Scopes:
channels:read,channels:history,chat:write,groups:read,groups:history - Bot Token Scopes:
channels:read,channels:history,chat:write,groups:read,groups:history
- User Token Scopes:
- Install the app to your workspace
- Copy the token (starts with
xoxp-for user tokens orxoxb-for bot tokens)
Programmatic Configuration:
// Override environment variables with custom configuration
const slackTools = createSlackTools({
token: 'xoxb-your-custom-token'
});
// Use the custom tools (returns object with named properties)
const { sendMessage, notify, getChannels, getMessages } = slackTools;Confluence Integration
import {
searchConfluence,
getConfluencePage,
listConfluenceSpaces,
getSpacePages,
createConfluencePage,
updateConfluencePage,
archiveConfluencePage,
createConfluenceTools
} from '@agentforge/tools';
// Search for pages across all spaces
const searchResult = await searchConfluence.invoke({
query: 'API documentation',
limit: 10
});
// Parse JSON string result
const searchData = JSON.parse(searchResult);
console.log(`Found ${searchData.count} pages (total: ${searchData.total})`);
searchData.results.forEach(page => {
console.log(`${page.title} (${page.space})`);
console.log(` URL: ${page.url}`);
});
// Get a specific page by ID
const pageResult = await getConfluencePage.invoke({
page_id: '123456'
});
// Parse JSON string result
const pageData = JSON.parse(pageResult);
console.log(`Title: ${pageData.page.title}`);
console.log(`Space: ${pageData.page.space}`);
console.log(`Content: ${pageData.page.content}`);
console.log(`Version: ${pageData.page.version}`);
// List all available spaces
const spacesResult = await listConfluenceSpaces.invoke({
limit: 25
});
// Parse JSON string result
const spacesData = JSON.parse(spacesResult);
console.log(`Found ${spacesData.count} spaces`);
spacesData.spaces.forEach(space => {
console.log(`${space.name} (${space.key})`);
});
// Get all pages in a specific space
const spacePagesResult = await getSpacePages.invoke({
space_key: 'DOCS',
limit: 50
});
// Parse JSON string result
const spacePagesData = JSON.parse(spacePagesResult);
console.log(`Found ${spacePagesData.count} pages in space ${spacePagesData.space}`);
spacePagesData.pages.forEach(page => {
console.log(`${page.title} (ID: ${page.id})`);
});
// Create a new page
const newPageResult = await createConfluencePage.invoke({
space_key: 'DOCS',
title: 'New Documentation Page',
content: '<p>This is the page content in Confluence storage format.</p>'
});
// Parse JSON string result
const newPageData = JSON.parse(newPageResult);
console.log(`Created page: ${newPageData.page.title}`);
console.log(`Page ID: ${newPageData.page.id}`);
console.log(`URL: ${newPageData.page.url}`);
// Create a child page
const childPageResult = await createConfluencePage.invoke({
space_key: 'DOCS',
title: 'Child Page',
content: '<p>This is a child page.</p>',
parent_page_id: '123456'
});
// Update an existing page
const updatedPageResult = await updateConfluencePage.invoke({
page_id: '123456',
title: 'Updated Title',
content: '<p>Updated content.</p>'
});
// Parse JSON string result
const updatedPageData = JSON.parse(updatedPageResult);
console.log(`Updated page to version ${updatedPageData.page.version}`);
// Archive a page (move to trash)
const archivedPageResult = await archiveConfluencePage.invoke({
page_id: '123456'
});
// Parse JSON string result
const archivedPageData = JSON.parse(archivedPageResult);
console.log(`Archived page: ${archivedPageData.page.title}`);
// Custom configuration with factory function
const customConfluenceTools = createConfluenceTools({
apiKey: 'your-custom-api-key',
email: 'custom@example.com',
siteUrl: 'https://custom.atlassian.net'
});
// Use custom tools (returns object with named properties, not array)
const customResult = await customConfluenceTools.searchConfluence.invoke({
query: 'search query',
limit: 10
});
// Parse JSON string result
const customData = JSON.parse(customResult);
console.log(`Found ${customData.count} results`);Features:
- Search pages across all Confluence spaces
- Retrieve full page content with metadata
- List all available spaces in your Confluence instance
- Get all pages within a specific space
- Create new pages with optional parent pages (hierarchical structure)
- Update existing page content and metadata
- Archive pages (move to trash)
- Configurable via environment variables or programmatic configuration
- Full TypeScript support with Zod validation
- Structured logging with
[[tools:confluence]]prefix for debugging
Environment Setup:
# Add to your .env file
ATLASSIAN_API_KEY=your-atlassian-api-key-here
ATLASSIAN_EMAIL=your-email@example.com
ATLASSIAN_SITE_URL=https://your-site.atlassian.netGetting Confluence API Credentials:
- Go to Atlassian Account Settings
- Click "Create API token"
- Give it a descriptive label (e.g., "AgentForge Integration")
- Copy the generated API token
- Use your Atlassian account email and the API token for authentication
- Your site URL is your Confluence instance URL (e.g.,
https://yourcompany.atlassian.net)
Programmatic Configuration:
// Override environment variables with custom configuration
const confluenceTools = createConfluenceTools({
apiKey: 'your-custom-api-key',
email: 'custom@example.com',
siteUrl: 'https://custom.atlassian.net'
});
// Use the custom tools (returns object with named properties)
const { searchConfluence, getConfluencePage, listConfluenceSpaces, getSpacePages, createConfluencePage, updateConfluencePage, archiveConfluencePage } = confluenceTools;Content Format: Confluence uses "storage format" (a subset of HTML) for page content. Common elements:
<p>Paragraph text</p>
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<ul><li>Bullet point</li></ul>
<ol><li>Numbered list</li></ol>
<ac:structured-macro ac:name="code">
<ac:plain-text-body><![CDATA[code block]]></ac:plain-text-body>
</ac:structured-macro>Data Tools (31)
JSON Processing
import {
jsonParser,
jsonStringify,
jsonQuery,
jsonValidator,
jsonMerge
} from '@agentforge/tools';
// Parse JSON
const parseResult = await jsonParser.invoke({
json: '{"name": "John"}'
});
if (parseResult.success) {
console.log(parseResult.data?.data); // { name: 'John' }
console.log(parseResult.data?.type); // 'object'
}
// Stringify
const stringifyResult = await jsonStringify.invoke({
data: { name: 'John' },
pretty: true
});
if (stringifyResult.success) {
console.log(stringifyResult.data?.json); // '{\n "name": "John"\n}'
console.log(stringifyResult.data?.length); // 24
}
// Query JSON (dot notation)
const queryResult = await jsonQuery.invoke({
data: { users: [{ name: 'John' }] },
path: 'users[0].name'
});
if (queryResult.success) {
console.log(queryResult.data?.value); // 'John'
console.log(queryResult.data?.type); // 'string'
}
// Validate JSON
const validationResult = await jsonValidator.invoke({
json: '{"name": "John"}',
schema: { type: 'object' }
});
if (validationResult.success) {
console.log(validationResult.data?.valid); // true
console.log(validationResult.data?.message); // 'Valid JSON'
}
// Merge JSON objects (uses .implement, returns raw value)
const merged = await jsonMerge.invoke({
objects: [{ a: 1 }, { b: 2 }]
});
console.log(merged); // { a: 1, b: 2 }CSV Processing
import { csvParser, csvGenerator, csvToJson, jsonToCsv } from '@agentforge/tools';
// Parse CSV
const parseResult = await csvParser.invoke({
csv: 'name,age\nJohn,30',
delimiter: ','
});
if (parseResult.success) {
console.log(parseResult.data); // [{ name: 'John', age: '30' }]
console.log(parseResult.rowCount); // 1
console.log(parseResult.columnCount); // 2
}
// Generate CSV
const generateResult = await csvGenerator.invoke({
data: [{ name: 'John', age: 30 }]
});
if (generateResult.success) {
console.log(generateResult.csv); // 'name,age\nJohn,30\n'
console.log(generateResult.rowCount); // 1
}
// Convert CSV to JSON
const csvToJsonResult = await csvToJson.invoke({
csv: 'name,age\nJohn,30'
});
if (csvToJsonResult.success) {
console.log(csvToJsonResult.json); // '[{"name":"John","age":"30"}]'
console.log(csvToJsonResult.recordCount); // 1
}
// Convert JSON to CSV
const jsonToCsvResult = await jsonToCsv.invoke({
json: '[{"name":"John","age":30}]'
});
if (jsonToCsvResult.success) {
console.log(jsonToCsvResult.csv); // 'name,age\nJohn,30\n'
console.log(jsonToCsvResult.rowCount); // 1
}XML Processing
import { xmlParser, xmlGenerator, xmlToJson, jsonToXml } from '@agentforge/tools';
// Parse XML
const parseResult = await xmlParser.invoke({
xml: '<root><name>John</name></root>'
});
if (parseResult.success) {
console.log(parseResult.data); // { root: { name: 'John' } }
}
// Generate XML
const generateResult = await xmlGenerator.invoke({
data: { root: { name: 'John' } }
});
if (generateResult.success) {
console.log(generateResult.xml); // '<root><name>John</name></root>'
}
// Convert XML to JSON
const xmlToJsonResult = await xmlToJson.invoke({
xml: '<root><name>John</name></root>',
pretty: true
});
if (xmlToJsonResult.success) {
console.log(xmlToJsonResult.json); // '{\n "root": {\n "name": "John"\n }\n}'
}
// Convert JSON to XML
const jsonToXmlResult = await jsonToXml.invoke({
json: '{"name":"John"}',
rootName: 'person',
format: true
});
if (jsonToXmlResult.success) {
console.log(jsonToXmlResult.xml); // '<person>\n <name>John</name>\n</person>'
}Data Transformation
import {
arrayFilter,
arrayMap,
arraySort,
arrayGroupBy,
objectPick,
objectOmit
} from '@agentforge/tools';
// Filter array
const filterResult = await arrayFilter.invoke({
array: [{ age: 25 }, { age: 30 }, { age: 35 }],
property: 'age',
operator: 'greater-than',
value: 28
});
console.log(filterResult.filtered); // [{ age: 30 }, { age: 35 }]
console.log(filterResult.filteredCount); // 2
// Map array (extract properties)
const mapResult = await arrayMap.invoke({
array: [{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }],
properties: ['name']
});
console.log(mapResult.mapped); // [{ name: 'John' }, { name: 'Jane' }]
console.log(mapResult.count); // 2
// Sort array
const sortResult = await arraySort.invoke({
array: [{ score: 3 }, { score: 1 }, { score: 2 }],
property: 'score',
order: 'asc'
});
console.log(sortResult.sorted); // [{ score: 1 }, { score: 2 }, { score: 3 }]
console.log(sortResult.count); // 3
// Group by
const groupResult = await arrayGroupBy.invoke({
array: [{ type: 'a', val: 1 }, { type: 'a', val: 2 }, { type: 'b', val: 3 }],
property: 'type'
});
console.log(groupResult.groups); // { a: [{ type: 'a', val: 1 }, { type: 'a', val: 2 }], b: [{ type: 'b', val: 3 }] }
console.log(groupResult.groupCount); // 2
console.log(groupResult.totalItems); // 3Neo4j Graph Database (7 tools)
Production-ready Neo4j integration with support for knowledge graphs, GraphRAG, and vector search.
Quick Start
# Install Neo4j driver (peer dependency)
pnpm add neo4j-driver
# Set environment variables
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your-password
# For embedding-enabled tools (optional)
EMBEDDING_PROVIDER=openai # Options: openai, cohere, huggingface, voyage, ollama
OPENAI_API_KEY=sk-... # Or appropriate key for your providerInitialize Connection
import { initializeNeo4jTools } from '@agentforge/tools';
// Initialize from environment variables
await initializeNeo4jTools();
// Or with custom config
import { neo4jPool } from '@agentforge/tools';
await neo4jPool.initialize({
uri: 'bolt://localhost:7687',
username: 'neo4j',
password: 'your-password',
database: 'neo4j',
});1. neo4jQuery - Execute Cypher Queries
import { neo4jQuery } from '@agentforge/tools';
// Create nodes and relationships
const result = await neo4jQuery.execute({
cypher: `
CREATE (p:Person {name: $name, age: $age})
RETURN p
`,
parameters: { name: 'Alice', age: 30 }
});
console.log('Records:', result.data);
console.log('Summary:', result.summary);2. neo4jGetSchema - Inspect Graph Schema
import { neo4jGetSchema } from '@agentforge/tools';
const schema = await neo4jGetSchema.execute({});
console.log('Node labels:', schema.schema.nodeLabels);
console.log('Relationship types:', schema.schema.relationshipTypes);
console.log('Property keys:', schema.schema.propertyKeys);
console.log('Indexes:', schema.schema.indexes);
console.log('Constraints:', schema.schema.constraints);3. neo4jFindNodes - Find Nodes by Label/Properties
import { neo4jFindNodes } from '@agentforge/tools';
// Find all persons
const persons = await neo4jFindNodes.execute({
label: 'Person',
limit: 10
});
// Find with property filters
const adults = await neo4jFindNodes.execute({
label: 'Person',
properties: { age: 30 },
limit: 5
});
console.log(`Found ${adults.count} nodes`);
adults.nodes.forEach(node => {
console.log(node.properties);
});4. neo4jTraverse - Graph Traversal
import { neo4jTraverse } from '@agentforge/tools';
// Traverse from a starting node
const result = await neo4jTraverse.execute({
startNodeId: 123,
relationshipType: 'KNOWS', // Single relationship type (optional)
direction: 'outgoing', // lowercase: 'outgoing', 'incoming', or 'both'
maxDepth: 2,
limit: 20
});
console.log(`Found ${result.count} paths`);
result.paths.forEach(path => {
console.log('Start node:', path.start);
console.log('End node:', path.end);
console.log('Relationships:', path.relationships);
console.log('Depth:', path.depth);
});5. neo4jVectorSearch - Semantic Search
import { neo4jVectorSearch } from '@agentforge/tools';
// Search with pre-computed embedding
const embedding = [0.1, 0.2, 0.3, /* ... 1536 dimensions */];
const results = await neo4jVectorSearch.execute({
indexName: 'document_embeddings',
queryVector: embedding,
limit: 5
});
console.log(`Found ${results.count} similar nodes`);
results.results.forEach(item => {
console.log(`Score: ${item.score}`);
console.log('Node:', item.node.properties);
});6. neo4jVectorSearchWithEmbedding - Semantic Search (Auto-Embedding)
import { neo4jVectorSearchWithEmbedding } from '@agentforge/tools';
// Search with plain text - embedding generated automatically!
const results = await neo4jVectorSearchWithEmbedding.execute({
indexName: 'document_embeddings',
queryText: 'How do graph databases work?',
limit: 5
});
console.log('Query:', results.query.text);
console.log('Embedding model:', results.embedding.model);
console.log('Embedding dimensions:', results.embedding.dimensions);
results.results.forEach(item => {
console.log(`${item.node.properties.title} (score: ${item.score})`);
});7. neo4jCreateNodeWithEmbedding - Create Nodes with Auto-Embedding
import { neo4jCreateNodeWithEmbedding } from '@agentforge/tools';
// Create node with automatic embedding generation
const result = await neo4jCreateNodeWithEmbedding.execute({
label: 'Document',
properties: {
title: 'Introduction to Neo4j',
content: 'Neo4j is a powerful graph database...',
category: 'database'
},
textProperty: 'content', // Property to generate embedding from
embeddingProperty: 'embedding' // Where to store the embedding
});
console.log('Node created:', result.nodeId);
console.log('Embedding dimensions:', result.embedding.dimensions);
console.log('Model used:', result.embedding.model);Embedding Providers
AgentForge supports 5 embedding providers for maximum flexibility:
| Provider | Best For | Dimensions | Cost | Setup |
|---|---|---|---|---|
| OpenAI | Production, quality | 1536-3072 | Low | OPENAI_API_KEY |
| Cohere | Multilingual | 384-1024 | Medium | COHERE_API_KEY |
| HuggingFace | Open-source | 384-1024 | Low-Free | HUGGINGFACE_API_KEY |
| Voyage | Domain-specific | 1024-1536 | Medium | VOYAGE_API_KEY |
| Ollama | Privacy, offline | 384-1024 | Free | Local (no key) |
Switch providers easily:
# OpenAI (default)
EMBEDDING_PROVIDER=openai
OPENAI_API_KEY=sk-...
# Cohere
EMBEDDING_PROVIDER=cohere
COHERE_API_KEY=...
# HuggingFace
EMBEDDING_PROVIDER=huggingface
HUGGINGFACE_API_KEY=...
# Voyage AI
EMBEDDING_PROVIDER=voyage
VOYAGE_API_KEY=...
# Ollama (local, no API key!)
EMBEDDING_PROVIDER=ollama
OLLAMA_BASE_URL=http://localhost:11434 # OptionalOllama Setup (Privacy-Focused):
# Install Ollama
curl -fsSL https://ollama.ai/install.sh | sh
# Pull an embedding model
ollama pull nomic-embed-text
# Start Ollama
ollama serve
# Use in AgentForge (no API key needed!)
EMBEDDING_PROVIDER=ollamaGraphRAG Example
Complete GraphRAG implementation with automatic embeddings:
import {
neo4jQuery,
neo4jCreateNodeWithEmbedding,
neo4jVectorSearchWithEmbedding
} from '@agentforge/tools';
// 1. Create vector index (run once)
await neo4jQuery.execute({
cypher: `
CREATE VECTOR INDEX document_embeddings IF NOT EXISTS
FOR (n:Document)
ON (n.embedding)
OPTIONS {
indexConfig: {
\`vector.dimensions\`: 1536,
\`vector.similarity_function\`: 'cosine'
}
}
`
});
// 2. Add documents with automatic embeddings
const documents = [
{
title: 'Neo4j Guide',
content: 'Neo4j is a graph database that stores data as nodes and relationships.'
},
{
title: 'GraphRAG Overview',
content: 'GraphRAG combines graph databases with retrieval-augmented generation.'
},
{
title: 'Vector Search',
content: 'Vector search enables semantic similarity matching using embeddings.'
}
];
for (const doc of documents) {
await neo4jCreateNodeWithEmbedding.execute({
label: 'Document',
properties: doc,
textProperty: 'content',
embeddingProperty: 'embedding'
});
}
// 3. Query using semantic search
const results = await neo4jVectorSearchWithEmbedding.execute({
indexName: 'document_embeddings',
queryText: 'What is GraphRAG?',
limit: 3
});
console.log(`Found ${results.count} relevant documents:`);
results.results.forEach(item => {
console.log(`- ${item.node.properties.title} (score: ${item.score})`);
});Key Benefits:
- No manual embedding generation
- Provider-agnostic (switch with env vars)
- Automatic dimension handling
- Built-in retry logic
- Production-ready error handling
Relational Database Tools (6)
Vendor-agnostic tools for PostgreSQL, MySQL, and SQLite. See the Database Tools Guide for concepts and the Database Agent Tutorial for a walkthrough.
Installation:
pnpm add @agentforge/tools pg # PostgreSQL
pnpm add @agentforge/tools mysql2 # MySQL
pnpm add @agentforge/tools better-sqlite3 # SQLiteReturn Pattern
All relational tools return { success: true, ... } on success or { success: false, error: string } on failure. They do not throw for database or query errors.
Exception: If the required vendor driver (pg, mysql2, or better-sqlite3) is not installed, the tool will throw a MissingPeerDependencyError synchronously. Ensure peer dependencies are installed to avoid this.
relationalQuery
Execute raw SQL with parameterized binding:
import { relationalQuery } from '@agentforge/tools';
const result = await relationalQuery.invoke({
sql: 'SELECT id, email FROM users WHERE status = $1 LIMIT 10',
params: ['active'],
vendor: 'postgresql',
connectionString: 'postgresql://user:pass@localhost:5432/mydb',
});
// Success: { success: true, rows: [...], rowCount: 10, executionTime: 12 }
// Error: { success: false, error: '...', rows: [], rowCount: 0 }| Parameter | Type | Required | Description |
|---|---|---|---|
sql | string | Yes | SQL query with placeholders ($1 for PG, ? for MySQL/SQLite) |
params | unknown[] | Record<string, unknown> | No | Positional or named parameters |
vendor | 'postgresql' | 'mysql' | 'sqlite' | Yes | Database vendor |
connectionString | string | Yes | Database connection string |
relationalSelect
Type-safe SELECT without writing SQL:
import { relationalSelect } from '@agentforge/tools';
const result = await relationalSelect.invoke({
table: 'orders',
columns: ['id', 'total', 'status'],
where: [
{ column: 'status', operator: 'eq', value: 'pending' },
{ column: 'total', operator: 'gt', value: 100 },
],
orderBy: [{ column: 'total', direction: 'desc' }],
limit: 10,
offset: 0,
vendor: 'postgresql',
connectionString: DB_URL,
});
// Success: { success: true, rows: [...], rowCount: 10, executionTime: 8 }| Parameter | Type | Required | Description |
|---|---|---|---|
table | string | Yes | Table name (e.g. users or public.users) |
columns | string[] | No | Column filter (omit for SELECT *) |
where | WhereCondition[] | No | WHERE conditions (combined with AND) |
orderBy | OrderBy[] | No | ORDER BY clauses |
limit | number | No | Max rows to return |
offset | number | No | Rows to skip |
streaming | StreamingOptions | No | Chunked streaming mode |
vendor | string | Yes | Database vendor |
connectionString | string | Yes | Connection string |
WhereCondition:
{ column: string, operator: Operator, value?: ScalarValue }Operators: eq, ne, gt, lt, gte, lte, like, in, notIn, isNull, isNotNull
StreamingOptions:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable chunked streaming |
chunkSize | number | 100 | Rows per chunk (1–5000) |
maxRows | number | — | Cap on total streamed rows |
sampleSize | number | 50 | Rows included in response payload (0–5000) |
benchmark | boolean | false | Memory benchmark mode |
relationalInsert
Insert single rows or batch arrays:
import { relationalInsert } from '@agentforge/tools';
// Single row with RETURNING
const result = await relationalInsert.invoke({
table: 'users',
data: { email: 'alice@example.com', name: 'Alice' },
returning: { mode: 'id', idColumn: 'id' },
vendor: 'postgresql',
connectionString: DB_URL,
});
// { success: true, rowCount: 1, insertedIds: [42], rows: [], executionTime: 5 }
// Batch insert
const batch = await relationalInsert.invoke({
table: 'events',
data: events, // Array of records
batch: { batchSize: 200, continueOnError: true },
vendor: 'postgresql',
connectionString: DB_URL,
});
// { success: true, rowCount: 1000, batch: { successfulItems: 1000, ... } }| Parameter | Type | Required | Description |
|---|---|---|---|
table | string | Yes | Table name |
data | Record | Record[] | Yes | Row(s) to insert |
returning | { mode, idColumn? } | No | Return mode: 'none' (default), 'id', 'row' |
batch | BatchOptions | No | Batch execution controls |
vendor | string | Yes | Database vendor |
connectionString | string | Yes | Connection string |
BatchOptions (shared by Insert, Update, Delete):
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable batched processing |
batchSize | number | 100 | Items per chunk (1–5000) |
continueOnError | boolean | true | Continue on chunk failure |
maxRetries | number | 0 | Retries per failed chunk (0–5) |
retryDelayMs | number | 0 | Delay between retries in ms (0–60000) |
benchmark | boolean | false | Synthetic benchmark mode |
relationalUpdate
Conditional updates with safety guards:
import { relationalUpdate } from '@agentforge/tools';
// Single update
const result = await relationalUpdate.invoke({
table: 'users',
data: { status: 'verified' },
where: [{ column: 'id', operator: 'eq', value: 42 }],
vendor: 'postgresql',
connectionString: DB_URL,
});
// { success: true, rowCount: 1, executionTime: 3 }
// Batch update (multiple independent operations)
const batch = await relationalUpdate.invoke({
table: 'products',
operations: [
{ data: { price: 9.99 }, where: [{ column: 'id', operator: 'eq', value: 1 }] },
{ data: { price: 19.99 }, where: [{ column: 'id', operator: 'eq', value: 2 }] },
],
batch: { batchSize: 50 },
vendor: 'postgresql',
connectionString: DB_URL,
});| Parameter | Type | Required | Description |
|---|---|---|---|
table | string | Yes | Table name |
data | Record | Conditional | Column-value pairs (single mode) |
where | WhereCondition[] | Conditional | Required unless allowFullTableUpdate: true |
allowFullTableUpdate | boolean | No | Explicit opt-in for no-WHERE update |
optimisticLock | { column, expectedValue } | No | Optimistic locking condition |
operations | UpdateOperation[] | Conditional | Batch mode (mutually exclusive with data) |
batch | BatchOptions | No | Batch execution controls |
relationalDelete
Safe deletion with WHERE requirement:
import { relationalDelete } from '@agentforge/tools';
// Hard delete
const result = await relationalDelete.invoke({
table: 'sessions',
where: [{ column: 'expires_at', operator: 'lt', value: '2026-01-01' }],
vendor: 'postgresql',
connectionString: DB_URL,
});
// { success: true, rowCount: 150, softDeleted: false, executionTime: 12 }
// Soft delete
const soft = await relationalDelete.invoke({
table: 'users',
where: [{ column: 'id', operator: 'eq', value: 42 }],
softDelete: { column: 'deleted_at' },
vendor: 'postgresql',
connectionString: DB_URL,
});
// { success: true, rowCount: 1, softDeleted: true }| Parameter | Type | Required | Description |
|---|---|---|---|
table | string | Yes | Table name |
where | WhereCondition[] | Conditional | Required unless allowFullTableDelete: true |
allowFullTableDelete | boolean | No | Explicit opt-in for full-table delete |
cascade | boolean | No | Cascade-aware error messaging |
softDelete | { column?, value? } | No | Soft-delete mode (column defaults to 'deleted_at') |
operations | DeleteOperation[] | Conditional | Batch delete operations |
batch | BatchOptions | No | Batch execution controls |
relationalGetSchema
Introspect tables, columns, keys, and indexes:
import { relationalGetSchema } from '@agentforge/tools';
const result = await relationalGetSchema.invoke({
vendor: 'postgresql',
connectionString: DB_URL,
tables: ['users', 'orders'],
cacheTtlMs: 300000,
});
// Success response:
// {
// success: true,
// schema: {
// vendor: 'postgresql',
// tables: [
// {
// name: 'users',
// schema: 'public',
// columns: [
// { name: 'id', type: 'integer', isNullable: false, isPrimaryKey: true, defaultValue: null },
// { name: 'email', type: 'varchar(255)', isNullable: false, isPrimaryKey: false, defaultValue: null },
// ],
// primaryKey: ['id'],
// foreignKeys: [],
// indexes: [{ name: 'users_pkey', columns: ['id'], isUnique: true }],
// },
// ],
// generatedAt: '2026-02-21T...',
// },
// summary: { tableCount: 2, columnCount: 8, foreignKeyCount: 1, indexCount: 3 },
// }| Parameter | Type | Required | Description |
|---|---|---|---|
vendor | string | Yes | Database vendor |
connectionString | string | Yes | Connection string |
database | string | No | Database name for cache scoping |
tables | string[] | No | Filter to specific tables |
cacheTtlMs | number | No | Cache TTL in ms (0 disables) |
refreshCache | boolean | No | Force cache invalidation |
ConnectionManager
The ConnectionManager class handles connection lifecycle, pooling, and reconnection.
import { ConnectionManager } from '@agentforge/tools';
const manager = new ConnectionManager(
{
vendor: 'postgresql',
connection: {
connectionString: 'postgresql://user:pass@localhost:5432/mydb',
pool: { max: 20, acquireTimeoutMillis: 5000, idleTimeoutMillis: 30000 },
},
},
{ enabled: true, maxAttempts: 5, baseDelayMs: 1000, maxDelayMs: 30000 },
);Methods:
| Method | Returns | Description |
|---|---|---|
connect() | Promise<void> | Connect to the database (idempotent) |
disconnect() | Promise<void> | Close connection and cancel reconnection |
dispose() | Promise<void> | Disconnect and remove all event listeners |
isConnected() | boolean | Check connection status |
getState() | ConnectionState | Get current state ('disconnected', 'connecting', 'connected', 'reconnecting', 'error') |
getVendor() | DatabaseVendor | Get configured vendor |
getPoolMetrics() | PoolMetrics | Pool stats: totalCount, activeCount, idleCount, waitingCount |
isHealthy() | Promise<boolean> | Health check (SELECT 1) |
Events:
| Event | Payload | Description |
|---|---|---|
'connected' | — | Connection established |
'disconnected' | — | Connection closed |
'error' | Error | Connection error |
'reconnecting' | { attempt, maxAttempts, delayMs } | Reconnection attempt scheduled |
withTransaction
ACID transactions with isolation levels and timeout:
import { withTransaction, ConnectionManager } from '@agentforge/tools';
import { sql } from 'drizzle-orm';
const result = await withTransaction(manager, async (tx) => {
await tx.execute(sql`UPDATE accounts SET balance = balance - 100 WHERE id = 1`);
await tx.execute(sql`UPDATE accounts SET balance = balance + 100 WHERE id = 2`);
return { transferred: true };
}, {
isolationLevel: 'serializable',
timeoutMs: 5000,
});drizzle-orm Dependency
The sql tagged template comes from drizzle-orm. Add it as a direct dependency:
pnpm add drizzle-orm| Option | Type | Description |
|---|---|---|
isolationLevel | 'read uncommitted' | 'read committed' | 'repeatable read' | 'serializable' | Transaction isolation level |
timeoutMs | number | Max transaction duration before auto-rollback |
The TransactionContext (tx) provides: execute(), isActive(), commit(), rollback(), createSavepoint(), rollbackToSavepoint(), releaseSavepoint(), withSavepoint().
File Tools (18)
File Operations
import {
fileReader,
fileWriter,
fileAppend,
fileDelete,
fileExists
} from '@agentforge/tools';
// Read file
const readResult = await fileReader.invoke({
path: '/path/to/file.txt',
encoding: 'utf-8'
});
if (readResult.success) {
console.log(readResult.data?.content); // File contents
console.log(readResult.data?.size); // File size in bytes
console.log(readResult.data?.path); // '/path/to/file.txt'
console.log(readResult.data?.encoding); // 'utf-8'
}
// Write file
await fileWriter.invoke({
path: '/path/to/file.txt',
content: 'Hello World'
});
// Append to file
await fileAppend.invoke({
path: '/path/to/file.txt',
content: '\nNew line'
});
// Delete file
await fileDelete.invoke({
path: '/path/to/file.txt'
});
// Check if file exists
const exists = await fileExists.invoke({
path: '/path/to/file.txt'
});Directory Operations
import {
directoryList,
directoryCreate,
directoryDelete,
fileSearch
} from '@agentforge/tools';
// List directory
const listResult = await directoryList.invoke({
path: '/path/to/dir',
recursive: true
});
if (listResult.success) {
console.log(listResult.data?.files); // Array of file objects
console.log(listResult.data?.count); // Number of files
console.log(listResult.data?.path); // '/path/to/dir'
}
// Create directory
await directoryCreate.invoke({
path: '/path/to/new/dir'
});
// Search files
const searchResult = await fileSearch.invoke({
directory: '/path/to/dir',
pattern: '*.ts',
recursive: true
});
if (searchResult.success) {
console.log(searchResult.data?.matches); // Array of matching file paths
console.log(searchResult.data?.count); // Number of matches
console.log(searchResult.data?.directory); // '/path/to/dir'
console.log(searchResult.data?.pattern); // '*.ts'
}Filesystem Confinement
The standalone file and directory tool exports are privileged filesystem operations. For model-exposed agents, configure both factory groups with one shared policy:
import {
createDirectoryOperationTools,
createFileOperationTools,
createFileSystemPolicy,
} from '@agentforge/tools';
const policy = createFileSystemPolicy({
workspaceRoot: process.cwd(),
});
const safeFileTools = createFileOperationTools({ policy });
const safeDirectoryTools = createDirectoryOperationTools({ policy });workspaceRoot resolves relative paths from the workspace and confines all configured operations to that root. Use allowedRoots for multiple permitted roots. The policy rejects lexical traversal, resolved symlink escapes, paths outside the allowed roots, and recursive deletion of an allowed root. Set allowOutsideRoots: true only for trusted local automation that explicitly needs unrestricted host access.
Model-Safe File and Web Preset
When file and web tools are exposed to a model, use the combined preset instead of wiring separate policy objects by hand:
import { createModelSafeToolPreset } from '@agentforge/tools';
const preset = createModelSafeToolPreset({
fileSystem: { workspaceRoot: process.cwd() },
});
const agentTools = preset.tools;The preset requires workspaceRoot or allowedRoots, applies one confinement policy to all file and directory tools, and preserves the default web blocks for localhost, private-network, link-local, metadata, and unsafe redirect destinations. Privileged override flags are forced back to safe values; use the standalone factories only for trusted operator-controlled automation.
Utility Tools (22)
Date/Time
import { dateFormatter, currentDateTime, dateArithmetic, dateDifference } from '@agentforge/tools';String Utilities
import {
stringCaseConverter,
stringTrim,
stringReplace,
stringSplit,
stringJoin,
stringSubstring,
stringLength
} from '@agentforge/tools';Math Operations
import {
calculator,
mathFunctions, // Includes round, abs, floor, ceil, etc.
randomNumber,
statistics
} from '@agentforge/tools';Validation
import {
emailValidator,
urlValidatorSimple,
phoneValidator,
creditCardValidator,
ipValidator,
uuidValidator
} from '@agentforge/tools';Agent Tools (1)
Ask Human
import { createAskHumanTool } from '@agentforge/tools';
// Create the tool
const askHuman = createAskHumanTool();
// Use in your agent
const tools = [askHuman, ...otherTools];
// The agent can call this tool to request human input
// Execution pauses until a human respondsFeatures:
- Human-in-the-loop workflows
- Priority levels (low, normal, high, critical)
- Timeout handling with default responses
- Suggested responses for UI
- LangGraph interrupt integration
Note: Requires @langchain/langgraph to be installed.
Complete Tool List
See the Tools README for the complete list of all 88 tools with detailed documentation.