@absolutejs/mcp 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,13 @@ tasks: {
50
50
  }
51
51
  ```
52
52
 
53
+ For multi-instance production deployments, use
54
+ `createPostgresMcpTaskStore()` and `createPostgresMcpSessionStore()` after
55
+ applying `mcpPostgresSchemaSql()`. Task updates and cancellation protect
56
+ terminal states in the database, task reads enforce TTL, and session access
57
+ atomically extends only unexpired sessions. The adapters accept a structural
58
+ SQL client and do not require a particular PostgreSQL driver.
59
+
53
60
  Nothing here depends on a model. The tool shape is structurally compatible with
54
61
  [`@absolutejs/ai`](https://github.com/absolutejs/ai)'s `AIToolMap`, so an AI tool
55
62
  registry serves over MCP without conversion — but any typed tool registry works.
package/dist/index.js CHANGED
@@ -1129,15 +1129,98 @@ var mcpServer = (config) => {
1129
1129
  const app = config.serveRootMetadata ? base.get(ROOT_METADATA_PATH, () => metadataResponse(config)) : base;
1130
1130
  return app;
1131
1131
  };
1132
+ // src/postgres.ts
1133
+ var namespaceOf = (namespace) => {
1134
+ if (!/^[a-z_][a-z0-9_]*$/.test(namespace))
1135
+ throw new Error("MCP PostgreSQL namespace must be a simple identifier");
1136
+ return namespace;
1137
+ };
1138
+ var mcpPostgresSchemaSql = (namespace = "mcp") => {
1139
+ const ns = namespaceOf(namespace);
1140
+ return `CREATE SCHEMA IF NOT EXISTS ${ns};
1141
+ CREATE TABLE IF NOT EXISTS ${ns}.tasks (
1142
+ task_id text PRIMARY KEY,
1143
+ authorization_key text NOT NULL,
1144
+ status text NOT NULL CHECK (status IN ('working','input_required','completed','failed','cancelled')),
1145
+ created_at timestamptz NOT NULL,
1146
+ updated_at timestamptz NOT NULL,
1147
+ expires_at timestamptz,
1148
+ data jsonb NOT NULL
1149
+ );
1150
+ CREATE INDEX IF NOT EXISTS tasks_authorization_updated_idx ON ${ns}.tasks (authorization_key, updated_at DESC);
1151
+ CREATE INDEX IF NOT EXISTS tasks_expiry_idx ON ${ns}.tasks (expires_at) WHERE expires_at IS NOT NULL;
1152
+ CREATE TABLE IF NOT EXISTS ${ns}.sessions (
1153
+ session_id text PRIMARY KEY,
1154
+ can_elicit boolean NOT NULL,
1155
+ created_at timestamptz NOT NULL DEFAULT now(),
1156
+ last_seen_at timestamptz NOT NULL DEFAULT now(),
1157
+ expires_at timestamptz NOT NULL
1158
+ );
1159
+ CREATE INDEX IF NOT EXISTS sessions_expiry_idx ON ${ns}.sessions (expires_at);`;
1160
+ };
1161
+ var createPostgresMcpTaskStore = ({
1162
+ client,
1163
+ namespace = "mcp",
1164
+ now = () => new Date
1165
+ }) => {
1166
+ const ns = namespaceOf(namespace);
1167
+ return {
1168
+ cancel: async (taskId) => {
1169
+ const updatedAt = now().toISOString();
1170
+ await client.query(`UPDATE ${ns}.tasks SET status = 'cancelled', updated_at = $2::timestamptz, data = data || $3::jsonb WHERE task_id = $1 AND status NOT IN ('cancelled','completed','failed')`, [taskId, updatedAt, JSON.stringify({ lastUpdatedAt: updatedAt, status: "cancelled" })]);
1171
+ },
1172
+ get: async (taskId) => (await client.query(`SELECT data FROM ${ns}.tasks WHERE task_id = $1 AND (expires_at IS NULL OR expires_at > $2::timestamptz)`, [taskId, now().toISOString()])).rows[0]?.data ?? null,
1173
+ save: async (task) => {
1174
+ const expiresAt = task.ttlMs === null ? null : new Date(new Date(task.createdAt).getTime() + task.ttlMs).toISOString();
1175
+ await client.query(`INSERT INTO ${ns}.tasks (task_id, authorization_key, status, created_at, updated_at, expires_at, data) VALUES ($1, $2, $3, $4::timestamptz, $5::timestamptz, $6::timestamptz, $7::jsonb) ON CONFLICT (task_id) DO NOTHING`, [task.taskId, task.authorizationKey, task.status, task.createdAt, task.lastUpdatedAt, expiresAt, JSON.stringify(task)]);
1176
+ },
1177
+ update: async (taskId, update) => {
1178
+ const updatedAt = now().toISOString();
1179
+ const data = { ...update, lastUpdatedAt: updatedAt };
1180
+ const result = await client.query(`UPDATE ${ns}.tasks SET status = COALESCE($2::text, status), updated_at = $3::timestamptz, data = data || $4::jsonb WHERE task_id = $1 AND status NOT IN ('cancelled','completed','failed') RETURNING data`, [taskId, update.status ?? null, updatedAt, JSON.stringify(data)]);
1181
+ if (result.rows[0] !== undefined)
1182
+ return result.rows[0].data;
1183
+ return (await client.query(`SELECT data FROM ${ns}.tasks WHERE task_id = $1`, [taskId])).rows[0]?.data ?? null;
1184
+ }
1185
+ };
1186
+ };
1187
+ var createPostgresMcpSessionStore = ({
1188
+ client,
1189
+ namespace = "mcp",
1190
+ now = () => new Date,
1191
+ ttlMs = 3600000
1192
+ }) => {
1193
+ const ns = namespaceOf(namespace);
1194
+ return {
1195
+ create: async ({ canElicit }) => {
1196
+ const id = crypto.randomUUID();
1197
+ const current = now();
1198
+ await client.query(`INSERT INTO ${ns}.sessions (session_id, can_elicit, created_at, last_seen_at, expires_at) VALUES ($1, $2, $3::timestamptz, $3::timestamptz, $4::timestamptz)`, [id, canElicit, current.toISOString(), new Date(current.getTime() + ttlMs).toISOString()]);
1199
+ return id;
1200
+ },
1201
+ drop: async (id) => {
1202
+ await client.query(`DELETE FROM ${ns}.sessions WHERE session_id = $1`, [id]);
1203
+ },
1204
+ get: async (id) => {
1205
+ const current = now();
1206
+ const result = await client.query(`UPDATE ${ns}.sessions SET last_seen_at = $2::timestamptz, expires_at = $3::timestamptz WHERE session_id = $1 AND expires_at > $2::timestamptz RETURNING can_elicit`, [id, current.toISOString(), new Date(current.getTime() + ttlMs).toISOString()]);
1207
+ const row = result.rows[0];
1208
+ return row === undefined ? null : { canElicit: row.can_elicit };
1209
+ }
1210
+ };
1211
+ };
1132
1212
  export {
1133
1213
  verifyBearer,
1134
1214
  publicMcpTask,
1135
1215
  protectedResourceMetadata,
1136
1216
  metadataPathFor,
1137
1217
  mcpServer,
1218
+ mcpPostgresSchemaSql,
1138
1219
  feedbackTools,
1139
1220
  dispatchMcp,
1140
1221
  createSessionRegistry,
1222
+ createPostgresMcpTaskStore,
1223
+ createPostgresMcpSessionStore,
1141
1224
  createMemoryMcpTaskStore,
1142
1225
  createMcpHandler,
1143
1226
  createMcpClient,
@@ -39,4 +39,5 @@ export { metadataPathFor, protectedResourceMetadata, type ProtectedResourceMetad
39
39
  export { mcpServer } from "./server";
40
40
  export { createSessionRegistry, type SessionRegistry } from "./sessions";
41
41
  export { createMemoryMcpTaskStore, publicMcpTask } from "./tasks";
42
+ export { createPostgresMcpSessionStore, createPostgresMcpTaskStore, mcpPostgresSchemaSql, type McpSqlClient, type McpSqlResult, } from "./postgres";
42
43
  export type { McpAgencyOptions, McpAudioContent, McpElicitAnswer, McpElicitationRequest, McpElicitBus, McpElicitResult, McpSessionStore, McpAuthResult, McpCallGate, McpCallMeta, McpContent, McpImageContent, McpPromptArgument, McpPromptDefinition, McpPrompts, McpResource, McpResourceLink, McpResources, McpServerConfig, McpServerInfo, McpTextContent, McpTask, McpTaskStatus, McpTaskStore, McpTasksOptions, McpTool, McpToolAnnotations, McpToolCallContext, McpToolContext, McpToolRegistry, McpToolResult, McpToolReturn, } from "./types";
@@ -0,0 +1,20 @@
1
+ import type { McpSessionStore, McpTaskStore } from "./types";
2
+ export type McpSqlResult<Row> = {
3
+ rowCount: number;
4
+ rows: ReadonlyArray<Row>;
5
+ };
6
+ export type McpSqlClient = {
7
+ query: <Row = Record<string, unknown>>(sql: string, parameters?: ReadonlyArray<unknown>) => Promise<McpSqlResult<Row>>;
8
+ };
9
+ export declare const mcpPostgresSchemaSql: (namespace?: string) => string;
10
+ export declare const createPostgresMcpTaskStore: ({ client, namespace, now, }: {
11
+ client: McpSqlClient;
12
+ namespace?: string;
13
+ now?: () => Date;
14
+ }) => McpTaskStore;
15
+ export declare const createPostgresMcpSessionStore: ({ client, namespace, now, ttlMs, }: {
16
+ client: McpSqlClient;
17
+ namespace?: string;
18
+ now?: () => Date;
19
+ ttlMs?: number;
20
+ }) => McpSessionStore;
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "elysia": ">=1.1.0"
12
12
  },
13
13
  "dependencies": {
14
- "@absolutejs/agency": "^0.1.0",
14
+ "@absolutejs/agency": "^0.3.0",
15
15
  "@absolutejs/manifest": "^0.2.0",
16
16
  "@sinclair/typebox": "^0.34.0"
17
17
  },
@@ -58,5 +58,5 @@
58
58
  "typecheck": "tsc --noEmit --project tsconfig.json"
59
59
  },
60
60
  "types": "./dist/src/index.d.ts",
61
- "version": "0.5.0"
61
+ "version": "0.6.0"
62
62
  }