@formstr/mcp 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -6
- package/dist/index.js +341 -69
- package/dist/index.js.map +1 -1
- package/package.json +14 -13
package/dist/index.js
CHANGED
|
@@ -24568,7 +24568,13 @@ function fileBackend() {
|
|
|
24568
24568
|
if (!passphrase) {
|
|
24569
24569
|
throw new Error("Encrypted keystore file exists but FORMSTR_MCP_PASSPHRASE is not set.");
|
|
24570
24570
|
}
|
|
24571
|
-
|
|
24571
|
+
try {
|
|
24572
|
+
return parseMap(decryptBlob((0, import_node_fs.readFileSync)(path, "utf8"), passphrase));
|
|
24573
|
+
} catch {
|
|
24574
|
+
throw new Error(
|
|
24575
|
+
`Could not decrypt the keystore file (${path}): FORMSTR_MCP_PASSPHRASE does not match the passphrase it was encrypted with (or the file is corrupt). Fix the passphrase, or delete the file and run \`formstr-mcp login\` to sign in again.`
|
|
24576
|
+
);
|
|
24577
|
+
}
|
|
24572
24578
|
},
|
|
24573
24579
|
save(map) {
|
|
24574
24580
|
const passphrase = process.env.FORMSTR_MCP_PASSPHRASE;
|
|
@@ -27493,12 +27499,23 @@ async function unlockNcryptsec(signer, account, deps) {
|
|
|
27493
27499
|
}
|
|
27494
27500
|
|
|
27495
27501
|
// src/cli.ts
|
|
27496
|
-
var SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
27502
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
27503
|
+
"login",
|
|
27504
|
+
"logout",
|
|
27505
|
+
"whoami",
|
|
27506
|
+
"accounts",
|
|
27507
|
+
"switch",
|
|
27508
|
+
"help",
|
|
27509
|
+
"version"
|
|
27510
|
+
]);
|
|
27497
27511
|
function parseCli(argv) {
|
|
27498
27512
|
const rest = [...argv];
|
|
27499
27513
|
if (rest.includes("-h") || rest.includes("--help")) {
|
|
27500
27514
|
return { command: "help", allowWrites: false };
|
|
27501
27515
|
}
|
|
27516
|
+
if (rest.includes("-v") || rest.includes("--version")) {
|
|
27517
|
+
return { command: "version", allowWrites: false };
|
|
27518
|
+
}
|
|
27502
27519
|
let command = "run";
|
|
27503
27520
|
if (rest[0] && SUBCOMMANDS.has(rest[0])) {
|
|
27504
27521
|
command = rest.shift();
|
|
@@ -27539,15 +27556,40 @@ function helpText() {
|
|
|
27539
27556
|
" accounts List stored accounts ('*' marks the active one).",
|
|
27540
27557
|
" switch <npub> Set the active account (accepts an npub or hex pubkey).",
|
|
27541
27558
|
" help Show this help (also -h, --help).",
|
|
27559
|
+
" version Print the installed version and check for an update (also -v, --version).",
|
|
27542
27560
|
"",
|
|
27543
27561
|
"Flags:",
|
|
27544
27562
|
" --allow-writes Enable gated write tools (update / delete / share / submit).",
|
|
27563
|
+
" Note: create/import tools (create_form, create_calendar_event, \u2026)",
|
|
27564
|
+
" are ALWAYS enabled and publish to relays even without this flag.",
|
|
27545
27565
|
" --account <npub|hex> Boot a specific account instead of the active one.",
|
|
27546
27566
|
" --relays <a,b,\u2026> Override relays (comma-separated).",
|
|
27547
27567
|
"",
|
|
27548
27568
|
"Env:",
|
|
27549
27569
|
" FORMSTR_MCP_NCRYPTSEC_PASSPHRASE Unlock the active ncryptsec account at boot.",
|
|
27550
|
-
" FORMSTR_MCP_PASSPHRASE Encrypt the keystore file (keychain-less hosts)."
|
|
27570
|
+
" FORMSTR_MCP_PASSPHRASE Encrypt the keystore file (keychain-less hosts).",
|
|
27571
|
+
"",
|
|
27572
|
+
"Setting the passphrase in your MCP host config:",
|
|
27573
|
+
" When an MCP host (Claude Desktop/Code, Cursor, \u2026) spawns the server, stdin is the",
|
|
27574
|
+
" JSON-RPC channel, so it can't prompt for your ncryptsec passphrase. Pass it via an",
|
|
27575
|
+
' "env" block in the server entry of your MCP config (e.g. mcp_config.json /',
|
|
27576
|
+
" claude_desktop_config.json) so the host hands it to the server at startup:",
|
|
27577
|
+
"",
|
|
27578
|
+
" {",
|
|
27579
|
+
' "mcpServers": {',
|
|
27580
|
+
' "formstr": {',
|
|
27581
|
+
' "command": "npx",',
|
|
27582
|
+
' "args": ["-y", "@formstr/mcp"],',
|
|
27583
|
+
' "env": {',
|
|
27584
|
+
' "FORMSTR_MCP_NCRYPTSEC_PASSPHRASE": "your-passphrase-here"',
|
|
27585
|
+
" }",
|
|
27586
|
+
" }",
|
|
27587
|
+
" }",
|
|
27588
|
+
" }",
|
|
27589
|
+
"",
|
|
27590
|
+
" Tip: a NIP-46 (bunker) account needs no passphrase \u2014 `switch` to one and the config",
|
|
27591
|
+
' can stay secret-free. Add "--allow-writes" to args to enable gated write tools',
|
|
27592
|
+
" (creates are always on)."
|
|
27551
27593
|
].join("\n");
|
|
27552
27594
|
}
|
|
27553
27595
|
|
|
@@ -32373,11 +32415,11 @@ async function decryptListEntries(signer, userPubkey, content) {
|
|
|
32373
32415
|
const parsed = JSON.parse(decrypted);
|
|
32374
32416
|
return Array.isArray(parsed) ? parsed : [];
|
|
32375
32417
|
}
|
|
32376
|
-
async function publishMyFormsList(signer, relays, entries) {
|
|
32418
|
+
async function publishMyFormsList(signer, relays, entries, prevCreatedAt) {
|
|
32377
32419
|
const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(entries));
|
|
32378
32420
|
const event = {
|
|
32379
32421
|
kind: FORM_KINDS.myFormsList,
|
|
32380
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
32422
|
+
created_at: Math.max(Math.floor(Date.now() / 1e3), prevCreatedAt + 1),
|
|
32381
32423
|
tags: [],
|
|
32382
32424
|
content: encrypted
|
|
32383
32425
|
};
|
|
@@ -32431,20 +32473,33 @@ async function fetchMyForms() {
|
|
|
32431
32473
|
return summary;
|
|
32432
32474
|
}).filter((s) => s !== null);
|
|
32433
32475
|
}
|
|
32476
|
+
var MY_FORMS_LIST_READ_ATTEMPTS = 3;
|
|
32477
|
+
async function readMyFormsEntriesForWrite(signer, userPubkey, relays) {
|
|
32478
|
+
let listEvent = null;
|
|
32479
|
+
for (let attempt = 0; attempt < MY_FORMS_LIST_READ_ATTEMPTS; attempt++) {
|
|
32480
|
+
listEvent = await fetchLatestMyFormsEvent(relays, userPubkey);
|
|
32481
|
+
if (listEvent?.content) break;
|
|
32482
|
+
}
|
|
32483
|
+
if (!listEvent?.content) return { entries: [], prevCreatedAt: 0 };
|
|
32484
|
+
try {
|
|
32485
|
+
const entries = await decryptListEntries(signer, userPubkey, listEvent.content);
|
|
32486
|
+
return { entries, prevCreatedAt: listEvent.created_at };
|
|
32487
|
+
} catch (err) {
|
|
32488
|
+
throw new Error(
|
|
32489
|
+
`Could not read your existing forms list to add this form, so it was left unchanged to avoid losing your other forms. The form itself was published \u2014 retry to add it. (${err instanceof Error ? err.message : String(err)})`
|
|
32490
|
+
);
|
|
32491
|
+
}
|
|
32492
|
+
}
|
|
32434
32493
|
async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, viewKeyHex) {
|
|
32435
32494
|
const signer = await signerManager.getSigner();
|
|
32436
32495
|
const userPubkey = await signer.getPublicKey();
|
|
32437
32496
|
const relays = relayManager.getRelaysForModule("forms");
|
|
32438
|
-
const existing = await
|
|
32439
|
-
|
|
32440
|
-
|
|
32441
|
-
|
|
32442
|
-
|
|
32443
|
-
|
|
32444
|
-
entries = [];
|
|
32445
|
-
}
|
|
32446
|
-
}
|
|
32447
|
-
entries = entries.map(
|
|
32497
|
+
const { entries: existing, prevCreatedAt } = await readMyFormsEntriesForWrite(
|
|
32498
|
+
signer,
|
|
32499
|
+
userPubkey,
|
|
32500
|
+
relays
|
|
32501
|
+
);
|
|
32502
|
+
const entries = existing.map(
|
|
32448
32503
|
(e) => e[0] === "f" && e.length < 4 ? ["f", e[1] ?? "", e[2] ?? "", ""] : e
|
|
32449
32504
|
);
|
|
32450
32505
|
const coordKey = `${formPubkey}:${formId}`;
|
|
@@ -32452,10 +32507,11 @@ async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, vie
|
|
|
32452
32507
|
const secrets = signingKeyHex ? encodeFormKeys(signingKeyHex, viewKeyHex) : "";
|
|
32453
32508
|
entries.push(["f", coordKey, relay, secrets]);
|
|
32454
32509
|
}
|
|
32455
|
-
await publishMyFormsList(signer, relays, entries);
|
|
32510
|
+
await publishMyFormsList(signer, relays, entries, prevCreatedAt);
|
|
32456
32511
|
}
|
|
32457
32512
|
async function saveToMyForms(summaries) {
|
|
32458
32513
|
const signer = await signerManager.getSigner();
|
|
32514
|
+
const userPubkey = await signer.getPublicKey();
|
|
32459
32515
|
const relays = relayManager.getRelaysForModule("forms");
|
|
32460
32516
|
const entries = summaries.map((s) => [
|
|
32461
32517
|
"f",
|
|
@@ -32463,7 +32519,8 @@ async function saveToMyForms(summaries) {
|
|
|
32463
32519
|
s.relay ?? "",
|
|
32464
32520
|
s.signingKey ? encodeFormKeys(s.signingKey, s.viewKey) : ""
|
|
32465
32521
|
]);
|
|
32466
|
-
await
|
|
32522
|
+
const prev = await fetchLatestMyFormsEvent(relays, userPubkey);
|
|
32523
|
+
await publishMyFormsList(signer, relays, entries, prev?.created_at ?? 0);
|
|
32467
32524
|
}
|
|
32468
32525
|
async function fetchMyFormsByAuthor(pubkey, relays) {
|
|
32469
32526
|
const filter = {
|
|
@@ -32510,7 +32567,7 @@ async function deleteForm(formId, formPubkey) {
|
|
|
32510
32567
|
const coordKey = `${formPubkey}:${formId}`;
|
|
32511
32568
|
const trimmed = entries.filter((e) => e[1] !== coordKey);
|
|
32512
32569
|
if (trimmed.length === entries.length) return;
|
|
32513
|
-
await publishMyFormsList(signer, relays, trimmed);
|
|
32570
|
+
await publishMyFormsList(signer, relays, trimmed, listEvent.created_at);
|
|
32514
32571
|
}
|
|
32515
32572
|
async function updateForm(params) {
|
|
32516
32573
|
const relays = relayManager.getRelaysForModule("forms");
|
|
@@ -32627,9 +32684,13 @@ async function fetchFormSummaryFromRef(pubkey, formId) {
|
|
|
32627
32684
|
};
|
|
32628
32685
|
}
|
|
32629
32686
|
async function importForm(summary) {
|
|
32630
|
-
|
|
32631
|
-
|
|
32632
|
-
|
|
32687
|
+
await appendToMyFormsList(
|
|
32688
|
+
summary.pubkey,
|
|
32689
|
+
summary.id,
|
|
32690
|
+
summary.relay ?? "",
|
|
32691
|
+
summary.signingKey,
|
|
32692
|
+
summary.viewKey
|
|
32693
|
+
);
|
|
32633
32694
|
}
|
|
32634
32695
|
function parseFormEvent(event, decryptedRows) {
|
|
32635
32696
|
const merged = decryptedRows ? [...event.tags, ...decryptedRows] : event.tags;
|
|
@@ -34242,7 +34303,7 @@ async function fetchAllDocMetadata() {
|
|
|
34242
34303
|
}
|
|
34243
34304
|
return result;
|
|
34244
34305
|
}
|
|
34245
|
-
async function
|
|
34306
|
+
async function fetchNewestDocMetadataEvent(address) {
|
|
34246
34307
|
const signer = await signerManager.getSigner();
|
|
34247
34308
|
const pubkey = await signer.getPublicKey();
|
|
34248
34309
|
const events = await nostrRuntime.querySync(RELAYS(), {
|
|
@@ -34251,7 +34312,12 @@ async function fetchDocMetadata(address) {
|
|
|
34251
34312
|
"#d": [address]
|
|
34252
34313
|
});
|
|
34253
34314
|
if (events.length === 0) return void 0;
|
|
34254
|
-
|
|
34315
|
+
return events.reduce((a, b) => b.created_at > a.created_at ? b : a);
|
|
34316
|
+
}
|
|
34317
|
+
async function fetchDocMetadata(address) {
|
|
34318
|
+
const signer = await signerManager.getSigner();
|
|
34319
|
+
const newest = await fetchNewestDocMetadataEvent(address);
|
|
34320
|
+
if (!newest) return void 0;
|
|
34255
34321
|
try {
|
|
34256
34322
|
return parseMetadataJson(await nip44SelfDecrypt(signer, newest.content));
|
|
34257
34323
|
} catch {
|
|
@@ -34259,14 +34325,24 @@ async function fetchDocMetadata(address) {
|
|
|
34259
34325
|
}
|
|
34260
34326
|
}
|
|
34261
34327
|
async function saveDocMetadata(address, patch) {
|
|
34262
|
-
const
|
|
34328
|
+
const signer = await signerManager.getSigner();
|
|
34329
|
+
const prevEvent = await fetchNewestDocMetadataEvent(address);
|
|
34330
|
+
let existing = {};
|
|
34331
|
+
if (prevEvent) {
|
|
34332
|
+
try {
|
|
34333
|
+
existing = parseMetadataJson(await nip44SelfDecrypt(signer, prevEvent.content)) ?? {};
|
|
34334
|
+
} catch {
|
|
34335
|
+
}
|
|
34336
|
+
}
|
|
34263
34337
|
const merged = { ...existing, ...patch };
|
|
34264
34338
|
if (!Array.isArray(merged.tags)) merged.tags = [];
|
|
34265
|
-
const signer = await signerManager.getSigner();
|
|
34266
34339
|
const content = await nip44SelfEncrypt(signer, JSON.stringify(merged));
|
|
34267
34340
|
const signed = await signer.signEvent({
|
|
34268
34341
|
kind: PAGES_KINDS.docMetadata,
|
|
34269
|
-
|
|
34342
|
+
// Strictly supersede the event this merge was read from: two saves in the
|
|
34343
|
+
// same second otherwise tie on created_at and NIP-01 tie-breaking (lowest
|
|
34344
|
+
// id wins) can resurrect the stale copy — losing a just-saved viewKey/title.
|
|
34345
|
+
created_at: Math.max(Math.floor(Date.now() / 1e3), (prevEvent?.created_at ?? 0) + 1),
|
|
34270
34346
|
tags: [["d", address]],
|
|
34271
34347
|
content
|
|
34272
34348
|
});
|
|
@@ -34515,14 +34591,22 @@ async function fetchFileIndex() {
|
|
|
34515
34591
|
}
|
|
34516
34592
|
async function saveFileMetadata(metadata) {
|
|
34517
34593
|
const signer = await signerManager.getSigner();
|
|
34594
|
+
const relays = relayManager.getRelaysForModule("drive");
|
|
34518
34595
|
const full = {
|
|
34519
34596
|
...metadata,
|
|
34520
34597
|
encryptionAlgorithm: metadata.encryptionAlgorithm ?? "aes-gcm"
|
|
34521
34598
|
};
|
|
34522
34599
|
const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(full));
|
|
34600
|
+
const pubkey = await signer.getPublicKey();
|
|
34601
|
+
const existing = await nostrRuntime.querySync(relays, {
|
|
34602
|
+
kinds: [DRIVE_KINDS.fileMetadata],
|
|
34603
|
+
authors: [pubkey],
|
|
34604
|
+
"#d": [metadata.hash]
|
|
34605
|
+
});
|
|
34606
|
+
const prevCreatedAt = existing.reduce((max, e) => Math.max(max, e.created_at), 0);
|
|
34523
34607
|
const event = {
|
|
34524
34608
|
kind: DRIVE_KINDS.fileMetadata,
|
|
34525
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
34609
|
+
created_at: Math.max(Math.floor(Date.now() / 1e3), prevCreatedAt + 1),
|
|
34526
34610
|
tags: [
|
|
34527
34611
|
["d", metadata.hash],
|
|
34528
34612
|
["client", "formstr-drive"],
|
|
@@ -34531,7 +34615,6 @@ async function saveFileMetadata(metadata) {
|
|
|
34531
34615
|
content: encrypted
|
|
34532
34616
|
};
|
|
34533
34617
|
const signed = await signer.signEvent(event);
|
|
34534
|
-
const relays = relayManager.getRelaysForModule("drive");
|
|
34535
34618
|
await nostrRuntime.publish(relays, signed);
|
|
34536
34619
|
}
|
|
34537
34620
|
async function updateFileMetadata(hash, updates) {
|
|
@@ -34616,6 +34699,7 @@ __export(service_exports5, {
|
|
|
34616
34699
|
});
|
|
34617
34700
|
|
|
34618
34701
|
// ../agent/src/services/polls/pow.ts
|
|
34702
|
+
var MAX_MINED_POW_DIFFICULTY = 20;
|
|
34619
34703
|
function minePollEvent(unsigned, difficulty) {
|
|
34620
34704
|
const event = unsigned;
|
|
34621
34705
|
const nonceTag = ["nonce", "0", difficulty.toString()];
|
|
@@ -34708,6 +34792,11 @@ async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRel
|
|
|
34708
34792
|
tags,
|
|
34709
34793
|
content: ""
|
|
34710
34794
|
};
|
|
34795
|
+
if (powDifficulty && powDifficulty > MAX_MINED_POW_DIFFICULTY) {
|
|
34796
|
+
throw new Error(
|
|
34797
|
+
`This poll demands proof-of-work difficulty ${powDifficulty}, above the supported maximum of ${MAX_MINED_POW_DIFFICULTY} \u2014 refusing to mine (it could take hours).`
|
|
34798
|
+
);
|
|
34799
|
+
}
|
|
34711
34800
|
if (powDifficulty && powDifficulty > 0) {
|
|
34712
34801
|
const unsigned = { ...event, pubkey: await signer.getPublicKey() };
|
|
34713
34802
|
const mined = minePollEvent(unsigned, powDifficulty);
|
|
@@ -34898,6 +34987,16 @@ function normalizePubkeyList(values) {
|
|
|
34898
34987
|
}
|
|
34899
34988
|
|
|
34900
34989
|
// ../agent/src/tools/calendar.ts
|
|
34990
|
+
function parseIsoDate(value) {
|
|
34991
|
+
const date3 = new Date(value);
|
|
34992
|
+
return Number.isFinite(date3.getTime()) ? date3 : null;
|
|
34993
|
+
}
|
|
34994
|
+
function badDate(field, value) {
|
|
34995
|
+
return fail(
|
|
34996
|
+
`Could not parse ${field} "${value}" \u2014 pass an ISO 8601 date-time (e.g. 2026-07-02T15:00:00Z).`,
|
|
34997
|
+
"BAD_INPUT"
|
|
34998
|
+
);
|
|
34999
|
+
}
|
|
34901
35000
|
var calendarTools = buildCalendarTools();
|
|
34902
35001
|
function buildCalendarTools() {
|
|
34903
35002
|
const tools = [];
|
|
@@ -34914,11 +35013,15 @@ function buildCalendarTools() {
|
|
|
34914
35013
|
inputSchema: { since: external_exports.string().optional(), until: external_exports.string().optional() }
|
|
34915
35014
|
},
|
|
34916
35015
|
async ({ since, until }) => {
|
|
35016
|
+
const sinceDate = since ? parseIsoDate(since) : void 0;
|
|
35017
|
+
if (since && !sinceDate) return badDate("since", since);
|
|
35018
|
+
const untilDate = until ? parseIsoDate(until) : void 0;
|
|
35019
|
+
if (until && !untilDate) return badDate("until", until);
|
|
34917
35020
|
const pubkey = signerManager.getPublicKey();
|
|
34918
35021
|
const events = await service_exports2.fetchCalendarEventsSync({
|
|
34919
35022
|
authors: pubkey ? [pubkey] : void 0,
|
|
34920
|
-
since:
|
|
34921
|
-
until:
|
|
35023
|
+
since: sinceDate ? Math.floor(sinceDate.getTime() / 1e3) : void 0,
|
|
35024
|
+
until: untilDate ? Math.floor(untilDate.getTime() / 1e3) : void 0
|
|
34922
35025
|
});
|
|
34923
35026
|
return ok(`Found ${events.length} event(s).`, {
|
|
34924
35027
|
events: events.map((e) => ({
|
|
@@ -34936,7 +35039,7 @@ function buildCalendarTools() {
|
|
|
34936
35039
|
server.registerTool(
|
|
34937
35040
|
"create_calendar_event",
|
|
34938
35041
|
{
|
|
34939
|
-
description: "Schedule a calendar event. start/end are ISO 8601. Events default to PRIVATE (encrypted) and are linked into a calendar list \u2014 that is the only way they show on calendar.formstr.app, which renders only events referenced in a calendar list. Pass calendarId to choose which calendar; if omitted and the user already has calendars, this tool returns the list so you can ASK the user which one (then re-run with calendarId). Set isPrivate:false for a public, unencrypted event \u2014 note public events do NOT sync to calendar.formstr.app. participants (npub or hex) receive NIP-59 invitations.",
|
|
35042
|
+
description: "Schedule a calendar event. start/end are ISO 8601. Events default to PRIVATE (encrypted) and are linked into a calendar list \u2014 that is the only way they show on calendar.formstr.app, which renders only events referenced in a calendar list. Pass calendarId to choose which calendar; if omitted and the user already has calendars, this tool returns the list so you can ASK the user which one (then re-run with calendarId). Set isPrivate:false for a public, unencrypted event \u2014 note public events do NOT sync to calendar.formstr.app. participants (npub or hex) receive NIP-59 invitations. registrationFormRef attaches a Formstr form; for an ENCRYPTED form also pass registrationFormViewKey or attendees cannot read it.",
|
|
34940
35043
|
inputSchema: {
|
|
34941
35044
|
title: external_exports.string(),
|
|
34942
35045
|
description: external_exports.string().optional(),
|
|
@@ -34948,10 +35051,15 @@ function buildCalendarTools() {
|
|
|
34948
35051
|
participants: external_exports.array(external_exports.string()).optional(),
|
|
34949
35052
|
rrule: external_exports.string().optional(),
|
|
34950
35053
|
startTzid: external_exports.string().optional(),
|
|
34951
|
-
registrationFormRef: external_exports.string().optional()
|
|
35054
|
+
registrationFormRef: external_exports.string().optional(),
|
|
35055
|
+
registrationFormViewKey: external_exports.string().optional()
|
|
34952
35056
|
}
|
|
34953
35057
|
},
|
|
34954
35058
|
async (args) => {
|
|
35059
|
+
const begin = parseIsoDate(args.start);
|
|
35060
|
+
if (!begin) return badDate("start", args.start);
|
|
35061
|
+
const end = args.end ? parseIsoDate(args.end) : new Date(begin.getTime() + 36e5);
|
|
35062
|
+
if (!end) return badDate("end", args.end);
|
|
34955
35063
|
const isPrivate = args.isPrivate ?? true;
|
|
34956
35064
|
const calendars = await service_exports2.fetchCalendarLists();
|
|
34957
35065
|
if (isPrivate && !args.calendarId && calendars.length > 0) {
|
|
@@ -34968,8 +35076,6 @@ function buildCalendarTools() {
|
|
|
34968
35076
|
"NOT_FOUND"
|
|
34969
35077
|
);
|
|
34970
35078
|
}
|
|
34971
|
-
const begin = new Date(args.start);
|
|
34972
|
-
const end = args.end ? new Date(args.end) : new Date(begin.getTime() + 36e5);
|
|
34973
35079
|
const draft = {
|
|
34974
35080
|
title: args.title,
|
|
34975
35081
|
description: args.description ?? "",
|
|
@@ -34983,17 +35089,20 @@ function buildCalendarTools() {
|
|
|
34983
35089
|
calendarId: args.calendarId,
|
|
34984
35090
|
rrule: args.rrule,
|
|
34985
35091
|
startTzid: args.startTzid,
|
|
34986
|
-
registrationFormRef: args.registrationFormRef
|
|
35092
|
+
registrationFormRef: args.registrationFormRef,
|
|
35093
|
+
registrationFormViewKey: args.registrationFormViewKey
|
|
34987
35094
|
};
|
|
34988
35095
|
const { event, calendar: list } = await service_exports2.createCalendarEvent(draft, { calendars });
|
|
34989
35096
|
const coordinate = `${event.kind}:${event.user}:${event.id}`;
|
|
35097
|
+
const invitationsSent = isPrivate ? draft.participants?.length ?? 0 : 0;
|
|
34990
35098
|
return ok(
|
|
34991
|
-
`Created ${isPrivate ? "private" : "public"} event "${args.title}"${list ? ` in calendar "${list.title}"` : ""}.`,
|
|
35099
|
+
`Created ${isPrivate ? "private" : "public"} event "${args.title}"${list ? ` in calendar "${list.title}"` : ""}${invitationsSent ? ` \u2014 sent ${invitationsSent} invitation(s)` : ""}.`,
|
|
34992
35100
|
{
|
|
34993
35101
|
id: event.id,
|
|
34994
35102
|
eventId: event.eventId,
|
|
34995
35103
|
coordinate,
|
|
34996
|
-
calendarId: list?.id
|
|
35104
|
+
calendarId: list?.id,
|
|
35105
|
+
invitationsSent
|
|
34997
35106
|
}
|
|
34998
35107
|
);
|
|
34999
35108
|
}
|
|
@@ -35012,12 +35121,19 @@ function buildCalendarTools() {
|
|
|
35012
35121
|
event: {
|
|
35013
35122
|
id: event.id,
|
|
35014
35123
|
title: event.title,
|
|
35124
|
+
description: event.description,
|
|
35015
35125
|
begin: event.begin,
|
|
35016
35126
|
end: event.end,
|
|
35017
35127
|
location: event.location,
|
|
35018
35128
|
isPrivate: event.isPrivate,
|
|
35019
35129
|
rrule: event.repeat?.rrule ?? null,
|
|
35020
|
-
|
|
35130
|
+
startTzid: event.startTzid ?? null,
|
|
35131
|
+
participants: event.participants,
|
|
35132
|
+
calendarId: event.calendarId ?? null,
|
|
35133
|
+
// The attached form ref is surfaced so a write can be verified; the
|
|
35134
|
+
// form's viewKey itself is never returned (no key material in results).
|
|
35135
|
+
registrationFormRef: event.registrationFormRef ?? null,
|
|
35136
|
+
registrationFormHasViewKey: Boolean(event.registrationFormViewKey)
|
|
35021
35137
|
}
|
|
35022
35138
|
});
|
|
35023
35139
|
}
|
|
@@ -35028,7 +35144,12 @@ function buildCalendarTools() {
|
|
|
35028
35144
|
async () => {
|
|
35029
35145
|
const lists = await service_exports2.fetchCalendarLists();
|
|
35030
35146
|
return ok(`Found ${lists.length} calendar(s).`, {
|
|
35031
|
-
calendars: lists.map((c) => ({
|
|
35147
|
+
calendars: lists.map((c) => ({
|
|
35148
|
+
id: c.id,
|
|
35149
|
+
title: c.title,
|
|
35150
|
+
color: c.color,
|
|
35151
|
+
description: c.description
|
|
35152
|
+
}))
|
|
35032
35153
|
});
|
|
35033
35154
|
}
|
|
35034
35155
|
);
|
|
@@ -35237,7 +35358,7 @@ function buildCalendarTools() {
|
|
|
35237
35358
|
server.registerTool(
|
|
35238
35359
|
"update_calendar_event",
|
|
35239
35360
|
{
|
|
35240
|
-
description: "Update a calendar event by its coordinate kind:pubkey:d. Only changed fields need be sent. Requires confirm:true.",
|
|
35361
|
+
description: "Update a calendar event by its coordinate kind:pubkey:d. Only changed fields need be sent. registrationFormRef attaches/replaces the event's registration form (empty string detaches it); for an ENCRYPTED form also pass registrationFormViewKey or attendees cannot read it. Requires confirm:true.",
|
|
35241
35362
|
inputSchema: {
|
|
35242
35363
|
coordinate: external_exports.string(),
|
|
35243
35364
|
title: external_exports.string().optional(),
|
|
@@ -35247,6 +35368,8 @@ function buildCalendarTools() {
|
|
|
35247
35368
|
location: external_exports.string().optional(),
|
|
35248
35369
|
rrule: external_exports.string().optional(),
|
|
35249
35370
|
startTzid: external_exports.string().optional(),
|
|
35371
|
+
registrationFormRef: external_exports.string().optional(),
|
|
35372
|
+
registrationFormViewKey: external_exports.string().optional(),
|
|
35250
35373
|
confirm: external_exports.boolean().optional()
|
|
35251
35374
|
}
|
|
35252
35375
|
},
|
|
@@ -35257,21 +35380,27 @@ function buildCalendarTools() {
|
|
|
35257
35380
|
`updates event ${args.coordinate}`
|
|
35258
35381
|
);
|
|
35259
35382
|
if (blocked) return blocked;
|
|
35383
|
+
const newBegin = args.start ? parseIsoDate(args.start) : void 0;
|
|
35384
|
+
if (args.start && !newBegin) return badDate("start", args.start);
|
|
35385
|
+
const newEnd = args.end ? parseIsoDate(args.end) : void 0;
|
|
35386
|
+
if (args.end && !newEnd) return badDate("end", args.end);
|
|
35260
35387
|
const viewKey = await service_exports2.lookupEventViewKey(args.coordinate);
|
|
35261
35388
|
const existing = await service_exports2.fetchCalendarEventByCoordinate(args.coordinate, viewKey);
|
|
35262
35389
|
if (!existing) return fail(`No event found for ${args.coordinate}.`, "NOT_FOUND");
|
|
35390
|
+
const formRef = args.registrationFormRef === void 0 ? existing.registrationFormRef : args.registrationFormRef || void 0;
|
|
35391
|
+
const formViewKey = !formRef ? void 0 : args.registrationFormViewKey ?? (formRef === existing.registrationFormRef ? existing.registrationFormViewKey : void 0);
|
|
35263
35392
|
const draft = {
|
|
35264
35393
|
title: args.title ?? existing.title,
|
|
35265
35394
|
description: args.description ?? existing.description,
|
|
35266
|
-
begin:
|
|
35267
|
-
end:
|
|
35395
|
+
begin: newBegin ?? new Date(existing.begin),
|
|
35396
|
+
end: newEnd ?? new Date(existing.end),
|
|
35268
35397
|
location: args.location ?? existing.location[0],
|
|
35269
35398
|
participants: existing.participants,
|
|
35270
35399
|
isPrivate: existing.isPrivate,
|
|
35271
35400
|
rrule: args.rrule ?? existing.repeat.rrule ?? void 0,
|
|
35272
35401
|
startTzid: args.startTzid ?? existing.startTzid,
|
|
35273
|
-
registrationFormRef:
|
|
35274
|
-
registrationFormViewKey:
|
|
35402
|
+
registrationFormRef: formRef,
|
|
35403
|
+
registrationFormViewKey: formViewKey,
|
|
35275
35404
|
notificationPreference: existing.notificationPreference,
|
|
35276
35405
|
viewKey: existing.viewKey,
|
|
35277
35406
|
existingId: existing.id
|
|
@@ -35286,14 +35415,15 @@ function buildCalendarTools() {
|
|
|
35286
35415
|
server.registerTool(
|
|
35287
35416
|
"attach_form_to_event",
|
|
35288
35417
|
{
|
|
35289
|
-
description: "Attach a Formstr form (naddr or coordinate) as an event's registration form. Requires confirm:true.",
|
|
35418
|
+
description: "Attach a Formstr form (naddr or coordinate) as an event's registration form. For an ENCRYPTED form, also pass formViewKey (the form's view key) \u2014 without it attendees cannot read the attached form. Requires confirm:true.",
|
|
35290
35419
|
inputSchema: {
|
|
35291
35420
|
coordinate: external_exports.string(),
|
|
35292
35421
|
formRef: external_exports.string(),
|
|
35422
|
+
formViewKey: external_exports.string().optional(),
|
|
35293
35423
|
confirm: external_exports.boolean().optional()
|
|
35294
35424
|
}
|
|
35295
35425
|
},
|
|
35296
|
-
async ({ coordinate, formRef, confirm }) => {
|
|
35426
|
+
async ({ coordinate, formRef, formViewKey, confirm }) => {
|
|
35297
35427
|
const blocked = requireConfirm(
|
|
35298
35428
|
"attach_form_to_event",
|
|
35299
35429
|
{ confirm },
|
|
@@ -35303,6 +35433,7 @@ function buildCalendarTools() {
|
|
|
35303
35433
|
const viewKey = await service_exports2.lookupEventViewKey(coordinate);
|
|
35304
35434
|
const existing = await service_exports2.fetchCalendarEventByCoordinate(coordinate, viewKey);
|
|
35305
35435
|
if (!existing) return fail(`No event found for ${coordinate}.`, "NOT_FOUND");
|
|
35436
|
+
const registrationFormViewKey = formViewKey ?? (formRef === existing.registrationFormRef ? existing.registrationFormViewKey : void 0);
|
|
35306
35437
|
const draft = {
|
|
35307
35438
|
title: existing.title,
|
|
35308
35439
|
description: existing.description,
|
|
@@ -35314,6 +35445,7 @@ function buildCalendarTools() {
|
|
|
35314
35445
|
rrule: existing.repeat.rrule ?? void 0,
|
|
35315
35446
|
startTzid: existing.startTzid,
|
|
35316
35447
|
registrationFormRef: formRef,
|
|
35448
|
+
registrationFormViewKey,
|
|
35317
35449
|
notificationPreference: existing.notificationPreference,
|
|
35318
35450
|
viewKey: existing.viewKey,
|
|
35319
35451
|
existingId: existing.id
|
|
@@ -35506,7 +35638,7 @@ function buildDriveTools() {
|
|
|
35506
35638
|
server.registerTool(
|
|
35507
35639
|
"delete_file",
|
|
35508
35640
|
{
|
|
35509
|
-
description: "
|
|
35641
|
+
description: "Remove a file from your drive index by name. This is a soft delete: the encrypted blob stays on the Blossom server (unreadable without your key) \u2014 only the drive listing forgets it. Requires confirm:true.",
|
|
35510
35642
|
inputSchema: {
|
|
35511
35643
|
name: external_exports.string(),
|
|
35512
35644
|
folder: external_exports.string().optional(),
|
|
@@ -35519,7 +35651,9 @@ function buildDriveTools() {
|
|
|
35519
35651
|
const resolved = await resolveFile(name, folder);
|
|
35520
35652
|
if ("error" in resolved) return resolved.error;
|
|
35521
35653
|
await service_exports4.deleteFile(resolved.file);
|
|
35522
|
-
return ok(
|
|
35654
|
+
return ok(
|
|
35655
|
+
`Removed "${name}" from your drive index. The encrypted blob remains on the Blossom server; it is unreadable without your key.`
|
|
35656
|
+
);
|
|
35523
35657
|
}
|
|
35524
35658
|
);
|
|
35525
35659
|
server.registerTool(
|
|
@@ -36053,16 +36187,29 @@ function buildPagesTools() {
|
|
|
36053
36187
|
return ok(`Saved page "${title}".`, { address: page.address });
|
|
36054
36188
|
}
|
|
36055
36189
|
);
|
|
36190
|
+
server.registerTool(
|
|
36191
|
+
"set_page_tags",
|
|
36192
|
+
{
|
|
36193
|
+
description: "Set the private labels/tags for a document address.",
|
|
36194
|
+
inputSchema: { address: external_exports.string(), tags: external_exports.array(external_exports.string()) }
|
|
36195
|
+
},
|
|
36196
|
+
async ({ address, tags }) => {
|
|
36197
|
+
await service_exports3.setDocTags(address, tags);
|
|
36198
|
+
return ok(`Set ${tags.length} tag(s) on ${address}.`);
|
|
36199
|
+
}
|
|
36200
|
+
);
|
|
36201
|
+
write = true;
|
|
36056
36202
|
server.registerTool(
|
|
36057
36203
|
"update_page",
|
|
36058
36204
|
{
|
|
36059
|
-
description: "Update an existing document. Pass its docId (d-tag) and the new Markdown.",
|
|
36205
|
+
description: "Update an existing document, replacing its content. Pass its docId (d-tag) and the new Markdown. Requires confirm:true.",
|
|
36060
36206
|
inputSchema: {
|
|
36061
36207
|
docId: external_exports.string(),
|
|
36062
36208
|
content: external_exports.string(),
|
|
36063
36209
|
title: external_exports.string().optional(),
|
|
36064
36210
|
viewKey: external_exports.string().optional(),
|
|
36065
|
-
editKey: external_exports.string().optional()
|
|
36211
|
+
editKey: external_exports.string().optional(),
|
|
36212
|
+
confirm: external_exports.boolean().optional()
|
|
36066
36213
|
}
|
|
36067
36214
|
},
|
|
36068
36215
|
async ({
|
|
@@ -36070,8 +36217,15 @@ function buildPagesTools() {
|
|
|
36070
36217
|
content,
|
|
36071
36218
|
title,
|
|
36072
36219
|
viewKey,
|
|
36073
|
-
editKey
|
|
36220
|
+
editKey,
|
|
36221
|
+
confirm
|
|
36074
36222
|
}) => {
|
|
36223
|
+
const blocked = requireConfirm(
|
|
36224
|
+
"update_page",
|
|
36225
|
+
{ confirm },
|
|
36226
|
+
`overwrites document ${docId} with new content`
|
|
36227
|
+
);
|
|
36228
|
+
if (blocked) return blocked;
|
|
36075
36229
|
const page = await service_exports3.savePage({
|
|
36076
36230
|
existingId: docId,
|
|
36077
36231
|
content: withTitle(title, content),
|
|
@@ -36081,18 +36235,6 @@ function buildPagesTools() {
|
|
|
36081
36235
|
return ok(`Updated page ${docId}.`, { address: page.address });
|
|
36082
36236
|
}
|
|
36083
36237
|
);
|
|
36084
|
-
server.registerTool(
|
|
36085
|
-
"set_page_tags",
|
|
36086
|
-
{
|
|
36087
|
-
description: "Set the private labels/tags for a document address.",
|
|
36088
|
-
inputSchema: { address: external_exports.string(), tags: external_exports.array(external_exports.string()) }
|
|
36089
|
-
},
|
|
36090
|
-
async ({ address, tags }) => {
|
|
36091
|
-
await service_exports3.setDocTags(address, tags);
|
|
36092
|
-
return ok(`Set ${tags.length} tag(s) on ${address}.`);
|
|
36093
|
-
}
|
|
36094
|
-
);
|
|
36095
|
-
write = true;
|
|
36096
36238
|
server.registerTool(
|
|
36097
36239
|
"delete_page",
|
|
36098
36240
|
{
|
|
@@ -36242,7 +36384,11 @@ function buildPollsTools() {
|
|
|
36242
36384
|
pubkey: poll.pubkey,
|
|
36243
36385
|
createdAt: poll.createdAt,
|
|
36244
36386
|
endsAt: poll.endsAt,
|
|
36245
|
-
hashtags: poll.hashtags
|
|
36387
|
+
hashtags: poll.hashtags,
|
|
36388
|
+
// Votes on a PoW-gated poll must be NIP-13 mined to this difficulty
|
|
36389
|
+
// (submit_poll_response caps mining at 20) — surface it so callers
|
|
36390
|
+
// know why a vote may be slow or refused.
|
|
36391
|
+
powDifficulty: poll.powDifficulty ?? null
|
|
36246
36392
|
}
|
|
36247
36393
|
});
|
|
36248
36394
|
}
|
|
@@ -36281,11 +36427,18 @@ function buildPollsTools() {
|
|
|
36281
36427
|
}
|
|
36282
36428
|
},
|
|
36283
36429
|
async (args) => {
|
|
36430
|
+
const endsAt = args.endsAt ? new Date(args.endsAt) : void 0;
|
|
36431
|
+
if (args.endsAt && !Number.isFinite(endsAt.getTime())) {
|
|
36432
|
+
return fail(
|
|
36433
|
+
`Could not parse endsAt "${args.endsAt}" \u2014 pass an ISO 8601 date-time (e.g. 2026-07-02T15:00:00Z).`,
|
|
36434
|
+
"BAD_INPUT"
|
|
36435
|
+
);
|
|
36436
|
+
}
|
|
36284
36437
|
const poll = await service_exports5.createPoll({
|
|
36285
36438
|
question: args.question,
|
|
36286
36439
|
options: args.options.map((label) => ({ label })),
|
|
36287
36440
|
pollType: args.pollType ?? "singlechoice",
|
|
36288
|
-
endsAt
|
|
36441
|
+
endsAt,
|
|
36289
36442
|
hashtags: args.hashtags
|
|
36290
36443
|
});
|
|
36291
36444
|
return ok(`Created poll "${args.question}".`, { id: poll.id });
|
|
@@ -36356,6 +36509,26 @@ function buildPollsTools() {
|
|
|
36356
36509
|
return tools;
|
|
36357
36510
|
}
|
|
36358
36511
|
|
|
36512
|
+
// ../agent/src/tools/validate.ts
|
|
36513
|
+
function withStrictArgs(entry) {
|
|
36514
|
+
const schema = external_exports.object(entry.inputSchema).strict();
|
|
36515
|
+
return {
|
|
36516
|
+
...entry,
|
|
36517
|
+
handler: async (args, ctx) => {
|
|
36518
|
+
const parsed = schema.safeParse(args ?? {});
|
|
36519
|
+
if (!parsed.success) {
|
|
36520
|
+
const problems = parsed.error.issues.map((i4) => i4.path.length ? `${i4.path.join(".")}: ${i4.message}` : i4.message).join("; ");
|
|
36521
|
+
const valid = Object.keys(entry.inputSchema);
|
|
36522
|
+
return fail(
|
|
36523
|
+
`Invalid arguments for ${entry.name}: ${problems}. Valid parameters: ${valid.length ? valid.join(", ") : "(none)"}.`,
|
|
36524
|
+
"BAD_INPUT"
|
|
36525
|
+
);
|
|
36526
|
+
}
|
|
36527
|
+
return entry.handler(parsed.data, ctx);
|
|
36528
|
+
}
|
|
36529
|
+
};
|
|
36530
|
+
}
|
|
36531
|
+
|
|
36359
36532
|
// ../agent/src/tools/index.ts
|
|
36360
36533
|
var toolRegistry = [
|
|
36361
36534
|
...formsTools,
|
|
@@ -36363,7 +36536,7 @@ var toolRegistry = [
|
|
|
36363
36536
|
...pagesTools,
|
|
36364
36537
|
...pollsTools,
|
|
36365
36538
|
...driveTools
|
|
36366
|
-
];
|
|
36539
|
+
].map(withStrictArgs);
|
|
36367
36540
|
|
|
36368
36541
|
// ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
|
36369
36542
|
var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
@@ -46536,6 +46709,95 @@ var StdioServerTransport = class {
|
|
|
46536
46709
|
}
|
|
46537
46710
|
};
|
|
46538
46711
|
|
|
46712
|
+
// src/version.ts
|
|
46713
|
+
var import_node_fs2 = require("fs");
|
|
46714
|
+
var import_node_path2 = require("path");
|
|
46715
|
+
var PACKAGE_NAME = "@formstr/mcp";
|
|
46716
|
+
function readInstalledVersion() {
|
|
46717
|
+
try {
|
|
46718
|
+
const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(__dirname, "..", "package.json"), "utf8"));
|
|
46719
|
+
return pkg.version ?? "0.0.0";
|
|
46720
|
+
} catch {
|
|
46721
|
+
return "0.0.0";
|
|
46722
|
+
}
|
|
46723
|
+
}
|
|
46724
|
+
function parseSemver(version2) {
|
|
46725
|
+
const cleaned = version2.trim().replace(/^v/i, "").split("+")[0];
|
|
46726
|
+
const [core, pre] = cleaned.split("-", 2);
|
|
46727
|
+
const release = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
46728
|
+
while (release.length < 3) release.push(0);
|
|
46729
|
+
return { release, prerelease: pre ? pre.split(".") : [] };
|
|
46730
|
+
}
|
|
46731
|
+
function compareIdentifiers(a, b) {
|
|
46732
|
+
const na = /^\d+$/.test(a);
|
|
46733
|
+
const nb = /^\d+$/.test(b);
|
|
46734
|
+
if (na && nb) return Number(a) - Number(b) < 0 ? -1 : Number(a) === Number(b) ? 0 : 1;
|
|
46735
|
+
if (na) return -1;
|
|
46736
|
+
if (nb) return 1;
|
|
46737
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
46738
|
+
}
|
|
46739
|
+
function compareVersions(a, b) {
|
|
46740
|
+
const va = parseSemver(a);
|
|
46741
|
+
const vb = parseSemver(b);
|
|
46742
|
+
for (let i4 = 0; i4 < 3; i4++) {
|
|
46743
|
+
if (va.release[i4] !== vb.release[i4]) return va.release[i4] < vb.release[i4] ? -1 : 1;
|
|
46744
|
+
}
|
|
46745
|
+
if (va.prerelease.length === 0 && vb.prerelease.length === 0) return 0;
|
|
46746
|
+
if (va.prerelease.length === 0) return 1;
|
|
46747
|
+
if (vb.prerelease.length === 0) return -1;
|
|
46748
|
+
const len = Math.max(va.prerelease.length, vb.prerelease.length);
|
|
46749
|
+
for (let i4 = 0; i4 < len; i4++) {
|
|
46750
|
+
const ia = va.prerelease[i4];
|
|
46751
|
+
const ib = vb.prerelease[i4];
|
|
46752
|
+
if (ia === void 0) return -1;
|
|
46753
|
+
if (ib === void 0) return 1;
|
|
46754
|
+
const c = compareIdentifiers(ia, ib);
|
|
46755
|
+
if (c !== 0) return c < 0 ? -1 : 1;
|
|
46756
|
+
}
|
|
46757
|
+
return 0;
|
|
46758
|
+
}
|
|
46759
|
+
async function fetchLatestVersion(pkgName, opts = {}) {
|
|
46760
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
46761
|
+
const timeoutMs = opts.timeoutMs ?? 3e3;
|
|
46762
|
+
const controller = new AbortController();
|
|
46763
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
46764
|
+
try {
|
|
46765
|
+
const res = await fetchImpl(`https://registry.npmjs.org/${pkgName}/latest`, {
|
|
46766
|
+
signal: controller.signal,
|
|
46767
|
+
// The abbreviated metadata document is far smaller than the full packument.
|
|
46768
|
+
headers: { accept: "application/vnd.npm.install-v1+json" }
|
|
46769
|
+
});
|
|
46770
|
+
if (!res.ok) return null;
|
|
46771
|
+
const body = await res.json();
|
|
46772
|
+
return typeof body.version === "string" ? body.version : null;
|
|
46773
|
+
} catch {
|
|
46774
|
+
return null;
|
|
46775
|
+
} finally {
|
|
46776
|
+
clearTimeout(timer);
|
|
46777
|
+
}
|
|
46778
|
+
}
|
|
46779
|
+
function formatVersionReport(installed, latest) {
|
|
46780
|
+
const head = `${PACKAGE_NAME} ${installed}`;
|
|
46781
|
+
if (!latest) {
|
|
46782
|
+
return `${head}
|
|
46783
|
+
(could not check for updates \u2014 offline or registry unreachable)`;
|
|
46784
|
+
}
|
|
46785
|
+
const cmp = compareVersions(installed, latest);
|
|
46786
|
+
if (cmp < 0) {
|
|
46787
|
+
return [
|
|
46788
|
+
head,
|
|
46789
|
+
`Update available: ${latest} (you have ${installed}).`,
|
|
46790
|
+
`Upgrade: npm install -g ${PACKAGE_NAME}@latest`,
|
|
46791
|
+
`Or just re-run via: npx -y ${PACKAGE_NAME}@latest`
|
|
46792
|
+
].join("\n");
|
|
46793
|
+
}
|
|
46794
|
+
if (cmp > 0) {
|
|
46795
|
+
return `${head}
|
|
46796
|
+
(ahead of the latest published release ${latest})`;
|
|
46797
|
+
}
|
|
46798
|
+
return `${head} (latest)`;
|
|
46799
|
+
}
|
|
46800
|
+
|
|
46539
46801
|
// src/server.ts
|
|
46540
46802
|
function adapt(r) {
|
|
46541
46803
|
const text = r.ok || r.errorCode === void 0 ? r.text : `${r.text} (${r.errorCode})`;
|
|
@@ -46546,12 +46808,16 @@ function adapt(r) {
|
|
|
46546
46808
|
};
|
|
46547
46809
|
}
|
|
46548
46810
|
function buildServer(ctx) {
|
|
46549
|
-
const server = new McpServer({ name: "formstr", version:
|
|
46811
|
+
const server = new McpServer({ name: "formstr", version: readInstalledVersion() });
|
|
46550
46812
|
for (const t of toolRegistry) {
|
|
46551
46813
|
if (t.write && !ctx.allowWrites) continue;
|
|
46552
46814
|
server.registerTool(
|
|
46553
46815
|
t.name,
|
|
46554
|
-
|
|
46816
|
+
// Strict object, not the raw shape: the SDK's own parse would silently
|
|
46817
|
+
// STRIP unknown keys before our handler runs, so a misspelled parameter
|
|
46818
|
+
// looked accepted while doing nothing. Strict makes it an InvalidParams
|
|
46819
|
+
// error the model can react to (and advertises additionalProperties:false).
|
|
46820
|
+
{ description: t.description, inputSchema: external_exports.object(t.inputSchema).strict() },
|
|
46555
46821
|
async (args) => adapt(await t.handler(args, ctx))
|
|
46556
46822
|
);
|
|
46557
46823
|
}
|
|
@@ -46578,7 +46844,7 @@ async function runServer(cli) {
|
|
|
46578
46844
|
} : {};
|
|
46579
46845
|
const account = await bootstrap({ account: cfg.account, relays: cfg.relays }, deps);
|
|
46580
46846
|
console.error(
|
|
46581
|
-
`formstr-mcp: signed in as ${account.npub} (${account.method}), writes ${cfg.allowWrites ? "ENABLED" : "disabled"}`
|
|
46847
|
+
`formstr-mcp: signed in as ${account.npub} (${account.method}), gated writes ${cfg.allowWrites ? "ENABLED" : "disabled (creates always on)"}`
|
|
46582
46848
|
);
|
|
46583
46849
|
await startStdio({ allowWrites: cfg.allowWrites });
|
|
46584
46850
|
console.error("formstr-mcp: server running on stdio");
|
|
@@ -46653,6 +46919,12 @@ async function main() {
|
|
|
46653
46919
|
case "help":
|
|
46654
46920
|
console.error(helpText());
|
|
46655
46921
|
return cli.command;
|
|
46922
|
+
case "version": {
|
|
46923
|
+
const installed = readInstalledVersion();
|
|
46924
|
+
const latest = await fetchLatestVersion(PACKAGE_NAME);
|
|
46925
|
+
console.log(formatVersionReport(installed, latest));
|
|
46926
|
+
return cli.command;
|
|
46927
|
+
}
|
|
46656
46928
|
case "run":
|
|
46657
46929
|
default:
|
|
46658
46930
|
await runServer(cli);
|