@fastmcp-me/mcp-sqlew
Version:
MCP server for efficient context sharing between Claude Code sub-agents with 96% token reduction via action-based tools
442 lines (439 loc) • 16.4 kB
JavaScript
/**
* Context management tools for MCP Shared Context Server
* Implements set_decision, get_context, and get_decision tools
*/
import { getDatabase, getOrCreateAgent, getOrCreateContextKey, getOrCreateTag, getOrCreateScope, getLayerId, transaction } from '../database.js';
import { STRING_TO_STATUS, DEFAULT_VERSION, DEFAULT_STATUS } from '../constants.js';
/**
* Set or update a decision in the context
* Auto-detects numeric vs string values and routes to appropriate table
* Supports tags, layers, scopes, and version tracking
*
* @param params - Decision parameters
* @returns Response with success status and metadata
*/
export function setDecision(params) {
const db = getDatabase();
// Validate required parameters
if (!params.key || params.key.trim() === '') {
throw new Error('Parameter "key" is required and cannot be empty');
}
if (params.value === undefined || params.value === null) {
throw new Error('Parameter "value" is required');
}
// Determine if value is numeric
const isNumeric = typeof params.value === 'number';
const value = params.value;
// Set defaults
const version = params.version || DEFAULT_VERSION;
const status = params.status ? STRING_TO_STATUS[params.status] : DEFAULT_STATUS;
const agentName = params.agent || 'system';
// Validate status
if (params.status && !STRING_TO_STATUS[params.status]) {
throw new Error(`Invalid status: ${params.status}. Must be 'active', 'deprecated', or 'draft'`);
}
// Validate layer if provided
let layerId = null;
if (params.layer) {
layerId = getLayerId(db, params.layer);
if (layerId === null) {
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
}
}
try {
// Use transaction for atomicity
return transaction(db, () => {
// Get or create master records
const agentId = getOrCreateAgent(db, agentName);
const keyId = getOrCreateContextKey(db, params.key);
// Current timestamp
const ts = Math.floor(Date.now() / 1000);
// Insert or update decision based on value type
if (isNumeric) {
// Numeric decision
const stmt = db.prepare(`
INSERT INTO t_decisions_numeric (key_id, value, agent_id, layer_id, version, status, ts)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(key_id) DO UPDATE SET
value = excluded.value,
agent_id = excluded.agent_id,
layer_id = excluded.layer_id,
version = excluded.version,
status = excluded.status,
ts = excluded.ts
`);
stmt.run(keyId, value, agentId, layerId, version, status, ts);
}
else {
// String decision
const stmt = db.prepare(`
INSERT INTO t_decisions (key_id, value, agent_id, layer_id, version, status, ts)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(key_id) DO UPDATE SET
value = excluded.value,
agent_id = excluded.agent_id,
layer_id = excluded.layer_id,
version = excluded.version,
status = excluded.status,
ts = excluded.ts
`);
stmt.run(keyId, String(value), agentId, layerId, version, status, ts);
}
// Handle m_tags (many-to-many)
if (params.tags && params.tags.length > 0) {
// Clear existing tags
db.prepare('DELETE FROM t_decision_tags WHERE decision_key_id = ?').run(keyId);
// Insert new tags
const tagStmt = db.prepare('INSERT INTO t_decision_tags (decision_key_id, tag_id) VALUES (?, ?)');
for (const tagName of params.tags) {
const tagId = getOrCreateTag(db, tagName);
tagStmt.run(keyId, tagId);
}
}
// Handle m_scopes (many-to-many)
if (params.scopes && params.scopes.length > 0) {
// Clear existing scopes
db.prepare('DELETE FROM t_decision_scopes WHERE decision_key_id = ?').run(keyId);
// Insert new scopes
const scopeStmt = db.prepare('INSERT INTO t_decision_scopes (decision_key_id, scope_id) VALUES (?, ?)');
for (const scopeName of params.scopes) {
const scopeId = getOrCreateScope(db, scopeName);
scopeStmt.run(keyId, scopeId);
}
}
return {
success: true,
key: params.key,
key_id: keyId,
version: version,
message: `Decision "${params.key}" set successfully`
};
});
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to set decision: ${message}`);
}
}
/**
* Get context t_decisions with advanced filtering
* Uses v_tagged_decisions view for token efficiency
* Supports filtering by status, layer, tags, and scope
*
* @param params - Filter parameters
* @returns Array of t_decisions with metadata
*/
export function getContext(params = {}) {
const db = getDatabase();
try {
// Build query dynamically based on filters
let query = 'SELECT * FROM v_tagged_decisions WHERE 1=1';
const queryParams = [];
// Filter by status
if (params.status) {
if (!STRING_TO_STATUS[params.status]) {
throw new Error(`Invalid status: ${params.status}`);
}
query += ' AND status = ?';
queryParams.push(params.status);
}
// Filter by layer
if (params.layer) {
query += ' AND layer = ?';
queryParams.push(params.layer);
}
// Filter by scope
if (params.scope) {
// Use LIKE for comma-separated scopes
query += ' AND (scopes LIKE ? OR m_scopes = ?)';
queryParams.push(`%${params.scope}%`, params.scope);
}
// Filter by tags
if (params.tags && params.tags.length > 0) {
const tagMatch = params.tag_match || 'OR';
if (tagMatch === 'AND') {
// All m_tags must be present
for (const tag of params.tags) {
query += ' AND (tags LIKE ? OR m_tags = ?)';
queryParams.push(`%${tag}%`, tag);
}
}
else {
// Any tag must be present (OR)
const tagConditions = params.tags.map(() => '(tags LIKE ? OR m_tags = ?)').join(' OR ');
query += ` AND (${tagConditions})`;
for (const tag of params.tags) {
queryParams.push(`%${tag}%`, tag);
}
}
}
// Order by most recent
query += ' ORDER BY updated DESC';
// Execute query
const stmt = db.prepare(query);
const rows = stmt.all(...queryParams);
return {
decisions: rows,
count: rows.length
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to get context: ${message}`);
}
}
/**
* Get a specific decision by key
* Returns full metadata including tags, layer, scopes, version
*
* @param params - Decision key
* @returns Decision details or not found
*/
export function getDecision(params) {
const db = getDatabase();
// Validate parameter
if (!params.key || params.key.trim() === '') {
throw new Error('Parameter "key" is required and cannot be empty');
}
try {
// Query v_tagged_decisions view
const stmt = db.prepare('SELECT * FROM v_tagged_decisions WHERE key = ?');
const row = stmt.get(params.key);
if (!row) {
return {
found: false
};
}
return {
found: true,
decision: row
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to get decision: ${message}`);
}
}
/**
* Search for t_decisions by m_tags with AND/OR logic
* Provides flexible tag-based filtering with status and layer support
*
* @param params - Search parameters (tags, match_mode, status, layer)
* @returns Array of t_decisions matching tag criteria
*/
export function searchByTags(params) {
const db = getDatabase();
// Validate required parameters
if (!params.tags || params.tags.length === 0) {
throw new Error('Parameter "tags" is required and must contain at least one tag');
}
try {
const matchMode = params.match_mode || 'OR';
let query = 'SELECT * FROM v_tagged_decisions WHERE 1=1';
const queryParams = [];
// Apply tag filtering based on match mode
if (matchMode === 'AND') {
// All m_tags must be present
for (const tag of params.tags) {
query += ' AND (tags LIKE ? OR m_tags = ?)';
queryParams.push(`%${tag}%`, tag);
}
}
else if (matchMode === 'OR') {
// Any tag must be present
const tagConditions = params.tags.map(() => '(tags LIKE ? OR m_tags = ?)').join(' OR ');
query += ` AND (${tagConditions})`;
for (const tag of params.tags) {
queryParams.push(`%${tag}%`, tag);
}
}
else {
throw new Error(`Invalid match_mode: ${matchMode}. Must be 'AND' or 'OR'`);
}
// Optional status filter
if (params.status) {
if (!STRING_TO_STATUS[params.status]) {
throw new Error(`Invalid status: ${params.status}. Must be 'active', 'deprecated', or 'draft'`);
}
query += ' AND status = ?';
queryParams.push(params.status);
}
// Optional layer filter
if (params.layer) {
// Validate layer exists
const layerId = getLayerId(db, params.layer);
if (layerId === null) {
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
}
query += ' AND layer = ?';
queryParams.push(params.layer);
}
// Order by most recent
query += ' ORDER BY updated DESC';
// Execute query
const stmt = db.prepare(query);
const rows = stmt.all(...queryParams);
return {
decisions: rows,
count: rows.length
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to search by tags: ${message}`);
}
}
/**
* Get version history for a specific decision key
* Returns all historical versions ordered by timestamp (newest first)
*
* @param params - Decision key to get history for
* @returns Array of historical versions with metadata
*/
export function getVersions(params) {
const db = getDatabase();
// Validate required parameter
if (!params.key || params.key.trim() === '') {
throw new Error('Parameter "key" is required and cannot be empty');
}
try {
// Get key_id for the decision
const keyResult = db.prepare('SELECT id FROM m_context_keys WHERE key = ?').get(params.key);
if (!keyResult) {
// Key doesn't exist, return empty history
return {
key: params.key,
history: [],
count: 0
};
}
const keyId = keyResult.id;
// Query t_decision_history with agent join
const stmt = db.prepare(`
SELECT
dh.version,
dh.value,
a.name as agent_name,
datetime(dh.ts, 'unixepoch') as timestamp
FROM t_decision_history dh
LEFT JOIN m_agents a ON dh.agent_id = a.id
WHERE dh.key_id = ?
ORDER BY dh.ts DESC
`);
const rows = stmt.all(keyId);
// Transform to response format
const history = rows.map(row => ({
version: row.version,
value: row.value,
agent: row.agent_name,
timestamp: row.timestamp
}));
return {
key: params.key,
history: history,
count: history.length
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to get versions: ${message}`);
}
}
/**
* Search for t_decisions within a specific architecture layer
* Supports status filtering and optional tag inclusion
*
* @param params - Layer name, optional status and include_tags
* @returns Array of t_decisions in the specified layer
*/
export function searchByLayer(params) {
const db = getDatabase();
// Validate required parameter
if (!params.layer || params.layer.trim() === '') {
throw new Error('Parameter "layer" is required and cannot be empty');
}
try {
// Validate layer exists
const layerId = getLayerId(db, params.layer);
if (layerId === null) {
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
}
// Determine which view/table to use
const includeTagsValue = params.include_tags !== undefined ? params.include_tags : true;
const statusValue = params.status || 'active';
// Validate status
if (!STRING_TO_STATUS[statusValue]) {
throw new Error(`Invalid status: ${statusValue}. Must be 'active', 'deprecated', or 'draft'`);
}
let query;
const queryParams = [params.layer, statusValue];
if (includeTagsValue) {
// Use v_tagged_decisions view for full metadata
query = `
SELECT * FROM v_tagged_decisions
WHERE layer = ? AND status = ?
ORDER BY updated DESC
`;
}
else {
// Use base t_decisions table with minimal joins
query = `
SELECT
ck.key,
d.value,
d.version,
CASE d.status
WHEN 1 THEN 'active'
WHEN 2 THEN 'deprecated'
WHEN 3 THEN 'draft'
END as status,
l.name as layer,
NULL as tags,
NULL as scopes,
a.name as decided_by,
datetime(d.ts, 'unixepoch') as updated
FROM t_decisions d
INNER JOIN m_context_keys ck ON d.key_id = ck.id
LEFT JOIN m_layers l ON d.layer_id = l.id
LEFT JOIN m_agents a ON d.agent_id = a.id
WHERE l.name = ? AND d.status = ?
UNION ALL
SELECT
ck.key,
CAST(dn.value AS TEXT) as value,
dn.version,
CASE dn.status
WHEN 1 THEN 'active'
WHEN 2 THEN 'deprecated'
WHEN 3 THEN 'draft'
END as status,
l.name as layer,
NULL as tags,
NULL as scopes,
a.name as decided_by,
datetime(dn.ts, 'unixepoch') as updated
FROM t_decisions_numeric dn
INNER JOIN m_context_keys ck ON dn.key_id = ck.id
LEFT JOIN m_layers l ON dn.layer_id = l.id
LEFT JOIN m_agents a ON dn.agent_id = a.id
WHERE l.name = ? AND dn.status = ?
ORDER BY updated DESC
`;
// Add params for the numeric table part of UNION
queryParams.push(params.layer, statusValue);
}
// Execute query
const stmt = db.prepare(query);
const rows = stmt.all(...queryParams);
return {
layer: params.layer,
decisions: rows,
count: rows.length
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to search by layer: ${message}`);
}
}
//# sourceMappingURL=context.js.map