We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/suttonwilliamd/tpc-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
const express = require('express');
const { Router } = express;
const { getAll, runSql, getDB } = require('../db/database.js');
const router = Router();
// POST /
router.post('/', async (req, res, next) => {
try {
const { content, plan_id, tags: inputTags } = req.body;
if (!content || content.trim() === '') {
return res.status(400).json({ error: 'Content is required and cannot be empty' });
}
let tags = [];
if (inputTags) {
if (!Array.isArray(inputTags)) {
return res.status(400).json({ error: 'Tags must be an array of strings' });
}
tags = inputTags.filter(tag => typeof tag === 'string' && tag.trim() !== '').map(tag => tag.trim().toLowerCase());
const uniqueTags = [...new Set(tags)];
if (uniqueTags.length !== tags.length) {
return res.status(400).json({ error: 'Tags must not contain duplicates' });
}
if (uniqueTags.length > 10) {
return res.status(400).json({ error: 'Maximum 10 tags allowed' });
}
tags = uniqueTags;
}
const db = req.db || getDB();
const timestamp = new Date().toISOString();
const planIdParam = plan_id ? parseInt(plan_id) : null;
if (plan_id && isNaN(planIdParam)) {
return res.status(400).json({ error: 'Invalid plan_id' });
}
const result = await runSql(db, "INSERT INTO thoughts (timestamp, content, plan_id, tags) VALUES (?, ?, ?, ?)", [timestamp, content, planIdParam, JSON.stringify(tags)]);
const id = result.lastID;
console.log(`POST /thoughts: Inserted ID ${id}, content: "${content}"`);
const newThought = {
id: id.toString(),
content,
timestamp,
...(plan_id && { plan_id }),
tags
};
res.status(201).json(newThought);
} catch (err) {
next(err);
}
});
// POST /bulk - Bulk insert multiple thoughts at once
router.post('/bulk', async (req, res, next) => {
try {
const { thoughts } = req.body;
if (!thoughts || !Array.isArray(thoughts)) {
return res.status(400).json({ error: 'Thoughts must be an array of objects with content (and optional tags)' });
}
if (thoughts.length > 1000) {
return res.status(400).json({ error: 'Maximum 1000 thoughts per bulk insert' });
}
const db = req.db || getDB();
const timestamp = new Date().toISOString();
const insertedIds = [];
const insertStmt = await db.prepare("INSERT INTO thoughts (timestamp, content, plan_id, tags) VALUES (?, ?, ?, ?)");
for (const thought of thoughts) {
const content = thought.content;
if (!content || content.trim() === '') continue;
let tags = [];
if (thought.tags && Array.isArray(thought.tags)) {
tags = thought.tags.filter(tag => typeof tag === 'string' && tag.trim() !== '').map(tag => tag.trim().toLowerCase());
}
const result = insertStmt.run(timestamp, content, null, JSON.stringify(tags));
insertedIds.push(result.lastID);
}
console.log(`POST /thoughts/bulk: Inserted ${insertedIds.length} thoughts`);
res.status(201).json({ inserted: insertedIds.length, ids: insertedIds });
} catch (err) {
next(err);
}
});
// DELETE /cleanup - Remove buggy DF entries
router.delete('/cleanup', async (req, res, next) => {
try {
const db = req.db || getDB();
const buggyTimestamps = ['2026-02-22T14:47', '2026-02-22T15:06', '2026-02-22T15:16'];
let totalDeleted = 0;
for (const ts of buggyTimestamps) {
const result = db.prepare("DELETE FROM thoughts WHERE content LIKE '%DF Legends%' AND timestamp LIKE ?").run(ts + '%');
totalDeleted += result.changes;
}
console.log(`Deleted ${totalDeleted} buggy DF entries`);
res.json({ deleted: totalDeleted });
} catch (err) {
next(err);
}
});
// GET /
router.get('/', async (req, res, next) => {
try {
const db = req.db || getDB();
let sql = "SELECT * FROM thoughts";
let params = [];
let whereClauses = [];
if (req.query.since) {
const since = Number(req.query.since);
if (!isNaN(since)) {
const sinceIso = new Date(since).toISOString();
whereClauses.push("timestamp >= ?");
params.push(sinceIso);
}
}
// Tags filtering
if (req.query.tags) {
const tagsValue = req.query.tags.toString().trim();
let mode = 'any';
let tagsList = [];
if (tagsValue.startsWith('any:')) {
tagsList = tagsValue.substring(4).split(',').map(t => t.trim().toLowerCase()).filter(t => t);
} else if (tagsValue.startsWith('all:')) {
mode = 'all';
tagsList = tagsValue.substring(4).split(',').map(t => t.trim().toLowerCase()).filter(t => t);
} else {
tagsList = tagsValue.split(',').map(t => t.trim().toLowerCase()).filter(t => t);
}
if (tagsList.length > 0) {
const tagConditions = tagsList.map(tag => `tags LIKE '%"${tag}"%'`).join(mode === 'all' ? ' AND ' : ' OR ');
whereClauses.push(`(${tagConditions})`);
}
}
if (whereClauses.length > 0) {
sql += " WHERE " + whereClauses.join(" AND ");
}
sql += " ORDER BY timestamp ASC";
const limit = req.query.limit;
if (limit) {
const limitNum = parseInt(limit);
if (!isNaN(limitNum) && limitNum > 0) {
sql += " LIMIT ?";
params.push(limitNum);
}
// ignore invalid or <=0
}
const rawThoughts = await new Promise((resolve, reject) => {
db.all(sql, params, (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
const responseThoughts = rawThoughts.map(t => ({
id: t.id.toString(),
content: t.content,
timestamp: t.timestamp,
tags: JSON.parse(t.tags || '[]'),
...(t.plan_id && { plan_id: t.plan_id.toString() })
}));
console.log(`GET /thoughts: Returning ${responseThoughts.length} thoughts`);
res.status(200).json(responseThoughts);
} catch (err) {
next(err);
}
});
router.get('/:id', async (req, res, next) => {
try {
const db = req.db || getDB();
const thoughtId = parseInt(req.params.id);
const thought = await new Promise((resolve, reject) => {
db.get("SELECT * FROM thoughts WHERE id = ?", [thoughtId], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
if (!thought) {
return res.status(404).json({ error: 'Thought not found' });
}
const responseThought = {
id: thought.id.toString(),
content: thought.content,
timestamp: thought.timestamp,
tags: JSON.parse(thought.tags || '[]'),
...(thought.plan_id && { plan_id: thought.plan_id.toString() })
};
res.status(200).json(responseThought);
} catch (err) {
next(err);
}
});
router.patch('/:id/tags', async (req, res, next) => {
try {
const db = req.db || getDB();
const { add, remove } = req.body;
const thoughtId = parseInt(req.params.id);
if (!add && !remove) {
return res.status(400).json({ error: 'At least one of "add" or "remove" must be provided as arrays' });
}
const thought = await new Promise((resolve, reject) => {
db.get("SELECT tags FROM thoughts WHERE id = ?", [thoughtId], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
if (!thought) {
const err = new Error('Thought not found');
err.status = 404;
throw err;
}
let currentTags = JSON.parse(thought.tags || '[]');
let newTags = [...currentTags];
if (add) {
if (!Array.isArray(add)) {
return res.status(400).json({ error: '"add" must be an array of strings' });
}
const addTags = add.filter(tag => typeof tag === 'string' && tag.trim() !== '').map(tag => tag.trim().toLowerCase());
const uniqueAdd = addTags.filter(tag => !newTags.includes(tag));
newTags = [...new Set([...newTags, ...uniqueAdd])];
}
if (remove) {
if (!Array.isArray(remove)) {
return res.status(400).json({ error: '"remove" must be an array of strings' });
}
const removeTags = remove.filter(tag => typeof tag === 'string' && tag.trim() !== '').map(tag => tag.trim().toLowerCase());
newTags = newTags.filter(tag => !removeTags.includes(tag));
}
if (newTags.length > 10) {
return res.status(400).json({ error: 'Maximum 10 tags allowed after operation' });
}
const now = Date.now();
await new Promise((resolve, reject) => {
db.run("UPDATE thoughts SET tags = ? WHERE id = ?", [JSON.stringify(newTags), thoughtId], function(err) {
if (err) reject(err);
else resolve(this);
});
});
const updatedThought = {
id: thoughtId.toString(),
tags: newTags
};
res.status(200).json(updatedThought);
} catch (err) {
next(err);
}
});
module.exports = router;