This commit is contained in:
seb
2026-10-12 22:32:21 +02:00
parent 28e62093b3
commit 8a8aeaedd7
21 changed files with 786 additions and 4 deletions

177
API.mcp.md Normal file
View File

@@ -0,0 +1,177 @@
# mcp
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
## `mcp.consumeAuthCode`
*Source: `src/store/mcp/consumeAuthCode.js`*
Marks an authorization code as used.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `code` | `string` | The authorization code to mark used. |
**Returns**
`Promise<boolean>` — True once marked.
## `mcp.createAuthCode`
*Source: `src/store/mcp/createAuthCode.js`*
Stores an OAuth 2.1 authorization code with PKCE parameters.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | |
| `input.code` | `string` | The generated authorization code. |
| `input.clientId` | `string` | The client ID. |
| `input.googleAuthId` | `number` | The ID of the authenticated user. |
| `input.redirectUri` | `string` | The redirect URI used in the authorize request. |
| `input.codeChallenge` | `string` | The PKCE code challenge. |
| `input.codeChallengeMethod` | `string` | The PKCE method (e.g. S256). |
| `[input.scope]` | `string|null` | Granted scope. |
| `input.expiresAt` | `string|Date` | Expiration ISO string or Date. |
**Returns**
`Promise<string>` — The authorization code.
## `mcp.createRefreshToken`
*Source: `src/store/mcp/createRefreshToken.js`*
Stores an OAuth 2.1 refresh token.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | |
| `input.refreshToken` | `string` | The refresh token string. |
| `input.clientId` | `string` | The client ID. |
| `input.googleAuthId` | `number` | The user ID. |
| `[input.scope]` | `string|null` | Scopes granted. |
| `input.expiresAt` | `string|Date` | Expiration Date or ISO string. |
**Returns**
`Promise<object>` — Created token record or id.
## `mcp.deleteClient`
*Source: `src/store/mcp/deleteClient.js`*
Deletes a registered MCP client by client ID.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `clientId` | `string` | The client identifier to delete. |
**Returns**
`Promise<boolean>` — True if deleted.
## `mcp.getAuthCode`
*Source: `src/store/mcp/getAuthCode.js`*
Retrieves an authorization code record.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `code` | `string` | The authorization code. |
**Returns**
`Promise<object|null>` — The auth code record or null.
## `mcp.getClient`
*Source: `src/store/mcp/getClient.js`*
Retrieves an MCP client by client ID.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `clientId` | `string` | The client identifier. |
**Returns**
`Promise<object|null>` — The client record or null if not found.
## `mcp.getRefreshToken`
*Source: `src/store/mcp/getRefreshToken.js`*
Retrieves an OAuth 2.1 refresh token.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `refreshToken` | `string` | The refresh token. |
**Returns**
`Promise<object|null>` — The refresh token record or null.
## `mcp.listClients`
*Source: `src/store/mcp/listClients.js`*
Lists all registered MCP clients.
**Returns**
`Promise<Array<object>>` — List of client records.
## `mcp.registerClient`
*Source: `src/store/mcp/registerClient.js`*
Registers a new OAuth MCP client.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `input` | `object` | |
| `input.clientId` | `string` | Unique client identifier. |
| `[input.clientSecret]` | `string|null` | Optional client secret for confidential clients. |
| `input.clientName` | `string` | Human-readable client name. |
| `input.redirectUris` | `string|Array<string>` | JSON string or array of redirect URIs. |
| `[input.grantTypes]` | `string|Array<string>` | Supported grant types. |
| `[input.responseTypes]` | `string|Array<string>` | Supported response types. |
| `[input.scope]` | `string|null` | Allowed scope. |
**Returns**
`Promise<object>` — The created client record.
## `mcp.revokeRefreshToken`
*Source: `src/store/mcp/revokeRefreshToken.js`*
Revokes an OAuth 2.1 refresh token.
**Parameters**
| Name | Type | Description |
| --- | --- | --- |
| `refreshToken` | `string` | The refresh token to revoke. |
**Returns**
`Promise<boolean>` — True once revoked.

11
API.md
View File

@@ -307,6 +307,17 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
- `delete(googleAuthId: number, categoryId: number, propertyId: number, tx?: object): Promise<void>`
- `listByCategoryId(googleAuthId: number, categoryId: number, tx?: object): Promise<Array<{id: number, categoryId: number, propertyId: number, name: string, exclusive: boolean, value: string, sort: number}>>`
- `setByCategoryId(googleAuthId: number, categoryId: number, properties: Array<{propertyId: number, value: string, sort?: number}>, tx?: object): Promise<Array<{id: number, categoryId: number, propertyId: number, name: string, exclusive: boolean, value: string, sort: number}>>`
- [mcp](API.mcp.md)
- `consumeAuthCode(code: string): Promise<boolean>`
- `createAuthCode(input: {code: string, clientId: string, googleAuthId: number, redirectUri: string, codeChallenge: string, codeChallengeMethod: string, scope?: string|null, expiresAt: string|Date}): Promise<string>`
- `createRefreshToken(input: {refreshToken: string, clientId: string, googleAuthId: number, scope?: string|null, expiresAt: string|Date}): Promise<object>`
- `deleteClient(clientId: string): Promise<boolean>`
- `getAuthCode(code: string): Promise<object|null>`
- `getClient(clientId: string): Promise<object|null>`
- `getRefreshToken(refreshToken: string): Promise<object|null>`
- `listClients(): Promise<Array<object>>`
- `registerClient(input: {clientId: string, clientSecret?: string|null, clientName: string, redirectUris: string|Array<string>, grantTypes?: string|Array<string>, responseTypes?: string|Array<string>, scope?: string|null}): Promise<object>`
- `revokeRefreshToken(refreshToken: string): Promise<boolean>`
- [modelconversation](API.modelconversation.md)
- `create(googleAuthId: number, input: {provider: string, model: string, started_by?: number|null}, tx?: object): Promise<{id: number, companyuser_id: number, provider: string, model: string, created_at: string, updated_at: string, state: string, started_by: number|null}>`
- `delete(googleAuthId: number, conversationId: number, tx?: object): Promise<void>`

94
scripts/test-mcp.js Normal file
View File

@@ -0,0 +1,94 @@
import assert from 'node:assert';
import LibDashStore from '../src/index.js';
import { BetterSqlite3Provider } from '../src/providers/sqlite3/index.js';
async function main() {
console.log('Testing MCP store methods on in-memory SQLite...');
const provider = new BetterSqlite3Provider({ path: ':memory:' });
await provider.initialize();
const store = await new LibDashStore(provider);
// 1. Create a google_auth user
const googleAuthId = await store.google_auth.add('mcp-tester@example.com');
assert(googleAuthId > 0, 'googleAuthId should be positive integer');
// 2. Register client
const client = await store.mcp.registerClient({
clientId: 'test-client-123',
clientSecret: 'secret-456',
clientName: 'Test Client',
redirectUris: ['http://localhost:3000/callback'],
grantTypes: ['authorization_code', 'refresh_token'],
responseTypes: ['code'],
scope: 'mcp:tools',
});
console.log('Registered client:', client);
assert.strictEqual(client.clientId, 'test-client-123');
assert.strictEqual(client.clientName, 'Test Client');
// 3. Get client
const fetched = await store.mcp.getClient('test-client-123');
assert(fetched, 'Should find client');
assert.strictEqual(fetched.clientId, 'test-client-123');
// 4. List clients
const clients = await store.mcp.listClients();
assert.strictEqual(clients.length, 1);
// 5. Create auth code
const code = 'auth-code-xyz';
await store.mcp.createAuthCode({
code,
clientId: 'test-client-123',
googleAuthId,
redirectUri: 'http://localhost:3000/callback',
codeChallenge: 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM',
codeChallengeMethod: 'S256',
scope: 'mcp:tools',
expiresAt: new Date(Date.now() + 300_000),
});
// 6. Get auth code
const codeRow = await store.mcp.getAuthCode(code);
assert(codeRow, 'Should find auth code');
assert.strictEqual(codeRow.clientId, 'test-client-123');
assert.strictEqual(codeRow.used, 0);
// 7. Consume auth code
await store.mcp.consumeAuthCode(code);
const consumedRow = await store.mcp.getAuthCode(code);
assert.strictEqual(consumedRow.used, 1);
// 8. Create refresh token
const refreshToken = 'refresh-token-abc';
await store.mcp.createRefreshToken({
refreshToken,
clientId: 'test-client-123',
googleAuthId,
scope: 'mcp:tools',
expiresAt: new Date(Date.now() + 86400_000),
});
// 9. Get refresh token
const tokenRow = await store.mcp.getRefreshToken(refreshToken);
assert(tokenRow, 'Should find refresh token');
assert.strictEqual(tokenRow.revoked, 0);
// 10. Revoke refresh token
await store.mcp.revokeRefreshToken(refreshToken);
const revokedRow = await store.mcp.getRefreshToken(refreshToken);
assert.strictEqual(revokedRow.revoked, 1);
// 11. Delete client
await store.mcp.deleteClient('test-client-123');
const deleted = await store.mcp.getClient('test-client-123');
assert.strictEqual(deleted, null);
await provider.close();
console.log('✓ All MCP store tests passed successfully!');
}
main().catch((err) => {
console.error('Test failed:', err);
process.exit(1);
});

View File

@@ -214,6 +214,18 @@ async function run() {
assert.equal(shipmentsAfterDel.length, 1);
console.log('PASS: Unshipped shipment 2 successfully deleted');
// 9. Verify peek next number works for entity and other types without error
const nextEntity = await store.entity.peekNextNumber(googleAuthId);
assert.ok(typeof nextEntity === 'string' && nextEntity.length > 0);
console.log('PASS: store.entity.peekNextNumber succeeded:', nextEntity);
const numList = await store.numbers.list(googleAuthId);
assert.ok(Array.isArray(numList) && numList.length > 0);
const shipmentSeq = numList.find((n) => n.type === 'shipment');
assert.ok(shipmentSeq, 'Expected shipment sequence in numbers.list');
assert.equal(shipmentSeq.prefix, '-L-');
console.log('PASS: store.numbers.list includes shipment with prefix -L-');
console.log('--- ALL TESTS PASSED SUCCESSFULLY! ---');
} finally {
try {

View File

@@ -230,6 +230,16 @@ import articlelistGetItems from './store/articlelist/getItemsByArticleSku.js';
import articlelistSetItems from './store/articlelist/setItemsByArticleSku.js';
import optionslistGetOptions from './store/optionslist/getOptionsByArticleSku.js';
import optionslistSetOptions from './store/optionslist/setOptionsByArticleSku.js';
import mcpRegisterClient from './store/mcp/registerClient.js';
import mcpGetClient from './store/mcp/getClient.js';
import mcpListClients from './store/mcp/listClients.js';
import mcpDeleteClient from './store/mcp/deleteClient.js';
import mcpCreateAuthCode from './store/mcp/createAuthCode.js';
import mcpGetAuthCode from './store/mcp/getAuthCode.js';
import mcpConsumeAuthCode from './store/mcp/consumeAuthCode.js';
import mcpCreateRefreshToken from './store/mcp/createRefreshToken.js';
import mcpGetRefreshToken from './store/mcp/getRefreshToken.js';
import mcpRevokeRefreshToken from './store/mcp/revokeRefreshToken.js';
export default class LibDashStore {
/**
@@ -619,6 +629,19 @@ export default class LibDashStore {
setOptionsByArticleSku: optionslistSetOptions(provider),
};
this.mcp = {
registerClient: mcpRegisterClient(provider),
getClient: mcpGetClient(provider),
listClients: mcpListClients(provider),
deleteClient: mcpDeleteClient(provider),
createAuthCode: mcpCreateAuthCode(provider),
getAuthCode: mcpGetAuthCode(provider),
consumeAuthCode: mcpConsumeAuthCode(provider),
createRefreshToken: mcpCreateRefreshToken(provider),
getRefreshToken: mcpGetRefreshToken(provider),
revokeRefreshToken: mcpRevokeRefreshToken(provider),
};
this.purge = storePurge(provider);
this.initialize = storeInitialize(provider);

View File

@@ -0,0 +1,90 @@
export const INSERT_CLIENT = `
INSERT INTO mcp_client (client_id, client_secret, client_name, redirect_uris, grant_types, response_types, scope)
OUTPUT INSERTED.id, INSERTED.client_id, INSERTED.client_secret, INSERTED.client_name, INSERTED.redirect_uris, INSERTED.grant_types, INSERTED.response_types, INSERTED.scope, INSERTED.created_at
VALUES (@clientId, @clientSecret, @clientName, @redirectUris, @grantTypes, @responseTypes, @scope)
`;
export const GET_CLIENT = `
SELECT
id,
client_id AS clientId,
client_secret AS clientSecret,
client_name AS clientName,
redirect_uris AS redirectUris,
grant_types AS grantTypes,
response_types AS responseTypes,
scope,
created_at AS createdAt
FROM mcp_client
WHERE client_id = @clientId
`;
export const LIST_CLIENTS = `
SELECT
id,
client_id AS clientId,
client_name AS clientName,
redirect_uris AS redirectUris,
grant_types AS grantTypes,
response_types AS responseTypes,
scope,
created_at AS createdAt
FROM mcp_client
ORDER BY created_at DESC
`;
export const DELETE_CLIENT = `
DELETE FROM mcp_client
WHERE client_id = @clientId
`;
export const INSERT_AUTH_CODE = `
INSERT INTO mcp_auth_code (code, client_id, google_auth_id, redirect_uri, code_challenge, code_challenge_method, scope, expires_at)
VALUES (@code, @clientId, @googleAuthId, @redirectUri, @codeChallenge, @codeChallengeMethod, @scope, @expiresAt)
`;
export const GET_AUTH_CODE = `
SELECT
code,
client_id AS clientId,
google_auth_id AS googleAuthId,
redirect_uri AS redirectUri,
code_challenge AS codeChallenge,
code_challenge_method AS codeChallengeMethod,
scope,
expires_at AS expiresAt,
used
FROM mcp_auth_code
WHERE code = @code
`;
export const CONSUME_AUTH_CODE = `
UPDATE mcp_auth_code
SET used = 1
WHERE code = @code
`;
export const INSERT_REFRESH_TOKEN = `
INSERT INTO mcp_token (refresh_token, client_id, google_auth_id, scope, expires_at)
OUTPUT INSERTED.id
VALUES (@refreshToken, @clientId, @googleAuthId, @scope, @expiresAt)
`;
export const GET_REFRESH_TOKEN = `
SELECT
id,
refresh_token AS refreshToken,
client_id AS clientId,
google_auth_id AS googleAuthId,
scope,
expires_at AS expiresAt,
revoked
FROM mcp_token
WHERE refresh_token = @refreshToken
`;
export const REVOKE_REFRESH_TOKEN = `
UPDATE mcp_token
SET revoked = 1
WHERE refresh_token = @refreshToken
`;

View File

@@ -1177,6 +1177,61 @@ BEGIN
END
`;
export const INIT_MCP_CLIENT = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'mcp_client')
BEGIN
CREATE TABLE mcp_client (
id INT IDENTITY(1,1) PRIMARY KEY,
client_id NVARCHAR(100) NOT NULL,
client_secret NVARCHAR(255) NULL,
client_name NVARCHAR(255) NOT NULL,
redirect_uris NVARCHAR(MAX) NOT NULL,
grant_types NVARCHAR(255) NULL,
response_types NVARCHAR(255) NULL,
scope NVARCHAR(255) NULL,
created_at DATETIME2 NOT NULL CONSTRAINT DF_mcp_client_created_at DEFAULT SYSUTCDATETIME(),
CONSTRAINT UQ_mcp_client_client_id UNIQUE (client_id)
);
END
`;
export const INIT_MCP_AUTH_CODE = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'mcp_auth_code')
BEGIN
CREATE TABLE mcp_auth_code (
code NVARCHAR(128) PRIMARY KEY,
client_id NVARCHAR(100) NOT NULL,
google_auth_id INT NOT NULL,
redirect_uri NVARCHAR(2048) NOT NULL,
code_challenge NVARCHAR(255) NOT NULL,
code_challenge_method NVARCHAR(32) NOT NULL,
scope NVARCHAR(255) NULL,
expires_at DATETIME2 NOT NULL,
used TINYINT NOT NULL CONSTRAINT DF_mcp_auth_code_used DEFAULT 0,
created_at DATETIME2 NOT NULL CONSTRAINT DF_mcp_auth_code_created_at DEFAULT SYSUTCDATETIME(),
CONSTRAINT FK_mcp_auth_code_google_auth FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE
);
END
`;
export const INIT_MCP_TOKEN = `
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'mcp_token')
BEGIN
CREATE TABLE mcp_token (
id INT IDENTITY(1,1) PRIMARY KEY,
refresh_token NVARCHAR(128) NOT NULL,
client_id NVARCHAR(100) NOT NULL,
google_auth_id INT NOT NULL,
scope NVARCHAR(255) NULL,
expires_at DATETIME2 NOT NULL,
revoked TINYINT NOT NULL CONSTRAINT DF_mcp_token_revoked DEFAULT 0,
created_at DATETIME2 NOT NULL CONSTRAINT DF_mcp_token_created_at DEFAULT SYSUTCDATETIME(),
CONSTRAINT UQ_mcp_token_refresh UNIQUE (refresh_token),
CONSTRAINT FK_mcp_token_google_auth FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE
);
END
`;
/** DDL scripts in dependency order for schema init. */
export const SCHEMA_SCRIPTS = [
INIT_META,
@@ -1244,4 +1299,7 @@ export const SCHEMA_SCRIPTS = [
INIT_PAYMENT,
INIT_SHIPPING,
INIT_PACKAGE,
INIT_MCP_CLIENT,
INIT_MCP_AUTH_CODE,
INIT_MCP_TOKEN,
];

View File

@@ -19,13 +19,23 @@ function normalizeParamValue(value) {
return value;
}
function bindParams(params) {
return Object.fromEntries(
function bindParams(params, sql = null) {
const bound = Object.fromEntries(
Object.entries(params).map(([key, value]) => [
key.replace(/^[@:$]/, ''),
normalizeParamValue(value),
]),
);
if (typeof sql === 'string') {
const matches = sql.matchAll(/[@:$]([a-zA-Z0-9_]+)/g);
for (const match of matches) {
const name = match[1];
if (!(name in bound)) {
bound[name] = null;
}
}
}
return bound;
}
function normalizeInsertId(value) {
@@ -227,7 +237,7 @@ export class BetterSqlite3Executor {
/** @returns {Promise<import('../provider.js').QueryResult>} */
async query(sql, params = {}) {
const statement = this.db.prepare(sql);
const bindings = bindParams(params);
const bindings = bindParams(params, sql);
if (statement.reader) {
const rows = statement.all(bindings);

View File

@@ -0,0 +1,16 @@
import * as source from '../../mssql/queries/mcp.js';
import { translateQueries } from './translate.js';
const queries = translateQueries(source, 'mcp');
export const {
INSERT_CLIENT,
GET_CLIENT,
LIST_CLIENTS,
DELETE_CLIENT,
INSERT_AUTH_CODE,
GET_AUTH_CODE,
CONSUME_AUTH_CODE,
INSERT_REFRESH_TOKEN,
GET_REFRESH_TOKEN,
REVOKE_REFRESH_TOKEN,
} = queries;

View File

@@ -845,6 +845,50 @@ CREATE TABLE IF NOT EXISTS package (
);
`;
export const INIT_MCP_CLIENT = `
CREATE TABLE IF NOT EXISTS mcp_client (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id TEXT NOT NULL UNIQUE,
client_secret TEXT,
client_name TEXT NOT NULL,
redirect_uris TEXT NOT NULL,
grant_types TEXT,
response_types TEXT,
scope TEXT,
created_at TEXT NOT NULL DEFAULT (${UTC_NOW})
);
`;
export const INIT_MCP_AUTH_CODE = `
CREATE TABLE IF NOT EXISTS mcp_auth_code (
code TEXT PRIMARY KEY,
client_id TEXT NOT NULL,
google_auth_id INTEGER NOT NULL,
redirect_uri TEXT NOT NULL,
code_challenge TEXT NOT NULL,
code_challenge_method TEXT NOT NULL,
scope TEXT,
expires_at TEXT NOT NULL,
used INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE
);
`;
export const INIT_MCP_TOKEN = `
CREATE TABLE IF NOT EXISTS mcp_token (
id INTEGER PRIMARY KEY AUTOINCREMENT,
refresh_token TEXT NOT NULL UNIQUE,
client_id TEXT NOT NULL,
google_auth_id INTEGER NOT NULL,
scope TEXT,
expires_at TEXT NOT NULL,
revoked INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE
);
`;
/** DDL scripts in dependency order for SQLite schema initialization. */
export const SCHEMA_SCRIPTS = [
INIT_META,
@@ -909,4 +953,7 @@ export const SCHEMA_SCRIPTS = [
INIT_PAYMENT,
INIT_SHIPPING,
INIT_PACKAGE,
INIT_MCP_CLIENT,
INIT_MCP_AUTH_CODE,
INIT_MCP_TOKEN,
];

View File

@@ -0,0 +1,16 @@
import { CONSUME_AUTH_CODE as MSSQL_CONSUME } from '../../providers/mssql/queries/mcp.js';
import { CONSUME_AUTH_CODE as SQLITE_CONSUME } from '../../providers/sqlite3/queries/mcp.js';
export default function consumeAuthCode(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_CONSUME : MSSQL_CONSUME;
/**
* Marks an authorization code as used.
*
* @param {string} code - The authorization code to mark used.
* @returns {Promise<boolean>} True once marked.
*/
return async (code) => {
await provider.query(sql, { code });
return true;
};
}

View File

@@ -0,0 +1,43 @@
import { INSERT_AUTH_CODE as MSSQL_INSERT } from '../../providers/mssql/queries/mcp.js';
import { INSERT_AUTH_CODE as SQLITE_INSERT } from '../../providers/sqlite3/queries/mcp.js';
export default function createAuthCode(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_INSERT : MSSQL_INSERT;
/**
* Stores an OAuth 2.1 authorization code with PKCE parameters.
*
* @param {object} input
* @param {string} input.code - The generated authorization code.
* @param {string} input.clientId - The client ID.
* @param {number} input.googleAuthId - The ID of the authenticated user.
* @param {string} input.redirectUri - The redirect URI used in the authorize request.
* @param {string} input.codeChallenge - The PKCE code challenge.
* @param {string} input.codeChallengeMethod - The PKCE method (e.g. S256).
* @param {string|null} [input.scope] - Granted scope.
* @param {string|Date} input.expiresAt - Expiration ISO string or Date.
* @returns {Promise<string>} The authorization code.
*/
return async ({
code,
clientId,
googleAuthId,
redirectUri,
codeChallenge,
codeChallengeMethod = 'S256',
scope = 'mcp:tools',
expiresAt,
}) => {
const exp = expiresAt instanceof Date ? expiresAt.toISOString() : expiresAt;
await provider.query(sql, {
code,
clientId,
googleAuthId,
redirectUri,
codeChallenge,
codeChallengeMethod,
scope,
expiresAt: exp,
});
return code;
};
}

View File

@@ -0,0 +1,34 @@
import { INSERT_REFRESH_TOKEN as MSSQL_INSERT } from '../../providers/mssql/queries/mcp.js';
import { INSERT_REFRESH_TOKEN as SQLITE_INSERT } from '../../providers/sqlite3/queries/mcp.js';
export default function createRefreshToken(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_INSERT : MSSQL_INSERT;
/**
* Stores an OAuth 2.1 refresh token.
*
* @param {object} input
* @param {string} input.refreshToken - The refresh token string.
* @param {string} input.clientId - The client ID.
* @param {number} input.googleAuthId - The user ID.
* @param {string|null} [input.scope] - Scopes granted.
* @param {string|Date} input.expiresAt - Expiration Date or ISO string.
* @returns {Promise<object>} Created token record or id.
*/
return async ({
refreshToken,
clientId,
googleAuthId,
scope = 'mcp:tools',
expiresAt,
}) => {
const exp = expiresAt instanceof Date ? expiresAt.toISOString() : expiresAt;
const result = await provider.query(sql, {
refreshToken,
clientId,
googleAuthId,
scope,
expiresAt: exp,
});
return result.rows[0]?.id ?? result.insertId;
};
}

View File

@@ -0,0 +1,16 @@
import { DELETE_CLIENT as MSSQL_DELETE } from '../../providers/mssql/queries/mcp.js';
import { DELETE_CLIENT as SQLITE_DELETE } from '../../providers/sqlite3/queries/mcp.js';
export default function deleteClient(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
/**
* Deletes a registered MCP client by client ID.
*
* @param {string} clientId - The client identifier to delete.
* @returns {Promise<boolean>} True if deleted.
*/
return async (clientId) => {
await provider.query(sql, { clientId });
return true;
};
}

View File

@@ -0,0 +1,16 @@
import { GET_AUTH_CODE as MSSQL_GET } from '../../providers/mssql/queries/mcp.js';
import { GET_AUTH_CODE as SQLITE_GET } from '../../providers/sqlite3/queries/mcp.js';
export default function getAuthCode(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
/**
* Retrieves an authorization code record.
*
* @param {string} code - The authorization code.
* @returns {Promise<object|null>} The auth code record or null.
*/
return async (code) => {
const result = await provider.query(sql, { code });
return result.rows[0] ?? null;
};
}

View File

@@ -0,0 +1,16 @@
import { GET_CLIENT as MSSQL_GET } from '../../providers/mssql/queries/mcp.js';
import { GET_CLIENT as SQLITE_GET } from '../../providers/sqlite3/queries/mcp.js';
export default function getClient(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
/**
* Retrieves an MCP client by client ID.
*
* @param {string} clientId - The client identifier.
* @returns {Promise<object|null>} The client record or null if not found.
*/
return async (clientId) => {
const result = await provider.query(sql, { clientId });
return result.rows[0] ?? null;
};
}

View File

@@ -0,0 +1,16 @@
import { GET_REFRESH_TOKEN as MSSQL_GET } from '../../providers/mssql/queries/mcp.js';
import { GET_REFRESH_TOKEN as SQLITE_GET } from '../../providers/sqlite3/queries/mcp.js';
export default function getRefreshToken(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
/**
* Retrieves an OAuth 2.1 refresh token.
*
* @param {string} refreshToken - The refresh token.
* @returns {Promise<object|null>} The refresh token record or null.
*/
return async (refreshToken) => {
const result = await provider.query(sql, { refreshToken });
return result.rows[0] ?? null;
};
}

View File

@@ -0,0 +1,15 @@
import { LIST_CLIENTS as MSSQL_LIST } from '../../providers/mssql/queries/mcp.js';
import { LIST_CLIENTS as SQLITE_LIST } from '../../providers/sqlite3/queries/mcp.js';
export default function listClients(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_LIST : MSSQL_LIST;
/**
* Lists all registered MCP clients.
*
* @returns {Promise<Array<object>>} List of client records.
*/
return async () => {
const result = await provider.query(sql);
return result.rows ?? [];
};
}

View File

@@ -0,0 +1,55 @@
import { INSERT_CLIENT as MSSQL_INSERT } from '../../providers/mssql/queries/mcp.js';
import { INSERT_CLIENT as SQLITE_INSERT } from '../../providers/sqlite3/queries/mcp.js';
export default function registerClient(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_INSERT : MSSQL_INSERT;
/**
* Registers a new OAuth MCP client.
*
* @param {object} input
* @param {string} input.clientId - Unique client identifier.
* @param {string|null} [input.clientSecret] - Optional client secret for confidential clients.
* @param {string} input.clientName - Human-readable client name.
* @param {string|Array<string>} input.redirectUris - JSON string or array of redirect URIs.
* @param {string|Array<string>} [input.grantTypes] - Supported grant types.
* @param {string|Array<string>} [input.responseTypes] - Supported response types.
* @param {string|null} [input.scope] - Allowed scope.
* @returns {Promise<object>} The created client record.
*/
return async ({
clientId,
clientSecret = null,
clientName,
redirectUris,
grantTypes = 'authorization_code,refresh_token',
responseTypes = 'code',
scope = 'mcp:tools',
}) => {
const urisStr = Array.isArray(redirectUris) ? JSON.stringify(redirectUris) : redirectUris;
const grantsStr = Array.isArray(grantTypes) ? grantTypes.join(',') : grantTypes;
const resTypesStr = Array.isArray(responseTypes) ? responseTypes.join(',') : responseTypes;
const result = await provider.query(sql, {
clientId,
clientSecret,
clientName,
redirectUris: urisStr,
grantTypes: grantsStr,
responseTypes: resTypesStr,
scope,
});
const row = result.rows[0];
return {
id: row?.id ?? result.insertId,
clientId: row?.clientId ?? row?.client_id ?? clientId,
clientSecret: row?.clientSecret ?? row?.client_secret ?? clientSecret,
clientName: row?.clientName ?? row?.client_name ?? clientName,
redirectUris: row?.redirectUris ?? row?.redirect_uris ?? urisStr,
grantTypes: row?.grantTypes ?? row?.grant_types ?? grantsStr,
responseTypes: row?.responseTypes ?? row?.response_types ?? resTypesStr,
scope: row?.scope ?? scope,
createdAt: row?.createdAt ?? row?.created_at,
};
};
}

View File

@@ -0,0 +1,16 @@
import { REVOKE_REFRESH_TOKEN as MSSQL_REVOKE } from '../../providers/mssql/queries/mcp.js';
import { REVOKE_REFRESH_TOKEN as SQLITE_REVOKE } from '../../providers/sqlite3/queries/mcp.js';
export default function revokeRefreshToken(provider) {
const sql = provider.dialect === 'sqlite3' ? SQLITE_REVOKE : MSSQL_REVOKE;
/**
* Revokes an OAuth 2.1 refresh token.
*
* @param {string} refreshToken - The refresh token to revoke.
* @returns {Promise<boolean>} True once revoked.
*/
return async (refreshToken) => {
await provider.query(sql, { refreshToken });
return true;
};
}

View File

@@ -34,7 +34,8 @@ export default function peekNumber(provider) {
*/
return async (companyId, type, tx) => {
const doPeek = async (runner) => {
await runner.query(ensureSql, { companyId, type });
const prefix = type === 'shipment' ? '-L-' : '';
await runner.query(ensureSql, { companyId, type, prefix });
const res = await runner.query(getSql, { companyId, type });
const row = res.rows[0];
if (!row) {