@hasna/cloud 0.1.31 → 0.1.32
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/LICENSE +2 -1
- package/README.md +17 -0
- package/dist/adapter.test.d.ts +2 -0
- package/dist/adapter.test.d.ts.map +1 -0
- package/dist/auto-sync.d.ts.map +1 -1
- package/dist/cli/cmd-doctor.d.ts +3 -0
- package/dist/cli/cmd-doctor.d.ts.map +1 -0
- package/dist/cli/cmd-feedback.d.ts +3 -0
- package/dist/cli/cmd-feedback.d.ts.map +1 -0
- package/dist/cli/cmd-migrate.d.ts +3 -0
- package/dist/cli/cmd-migrate.d.ts.map +1 -0
- package/dist/cli/cmd-setup.d.ts +3 -0
- package/dist/cli/cmd-setup.d.ts.map +1 -0
- package/dist/cli/cmd-sync.d.ts +4 -0
- package/dist/cli/cmd-sync.d.ts.map +1 -0
- package/dist/cli/index.js +2330 -1079
- package/dist/config.d.ts +138 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/daemon-sync.d.ts +108 -0
- package/dist/daemon-sync.d.ts.map +1 -0
- package/dist/dialect.test.d.ts +2 -0
- package/dist/dialect.test.d.ts.map +1 -0
- package/dist/discover.test.d.ts +2 -0
- package/dist/discover.test.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1143 -153
- package/dist/machines.d.ts +63 -0
- package/dist/machines.d.ts.map +1 -0
- package/dist/mcp/http.d.ts +27 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +2125 -438
- package/dist/scheduled-sync.js +205 -44
- package/dist/sync-conflicts.test.d.ts +2 -0
- package/dist/sync-conflicts.test.d.ts.map +1 -0
- package/dist/sync-incremental.d.ts +5 -0
- package/dist/sync-incremental.d.ts.map +1 -1
- package/dist/sync-schedule.test.d.ts +2 -0
- package/dist/sync-schedule.test.d.ts.map +1 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.test.d.ts +2 -0
- package/dist/sync.test.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -10596,7 +10596,7 @@ var require_arrayParser = __commonJS((exports, module) => {
|
|
|
10596
10596
|
};
|
|
10597
10597
|
});
|
|
10598
10598
|
|
|
10599
|
-
// node_modules/
|
|
10599
|
+
// node_modules/postgres-date/index.js
|
|
10600
10600
|
var require_postgres_date = __commonJS((exports, module) => {
|
|
10601
10601
|
var DATE_TIME = /(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?.*?( BC)?$/;
|
|
10602
10602
|
var DATE = /^(\d{1,})-(\d{2})-(\d{2})( BC)?$/;
|
|
@@ -10698,7 +10698,7 @@ var require_mutable = __commonJS((exports, module) => {
|
|
|
10698
10698
|
}
|
|
10699
10699
|
});
|
|
10700
10700
|
|
|
10701
|
-
// node_modules/
|
|
10701
|
+
// node_modules/postgres-interval/index.js
|
|
10702
10702
|
var require_postgres_interval = __commonJS((exports, module) => {
|
|
10703
10703
|
var extend2 = require_mutable();
|
|
10704
10704
|
module.exports = PostgresInterval;
|
|
@@ -10790,7 +10790,7 @@ var require_postgres_interval = __commonJS((exports, module) => {
|
|
|
10790
10790
|
}
|
|
10791
10791
|
});
|
|
10792
10792
|
|
|
10793
|
-
// node_modules/
|
|
10793
|
+
// node_modules/postgres-bytea/index.js
|
|
10794
10794
|
var require_postgres_bytea = __commonJS((exports, module) => {
|
|
10795
10795
|
var bufferFrom = Buffer.from || Buffer;
|
|
10796
10796
|
module.exports = function parseBytea(input) {
|
|
@@ -11814,7 +11814,7 @@ var require_cert_signatures = __commonJS((exports, module) => {
|
|
|
11814
11814
|
|
|
11815
11815
|
// node_modules/pg/lib/crypto/sasl.js
|
|
11816
11816
|
var require_sasl = __commonJS((exports, module) => {
|
|
11817
|
-
var
|
|
11817
|
+
var crypto2 = require_utils3();
|
|
11818
11818
|
var { signatureAlgorithmHashFromCertificate } = require_cert_signatures();
|
|
11819
11819
|
function startSession(mechanisms, stream) {
|
|
11820
11820
|
const candidates = ["SCRAM-SHA-256"];
|
|
@@ -11827,7 +11827,7 @@ var require_sasl = __commonJS((exports, module) => {
|
|
|
11827
11827
|
if (mechanism === "SCRAM-SHA-256-PLUS" && typeof stream.getPeerCertificate !== "function") {
|
|
11828
11828
|
throw new Error("SASL: Mechanism SCRAM-SHA-256-PLUS requires a certificate");
|
|
11829
11829
|
}
|
|
11830
|
-
const clientNonce =
|
|
11830
|
+
const clientNonce = crypto2.randomBytes(18).toString("base64");
|
|
11831
11831
|
const gs2Header = mechanism === "SCRAM-SHA-256-PLUS" ? "p=tls-server-end-point" : stream ? "y" : "n";
|
|
11832
11832
|
return {
|
|
11833
11833
|
mechanism,
|
|
@@ -11863,20 +11863,20 @@ var require_sasl = __commonJS((exports, module) => {
|
|
|
11863
11863
|
let hashName = signatureAlgorithmHashFromCertificate(peerCert);
|
|
11864
11864
|
if (hashName === "MD5" || hashName === "SHA-1")
|
|
11865
11865
|
hashName = "SHA-256";
|
|
11866
|
-
const certHash = await
|
|
11866
|
+
const certHash = await crypto2.hashByName(hashName, peerCert);
|
|
11867
11867
|
const bindingData = Buffer.concat([Buffer.from("p=tls-server-end-point,,"), Buffer.from(certHash)]);
|
|
11868
11868
|
channelBinding = bindingData.toString("base64");
|
|
11869
11869
|
}
|
|
11870
11870
|
const clientFinalMessageWithoutProof = "c=" + channelBinding + ",r=" + sv.nonce;
|
|
11871
11871
|
const authMessage = clientFirstMessageBare + "," + serverFirstMessage + "," + clientFinalMessageWithoutProof;
|
|
11872
11872
|
const saltBytes = Buffer.from(sv.salt, "base64");
|
|
11873
|
-
const saltedPassword = await
|
|
11874
|
-
const clientKey = await
|
|
11875
|
-
const storedKey = await
|
|
11876
|
-
const clientSignature = await
|
|
11873
|
+
const saltedPassword = await crypto2.deriveKey(password, saltBytes, sv.iteration);
|
|
11874
|
+
const clientKey = await crypto2.hmacSha256(saltedPassword, "Client Key");
|
|
11875
|
+
const storedKey = await crypto2.sha256(clientKey);
|
|
11876
|
+
const clientSignature = await crypto2.hmacSha256(storedKey, authMessage);
|
|
11877
11877
|
const clientProof = xorBuffers(Buffer.from(clientKey), Buffer.from(clientSignature)).toString("base64");
|
|
11878
|
-
const serverKey = await
|
|
11879
|
-
const serverSignatureBytes = await
|
|
11878
|
+
const serverKey = await crypto2.hmacSha256(saltedPassword, "Server Key");
|
|
11879
|
+
const serverSignatureBytes = await crypto2.hmacSha256(serverKey, authMessage);
|
|
11880
11880
|
session.message = "SASLResponse";
|
|
11881
11881
|
session.serverSignature = Buffer.from(serverSignatureBytes).toString("base64");
|
|
11882
11882
|
session.response = clientFinalMessageWithoutProof + ",p=" + clientProof;
|
|
@@ -13930,7 +13930,7 @@ var require_client = __commonJS((exports, module) => {
|
|
|
13930
13930
|
var Query = require_query();
|
|
13931
13931
|
var defaults = require_defaults2();
|
|
13932
13932
|
var Connection = require_connection();
|
|
13933
|
-
var
|
|
13933
|
+
var crypto2 = require_utils3();
|
|
13934
13934
|
var activeQueryDeprecationNotice = nodeUtils.deprecate(() => {}, "Client.activeQuery is deprecated and will be removed in pg@9.0");
|
|
13935
13935
|
var queryQueueDeprecationNotice = nodeUtils.deprecate(() => {}, "Client.queryQueue is deprecated and will be removed in pg@9.0.");
|
|
13936
13936
|
var pgPassDeprecationNotice = nodeUtils.deprecate(() => {}, "pgpass support is deprecated and will be removed in pg@9.0. " + "You can provide an async function as the password property to the Client/Pool constructor that returns a password instead. Within this function you can call the pgpass module in your own code.");
|
|
@@ -14146,7 +14146,7 @@ var require_client = __commonJS((exports, module) => {
|
|
|
14146
14146
|
_handleAuthMD5Password(msg) {
|
|
14147
14147
|
this._getPassword(async () => {
|
|
14148
14148
|
try {
|
|
14149
|
-
const hashedPassword = await
|
|
14149
|
+
const hashedPassword = await crypto2.postgresMd5PasswordHash(this.user, this.password, msg.salt);
|
|
14150
14150
|
this.connection.password(hashedPassword);
|
|
14151
14151
|
} catch (e) {
|
|
14152
14152
|
this.emit("error", e);
|
|
@@ -15697,6 +15697,520 @@ var init_dotfile = __esm(() => {
|
|
|
15697
15697
|
HASNA_DIR = join(homedir(), ".hasna");
|
|
15698
15698
|
});
|
|
15699
15699
|
|
|
15700
|
+
// src/discover.ts
|
|
15701
|
+
var exports_discover = {};
|
|
15702
|
+
__export(exports_discover, {
|
|
15703
|
+
isSyncExcludedTable: () => isSyncExcludedTable,
|
|
15704
|
+
getServiceDbPath: () => getServiceDbPath,
|
|
15705
|
+
discoverSyncableServices: () => discoverSyncableServices,
|
|
15706
|
+
discoverServices: () => discoverServices,
|
|
15707
|
+
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
|
|
15708
|
+
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
15709
|
+
});
|
|
15710
|
+
import { readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
15711
|
+
import { join as join2 } from "path";
|
|
15712
|
+
import { homedir as homedir2 } from "os";
|
|
15713
|
+
function isSyncExcludedTable(table) {
|
|
15714
|
+
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
15715
|
+
}
|
|
15716
|
+
function discoverServices() {
|
|
15717
|
+
const dataDir = join2(homedir2(), ".hasna");
|
|
15718
|
+
if (!existsSync2(dataDir))
|
|
15719
|
+
return [];
|
|
15720
|
+
try {
|
|
15721
|
+
const entries = readdirSync2(dataDir, { withFileTypes: true });
|
|
15722
|
+
return entries.filter((e) => {
|
|
15723
|
+
if (!e.isDirectory())
|
|
15724
|
+
return false;
|
|
15725
|
+
if (e.name === "cloud" || e.name.startsWith("."))
|
|
15726
|
+
return false;
|
|
15727
|
+
return true;
|
|
15728
|
+
}).map((e) => e.name).sort();
|
|
15729
|
+
} catch {
|
|
15730
|
+
return [];
|
|
15731
|
+
}
|
|
15732
|
+
}
|
|
15733
|
+
function discoverSyncableServices() {
|
|
15734
|
+
const local = discoverServices();
|
|
15735
|
+
const pgSet = new Set(KNOWN_PG_SERVICES);
|
|
15736
|
+
return local.filter((s) => pgSet.has(s));
|
|
15737
|
+
}
|
|
15738
|
+
function getServiceDbPath(service) {
|
|
15739
|
+
const dataDir = join2(homedir2(), ".hasna", service);
|
|
15740
|
+
if (!existsSync2(dataDir))
|
|
15741
|
+
return null;
|
|
15742
|
+
const candidates = [
|
|
15743
|
+
join2(dataDir, `${service}.db`),
|
|
15744
|
+
join2(dataDir, "data.db"),
|
|
15745
|
+
join2(dataDir, "database.db")
|
|
15746
|
+
];
|
|
15747
|
+
try {
|
|
15748
|
+
const files = readdirSync2(dataDir);
|
|
15749
|
+
for (const f of files) {
|
|
15750
|
+
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
15751
|
+
candidates.push(join2(dataDir, f));
|
|
15752
|
+
}
|
|
15753
|
+
}
|
|
15754
|
+
} catch {}
|
|
15755
|
+
for (const p of candidates) {
|
|
15756
|
+
if (existsSync2(p))
|
|
15757
|
+
return p;
|
|
15758
|
+
}
|
|
15759
|
+
return null;
|
|
15760
|
+
}
|
|
15761
|
+
var KNOWN_PG_SERVICES, SYNC_EXCLUDED_TABLE_PATTERNS;
|
|
15762
|
+
var init_discover = __esm(() => {
|
|
15763
|
+
KNOWN_PG_SERVICES = [
|
|
15764
|
+
"assistants",
|
|
15765
|
+
"attachments",
|
|
15766
|
+
"brains",
|
|
15767
|
+
"configs",
|
|
15768
|
+
"connectors",
|
|
15769
|
+
"contacts",
|
|
15770
|
+
"context",
|
|
15771
|
+
"conversations",
|
|
15772
|
+
"crawl",
|
|
15773
|
+
"deployment",
|
|
15774
|
+
"economy",
|
|
15775
|
+
"emails",
|
|
15776
|
+
"files",
|
|
15777
|
+
"hooks",
|
|
15778
|
+
"implementations",
|
|
15779
|
+
"logs",
|
|
15780
|
+
"mcps",
|
|
15781
|
+
"mementos",
|
|
15782
|
+
"microservices",
|
|
15783
|
+
"predictor",
|
|
15784
|
+
"prompts",
|
|
15785
|
+
"recordings",
|
|
15786
|
+
"researcher",
|
|
15787
|
+
"sandboxes",
|
|
15788
|
+
"search",
|
|
15789
|
+
"secrets",
|
|
15790
|
+
"sessions",
|
|
15791
|
+
"signatures",
|
|
15792
|
+
"skills",
|
|
15793
|
+
"telephony",
|
|
15794
|
+
"terminal",
|
|
15795
|
+
"testers",
|
|
15796
|
+
"tickets",
|
|
15797
|
+
"todos",
|
|
15798
|
+
"wallets"
|
|
15799
|
+
];
|
|
15800
|
+
SYNC_EXCLUDED_TABLE_PATTERNS = [
|
|
15801
|
+
/^sqlite_/,
|
|
15802
|
+
/_fts$/,
|
|
15803
|
+
/_fts_/,
|
|
15804
|
+
/^_sync_/,
|
|
15805
|
+
/^_pg_migrations$/
|
|
15806
|
+
];
|
|
15807
|
+
});
|
|
15808
|
+
|
|
15809
|
+
// src/machines.ts
|
|
15810
|
+
import { spawnSync } from "child_process";
|
|
15811
|
+
import { existsSync as existsSync3 } from "fs";
|
|
15812
|
+
import { homedir as homedir3, hostname as hostname2, platform, arch, userInfo } from "os";
|
|
15813
|
+
import { dirname, join as join3 } from "path";
|
|
15814
|
+
function quoteSqlString(value) {
|
|
15815
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
15816
|
+
}
|
|
15817
|
+
function normalizePlatform(value) {
|
|
15818
|
+
if (value === "darwin")
|
|
15819
|
+
return "macos";
|
|
15820
|
+
if (value === "win32")
|
|
15821
|
+
return "windows";
|
|
15822
|
+
return value;
|
|
15823
|
+
}
|
|
15824
|
+
function detectWorkspacePath() {
|
|
15825
|
+
const home = homedir3();
|
|
15826
|
+
const candidates = [join3(home, "workspace"), join3(home, "Workspace")];
|
|
15827
|
+
for (const candidate of candidates) {
|
|
15828
|
+
if (existsSync3(candidate))
|
|
15829
|
+
return candidate;
|
|
15830
|
+
}
|
|
15831
|
+
const cwd = process.cwd();
|
|
15832
|
+
const workspaceIdx = cwd.indexOf("/workspace/");
|
|
15833
|
+
if (workspaceIdx >= 0) {
|
|
15834
|
+
return cwd.slice(0, workspaceIdx + "/workspace".length);
|
|
15835
|
+
}
|
|
15836
|
+
const workspaceUpperIdx = cwd.indexOf("/Workspace/");
|
|
15837
|
+
if (workspaceUpperIdx >= 0) {
|
|
15838
|
+
return cwd.slice(0, workspaceUpperIdx + "/Workspace".length);
|
|
15839
|
+
}
|
|
15840
|
+
return cwd;
|
|
15841
|
+
}
|
|
15842
|
+
function detectBunPath() {
|
|
15843
|
+
return dirname(process.execPath);
|
|
15844
|
+
}
|
|
15845
|
+
function toFlag(value, fallback = 0) {
|
|
15846
|
+
if (value === undefined)
|
|
15847
|
+
return fallback;
|
|
15848
|
+
return value ? 1 : 0;
|
|
15849
|
+
}
|
|
15850
|
+
function getCurrentMachineId() {
|
|
15851
|
+
return hostname2();
|
|
15852
|
+
}
|
|
15853
|
+
function detectCurrentMachine(opts = {}) {
|
|
15854
|
+
const id = opts.id ?? getCurrentMachineId();
|
|
15855
|
+
const username = userInfo().username;
|
|
15856
|
+
return {
|
|
15857
|
+
id,
|
|
15858
|
+
ssh_address: opts.ssh_address ?? `${username}@${id}`,
|
|
15859
|
+
arch: opts.arch ?? `${normalizePlatform(platform())}-${arch()}`,
|
|
15860
|
+
workspace_path: opts.workspace_path ?? detectWorkspacePath(),
|
|
15861
|
+
bun_path: opts.bun_path ?? detectBunPath(),
|
|
15862
|
+
is_primary: opts.is_primary,
|
|
15863
|
+
archived: opts.archived,
|
|
15864
|
+
last_seen_at: opts.last_seen_at,
|
|
15865
|
+
registered_at: opts.registered_at
|
|
15866
|
+
};
|
|
15867
|
+
}
|
|
15868
|
+
function ensureMachinesTable(db) {
|
|
15869
|
+
db.exec(MACHINES_TABLE_SQL);
|
|
15870
|
+
}
|
|
15871
|
+
function getMachineRecord(db, id) {
|
|
15872
|
+
ensureMachinesTable(db);
|
|
15873
|
+
return db.get(`SELECT id, ssh_address, arch, workspace_path, bun_path, is_primary, last_seen_at, registered_at, archived
|
|
15874
|
+
FROM machines
|
|
15875
|
+
WHERE id = ?`, id) ?? null;
|
|
15876
|
+
}
|
|
15877
|
+
function registerMachine(db, opts = {}) {
|
|
15878
|
+
ensureMachinesTable(db);
|
|
15879
|
+
const detected = detectCurrentMachine(opts);
|
|
15880
|
+
const id = detected.id ?? getCurrentMachineId();
|
|
15881
|
+
const now = new Date().toISOString();
|
|
15882
|
+
const existing = getMachineRecord(db, id);
|
|
15883
|
+
const isPrimary = toFlag(detected.is_primary, existing?.is_primary ?? 0);
|
|
15884
|
+
const archived = toFlag(detected.archived, existing?.archived ?? 0);
|
|
15885
|
+
if (isPrimary === 1 && archived === 1) {
|
|
15886
|
+
throw new Error(`Primary machine "${id}" cannot be archived.`);
|
|
15887
|
+
}
|
|
15888
|
+
const record3 = {
|
|
15889
|
+
id,
|
|
15890
|
+
ssh_address: detected.ssh_address ?? existing?.ssh_address ?? "",
|
|
15891
|
+
arch: detected.arch ?? existing?.arch ?? "",
|
|
15892
|
+
workspace_path: detected.workspace_path ?? existing?.workspace_path ?? "",
|
|
15893
|
+
bun_path: detected.bun_path ?? existing?.bun_path ?? "",
|
|
15894
|
+
is_primary: isPrimary,
|
|
15895
|
+
last_seen_at: detected.last_seen_at ?? now,
|
|
15896
|
+
registered_at: existing?.registered_at ?? detected.registered_at ?? now,
|
|
15897
|
+
archived
|
|
15898
|
+
};
|
|
15899
|
+
db.run(`INSERT INTO machines (
|
|
15900
|
+
id,
|
|
15901
|
+
ssh_address,
|
|
15902
|
+
arch,
|
|
15903
|
+
workspace_path,
|
|
15904
|
+
bun_path,
|
|
15905
|
+
is_primary,
|
|
15906
|
+
last_seen_at,
|
|
15907
|
+
registered_at,
|
|
15908
|
+
archived
|
|
15909
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
15910
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
15911
|
+
ssh_address = excluded.ssh_address,
|
|
15912
|
+
arch = excluded.arch,
|
|
15913
|
+
workspace_path = excluded.workspace_path,
|
|
15914
|
+
bun_path = excluded.bun_path,
|
|
15915
|
+
is_primary = excluded.is_primary,
|
|
15916
|
+
last_seen_at = excluded.last_seen_at,
|
|
15917
|
+
registered_at = excluded.registered_at,
|
|
15918
|
+
archived = excluded.archived`, record3.id, record3.ssh_address, record3.arch, record3.workspace_path, record3.bun_path, record3.is_primary, record3.last_seen_at, record3.registered_at, record3.archived);
|
|
15919
|
+
return getMachineRecord(db, record3.id) ?? record3;
|
|
15920
|
+
}
|
|
15921
|
+
function listMachines(db, opts = {}) {
|
|
15922
|
+
ensureMachinesTable(db);
|
|
15923
|
+
const includeArchived = opts.includeArchived ?? false;
|
|
15924
|
+
const whereClause = includeArchived ? "" : "WHERE archived = 0";
|
|
15925
|
+
return db.all(`SELECT id, ssh_address, arch, workspace_path, bun_path, is_primary, last_seen_at, registered_at, archived
|
|
15926
|
+
FROM machines
|
|
15927
|
+
${whereClause}
|
|
15928
|
+
ORDER BY is_primary DESC, id ASC`);
|
|
15929
|
+
}
|
|
15930
|
+
function pingMachine(machine) {
|
|
15931
|
+
const record3 = typeof machine === "string" ? {
|
|
15932
|
+
id: machine,
|
|
15933
|
+
ssh_address: machine
|
|
15934
|
+
} : machine;
|
|
15935
|
+
const startedAt = Date.now();
|
|
15936
|
+
const checkedAt = new Date().toISOString();
|
|
15937
|
+
const currentId = getCurrentMachineId();
|
|
15938
|
+
if (record3.id === currentId) {
|
|
15939
|
+
return {
|
|
15940
|
+
id: record3.id,
|
|
15941
|
+
online: true,
|
|
15942
|
+
checked_at: checkedAt,
|
|
15943
|
+
latency_ms: Date.now() - startedAt
|
|
15944
|
+
};
|
|
15945
|
+
}
|
|
15946
|
+
const target = record3.ssh_address || record3.id;
|
|
15947
|
+
if (!target) {
|
|
15948
|
+
return {
|
|
15949
|
+
id: record3.id,
|
|
15950
|
+
online: false,
|
|
15951
|
+
error: "Machine has no ssh target.",
|
|
15952
|
+
checked_at: checkedAt,
|
|
15953
|
+
latency_ms: Date.now() - startedAt
|
|
15954
|
+
};
|
|
15955
|
+
}
|
|
15956
|
+
const result = spawnSync("ssh", ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", target, "true"], {
|
|
15957
|
+
encoding: "utf-8",
|
|
15958
|
+
timeout: 6000
|
|
15959
|
+
});
|
|
15960
|
+
return {
|
|
15961
|
+
id: record3.id,
|
|
15962
|
+
online: result.status === 0,
|
|
15963
|
+
error: result.status === 0 ? undefined : (result.stderr || result.error?.message || "SSH health check failed").trim(),
|
|
15964
|
+
checked_at: checkedAt,
|
|
15965
|
+
latency_ms: Date.now() - startedAt
|
|
15966
|
+
};
|
|
15967
|
+
}
|
|
15968
|
+
function getMachineStatus(db, opts = {}) {
|
|
15969
|
+
return listMachines(db, opts).map((machine) => pingMachine(machine));
|
|
15970
|
+
}
|
|
15971
|
+
function tableExists(db, table) {
|
|
15972
|
+
try {
|
|
15973
|
+
if (typeof db.query === "function") {
|
|
15974
|
+
const rows2 = db.all(`SELECT name FROM sqlite_master WHERE type='table' AND name = ?`, table);
|
|
15975
|
+
return rows2.length > 0;
|
|
15976
|
+
}
|
|
15977
|
+
const rows = db.all(`SELECT table_name
|
|
15978
|
+
FROM information_schema.tables
|
|
15979
|
+
WHERE table_schema = 'public' AND table_name = ?`, table);
|
|
15980
|
+
return rows.length > 0;
|
|
15981
|
+
} catch {
|
|
15982
|
+
return false;
|
|
15983
|
+
}
|
|
15984
|
+
}
|
|
15985
|
+
function hasMachineIdColumn(db, table) {
|
|
15986
|
+
try {
|
|
15987
|
+
if (typeof db.query === "function") {
|
|
15988
|
+
const rows2 = db.all(`PRAGMA table_info("${table}")`);
|
|
15989
|
+
return rows2.some((row) => row.name === "machine_id");
|
|
15990
|
+
}
|
|
15991
|
+
const rows = db.all(`SELECT column_name
|
|
15992
|
+
FROM information_schema.columns
|
|
15993
|
+
WHERE table_schema = 'public' AND table_name = ? AND column_name = 'machine_id'`, table);
|
|
15994
|
+
return rows.length > 0;
|
|
15995
|
+
} catch {
|
|
15996
|
+
return false;
|
|
15997
|
+
}
|
|
15998
|
+
}
|
|
15999
|
+
function shouldTrackMachineId(table) {
|
|
16000
|
+
return table !== "machines" && !isSyncExcludedTable(table);
|
|
16001
|
+
}
|
|
16002
|
+
function ensureMachineIdColumn(db, table) {
|
|
16003
|
+
if (!shouldTrackMachineId(table) || !tableExists(db, table)) {
|
|
16004
|
+
return false;
|
|
16005
|
+
}
|
|
16006
|
+
if (hasMachineIdColumn(db, table)) {
|
|
16007
|
+
return false;
|
|
16008
|
+
}
|
|
16009
|
+
db.exec(`ALTER TABLE "${table}" ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
16010
|
+
return true;
|
|
16011
|
+
}
|
|
16012
|
+
function extractTableName(sql, pattern) {
|
|
16013
|
+
const match = sql.match(pattern);
|
|
16014
|
+
if (!match)
|
|
16015
|
+
return null;
|
|
16016
|
+
return match[2] ?? match[3] ?? null;
|
|
16017
|
+
}
|
|
16018
|
+
function appendLiteralToValueTuples(sql, literal3) {
|
|
16019
|
+
let output = "";
|
|
16020
|
+
let depth = 0;
|
|
16021
|
+
let inSingle = false;
|
|
16022
|
+
let inDouble = false;
|
|
16023
|
+
let sawTuple = false;
|
|
16024
|
+
let inValues = true;
|
|
16025
|
+
for (let i = 0;i < sql.length; i++) {
|
|
16026
|
+
const char = sql[i];
|
|
16027
|
+
const prev = sql[i - 1];
|
|
16028
|
+
if (!inDouble && char === "'" && prev !== "\\") {
|
|
16029
|
+
inSingle = !inSingle;
|
|
16030
|
+
output += char;
|
|
16031
|
+
continue;
|
|
16032
|
+
}
|
|
16033
|
+
if (!inSingle && char === `"` && prev !== "\\") {
|
|
16034
|
+
inDouble = !inDouble;
|
|
16035
|
+
output += char;
|
|
16036
|
+
continue;
|
|
16037
|
+
}
|
|
16038
|
+
if (inSingle || inDouble) {
|
|
16039
|
+
output += char;
|
|
16040
|
+
continue;
|
|
16041
|
+
}
|
|
16042
|
+
if (inValues && char === "(") {
|
|
16043
|
+
depth += 1;
|
|
16044
|
+
if (depth === 1)
|
|
16045
|
+
sawTuple = true;
|
|
16046
|
+
output += char;
|
|
16047
|
+
continue;
|
|
16048
|
+
}
|
|
16049
|
+
if (inValues && char === ")" && depth === 1) {
|
|
16050
|
+
output += `, ${literal3})`;
|
|
16051
|
+
depth = 0;
|
|
16052
|
+
continue;
|
|
16053
|
+
}
|
|
16054
|
+
if (inValues && char === ")" && depth > 1) {
|
|
16055
|
+
depth -= 1;
|
|
16056
|
+
output += char;
|
|
16057
|
+
continue;
|
|
16058
|
+
}
|
|
16059
|
+
if (inValues && depth === 0 && sawTuple && /[A-Za-z]/.test(char)) {
|
|
16060
|
+
inValues = false;
|
|
16061
|
+
}
|
|
16062
|
+
output += char;
|
|
16063
|
+
}
|
|
16064
|
+
return sawTuple ? output : null;
|
|
16065
|
+
}
|
|
16066
|
+
function rewriteInsertSql(sql, machineId) {
|
|
16067
|
+
const match = sql.match(/^\s*(insert(?:\s+or\s+\w+)?\s+into\s+((?:"([^"]+)")|([A-Za-z_][\w$]*))\s*)\(([^)]*)\)(\s*values\s*)([\s\S]*)$/i);
|
|
16068
|
+
if (!match)
|
|
16069
|
+
return null;
|
|
16070
|
+
const table = match[3] ?? match[4];
|
|
16071
|
+
if (!table || !shouldTrackMachineId(table))
|
|
16072
|
+
return null;
|
|
16073
|
+
const columns = match[5].split(",").map((column) => column.trim().replace(/^"|"$/g, ""));
|
|
16074
|
+
if (columns.some((column) => column.toLowerCase() === "machine_id")) {
|
|
16075
|
+
return { table, sql };
|
|
16076
|
+
}
|
|
16077
|
+
const rewrittenValues = appendLiteralToValueTuples(match[7], quoteSqlString(machineId));
|
|
16078
|
+
if (!rewrittenValues)
|
|
16079
|
+
return { table, sql };
|
|
16080
|
+
const nextColumns = `${match[5].trim()}, "machine_id"`;
|
|
16081
|
+
return {
|
|
16082
|
+
table,
|
|
16083
|
+
sql: `${match[1]}(${nextColumns})${match[6]}${rewrittenValues}`
|
|
16084
|
+
};
|
|
16085
|
+
}
|
|
16086
|
+
function rewriteUpdateSql(sql, machineId) {
|
|
16087
|
+
const match = sql.match(/^\s*(update\s+((?:"([^"]+)")|([A-Za-z_][\w$]*))\s+set\s*)([\s\S]*?)(\s+(?:where|returning)\b[\s\S]*|\s*)$/i);
|
|
16088
|
+
if (!match)
|
|
16089
|
+
return null;
|
|
16090
|
+
const table = match[3] ?? match[4];
|
|
16091
|
+
if (!table || !shouldTrackMachineId(table))
|
|
16092
|
+
return null;
|
|
16093
|
+
if (/\bmachine_id\b/i.test(match[5])) {
|
|
16094
|
+
return { table, sql };
|
|
16095
|
+
}
|
|
16096
|
+
return {
|
|
16097
|
+
table,
|
|
16098
|
+
sql: `${match[1]}${match[5].trimEnd()}, "machine_id" = ${quoteSqlString(machineId)}${match[6]}`
|
|
16099
|
+
};
|
|
16100
|
+
}
|
|
16101
|
+
function maybeRewriteMachineSql(db, sql, machineId) {
|
|
16102
|
+
const trimmed = sql.trimStart();
|
|
16103
|
+
if (/^insert\b/i.test(trimmed)) {
|
|
16104
|
+
const rewritten = rewriteInsertSql(sql, machineId);
|
|
16105
|
+
if (rewritten?.table) {
|
|
16106
|
+
ensureMachineIdColumn(db, rewritten.table);
|
|
16107
|
+
return rewritten.sql;
|
|
16108
|
+
}
|
|
16109
|
+
return sql;
|
|
16110
|
+
}
|
|
16111
|
+
if (/^update\b/i.test(trimmed)) {
|
|
16112
|
+
const rewritten = rewriteUpdateSql(sql, machineId);
|
|
16113
|
+
if (rewritten?.table) {
|
|
16114
|
+
ensureMachineIdColumn(db, rewritten.table);
|
|
16115
|
+
return rewritten.sql;
|
|
16116
|
+
}
|
|
16117
|
+
return sql;
|
|
16118
|
+
}
|
|
16119
|
+
return sql;
|
|
16120
|
+
}
|
|
16121
|
+
function maybeEnsureCreatedTableHasMachineId(db, sql) {
|
|
16122
|
+
const table = extractTableName(sql, /^\s*(create\s+table(?:\s+if\s+not\s+exists)?\s+((?:"([^"]+)")|([A-Za-z_][\w$]*)))/i);
|
|
16123
|
+
if (table) {
|
|
16124
|
+
ensureMachineIdColumn(db, table);
|
|
16125
|
+
}
|
|
16126
|
+
}
|
|
16127
|
+
function createMachineRegistry(db, machineId = getCurrentMachineId()) {
|
|
16128
|
+
return {
|
|
16129
|
+
register(opts = {}) {
|
|
16130
|
+
return registerMachine(db, { ...opts, id: opts.id ?? machineId });
|
|
16131
|
+
},
|
|
16132
|
+
list(opts = {}) {
|
|
16133
|
+
return listMachines(db, opts);
|
|
16134
|
+
},
|
|
16135
|
+
ping(machine) {
|
|
16136
|
+
if (!machine) {
|
|
16137
|
+
return pingMachine(registerMachine(db, { id: machineId }));
|
|
16138
|
+
}
|
|
16139
|
+
return pingMachine(typeof machine === "string" ? getMachineRecord(db, machine) ?? machine : machine);
|
|
16140
|
+
},
|
|
16141
|
+
status(opts = {}) {
|
|
16142
|
+
return getMachineStatus(db, opts);
|
|
16143
|
+
},
|
|
16144
|
+
currentMachine() {
|
|
16145
|
+
return registerMachine(db, { id: machineId });
|
|
16146
|
+
}
|
|
16147
|
+
};
|
|
16148
|
+
}
|
|
16149
|
+
function createMachineAwareAdapter(db) {
|
|
16150
|
+
ensureMachinesTable(db);
|
|
16151
|
+
const machineId = registerMachine(db).id;
|
|
16152
|
+
const machines = createMachineRegistry(db, machineId);
|
|
16153
|
+
const wrapped = {
|
|
16154
|
+
machine_id: machineId,
|
|
16155
|
+
machines,
|
|
16156
|
+
run(sql, ...params) {
|
|
16157
|
+
return db.run(maybeRewriteMachineSql(db, sql, machineId), ...params);
|
|
16158
|
+
},
|
|
16159
|
+
get(sql, ...params) {
|
|
16160
|
+
return db.get(sql, ...params);
|
|
16161
|
+
},
|
|
16162
|
+
all(sql, ...params) {
|
|
16163
|
+
return db.all(sql, ...params);
|
|
16164
|
+
},
|
|
16165
|
+
exec(sql) {
|
|
16166
|
+
db.exec(sql);
|
|
16167
|
+
maybeEnsureCreatedTableHasMachineId(db, sql);
|
|
16168
|
+
},
|
|
16169
|
+
prepare(sql) {
|
|
16170
|
+
const statement = db.prepare(maybeRewriteMachineSql(db, sql, machineId));
|
|
16171
|
+
return {
|
|
16172
|
+
run(...params) {
|
|
16173
|
+
return statement.run(...params);
|
|
16174
|
+
},
|
|
16175
|
+
get(...params) {
|
|
16176
|
+
return statement.get(...params);
|
|
16177
|
+
},
|
|
16178
|
+
all(...params) {
|
|
16179
|
+
return statement.all(...params);
|
|
16180
|
+
},
|
|
16181
|
+
finalize() {
|
|
16182
|
+
statement.finalize();
|
|
16183
|
+
}
|
|
16184
|
+
};
|
|
16185
|
+
},
|
|
16186
|
+
close() {
|
|
16187
|
+
db.close();
|
|
16188
|
+
},
|
|
16189
|
+
transaction(fn) {
|
|
16190
|
+
return db.transaction(fn);
|
|
16191
|
+
},
|
|
16192
|
+
raw: db.raw,
|
|
16193
|
+
query: typeof db.query === "function" ? db.query.bind(db) : undefined
|
|
16194
|
+
};
|
|
16195
|
+
return wrapped;
|
|
16196
|
+
}
|
|
16197
|
+
var MACHINES_TABLE_SQL = `
|
|
16198
|
+
CREATE TABLE IF NOT EXISTS machines (
|
|
16199
|
+
id TEXT PRIMARY KEY,
|
|
16200
|
+
ssh_address TEXT DEFAULT '',
|
|
16201
|
+
arch TEXT DEFAULT '',
|
|
16202
|
+
workspace_path TEXT DEFAULT '',
|
|
16203
|
+
bun_path TEXT DEFAULT '',
|
|
16204
|
+
is_primary INTEGER DEFAULT 0 CHECK (is_primary IN (0, 1)),
|
|
16205
|
+
last_seen_at TEXT,
|
|
16206
|
+
registered_at TEXT,
|
|
16207
|
+
archived INTEGER DEFAULT 0 CHECK (archived IN (0, 1)),
|
|
16208
|
+
CHECK (NOT (is_primary = 1 AND archived = 1))
|
|
16209
|
+
)`;
|
|
16210
|
+
var init_machines = __esm(() => {
|
|
16211
|
+
init_discover();
|
|
16212
|
+
});
|
|
16213
|
+
|
|
15700
16214
|
// src/config.ts
|
|
15701
16215
|
var exports_config = {};
|
|
15702
16216
|
__export(exports_config, {
|
|
@@ -15708,9 +16222,9 @@ __export(exports_config, {
|
|
|
15708
16222
|
createDatabase: () => createDatabase2,
|
|
15709
16223
|
CloudConfigSchema: () => CloudConfigSchema2
|
|
15710
16224
|
});
|
|
15711
|
-
import { existsSync as
|
|
15712
|
-
import { homedir as
|
|
15713
|
-
import { join as
|
|
16225
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16226
|
+
import { homedir as homedir5 } from "os";
|
|
16227
|
+
import { join as join5 } from "path";
|
|
15714
16228
|
function getConfigDir() {
|
|
15715
16229
|
return CONFIG_DIR2;
|
|
15716
16230
|
}
|
|
@@ -15718,7 +16232,7 @@ function getConfigPath() {
|
|
|
15718
16232
|
return CONFIG_PATH2;
|
|
15719
16233
|
}
|
|
15720
16234
|
function getCloudConfig2() {
|
|
15721
|
-
if (!
|
|
16235
|
+
if (!existsSync5(CONFIG_PATH2)) {
|
|
15722
16236
|
return CloudConfigSchema2.parse({});
|
|
15723
16237
|
}
|
|
15724
16238
|
try {
|
|
@@ -15751,16 +16265,32 @@ function createDatabase2(options) {
|
|
|
15751
16265
|
const mode = options.mode ?? config2.mode;
|
|
15752
16266
|
if (mode === "cloud") {
|
|
15753
16267
|
const connStr = options.pgConnectionString ?? getConnectionString2(options.service);
|
|
15754
|
-
return new PgAdapter(connStr);
|
|
16268
|
+
return createMachineAwareAdapter(new PgAdapter(connStr));
|
|
15755
16269
|
}
|
|
15756
16270
|
const dbPath = options.sqlitePath ?? getDbPath(options.service);
|
|
15757
|
-
return new SqliteAdapter(dbPath);
|
|
16271
|
+
return createMachineAwareAdapter(new SqliteAdapter(dbPath));
|
|
15758
16272
|
}
|
|
15759
|
-
var CloudConfigSchema2, CONFIG_DIR2, CONFIG_PATH2;
|
|
16273
|
+
var DaemonConfigSchema2, CloudConfigSchema2, CONFIG_DIR2, CONFIG_PATH2;
|
|
15760
16274
|
var init_config = __esm(() => {
|
|
15761
16275
|
init_zod();
|
|
15762
16276
|
init_adapter();
|
|
15763
16277
|
init_dotfile();
|
|
16278
|
+
init_machines();
|
|
16279
|
+
DaemonConfigSchema2 = exports_external.object({
|
|
16280
|
+
enabled: exports_external.boolean().default(false),
|
|
16281
|
+
paused: exports_external.boolean().default(false),
|
|
16282
|
+
watch_interval_seconds: exports_external.number().int().positive().default(5),
|
|
16283
|
+
pull_interval_seconds: exports_external.number().int().positive().default(60),
|
|
16284
|
+
push_debounce_seconds: exports_external.number().int().positive().default(5),
|
|
16285
|
+
conflict_strategy: exports_external.enum(["newest-wins", "local-wins", "remote-wins"]).default("newest-wins"),
|
|
16286
|
+
services: exports_external.array(exports_external.string()).default([]),
|
|
16287
|
+
table_intervals: exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), exports_external.number().int().positive())).default({}),
|
|
16288
|
+
file_rules: exports_external.array(exports_external.object({
|
|
16289
|
+
path: exports_external.string(),
|
|
16290
|
+
interval_seconds: exports_external.number().int().positive().default(30),
|
|
16291
|
+
enabled: exports_external.boolean().default(true)
|
|
16292
|
+
})).default([])
|
|
16293
|
+
}).default({});
|
|
15764
16294
|
CloudConfigSchema2 = exports_external.object({
|
|
15765
16295
|
rds: exports_external.object({
|
|
15766
16296
|
host: exports_external.string().default(""),
|
|
@@ -15774,34 +16304,35 @@ var init_config = __esm(() => {
|
|
|
15774
16304
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
15775
16305
|
sync: exports_external.object({
|
|
15776
16306
|
schedule_minutes: exports_external.number().default(0)
|
|
15777
|
-
}).default({})
|
|
16307
|
+
}).default({}),
|
|
16308
|
+
daemon: DaemonConfigSchema2
|
|
15778
16309
|
});
|
|
15779
|
-
CONFIG_DIR2 =
|
|
15780
|
-
CONFIG_PATH2 =
|
|
16310
|
+
CONFIG_DIR2 = join5(homedir5(), ".hasna", "cloud");
|
|
16311
|
+
CONFIG_PATH2 = join5(CONFIG_DIR2, "config.json");
|
|
15781
16312
|
});
|
|
15782
16313
|
|
|
15783
16314
|
// src/discover.ts
|
|
15784
|
-
var
|
|
15785
|
-
__export(
|
|
15786
|
-
isSyncExcludedTable: () =>
|
|
15787
|
-
getServiceDbPath: () =>
|
|
15788
|
-
discoverSyncableServices: () =>
|
|
15789
|
-
discoverServices: () =>
|
|
15790
|
-
SYNC_EXCLUDED_TABLE_PATTERNS: () =>
|
|
15791
|
-
KNOWN_PG_SERVICES: () =>
|
|
16315
|
+
var exports_discover2 = {};
|
|
16316
|
+
__export(exports_discover2, {
|
|
16317
|
+
isSyncExcludedTable: () => isSyncExcludedTable2,
|
|
16318
|
+
getServiceDbPath: () => getServiceDbPath2,
|
|
16319
|
+
discoverSyncableServices: () => discoverSyncableServices2,
|
|
16320
|
+
discoverServices: () => discoverServices2,
|
|
16321
|
+
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS2,
|
|
16322
|
+
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES2
|
|
15792
16323
|
});
|
|
15793
|
-
import { readdirSync as
|
|
15794
|
-
import { join as
|
|
15795
|
-
import { homedir as
|
|
15796
|
-
function
|
|
15797
|
-
return
|
|
16324
|
+
import { readdirSync as readdirSync4, existsSync as existsSync7 } from "fs";
|
|
16325
|
+
import { join as join7 } from "path";
|
|
16326
|
+
import { homedir as homedir7 } from "os";
|
|
16327
|
+
function isSyncExcludedTable2(table) {
|
|
16328
|
+
return SYNC_EXCLUDED_TABLE_PATTERNS2.some((p) => p.test(table));
|
|
15798
16329
|
}
|
|
15799
|
-
function
|
|
15800
|
-
const dataDir =
|
|
15801
|
-
if (!
|
|
16330
|
+
function discoverServices2() {
|
|
16331
|
+
const dataDir = join7(homedir7(), ".hasna");
|
|
16332
|
+
if (!existsSync7(dataDir))
|
|
15802
16333
|
return [];
|
|
15803
16334
|
try {
|
|
15804
|
-
const entries =
|
|
16335
|
+
const entries = readdirSync4(dataDir, { withFileTypes: true });
|
|
15805
16336
|
return entries.filter((e) => {
|
|
15806
16337
|
if (!e.isDirectory())
|
|
15807
16338
|
return false;
|
|
@@ -15813,37 +16344,37 @@ function discoverServices() {
|
|
|
15813
16344
|
return [];
|
|
15814
16345
|
}
|
|
15815
16346
|
}
|
|
15816
|
-
function
|
|
15817
|
-
const local =
|
|
15818
|
-
const pgSet = new Set(
|
|
16347
|
+
function discoverSyncableServices2() {
|
|
16348
|
+
const local = discoverServices2();
|
|
16349
|
+
const pgSet = new Set(KNOWN_PG_SERVICES2);
|
|
15819
16350
|
return local.filter((s) => pgSet.has(s));
|
|
15820
16351
|
}
|
|
15821
|
-
function
|
|
15822
|
-
const dataDir =
|
|
15823
|
-
if (!
|
|
16352
|
+
function getServiceDbPath2(service) {
|
|
16353
|
+
const dataDir = join7(homedir7(), ".hasna", service);
|
|
16354
|
+
if (!existsSync7(dataDir))
|
|
15824
16355
|
return null;
|
|
15825
16356
|
const candidates = [
|
|
15826
|
-
|
|
15827
|
-
|
|
15828
|
-
|
|
16357
|
+
join7(dataDir, `${service}.db`),
|
|
16358
|
+
join7(dataDir, "data.db"),
|
|
16359
|
+
join7(dataDir, "database.db")
|
|
15829
16360
|
];
|
|
15830
16361
|
try {
|
|
15831
|
-
const files =
|
|
16362
|
+
const files = readdirSync4(dataDir);
|
|
15832
16363
|
for (const f of files) {
|
|
15833
16364
|
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
15834
|
-
candidates.push(
|
|
16365
|
+
candidates.push(join7(dataDir, f));
|
|
15835
16366
|
}
|
|
15836
16367
|
}
|
|
15837
16368
|
} catch {}
|
|
15838
16369
|
for (const p of candidates) {
|
|
15839
|
-
if (
|
|
16370
|
+
if (existsSync7(p))
|
|
15840
16371
|
return p;
|
|
15841
16372
|
}
|
|
15842
16373
|
return null;
|
|
15843
16374
|
}
|
|
15844
|
-
var
|
|
15845
|
-
var
|
|
15846
|
-
|
|
16375
|
+
var KNOWN_PG_SERVICES2, SYNC_EXCLUDED_TABLE_PATTERNS2;
|
|
16376
|
+
var init_discover2 = __esm(() => {
|
|
16377
|
+
KNOWN_PG_SERVICES2 = [
|
|
15847
16378
|
"assistants",
|
|
15848
16379
|
"attachments",
|
|
15849
16380
|
"brains",
|
|
@@ -15880,116 +16411,7 @@ var init_discover = __esm(() => {
|
|
|
15880
16411
|
"todos",
|
|
15881
16412
|
"wallets"
|
|
15882
16413
|
];
|
|
15883
|
-
|
|
15884
|
-
/^sqlite_/,
|
|
15885
|
-
/_fts$/,
|
|
15886
|
-
/_fts_/,
|
|
15887
|
-
/^_sync_/,
|
|
15888
|
-
/^_pg_migrations$/
|
|
15889
|
-
];
|
|
15890
|
-
});
|
|
15891
|
-
|
|
15892
|
-
// src/discover.ts
|
|
15893
|
-
var exports_discover2 = {};
|
|
15894
|
-
__export(exports_discover2, {
|
|
15895
|
-
isSyncExcludedTable: () => isSyncExcludedTable2,
|
|
15896
|
-
getServiceDbPath: () => getServiceDbPath2,
|
|
15897
|
-
discoverSyncableServices: () => discoverSyncableServices2,
|
|
15898
|
-
discoverServices: () => discoverServices2,
|
|
15899
|
-
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS2,
|
|
15900
|
-
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES2
|
|
15901
|
-
});
|
|
15902
|
-
import { readdirSync as readdirSync4, existsSync as existsSync6 } from "fs";
|
|
15903
|
-
import { join as join6 } from "path";
|
|
15904
|
-
import { homedir as homedir6 } from "os";
|
|
15905
|
-
function isSyncExcludedTable2(table) {
|
|
15906
|
-
return SYNC_EXCLUDED_TABLE_PATTERNS2.some((p) => p.test(table));
|
|
15907
|
-
}
|
|
15908
|
-
function discoverServices2() {
|
|
15909
|
-
const dataDir = join6(homedir6(), ".hasna");
|
|
15910
|
-
if (!existsSync6(dataDir))
|
|
15911
|
-
return [];
|
|
15912
|
-
try {
|
|
15913
|
-
const entries = readdirSync4(dataDir, { withFileTypes: true });
|
|
15914
|
-
return entries.filter((e) => {
|
|
15915
|
-
if (!e.isDirectory())
|
|
15916
|
-
return false;
|
|
15917
|
-
if (e.name === "cloud" || e.name.startsWith("."))
|
|
15918
|
-
return false;
|
|
15919
|
-
return true;
|
|
15920
|
-
}).map((e) => e.name).sort();
|
|
15921
|
-
} catch {
|
|
15922
|
-
return [];
|
|
15923
|
-
}
|
|
15924
|
-
}
|
|
15925
|
-
function discoverSyncableServices2() {
|
|
15926
|
-
const local = discoverServices2();
|
|
15927
|
-
const pgSet = new Set(KNOWN_PG_SERVICES2);
|
|
15928
|
-
return local.filter((s) => pgSet.has(s));
|
|
15929
|
-
}
|
|
15930
|
-
function getServiceDbPath2(service) {
|
|
15931
|
-
const dataDir = join6(homedir6(), ".hasna", service);
|
|
15932
|
-
if (!existsSync6(dataDir))
|
|
15933
|
-
return null;
|
|
15934
|
-
const candidates = [
|
|
15935
|
-
join6(dataDir, `${service}.db`),
|
|
15936
|
-
join6(dataDir, "data.db"),
|
|
15937
|
-
join6(dataDir, "database.db")
|
|
15938
|
-
];
|
|
15939
|
-
try {
|
|
15940
|
-
const files = readdirSync4(dataDir);
|
|
15941
|
-
for (const f of files) {
|
|
15942
|
-
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
15943
|
-
candidates.push(join6(dataDir, f));
|
|
15944
|
-
}
|
|
15945
|
-
}
|
|
15946
|
-
} catch {}
|
|
15947
|
-
for (const p of candidates) {
|
|
15948
|
-
if (existsSync6(p))
|
|
15949
|
-
return p;
|
|
15950
|
-
}
|
|
15951
|
-
return null;
|
|
15952
|
-
}
|
|
15953
|
-
var KNOWN_PG_SERVICES2, SYNC_EXCLUDED_TABLE_PATTERNS2;
|
|
15954
|
-
var init_discover2 = __esm(() => {
|
|
15955
|
-
KNOWN_PG_SERVICES2 = [
|
|
15956
|
-
"assistants",
|
|
15957
|
-
"attachments",
|
|
15958
|
-
"brains",
|
|
15959
|
-
"configs",
|
|
15960
|
-
"connectors",
|
|
15961
|
-
"contacts",
|
|
15962
|
-
"context",
|
|
15963
|
-
"conversations",
|
|
15964
|
-
"crawl",
|
|
15965
|
-
"deployment",
|
|
15966
|
-
"economy",
|
|
15967
|
-
"emails",
|
|
15968
|
-
"files",
|
|
15969
|
-
"hooks",
|
|
15970
|
-
"implementations",
|
|
15971
|
-
"logs",
|
|
15972
|
-
"mcps",
|
|
15973
|
-
"mementos",
|
|
15974
|
-
"microservices",
|
|
15975
|
-
"predictor",
|
|
15976
|
-
"prompts",
|
|
15977
|
-
"recordings",
|
|
15978
|
-
"researcher",
|
|
15979
|
-
"sandboxes",
|
|
15980
|
-
"search",
|
|
15981
|
-
"secrets",
|
|
15982
|
-
"sessions",
|
|
15983
|
-
"signatures",
|
|
15984
|
-
"skills",
|
|
15985
|
-
"telephony",
|
|
15986
|
-
"terminal",
|
|
15987
|
-
"testers",
|
|
15988
|
-
"tickets",
|
|
15989
|
-
"todos",
|
|
15990
|
-
"wallets"
|
|
15991
|
-
];
|
|
15992
|
-
SYNC_EXCLUDED_TABLE_PATTERNS2 = [
|
|
16414
|
+
SYNC_EXCLUDED_TABLE_PATTERNS2 = [
|
|
15993
16415
|
/^sqlite_/,
|
|
15994
16416
|
/_fts$/,
|
|
15995
16417
|
/_fts_/,
|
|
@@ -16081,7 +16503,7 @@ async function migrateService(service, connectionString) {
|
|
|
16081
16503
|
return applyPgMigrations(connStr, migrations, service);
|
|
16082
16504
|
}
|
|
16083
16505
|
async function migrateAllServices() {
|
|
16084
|
-
const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (
|
|
16506
|
+
const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (init_discover(), exports_discover));
|
|
16085
16507
|
const services = discoverServices3();
|
|
16086
16508
|
const results = [];
|
|
16087
16509
|
for (const service of services) {
|
|
@@ -16121,7 +16543,7 @@ async function ensurePgDatabase(service) {
|
|
|
16121
16543
|
}
|
|
16122
16544
|
}
|
|
16123
16545
|
async function ensureAllPgDatabases() {
|
|
16124
|
-
const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (
|
|
16546
|
+
const { discoverServices: discoverServices3 } = await Promise.resolve().then(() => (init_discover(), exports_discover));
|
|
16125
16547
|
const services = discoverServices3();
|
|
16126
16548
|
const results = [];
|
|
16127
16549
|
for (const service of services) {
|
|
@@ -20781,6 +21203,7 @@ config(en_default2());
|
|
|
20781
21203
|
|
|
20782
21204
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
|
|
20783
21205
|
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
21206
|
+
var DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26";
|
|
20784
21207
|
var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05", "2024-10-07"];
|
|
20785
21208
|
var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
20786
21209
|
var JSONRPC_VERSION = "2.0";
|
|
@@ -20953,6 +21376,7 @@ var InitializeRequestSchema = RequestSchema.extend({
|
|
|
20953
21376
|
method: literal("initialize"),
|
|
20954
21377
|
params: InitializeRequestParamsSchema
|
|
20955
21378
|
});
|
|
21379
|
+
var isInitializeRequest = (value) => InitializeRequestSchema.safeParse(value).success;
|
|
20956
21380
|
var ServerCapabilitiesSchema = object2({
|
|
20957
21381
|
experimental: record(string2(), AssertObjectSchema).optional(),
|
|
20958
21382
|
logging: AssertObjectSchema.optional(),
|
|
@@ -25133,9 +25557,25 @@ init_external();
|
|
|
25133
25557
|
init_zod();
|
|
25134
25558
|
init_adapter();
|
|
25135
25559
|
init_dotfile();
|
|
25136
|
-
|
|
25137
|
-
import {
|
|
25138
|
-
import {
|
|
25560
|
+
init_machines();
|
|
25561
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
25562
|
+
import { homedir as homedir4 } from "os";
|
|
25563
|
+
import { join as join4 } from "path";
|
|
25564
|
+
var DaemonConfigSchema = exports_external.object({
|
|
25565
|
+
enabled: exports_external.boolean().default(false),
|
|
25566
|
+
paused: exports_external.boolean().default(false),
|
|
25567
|
+
watch_interval_seconds: exports_external.number().int().positive().default(5),
|
|
25568
|
+
pull_interval_seconds: exports_external.number().int().positive().default(60),
|
|
25569
|
+
push_debounce_seconds: exports_external.number().int().positive().default(5),
|
|
25570
|
+
conflict_strategy: exports_external.enum(["newest-wins", "local-wins", "remote-wins"]).default("newest-wins"),
|
|
25571
|
+
services: exports_external.array(exports_external.string()).default([]),
|
|
25572
|
+
table_intervals: exports_external.record(exports_external.string(), exports_external.record(exports_external.string(), exports_external.number().int().positive())).default({}),
|
|
25573
|
+
file_rules: exports_external.array(exports_external.object({
|
|
25574
|
+
path: exports_external.string(),
|
|
25575
|
+
interval_seconds: exports_external.number().int().positive().default(30),
|
|
25576
|
+
enabled: exports_external.boolean().default(true)
|
|
25577
|
+
})).default([])
|
|
25578
|
+
}).default({});
|
|
25139
25579
|
var CloudConfigSchema = exports_external.object({
|
|
25140
25580
|
rds: exports_external.object({
|
|
25141
25581
|
host: exports_external.string().default(""),
|
|
@@ -25149,12 +25589,13 @@ var CloudConfigSchema = exports_external.object({
|
|
|
25149
25589
|
feedback_endpoint: exports_external.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
25150
25590
|
sync: exports_external.object({
|
|
25151
25591
|
schedule_minutes: exports_external.number().default(0)
|
|
25152
|
-
}).default({})
|
|
25592
|
+
}).default({}),
|
|
25593
|
+
daemon: DaemonConfigSchema
|
|
25153
25594
|
});
|
|
25154
|
-
var CONFIG_DIR =
|
|
25155
|
-
var CONFIG_PATH =
|
|
25595
|
+
var CONFIG_DIR = join4(homedir4(), ".hasna", "cloud");
|
|
25596
|
+
var CONFIG_PATH = join4(CONFIG_DIR, "config.json");
|
|
25156
25597
|
function getCloudConfig() {
|
|
25157
|
-
if (!
|
|
25598
|
+
if (!existsSync4(CONFIG_PATH)) {
|
|
25158
25599
|
return CloudConfigSchema.parse({});
|
|
25159
25600
|
}
|
|
25160
25601
|
try {
|
|
@@ -25182,10 +25623,10 @@ function createDatabase(options) {
|
|
|
25182
25623
|
const mode = options.mode ?? config2.mode;
|
|
25183
25624
|
if (mode === "cloud") {
|
|
25184
25625
|
const connStr = options.pgConnectionString ?? getConnectionString(options.service);
|
|
25185
|
-
return new PgAdapter(connStr);
|
|
25626
|
+
return createMachineAwareAdapter(new PgAdapter(connStr));
|
|
25186
25627
|
}
|
|
25187
25628
|
const dbPath = options.sqlitePath ?? getDbPath(options.service);
|
|
25188
|
-
return new SqliteAdapter(dbPath);
|
|
25629
|
+
return createMachineAwareAdapter(new SqliteAdapter(dbPath));
|
|
25189
25630
|
}
|
|
25190
25631
|
|
|
25191
25632
|
// src/sync.ts
|
|
@@ -25364,6 +25805,9 @@ async function ensureTablesExist(source, target, tables) {
|
|
|
25364
25805
|
}
|
|
25365
25806
|
async function filterColumnsForTarget(target, table, sourceColumns) {
|
|
25366
25807
|
try {
|
|
25808
|
+
if (sourceColumns.includes("machine_id") && table !== "machines") {
|
|
25809
|
+
await ensureMachineIdColumnInTarget(target, table);
|
|
25810
|
+
}
|
|
25367
25811
|
if (!isAsyncAdapter(target)) {
|
|
25368
25812
|
const colInfo = target.all(`PRAGMA table_info("${table}")`);
|
|
25369
25813
|
if (Array.isArray(colInfo) && colInfo.length > 0) {
|
|
@@ -25386,6 +25830,22 @@ async function filterColumnsForTarget(target, table, sourceColumns) {
|
|
|
25386
25830
|
} catch {}
|
|
25387
25831
|
return sourceColumns;
|
|
25388
25832
|
}
|
|
25833
|
+
async function ensureMachineIdColumnInTarget(target, table) {
|
|
25834
|
+
if (!isAsyncAdapter(target)) {
|
|
25835
|
+
const colInfo2 = target.all(`PRAGMA table_info("${table}")`);
|
|
25836
|
+
const hasMachineId = Array.isArray(colInfo2) ? colInfo2.some((column) => column.name === "machine_id") : false;
|
|
25837
|
+
if (!hasMachineId) {
|
|
25838
|
+
target.exec(`ALTER TABLE "${table}" ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
25839
|
+
}
|
|
25840
|
+
return;
|
|
25841
|
+
}
|
|
25842
|
+
const colInfo = await target.all(`SELECT column_name
|
|
25843
|
+
FROM information_schema.columns
|
|
25844
|
+
WHERE table_schema = 'public' AND table_name = '${table}' AND column_name = 'machine_id'`);
|
|
25845
|
+
if (colInfo.length === 0) {
|
|
25846
|
+
await target.exec(`ALTER TABLE "${table}" ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
25847
|
+
}
|
|
25848
|
+
}
|
|
25389
25849
|
async function syncTransfer(source, target, options, _direction) {
|
|
25390
25850
|
const {
|
|
25391
25851
|
tables,
|
|
@@ -25621,7 +26081,7 @@ async function listPgTables(db) {
|
|
|
25621
26081
|
|
|
25622
26082
|
// src/feedback.ts
|
|
25623
26083
|
init_config();
|
|
25624
|
-
import { hostname as
|
|
26084
|
+
import { hostname as hostname3 } from "os";
|
|
25625
26085
|
var FEEDBACK_TABLE_SQL = `
|
|
25626
26086
|
CREATE TABLE IF NOT EXISTS feedback (
|
|
25627
26087
|
id TEXT PRIMARY KEY,
|
|
@@ -25639,7 +26099,7 @@ function saveFeedback(db, feedback) {
|
|
|
25639
26099
|
ensureFeedbackTable(db);
|
|
25640
26100
|
const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
25641
26101
|
const now = new Date().toISOString();
|
|
25642
|
-
const machineId = feedback.machine_id ??
|
|
26102
|
+
const machineId = feedback.machine_id ?? hostname3();
|
|
25643
26103
|
db.run(`INSERT INTO feedback (id, service, version, message, email, machine_id, created_at)
|
|
25644
26104
|
VALUES (?, ?, ?, ?, ?, ?, ?)`, id, feedback.service, feedback.version ?? "", feedback.message, feedback.email ?? "", machineId, feedback.created_at ?? now);
|
|
25645
26105
|
return id;
|
|
@@ -25647,7 +26107,7 @@ function saveFeedback(db, feedback) {
|
|
|
25647
26107
|
async function sendFeedback(feedback, db) {
|
|
25648
26108
|
const config2 = getCloudConfig2();
|
|
25649
26109
|
const id = feedback.id ?? Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
25650
|
-
const machineId = feedback.machine_id ??
|
|
26110
|
+
const machineId = feedback.machine_id ?? hostname3();
|
|
25651
26111
|
const now = new Date().toISOString();
|
|
25652
26112
|
const payload = {
|
|
25653
26113
|
id,
|
|
@@ -25687,22 +26147,22 @@ async function sendFeedback(feedback, db) {
|
|
|
25687
26147
|
|
|
25688
26148
|
// src/dotfile.ts
|
|
25689
26149
|
import {
|
|
25690
|
-
existsSync as
|
|
26150
|
+
existsSync as existsSync6,
|
|
25691
26151
|
mkdirSync as mkdirSync4,
|
|
25692
|
-
readdirSync as
|
|
26152
|
+
readdirSync as readdirSync3,
|
|
25693
26153
|
copyFileSync as copyFileSync2
|
|
25694
26154
|
} from "fs";
|
|
25695
|
-
import { homedir as
|
|
25696
|
-
import { join as
|
|
25697
|
-
var HASNA_DIR2 =
|
|
26155
|
+
import { homedir as homedir6 } from "os";
|
|
26156
|
+
import { join as join6, relative as relative2 } from "path";
|
|
26157
|
+
var HASNA_DIR2 = join6(homedir6(), ".hasna");
|
|
25698
26158
|
function getDataDir2(serviceName) {
|
|
25699
|
-
const dir =
|
|
26159
|
+
const dir = join6(HASNA_DIR2, serviceName);
|
|
25700
26160
|
mkdirSync4(dir, { recursive: true });
|
|
25701
26161
|
return dir;
|
|
25702
26162
|
}
|
|
25703
26163
|
function getDbPath2(serviceName) {
|
|
25704
26164
|
const dir = getDataDir2(serviceName);
|
|
25705
|
-
return
|
|
26165
|
+
return join6(dir, `${serviceName}.db`);
|
|
25706
26166
|
}
|
|
25707
26167
|
|
|
25708
26168
|
// src/adapter.ts
|
|
@@ -25824,282 +26284,1509 @@ class PgAdapterAsync2 {
|
|
|
25824
26284
|
}
|
|
25825
26285
|
}
|
|
25826
26286
|
|
|
25827
|
-
// src/mcp/
|
|
25828
|
-
|
|
25829
|
-
|
|
25830
|
-
|
|
25831
|
-
}
|
|
25832
|
-
|
|
25833
|
-
|
|
25834
|
-
|
|
25835
|
-
|
|
25836
|
-
|
|
25837
|
-
|
|
25838
|
-
|
|
25839
|
-
`SSL: ${config2.rds.ssl}`,
|
|
25840
|
-
`Auto-sync: ${config2.auto_sync_interval_minutes ? `${config2.auto_sync_interval_minutes} min` : "disabled"}`
|
|
25841
|
-
];
|
|
25842
|
-
if (config2.rds.host && config2.rds.username) {
|
|
25843
|
-
try {
|
|
25844
|
-
const connStr = getConnectionString("postgres");
|
|
25845
|
-
const pg2 = new PgAdapterAsync2(connStr);
|
|
25846
|
-
await pg2.get("SELECT 1 as ok");
|
|
25847
|
-
lines.push("PostgreSQL: connected");
|
|
25848
|
-
await pg2.close();
|
|
25849
|
-
} catch (err) {
|
|
25850
|
-
lines.push(`PostgreSQL: connection failed \u2014 ${err?.message}`);
|
|
25851
|
-
}
|
|
26287
|
+
// src/mcp/http.ts
|
|
26288
|
+
import { createServer } from "node:http";
|
|
26289
|
+
|
|
26290
|
+
// node_modules/@hono/node-server/dist/index.mjs
|
|
26291
|
+
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
26292
|
+
import { Http2ServerRequest } from "http2";
|
|
26293
|
+
import { Readable } from "stream";
|
|
26294
|
+
import crypto2 from "crypto";
|
|
26295
|
+
var RequestError = class extends Error {
|
|
26296
|
+
constructor(message, options) {
|
|
26297
|
+
super(message, options);
|
|
26298
|
+
this.name = "RequestError";
|
|
25852
26299
|
}
|
|
25853
|
-
|
|
25854
|
-
|
|
25855
|
-
|
|
25856
|
-
|
|
25857
|
-
service: exports_external.string().describe("Service name"),
|
|
25858
|
-
tables: exports_external.string().optional().describe("Comma-separated table names (default: all)")
|
|
25859
|
-
}, async ({ service, tables: tablesStr }) => {
|
|
25860
|
-
const config2 = getCloudConfig();
|
|
25861
|
-
if (config2.mode === "local") {
|
|
25862
|
-
return {
|
|
25863
|
-
content: [
|
|
25864
|
-
{
|
|
25865
|
-
type: "text",
|
|
25866
|
-
text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`."
|
|
25867
|
-
}
|
|
25868
|
-
],
|
|
25869
|
-
isError: true
|
|
25870
|
-
};
|
|
26300
|
+
};
|
|
26301
|
+
var toRequestError = (e) => {
|
|
26302
|
+
if (e instanceof RequestError) {
|
|
26303
|
+
return e;
|
|
25871
26304
|
}
|
|
25872
|
-
|
|
25873
|
-
|
|
25874
|
-
|
|
25875
|
-
|
|
25876
|
-
|
|
25877
|
-
|
|
25878
|
-
|
|
25879
|
-
} else {
|
|
25880
|
-
tableList = listSqliteTables(local);
|
|
25881
|
-
}
|
|
25882
|
-
const results = await syncPush(local, cloud, { tables: tableList });
|
|
25883
|
-
local.close();
|
|
25884
|
-
await cloud.close();
|
|
25885
|
-
const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
25886
|
-
const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
|
|
25887
|
-
const lines = [
|
|
25888
|
-
`Pushed ${tableList.length} table(s): ${totalWritten} rows, ${totalErrors} errors.`
|
|
25889
|
-
];
|
|
25890
|
-
for (const r of results) {
|
|
25891
|
-
lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
|
|
25892
|
-
for (const e of r.errors) {
|
|
25893
|
-
lines.push(` ERROR: ${e}`);
|
|
26305
|
+
return new RequestError(e.message, { cause: e });
|
|
26306
|
+
};
|
|
26307
|
+
var GlobalRequest = global.Request;
|
|
26308
|
+
var Request = class extends GlobalRequest {
|
|
26309
|
+
constructor(input, options) {
|
|
26310
|
+
if (typeof input === "object" && getRequestCache in input) {
|
|
26311
|
+
input = input[getRequestCache]();
|
|
25894
26312
|
}
|
|
26313
|
+
if (typeof options?.body?.getReader !== "undefined") {
|
|
26314
|
+
options.duplex ??= "half";
|
|
26315
|
+
}
|
|
26316
|
+
super(input, options);
|
|
25895
26317
|
}
|
|
25896
|
-
|
|
25897
|
-
|
|
25898
|
-
|
|
25899
|
-
|
|
25900
|
-
|
|
25901
|
-
|
|
25902
|
-
|
|
25903
|
-
|
|
25904
|
-
|
|
25905
|
-
|
|
25906
|
-
|
|
25907
|
-
|
|
25908
|
-
|
|
25909
|
-
|
|
26318
|
+
};
|
|
26319
|
+
var newHeadersFromIncoming = (incoming) => {
|
|
26320
|
+
const headerRecord = [];
|
|
26321
|
+
const rawHeaders = incoming.rawHeaders;
|
|
26322
|
+
for (let i = 0;i < rawHeaders.length; i += 2) {
|
|
26323
|
+
const { [i]: key, [i + 1]: value } = rawHeaders;
|
|
26324
|
+
if (key.charCodeAt(0) !== 58) {
|
|
26325
|
+
headerRecord.push([key, value]);
|
|
26326
|
+
}
|
|
26327
|
+
}
|
|
26328
|
+
return new Headers(headerRecord);
|
|
26329
|
+
};
|
|
26330
|
+
var wrapBodyStream = Symbol("wrapBodyStream");
|
|
26331
|
+
var newRequestFromIncoming = (method, url, headers, incoming, abortController) => {
|
|
26332
|
+
const init = {
|
|
26333
|
+
method,
|
|
26334
|
+
headers,
|
|
26335
|
+
signal: abortController.signal
|
|
26336
|
+
};
|
|
26337
|
+
if (method === "TRACE") {
|
|
26338
|
+
init.method = "GET";
|
|
26339
|
+
const req = new Request(url, init);
|
|
26340
|
+
Object.defineProperty(req, "method", {
|
|
26341
|
+
get() {
|
|
26342
|
+
return "TRACE";
|
|
26343
|
+
}
|
|
26344
|
+
});
|
|
26345
|
+
return req;
|
|
26346
|
+
}
|
|
26347
|
+
if (!(method === "GET" || method === "HEAD")) {
|
|
26348
|
+
if ("rawBody" in incoming && incoming.rawBody instanceof Buffer) {
|
|
26349
|
+
init.body = new ReadableStream({
|
|
26350
|
+
start(controller) {
|
|
26351
|
+
controller.enqueue(incoming.rawBody);
|
|
26352
|
+
controller.close();
|
|
25910
26353
|
}
|
|
25911
|
-
|
|
25912
|
-
|
|
25913
|
-
|
|
26354
|
+
});
|
|
26355
|
+
} else if (incoming[wrapBodyStream]) {
|
|
26356
|
+
let reader;
|
|
26357
|
+
init.body = new ReadableStream({
|
|
26358
|
+
async pull(controller) {
|
|
26359
|
+
try {
|
|
26360
|
+
reader ||= Readable.toWeb(incoming).getReader();
|
|
26361
|
+
const { done, value } = await reader.read();
|
|
26362
|
+
if (done) {
|
|
26363
|
+
controller.close();
|
|
26364
|
+
} else {
|
|
26365
|
+
controller.enqueue(value);
|
|
26366
|
+
}
|
|
26367
|
+
} catch (error2) {
|
|
26368
|
+
controller.error(error2);
|
|
26369
|
+
}
|
|
26370
|
+
}
|
|
26371
|
+
});
|
|
26372
|
+
} else {
|
|
26373
|
+
init.body = Readable.toWeb(incoming);
|
|
26374
|
+
}
|
|
25914
26375
|
}
|
|
25915
|
-
|
|
25916
|
-
|
|
25917
|
-
|
|
25918
|
-
|
|
25919
|
-
|
|
25920
|
-
|
|
25921
|
-
|
|
25922
|
-
|
|
26376
|
+
return new Request(url, init);
|
|
26377
|
+
};
|
|
26378
|
+
var getRequestCache = Symbol("getRequestCache");
|
|
26379
|
+
var requestCache = Symbol("requestCache");
|
|
26380
|
+
var incomingKey = Symbol("incomingKey");
|
|
26381
|
+
var urlKey = Symbol("urlKey");
|
|
26382
|
+
var headersKey = Symbol("headersKey");
|
|
26383
|
+
var abortControllerKey = Symbol("abortControllerKey");
|
|
26384
|
+
var getAbortController = Symbol("getAbortController");
|
|
26385
|
+
var requestPrototype = {
|
|
26386
|
+
get method() {
|
|
26387
|
+
return this[incomingKey].method || "GET";
|
|
26388
|
+
},
|
|
26389
|
+
get url() {
|
|
26390
|
+
return this[urlKey];
|
|
26391
|
+
},
|
|
26392
|
+
get headers() {
|
|
26393
|
+
return this[headersKey] ||= newHeadersFromIncoming(this[incomingKey]);
|
|
26394
|
+
},
|
|
26395
|
+
[getAbortController]() {
|
|
26396
|
+
this[getRequestCache]();
|
|
26397
|
+
return this[abortControllerKey];
|
|
26398
|
+
},
|
|
26399
|
+
[getRequestCache]() {
|
|
26400
|
+
this[abortControllerKey] ||= new AbortController;
|
|
26401
|
+
return this[requestCache] ||= newRequestFromIncoming(this.method, this[urlKey], this.headers, this[incomingKey], this[abortControllerKey]);
|
|
26402
|
+
}
|
|
26403
|
+
};
|
|
26404
|
+
[
|
|
26405
|
+
"body",
|
|
26406
|
+
"bodyUsed",
|
|
26407
|
+
"cache",
|
|
26408
|
+
"credentials",
|
|
26409
|
+
"destination",
|
|
26410
|
+
"integrity",
|
|
26411
|
+
"mode",
|
|
26412
|
+
"redirect",
|
|
26413
|
+
"referrer",
|
|
26414
|
+
"referrerPolicy",
|
|
26415
|
+
"signal",
|
|
26416
|
+
"keepalive"
|
|
26417
|
+
].forEach((k) => {
|
|
26418
|
+
Object.defineProperty(requestPrototype, k, {
|
|
26419
|
+
get() {
|
|
26420
|
+
return this[getRequestCache]()[k];
|
|
26421
|
+
}
|
|
26422
|
+
});
|
|
26423
|
+
});
|
|
26424
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
26425
|
+
Object.defineProperty(requestPrototype, k, {
|
|
26426
|
+
value: function() {
|
|
26427
|
+
return this[getRequestCache]()[k]();
|
|
26428
|
+
}
|
|
26429
|
+
});
|
|
26430
|
+
});
|
|
26431
|
+
Object.setPrototypeOf(requestPrototype, Request.prototype);
|
|
26432
|
+
var newRequest = (incoming, defaultHostname) => {
|
|
26433
|
+
const req = Object.create(requestPrototype);
|
|
26434
|
+
req[incomingKey] = incoming;
|
|
26435
|
+
const incomingUrl = incoming.url || "";
|
|
26436
|
+
if (incomingUrl[0] !== "/" && (incomingUrl.startsWith("http://") || incomingUrl.startsWith("https://"))) {
|
|
26437
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
26438
|
+
throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
|
|
26439
|
+
}
|
|
25923
26440
|
try {
|
|
25924
|
-
|
|
25925
|
-
|
|
25926
|
-
|
|
25927
|
-
|
|
25928
|
-
return {
|
|
25929
|
-
content: [
|
|
25930
|
-
{ type: "text", text: "Error: failed to list tables from cloud." }
|
|
25931
|
-
],
|
|
25932
|
-
isError: true
|
|
25933
|
-
};
|
|
26441
|
+
const url2 = new URL(incomingUrl);
|
|
26442
|
+
req[urlKey] = url2.href;
|
|
26443
|
+
} catch (e) {
|
|
26444
|
+
throw new RequestError("Invalid absolute URL", { cause: e });
|
|
25934
26445
|
}
|
|
26446
|
+
return req;
|
|
25935
26447
|
}
|
|
25936
|
-
const
|
|
25937
|
-
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
|
|
25941
|
-
|
|
25942
|
-
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
|
|
25946
|
-
for (const e of r.errors) {
|
|
25947
|
-
lines.push(` ERROR: ${e}`);
|
|
26448
|
+
const host = (incoming instanceof Http2ServerRequest ? incoming.authority : incoming.headers.host) || defaultHostname;
|
|
26449
|
+
if (!host) {
|
|
26450
|
+
throw new RequestError("Missing host header");
|
|
26451
|
+
}
|
|
26452
|
+
let scheme;
|
|
26453
|
+
if (incoming instanceof Http2ServerRequest) {
|
|
26454
|
+
scheme = incoming.scheme;
|
|
26455
|
+
if (!(scheme === "http" || scheme === "https")) {
|
|
26456
|
+
throw new RequestError("Unsupported scheme");
|
|
25948
26457
|
}
|
|
26458
|
+
} else {
|
|
26459
|
+
scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
|
|
25949
26460
|
}
|
|
25950
|
-
|
|
25951
|
-
|
|
25952
|
-
|
|
25953
|
-
server.tool("send_feedback", "Send feedback for a service", {
|
|
25954
|
-
service: exports_external.string().describe("Service name"),
|
|
25955
|
-
message: exports_external.string().describe("Feedback message"),
|
|
25956
|
-
email: exports_external.string().optional().describe("Contact email"),
|
|
25957
|
-
version: exports_external.string().optional().describe("Service version")
|
|
25958
|
-
}, async ({ service, message, email: email2, version: version2 }) => {
|
|
25959
|
-
const db = createDatabase({ service: "cloud" });
|
|
25960
|
-
const result = await sendFeedback({ service, message, email: email2, version: version2 }, db);
|
|
25961
|
-
db.close();
|
|
25962
|
-
if (result.sent) {
|
|
25963
|
-
return {
|
|
25964
|
-
content: [
|
|
25965
|
-
{
|
|
25966
|
-
type: "text",
|
|
25967
|
-
text: `Feedback sent successfully (id: ${result.id})`
|
|
25968
|
-
}
|
|
25969
|
-
]
|
|
25970
|
-
};
|
|
26461
|
+
const url = new URL(`${scheme}://${host}${incomingUrl}`);
|
|
26462
|
+
if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
|
|
26463
|
+
throw new RequestError("Invalid host header");
|
|
25971
26464
|
}
|
|
25972
|
-
|
|
25973
|
-
|
|
25974
|
-
|
|
25975
|
-
|
|
25976
|
-
|
|
26465
|
+
req[urlKey] = url.href;
|
|
26466
|
+
return req;
|
|
26467
|
+
};
|
|
26468
|
+
var responseCache = Symbol("responseCache");
|
|
26469
|
+
var getResponseCache = Symbol("getResponseCache");
|
|
26470
|
+
var cacheKey = Symbol("cache");
|
|
26471
|
+
var GlobalResponse = global.Response;
|
|
26472
|
+
var Response2 = class _Response {
|
|
26473
|
+
#body;
|
|
26474
|
+
#init;
|
|
26475
|
+
[getResponseCache]() {
|
|
26476
|
+
delete this[cacheKey];
|
|
26477
|
+
return this[responseCache] ||= new GlobalResponse(this.#body, this.#init);
|
|
26478
|
+
}
|
|
26479
|
+
constructor(body, init) {
|
|
26480
|
+
let headers;
|
|
26481
|
+
this.#body = body;
|
|
26482
|
+
if (init instanceof _Response) {
|
|
26483
|
+
const cachedGlobalResponse = init[responseCache];
|
|
26484
|
+
if (cachedGlobalResponse) {
|
|
26485
|
+
this.#init = cachedGlobalResponse;
|
|
26486
|
+
this[getResponseCache]();
|
|
26487
|
+
return;
|
|
26488
|
+
} else {
|
|
26489
|
+
this.#init = init.#init;
|
|
26490
|
+
headers = new Headers(init.#init.headers);
|
|
25977
26491
|
}
|
|
25978
|
-
|
|
25979
|
-
|
|
26492
|
+
} else {
|
|
26493
|
+
this.#init = init;
|
|
26494
|
+
}
|
|
26495
|
+
if (typeof body === "string" || typeof body?.getReader !== "undefined" || body instanceof Blob || body instanceof Uint8Array) {
|
|
26496
|
+
this[cacheKey] = [init?.status || 200, body, headers || init?.headers];
|
|
26497
|
+
}
|
|
26498
|
+
}
|
|
26499
|
+
get headers() {
|
|
26500
|
+
const cache = this[cacheKey];
|
|
26501
|
+
if (cache) {
|
|
26502
|
+
if (!(cache[2] instanceof Headers)) {
|
|
26503
|
+
cache[2] = new Headers(cache[2] || { "content-type": "text/plain; charset=UTF-8" });
|
|
26504
|
+
}
|
|
26505
|
+
return cache[2];
|
|
26506
|
+
}
|
|
26507
|
+
return this[getResponseCache]().headers;
|
|
26508
|
+
}
|
|
26509
|
+
get status() {
|
|
26510
|
+
return this[cacheKey]?.[0] ?? this[getResponseCache]().status;
|
|
26511
|
+
}
|
|
26512
|
+
get ok() {
|
|
26513
|
+
const status = this.status;
|
|
26514
|
+
return status >= 200 && status < 300;
|
|
26515
|
+
}
|
|
26516
|
+
};
|
|
26517
|
+
["body", "bodyUsed", "redirected", "statusText", "trailers", "type", "url"].forEach((k) => {
|
|
26518
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
26519
|
+
get() {
|
|
26520
|
+
return this[getResponseCache]()[k];
|
|
26521
|
+
}
|
|
26522
|
+
});
|
|
25980
26523
|
});
|
|
25981
|
-
|
|
25982
|
-
|
|
25983
|
-
|
|
25984
|
-
|
|
25985
|
-
|
|
25986
|
-
|
|
25987
|
-
|
|
25988
|
-
|
|
25989
|
-
|
|
26524
|
+
["arrayBuffer", "blob", "clone", "formData", "json", "text"].forEach((k) => {
|
|
26525
|
+
Object.defineProperty(Response2.prototype, k, {
|
|
26526
|
+
value: function() {
|
|
26527
|
+
return this[getResponseCache]()[k]();
|
|
26528
|
+
}
|
|
26529
|
+
});
|
|
26530
|
+
});
|
|
26531
|
+
Object.setPrototypeOf(Response2, GlobalResponse);
|
|
26532
|
+
Object.setPrototypeOf(Response2.prototype, GlobalResponse.prototype);
|
|
26533
|
+
async function readWithoutBlocking(readPromise) {
|
|
26534
|
+
return Promise.race([readPromise, Promise.resolve().then(() => Promise.resolve(undefined))]);
|
|
26535
|
+
}
|
|
26536
|
+
function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromise) {
|
|
26537
|
+
const cancel = (error2) => {
|
|
26538
|
+
reader.cancel(error2).catch(() => {});
|
|
26539
|
+
};
|
|
26540
|
+
writable.on("close", cancel);
|
|
26541
|
+
writable.on("error", cancel);
|
|
26542
|
+
(currentReadPromise ?? reader.read()).then(flow, handleStreamError);
|
|
26543
|
+
return reader.closed.finally(() => {
|
|
26544
|
+
writable.off("close", cancel);
|
|
26545
|
+
writable.off("error", cancel);
|
|
26546
|
+
});
|
|
26547
|
+
function handleStreamError(error2) {
|
|
26548
|
+
if (error2) {
|
|
26549
|
+
writable.destroy(error2);
|
|
26550
|
+
}
|
|
25990
26551
|
}
|
|
25991
|
-
|
|
25992
|
-
|
|
25993
|
-
|
|
25994
|
-
|
|
25995
|
-
let grandErrors = 0;
|
|
25996
|
-
for (const service of services) {
|
|
26552
|
+
function onDrain() {
|
|
26553
|
+
reader.read().then(flow, handleStreamError);
|
|
26554
|
+
}
|
|
26555
|
+
function flow({ done, value }) {
|
|
25997
26556
|
try {
|
|
25998
|
-
|
|
25999
|
-
|
|
26000
|
-
|
|
26001
|
-
|
|
26002
|
-
let tableList;
|
|
26003
|
-
if (direction === "push") {
|
|
26004
|
-
tableList = listSqliteTables(local).filter((t) => !isSyncExcludedTable3(t));
|
|
26557
|
+
if (done) {
|
|
26558
|
+
writable.end();
|
|
26559
|
+
} else if (!writable.write(value)) {
|
|
26560
|
+
writable.once("drain", onDrain);
|
|
26005
26561
|
} else {
|
|
26006
|
-
|
|
26007
|
-
tableList = (await listPgTables(cloud)).filter((t) => !isSyncExcludedTable3(t));
|
|
26008
|
-
} catch {
|
|
26009
|
-
local.close();
|
|
26010
|
-
await cloud.close();
|
|
26011
|
-
continue;
|
|
26012
|
-
}
|
|
26013
|
-
}
|
|
26014
|
-
if (tableList.length === 0) {
|
|
26015
|
-
local.close();
|
|
26016
|
-
await cloud.close();
|
|
26017
|
-
continue;
|
|
26562
|
+
return reader.read().then(flow, handleStreamError);
|
|
26018
26563
|
}
|
|
26019
|
-
|
|
26020
|
-
|
|
26021
|
-
await cloud.close();
|
|
26022
|
-
const written = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
26023
|
-
const errors4 = results.reduce((s, r) => s + r.errors.length, 0);
|
|
26024
|
-
grandTotal += written;
|
|
26025
|
-
grandErrors += errors4;
|
|
26026
|
-
if (written > 0 || errors4 > 0) {
|
|
26027
|
-
lines.push(` ${service}: ${written} rows${errors4 > 0 ? `, ${errors4} errors` : ""}`);
|
|
26028
|
-
}
|
|
26029
|
-
} catch (err) {
|
|
26030
|
-
grandErrors++;
|
|
26031
|
-
lines.push(` ${service}: ERROR \u2014 ${err?.message ?? String(err)}`);
|
|
26564
|
+
} catch (e) {
|
|
26565
|
+
handleStreamError(e);
|
|
26032
26566
|
}
|
|
26033
26567
|
}
|
|
26034
|
-
|
|
26035
|
-
|
|
26036
|
-
|
|
26037
|
-
|
|
26038
|
-
})
|
|
26039
|
-
|
|
26040
|
-
|
|
26041
|
-
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
|
-
|
|
26045
|
-
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
|
|
26049
|
-
const
|
|
26050
|
-
|
|
26051
|
-
|
|
26052
|
-
|
|
26053
|
-
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
}
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
|
|
26568
|
+
}
|
|
26569
|
+
function writeFromReadableStream(stream, writable) {
|
|
26570
|
+
if (stream.locked) {
|
|
26571
|
+
throw new TypeError("ReadableStream is locked.");
|
|
26572
|
+
} else if (writable.destroyed) {
|
|
26573
|
+
return;
|
|
26574
|
+
}
|
|
26575
|
+
return writeFromReadableStreamDefaultReader(stream.getReader(), writable);
|
|
26576
|
+
}
|
|
26577
|
+
var buildOutgoingHttpHeaders = (headers) => {
|
|
26578
|
+
const res = {};
|
|
26579
|
+
if (!(headers instanceof Headers)) {
|
|
26580
|
+
headers = new Headers(headers ?? undefined);
|
|
26581
|
+
}
|
|
26582
|
+
const cookies = [];
|
|
26583
|
+
for (const [k, v] of headers) {
|
|
26584
|
+
if (k === "set-cookie") {
|
|
26585
|
+
cookies.push(v);
|
|
26586
|
+
} else {
|
|
26587
|
+
res[k] = v;
|
|
26588
|
+
}
|
|
26589
|
+
}
|
|
26590
|
+
if (cookies.length > 0) {
|
|
26591
|
+
res["set-cookie"] = cookies;
|
|
26592
|
+
}
|
|
26593
|
+
res["content-type"] ??= "text/plain; charset=UTF-8";
|
|
26594
|
+
return res;
|
|
26595
|
+
};
|
|
26596
|
+
var X_ALREADY_SENT = "x-hono-already-sent";
|
|
26597
|
+
if (typeof global.crypto === "undefined") {
|
|
26598
|
+
global.crypto = crypto2;
|
|
26599
|
+
}
|
|
26600
|
+
var outgoingEnded = Symbol("outgoingEnded");
|
|
26601
|
+
var handleRequestError = () => new Response(null, {
|
|
26602
|
+
status: 400
|
|
26063
26603
|
});
|
|
26064
|
-
var
|
|
26065
|
-
|
|
26066
|
-
name: exports_external.string().describe("Agent name"),
|
|
26067
|
-
session_id: exports_external.string().optional().describe("Session identifier")
|
|
26068
|
-
}, async ({ name, session_id }) => {
|
|
26069
|
-
const existing = [...mcpAgentRegistry.values()].find((a) => a.name === name);
|
|
26070
|
-
if (existing) {
|
|
26071
|
-
existing.last_seen_at = new Date().toISOString();
|
|
26072
|
-
return { content: [{ type: "text", text: JSON.stringify({ agent_id: existing.id, name: existing.name, last_seen_at: existing.last_seen_at }) }] };
|
|
26073
|
-
}
|
|
26074
|
-
const id = Math.random().toString(36).slice(2, 10);
|
|
26075
|
-
const agent = { id, name, last_seen_at: new Date().toISOString() };
|
|
26076
|
-
mcpAgentRegistry.set(id, agent);
|
|
26077
|
-
return { content: [{ type: "text", text: JSON.stringify(agent) }] };
|
|
26078
|
-
});
|
|
26079
|
-
server.tool("heartbeat", "Update agent last_seen_at", {
|
|
26080
|
-
agent_id: exports_external.string().optional().describe("Agent ID (optional \u2014 updates by name if registered)")
|
|
26081
|
-
}, async () => {
|
|
26082
|
-
return { content: [{ type: "text", text: `heartbeat at ${new Date().toISOString()}` }] };
|
|
26083
|
-
});
|
|
26084
|
-
server.tool("list_agents", "List registered agents", {}, async () => {
|
|
26085
|
-
const agents = [...mcpAgentRegistry.values()];
|
|
26086
|
-
return { content: [{ type: "text", text: agents.length > 0 ? JSON.stringify(agents) : "No agents registered" }] };
|
|
26087
|
-
});
|
|
26088
|
-
server.tool("set_focus", "Set active project context", {
|
|
26089
|
-
agent_id: exports_external.string().describe("Agent ID"),
|
|
26090
|
-
project_id: exports_external.string().optional().describe("Project to focus on")
|
|
26091
|
-
}, async ({ agent_id, project_id }) => {
|
|
26092
|
-
const agent = mcpAgentRegistry.get(agent_id);
|
|
26093
|
-
if (!agent)
|
|
26094
|
-
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
26095
|
-
agent.project_id = project_id;
|
|
26096
|
-
return { content: [{ type: "text", text: project_id ? `Focus set: ${project_id}` : "Focus cleared" }] };
|
|
26604
|
+
var handleFetchError = (e) => new Response(null, {
|
|
26605
|
+
status: e instanceof Error && (e.name === "TimeoutError" || e.constructor.name === "TimeoutError") ? 504 : 500
|
|
26097
26606
|
});
|
|
26098
|
-
|
|
26099
|
-
const
|
|
26607
|
+
var handleResponseError = (e, outgoing) => {
|
|
26608
|
+
const err = e instanceof Error ? e : new Error("unknown error", { cause: e });
|
|
26609
|
+
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
26610
|
+
console.info("The user aborted a request.");
|
|
26611
|
+
} else {
|
|
26612
|
+
console.error(e);
|
|
26613
|
+
if (!outgoing.headersSent) {
|
|
26614
|
+
outgoing.writeHead(500, { "Content-Type": "text/plain" });
|
|
26615
|
+
}
|
|
26616
|
+
outgoing.end(`Error: ${err.message}`);
|
|
26617
|
+
outgoing.destroy(err);
|
|
26618
|
+
}
|
|
26619
|
+
};
|
|
26620
|
+
var flushHeaders = (outgoing) => {
|
|
26621
|
+
if ("flushHeaders" in outgoing && outgoing.writable) {
|
|
26622
|
+
outgoing.flushHeaders();
|
|
26623
|
+
}
|
|
26624
|
+
};
|
|
26625
|
+
var responseViaCache = async (res, outgoing) => {
|
|
26626
|
+
let [status, body, header] = res[cacheKey];
|
|
26627
|
+
let hasContentLength = false;
|
|
26628
|
+
if (!header) {
|
|
26629
|
+
header = { "content-type": "text/plain; charset=UTF-8" };
|
|
26630
|
+
} else if (header instanceof Headers) {
|
|
26631
|
+
hasContentLength = header.has("content-length");
|
|
26632
|
+
header = buildOutgoingHttpHeaders(header);
|
|
26633
|
+
} else if (Array.isArray(header)) {
|
|
26634
|
+
const headerObj = new Headers(header);
|
|
26635
|
+
hasContentLength = headerObj.has("content-length");
|
|
26636
|
+
header = buildOutgoingHttpHeaders(headerObj);
|
|
26637
|
+
} else {
|
|
26638
|
+
for (const key in header) {
|
|
26639
|
+
if (key.length === 14 && key.toLowerCase() === "content-length") {
|
|
26640
|
+
hasContentLength = true;
|
|
26641
|
+
break;
|
|
26642
|
+
}
|
|
26643
|
+
}
|
|
26644
|
+
}
|
|
26645
|
+
if (!hasContentLength) {
|
|
26646
|
+
if (typeof body === "string") {
|
|
26647
|
+
header["Content-Length"] = Buffer.byteLength(body);
|
|
26648
|
+
} else if (body instanceof Uint8Array) {
|
|
26649
|
+
header["Content-Length"] = body.byteLength;
|
|
26650
|
+
} else if (body instanceof Blob) {
|
|
26651
|
+
header["Content-Length"] = body.size;
|
|
26652
|
+
}
|
|
26653
|
+
}
|
|
26654
|
+
outgoing.writeHead(status, header);
|
|
26655
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
26656
|
+
outgoing.end(body);
|
|
26657
|
+
} else if (body instanceof Blob) {
|
|
26658
|
+
outgoing.end(new Uint8Array(await body.arrayBuffer()));
|
|
26659
|
+
} else {
|
|
26660
|
+
flushHeaders(outgoing);
|
|
26661
|
+
await writeFromReadableStream(body, outgoing)?.catch((e) => handleResponseError(e, outgoing));
|
|
26662
|
+
}
|
|
26663
|
+
outgoing[outgoingEnded]?.();
|
|
26664
|
+
};
|
|
26665
|
+
var isPromise = (res) => typeof res.then === "function";
|
|
26666
|
+
var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
26667
|
+
if (isPromise(res)) {
|
|
26668
|
+
if (options.errorHandler) {
|
|
26669
|
+
try {
|
|
26670
|
+
res = await res;
|
|
26671
|
+
} catch (err) {
|
|
26672
|
+
const errRes = await options.errorHandler(err);
|
|
26673
|
+
if (!errRes) {
|
|
26674
|
+
return;
|
|
26675
|
+
}
|
|
26676
|
+
res = errRes;
|
|
26677
|
+
}
|
|
26678
|
+
} else {
|
|
26679
|
+
res = await res.catch(handleFetchError);
|
|
26680
|
+
}
|
|
26681
|
+
}
|
|
26682
|
+
if (cacheKey in res) {
|
|
26683
|
+
return responseViaCache(res, outgoing);
|
|
26684
|
+
}
|
|
26685
|
+
const resHeaderRecord = buildOutgoingHttpHeaders(res.headers);
|
|
26686
|
+
if (res.body) {
|
|
26687
|
+
const reader = res.body.getReader();
|
|
26688
|
+
const values = [];
|
|
26689
|
+
let done = false;
|
|
26690
|
+
let currentReadPromise = undefined;
|
|
26691
|
+
if (resHeaderRecord["transfer-encoding"] !== "chunked") {
|
|
26692
|
+
let maxReadCount = 2;
|
|
26693
|
+
for (let i = 0;i < maxReadCount; i++) {
|
|
26694
|
+
currentReadPromise ||= reader.read();
|
|
26695
|
+
const chunk = await readWithoutBlocking(currentReadPromise).catch((e) => {
|
|
26696
|
+
console.error(e);
|
|
26697
|
+
done = true;
|
|
26698
|
+
});
|
|
26699
|
+
if (!chunk) {
|
|
26700
|
+
if (i === 1) {
|
|
26701
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
26702
|
+
maxReadCount = 3;
|
|
26703
|
+
continue;
|
|
26704
|
+
}
|
|
26705
|
+
break;
|
|
26706
|
+
}
|
|
26707
|
+
currentReadPromise = undefined;
|
|
26708
|
+
if (chunk.value) {
|
|
26709
|
+
values.push(chunk.value);
|
|
26710
|
+
}
|
|
26711
|
+
if (chunk.done) {
|
|
26712
|
+
done = true;
|
|
26713
|
+
break;
|
|
26714
|
+
}
|
|
26715
|
+
}
|
|
26716
|
+
if (done && !("content-length" in resHeaderRecord)) {
|
|
26717
|
+
resHeaderRecord["content-length"] = values.reduce((acc, value) => acc + value.length, 0);
|
|
26718
|
+
}
|
|
26719
|
+
}
|
|
26720
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
26721
|
+
values.forEach((value) => {
|
|
26722
|
+
outgoing.write(value);
|
|
26723
|
+
});
|
|
26724
|
+
if (done) {
|
|
26725
|
+
outgoing.end();
|
|
26726
|
+
} else {
|
|
26727
|
+
if (values.length === 0) {
|
|
26728
|
+
flushHeaders(outgoing);
|
|
26729
|
+
}
|
|
26730
|
+
await writeFromReadableStreamDefaultReader(reader, outgoing, currentReadPromise);
|
|
26731
|
+
}
|
|
26732
|
+
} else if (resHeaderRecord[X_ALREADY_SENT]) {} else {
|
|
26733
|
+
outgoing.writeHead(res.status, resHeaderRecord);
|
|
26734
|
+
outgoing.end();
|
|
26735
|
+
}
|
|
26736
|
+
outgoing[outgoingEnded]?.();
|
|
26737
|
+
};
|
|
26738
|
+
var getRequestListener = (fetchCallback, options = {}) => {
|
|
26739
|
+
const autoCleanupIncoming = options.autoCleanupIncoming ?? true;
|
|
26740
|
+
if (options.overrideGlobalObjects !== false && global.Request !== Request) {
|
|
26741
|
+
Object.defineProperty(global, "Request", {
|
|
26742
|
+
value: Request
|
|
26743
|
+
});
|
|
26744
|
+
Object.defineProperty(global, "Response", {
|
|
26745
|
+
value: Response2
|
|
26746
|
+
});
|
|
26747
|
+
}
|
|
26748
|
+
return async (incoming, outgoing) => {
|
|
26749
|
+
let res, req;
|
|
26750
|
+
try {
|
|
26751
|
+
req = newRequest(incoming, options.hostname);
|
|
26752
|
+
let incomingEnded = !autoCleanupIncoming || incoming.method === "GET" || incoming.method === "HEAD";
|
|
26753
|
+
if (!incomingEnded) {
|
|
26754
|
+
incoming[wrapBodyStream] = true;
|
|
26755
|
+
incoming.on("end", () => {
|
|
26756
|
+
incomingEnded = true;
|
|
26757
|
+
});
|
|
26758
|
+
if (incoming instanceof Http2ServerRequest2) {
|
|
26759
|
+
outgoing[outgoingEnded] = () => {
|
|
26760
|
+
if (!incomingEnded) {
|
|
26761
|
+
setTimeout(() => {
|
|
26762
|
+
if (!incomingEnded) {
|
|
26763
|
+
setTimeout(() => {
|
|
26764
|
+
incoming.destroy();
|
|
26765
|
+
outgoing.destroy();
|
|
26766
|
+
});
|
|
26767
|
+
}
|
|
26768
|
+
});
|
|
26769
|
+
}
|
|
26770
|
+
};
|
|
26771
|
+
}
|
|
26772
|
+
}
|
|
26773
|
+
outgoing.on("close", () => {
|
|
26774
|
+
const abortController = req[abortControllerKey];
|
|
26775
|
+
if (abortController) {
|
|
26776
|
+
if (incoming.errored) {
|
|
26777
|
+
req[abortControllerKey].abort(incoming.errored.toString());
|
|
26778
|
+
} else if (!outgoing.writableFinished) {
|
|
26779
|
+
req[abortControllerKey].abort("Client connection prematurely closed.");
|
|
26780
|
+
}
|
|
26781
|
+
}
|
|
26782
|
+
if (!incomingEnded) {
|
|
26783
|
+
setTimeout(() => {
|
|
26784
|
+
if (!incomingEnded) {
|
|
26785
|
+
setTimeout(() => {
|
|
26786
|
+
incoming.destroy();
|
|
26787
|
+
});
|
|
26788
|
+
}
|
|
26789
|
+
});
|
|
26790
|
+
}
|
|
26791
|
+
});
|
|
26792
|
+
res = fetchCallback(req, { incoming, outgoing });
|
|
26793
|
+
if (cacheKey in res) {
|
|
26794
|
+
return responseViaCache(res, outgoing);
|
|
26795
|
+
}
|
|
26796
|
+
} catch (e) {
|
|
26797
|
+
if (!res) {
|
|
26798
|
+
if (options.errorHandler) {
|
|
26799
|
+
res = await options.errorHandler(req ? e : toRequestError(e));
|
|
26800
|
+
if (!res) {
|
|
26801
|
+
return;
|
|
26802
|
+
}
|
|
26803
|
+
} else if (!req) {
|
|
26804
|
+
res = handleRequestError();
|
|
26805
|
+
} else {
|
|
26806
|
+
res = handleFetchError(e);
|
|
26807
|
+
}
|
|
26808
|
+
} else {
|
|
26809
|
+
return handleResponseError(e, outgoing);
|
|
26810
|
+
}
|
|
26811
|
+
}
|
|
26812
|
+
try {
|
|
26813
|
+
return await responseViaResponseObject(res, outgoing, options);
|
|
26814
|
+
} catch (e) {
|
|
26815
|
+
return handleResponseError(e, outgoing);
|
|
26816
|
+
}
|
|
26817
|
+
};
|
|
26818
|
+
};
|
|
26819
|
+
|
|
26820
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js
|
|
26821
|
+
class WebStandardStreamableHTTPServerTransport {
|
|
26822
|
+
constructor(options = {}) {
|
|
26823
|
+
this._started = false;
|
|
26824
|
+
this._hasHandledRequest = false;
|
|
26825
|
+
this._streamMapping = new Map;
|
|
26826
|
+
this._requestToStreamMapping = new Map;
|
|
26827
|
+
this._requestResponseMap = new Map;
|
|
26828
|
+
this._initialized = false;
|
|
26829
|
+
this._enableJsonResponse = false;
|
|
26830
|
+
this._standaloneSseStreamId = "_GET_stream";
|
|
26831
|
+
this.sessionIdGenerator = options.sessionIdGenerator;
|
|
26832
|
+
this._enableJsonResponse = options.enableJsonResponse ?? false;
|
|
26833
|
+
this._eventStore = options.eventStore;
|
|
26834
|
+
this._onsessioninitialized = options.onsessioninitialized;
|
|
26835
|
+
this._onsessionclosed = options.onsessionclosed;
|
|
26836
|
+
this._allowedHosts = options.allowedHosts;
|
|
26837
|
+
this._allowedOrigins = options.allowedOrigins;
|
|
26838
|
+
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
|
|
26839
|
+
this._retryInterval = options.retryInterval;
|
|
26840
|
+
}
|
|
26841
|
+
async start() {
|
|
26842
|
+
if (this._started) {
|
|
26843
|
+
throw new Error("Transport already started");
|
|
26844
|
+
}
|
|
26845
|
+
this._started = true;
|
|
26846
|
+
}
|
|
26847
|
+
createJsonErrorResponse(status, code, message, options) {
|
|
26848
|
+
const error2 = { code, message };
|
|
26849
|
+
if (options?.data !== undefined) {
|
|
26850
|
+
error2.data = options.data;
|
|
26851
|
+
}
|
|
26852
|
+
return new Response(JSON.stringify({
|
|
26853
|
+
jsonrpc: "2.0",
|
|
26854
|
+
error: error2,
|
|
26855
|
+
id: null
|
|
26856
|
+
}), {
|
|
26857
|
+
status,
|
|
26858
|
+
headers: {
|
|
26859
|
+
"Content-Type": "application/json",
|
|
26860
|
+
...options?.headers
|
|
26861
|
+
}
|
|
26862
|
+
});
|
|
26863
|
+
}
|
|
26864
|
+
validateRequestHeaders(req) {
|
|
26865
|
+
if (!this._enableDnsRebindingProtection) {
|
|
26866
|
+
return;
|
|
26867
|
+
}
|
|
26868
|
+
if (this._allowedHosts && this._allowedHosts.length > 0) {
|
|
26869
|
+
const hostHeader = req.headers.get("host");
|
|
26870
|
+
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
|
|
26871
|
+
const error2 = `Invalid Host header: ${hostHeader}`;
|
|
26872
|
+
this.onerror?.(new Error(error2));
|
|
26873
|
+
return this.createJsonErrorResponse(403, -32000, error2);
|
|
26874
|
+
}
|
|
26875
|
+
}
|
|
26876
|
+
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
|
|
26877
|
+
const originHeader = req.headers.get("origin");
|
|
26878
|
+
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
|
|
26879
|
+
const error2 = `Invalid Origin header: ${originHeader}`;
|
|
26880
|
+
this.onerror?.(new Error(error2));
|
|
26881
|
+
return this.createJsonErrorResponse(403, -32000, error2);
|
|
26882
|
+
}
|
|
26883
|
+
}
|
|
26884
|
+
return;
|
|
26885
|
+
}
|
|
26886
|
+
async handleRequest(req, options) {
|
|
26887
|
+
if (!this.sessionIdGenerator && this._hasHandledRequest) {
|
|
26888
|
+
throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
|
|
26889
|
+
}
|
|
26890
|
+
this._hasHandledRequest = true;
|
|
26891
|
+
const validationError = this.validateRequestHeaders(req);
|
|
26892
|
+
if (validationError) {
|
|
26893
|
+
return validationError;
|
|
26894
|
+
}
|
|
26895
|
+
switch (req.method) {
|
|
26896
|
+
case "POST":
|
|
26897
|
+
return this.handlePostRequest(req, options);
|
|
26898
|
+
case "GET":
|
|
26899
|
+
return this.handleGetRequest(req);
|
|
26900
|
+
case "DELETE":
|
|
26901
|
+
return this.handleDeleteRequest(req);
|
|
26902
|
+
default:
|
|
26903
|
+
return this.handleUnsupportedRequest();
|
|
26904
|
+
}
|
|
26905
|
+
}
|
|
26906
|
+
async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
|
|
26907
|
+
if (!this._eventStore) {
|
|
26908
|
+
return;
|
|
26909
|
+
}
|
|
26910
|
+
if (protocolVersion < "2025-11-25") {
|
|
26911
|
+
return;
|
|
26912
|
+
}
|
|
26913
|
+
const primingEventId = await this._eventStore.storeEvent(streamId, {});
|
|
26914
|
+
let primingEvent = `id: ${primingEventId}
|
|
26915
|
+
data:
|
|
26916
|
+
|
|
26917
|
+
`;
|
|
26918
|
+
if (this._retryInterval !== undefined) {
|
|
26919
|
+
primingEvent = `id: ${primingEventId}
|
|
26920
|
+
retry: ${this._retryInterval}
|
|
26921
|
+
data:
|
|
26922
|
+
|
|
26923
|
+
`;
|
|
26924
|
+
}
|
|
26925
|
+
controller.enqueue(encoder.encode(primingEvent));
|
|
26926
|
+
}
|
|
26927
|
+
async handleGetRequest(req) {
|
|
26928
|
+
const acceptHeader = req.headers.get("accept");
|
|
26929
|
+
if (!acceptHeader?.includes("text/event-stream")) {
|
|
26930
|
+
this.onerror?.(new Error("Not Acceptable: Client must accept text/event-stream"));
|
|
26931
|
+
return this.createJsonErrorResponse(406, -32000, "Not Acceptable: Client must accept text/event-stream");
|
|
26932
|
+
}
|
|
26933
|
+
const sessionError = this.validateSession(req);
|
|
26934
|
+
if (sessionError) {
|
|
26935
|
+
return sessionError;
|
|
26936
|
+
}
|
|
26937
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
26938
|
+
if (protocolError) {
|
|
26939
|
+
return protocolError;
|
|
26940
|
+
}
|
|
26941
|
+
if (this._eventStore) {
|
|
26942
|
+
const lastEventId = req.headers.get("last-event-id");
|
|
26943
|
+
if (lastEventId) {
|
|
26944
|
+
return this.replayEvents(lastEventId);
|
|
26945
|
+
}
|
|
26946
|
+
}
|
|
26947
|
+
if (this._streamMapping.get(this._standaloneSseStreamId) !== undefined) {
|
|
26948
|
+
this.onerror?.(new Error("Conflict: Only one SSE stream is allowed per session"));
|
|
26949
|
+
return this.createJsonErrorResponse(409, -32000, "Conflict: Only one SSE stream is allowed per session");
|
|
26950
|
+
}
|
|
26951
|
+
const encoder = new TextEncoder;
|
|
26952
|
+
let streamController;
|
|
26953
|
+
const readable = new ReadableStream({
|
|
26954
|
+
start: (controller) => {
|
|
26955
|
+
streamController = controller;
|
|
26956
|
+
},
|
|
26957
|
+
cancel: () => {
|
|
26958
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
26959
|
+
}
|
|
26960
|
+
});
|
|
26961
|
+
const headers = {
|
|
26962
|
+
"Content-Type": "text/event-stream",
|
|
26963
|
+
"Cache-Control": "no-cache, no-transform",
|
|
26964
|
+
Connection: "keep-alive"
|
|
26965
|
+
};
|
|
26966
|
+
if (this.sessionId !== undefined) {
|
|
26967
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
26968
|
+
}
|
|
26969
|
+
this._streamMapping.set(this._standaloneSseStreamId, {
|
|
26970
|
+
controller: streamController,
|
|
26971
|
+
encoder,
|
|
26972
|
+
cleanup: () => {
|
|
26973
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
26974
|
+
try {
|
|
26975
|
+
streamController.close();
|
|
26976
|
+
} catch {}
|
|
26977
|
+
}
|
|
26978
|
+
});
|
|
26979
|
+
return new Response(readable, { headers });
|
|
26980
|
+
}
|
|
26981
|
+
async replayEvents(lastEventId) {
|
|
26982
|
+
if (!this._eventStore) {
|
|
26983
|
+
this.onerror?.(new Error("Event store not configured"));
|
|
26984
|
+
return this.createJsonErrorResponse(400, -32000, "Event store not configured");
|
|
26985
|
+
}
|
|
26986
|
+
try {
|
|
26987
|
+
let streamId;
|
|
26988
|
+
if (this._eventStore.getStreamIdForEventId) {
|
|
26989
|
+
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
|
|
26990
|
+
if (!streamId) {
|
|
26991
|
+
this.onerror?.(new Error("Invalid event ID format"));
|
|
26992
|
+
return this.createJsonErrorResponse(400, -32000, "Invalid event ID format");
|
|
26993
|
+
}
|
|
26994
|
+
if (this._streamMapping.get(streamId) !== undefined) {
|
|
26995
|
+
this.onerror?.(new Error("Conflict: Stream already has an active connection"));
|
|
26996
|
+
return this.createJsonErrorResponse(409, -32000, "Conflict: Stream already has an active connection");
|
|
26997
|
+
}
|
|
26998
|
+
}
|
|
26999
|
+
const headers = {
|
|
27000
|
+
"Content-Type": "text/event-stream",
|
|
27001
|
+
"Cache-Control": "no-cache, no-transform",
|
|
27002
|
+
Connection: "keep-alive"
|
|
27003
|
+
};
|
|
27004
|
+
if (this.sessionId !== undefined) {
|
|
27005
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
27006
|
+
}
|
|
27007
|
+
const encoder = new TextEncoder;
|
|
27008
|
+
let streamController;
|
|
27009
|
+
const readable = new ReadableStream({
|
|
27010
|
+
start: (controller) => {
|
|
27011
|
+
streamController = controller;
|
|
27012
|
+
},
|
|
27013
|
+
cancel: () => {}
|
|
27014
|
+
});
|
|
27015
|
+
const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
|
|
27016
|
+
send: async (eventId, message) => {
|
|
27017
|
+
const success = this.writeSSEEvent(streamController, encoder, message, eventId);
|
|
27018
|
+
if (!success) {
|
|
27019
|
+
this.onerror?.(new Error("Failed replay events"));
|
|
27020
|
+
try {
|
|
27021
|
+
streamController.close();
|
|
27022
|
+
} catch {}
|
|
27023
|
+
}
|
|
27024
|
+
}
|
|
27025
|
+
});
|
|
27026
|
+
this._streamMapping.set(replayedStreamId, {
|
|
27027
|
+
controller: streamController,
|
|
27028
|
+
encoder,
|
|
27029
|
+
cleanup: () => {
|
|
27030
|
+
this._streamMapping.delete(replayedStreamId);
|
|
27031
|
+
try {
|
|
27032
|
+
streamController.close();
|
|
27033
|
+
} catch {}
|
|
27034
|
+
}
|
|
27035
|
+
});
|
|
27036
|
+
return new Response(readable, { headers });
|
|
27037
|
+
} catch (error2) {
|
|
27038
|
+
this.onerror?.(error2);
|
|
27039
|
+
return this.createJsonErrorResponse(500, -32000, "Error replaying events");
|
|
27040
|
+
}
|
|
27041
|
+
}
|
|
27042
|
+
writeSSEEvent(controller, encoder, message, eventId) {
|
|
27043
|
+
try {
|
|
27044
|
+
let eventData = `event: message
|
|
27045
|
+
`;
|
|
27046
|
+
if (eventId) {
|
|
27047
|
+
eventData += `id: ${eventId}
|
|
27048
|
+
`;
|
|
27049
|
+
}
|
|
27050
|
+
eventData += `data: ${JSON.stringify(message)}
|
|
27051
|
+
|
|
27052
|
+
`;
|
|
27053
|
+
controller.enqueue(encoder.encode(eventData));
|
|
27054
|
+
return true;
|
|
27055
|
+
} catch (error2) {
|
|
27056
|
+
this.onerror?.(error2);
|
|
27057
|
+
return false;
|
|
27058
|
+
}
|
|
27059
|
+
}
|
|
27060
|
+
handleUnsupportedRequest() {
|
|
27061
|
+
this.onerror?.(new Error("Method not allowed."));
|
|
27062
|
+
return new Response(JSON.stringify({
|
|
27063
|
+
jsonrpc: "2.0",
|
|
27064
|
+
error: {
|
|
27065
|
+
code: -32000,
|
|
27066
|
+
message: "Method not allowed."
|
|
27067
|
+
},
|
|
27068
|
+
id: null
|
|
27069
|
+
}), {
|
|
27070
|
+
status: 405,
|
|
27071
|
+
headers: {
|
|
27072
|
+
Allow: "GET, POST, DELETE",
|
|
27073
|
+
"Content-Type": "application/json"
|
|
27074
|
+
}
|
|
27075
|
+
});
|
|
27076
|
+
}
|
|
27077
|
+
async handlePostRequest(req, options) {
|
|
27078
|
+
try {
|
|
27079
|
+
const acceptHeader = req.headers.get("accept");
|
|
27080
|
+
if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
|
|
27081
|
+
this.onerror?.(new Error("Not Acceptable: Client must accept both application/json and text/event-stream"));
|
|
27082
|
+
return this.createJsonErrorResponse(406, -32000, "Not Acceptable: Client must accept both application/json and text/event-stream");
|
|
27083
|
+
}
|
|
27084
|
+
const ct = req.headers.get("content-type");
|
|
27085
|
+
if (!ct || !ct.includes("application/json")) {
|
|
27086
|
+
this.onerror?.(new Error("Unsupported Media Type: Content-Type must be application/json"));
|
|
27087
|
+
return this.createJsonErrorResponse(415, -32000, "Unsupported Media Type: Content-Type must be application/json");
|
|
27088
|
+
}
|
|
27089
|
+
const requestInfo = {
|
|
27090
|
+
headers: Object.fromEntries(req.headers.entries()),
|
|
27091
|
+
url: new URL(req.url)
|
|
27092
|
+
};
|
|
27093
|
+
let rawMessage;
|
|
27094
|
+
if (options?.parsedBody !== undefined) {
|
|
27095
|
+
rawMessage = options.parsedBody;
|
|
27096
|
+
} else {
|
|
27097
|
+
try {
|
|
27098
|
+
rawMessage = await req.json();
|
|
27099
|
+
} catch {
|
|
27100
|
+
this.onerror?.(new Error("Parse error: Invalid JSON"));
|
|
27101
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
|
|
27102
|
+
}
|
|
27103
|
+
}
|
|
27104
|
+
let messages;
|
|
27105
|
+
try {
|
|
27106
|
+
if (Array.isArray(rawMessage)) {
|
|
27107
|
+
messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg));
|
|
27108
|
+
} else {
|
|
27109
|
+
messages = [JSONRPCMessageSchema.parse(rawMessage)];
|
|
27110
|
+
}
|
|
27111
|
+
} catch {
|
|
27112
|
+
this.onerror?.(new Error("Parse error: Invalid JSON-RPC message"));
|
|
27113
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
|
|
27114
|
+
}
|
|
27115
|
+
const isInitializationRequest = messages.some(isInitializeRequest);
|
|
27116
|
+
if (isInitializationRequest) {
|
|
27117
|
+
if (this._initialized && this.sessionId !== undefined) {
|
|
27118
|
+
this.onerror?.(new Error("Invalid Request: Server already initialized"));
|
|
27119
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
|
|
27120
|
+
}
|
|
27121
|
+
if (messages.length > 1) {
|
|
27122
|
+
this.onerror?.(new Error("Invalid Request: Only one initialization request is allowed"));
|
|
27123
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
|
|
27124
|
+
}
|
|
27125
|
+
this.sessionId = this.sessionIdGenerator?.();
|
|
27126
|
+
this._initialized = true;
|
|
27127
|
+
if (this.sessionId && this._onsessioninitialized) {
|
|
27128
|
+
await Promise.resolve(this._onsessioninitialized(this.sessionId));
|
|
27129
|
+
}
|
|
27130
|
+
}
|
|
27131
|
+
if (!isInitializationRequest) {
|
|
27132
|
+
const sessionError = this.validateSession(req);
|
|
27133
|
+
if (sessionError) {
|
|
27134
|
+
return sessionError;
|
|
27135
|
+
}
|
|
27136
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
27137
|
+
if (protocolError) {
|
|
27138
|
+
return protocolError;
|
|
27139
|
+
}
|
|
27140
|
+
}
|
|
27141
|
+
const hasRequests = messages.some(isJSONRPCRequest);
|
|
27142
|
+
if (!hasRequests) {
|
|
27143
|
+
for (const message of messages) {
|
|
27144
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
27145
|
+
}
|
|
27146
|
+
return new Response(null, { status: 202 });
|
|
27147
|
+
}
|
|
27148
|
+
const streamId = crypto.randomUUID();
|
|
27149
|
+
const initRequest = messages.find((m) => isInitializeRequest(m));
|
|
27150
|
+
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
27151
|
+
if (this._enableJsonResponse) {
|
|
27152
|
+
return new Promise((resolve) => {
|
|
27153
|
+
this._streamMapping.set(streamId, {
|
|
27154
|
+
resolveJson: resolve,
|
|
27155
|
+
cleanup: () => {
|
|
27156
|
+
this._streamMapping.delete(streamId);
|
|
27157
|
+
}
|
|
27158
|
+
});
|
|
27159
|
+
for (const message of messages) {
|
|
27160
|
+
if (isJSONRPCRequest(message)) {
|
|
27161
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
27162
|
+
}
|
|
27163
|
+
}
|
|
27164
|
+
for (const message of messages) {
|
|
27165
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo });
|
|
27166
|
+
}
|
|
27167
|
+
});
|
|
27168
|
+
}
|
|
27169
|
+
const encoder = new TextEncoder;
|
|
27170
|
+
let streamController;
|
|
27171
|
+
const readable = new ReadableStream({
|
|
27172
|
+
start: (controller) => {
|
|
27173
|
+
streamController = controller;
|
|
27174
|
+
},
|
|
27175
|
+
cancel: () => {
|
|
27176
|
+
this._streamMapping.delete(streamId);
|
|
27177
|
+
}
|
|
27178
|
+
});
|
|
27179
|
+
const headers = {
|
|
27180
|
+
"Content-Type": "text/event-stream",
|
|
27181
|
+
"Cache-Control": "no-cache",
|
|
27182
|
+
Connection: "keep-alive"
|
|
27183
|
+
};
|
|
27184
|
+
if (this.sessionId !== undefined) {
|
|
27185
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
27186
|
+
}
|
|
27187
|
+
for (const message of messages) {
|
|
27188
|
+
if (isJSONRPCRequest(message)) {
|
|
27189
|
+
this._streamMapping.set(streamId, {
|
|
27190
|
+
controller: streamController,
|
|
27191
|
+
encoder,
|
|
27192
|
+
cleanup: () => {
|
|
27193
|
+
this._streamMapping.delete(streamId);
|
|
27194
|
+
try {
|
|
27195
|
+
streamController.close();
|
|
27196
|
+
} catch {}
|
|
27197
|
+
}
|
|
27198
|
+
});
|
|
27199
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
27200
|
+
}
|
|
27201
|
+
}
|
|
27202
|
+
await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
|
|
27203
|
+
for (const message of messages) {
|
|
27204
|
+
let closeSSEStream;
|
|
27205
|
+
let closeStandaloneSSEStream;
|
|
27206
|
+
if (isJSONRPCRequest(message) && this._eventStore && clientProtocolVersion >= "2025-11-25") {
|
|
27207
|
+
closeSSEStream = () => {
|
|
27208
|
+
this.closeSSEStream(message.id);
|
|
27209
|
+
};
|
|
27210
|
+
closeStandaloneSSEStream = () => {
|
|
27211
|
+
this.closeStandaloneSSEStream();
|
|
27212
|
+
};
|
|
27213
|
+
}
|
|
27214
|
+
this.onmessage?.(message, { authInfo: options?.authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
|
|
27215
|
+
}
|
|
27216
|
+
return new Response(readable, { status: 200, headers });
|
|
27217
|
+
} catch (error2) {
|
|
27218
|
+
this.onerror?.(error2);
|
|
27219
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error2) });
|
|
27220
|
+
}
|
|
27221
|
+
}
|
|
27222
|
+
async handleDeleteRequest(req) {
|
|
27223
|
+
const sessionError = this.validateSession(req);
|
|
27224
|
+
if (sessionError) {
|
|
27225
|
+
return sessionError;
|
|
27226
|
+
}
|
|
27227
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
27228
|
+
if (protocolError) {
|
|
27229
|
+
return protocolError;
|
|
27230
|
+
}
|
|
27231
|
+
await Promise.resolve(this._onsessionclosed?.(this.sessionId));
|
|
27232
|
+
await this.close();
|
|
27233
|
+
return new Response(null, { status: 200 });
|
|
27234
|
+
}
|
|
27235
|
+
validateSession(req) {
|
|
27236
|
+
if (this.sessionIdGenerator === undefined) {
|
|
27237
|
+
return;
|
|
27238
|
+
}
|
|
27239
|
+
if (!this._initialized) {
|
|
27240
|
+
this.onerror?.(new Error("Bad Request: Server not initialized"));
|
|
27241
|
+
return this.createJsonErrorResponse(400, -32000, "Bad Request: Server not initialized");
|
|
27242
|
+
}
|
|
27243
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
27244
|
+
if (!sessionId) {
|
|
27245
|
+
this.onerror?.(new Error("Bad Request: Mcp-Session-Id header is required"));
|
|
27246
|
+
return this.createJsonErrorResponse(400, -32000, "Bad Request: Mcp-Session-Id header is required");
|
|
27247
|
+
}
|
|
27248
|
+
if (sessionId !== this.sessionId) {
|
|
27249
|
+
this.onerror?.(new Error("Session not found"));
|
|
27250
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
27251
|
+
}
|
|
27252
|
+
return;
|
|
27253
|
+
}
|
|
27254
|
+
validateProtocolVersion(req) {
|
|
27255
|
+
const protocolVersion = req.headers.get("mcp-protocol-version");
|
|
27256
|
+
if (protocolVersion !== null && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
|
|
27257
|
+
this.onerror?.(new Error(`Bad Request: Unsupported protocol version: ${protocolVersion}` + ` (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`));
|
|
27258
|
+
return this.createJsonErrorResponse(400, -32000, `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")})`);
|
|
27259
|
+
}
|
|
27260
|
+
return;
|
|
27261
|
+
}
|
|
27262
|
+
async close() {
|
|
27263
|
+
this._streamMapping.forEach(({ cleanup }) => {
|
|
27264
|
+
cleanup();
|
|
27265
|
+
});
|
|
27266
|
+
this._streamMapping.clear();
|
|
27267
|
+
this._requestResponseMap.clear();
|
|
27268
|
+
this.onclose?.();
|
|
27269
|
+
}
|
|
27270
|
+
closeSSEStream(requestId) {
|
|
27271
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
27272
|
+
if (!streamId)
|
|
27273
|
+
return;
|
|
27274
|
+
const stream = this._streamMapping.get(streamId);
|
|
27275
|
+
if (stream) {
|
|
27276
|
+
stream.cleanup();
|
|
27277
|
+
}
|
|
27278
|
+
}
|
|
27279
|
+
closeStandaloneSSEStream() {
|
|
27280
|
+
const stream = this._streamMapping.get(this._standaloneSseStreamId);
|
|
27281
|
+
if (stream) {
|
|
27282
|
+
stream.cleanup();
|
|
27283
|
+
}
|
|
27284
|
+
}
|
|
27285
|
+
async send(message, options) {
|
|
27286
|
+
let requestId = options?.relatedRequestId;
|
|
27287
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
27288
|
+
requestId = message.id;
|
|
27289
|
+
}
|
|
27290
|
+
if (requestId === undefined) {
|
|
27291
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
27292
|
+
throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
|
|
27293
|
+
}
|
|
27294
|
+
let eventId;
|
|
27295
|
+
if (this._eventStore) {
|
|
27296
|
+
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
|
|
27297
|
+
}
|
|
27298
|
+
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
|
|
27299
|
+
if (standaloneSse === undefined) {
|
|
27300
|
+
return;
|
|
27301
|
+
}
|
|
27302
|
+
if (standaloneSse.controller && standaloneSse.encoder) {
|
|
27303
|
+
this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
|
|
27304
|
+
}
|
|
27305
|
+
return;
|
|
27306
|
+
}
|
|
27307
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
27308
|
+
if (!streamId) {
|
|
27309
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
27310
|
+
}
|
|
27311
|
+
const stream = this._streamMapping.get(streamId);
|
|
27312
|
+
if (!this._enableJsonResponse && stream?.controller && stream?.encoder) {
|
|
27313
|
+
let eventId;
|
|
27314
|
+
if (this._eventStore) {
|
|
27315
|
+
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
27316
|
+
}
|
|
27317
|
+
this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
|
|
27318
|
+
}
|
|
27319
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
27320
|
+
this._requestResponseMap.set(requestId, message);
|
|
27321
|
+
const relatedIds = Array.from(this._requestToStreamMapping.entries()).filter(([_, sid]) => sid === streamId).map(([id]) => id);
|
|
27322
|
+
const allResponsesReady = relatedIds.every((id) => this._requestResponseMap.has(id));
|
|
27323
|
+
if (allResponsesReady) {
|
|
27324
|
+
if (!stream) {
|
|
27325
|
+
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
27326
|
+
}
|
|
27327
|
+
if (this._enableJsonResponse && stream.resolveJson) {
|
|
27328
|
+
const headers = {
|
|
27329
|
+
"Content-Type": "application/json"
|
|
27330
|
+
};
|
|
27331
|
+
if (this.sessionId !== undefined) {
|
|
27332
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
27333
|
+
}
|
|
27334
|
+
const responses = relatedIds.map((id) => this._requestResponseMap.get(id));
|
|
27335
|
+
if (responses.length === 1) {
|
|
27336
|
+
stream.resolveJson(new Response(JSON.stringify(responses[0]), { status: 200, headers }));
|
|
27337
|
+
} else {
|
|
27338
|
+
stream.resolveJson(new Response(JSON.stringify(responses), { status: 200, headers }));
|
|
27339
|
+
}
|
|
27340
|
+
} else {
|
|
27341
|
+
stream.cleanup();
|
|
27342
|
+
}
|
|
27343
|
+
for (const id of relatedIds) {
|
|
27344
|
+
this._requestResponseMap.delete(id);
|
|
27345
|
+
this._requestToStreamMapping.delete(id);
|
|
27346
|
+
}
|
|
27347
|
+
}
|
|
27348
|
+
}
|
|
27349
|
+
}
|
|
27350
|
+
}
|
|
27351
|
+
|
|
27352
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/streamableHttp.js
|
|
27353
|
+
class StreamableHTTPServerTransport {
|
|
27354
|
+
constructor(options = {}) {
|
|
27355
|
+
this._requestContext = new WeakMap;
|
|
27356
|
+
this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);
|
|
27357
|
+
this._requestListener = getRequestListener(async (webRequest) => {
|
|
27358
|
+
const context = this._requestContext.get(webRequest);
|
|
27359
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
27360
|
+
authInfo: context?.authInfo,
|
|
27361
|
+
parsedBody: context?.parsedBody
|
|
27362
|
+
});
|
|
27363
|
+
}, { overrideGlobalObjects: false });
|
|
27364
|
+
}
|
|
27365
|
+
get sessionId() {
|
|
27366
|
+
return this._webStandardTransport.sessionId;
|
|
27367
|
+
}
|
|
27368
|
+
set onclose(handler) {
|
|
27369
|
+
this._webStandardTransport.onclose = handler;
|
|
27370
|
+
}
|
|
27371
|
+
get onclose() {
|
|
27372
|
+
return this._webStandardTransport.onclose;
|
|
27373
|
+
}
|
|
27374
|
+
set onerror(handler) {
|
|
27375
|
+
this._webStandardTransport.onerror = handler;
|
|
27376
|
+
}
|
|
27377
|
+
get onerror() {
|
|
27378
|
+
return this._webStandardTransport.onerror;
|
|
27379
|
+
}
|
|
27380
|
+
set onmessage(handler) {
|
|
27381
|
+
this._webStandardTransport.onmessage = handler;
|
|
27382
|
+
}
|
|
27383
|
+
get onmessage() {
|
|
27384
|
+
return this._webStandardTransport.onmessage;
|
|
27385
|
+
}
|
|
27386
|
+
async start() {
|
|
27387
|
+
return this._webStandardTransport.start();
|
|
27388
|
+
}
|
|
27389
|
+
async close() {
|
|
27390
|
+
return this._webStandardTransport.close();
|
|
27391
|
+
}
|
|
27392
|
+
async send(message, options) {
|
|
27393
|
+
return this._webStandardTransport.send(message, options);
|
|
27394
|
+
}
|
|
27395
|
+
async handleRequest(req, res, parsedBody) {
|
|
27396
|
+
const authInfo = req.auth;
|
|
27397
|
+
const handler = getRequestListener(async (webRequest) => {
|
|
27398
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
27399
|
+
authInfo,
|
|
27400
|
+
parsedBody
|
|
27401
|
+
});
|
|
27402
|
+
}, { overrideGlobalObjects: false });
|
|
27403
|
+
await handler(req, res);
|
|
27404
|
+
}
|
|
27405
|
+
closeSSEStream(requestId) {
|
|
27406
|
+
this._webStandardTransport.closeSSEStream(requestId);
|
|
27407
|
+
}
|
|
27408
|
+
closeStandaloneSSEStream() {
|
|
27409
|
+
this._webStandardTransport.closeStandaloneSSEStream();
|
|
27410
|
+
}
|
|
27411
|
+
}
|
|
27412
|
+
|
|
27413
|
+
// src/mcp/http.ts
|
|
27414
|
+
var DEFAULT_MCP_HTTP_PORT = 8804;
|
|
27415
|
+
var MCP_SERVICE_NAME = "cloud";
|
|
27416
|
+
function resolveMcpHttpPort(explicit) {
|
|
27417
|
+
if (explicit != null && !Number.isNaN(explicit))
|
|
27418
|
+
return explicit;
|
|
27419
|
+
const env = process.env.MCP_HTTP_PORT;
|
|
27420
|
+
if (env) {
|
|
27421
|
+
const parsed = parseInt(env, 10);
|
|
27422
|
+
if (!Number.isNaN(parsed))
|
|
27423
|
+
return parsed;
|
|
27424
|
+
}
|
|
27425
|
+
return DEFAULT_MCP_HTTP_PORT;
|
|
27426
|
+
}
|
|
27427
|
+
function isHttpMode(argv = process.argv) {
|
|
27428
|
+
return argv.includes("--http") || process.env.MCP_HTTP === "1";
|
|
27429
|
+
}
|
|
27430
|
+
function parseHttpArgv(argv = process.argv) {
|
|
27431
|
+
const http = isHttpMode(argv);
|
|
27432
|
+
let port;
|
|
27433
|
+
const portIdx = argv.indexOf("--port");
|
|
27434
|
+
if (portIdx !== -1 && argv[portIdx + 1]) {
|
|
27435
|
+
port = parseInt(argv[portIdx + 1], 10);
|
|
27436
|
+
}
|
|
27437
|
+
return { http, port };
|
|
27438
|
+
}
|
|
27439
|
+
async function readJsonBody(req) {
|
|
27440
|
+
const chunks = [];
|
|
27441
|
+
for await (const chunk of req)
|
|
27442
|
+
chunks.push(chunk);
|
|
27443
|
+
if (chunks.length === 0)
|
|
27444
|
+
return;
|
|
27445
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
27446
|
+
return text ? JSON.parse(text) : undefined;
|
|
27447
|
+
}
|
|
27448
|
+
async function handleStatelessMcpNode(req, res, getServer = buildServer) {
|
|
27449
|
+
const server = await getServer();
|
|
27450
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
27451
|
+
await server.connect(transport);
|
|
27452
|
+
const body = req.method === "POST" ? await readJsonBody(req) : undefined;
|
|
27453
|
+
await transport.handleRequest(req, res, body);
|
|
27454
|
+
res.on("close", () => {
|
|
27455
|
+
transport.close();
|
|
27456
|
+
server.close();
|
|
27457
|
+
});
|
|
27458
|
+
}
|
|
27459
|
+
function healthPayload(name = MCP_SERVICE_NAME) {
|
|
27460
|
+
return { status: "ok", name };
|
|
27461
|
+
}
|
|
27462
|
+
async function startMcpHttpServer(options = {}) {
|
|
27463
|
+
const port = options.port ?? resolveMcpHttpPort();
|
|
27464
|
+
const host = "127.0.0.1";
|
|
27465
|
+
const getServer = options.getServer ?? buildServer;
|
|
27466
|
+
const name = options.name ?? MCP_SERVICE_NAME;
|
|
27467
|
+
const httpServer = createServer(async (req, res) => {
|
|
27468
|
+
const url = new URL(req.url ?? "/", `http://${host}:${port}`);
|
|
27469
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
27470
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
27471
|
+
res.end(JSON.stringify(healthPayload(name)));
|
|
27472
|
+
return;
|
|
27473
|
+
}
|
|
27474
|
+
if (url.pathname === "/mcp") {
|
|
27475
|
+
await handleStatelessMcpNode(req, res, getServer);
|
|
27476
|
+
return;
|
|
27477
|
+
}
|
|
27478
|
+
res.writeHead(404);
|
|
27479
|
+
res.end("Not found");
|
|
27480
|
+
});
|
|
27481
|
+
await new Promise((resolve, reject) => {
|
|
27482
|
+
httpServer.once("error", reject);
|
|
27483
|
+
httpServer.listen(port, host, () => resolve());
|
|
27484
|
+
});
|
|
27485
|
+
const address = httpServer.address();
|
|
27486
|
+
const boundPort = typeof address === "object" && address ? address.port : port;
|
|
27487
|
+
return {
|
|
27488
|
+
port: boundPort,
|
|
27489
|
+
close: () => new Promise((resolve, reject) => {
|
|
27490
|
+
httpServer.close((err) => err ? reject(err) : resolve());
|
|
27491
|
+
})
|
|
27492
|
+
};
|
|
27493
|
+
}
|
|
27494
|
+
async function runMcpHttpServer(options = {}) {
|
|
27495
|
+
const { port } = await startMcpHttpServer(options);
|
|
27496
|
+
console.error(`cloud-mcp listening on http://127.0.0.1:${port}/mcp`);
|
|
27497
|
+
await new Promise(() => {});
|
|
27498
|
+
}
|
|
27499
|
+
|
|
27500
|
+
// src/mcp/index.ts
|
|
27501
|
+
function buildServer() {
|
|
27502
|
+
const server = new McpServer({
|
|
27503
|
+
name: "cloud",
|
|
27504
|
+
version: "0.1.0"
|
|
27505
|
+
});
|
|
27506
|
+
server.tool("cloud_status", "Show cloud configuration and connection health", {}, async () => {
|
|
27507
|
+
const config2 = getCloudConfig();
|
|
27508
|
+
const lines = [
|
|
27509
|
+
`Mode: ${config2.mode}`,
|
|
27510
|
+
`RDS Host: ${config2.rds.host || "(not configured)"}`,
|
|
27511
|
+
`RDS Port: ${config2.rds.port}`,
|
|
27512
|
+
`RDS Username: ${config2.rds.username || "(not configured)"}`,
|
|
27513
|
+
`SSL: ${config2.rds.ssl}`,
|
|
27514
|
+
`Auto-sync: ${config2.auto_sync_interval_minutes ? `${config2.auto_sync_interval_minutes} min` : "disabled"}`
|
|
27515
|
+
];
|
|
27516
|
+
if (config2.rds.host && config2.rds.username) {
|
|
27517
|
+
try {
|
|
27518
|
+
const connStr = getConnectionString("postgres");
|
|
27519
|
+
const pg2 = new PgAdapterAsync2(connStr);
|
|
27520
|
+
await pg2.get("SELECT 1 as ok");
|
|
27521
|
+
lines.push("PostgreSQL: connected");
|
|
27522
|
+
await pg2.close();
|
|
27523
|
+
} catch (err) {
|
|
27524
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
27525
|
+
lines.push(`PostgreSQL: connection failed \u2014 ${message}`);
|
|
27526
|
+
}
|
|
27527
|
+
}
|
|
27528
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
27529
|
+
`) }] };
|
|
27530
|
+
});
|
|
27531
|
+
server.tool("sync_push", "Push local SQLite data to cloud PostgreSQL", {
|
|
27532
|
+
service: exports_external.string().describe("Service name"),
|
|
27533
|
+
tables: exports_external.string().optional().describe("Comma-separated table names (default: all)")
|
|
27534
|
+
}, async ({ service, tables: tablesStr }) => {
|
|
27535
|
+
const config2 = getCloudConfig();
|
|
27536
|
+
if (config2.mode === "local") {
|
|
27537
|
+
return {
|
|
27538
|
+
content: [
|
|
27539
|
+
{
|
|
27540
|
+
type: "text",
|
|
27541
|
+
text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`."
|
|
27542
|
+
}
|
|
27543
|
+
],
|
|
27544
|
+
isError: true
|
|
27545
|
+
};
|
|
27546
|
+
}
|
|
27547
|
+
const dbPath = getDbPath2(service);
|
|
27548
|
+
const local = new SqliteAdapter2(dbPath);
|
|
27549
|
+
const connStr = getConnectionString(service);
|
|
27550
|
+
const cloud = new PgAdapterAsync2(connStr);
|
|
27551
|
+
let tableList;
|
|
27552
|
+
if (tablesStr) {
|
|
27553
|
+
tableList = tablesStr.split(",").map((t) => t.trim());
|
|
27554
|
+
} else {
|
|
27555
|
+
tableList = listSqliteTables(local);
|
|
27556
|
+
}
|
|
27557
|
+
const results = await syncPush(local, cloud, { tables: tableList });
|
|
27558
|
+
local.close();
|
|
27559
|
+
await cloud.close();
|
|
27560
|
+
const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
27561
|
+
const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
|
|
27562
|
+
const lines = [
|
|
27563
|
+
`Pushed ${tableList.length} table(s): ${totalWritten} rows, ${totalErrors} errors.`
|
|
27564
|
+
];
|
|
27565
|
+
for (const r of results) {
|
|
27566
|
+
lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
|
|
27567
|
+
for (const e of r.errors) {
|
|
27568
|
+
lines.push(` ERROR: ${e}`);
|
|
27569
|
+
}
|
|
27570
|
+
}
|
|
27571
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
27572
|
+
`) }] };
|
|
27573
|
+
});
|
|
27574
|
+
server.tool("sync_pull", "Pull cloud PostgreSQL data to local SQLite", {
|
|
27575
|
+
service: exports_external.string().describe("Service name"),
|
|
27576
|
+
tables: exports_external.string().optional().describe("Comma-separated table names (default: all)")
|
|
27577
|
+
}, async ({ service, tables: tablesStr }) => {
|
|
27578
|
+
const config2 = getCloudConfig();
|
|
27579
|
+
if (config2.mode === "local") {
|
|
27580
|
+
return {
|
|
27581
|
+
content: [
|
|
27582
|
+
{
|
|
27583
|
+
type: "text",
|
|
27584
|
+
text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`."
|
|
27585
|
+
}
|
|
27586
|
+
],
|
|
27587
|
+
isError: true
|
|
27588
|
+
};
|
|
27589
|
+
}
|
|
27590
|
+
const dbPath = getDbPath2(service);
|
|
27591
|
+
const local = new SqliteAdapter2(dbPath);
|
|
27592
|
+
const connStr = getConnectionString(service);
|
|
27593
|
+
const cloud = new PgAdapterAsync2(connStr);
|
|
27594
|
+
let tableList;
|
|
27595
|
+
if (tablesStr) {
|
|
27596
|
+
tableList = tablesStr.split(",").map((t) => t.trim());
|
|
27597
|
+
} else {
|
|
27598
|
+
try {
|
|
27599
|
+
tableList = await listPgTables(cloud);
|
|
27600
|
+
} catch {
|
|
27601
|
+
local.close();
|
|
27602
|
+
await cloud.close();
|
|
27603
|
+
return {
|
|
27604
|
+
content: [
|
|
27605
|
+
{ type: "text", text: "Error: failed to list tables from cloud." }
|
|
27606
|
+
],
|
|
27607
|
+
isError: true
|
|
27608
|
+
};
|
|
27609
|
+
}
|
|
27610
|
+
}
|
|
27611
|
+
const results = await syncPull(cloud, local, { tables: tableList });
|
|
27612
|
+
local.close();
|
|
27613
|
+
await cloud.close();
|
|
27614
|
+
const totalWritten = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
27615
|
+
const totalErrors = results.reduce((s, r) => s + r.errors.length, 0);
|
|
27616
|
+
const lines = [
|
|
27617
|
+
`Pulled ${tableList.length} table(s): ${totalWritten} rows, ${totalErrors} errors.`
|
|
27618
|
+
];
|
|
27619
|
+
for (const r of results) {
|
|
27620
|
+
lines.push(` ${r.table}: ${r.rowsWritten} written, ${r.rowsSkipped} skipped`);
|
|
27621
|
+
for (const e of r.errors) {
|
|
27622
|
+
lines.push(` ERROR: ${e}`);
|
|
27623
|
+
}
|
|
27624
|
+
}
|
|
27625
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
27626
|
+
`) }] };
|
|
27627
|
+
});
|
|
27628
|
+
server.tool("send_feedback", "Send feedback for a service", {
|
|
27629
|
+
service: exports_external.string().describe("Service name"),
|
|
27630
|
+
message: exports_external.string().describe("Feedback message"),
|
|
27631
|
+
email: exports_external.string().optional().describe("Contact email"),
|
|
27632
|
+
version: exports_external.string().optional().describe("Service version")
|
|
27633
|
+
}, async ({ service, message, email: email2, version: version2 }) => {
|
|
27634
|
+
const db = createDatabase({ service: "cloud" });
|
|
27635
|
+
const result = await sendFeedback({ service, message, email: email2, version: version2 }, db);
|
|
27636
|
+
db.close();
|
|
27637
|
+
if (result.sent) {
|
|
27638
|
+
return {
|
|
27639
|
+
content: [
|
|
27640
|
+
{
|
|
27641
|
+
type: "text",
|
|
27642
|
+
text: `Feedback sent successfully (id: ${result.id})`
|
|
27643
|
+
}
|
|
27644
|
+
]
|
|
27645
|
+
};
|
|
27646
|
+
}
|
|
27647
|
+
return {
|
|
27648
|
+
content: [
|
|
27649
|
+
{
|
|
27650
|
+
type: "text",
|
|
27651
|
+
text: `Feedback saved locally (id: ${result.id}). Remote send failed: ${result.error}`
|
|
27652
|
+
}
|
|
27653
|
+
]
|
|
27654
|
+
};
|
|
27655
|
+
});
|
|
27656
|
+
server.tool("sync_all", "Sync all discovered services between local and cloud. Pulls from PG to SQLite by default.", {
|
|
27657
|
+
direction: exports_external.enum(["pull", "push"]).default("pull").describe("Sync direction")
|
|
27658
|
+
}, async ({ direction }) => {
|
|
27659
|
+
const config2 = getCloudConfig();
|
|
27660
|
+
if (config2.mode === "local") {
|
|
27661
|
+
return {
|
|
27662
|
+
content: [{ type: "text", text: "Error: mode is 'local'. Configure cloud mode first via `cloud setup`." }],
|
|
27663
|
+
isError: true
|
|
27664
|
+
};
|
|
27665
|
+
}
|
|
27666
|
+
const { discoverServices: discoverServices3, isSyncExcludedTable: isSyncExcludedTable3 } = await Promise.resolve().then(() => (init_discover2(), exports_discover2));
|
|
27667
|
+
const services = discoverServices3();
|
|
27668
|
+
const lines = [`Syncing ${services.length} services (${direction})...`];
|
|
27669
|
+
let grandTotal = 0;
|
|
27670
|
+
let grandErrors = 0;
|
|
27671
|
+
for (const service of services) {
|
|
27672
|
+
try {
|
|
27673
|
+
const dbPath = getDbPath2(service);
|
|
27674
|
+
const local = new SqliteAdapter2(dbPath);
|
|
27675
|
+
const connStr = getConnectionString(service);
|
|
27676
|
+
const cloud = new PgAdapterAsync2(connStr);
|
|
27677
|
+
let tableList;
|
|
27678
|
+
if (direction === "push") {
|
|
27679
|
+
tableList = listSqliteTables(local).filter((t) => !isSyncExcludedTable3(t));
|
|
27680
|
+
} else {
|
|
27681
|
+
try {
|
|
27682
|
+
tableList = (await listPgTables(cloud)).filter((t) => !isSyncExcludedTable3(t));
|
|
27683
|
+
} catch {
|
|
27684
|
+
local.close();
|
|
27685
|
+
await cloud.close();
|
|
27686
|
+
continue;
|
|
27687
|
+
}
|
|
27688
|
+
}
|
|
27689
|
+
if (tableList.length === 0) {
|
|
27690
|
+
local.close();
|
|
27691
|
+
await cloud.close();
|
|
27692
|
+
continue;
|
|
27693
|
+
}
|
|
27694
|
+
const results = direction === "push" ? await syncPush(local, cloud, { tables: tableList }) : await syncPull(cloud, local, { tables: tableList });
|
|
27695
|
+
local.close();
|
|
27696
|
+
await cloud.close();
|
|
27697
|
+
const written = results.reduce((s, r) => s + r.rowsWritten, 0);
|
|
27698
|
+
const errors4 = results.reduce((s, r) => s + r.errors.length, 0);
|
|
27699
|
+
grandTotal += written;
|
|
27700
|
+
grandErrors += errors4;
|
|
27701
|
+
if (written > 0 || errors4 > 0) {
|
|
27702
|
+
lines.push(` ${service}: ${written} rows${errors4 > 0 ? `, ${errors4} errors` : ""}`);
|
|
27703
|
+
}
|
|
27704
|
+
} catch (err) {
|
|
27705
|
+
grandErrors++;
|
|
27706
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
27707
|
+
lines.push(` ${service}: ERROR \u2014 ${message}`);
|
|
27708
|
+
}
|
|
27709
|
+
}
|
|
27710
|
+
lines.push(`
|
|
27711
|
+
Done. ${services.length} services, ${grandTotal} rows, ${grandErrors} errors.`);
|
|
27712
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
27713
|
+
`) }] };
|
|
27714
|
+
});
|
|
27715
|
+
server.tool("migrate_all", "Run PG migrations for all discovered services. Creates databases if needed.", {}, async () => {
|
|
27716
|
+
const { migrateAllServices: migrateAllServices2, ensureAllPgDatabases: ensureAllPgDatabases2 } = await Promise.resolve().then(() => (init_pg_migrate(), exports_pg_migrate));
|
|
27717
|
+
const lines = ["Running PG migrations..."];
|
|
27718
|
+
const dbResults = await ensureAllPgDatabases2();
|
|
27719
|
+
for (const r of dbResults) {
|
|
27720
|
+
if (r.created)
|
|
27721
|
+
lines.push(` Created DB: ${r.service}`);
|
|
27722
|
+
if (r.error)
|
|
27723
|
+
lines.push(` DB error: ${r.service} \u2014 ${r.error}`);
|
|
27724
|
+
}
|
|
27725
|
+
const results = await migrateAllServices2();
|
|
27726
|
+
let totalApplied = 0;
|
|
27727
|
+
for (const r of results) {
|
|
27728
|
+
totalApplied += r.applied.length;
|
|
27729
|
+
if (r.applied.length > 0 || r.errors.length > 0) {
|
|
27730
|
+
lines.push(` ${r.service}: ${r.applied.length} applied${r.errors.length > 0 ? `, ${r.errors.length} errors` : ""}`);
|
|
27731
|
+
for (const e of r.errors)
|
|
27732
|
+
lines.push(` ${e}`);
|
|
27733
|
+
}
|
|
27734
|
+
}
|
|
27735
|
+
lines.push(`
|
|
27736
|
+
Done. ${results.length} services, ${totalApplied} migrations applied.`);
|
|
27737
|
+
return { content: [{ type: "text", text: lines.join(`
|
|
27738
|
+
`) }] };
|
|
27739
|
+
});
|
|
27740
|
+
const mcpAgentRegistry = new Map;
|
|
27741
|
+
server.tool("register_agent", "Register an agent session for attribution", {
|
|
27742
|
+
name: exports_external.string().describe("Agent name"),
|
|
27743
|
+
session_id: exports_external.string().optional().describe("Session identifier")
|
|
27744
|
+
}, async ({ name }) => {
|
|
27745
|
+
const existing = [...mcpAgentRegistry.values()].find((a) => a.name === name);
|
|
27746
|
+
if (existing) {
|
|
27747
|
+
existing.last_seen_at = new Date().toISOString();
|
|
27748
|
+
return { content: [{ type: "text", text: JSON.stringify({ agent_id: existing.id, name: existing.name, last_seen_at: existing.last_seen_at }) }] };
|
|
27749
|
+
}
|
|
27750
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
27751
|
+
const agent = { id, name, last_seen_at: new Date().toISOString() };
|
|
27752
|
+
mcpAgentRegistry.set(id, agent);
|
|
27753
|
+
return { content: [{ type: "text", text: JSON.stringify(agent) }] };
|
|
27754
|
+
});
|
|
27755
|
+
server.tool("heartbeat", "Update agent last_seen_at", {
|
|
27756
|
+
agent_id: exports_external.string().optional().describe("Agent ID (optional \u2014 updates by name if registered)")
|
|
27757
|
+
}, async () => {
|
|
27758
|
+
return { content: [{ type: "text", text: `heartbeat at ${new Date().toISOString()}` }] };
|
|
27759
|
+
});
|
|
27760
|
+
server.tool("list_agents", "List registered agents", {}, async () => {
|
|
27761
|
+
const agents = [...mcpAgentRegistry.values()];
|
|
27762
|
+
return { content: [{ type: "text", text: agents.length > 0 ? JSON.stringify(agents) : "No agents registered" }] };
|
|
27763
|
+
});
|
|
27764
|
+
server.tool("set_focus", "Set active project context", {
|
|
27765
|
+
agent_id: exports_external.string().describe("Agent ID"),
|
|
27766
|
+
project_id: exports_external.string().optional().describe("Project to focus on")
|
|
27767
|
+
}, async ({ agent_id, project_id }) => {
|
|
27768
|
+
const agent = mcpAgentRegistry.get(agent_id);
|
|
27769
|
+
if (!agent)
|
|
27770
|
+
return { content: [{ type: "text", text: `Agent not found: ${agent_id}` }], isError: true };
|
|
27771
|
+
agent.project_id = project_id;
|
|
27772
|
+
return { content: [{ type: "text", text: project_id ? `Focus set: ${project_id}` : "Focus cleared" }] };
|
|
27773
|
+
});
|
|
27774
|
+
return server;
|
|
27775
|
+
}
|
|
27776
|
+
async function main() {
|
|
27777
|
+
const { http, port } = parseHttpArgv();
|
|
27778
|
+
if (http) {
|
|
27779
|
+
await runMcpHttpServer({ port: resolveMcpHttpPort(port) });
|
|
27780
|
+
return;
|
|
27781
|
+
}
|
|
27782
|
+
const server = buildServer();
|
|
27783
|
+
const transport = new StdioServerTransport;
|
|
26100
27784
|
await server.connect(transport);
|
|
26101
27785
|
}
|
|
26102
27786
|
main().catch((err) => {
|
|
26103
27787
|
console.error("cloud-mcp failed to start:", err);
|
|
26104
27788
|
process.exit(1);
|
|
26105
27789
|
});
|
|
27790
|
+
export {
|
|
27791
|
+
buildServer
|
|
27792
|
+
};
|