@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 +7 -0
- package/dist/index.js +83 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/postgres.d.ts +20 -0
- package/package.json +2 -2
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,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
61
|
+
"version": "0.6.0"
|
|
62
62
|
}
|