adding an MCP Server 🎉

This commit is contained in:
orangecoding
2026-03-09 15:35:29 +01:00
parent a460b813c1
commit be5c4af3cf
21 changed files with 1374 additions and 51 deletions

View File

@@ -5,10 +5,18 @@
import * as hasher from '../security/hash.js';
import { nanoid } from 'nanoid';
import crypto from 'crypto';
import SqliteConnection from './SqliteConnection.js';
import { getSettings } from './settingsStorage.js';
import { inDevMode } from '../../utils.js';
/**
* Generate a permanent, non-expiring MCP API token.
* These tokens are secrets that never expire and are used for MCP authentication.
* @returns {string}
*/
const generateMcpToken = () => `fredy_${crypto.randomBytes(32).toString('hex')}`;
/**
* Get all users.
*
@@ -21,7 +29,7 @@ import { inDevMode } from '../../utils.js';
*/
export const getUsers = (withPassword) => {
const rows = SqliteConnection.query(
`SELECT u.id, u.username, u.password, u.last_login AS lastLogin, u.is_admin AS isAdmin,
`SELECT u.id, u.username, u.password, u.last_login AS lastLogin, u.is_admin AS isAdmin, u.mcp_token AS mcpToken,
(SELECT COUNT(1) FROM jobs j WHERE j.user_id = u.id) AS numberOfJobs
FROM users u
ORDER BY u.username`,
@@ -41,7 +49,7 @@ export const getUsers = (withPassword) => {
*/
export const getUser = (id) => {
const rows = SqliteConnection.query(
`SELECT u.id, u.username, u.password, u.last_login AS lastLogin, u.is_admin AS isAdmin,
`SELECT u.id, u.username, u.password, u.last_login AS lastLogin, u.is_admin AS isAdmin, u.mcp_token AS mcpToken,
(SELECT COUNT(1) FROM jobs j WHERE j.user_id = u.id) AS numberOfJobs
FROM users u
WHERE u.id = @id
@@ -88,14 +96,15 @@ export const upsertUser = ({ username, password, userId, isAdmin }) => {
}
} else {
SqliteConnection.execute(
`INSERT INTO users (id, username, password, last_login, is_admin)
VALUES (@id, @username, @password, @last_login, @is_admin)`,
`INSERT INTO users (id, username, password, last_login, is_admin, mcp_token)
VALUES (@id, @username, @password, @last_login, @is_admin, @mcp_token)`,
{
id,
username,
password: hasher.hash(password || ''),
last_login: null,
is_admin: isAdmin ? 1 : 0,
mcp_token: generateMcpToken(),
},
);
}
@@ -150,9 +159,9 @@ export const ensureDemoUserExists = async () => {
const existing = SqliteConnection.query(`SELECT id FROM users WHERE username = 'demo' LIMIT 1`);
if (existing.length === 0) {
SqliteConnection.execute(
`INSERT INTO users (id, username, password, last_login, is_admin)
VALUES (@id, 'demo', @password, NULL, 1)`,
{ id: nanoid(), password: hasher.hash('demo') },
`INSERT INTO users (id, username, password, last_login, is_admin, mcp_token)
VALUES (@id, 'demo', @password, NULL, 1, @mcp_token)`,
{ id: nanoid(), password: hasher.hash('demo'), mcp_token: generateMcpToken() },
);
}
};
@@ -167,13 +176,25 @@ export const ensureDemoUserExists = async () => {
* Security: On a fresh instance, a default admin/admin is created; change this password immediately.
* @returns {void}
*/
/**
* Validate an MCP API token and return the associated user id.
* MCP tokens are permanent secrets stored in the users table that never expire.
* @param {string} token - The raw token string (e.g. fredy_...).
* @returns {{ userId: string } | null} The user id or null if invalid.
*/
export const validateMcpToken = (token) => {
if (!token) return null;
const row = SqliteConnection.query(`SELECT id FROM users WHERE mcp_token = @token LIMIT 1`, { token })[0];
return row ? { userId: row.id } : null;
};
export const ensureAdminUserExists = () => {
const anyUser = SqliteConnection.query(`SELECT id FROM users LIMIT 1`).length > 0;
if (!anyUser) {
SqliteConnection.execute(
`INSERT INTO users (id, username, password, last_login, is_admin)
VALUES (@id, 'admin', @password, @last_login, 1)`,
{ id: nanoid(), password: hasher.hash('admin'), last_login: Date.now() },
`INSERT INTO users (id, username, password, last_login, is_admin, mcp_token)
VALUES (@id, 'admin', @password, @last_login, 1, @mcp_token)`,
{ id: nanoid(), password: hasher.hash('admin'), last_login: Date.now(), mcp_token: generateMcpToken() },
);
return;
}