@formstr/mcp 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +136 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24436,8 +24436,21 @@ async function loginNostrConnect(deps, log) {
|
|
|
24436
24436
|
perms: NIP46_PERMS
|
|
24437
24437
|
});
|
|
24438
24438
|
}
|
|
24439
|
-
async function doLogout(signer,
|
|
24440
|
-
|
|
24439
|
+
async function doLogout(signer, target) {
|
|
24440
|
+
if (target) {
|
|
24441
|
+
const match = findAccount(listAccounts(signer), target);
|
|
24442
|
+
if (!match) {
|
|
24443
|
+
throw new Error(
|
|
24444
|
+
`No stored account matching "${target}". Run \`formstr-mcp accounts\` to list them.`
|
|
24445
|
+
);
|
|
24446
|
+
}
|
|
24447
|
+
await signer.logout(match.pubkey);
|
|
24448
|
+
return match;
|
|
24449
|
+
}
|
|
24450
|
+
const active = signer.getActiveAccount();
|
|
24451
|
+
if (!active) return null;
|
|
24452
|
+
await signer.logout(active.pubkey);
|
|
24453
|
+
return active;
|
|
24441
24454
|
}
|
|
24442
24455
|
function whoami(signer) {
|
|
24443
24456
|
return signer.getActiveAccount();
|
|
@@ -27521,7 +27534,7 @@ function helpText() {
|
|
|
27521
27534
|
"Commands:",
|
|
27522
27535
|
" run Run the stdio MCP server (default when no command is given).",
|
|
27523
27536
|
" login Sign in (create / import / bunker URI / QR) and store the key.",
|
|
27524
|
-
" logout [npub]
|
|
27537
|
+
" logout [npub|hex] Permanently remove a stored account from the keystore (defaults to active).",
|
|
27525
27538
|
" whoami Print the active account.",
|
|
27526
27539
|
" accounts List stored accounts ('*' marks the active one).",
|
|
27527
27540
|
" switch <npub> Set the active account (accepts an npub or hex pubkey).",
|
|
@@ -32161,14 +32174,22 @@ function buildSpecRows(formId, name, fields, settings) {
|
|
|
32161
32174
|
for (const field of fields) rows.push(buildFieldTag(field));
|
|
32162
32175
|
return rows;
|
|
32163
32176
|
}
|
|
32164
|
-
function
|
|
32165
|
-
const tags = [
|
|
32177
|
+
function buildParticipantTags(settings) {
|
|
32178
|
+
const tags = [];
|
|
32166
32179
|
const allowed = settings?.allowedResponders ?? [];
|
|
32167
32180
|
const pTags = /* @__PURE__ */ new Set([...allowed, ...settings?.collaborators ?? []]);
|
|
32168
32181
|
for (const pk of allowed) tags.push(["allowed", pk]);
|
|
32169
32182
|
for (const pk of pTags) tags.push(["p", pk]);
|
|
32170
32183
|
return tags;
|
|
32171
32184
|
}
|
|
32185
|
+
function buildEncryptedOuterTags(formId, name, relays, settings) {
|
|
32186
|
+
return [
|
|
32187
|
+
["d", formId],
|
|
32188
|
+
["name", name],
|
|
32189
|
+
...relays.map((r) => ["relay", r]),
|
|
32190
|
+
...buildParticipantTags(settings)
|
|
32191
|
+
];
|
|
32192
|
+
}
|
|
32172
32193
|
async function createForm(params) {
|
|
32173
32194
|
await signerManager.getSigner();
|
|
32174
32195
|
const formId = crypto.randomUUID().slice(0, 8);
|
|
@@ -32194,7 +32215,12 @@ async function createForm(params) {
|
|
|
32194
32215
|
await appendToMyFormsList(signingPubkey, formId, relays[0] ?? "", signingKeyHex, viewKeyHex);
|
|
32195
32216
|
return { formId, pubkey: signingPubkey, signingKey: signingKeyHex, viewKey: viewKeyHex };
|
|
32196
32217
|
}
|
|
32197
|
-
const tags = [
|
|
32218
|
+
const tags = [
|
|
32219
|
+
...specRows,
|
|
32220
|
+
["t", "public"],
|
|
32221
|
+
...relays.map((r) => ["relay", r]),
|
|
32222
|
+
...buildParticipantTags(params.settings)
|
|
32223
|
+
];
|
|
32198
32224
|
const event = {
|
|
32199
32225
|
kind: FORM_KINDS.template,
|
|
32200
32226
|
created_at: Math.floor(Date.now() / 1e3),
|
|
@@ -32514,7 +32540,12 @@ async function updateForm(params) {
|
|
|
32514
32540
|
await nostrRuntime.publish(relays, finalizeEvent(event2, signingKeyBytes));
|
|
32515
32541
|
return;
|
|
32516
32542
|
}
|
|
32517
|
-
const tags = [
|
|
32543
|
+
const tags = [
|
|
32544
|
+
...specRows,
|
|
32545
|
+
["t", "public"],
|
|
32546
|
+
...relays.map((r) => ["relay", r]),
|
|
32547
|
+
...buildParticipantTags(settings)
|
|
32548
|
+
];
|
|
32518
32549
|
const event = {
|
|
32519
32550
|
kind: FORM_KINDS.template,
|
|
32520
32551
|
created_at: Math.floor(Date.now() / 1e3),
|
|
@@ -32646,6 +32677,7 @@ function parseResponseEvent(event) {
|
|
|
32646
32677
|
var service_exports2 = {};
|
|
32647
32678
|
__export(service_exports2, {
|
|
32648
32679
|
addEventToCalendarList: () => addEventToCalendarList,
|
|
32680
|
+
createCalendarEvent: () => createCalendarEvent,
|
|
32649
32681
|
createCalendarList: () => createCalendarList,
|
|
32650
32682
|
deleteCalendarEvent: () => deleteCalendarEvent,
|
|
32651
32683
|
deleteCalendarList: () => deleteCalendarList,
|
|
@@ -33076,6 +33108,33 @@ async function publishPrivateCalendarEvent(draft, calendarId) {
|
|
|
33076
33108
|
event: signed
|
|
33077
33109
|
};
|
|
33078
33110
|
}
|
|
33111
|
+
async function createCalendarEvent(draft, opts = {}) {
|
|
33112
|
+
let calendars = opts.calendars ?? null;
|
|
33113
|
+
const loadCalendars = async () => calendars ?? (calendars = await fetchCalendarLists());
|
|
33114
|
+
let targetCalendarId = draft.calendarId;
|
|
33115
|
+
let targetCalendar;
|
|
33116
|
+
if (targetCalendarId) {
|
|
33117
|
+
targetCalendar = (await loadCalendars()).find((c) => c.id === targetCalendarId);
|
|
33118
|
+
} else if (draft.isPrivate) {
|
|
33119
|
+
targetCalendar = (await loadCalendars())[0];
|
|
33120
|
+
if (!targetCalendar) targetCalendar = await createCalendarList("My Calendar", "#334155");
|
|
33121
|
+
targetCalendarId = targetCalendar.id;
|
|
33122
|
+
}
|
|
33123
|
+
const event = draft.isPrivate ? await publishPrivateCalendarEvent(
|
|
33124
|
+
{ ...draft, calendarId: targetCalendarId },
|
|
33125
|
+
targetCalendarId ?? "default"
|
|
33126
|
+
) : await publishPublicCalendarEvent(draft);
|
|
33127
|
+
let calendar;
|
|
33128
|
+
if (targetCalendar) {
|
|
33129
|
+
const ref = [
|
|
33130
|
+
`${event.kind}:${event.user}:${event.id}`,
|
|
33131
|
+
event.relayHint ?? "",
|
|
33132
|
+
event.viewKey ?? ""
|
|
33133
|
+
];
|
|
33134
|
+
calendar = await addEventToCalendarList(targetCalendar, ref);
|
|
33135
|
+
}
|
|
33136
|
+
return { event, calendar };
|
|
33137
|
+
}
|
|
33079
33138
|
async function fetchRelayListsForPubkeys(pubkeys) {
|
|
33080
33139
|
const result = /* @__PURE__ */ new Map();
|
|
33081
33140
|
if (pubkeys.length === 0) return result;
|
|
@@ -34202,6 +34261,7 @@ async function fetchDocMetadata(address) {
|
|
|
34202
34261
|
async function saveDocMetadata(address, patch) {
|
|
34203
34262
|
const existing = await fetchDocMetadata(address) ?? {};
|
|
34204
34263
|
const merged = { ...existing, ...patch };
|
|
34264
|
+
if (!Array.isArray(merged.tags)) merged.tags = [];
|
|
34205
34265
|
const signer = await signerManager.getSigner();
|
|
34206
34266
|
const content = await nip44SelfEncrypt(signer, JSON.stringify(merged));
|
|
34207
34267
|
const signed = await signer.signEvent({
|
|
@@ -34386,7 +34446,9 @@ async function uploadFile(params) {
|
|
|
34386
34446
|
name: params.file.name,
|
|
34387
34447
|
hash: result.sha256,
|
|
34388
34448
|
size: params.file.size,
|
|
34389
|
-
|
|
34449
|
+
// Browsers report "" for unknown MIME types; coalesce exactly as upstream
|
|
34450
|
+
// formstr-drive's uploadPreparedFile does so the on-wire metadata matches.
|
|
34451
|
+
type: params.file.type || "application/octet-stream",
|
|
34390
34452
|
folder: params.folder ?? "/",
|
|
34391
34453
|
uploadedAt: Date.now(),
|
|
34392
34454
|
server,
|
|
@@ -34602,8 +34664,9 @@ async function createPoll(draft) {
|
|
|
34602
34664
|
for (const tag of draft.hashtags ?? []) {
|
|
34603
34665
|
tags.push(["t", tag]);
|
|
34604
34666
|
}
|
|
34605
|
-
|
|
34606
|
-
|
|
34667
|
+
const endsAtSec = draft.endsAt && Number.isFinite(draft.endsAt.getTime()) ? Math.floor(draft.endsAt.getTime() / 1e3) : void 0;
|
|
34668
|
+
if (endsAtSec !== void 0) {
|
|
34669
|
+
tags.push(["endsAt", String(endsAtSec)]);
|
|
34607
34670
|
}
|
|
34608
34671
|
const event = {
|
|
34609
34672
|
kind: POLLS_KINDS.poll,
|
|
@@ -34620,7 +34683,7 @@ async function createPoll(draft) {
|
|
|
34620
34683
|
pollType: draft.pollType,
|
|
34621
34684
|
pubkey,
|
|
34622
34685
|
createdAt: signed.created_at,
|
|
34623
|
-
endsAt:
|
|
34686
|
+
endsAt: endsAtSec,
|
|
34624
34687
|
relays,
|
|
34625
34688
|
hashtags: draft.hashtags ?? [],
|
|
34626
34689
|
event: signed
|
|
@@ -34815,6 +34878,25 @@ function computeResults(responses) {
|
|
|
34815
34878
|
return { results, totalVotes: totalVoters };
|
|
34816
34879
|
}
|
|
34817
34880
|
|
|
34881
|
+
// ../agent/src/tools/pubkey.ts
|
|
34882
|
+
function normalizePubkey(value) {
|
|
34883
|
+
const trimmed = value.trim();
|
|
34884
|
+
if (!trimmed) return null;
|
|
34885
|
+
if (/^[0-9a-f]{64}$/i.test(trimmed)) return trimmed.toLowerCase();
|
|
34886
|
+
if (trimmed.startsWith("npub1")) {
|
|
34887
|
+
try {
|
|
34888
|
+
const decoded = nip19_exports.decode(trimmed);
|
|
34889
|
+
if (decoded.type === "npub") return decoded.data;
|
|
34890
|
+
} catch {
|
|
34891
|
+
}
|
|
34892
|
+
}
|
|
34893
|
+
return null;
|
|
34894
|
+
}
|
|
34895
|
+
function normalizePubkeyList(values) {
|
|
34896
|
+
if (!Array.isArray(values)) return [];
|
|
34897
|
+
return values.map((v) => typeof v === "string" ? normalizePubkey(v) : null).filter((p) => !!p);
|
|
34898
|
+
}
|
|
34899
|
+
|
|
34818
34900
|
// ../agent/src/tools/calendar.ts
|
|
34819
34901
|
var calendarTools = buildCalendarTools();
|
|
34820
34902
|
function buildCalendarTools() {
|
|
@@ -34854,7 +34936,7 @@ function buildCalendarTools() {
|
|
|
34854
34936
|
server.registerTool(
|
|
34855
34937
|
"create_calendar_event",
|
|
34856
34938
|
{
|
|
34857
|
-
description: "Schedule a calendar event. start/end are ISO 8601. Set isPrivate:
|
|
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.",
|
|
34858
34940
|
inputSchema: {
|
|
34859
34941
|
title: external_exports.string(),
|
|
34860
34942
|
description: external_exports.string().optional(),
|
|
@@ -34862,6 +34944,7 @@ function buildCalendarTools() {
|
|
|
34862
34944
|
end: external_exports.string().optional(),
|
|
34863
34945
|
location: external_exports.string().optional(),
|
|
34864
34946
|
isPrivate: external_exports.boolean().optional(),
|
|
34947
|
+
calendarId: external_exports.string().optional(),
|
|
34865
34948
|
participants: external_exports.array(external_exports.string()).optional(),
|
|
34866
34949
|
rrule: external_exports.string().optional(),
|
|
34867
34950
|
startTzid: external_exports.string().optional(),
|
|
@@ -34869,6 +34952,22 @@ function buildCalendarTools() {
|
|
|
34869
34952
|
}
|
|
34870
34953
|
},
|
|
34871
34954
|
async (args) => {
|
|
34955
|
+
const isPrivate = args.isPrivate ?? true;
|
|
34956
|
+
const calendars = await service_exports2.fetchCalendarLists();
|
|
34957
|
+
if (isPrivate && !args.calendarId && calendars.length > 0) {
|
|
34958
|
+
const choices = calendars.map((c) => `${c.title} (${c.id})`).join("; ");
|
|
34959
|
+
return fail(
|
|
34960
|
+
`Which calendar should "${args.title}" go in? Re-run create_calendar_event with calendarId set to one of: ${choices}.`,
|
|
34961
|
+
"CALENDAR_REQUIRED"
|
|
34962
|
+
);
|
|
34963
|
+
}
|
|
34964
|
+
if (args.calendarId && !calendars.some((c) => c.id === args.calendarId)) {
|
|
34965
|
+
const available = calendars.map((c) => c.id).join(", ") || "(none)";
|
|
34966
|
+
return fail(
|
|
34967
|
+
`No calendar found for id ${args.calendarId}. Available: ${available}.`,
|
|
34968
|
+
"NOT_FOUND"
|
|
34969
|
+
);
|
|
34970
|
+
}
|
|
34872
34971
|
const begin = new Date(args.start);
|
|
34873
34972
|
const end = args.end ? new Date(args.end) : new Date(begin.getTime() + 36e5);
|
|
34874
34973
|
const draft = {
|
|
@@ -34877,19 +34976,26 @@ function buildCalendarTools() {
|
|
|
34877
34976
|
begin,
|
|
34878
34977
|
end,
|
|
34879
34978
|
location: args.location,
|
|
34880
|
-
|
|
34881
|
-
|
|
34979
|
+
// Accept npub OR hex for each participant — the wire (["p"] tags, NIP-59
|
|
34980
|
+
// invitation wraps, relay-list query) needs hex, so convert here.
|
|
34981
|
+
participants: normalizePubkeyList(args.participants),
|
|
34982
|
+
isPrivate,
|
|
34983
|
+
calendarId: args.calendarId,
|
|
34882
34984
|
rrule: args.rrule,
|
|
34883
34985
|
startTzid: args.startTzid,
|
|
34884
34986
|
registrationFormRef: args.registrationFormRef
|
|
34885
34987
|
};
|
|
34886
|
-
const event
|
|
34988
|
+
const { event, calendar: list } = await service_exports2.createCalendarEvent(draft, { calendars });
|
|
34887
34989
|
const coordinate = `${event.kind}:${event.user}:${event.id}`;
|
|
34888
|
-
return ok(
|
|
34889
|
-
|
|
34890
|
-
|
|
34891
|
-
|
|
34892
|
-
|
|
34990
|
+
return ok(
|
|
34991
|
+
`Created ${isPrivate ? "private" : "public"} event "${args.title}"${list ? ` in calendar "${list.title}"` : ""}.`,
|
|
34992
|
+
{
|
|
34993
|
+
id: event.id,
|
|
34994
|
+
eventId: event.eventId,
|
|
34995
|
+
coordinate,
|
|
34996
|
+
calendarId: list?.id
|
|
34997
|
+
}
|
|
34998
|
+
);
|
|
34893
34999
|
}
|
|
34894
35000
|
);
|
|
34895
35001
|
server.registerTool(
|
|
@@ -34899,7 +35005,8 @@ function buildCalendarTools() {
|
|
|
34899
35005
|
inputSchema: { coordinate: external_exports.string() }
|
|
34900
35006
|
},
|
|
34901
35007
|
async ({ coordinate }) => {
|
|
34902
|
-
const
|
|
35008
|
+
const viewKey = await service_exports2.lookupEventViewKey(coordinate);
|
|
35009
|
+
const event = await service_exports2.fetchCalendarEventByCoordinate(coordinate, viewKey);
|
|
34903
35010
|
if (!event) return fail(`No event found for ${coordinate}.`, "NOT_FOUND");
|
|
34904
35011
|
return ok(`Event "${event.title}".`, {
|
|
34905
35012
|
event: {
|
|
@@ -35150,7 +35257,8 @@ function buildCalendarTools() {
|
|
|
35150
35257
|
`updates event ${args.coordinate}`
|
|
35151
35258
|
);
|
|
35152
35259
|
if (blocked) return blocked;
|
|
35153
|
-
const
|
|
35260
|
+
const viewKey = await service_exports2.lookupEventViewKey(args.coordinate);
|
|
35261
|
+
const existing = await service_exports2.fetchCalendarEventByCoordinate(args.coordinate, viewKey);
|
|
35154
35262
|
if (!existing) return fail(`No event found for ${args.coordinate}.`, "NOT_FOUND");
|
|
35155
35263
|
const draft = {
|
|
35156
35264
|
title: args.title ?? existing.title,
|
|
@@ -35192,7 +35300,8 @@ function buildCalendarTools() {
|
|
|
35192
35300
|
`attaches a form to ${coordinate}`
|
|
35193
35301
|
);
|
|
35194
35302
|
if (blocked) return blocked;
|
|
35195
|
-
const
|
|
35303
|
+
const viewKey = await service_exports2.lookupEventViewKey(coordinate);
|
|
35304
|
+
const existing = await service_exports2.fetchCalendarEventByCoordinate(coordinate, viewKey);
|
|
35196
35305
|
if (!existing) return fail(`No event found for ${coordinate}.`, "NOT_FOUND");
|
|
35197
35306
|
const draft = {
|
|
35198
35307
|
title: existing.title,
|
|
@@ -35471,23 +35580,6 @@ function buildDriveTools() {
|
|
|
35471
35580
|
}
|
|
35472
35581
|
|
|
35473
35582
|
// ../agent/src/tools/shared.ts
|
|
35474
|
-
function normalizePubkey(value) {
|
|
35475
|
-
const trimmed = value.trim();
|
|
35476
|
-
if (!trimmed) return null;
|
|
35477
|
-
if (/^[0-9a-f]{64}$/i.test(trimmed)) return trimmed.toLowerCase();
|
|
35478
|
-
if (trimmed.startsWith("npub1")) {
|
|
35479
|
-
try {
|
|
35480
|
-
const decoded = nip19_exports.decode(trimmed);
|
|
35481
|
-
if (decoded.type === "npub") return decoded.data;
|
|
35482
|
-
} catch {
|
|
35483
|
-
}
|
|
35484
|
-
}
|
|
35485
|
-
return null;
|
|
35486
|
-
}
|
|
35487
|
-
function normalizePubkeyList(values) {
|
|
35488
|
-
if (!Array.isArray(values)) return [];
|
|
35489
|
-
return values.map((v) => typeof v === "string" ? normalizePubkey(v) : null).filter((p) => !!p);
|
|
35490
|
-
}
|
|
35491
35583
|
var ANSWER_TYPES = new Set(Object.values(AnswerType));
|
|
35492
35584
|
var LEGACY_TYPE_ALIASES = {
|
|
35493
35585
|
multiChoiceGrid: "multipleChoiceGrid" /* multipleChoiceGrid */
|
|
@@ -46521,8 +46613,10 @@ async function main() {
|
|
|
46521
46613
|
await runLogin(cli);
|
|
46522
46614
|
return cli.command;
|
|
46523
46615
|
case "logout": {
|
|
46524
|
-
await doLogout(await buildMcpSigner(), cli.account);
|
|
46525
|
-
console.error(
|
|
46616
|
+
const removed = await doLogout(await buildMcpSigner(), cli.target ?? cli.account);
|
|
46617
|
+
console.error(
|
|
46618
|
+
removed ? `formstr-mcp: removed ${removed.npub} (${removed.method}).` : "formstr-mcp: no account to remove."
|
|
46619
|
+
);
|
|
46526
46620
|
return cli.command;
|
|
46527
46621
|
}
|
|
46528
46622
|
case "whoami": {
|