@formstr/mcp 0.4.0 → 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 +19 -6
- package/dist/index.js +216 -84
- 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;
|
|
@@ -27554,6 +27560,8 @@ function helpText() {
|
|
|
27554
27560
|
"",
|
|
27555
27561
|
"Flags:",
|
|
27556
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.",
|
|
27557
27565
|
" --account <npub|hex> Boot a specific account instead of the active one.",
|
|
27558
27566
|
" --relays <a,b,\u2026> Override relays (comma-separated).",
|
|
27559
27567
|
"",
|
|
@@ -27580,7 +27588,8 @@ function helpText() {
|
|
|
27580
27588
|
" }",
|
|
27581
27589
|
"",
|
|
27582
27590
|
" Tip: a NIP-46 (bunker) account needs no passphrase \u2014 `switch` to one and the config",
|
|
27583
|
-
' can stay secret-free. Add "--allow-writes" to args to enable gated write tools
|
|
27591
|
+
' can stay secret-free. Add "--allow-writes" to args to enable gated write tools',
|
|
27592
|
+
" (creates are always on)."
|
|
27584
27593
|
].join("\n");
|
|
27585
27594
|
}
|
|
27586
27595
|
|
|
@@ -32406,11 +32415,11 @@ async function decryptListEntries(signer, userPubkey, content) {
|
|
|
32406
32415
|
const parsed = JSON.parse(decrypted);
|
|
32407
32416
|
return Array.isArray(parsed) ? parsed : [];
|
|
32408
32417
|
}
|
|
32409
|
-
async function publishMyFormsList(signer, relays, entries) {
|
|
32418
|
+
async function publishMyFormsList(signer, relays, entries, prevCreatedAt) {
|
|
32410
32419
|
const encrypted = await nip44SelfEncrypt(signer, JSON.stringify(entries));
|
|
32411
32420
|
const event = {
|
|
32412
32421
|
kind: FORM_KINDS.myFormsList,
|
|
32413
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
32422
|
+
created_at: Math.max(Math.floor(Date.now() / 1e3), prevCreatedAt + 1),
|
|
32414
32423
|
tags: [],
|
|
32415
32424
|
content: encrypted
|
|
32416
32425
|
};
|
|
@@ -32471,9 +32480,10 @@ async function readMyFormsEntriesForWrite(signer, userPubkey, relays) {
|
|
|
32471
32480
|
listEvent = await fetchLatestMyFormsEvent(relays, userPubkey);
|
|
32472
32481
|
if (listEvent?.content) break;
|
|
32473
32482
|
}
|
|
32474
|
-
if (!listEvent?.content) return [];
|
|
32483
|
+
if (!listEvent?.content) return { entries: [], prevCreatedAt: 0 };
|
|
32475
32484
|
try {
|
|
32476
|
-
|
|
32485
|
+
const entries = await decryptListEntries(signer, userPubkey, listEvent.content);
|
|
32486
|
+
return { entries, prevCreatedAt: listEvent.created_at };
|
|
32477
32487
|
} catch (err) {
|
|
32478
32488
|
throw new Error(
|
|
32479
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)})`
|
|
@@ -32484,8 +32494,12 @@ async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, vie
|
|
|
32484
32494
|
const signer = await signerManager.getSigner();
|
|
32485
32495
|
const userPubkey = await signer.getPublicKey();
|
|
32486
32496
|
const relays = relayManager.getRelaysForModule("forms");
|
|
32487
|
-
|
|
32488
|
-
|
|
32497
|
+
const { entries: existing, prevCreatedAt } = await readMyFormsEntriesForWrite(
|
|
32498
|
+
signer,
|
|
32499
|
+
userPubkey,
|
|
32500
|
+
relays
|
|
32501
|
+
);
|
|
32502
|
+
const entries = existing.map(
|
|
32489
32503
|
(e) => e[0] === "f" && e.length < 4 ? ["f", e[1] ?? "", e[2] ?? "", ""] : e
|
|
32490
32504
|
);
|
|
32491
32505
|
const coordKey = `${formPubkey}:${formId}`;
|
|
@@ -32493,10 +32507,11 @@ async function appendToMyFormsList(formPubkey, formId, relay, signingKeyHex, vie
|
|
|
32493
32507
|
const secrets = signingKeyHex ? encodeFormKeys(signingKeyHex, viewKeyHex) : "";
|
|
32494
32508
|
entries.push(["f", coordKey, relay, secrets]);
|
|
32495
32509
|
}
|
|
32496
|
-
await publishMyFormsList(signer, relays, entries);
|
|
32510
|
+
await publishMyFormsList(signer, relays, entries, prevCreatedAt);
|
|
32497
32511
|
}
|
|
32498
32512
|
async function saveToMyForms(summaries) {
|
|
32499
32513
|
const signer = await signerManager.getSigner();
|
|
32514
|
+
const userPubkey = await signer.getPublicKey();
|
|
32500
32515
|
const relays = relayManager.getRelaysForModule("forms");
|
|
32501
32516
|
const entries = summaries.map((s) => [
|
|
32502
32517
|
"f",
|
|
@@ -32504,7 +32519,8 @@ async function saveToMyForms(summaries) {
|
|
|
32504
32519
|
s.relay ?? "",
|
|
32505
32520
|
s.signingKey ? encodeFormKeys(s.signingKey, s.viewKey) : ""
|
|
32506
32521
|
]);
|
|
32507
|
-
await
|
|
32522
|
+
const prev = await fetchLatestMyFormsEvent(relays, userPubkey);
|
|
32523
|
+
await publishMyFormsList(signer, relays, entries, prev?.created_at ?? 0);
|
|
32508
32524
|
}
|
|
32509
32525
|
async function fetchMyFormsByAuthor(pubkey, relays) {
|
|
32510
32526
|
const filter = {
|
|
@@ -32551,7 +32567,7 @@ async function deleteForm(formId, formPubkey) {
|
|
|
32551
32567
|
const coordKey = `${formPubkey}:${formId}`;
|
|
32552
32568
|
const trimmed = entries.filter((e) => e[1] !== coordKey);
|
|
32553
32569
|
if (trimmed.length === entries.length) return;
|
|
32554
|
-
await publishMyFormsList(signer, relays, trimmed);
|
|
32570
|
+
await publishMyFormsList(signer, relays, trimmed, listEvent.created_at);
|
|
32555
32571
|
}
|
|
32556
32572
|
async function updateForm(params) {
|
|
32557
32573
|
const relays = relayManager.getRelaysForModule("forms");
|
|
@@ -34287,7 +34303,7 @@ async function fetchAllDocMetadata() {
|
|
|
34287
34303
|
}
|
|
34288
34304
|
return result;
|
|
34289
34305
|
}
|
|
34290
|
-
async function
|
|
34306
|
+
async function fetchNewestDocMetadataEvent(address) {
|
|
34291
34307
|
const signer = await signerManager.getSigner();
|
|
34292
34308
|
const pubkey = await signer.getPublicKey();
|
|
34293
34309
|
const events = await nostrRuntime.querySync(RELAYS(), {
|
|
@@ -34296,7 +34312,12 @@ async function fetchDocMetadata(address) {
|
|
|
34296
34312
|
"#d": [address]
|
|
34297
34313
|
});
|
|
34298
34314
|
if (events.length === 0) return void 0;
|
|
34299
|
-
|
|
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;
|
|
34300
34321
|
try {
|
|
34301
34322
|
return parseMetadataJson(await nip44SelfDecrypt(signer, newest.content));
|
|
34302
34323
|
} catch {
|
|
@@ -34304,14 +34325,24 @@ async function fetchDocMetadata(address) {
|
|
|
34304
34325
|
}
|
|
34305
34326
|
}
|
|
34306
34327
|
async function saveDocMetadata(address, patch) {
|
|
34307
|
-
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
|
+
}
|
|
34308
34337
|
const merged = { ...existing, ...patch };
|
|
34309
34338
|
if (!Array.isArray(merged.tags)) merged.tags = [];
|
|
34310
|
-
const signer = await signerManager.getSigner();
|
|
34311
34339
|
const content = await nip44SelfEncrypt(signer, JSON.stringify(merged));
|
|
34312
34340
|
const signed = await signer.signEvent({
|
|
34313
34341
|
kind: PAGES_KINDS.docMetadata,
|
|
34314
|
-
|
|
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),
|
|
34315
34346
|
tags: [["d", address]],
|
|
34316
34347
|
content
|
|
34317
34348
|
});
|
|
@@ -34560,14 +34591,22 @@ async function fetchFileIndex() {
|
|
|
34560
34591
|
}
|
|
34561
34592
|
async function saveFileMetadata(metadata) {
|
|
34562
34593
|
const signer = await signerManager.getSigner();
|
|
34594
|
+
const relays = relayManager.getRelaysForModule("drive");
|
|
34563
34595
|
const full = {
|
|
34564
34596
|
...metadata,
|
|
34565
34597
|
encryptionAlgorithm: metadata.encryptionAlgorithm ?? "aes-gcm"
|
|
34566
34598
|
};
|
|
34567
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);
|
|
34568
34607
|
const event = {
|
|
34569
34608
|
kind: DRIVE_KINDS.fileMetadata,
|
|
34570
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
34609
|
+
created_at: Math.max(Math.floor(Date.now() / 1e3), prevCreatedAt + 1),
|
|
34571
34610
|
tags: [
|
|
34572
34611
|
["d", metadata.hash],
|
|
34573
34612
|
["client", "formstr-drive"],
|
|
@@ -34576,7 +34615,6 @@ async function saveFileMetadata(metadata) {
|
|
|
34576
34615
|
content: encrypted
|
|
34577
34616
|
};
|
|
34578
34617
|
const signed = await signer.signEvent(event);
|
|
34579
|
-
const relays = relayManager.getRelaysForModule("drive");
|
|
34580
34618
|
await nostrRuntime.publish(relays, signed);
|
|
34581
34619
|
}
|
|
34582
34620
|
async function updateFileMetadata(hash, updates) {
|
|
@@ -34661,6 +34699,7 @@ __export(service_exports5, {
|
|
|
34661
34699
|
});
|
|
34662
34700
|
|
|
34663
34701
|
// ../agent/src/services/polls/pow.ts
|
|
34702
|
+
var MAX_MINED_POW_DIFFICULTY = 20;
|
|
34664
34703
|
function minePollEvent(unsigned, difficulty) {
|
|
34665
34704
|
const event = unsigned;
|
|
34666
34705
|
const nonceTag = ["nonce", "0", difficulty.toString()];
|
|
@@ -34753,6 +34792,11 @@ async function submitPollResponse(pollId, pollAuthor, selectedOptionIds, pollRel
|
|
|
34753
34792
|
tags,
|
|
34754
34793
|
content: ""
|
|
34755
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
|
+
}
|
|
34756
34800
|
if (powDifficulty && powDifficulty > 0) {
|
|
34757
34801
|
const unsigned = { ...event, pubkey: await signer.getPublicKey() };
|
|
34758
34802
|
const mined = minePollEvent(unsigned, powDifficulty);
|
|
@@ -34943,6 +34987,16 @@ function normalizePubkeyList(values) {
|
|
|
34943
34987
|
}
|
|
34944
34988
|
|
|
34945
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
|
+
}
|
|
34946
35000
|
var calendarTools = buildCalendarTools();
|
|
34947
35001
|
function buildCalendarTools() {
|
|
34948
35002
|
const tools = [];
|
|
@@ -34959,11 +35013,15 @@ function buildCalendarTools() {
|
|
|
34959
35013
|
inputSchema: { since: external_exports.string().optional(), until: external_exports.string().optional() }
|
|
34960
35014
|
},
|
|
34961
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);
|
|
34962
35020
|
const pubkey = signerManager.getPublicKey();
|
|
34963
35021
|
const events = await service_exports2.fetchCalendarEventsSync({
|
|
34964
35022
|
authors: pubkey ? [pubkey] : void 0,
|
|
34965
|
-
since:
|
|
34966
|
-
until:
|
|
35023
|
+
since: sinceDate ? Math.floor(sinceDate.getTime() / 1e3) : void 0,
|
|
35024
|
+
until: untilDate ? Math.floor(untilDate.getTime() / 1e3) : void 0
|
|
34967
35025
|
});
|
|
34968
35026
|
return ok(`Found ${events.length} event(s).`, {
|
|
34969
35027
|
events: events.map((e) => ({
|
|
@@ -34981,7 +35039,7 @@ function buildCalendarTools() {
|
|
|
34981
35039
|
server.registerTool(
|
|
34982
35040
|
"create_calendar_event",
|
|
34983
35041
|
{
|
|
34984
|
-
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.",
|
|
34985
35043
|
inputSchema: {
|
|
34986
35044
|
title: external_exports.string(),
|
|
34987
35045
|
description: external_exports.string().optional(),
|
|
@@ -34993,10 +35051,15 @@ function buildCalendarTools() {
|
|
|
34993
35051
|
participants: external_exports.array(external_exports.string()).optional(),
|
|
34994
35052
|
rrule: external_exports.string().optional(),
|
|
34995
35053
|
startTzid: external_exports.string().optional(),
|
|
34996
|
-
registrationFormRef: external_exports.string().optional()
|
|
35054
|
+
registrationFormRef: external_exports.string().optional(),
|
|
35055
|
+
registrationFormViewKey: external_exports.string().optional()
|
|
34997
35056
|
}
|
|
34998
35057
|
},
|
|
34999
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);
|
|
35000
35063
|
const isPrivate = args.isPrivate ?? true;
|
|
35001
35064
|
const calendars = await service_exports2.fetchCalendarLists();
|
|
35002
35065
|
if (isPrivate && !args.calendarId && calendars.length > 0) {
|
|
@@ -35013,8 +35076,6 @@ function buildCalendarTools() {
|
|
|
35013
35076
|
"NOT_FOUND"
|
|
35014
35077
|
);
|
|
35015
35078
|
}
|
|
35016
|
-
const begin = new Date(args.start);
|
|
35017
|
-
const end = args.end ? new Date(args.end) : new Date(begin.getTime() + 36e5);
|
|
35018
35079
|
const draft = {
|
|
35019
35080
|
title: args.title,
|
|
35020
35081
|
description: args.description ?? "",
|
|
@@ -35028,17 +35089,20 @@ function buildCalendarTools() {
|
|
|
35028
35089
|
calendarId: args.calendarId,
|
|
35029
35090
|
rrule: args.rrule,
|
|
35030
35091
|
startTzid: args.startTzid,
|
|
35031
|
-
registrationFormRef: args.registrationFormRef
|
|
35092
|
+
registrationFormRef: args.registrationFormRef,
|
|
35093
|
+
registrationFormViewKey: args.registrationFormViewKey
|
|
35032
35094
|
};
|
|
35033
35095
|
const { event, calendar: list } = await service_exports2.createCalendarEvent(draft, { calendars });
|
|
35034
35096
|
const coordinate = `${event.kind}:${event.user}:${event.id}`;
|
|
35097
|
+
const invitationsSent = isPrivate ? draft.participants?.length ?? 0 : 0;
|
|
35035
35098
|
return ok(
|
|
35036
|
-
`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)` : ""}.`,
|
|
35037
35100
|
{
|
|
35038
35101
|
id: event.id,
|
|
35039
35102
|
eventId: event.eventId,
|
|
35040
35103
|
coordinate,
|
|
35041
|
-
calendarId: list?.id
|
|
35104
|
+
calendarId: list?.id,
|
|
35105
|
+
invitationsSent
|
|
35042
35106
|
}
|
|
35043
35107
|
);
|
|
35044
35108
|
}
|
|
@@ -35057,12 +35121,19 @@ function buildCalendarTools() {
|
|
|
35057
35121
|
event: {
|
|
35058
35122
|
id: event.id,
|
|
35059
35123
|
title: event.title,
|
|
35124
|
+
description: event.description,
|
|
35060
35125
|
begin: event.begin,
|
|
35061
35126
|
end: event.end,
|
|
35062
35127
|
location: event.location,
|
|
35063
35128
|
isPrivate: event.isPrivate,
|
|
35064
35129
|
rrule: event.repeat?.rrule ?? null,
|
|
35065
|
-
|
|
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)
|
|
35066
35137
|
}
|
|
35067
35138
|
});
|
|
35068
35139
|
}
|
|
@@ -35073,7 +35144,12 @@ function buildCalendarTools() {
|
|
|
35073
35144
|
async () => {
|
|
35074
35145
|
const lists = await service_exports2.fetchCalendarLists();
|
|
35075
35146
|
return ok(`Found ${lists.length} calendar(s).`, {
|
|
35076
|
-
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
|
+
}))
|
|
35077
35153
|
});
|
|
35078
35154
|
}
|
|
35079
35155
|
);
|
|
@@ -35282,7 +35358,7 @@ function buildCalendarTools() {
|
|
|
35282
35358
|
server.registerTool(
|
|
35283
35359
|
"update_calendar_event",
|
|
35284
35360
|
{
|
|
35285
|
-
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.",
|
|
35286
35362
|
inputSchema: {
|
|
35287
35363
|
coordinate: external_exports.string(),
|
|
35288
35364
|
title: external_exports.string().optional(),
|
|
@@ -35292,6 +35368,8 @@ function buildCalendarTools() {
|
|
|
35292
35368
|
location: external_exports.string().optional(),
|
|
35293
35369
|
rrule: external_exports.string().optional(),
|
|
35294
35370
|
startTzid: external_exports.string().optional(),
|
|
35371
|
+
registrationFormRef: external_exports.string().optional(),
|
|
35372
|
+
registrationFormViewKey: external_exports.string().optional(),
|
|
35295
35373
|
confirm: external_exports.boolean().optional()
|
|
35296
35374
|
}
|
|
35297
35375
|
},
|
|
@@ -35302,21 +35380,27 @@ function buildCalendarTools() {
|
|
|
35302
35380
|
`updates event ${args.coordinate}`
|
|
35303
35381
|
);
|
|
35304
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);
|
|
35305
35387
|
const viewKey = await service_exports2.lookupEventViewKey(args.coordinate);
|
|
35306
35388
|
const existing = await service_exports2.fetchCalendarEventByCoordinate(args.coordinate, viewKey);
|
|
35307
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);
|
|
35308
35392
|
const draft = {
|
|
35309
35393
|
title: args.title ?? existing.title,
|
|
35310
35394
|
description: args.description ?? existing.description,
|
|
35311
|
-
begin:
|
|
35312
|
-
end:
|
|
35395
|
+
begin: newBegin ?? new Date(existing.begin),
|
|
35396
|
+
end: newEnd ?? new Date(existing.end),
|
|
35313
35397
|
location: args.location ?? existing.location[0],
|
|
35314
35398
|
participants: existing.participants,
|
|
35315
35399
|
isPrivate: existing.isPrivate,
|
|
35316
35400
|
rrule: args.rrule ?? existing.repeat.rrule ?? void 0,
|
|
35317
35401
|
startTzid: args.startTzid ?? existing.startTzid,
|
|
35318
|
-
registrationFormRef:
|
|
35319
|
-
registrationFormViewKey:
|
|
35402
|
+
registrationFormRef: formRef,
|
|
35403
|
+
registrationFormViewKey: formViewKey,
|
|
35320
35404
|
notificationPreference: existing.notificationPreference,
|
|
35321
35405
|
viewKey: existing.viewKey,
|
|
35322
35406
|
existingId: existing.id
|
|
@@ -35331,14 +35415,15 @@ function buildCalendarTools() {
|
|
|
35331
35415
|
server.registerTool(
|
|
35332
35416
|
"attach_form_to_event",
|
|
35333
35417
|
{
|
|
35334
|
-
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.",
|
|
35335
35419
|
inputSchema: {
|
|
35336
35420
|
coordinate: external_exports.string(),
|
|
35337
35421
|
formRef: external_exports.string(),
|
|
35422
|
+
formViewKey: external_exports.string().optional(),
|
|
35338
35423
|
confirm: external_exports.boolean().optional()
|
|
35339
35424
|
}
|
|
35340
35425
|
},
|
|
35341
|
-
async ({ coordinate, formRef, confirm }) => {
|
|
35426
|
+
async ({ coordinate, formRef, formViewKey, confirm }) => {
|
|
35342
35427
|
const blocked = requireConfirm(
|
|
35343
35428
|
"attach_form_to_event",
|
|
35344
35429
|
{ confirm },
|
|
@@ -35348,6 +35433,7 @@ function buildCalendarTools() {
|
|
|
35348
35433
|
const viewKey = await service_exports2.lookupEventViewKey(coordinate);
|
|
35349
35434
|
const existing = await service_exports2.fetchCalendarEventByCoordinate(coordinate, viewKey);
|
|
35350
35435
|
if (!existing) return fail(`No event found for ${coordinate}.`, "NOT_FOUND");
|
|
35436
|
+
const registrationFormViewKey = formViewKey ?? (formRef === existing.registrationFormRef ? existing.registrationFormViewKey : void 0);
|
|
35351
35437
|
const draft = {
|
|
35352
35438
|
title: existing.title,
|
|
35353
35439
|
description: existing.description,
|
|
@@ -35359,6 +35445,7 @@ function buildCalendarTools() {
|
|
|
35359
35445
|
rrule: existing.repeat.rrule ?? void 0,
|
|
35360
35446
|
startTzid: existing.startTzid,
|
|
35361
35447
|
registrationFormRef: formRef,
|
|
35448
|
+
registrationFormViewKey,
|
|
35362
35449
|
notificationPreference: existing.notificationPreference,
|
|
35363
35450
|
viewKey: existing.viewKey,
|
|
35364
35451
|
existingId: existing.id
|
|
@@ -35551,7 +35638,7 @@ function buildDriveTools() {
|
|
|
35551
35638
|
server.registerTool(
|
|
35552
35639
|
"delete_file",
|
|
35553
35640
|
{
|
|
35554
|
-
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.",
|
|
35555
35642
|
inputSchema: {
|
|
35556
35643
|
name: external_exports.string(),
|
|
35557
35644
|
folder: external_exports.string().optional(),
|
|
@@ -35564,7 +35651,9 @@ function buildDriveTools() {
|
|
|
35564
35651
|
const resolved = await resolveFile(name, folder);
|
|
35565
35652
|
if ("error" in resolved) return resolved.error;
|
|
35566
35653
|
await service_exports4.deleteFile(resolved.file);
|
|
35567
|
-
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
|
+
);
|
|
35568
35657
|
}
|
|
35569
35658
|
);
|
|
35570
35659
|
server.registerTool(
|
|
@@ -36098,16 +36187,29 @@ function buildPagesTools() {
|
|
|
36098
36187
|
return ok(`Saved page "${title}".`, { address: page.address });
|
|
36099
36188
|
}
|
|
36100
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;
|
|
36101
36202
|
server.registerTool(
|
|
36102
36203
|
"update_page",
|
|
36103
36204
|
{
|
|
36104
|
-
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.",
|
|
36105
36206
|
inputSchema: {
|
|
36106
36207
|
docId: external_exports.string(),
|
|
36107
36208
|
content: external_exports.string(),
|
|
36108
36209
|
title: external_exports.string().optional(),
|
|
36109
36210
|
viewKey: external_exports.string().optional(),
|
|
36110
|
-
editKey: external_exports.string().optional()
|
|
36211
|
+
editKey: external_exports.string().optional(),
|
|
36212
|
+
confirm: external_exports.boolean().optional()
|
|
36111
36213
|
}
|
|
36112
36214
|
},
|
|
36113
36215
|
async ({
|
|
@@ -36115,8 +36217,15 @@ function buildPagesTools() {
|
|
|
36115
36217
|
content,
|
|
36116
36218
|
title,
|
|
36117
36219
|
viewKey,
|
|
36118
|
-
editKey
|
|
36220
|
+
editKey,
|
|
36221
|
+
confirm
|
|
36119
36222
|
}) => {
|
|
36223
|
+
const blocked = requireConfirm(
|
|
36224
|
+
"update_page",
|
|
36225
|
+
{ confirm },
|
|
36226
|
+
`overwrites document ${docId} with new content`
|
|
36227
|
+
);
|
|
36228
|
+
if (blocked) return blocked;
|
|
36120
36229
|
const page = await service_exports3.savePage({
|
|
36121
36230
|
existingId: docId,
|
|
36122
36231
|
content: withTitle(title, content),
|
|
@@ -36126,18 +36235,6 @@ function buildPagesTools() {
|
|
|
36126
36235
|
return ok(`Updated page ${docId}.`, { address: page.address });
|
|
36127
36236
|
}
|
|
36128
36237
|
);
|
|
36129
|
-
server.registerTool(
|
|
36130
|
-
"set_page_tags",
|
|
36131
|
-
{
|
|
36132
|
-
description: "Set the private labels/tags for a document address.",
|
|
36133
|
-
inputSchema: { address: external_exports.string(), tags: external_exports.array(external_exports.string()) }
|
|
36134
|
-
},
|
|
36135
|
-
async ({ address, tags }) => {
|
|
36136
|
-
await service_exports3.setDocTags(address, tags);
|
|
36137
|
-
return ok(`Set ${tags.length} tag(s) on ${address}.`);
|
|
36138
|
-
}
|
|
36139
|
-
);
|
|
36140
|
-
write = true;
|
|
36141
36238
|
server.registerTool(
|
|
36142
36239
|
"delete_page",
|
|
36143
36240
|
{
|
|
@@ -36287,7 +36384,11 @@ function buildPollsTools() {
|
|
|
36287
36384
|
pubkey: poll.pubkey,
|
|
36288
36385
|
createdAt: poll.createdAt,
|
|
36289
36386
|
endsAt: poll.endsAt,
|
|
36290
|
-
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
|
|
36291
36392
|
}
|
|
36292
36393
|
});
|
|
36293
36394
|
}
|
|
@@ -36326,11 +36427,18 @@ function buildPollsTools() {
|
|
|
36326
36427
|
}
|
|
36327
36428
|
},
|
|
36328
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
|
+
}
|
|
36329
36437
|
const poll = await service_exports5.createPoll({
|
|
36330
36438
|
question: args.question,
|
|
36331
36439
|
options: args.options.map((label) => ({ label })),
|
|
36332
36440
|
pollType: args.pollType ?? "singlechoice",
|
|
36333
|
-
endsAt
|
|
36441
|
+
endsAt,
|
|
36334
36442
|
hashtags: args.hashtags
|
|
36335
36443
|
});
|
|
36336
36444
|
return ok(`Created poll "${args.question}".`, { id: poll.id });
|
|
@@ -36401,6 +36509,26 @@ function buildPollsTools() {
|
|
|
36401
36509
|
return tools;
|
|
36402
36510
|
}
|
|
36403
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
|
+
|
|
36404
36532
|
// ../agent/src/tools/index.ts
|
|
36405
36533
|
var toolRegistry = [
|
|
36406
36534
|
...formsTools,
|
|
@@ -36408,7 +36536,7 @@ var toolRegistry = [
|
|
|
36408
36536
|
...pagesTools,
|
|
36409
36537
|
...pollsTools,
|
|
36410
36538
|
...driveTools
|
|
36411
|
-
];
|
|
36539
|
+
].map(withStrictArgs);
|
|
36412
36540
|
|
|
36413
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
|
|
36414
36542
|
var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
|
@@ -46581,33 +46709,6 @@ var StdioServerTransport = class {
|
|
|
46581
46709
|
}
|
|
46582
46710
|
};
|
|
46583
46711
|
|
|
46584
|
-
// src/server.ts
|
|
46585
|
-
function adapt(r) {
|
|
46586
|
-
const text = r.ok || r.errorCode === void 0 ? r.text : `${r.text} (${r.errorCode})`;
|
|
46587
|
-
return {
|
|
46588
|
-
content: [{ type: "text", text }],
|
|
46589
|
-
...r.data !== void 0 ? { structuredContent: r.data } : {},
|
|
46590
|
-
...r.ok ? {} : { isError: true }
|
|
46591
|
-
};
|
|
46592
|
-
}
|
|
46593
|
-
function buildServer(ctx) {
|
|
46594
|
-
const server = new McpServer({ name: "formstr", version: "0.1.0" });
|
|
46595
|
-
for (const t of toolRegistry) {
|
|
46596
|
-
if (t.write && !ctx.allowWrites) continue;
|
|
46597
|
-
server.registerTool(
|
|
46598
|
-
t.name,
|
|
46599
|
-
{ description: t.description, inputSchema: t.inputSchema },
|
|
46600
|
-
async (args) => adapt(await t.handler(args, ctx))
|
|
46601
|
-
);
|
|
46602
|
-
}
|
|
46603
|
-
return server;
|
|
46604
|
-
}
|
|
46605
|
-
async function startStdio(ctx) {
|
|
46606
|
-
const server = buildServer(ctx);
|
|
46607
|
-
const transport = new StdioServerTransport();
|
|
46608
|
-
await server.connect(transport);
|
|
46609
|
-
}
|
|
46610
|
-
|
|
46611
46712
|
// src/version.ts
|
|
46612
46713
|
var import_node_fs2 = require("fs");
|
|
46613
46714
|
var import_node_path2 = require("path");
|
|
@@ -46697,6 +46798,37 @@ function formatVersionReport(installed, latest) {
|
|
|
46697
46798
|
return `${head} (latest)`;
|
|
46698
46799
|
}
|
|
46699
46800
|
|
|
46801
|
+
// src/server.ts
|
|
46802
|
+
function adapt(r) {
|
|
46803
|
+
const text = r.ok || r.errorCode === void 0 ? r.text : `${r.text} (${r.errorCode})`;
|
|
46804
|
+
return {
|
|
46805
|
+
content: [{ type: "text", text }],
|
|
46806
|
+
...r.data !== void 0 ? { structuredContent: r.data } : {},
|
|
46807
|
+
...r.ok ? {} : { isError: true }
|
|
46808
|
+
};
|
|
46809
|
+
}
|
|
46810
|
+
function buildServer(ctx) {
|
|
46811
|
+
const server = new McpServer({ name: "formstr", version: readInstalledVersion() });
|
|
46812
|
+
for (const t of toolRegistry) {
|
|
46813
|
+
if (t.write && !ctx.allowWrites) continue;
|
|
46814
|
+
server.registerTool(
|
|
46815
|
+
t.name,
|
|
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() },
|
|
46821
|
+
async (args) => adapt(await t.handler(args, ctx))
|
|
46822
|
+
);
|
|
46823
|
+
}
|
|
46824
|
+
return server;
|
|
46825
|
+
}
|
|
46826
|
+
async function startStdio(ctx) {
|
|
46827
|
+
const server = buildServer(ctx);
|
|
46828
|
+
const transport = new StdioServerTransport();
|
|
46829
|
+
await server.connect(transport);
|
|
46830
|
+
}
|
|
46831
|
+
|
|
46700
46832
|
// src/index.ts
|
|
46701
46833
|
async function runServer(cli) {
|
|
46702
46834
|
const cfg = resolveConfig(cli, process.env);
|
|
@@ -46712,7 +46844,7 @@ async function runServer(cli) {
|
|
|
46712
46844
|
} : {};
|
|
46713
46845
|
const account = await bootstrap({ account: cfg.account, relays: cfg.relays }, deps);
|
|
46714
46846
|
console.error(
|
|
46715
|
-
`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)"}`
|
|
46716
46848
|
);
|
|
46717
46849
|
await startStdio({ allowWrites: cfg.allowWrites });
|
|
46718
46850
|
console.error("formstr-mcp: server running on stdio");
|