minutes-mcp 0.28.2 → 0.29.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/dist/index.d.ts +2 -0
- package/dist/index.js +63 -36
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -640,6 +640,8 @@ export declare function verifiedStopRecordingSummary(snapshot: {
|
|
|
640
640
|
};
|
|
641
641
|
};
|
|
642
642
|
}): string;
|
|
643
|
+
/** Temporal projection consumes only the exact authorized corpus snapshot. */
|
|
644
|
+
export declare function policyDecisionHistory(snapshot: StableCorpusSnapshot): reader.DecisionHistoryEntry[];
|
|
643
645
|
export declare function readVerifiedScreenImage(imagePath: string, expectedByteSize: number, expectedSha256: string, screenRoot?: string, hooks?: BoundReadHooks): Promise<Buffer>;
|
|
644
646
|
export declare function restrictedMeetingStubResult(meeting: PolicyVerifiedMeeting): {
|
|
645
647
|
content: {
|
package/dist/index.js
CHANGED
|
@@ -841,11 +841,11 @@ export async function policySearchMeetings(dir, query, limit, includeRestricted,
|
|
|
841
841
|
const boundedLimit = normalizeMcpResultLimit(limit, MCP_POLICY_MEETING_RESULT_MAX, "policy search");
|
|
842
842
|
if (!query)
|
|
843
843
|
return [];
|
|
844
|
-
|
|
845
|
-
return policyMatchingSnapshotOperation(dir, includeRestricted, (meeting) => meeting.frontmatter.title.toLowerCase().includes(needle) ||
|
|
846
|
-
meeting.body.toLowerCase().includes(needle), (snapshots) => {
|
|
844
|
+
return policyMatchingSnapshotOperation(dir, includeRestricted, (meeting) => reader.lexicalMatch(meeting.frontmatter.title, meeting.body, query) !== null, (snapshots) => {
|
|
847
845
|
const results = [];
|
|
848
|
-
|
|
846
|
+
const ranked = snapshots.map((snapshot) => ({ snapshot, score: reader.lexicalMatch(snapshot.meeting.frontmatter.title, snapshot.meeting.body, query)?.score ?? 0 }));
|
|
847
|
+
ranked.sort((a, b) => reader.compareSearchMatches({ score: a.score, date: a.snapshot.meeting.frontmatter.date, path: a.snapshot.path }, { score: b.score, date: b.snapshot.meeting.frontmatter.date, path: b.snapshot.path }));
|
|
848
|
+
for (const { snapshot } of ranked) {
|
|
849
849
|
results.push(snapshot.meeting);
|
|
850
850
|
if (results.length >= boundedLimit)
|
|
851
851
|
break;
|
|
@@ -867,17 +867,10 @@ function meetingMatchesSince(meeting, since) {
|
|
|
867
867
|
return Number.isFinite(boundary) && Number.isFinite(meetingDate) && meetingDate >= boundary;
|
|
868
868
|
}
|
|
869
869
|
function meetingMatchesPerson(meeting, person) {
|
|
870
|
-
if (
|
|
870
|
+
if (person === undefined)
|
|
871
871
|
return true;
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
return true;
|
|
875
|
-
const people = [
|
|
876
|
-
...meeting.frontmatter.attendees,
|
|
877
|
-
...meeting.frontmatter.people,
|
|
878
|
-
...(meeting.frontmatter.attendees_raw || "").split(/[,;\n]/),
|
|
879
|
-
];
|
|
880
|
-
return people.some((entry) => entry.trim().toLowerCase().includes(needle));
|
|
872
|
+
const identity = reader.resolvePersonIdentity([meeting], person);
|
|
873
|
+
return !!identity && reader.meetingMentionsPerson(meeting, identity);
|
|
881
874
|
}
|
|
882
875
|
function normalizedPersonSelector(value) {
|
|
883
876
|
return (value ?? "").trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -910,7 +903,7 @@ export function policyIntentResults(meetings, query, intentKind, owner, limit =
|
|
|
910
903
|
const queryFields = includeMeetingTitleInQuery
|
|
911
904
|
? [what, who || "", meeting.frontmatter.title]
|
|
912
905
|
: [what, who || ""];
|
|
913
|
-
if (needle && !
|
|
906
|
+
if (needle && !reader.lexicalMatch("", queryFields.join(" "), query))
|
|
914
907
|
return false;
|
|
915
908
|
if (ownerNeedle && !ownerMatchesExactSelector(who, ownerNeedle))
|
|
916
909
|
return false;
|
|
@@ -1037,17 +1030,30 @@ function meetingMatchesToolSearch(meeting, filter) {
|
|
|
1037
1030
|
if (intentMode) {
|
|
1038
1031
|
return policyIntentResults([meeting], filter.query, filter.intentKind, filter.owner, 1).length > 0;
|
|
1039
1032
|
}
|
|
1040
|
-
|
|
1041
|
-
return needle.length > 0 &&
|
|
1042
|
-
(meeting.frontmatter.title.toLowerCase().includes(needle) ||
|
|
1043
|
-
meeting.body.toLowerCase().includes(needle));
|
|
1033
|
+
return reader.lexicalMatch(meeting.frontmatter.title, meeting.body, filter.query) !== null;
|
|
1044
1034
|
}
|
|
1045
1035
|
export function collectPolicyToolSearchSnapshots(snapshot, includeRestricted, filter) {
|
|
1046
|
-
|
|
1036
|
+
const matches = [];
|
|
1037
|
+
for (const file of snapshot.files) {
|
|
1038
|
+
if (!isActiveCorpusMeetingPath(file.path, snapshot.canonicalRoot))
|
|
1039
|
+
continue;
|
|
1040
|
+
const meeting = parsePolicyVerifiedMeeting(file.content, file.path);
|
|
1041
|
+
if (!meeting || (!includeRestricted && meetingSensitivity(meeting) === "restricted"))
|
|
1042
|
+
continue;
|
|
1043
|
+
if (!meetingMatchesToolSearch(meeting, filter))
|
|
1044
|
+
continue;
|
|
1045
|
+
meeting.retrieval = reader.lexicalMatch(meeting.frontmatter.title, meeting.body, filter.query) ?? undefined;
|
|
1046
|
+
matches.push({ path: file.path, content: file.content, meeting });
|
|
1047
|
+
}
|
|
1048
|
+
return matches.sort((a, b) => reader.compareSearchMatches({ score: a.meeting.retrieval?.score ?? 0, date: a.meeting.frontmatter.date, path: a.path }, { score: b.meeting.retrieval?.score ?? 0, date: b.meeting.frontmatter.date, path: b.path })).slice(0, MCP_POLICY_MEETING_RESULT_MAX);
|
|
1047
1049
|
}
|
|
1048
|
-
async function policyToolSearchMeetings(dir, includeRestricted, filter) {
|
|
1050
|
+
async function policyToolSearchMeetings(dir, includeRestricted, filter, limit = MCP_MEETING_RESULT_MAX) {
|
|
1049
1051
|
return withStableCorpusLease(dir, (snapshot) => collectPolicyToolSearchSnapshots(snapshot, includeRestricted, filter)
|
|
1050
|
-
.
|
|
1052
|
+
.slice(0, limit)
|
|
1053
|
+
.map((entry) => ({ ...entry.meeting, evidence: reader.buildSourceEvidence({
|
|
1054
|
+
path: entry.path, content: entry.content, body: entry.meeting.body,
|
|
1055
|
+
speakerMap: entry.meeting.frontmatter.speaker_map,
|
|
1056
|
+
}, { query: filter.query, limit: 3 }) })));
|
|
1051
1057
|
}
|
|
1052
1058
|
export function openActionsFromMeetings(meetings, limit = MCP_ACTION_RESULT_MAX) {
|
|
1053
1059
|
const boundedLimit = normalizeMcpResultLimit(limit, MCP_ACTION_RESULT_MAX, "open action");
|
|
@@ -1336,7 +1342,7 @@ export function researchTopicProjection(sourceMeetings, query) {
|
|
|
1336
1342
|
for (const meeting of meetings) {
|
|
1337
1343
|
for (const decision of meeting.frontmatter.decisions) {
|
|
1338
1344
|
if (!needle ||
|
|
1339
|
-
!
|
|
1345
|
+
!reader.lexicalMatch(decision.topic || "", decision.text, query)) {
|
|
1340
1346
|
continue;
|
|
1341
1347
|
}
|
|
1342
1348
|
const line = `- ${boundedMcpField(meeting.frontmatter.date) ?? ""} — ${boundedMcpField(decision.text) ?? ""} (${boundedMcpField(meeting.frontmatter.title) ?? ""})`;
|
|
@@ -1351,7 +1357,7 @@ export function researchTopicProjection(sourceMeetings, query) {
|
|
|
1351
1357
|
const topicCounts = new Map();
|
|
1352
1358
|
for (const meeting of meetings) {
|
|
1353
1359
|
for (const rawTag of meeting.frontmatter.tags) {
|
|
1354
|
-
if (!needle || !
|
|
1360
|
+
if (!needle || !reader.lexicalMatch(rawTag, "", query))
|
|
1355
1361
|
continue;
|
|
1356
1362
|
const topic = boundedMcpField(rawTag) ?? "";
|
|
1357
1363
|
if (!topic)
|
|
@@ -1591,7 +1597,7 @@ const COPILOT_SUPPORTED = hasFeature(CLI_CAPABILITIES, "copilot_realtime");
|
|
|
1591
1597
|
// `./version.ts` (see issue #183). Hosted `.mcpb` bundles will run
|
|
1592
1598
|
// against CLIs with different minor/patch numbers within the same
|
|
1593
1599
|
// major; that is explicitly supported.
|
|
1594
|
-
const MCP_SERVER_VERSION = "0.
|
|
1600
|
+
const MCP_SERVER_VERSION = "0.29.0";
|
|
1595
1601
|
export function parseKnowledgeConfig(configContent) {
|
|
1596
1602
|
const knowledgeMatch = configContent.match(/\[knowledge\][\s\S]*?(?=\n\[|$)/);
|
|
1597
1603
|
if (!knowledgeMatch) {
|
|
@@ -4109,7 +4115,7 @@ registerDocsAppTool("search_meetings", {
|
|
|
4109
4115
|
intentKind: intent_kind,
|
|
4110
4116
|
owner,
|
|
4111
4117
|
intentsOnly: intents_only,
|
|
4112
|
-
});
|
|
4118
|
+
}, intentMode ? MCP_POLICY_MEETING_RESULT_MAX : boundedLimit);
|
|
4113
4119
|
let results;
|
|
4114
4120
|
if (intentMode) {
|
|
4115
4121
|
results = policyIntentResults(meetings, query, intent_kind, owner, boundedLimit);
|
|
@@ -4119,7 +4125,9 @@ registerDocsAppTool("search_meetings", {
|
|
|
4119
4125
|
for (const meeting of meetings) {
|
|
4120
4126
|
matches.push({
|
|
4121
4127
|
...meetingSearchItem(meeting),
|
|
4122
|
-
snippet: liveMeetingSnippet(meeting.body, query),
|
|
4128
|
+
snippet: meeting.evidence?.segments[0]?.quote ?? liveMeetingSnippet(meeting.body, query),
|
|
4129
|
+
retrieval: meeting.retrieval,
|
|
4130
|
+
evidence: meeting.evidence,
|
|
4123
4131
|
});
|
|
4124
4132
|
if (matches.length >= boundedLimit)
|
|
4125
4133
|
break;
|
|
@@ -4311,6 +4319,17 @@ if (hasFeature(CLI_CAPABILITIES, "get_moment"))
|
|
|
4311
4319
|
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, view: "context", kind: "get_moment" },
|
|
4312
4320
|
};
|
|
4313
4321
|
});
|
|
4322
|
+
/** Temporal projection consumes only the exact authorized corpus snapshot. */
|
|
4323
|
+
export function policyDecisionHistory(snapshot) {
|
|
4324
|
+
const sources = snapshot.files.flatMap((file) => {
|
|
4325
|
+
if (!isActiveCorpusMeetingPath(file.path, snapshot.canonicalRoot))
|
|
4326
|
+
return [];
|
|
4327
|
+
const meeting = parsePolicyVerifiedMeeting(file.content, file.path);
|
|
4328
|
+
return !meeting || meetingSensitivity(meeting) === "restricted"
|
|
4329
|
+
? [] : [{ ...meeting, content: file.content }];
|
|
4330
|
+
});
|
|
4331
|
+
return reader.buildDecisionHistory(sources);
|
|
4332
|
+
}
|
|
4314
4333
|
// ── Tool: consistency_report ───────────────────────────────
|
|
4315
4334
|
// ── Tool: get_screen_context ───────────────────────────────
|
|
4316
4335
|
// Direct image content is returned only for paths that the CLI resolved from
|
|
@@ -4445,14 +4464,18 @@ registerDocsAppTool("consistency_report", {
|
|
|
4445
4464
|
annotations: { title: "Consistency Report", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
4446
4465
|
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
4447
4466
|
}, async ({ owner, stale_after_days }) => {
|
|
4467
|
+
const meetingsDir = await getEffectiveMeetingsDir();
|
|
4448
4468
|
if (!(await isCliAvailable())) {
|
|
4449
|
-
|
|
4469
|
+
const decisionHistory = await withStableCorpusLease(meetingsDir, policyDecisionHistory);
|
|
4470
|
+
return {
|
|
4471
|
+
content: [{ type: "text", text: `Decision history: ${decisionHistory.length} sourced records. Full stale-commitment analysis requires the Minutes CLI.` }],
|
|
4472
|
+
structuredContent: { decision_conflicts: [], stale_commitments: [], decision_history: decisionHistory, analysis_available: false, view: "report" },
|
|
4473
|
+
};
|
|
4450
4474
|
}
|
|
4451
4475
|
const capabilities = await ensureCliCapabilities(["consistency_corpus_root_v1"]);
|
|
4452
4476
|
if (capabilities.kind !== "report" || !hasFeature(capabilities, "consistency_corpus_root_v1")) {
|
|
4453
4477
|
throw new Error("Consistency reports require a Minutes CLI that supports the selected meeting folder. Update Minutes, then try again.");
|
|
4454
4478
|
}
|
|
4455
|
-
const meetingsDir = await getEffectiveMeetingsDir();
|
|
4456
4479
|
const args = ["consistency", "--dir", meetingsDir, "--stale-after-days", String(stale_after_days)];
|
|
4457
4480
|
if (owner)
|
|
4458
4481
|
args.push("--owner", owner);
|
|
@@ -4461,6 +4484,7 @@ registerDocsAppTool("consistency_report", {
|
|
|
4461
4484
|
if (!report || typeof report !== "object") {
|
|
4462
4485
|
return { content: [{ type: "text", text: stderr || stdout }] };
|
|
4463
4486
|
}
|
|
4487
|
+
const decisionHistory = await withStableCorpusLease(meetingsDir, policyDecisionHistory);
|
|
4464
4488
|
const decisionConflicts = Array.isArray(report.decision_conflicts)
|
|
4465
4489
|
? report.decision_conflicts
|
|
4466
4490
|
: [];
|
|
@@ -4470,7 +4494,7 @@ registerDocsAppTool("consistency_report", {
|
|
|
4470
4494
|
if (decisionConflicts.length === 0 && staleCommitments.length === 0) {
|
|
4471
4495
|
return {
|
|
4472
4496
|
content: [{ type: "text", text: "No consistency issues found." }],
|
|
4473
|
-
structuredContent: { decision_conflicts: [], stale_commitments: [], view: "report" },
|
|
4497
|
+
structuredContent: { decision_conflicts: [], stale_commitments: [], decision_history: decisionHistory, view: "report" },
|
|
4474
4498
|
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, view: "report" },
|
|
4475
4499
|
};
|
|
4476
4500
|
}
|
|
@@ -4489,7 +4513,7 @@ registerDocsAppTool("consistency_report", {
|
|
|
4489
4513
|
}
|
|
4490
4514
|
return {
|
|
4491
4515
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4492
|
-
structuredContent: { decision_conflicts: decisionConflicts, stale_commitments: staleCommitments, view: "report" },
|
|
4516
|
+
structuredContent: { decision_conflicts: decisionConflicts, stale_commitments: staleCommitments, decision_history: decisionHistory, view: "report" },
|
|
4493
4517
|
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, view: "report" },
|
|
4494
4518
|
};
|
|
4495
4519
|
});
|
|
@@ -4585,14 +4609,14 @@ registerTool("research_topic", "Research a topic across policy-authorized meetin
|
|
|
4585
4609
|
const needle = query.trim().toLowerCase();
|
|
4586
4610
|
const meetingsDir = await getEffectiveMeetingsDir();
|
|
4587
4611
|
const sourceMeetings = await policyListMeetings(meetingsDir, MCP_POLICY_MEETING_RESULT_MAX, include_restricted);
|
|
4612
|
+
const identity = attendee === undefined ? undefined : reader.resolvePersonIdentity(sourceMeetings, attendee);
|
|
4588
4613
|
const meetings = [];
|
|
4589
4614
|
if (needle.length > 0) {
|
|
4590
4615
|
for (const meeting of sourceMeetings) {
|
|
4591
4616
|
if ((contentType && meeting.frontmatter.type !== contentType) ||
|
|
4592
4617
|
!meetingMatchesSince(meeting, since) ||
|
|
4593
|
-
!
|
|
4594
|
-
(
|
|
4595
|
-
!meeting.body.toLowerCase().includes(needle))) {
|
|
4618
|
+
(attendee !== undefined && (!identity || !reader.meetingMentionsPerson(meeting, identity))) ||
|
|
4619
|
+
!reader.lexicalMatch(meeting.frontmatter.title, meeting.body, query)) {
|
|
4596
4620
|
continue;
|
|
4597
4621
|
}
|
|
4598
4622
|
meetings.push(meeting);
|
|
@@ -4699,10 +4723,13 @@ registerDocsAppTool("get_meeting", {
|
|
|
4699
4723
|
// Non-fatal: fall through to raw content with no speaker_map enrichment.
|
|
4700
4724
|
}
|
|
4701
4725
|
}
|
|
4702
|
-
let structuredOut =
|
|
4726
|
+
let structuredOut = {
|
|
4727
|
+
...structured,
|
|
4728
|
+
evidence: reader.buildSourceEvidence({ path: resolved, content: rawContent, body: rawParsed.body, speakerMap: rawParsed.frontmatter.speaker_map }),
|
|
4729
|
+
};
|
|
4703
4730
|
if (include_restricted && meetingSensitivity(sensitivityParsed) === "restricted") {
|
|
4704
4731
|
structuredOut = {
|
|
4705
|
-
...
|
|
4732
|
+
...structuredOut,
|
|
4706
4733
|
sensitivity: "restricted",
|
|
4707
4734
|
sensitivity_override: { applied: true, logged: "durable-jsonl" },
|
|
4708
4735
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minutes-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"mcpName": "io.github.silverstein/minutes",
|
|
5
5
|
"description": "MCP server for minutes \u2014 conversation memory for AI assistants. Works with Claude Desktop, Mistral Vibe, Cursor, Windsurf, and any MCP client.",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@modelcontextprotocol/ext-apps": "^2.0.0",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
50
|
-
"minutes-sdk": "0.
|
|
50
|
+
"minutes-sdk": "0.29.0",
|
|
51
51
|
"yaml": "^2.8.3",
|
|
52
52
|
"zod": "^3.25 || ^4.0"
|
|
53
53
|
},
|