@memlink/cli 1.0.2 → 1.0.4
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/dist/cli/index.js +240 -38
- package/dist/server/index.js +174 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -45902,24 +45902,46 @@ Memlink is a self-hosted MCP server that gives you persistent, organized memory
|
|
|
45902
45902
|
## Connection
|
|
45903
45903
|
|
|
45904
45904
|
The MCP server runs at:
|
|
45905
|
+
|
|
45906
|
+
**Streamable HTTP** (modern clients):
|
|
45905
45907
|
\`\`\`
|
|
45906
45908
|
http://localhost:4444/mcp?id=YOUR_MEMORY_ID
|
|
45907
45909
|
\`\`\`
|
|
45908
45910
|
|
|
45911
|
+
**SSE** (legacy clients like OpenCode):
|
|
45912
|
+
\`\`\`
|
|
45913
|
+
http://localhost:4444/sse?id=YOUR_MEMORY_ID
|
|
45914
|
+
\`\`\`
|
|
45915
|
+
|
|
45909
45916
|
The memory ID is a 12-character alphanumeric string assigned when you create a memory via \`memlink init <name>\`.
|
|
45910
45917
|
|
|
45911
|
-
###
|
|
45918
|
+
### Streamable HTTP config
|
|
45912
45919
|
|
|
45913
45920
|
\`\`\`json
|
|
45914
45921
|
{
|
|
45915
45922
|
"mcpServers": {
|
|
45916
45923
|
"memlink": {
|
|
45924
|
+
"type": "http",
|
|
45917
45925
|
"url": "http://localhost:4444/mcp?id=YOUR_MEMORY_ID"
|
|
45918
45926
|
}
|
|
45919
45927
|
}
|
|
45920
45928
|
}
|
|
45921
45929
|
\`\`\`
|
|
45922
45930
|
|
|
45931
|
+
### SSE config (OpenCode)
|
|
45932
|
+
|
|
45933
|
+
\`\`\`json
|
|
45934
|
+
{
|
|
45935
|
+
"mcpServers": {
|
|
45936
|
+
"memlink": {
|
|
45937
|
+
"type": "remote",
|
|
45938
|
+
"enabled": true,
|
|
45939
|
+
"url": "http://localhost:4444/sse?id=YOUR_MEMORY_ID"
|
|
45940
|
+
}
|
|
45941
|
+
}
|
|
45942
|
+
}
|
|
45943
|
+
\`\`\`
|
|
45944
|
+
|
|
45923
45945
|
## Session Workflow
|
|
45924
45946
|
|
|
45925
45947
|
1. **Start of session** — Always call \`memory_read\` to load existing context
|
|
@@ -46002,7 +46024,7 @@ function nanoid(size = 21) {
|
|
|
46002
46024
|
}
|
|
46003
46025
|
|
|
46004
46026
|
// src/core/types.ts
|
|
46005
|
-
var MEMLINK_VERSION = "1.0.
|
|
46027
|
+
var MEMLINK_VERSION = "1.0.4";
|
|
46006
46028
|
var DEFAULT_PORT = 4444;
|
|
46007
46029
|
var DEFAULT_HOST = "localhost";
|
|
46008
46030
|
var CONFIG_DIR = ".memlink";
|
|
@@ -64883,6 +64905,134 @@ class StreamableHTTPServerTransport {
|
|
|
64883
64905
|
}
|
|
64884
64906
|
}
|
|
64885
64907
|
|
|
64908
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/sse.js
|
|
64909
|
+
import { randomUUID } from "node:crypto";
|
|
64910
|
+
import { TLSSocket } from "node:tls";
|
|
64911
|
+
var import_raw_body = __toESM(require_raw_body(), 1);
|
|
64912
|
+
var import_content_type = __toESM(require_content_type(), 1);
|
|
64913
|
+
import { URL as URL2 } from "node:url";
|
|
64914
|
+
var MAXIMUM_MESSAGE_SIZE = "4mb";
|
|
64915
|
+
|
|
64916
|
+
class SSEServerTransport {
|
|
64917
|
+
constructor(_endpoint, res, options) {
|
|
64918
|
+
this._endpoint = _endpoint;
|
|
64919
|
+
this.res = res;
|
|
64920
|
+
this._sessionId = randomUUID();
|
|
64921
|
+
this._options = options || { enableDnsRebindingProtection: false };
|
|
64922
|
+
}
|
|
64923
|
+
validateRequestHeaders(req) {
|
|
64924
|
+
if (!this._options.enableDnsRebindingProtection) {
|
|
64925
|
+
return;
|
|
64926
|
+
}
|
|
64927
|
+
if (this._options.allowedHosts && this._options.allowedHosts.length > 0) {
|
|
64928
|
+
const hostHeader = req.headers.host;
|
|
64929
|
+
if (!hostHeader || !this._options.allowedHosts.includes(hostHeader)) {
|
|
64930
|
+
return `Invalid Host header: ${hostHeader}`;
|
|
64931
|
+
}
|
|
64932
|
+
}
|
|
64933
|
+
if (this._options.allowedOrigins && this._options.allowedOrigins.length > 0) {
|
|
64934
|
+
const originHeader = req.headers.origin;
|
|
64935
|
+
if (originHeader && !this._options.allowedOrigins.includes(originHeader)) {
|
|
64936
|
+
return `Invalid Origin header: ${originHeader}`;
|
|
64937
|
+
}
|
|
64938
|
+
}
|
|
64939
|
+
return;
|
|
64940
|
+
}
|
|
64941
|
+
async start() {
|
|
64942
|
+
if (this._sseResponse) {
|
|
64943
|
+
throw new Error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
64944
|
+
}
|
|
64945
|
+
this.res.writeHead(200, {
|
|
64946
|
+
"Content-Type": "text/event-stream",
|
|
64947
|
+
"Cache-Control": "no-cache, no-transform",
|
|
64948
|
+
Connection: "keep-alive"
|
|
64949
|
+
});
|
|
64950
|
+
const dummyBase = "http://localhost";
|
|
64951
|
+
const endpointUrl = new URL2(this._endpoint, dummyBase);
|
|
64952
|
+
endpointUrl.searchParams.set("sessionId", this._sessionId);
|
|
64953
|
+
const relativeUrlWithSession = endpointUrl.pathname + endpointUrl.search + endpointUrl.hash;
|
|
64954
|
+
this.res.write(`event: endpoint
|
|
64955
|
+
data: ${relativeUrlWithSession}
|
|
64956
|
+
|
|
64957
|
+
`);
|
|
64958
|
+
this._sseResponse = this.res;
|
|
64959
|
+
this.res.on("close", () => {
|
|
64960
|
+
this._sseResponse = undefined;
|
|
64961
|
+
this.onclose?.();
|
|
64962
|
+
});
|
|
64963
|
+
}
|
|
64964
|
+
async handlePostMessage(req, res, parsedBody) {
|
|
64965
|
+
if (!this._sseResponse) {
|
|
64966
|
+
const message = "SSE connection not established";
|
|
64967
|
+
res.writeHead(500).end(message);
|
|
64968
|
+
throw new Error(message);
|
|
64969
|
+
}
|
|
64970
|
+
const validationError = this.validateRequestHeaders(req);
|
|
64971
|
+
if (validationError) {
|
|
64972
|
+
res.writeHead(403).end(validationError);
|
|
64973
|
+
this.onerror?.(new Error(validationError));
|
|
64974
|
+
return;
|
|
64975
|
+
}
|
|
64976
|
+
const authInfo = req.auth;
|
|
64977
|
+
const host = req.headers.host;
|
|
64978
|
+
const protocol = req.socket instanceof TLSSocket ? "https" : "http";
|
|
64979
|
+
const fullUrl = host && req.url ? new URL2(req.url, `${protocol}://${host}`) : undefined;
|
|
64980
|
+
const requestInfo = {
|
|
64981
|
+
headers: req.headers,
|
|
64982
|
+
url: fullUrl
|
|
64983
|
+
};
|
|
64984
|
+
let body;
|
|
64985
|
+
try {
|
|
64986
|
+
const ct = import_content_type.default.parse(req.headers["content-type"] ?? "");
|
|
64987
|
+
if (ct.type !== "application/json") {
|
|
64988
|
+
throw new Error(`Unsupported content-type: ${ct.type}`);
|
|
64989
|
+
}
|
|
64990
|
+
body = parsedBody ?? await import_raw_body.default(req, {
|
|
64991
|
+
limit: MAXIMUM_MESSAGE_SIZE,
|
|
64992
|
+
encoding: ct.parameters.charset ?? "utf-8"
|
|
64993
|
+
});
|
|
64994
|
+
} catch (error2) {
|
|
64995
|
+
res.writeHead(400).end(String(error2));
|
|
64996
|
+
this.onerror?.(error2);
|
|
64997
|
+
return;
|
|
64998
|
+
}
|
|
64999
|
+
try {
|
|
65000
|
+
await this.handleMessage(typeof body === "string" ? JSON.parse(body) : body, { requestInfo, authInfo });
|
|
65001
|
+
} catch {
|
|
65002
|
+
res.writeHead(400).end(`Invalid message: ${body}`);
|
|
65003
|
+
return;
|
|
65004
|
+
}
|
|
65005
|
+
res.writeHead(202).end("Accepted");
|
|
65006
|
+
}
|
|
65007
|
+
async handleMessage(message, extra) {
|
|
65008
|
+
let parsedMessage;
|
|
65009
|
+
try {
|
|
65010
|
+
parsedMessage = JSONRPCMessageSchema.parse(message);
|
|
65011
|
+
} catch (error2) {
|
|
65012
|
+
this.onerror?.(error2);
|
|
65013
|
+
throw error2;
|
|
65014
|
+
}
|
|
65015
|
+
this.onmessage?.(parsedMessage, extra);
|
|
65016
|
+
}
|
|
65017
|
+
async close() {
|
|
65018
|
+
this._sseResponse?.end();
|
|
65019
|
+
this._sseResponse = undefined;
|
|
65020
|
+
this.onclose?.();
|
|
65021
|
+
}
|
|
65022
|
+
async send(message) {
|
|
65023
|
+
if (!this._sseResponse) {
|
|
65024
|
+
throw new Error("Not connected");
|
|
65025
|
+
}
|
|
65026
|
+
this._sseResponse.write(`event: message
|
|
65027
|
+
data: ${JSON.stringify(message)}
|
|
65028
|
+
|
|
65029
|
+
`);
|
|
65030
|
+
}
|
|
65031
|
+
get sessionId() {
|
|
65032
|
+
return this._sessionId;
|
|
65033
|
+
}
|
|
65034
|
+
}
|
|
65035
|
+
|
|
64886
65036
|
// node_modules/zod/v4/classic/external.js
|
|
64887
65037
|
var exports_external = {};
|
|
64888
65038
|
__export(exports_external, {
|
|
@@ -79587,6 +79737,7 @@ Deleted: ${result.deleted}`
|
|
|
79587
79737
|
});
|
|
79588
79738
|
return server;
|
|
79589
79739
|
}
|
|
79740
|
+
var sseSessions = new Map;
|
|
79590
79741
|
function createApp() {
|
|
79591
79742
|
const app = import_express.default();
|
|
79592
79743
|
app.use(import_express.default.json({ limit: "10mb" }));
|
|
@@ -79624,6 +79775,50 @@ function createApp() {
|
|
|
79624
79775
|
content: AGENT_SYSTEM_PROMPT(name)
|
|
79625
79776
|
});
|
|
79626
79777
|
});
|
|
79778
|
+
app.get("/sse", async (req, res) => {
|
|
79779
|
+
const memId = req.query.id || req.query.mem_id;
|
|
79780
|
+
if (!memId) {
|
|
79781
|
+
res.status(401).json({ error: "Missing memory ID. Use ?id=<memory_id>" });
|
|
79782
|
+
return;
|
|
79783
|
+
}
|
|
79784
|
+
const memoryInfo = getMemoryById(memId);
|
|
79785
|
+
if (!memoryInfo) {
|
|
79786
|
+
res.status(403).json({ error: "Invalid memory ID." });
|
|
79787
|
+
return;
|
|
79788
|
+
}
|
|
79789
|
+
const memoryId = memoryInfo.memoryId;
|
|
79790
|
+
const memoryName = memoryInfo.memoryName;
|
|
79791
|
+
updateUniversalMemoryLastSeen(memoryId);
|
|
79792
|
+
const mcpServer = buildMcpServer(memoryId, memoryName);
|
|
79793
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
79794
|
+
sseSessions.set(transport.sessionId, { transport, memoryId, mcpServer });
|
|
79795
|
+
res.on("close", () => {
|
|
79796
|
+
sseSessions.delete(transport.sessionId);
|
|
79797
|
+
mcpServer.close();
|
|
79798
|
+
});
|
|
79799
|
+
try {
|
|
79800
|
+
await mcpServer.connect(transport);
|
|
79801
|
+
await transport.start();
|
|
79802
|
+
} catch (err2) {
|
|
79803
|
+
sseSessions.delete(transport.sessionId);
|
|
79804
|
+
mcpServer.close();
|
|
79805
|
+
}
|
|
79806
|
+
});
|
|
79807
|
+
app.post("/messages", async (req, res) => {
|
|
79808
|
+
const sessionId = req.query.sessionId;
|
|
79809
|
+
if (!sessionId || !sseSessions.has(sessionId)) {
|
|
79810
|
+
res.status(404).json({ error: "SSE session not found" });
|
|
79811
|
+
return;
|
|
79812
|
+
}
|
|
79813
|
+
const { transport } = sseSessions.get(sessionId);
|
|
79814
|
+
try {
|
|
79815
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
79816
|
+
} catch (err2) {
|
|
79817
|
+
if (!res.headersSent) {
|
|
79818
|
+
res.status(500).json({ error: String(err2) });
|
|
79819
|
+
}
|
|
79820
|
+
}
|
|
79821
|
+
});
|
|
79627
79822
|
app.all("/mcp", async (req, res) => {
|
|
79628
79823
|
const memId = req.query.id || req.query.mem_id;
|
|
79629
79824
|
if (!memId) {
|
|
@@ -79776,7 +79971,7 @@ function copyToClipboard(text) {
|
|
|
79776
79971
|
}
|
|
79777
79972
|
function openUrl(url2) {
|
|
79778
79973
|
const platform = process.platform;
|
|
79779
|
-
const cmd = platform === "darwin" ? "open" : platform === "win32" ?
|
|
79974
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? 'start ""' : "xdg-open";
|
|
79780
79975
|
try {
|
|
79781
79976
|
execSync(`${cmd} "${url2}"`, { stdio: "ignore" });
|
|
79782
79977
|
return true;
|
|
@@ -79787,8 +79982,11 @@ function openUrl(url2) {
|
|
|
79787
79982
|
function memoryTableRow(name, id, sizeKb) {
|
|
79788
79983
|
return [colors.white(name), colors.dim(id), colors.dim(`${sizeKb} KB`)];
|
|
79789
79984
|
}
|
|
79790
|
-
function
|
|
79791
|
-
return `http://${host}:${port}/mcp`;
|
|
79985
|
+
function mcpUrl(host, port, memId) {
|
|
79986
|
+
return `http://${host}:${port}/mcp?id=${memId}`;
|
|
79987
|
+
}
|
|
79988
|
+
function sseUrl(host, port, memId) {
|
|
79989
|
+
return `http://${host}:${port}/sse?id=${memId}`;
|
|
79792
79990
|
}
|
|
79793
79991
|
var program2 = new Command;
|
|
79794
79992
|
program2.name("memlink").description("Memlink — Universal memory for AI agents").version(MEMLINK_VERSION, "--version").addHelpText("before", logo());
|
|
@@ -79797,8 +79995,8 @@ program2.action(() => {
|
|
|
79797
79995
|
const config3 = loadConfig();
|
|
79798
79996
|
const host = envHost() || config3.serverHost || DEFAULT_HOST;
|
|
79799
79997
|
const port = envPort() || config3.serverPort || DEFAULT_PORT;
|
|
79800
|
-
const
|
|
79801
|
-
console.log(info("Server",
|
|
79998
|
+
const base = `http://${host}:${port}`;
|
|
79999
|
+
console.log(info("Server", base));
|
|
79802
80000
|
let totalSize = 0;
|
|
79803
80001
|
let totalEntries = 0;
|
|
79804
80002
|
if (config3.universalMemories.length > 0) {
|
|
@@ -79839,7 +80037,8 @@ program2.command("serve").description("Start the Memlink MCP server").option("--
|
|
|
79839
80037
|
const host = opts.host;
|
|
79840
80038
|
if (config3.universalMemories.length > 0) {
|
|
79841
80039
|
for (const mem of config3.universalMemories) {
|
|
79842
|
-
console.log(info("
|
|
80040
|
+
console.log(info("MCP", mcpUrl(host, port, mem.memoryId)));
|
|
80041
|
+
console.log(info("SSE", sseUrl(host, port, mem.memoryId)));
|
|
79843
80042
|
}
|
|
79844
80043
|
} else {
|
|
79845
80044
|
console.log(info("no memories", "Create one with: Memlink init <name>"));
|
|
@@ -79856,12 +80055,14 @@ function initAction(name, opts) {
|
|
|
79856
80055
|
const config3 = loadConfig();
|
|
79857
80056
|
const host = envHost() || config3.serverHost || DEFAULT_HOST;
|
|
79858
80057
|
const port = envPort() || config3.serverPort || DEFAULT_PORT;
|
|
79859
|
-
const
|
|
80058
|
+
const mcp = mcpUrl(host, port, memory.memoryId);
|
|
80059
|
+
const sse = sseUrl(host, port, memory.memoryId);
|
|
79860
80060
|
console.log(info("Name", memory.memoryName));
|
|
79861
80061
|
console.log(info("ID", memory.memoryId));
|
|
79862
|
-
console.log(info("
|
|
80062
|
+
console.log(info("MCP", mcp));
|
|
80063
|
+
console.log(info("SSE", sse));
|
|
79863
80064
|
console.log();
|
|
79864
|
-
const copied = copyToClipboard(
|
|
80065
|
+
const copied = copyToClipboard(mcp);
|
|
79865
80066
|
if (copied) {
|
|
79866
80067
|
console.log(ok("URL copied to clipboard"));
|
|
79867
80068
|
}
|
|
@@ -79912,7 +80113,8 @@ program2.command("connect <memoryId>").description("Get MCP connection details f
|
|
|
79912
80113
|
}
|
|
79913
80114
|
const host = envHost() || config3.serverHost || DEFAULT_HOST;
|
|
79914
80115
|
const port = envPort() || config3.serverPort || DEFAULT_PORT;
|
|
79915
|
-
const
|
|
80116
|
+
const mcp = mcpUrl(host, port, memory.memoryId);
|
|
80117
|
+
const sse = sseUrl(host, port, memory.memoryId);
|
|
79916
80118
|
const small = logoSmall();
|
|
79917
80119
|
if (small)
|
|
79918
80120
|
console.log(`
|
|
@@ -79920,24 +80122,41 @@ program2.command("connect <memoryId>").description("Get MCP connection details f
|
|
|
79920
80122
|
`);
|
|
79921
80123
|
console.log(info("Name", memory.memoryName));
|
|
79922
80124
|
console.log(info("ID", memory.memoryId));
|
|
79923
|
-
console.log(info("
|
|
80125
|
+
console.log(info("MCP", mcp));
|
|
80126
|
+
console.log(info("SSE", sse));
|
|
79924
80127
|
console.log();
|
|
79925
|
-
const
|
|
80128
|
+
const httpConfig = {
|
|
80129
|
+
mcpServers: {
|
|
80130
|
+
memlink: {
|
|
80131
|
+
type: "http",
|
|
80132
|
+
url: mcp
|
|
80133
|
+
}
|
|
80134
|
+
}
|
|
80135
|
+
};
|
|
80136
|
+
const sseConfig = {
|
|
79926
80137
|
mcpServers: {
|
|
79927
80138
|
memlink: {
|
|
79928
|
-
|
|
80139
|
+
type: "remote",
|
|
80140
|
+
enabled: true,
|
|
80141
|
+
url: sse
|
|
79929
80142
|
}
|
|
79930
80143
|
}
|
|
79931
80144
|
};
|
|
79932
|
-
console.log(subheading("
|
|
79933
|
-
const jsonStr = JSON.stringify(mcpConfig, null, 2);
|
|
80145
|
+
console.log(subheading("Streamable HTTP (modern clients):"));
|
|
79934
80146
|
console.log(colors.muted(" ```json"));
|
|
79935
|
-
console.log(colors.muted(
|
|
80147
|
+
console.log(colors.muted(JSON.stringify(httpConfig, null, 2).split(`
|
|
79936
80148
|
`).map((l) => " " + l).join(`
|
|
79937
80149
|
`)));
|
|
79938
80150
|
console.log(colors.muted(" ```"));
|
|
79939
80151
|
console.log();
|
|
79940
|
-
|
|
80152
|
+
console.log(subheading("SSE (legacy clients like OpenCode):"));
|
|
80153
|
+
console.log(colors.muted(" ```json"));
|
|
80154
|
+
console.log(colors.muted(JSON.stringify(sseConfig, null, 2).split(`
|
|
80155
|
+
`).map((l) => " " + l).join(`
|
|
80156
|
+
`)));
|
|
80157
|
+
console.log(colors.muted(" ```"));
|
|
80158
|
+
console.log();
|
|
80159
|
+
const copied = copyToClipboard(mcp);
|
|
79941
80160
|
if (copied) {
|
|
79942
80161
|
console.log(ok("URL copied to clipboard"));
|
|
79943
80162
|
}
|
|
@@ -80046,25 +80265,8 @@ program2.command("skill").description("Install Memlink agent skill for this work
|
|
|
80046
80265
|
console.log();
|
|
80047
80266
|
}
|
|
80048
80267
|
});
|
|
80049
|
-
program2.command("bug").description("Open GitHub to report a bug or send feedback").action(() => {
|
|
80050
|
-
const
|
|
80051
|
-
const template = encodeURIComponent([
|
|
80052
|
-
"**Type:** (Bug Report / Feature Request / Feedback)",
|
|
80053
|
-
"",
|
|
80054
|
-
"**Description:**",
|
|
80055
|
-
"(Write here)",
|
|
80056
|
-
"",
|
|
80057
|
-
"**Steps to reproduce (if bug):**",
|
|
80058
|
-
"1.",
|
|
80059
|
-
"2.",
|
|
80060
|
-
"",
|
|
80061
|
-
"**Expected behavior:**",
|
|
80062
|
-
"",
|
|
80063
|
-
"**Actual behavior:**",
|
|
80064
|
-
""
|
|
80065
|
-
].join(`
|
|
80066
|
-
`));
|
|
80067
|
-
const url2 = `${baseUrl}?body=${template}`;
|
|
80268
|
+
program2.command("bug").description("Open GitHub to report a bug, request a feature, or send feedback").action(() => {
|
|
80269
|
+
const url2 = "https://github.com/rblez/memlink/issues/new/choose";
|
|
80068
80270
|
const small = logoSmall();
|
|
80069
80271
|
if (small)
|
|
80070
80272
|
console.log(`
|
package/dist/server/index.js
CHANGED
|
@@ -56081,6 +56081,134 @@ class StreamableHTTPServerTransport {
|
|
|
56081
56081
|
}
|
|
56082
56082
|
}
|
|
56083
56083
|
|
|
56084
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/sse.js
|
|
56085
|
+
import { randomUUID } from "node:crypto";
|
|
56086
|
+
import { TLSSocket } from "node:tls";
|
|
56087
|
+
var import_raw_body = __toESM(require_raw_body(), 1);
|
|
56088
|
+
var import_content_type = __toESM(require_content_type(), 1);
|
|
56089
|
+
import { URL as URL2 } from "node:url";
|
|
56090
|
+
var MAXIMUM_MESSAGE_SIZE = "4mb";
|
|
56091
|
+
|
|
56092
|
+
class SSEServerTransport {
|
|
56093
|
+
constructor(_endpoint, res, options) {
|
|
56094
|
+
this._endpoint = _endpoint;
|
|
56095
|
+
this.res = res;
|
|
56096
|
+
this._sessionId = randomUUID();
|
|
56097
|
+
this._options = options || { enableDnsRebindingProtection: false };
|
|
56098
|
+
}
|
|
56099
|
+
validateRequestHeaders(req) {
|
|
56100
|
+
if (!this._options.enableDnsRebindingProtection) {
|
|
56101
|
+
return;
|
|
56102
|
+
}
|
|
56103
|
+
if (this._options.allowedHosts && this._options.allowedHosts.length > 0) {
|
|
56104
|
+
const hostHeader = req.headers.host;
|
|
56105
|
+
if (!hostHeader || !this._options.allowedHosts.includes(hostHeader)) {
|
|
56106
|
+
return `Invalid Host header: ${hostHeader}`;
|
|
56107
|
+
}
|
|
56108
|
+
}
|
|
56109
|
+
if (this._options.allowedOrigins && this._options.allowedOrigins.length > 0) {
|
|
56110
|
+
const originHeader = req.headers.origin;
|
|
56111
|
+
if (originHeader && !this._options.allowedOrigins.includes(originHeader)) {
|
|
56112
|
+
return `Invalid Origin header: ${originHeader}`;
|
|
56113
|
+
}
|
|
56114
|
+
}
|
|
56115
|
+
return;
|
|
56116
|
+
}
|
|
56117
|
+
async start() {
|
|
56118
|
+
if (this._sseResponse) {
|
|
56119
|
+
throw new Error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.");
|
|
56120
|
+
}
|
|
56121
|
+
this.res.writeHead(200, {
|
|
56122
|
+
"Content-Type": "text/event-stream",
|
|
56123
|
+
"Cache-Control": "no-cache, no-transform",
|
|
56124
|
+
Connection: "keep-alive"
|
|
56125
|
+
});
|
|
56126
|
+
const dummyBase = "http://localhost";
|
|
56127
|
+
const endpointUrl = new URL2(this._endpoint, dummyBase);
|
|
56128
|
+
endpointUrl.searchParams.set("sessionId", this._sessionId);
|
|
56129
|
+
const relativeUrlWithSession = endpointUrl.pathname + endpointUrl.search + endpointUrl.hash;
|
|
56130
|
+
this.res.write(`event: endpoint
|
|
56131
|
+
data: ${relativeUrlWithSession}
|
|
56132
|
+
|
|
56133
|
+
`);
|
|
56134
|
+
this._sseResponse = this.res;
|
|
56135
|
+
this.res.on("close", () => {
|
|
56136
|
+
this._sseResponse = undefined;
|
|
56137
|
+
this.onclose?.();
|
|
56138
|
+
});
|
|
56139
|
+
}
|
|
56140
|
+
async handlePostMessage(req, res, parsedBody) {
|
|
56141
|
+
if (!this._sseResponse) {
|
|
56142
|
+
const message = "SSE connection not established";
|
|
56143
|
+
res.writeHead(500).end(message);
|
|
56144
|
+
throw new Error(message);
|
|
56145
|
+
}
|
|
56146
|
+
const validationError = this.validateRequestHeaders(req);
|
|
56147
|
+
if (validationError) {
|
|
56148
|
+
res.writeHead(403).end(validationError);
|
|
56149
|
+
this.onerror?.(new Error(validationError));
|
|
56150
|
+
return;
|
|
56151
|
+
}
|
|
56152
|
+
const authInfo = req.auth;
|
|
56153
|
+
const host = req.headers.host;
|
|
56154
|
+
const protocol = req.socket instanceof TLSSocket ? "https" : "http";
|
|
56155
|
+
const fullUrl = host && req.url ? new URL2(req.url, `${protocol}://${host}`) : undefined;
|
|
56156
|
+
const requestInfo = {
|
|
56157
|
+
headers: req.headers,
|
|
56158
|
+
url: fullUrl
|
|
56159
|
+
};
|
|
56160
|
+
let body;
|
|
56161
|
+
try {
|
|
56162
|
+
const ct = import_content_type.default.parse(req.headers["content-type"] ?? "");
|
|
56163
|
+
if (ct.type !== "application/json") {
|
|
56164
|
+
throw new Error(`Unsupported content-type: ${ct.type}`);
|
|
56165
|
+
}
|
|
56166
|
+
body = parsedBody ?? await import_raw_body.default(req, {
|
|
56167
|
+
limit: MAXIMUM_MESSAGE_SIZE,
|
|
56168
|
+
encoding: ct.parameters.charset ?? "utf-8"
|
|
56169
|
+
});
|
|
56170
|
+
} catch (error2) {
|
|
56171
|
+
res.writeHead(400).end(String(error2));
|
|
56172
|
+
this.onerror?.(error2);
|
|
56173
|
+
return;
|
|
56174
|
+
}
|
|
56175
|
+
try {
|
|
56176
|
+
await this.handleMessage(typeof body === "string" ? JSON.parse(body) : body, { requestInfo, authInfo });
|
|
56177
|
+
} catch {
|
|
56178
|
+
res.writeHead(400).end(`Invalid message: ${body}`);
|
|
56179
|
+
return;
|
|
56180
|
+
}
|
|
56181
|
+
res.writeHead(202).end("Accepted");
|
|
56182
|
+
}
|
|
56183
|
+
async handleMessage(message, extra) {
|
|
56184
|
+
let parsedMessage;
|
|
56185
|
+
try {
|
|
56186
|
+
parsedMessage = JSONRPCMessageSchema.parse(message);
|
|
56187
|
+
} catch (error2) {
|
|
56188
|
+
this.onerror?.(error2);
|
|
56189
|
+
throw error2;
|
|
56190
|
+
}
|
|
56191
|
+
this.onmessage?.(parsedMessage, extra);
|
|
56192
|
+
}
|
|
56193
|
+
async close() {
|
|
56194
|
+
this._sseResponse?.end();
|
|
56195
|
+
this._sseResponse = undefined;
|
|
56196
|
+
this.onclose?.();
|
|
56197
|
+
}
|
|
56198
|
+
async send(message) {
|
|
56199
|
+
if (!this._sseResponse) {
|
|
56200
|
+
throw new Error("Not connected");
|
|
56201
|
+
}
|
|
56202
|
+
this._sseResponse.write(`event: message
|
|
56203
|
+
data: ${JSON.stringify(message)}
|
|
56204
|
+
|
|
56205
|
+
`);
|
|
56206
|
+
}
|
|
56207
|
+
get sessionId() {
|
|
56208
|
+
return this._sessionId;
|
|
56209
|
+
}
|
|
56210
|
+
}
|
|
56211
|
+
|
|
56084
56212
|
// node_modules/zod/v4/classic/external.js
|
|
56085
56213
|
var exports_external = {};
|
|
56086
56214
|
__export(exports_external, {
|
|
@@ -70395,7 +70523,7 @@ function nanoid4(size = 21) {
|
|
|
70395
70523
|
}
|
|
70396
70524
|
|
|
70397
70525
|
// src/core/types.ts
|
|
70398
|
-
var MEMLINK_VERSION = "1.0.
|
|
70526
|
+
var MEMLINK_VERSION = "1.0.4";
|
|
70399
70527
|
var DEFAULT_PORT = 4444;
|
|
70400
70528
|
var DEFAULT_HOST = "localhost";
|
|
70401
70529
|
var CONFIG_DIR = ".memlink";
|
|
@@ -71239,6 +71367,7 @@ Deleted: ${result.deleted}`
|
|
|
71239
71367
|
});
|
|
71240
71368
|
return server;
|
|
71241
71369
|
}
|
|
71370
|
+
var sseSessions = new Map;
|
|
71242
71371
|
function createApp() {
|
|
71243
71372
|
const app = import_express.default();
|
|
71244
71373
|
app.use(import_express.default.json({ limit: "10mb" }));
|
|
@@ -71276,6 +71405,50 @@ function createApp() {
|
|
|
71276
71405
|
content: AGENT_SYSTEM_PROMPT(name)
|
|
71277
71406
|
});
|
|
71278
71407
|
});
|
|
71408
|
+
app.get("/sse", async (req, res) => {
|
|
71409
|
+
const memId = req.query.id || req.query.mem_id;
|
|
71410
|
+
if (!memId) {
|
|
71411
|
+
res.status(401).json({ error: "Missing memory ID. Use ?id=<memory_id>" });
|
|
71412
|
+
return;
|
|
71413
|
+
}
|
|
71414
|
+
const memoryInfo = getMemoryById(memId);
|
|
71415
|
+
if (!memoryInfo) {
|
|
71416
|
+
res.status(403).json({ error: "Invalid memory ID." });
|
|
71417
|
+
return;
|
|
71418
|
+
}
|
|
71419
|
+
const memoryId = memoryInfo.memoryId;
|
|
71420
|
+
const memoryName = memoryInfo.memoryName;
|
|
71421
|
+
updateUniversalMemoryLastSeen(memoryId);
|
|
71422
|
+
const mcpServer = buildMcpServer(memoryId, memoryName);
|
|
71423
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
71424
|
+
sseSessions.set(transport.sessionId, { transport, memoryId, mcpServer });
|
|
71425
|
+
res.on("close", () => {
|
|
71426
|
+
sseSessions.delete(transport.sessionId);
|
|
71427
|
+
mcpServer.close();
|
|
71428
|
+
});
|
|
71429
|
+
try {
|
|
71430
|
+
await mcpServer.connect(transport);
|
|
71431
|
+
await transport.start();
|
|
71432
|
+
} catch (err) {
|
|
71433
|
+
sseSessions.delete(transport.sessionId);
|
|
71434
|
+
mcpServer.close();
|
|
71435
|
+
}
|
|
71436
|
+
});
|
|
71437
|
+
app.post("/messages", async (req, res) => {
|
|
71438
|
+
const sessionId = req.query.sessionId;
|
|
71439
|
+
if (!sessionId || !sseSessions.has(sessionId)) {
|
|
71440
|
+
res.status(404).json({ error: "SSE session not found" });
|
|
71441
|
+
return;
|
|
71442
|
+
}
|
|
71443
|
+
const { transport } = sseSessions.get(sessionId);
|
|
71444
|
+
try {
|
|
71445
|
+
await transport.handlePostMessage(req, res, req.body);
|
|
71446
|
+
} catch (err) {
|
|
71447
|
+
if (!res.headersSent) {
|
|
71448
|
+
res.status(500).json({ error: String(err) });
|
|
71449
|
+
}
|
|
71450
|
+
}
|
|
71451
|
+
});
|
|
71279
71452
|
app.all("/mcp", async (req, res) => {
|
|
71280
71453
|
const memId = req.query.id || req.query.mem_id;
|
|
71281
71454
|
if (!memId) {
|