@damper/mcp 0.3.14 ā 0.3.15
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/formatters.d.ts +1 -0
- package/dist/formatters.js +15 -9
- package/dist/index.js +192 -8
- package/package.json +1 -1
package/dist/formatters.d.ts
CHANGED
package/dist/formatters.js
CHANGED
|
@@ -9,22 +9,29 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export function formatStartTaskResponse(result) {
|
|
11
11
|
const lines = [`ā
Started ${result.id}: ${result.message}`];
|
|
12
|
+
// Critical rules section - shown first and prominently
|
|
13
|
+
if (result.context?.criticalRules && result.context.criticalRules.length > 0) {
|
|
14
|
+
lines.push('\nšØ **CRITICAL RULES (do not skip):**');
|
|
15
|
+
for (const rule of result.context.criticalRules) {
|
|
16
|
+
lines.push(`⢠${rule}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
12
19
|
// Context section - emphatic about reading it first
|
|
13
20
|
if (result.context) {
|
|
14
21
|
if (result.context.isEmpty) {
|
|
15
22
|
lines.push(`\nš ${result.context.hint || 'No project context available.'}`);
|
|
16
23
|
}
|
|
17
24
|
else {
|
|
18
|
-
lines.push('\n
|
|
25
|
+
lines.push('\nš **Project context available.** Read sections relevant to your task:');
|
|
19
26
|
lines.push('');
|
|
20
27
|
if (result.context.relevantSections && result.context.relevantSections.length > 0) {
|
|
21
|
-
lines.push(`**
|
|
28
|
+
lines.push(`**Suggested for this task:**`);
|
|
22
29
|
for (const section of result.context.relevantSections) {
|
|
23
30
|
lines.push(`ā \`get_context_section("${section}")\``);
|
|
24
31
|
}
|
|
25
32
|
lines.push('');
|
|
26
33
|
}
|
|
27
|
-
lines.push('**All
|
|
34
|
+
lines.push('**All sections:**');
|
|
28
35
|
for (const s of result.context.index) {
|
|
29
36
|
const relevant = result.context.relevantSections?.includes(s.section) ? ' ā' : '';
|
|
30
37
|
lines.push(`⢠${s.section}${relevant}`);
|
|
@@ -32,12 +39,11 @@ export function formatStartTaskResponse(result) {
|
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
lines.push('\n---');
|
|
35
|
-
lines.push('**š
|
|
36
|
-
lines.push('1.
|
|
37
|
-
lines.push('2. `add_note
|
|
38
|
-
lines.push('3.
|
|
39
|
-
lines.push('4. `
|
|
40
|
-
lines.push('5. `complete_task` or `abandon_task` (NEVER skip this)');
|
|
42
|
+
lines.push('**š Workflow:**');
|
|
43
|
+
lines.push('1. `add_note`: "Session started: <goal>"');
|
|
44
|
+
lines.push('2. Work: use `add_commit` for commits, `add_note` for decisions');
|
|
45
|
+
lines.push('3. `add_note`: "Session end: <summary>"');
|
|
46
|
+
lines.push('4. `complete_task` or `abandon_task`');
|
|
41
47
|
return lines.join('\n');
|
|
42
48
|
}
|
|
43
49
|
/**
|
package/dist/index.js
CHANGED
|
@@ -360,6 +360,7 @@ const ContextIndexSchema = z.object({
|
|
|
360
360
|
})),
|
|
361
361
|
relevantSections: z.array(z.string()).optional(),
|
|
362
362
|
hint: z.string().optional(),
|
|
363
|
+
criticalRules: z.array(z.string()).optional(),
|
|
363
364
|
});
|
|
364
365
|
// Tool: Start task
|
|
365
366
|
server.registerTool('start_task', {
|
|
@@ -653,7 +654,7 @@ server.registerTool('get_agent_instructions', {
|
|
|
653
654
|
openWorldHint: false,
|
|
654
655
|
},
|
|
655
656
|
}, async ({ format = 'section' }) => {
|
|
656
|
-
const lastModified = '2025-02-
|
|
657
|
+
const lastModified = '2025-02-05';
|
|
657
658
|
const section = `## Task Management with Damper MCP
|
|
658
659
|
|
|
659
660
|
> Last updated: ${lastModified}
|
|
@@ -662,7 +663,10 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
|
|
|
662
663
|
|
|
663
664
|
### At Session Start (MANDATORY)
|
|
664
665
|
1. \`get_project_context\` - **READ THIS FIRST.** Contains architecture, conventions, and critical project info.
|
|
665
|
-
2.
|
|
666
|
+
2. Load relevant architecture context using blocks (token-efficient):
|
|
667
|
+
- If TASK_CONTEXT.md has an "Available Architecture Context" section, use \`get_section_block_content(section, blockIds)\` to load only the blocks relevant to your task
|
|
668
|
+
- Otherwise, use \`get_section_blocks(section)\` to see what's available, then fetch specific blocks
|
|
669
|
+
- Only fall back to \`get_context_section\` if you need an entire section
|
|
666
670
|
3. \`list_tasks\` - Check for existing tasks to work on
|
|
667
671
|
4. If working on a task: \`start_task\` to lock it
|
|
668
672
|
|
|
@@ -671,13 +675,15 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
|
|
|
671
675
|
- \`add_note\` for decisions: "Decision: chose X because Y"
|
|
672
676
|
- \`update_subtask\` to mark subtask progress
|
|
673
677
|
- **Follow patterns from project context** - Don't reinvent; use existing conventions
|
|
678
|
+
- When you need architecture context mid-task, use \`get_section_block_content\` to load specific blocks rather than full sections
|
|
674
679
|
|
|
675
680
|
### Feedback & Changelog Integration
|
|
676
681
|
- \`link_feedback_to_task\` - Link user feedback IDs to your task (helps track what customer requests led to the feature)
|
|
677
682
|
- When completing a **public** task, it auto-adds to the project's draft changelog
|
|
678
683
|
- \`list_changelogs\` - View existing changelogs and drafts
|
|
679
|
-
- \`create_changelog\` / \`update_changelog\` - Create or edit changelog entries
|
|
684
|
+
- \`create_changelog\` / \`update_changelog\` - Create or edit changelog entries (use \`summary\` for social-friendly posts)
|
|
680
685
|
- \`add_to_changelog\` - Manually add completed tasks to a changelog
|
|
686
|
+
- \`publish_changelog\` - Publish with optional email notifications and Twitter posting
|
|
681
687
|
|
|
682
688
|
### At Session End (MANDATORY)
|
|
683
689
|
- ALWAYS call \`add_note\` with session summary before stopping
|
|
@@ -813,6 +819,10 @@ server.registerTool('update_context_section', {
|
|
|
813
819
|
'names like `overview`, `conventions` are sufficient.\n\n' +
|
|
814
820
|
'**When to add tags**: Only if you have multiple related sections that should surface ' +
|
|
815
821
|
'together (e.g., all "backend" docs). Skip for simple projects.\n\n' +
|
|
822
|
+
'**criticalRules**: Short, actionable rules that agents MUST follow. These are surfaced ' +
|
|
823
|
+
'automatically in `start_task` and `get_project_context` responses without requiring ' +
|
|
824
|
+
'agents to read the full section. Use for rules that, if skipped, cause real problems ' +
|
|
825
|
+
'(e.g., "Use migrations for DB changes: bunx prisma migrate dev --name <feature>").\n\n' +
|
|
816
826
|
'ā ļø SECURITY WARNING: Do NOT include sensitive information:\n' +
|
|
817
827
|
'- API keys, secrets, passwords, tokens\n' +
|
|
818
828
|
'- Database connection strings\n' +
|
|
@@ -826,12 +836,14 @@ server.registerTool('update_context_section', {
|
|
|
826
836
|
content: z.string().describe('Markdown documentation for this section. Must NOT contain secrets, API keys, or sensitive data.'),
|
|
827
837
|
tags: z.array(z.string()).optional().describe('Categorization tags (e.g., ["backend", "critical"]). Helps match sections to task labels.'),
|
|
828
838
|
appliesTo: z.array(z.string()).optional().describe('Module names this section applies to (e.g., ["server", "dashboard"]).'),
|
|
839
|
+
criticalRules: z.array(z.string()).optional().describe('Must-follow rules shown in start_task/get_project_context (e.g., ["Use migrations: bunx prisma migrate dev --name <feature>"])'),
|
|
829
840
|
}),
|
|
830
841
|
outputSchema: z.object({
|
|
831
842
|
success: z.boolean(),
|
|
832
843
|
section: z.string(),
|
|
833
844
|
tags: z.array(z.string()).optional(),
|
|
834
845
|
appliesTo: z.array(z.string()).optional(),
|
|
846
|
+
criticalRules: z.array(z.string()).optional(),
|
|
835
847
|
warnings: z.array(z.string()).optional(),
|
|
836
848
|
}),
|
|
837
849
|
annotations: {
|
|
@@ -840,13 +852,13 @@ server.registerTool('update_context_section', {
|
|
|
840
852
|
idempotentHint: true,
|
|
841
853
|
openWorldHint: false,
|
|
842
854
|
},
|
|
843
|
-
}, async ({ section, content, tags, appliesTo }) => {
|
|
855
|
+
}, async ({ section, content, tags, appliesTo, criticalRules }) => {
|
|
844
856
|
// Encode each path segment individually but preserve slashes for hierarchical sections
|
|
845
857
|
const encodedSection = section
|
|
846
858
|
.split('/')
|
|
847
859
|
.map(part => encodeURIComponent(part))
|
|
848
860
|
.join('/');
|
|
849
|
-
const result = await api('POST', `/api/agent/context/${encodedSection}`, { content, tags, appliesTo });
|
|
861
|
+
const result = await api('POST', `/api/agent/context/${encodedSection}`, { content, tags, appliesTo, criticalRules });
|
|
850
862
|
let text = `š Updated context section: ${result.section}`;
|
|
851
863
|
if (result.tags && result.tags.length > 0) {
|
|
852
864
|
text += ` [${result.tags.join(', ')}]`;
|
|
@@ -909,6 +921,7 @@ server.registerTool('sync_project_context', {
|
|
|
909
921
|
content: z.string().describe('Markdown documentation (no secrets!)'),
|
|
910
922
|
tags: z.array(z.string()).optional().describe('Categorization tags'),
|
|
911
923
|
appliesTo: z.array(z.string()).optional().describe('Module names this applies to'),
|
|
924
|
+
criticalRules: z.array(z.string()).optional().describe('Must-follow rules shown automatically'),
|
|
912
925
|
})).describe('Array of sections to upload'),
|
|
913
926
|
}),
|
|
914
927
|
outputSchema: z.object({
|
|
@@ -949,6 +962,7 @@ server.registerTool('get_project_context', {
|
|
|
949
962
|
})),
|
|
950
963
|
relevantSections: z.array(z.string()).optional(),
|
|
951
964
|
hint: z.string().optional(),
|
|
965
|
+
criticalRules: z.array(z.string()).optional(),
|
|
952
966
|
}),
|
|
953
967
|
annotations: {
|
|
954
968
|
readOnlyHint: true,
|
|
@@ -965,7 +979,16 @@ server.registerTool('get_project_context', {
|
|
|
965
979
|
structuredContent: data,
|
|
966
980
|
};
|
|
967
981
|
}
|
|
968
|
-
const lines = [
|
|
982
|
+
const lines = [];
|
|
983
|
+
// Critical rules first - most important
|
|
984
|
+
if (data.criticalRules && data.criticalRules.length > 0) {
|
|
985
|
+
lines.push('šØ **CRITICAL RULES (do not skip):**');
|
|
986
|
+
for (const rule of data.criticalRules) {
|
|
987
|
+
lines.push(`⢠${rule}`);
|
|
988
|
+
}
|
|
989
|
+
lines.push('');
|
|
990
|
+
}
|
|
991
|
+
lines.push('**Available context sections:**');
|
|
969
992
|
for (const s of data.index) {
|
|
970
993
|
const relevant = data.relevantSections?.includes(s.section) ? ' ā' : '';
|
|
971
994
|
const tags = s.tags && s.tags.length > 0 ? ` [${s.tags.join(', ')}]` : '';
|
|
@@ -979,6 +1002,91 @@ server.registerTool('get_project_context', {
|
|
|
979
1002
|
structuredContent: data,
|
|
980
1003
|
};
|
|
981
1004
|
});
|
|
1005
|
+
// Tool: Get section blocks (heading index)
|
|
1006
|
+
server.registerTool('get_section_blocks', {
|
|
1007
|
+
title: 'Get Section Blocks',
|
|
1008
|
+
description: 'Get a block index for a context section. Returns a list of headings (## and ###) with their ' +
|
|
1009
|
+
'character counts, without the full content. Use this to see what\'s in a section and then ' +
|
|
1010
|
+
'fetch only the blocks you need with `get_section_block_content`.\n\n' +
|
|
1011
|
+
'**When to use:**\n' +
|
|
1012
|
+
'- Before fetching a large section, check what blocks it contains\n' +
|
|
1013
|
+
'- To selectively load only relevant parts of a section\n' +
|
|
1014
|
+
'- To reduce token usage by skipping irrelevant subsections',
|
|
1015
|
+
inputSchema: z.object({
|
|
1016
|
+
section: z.string().describe('Section name (e.g., "api", "testing")'),
|
|
1017
|
+
}),
|
|
1018
|
+
outputSchema: z.object({
|
|
1019
|
+
section: z.string(),
|
|
1020
|
+
blocks: z.array(z.object({
|
|
1021
|
+
id: z.string(),
|
|
1022
|
+
heading: z.string().nullable(),
|
|
1023
|
+
level: z.number(),
|
|
1024
|
+
charCount: z.number(),
|
|
1025
|
+
})),
|
|
1026
|
+
totalChars: z.number(),
|
|
1027
|
+
}),
|
|
1028
|
+
annotations: {
|
|
1029
|
+
readOnlyHint: true,
|
|
1030
|
+
destructiveHint: false,
|
|
1031
|
+
idempotentHint: true,
|
|
1032
|
+
openWorldHint: false,
|
|
1033
|
+
},
|
|
1034
|
+
}, async ({ section }) => {
|
|
1035
|
+
const encodedSection = encodeURIComponent(section);
|
|
1036
|
+
const data = await api('GET', `/api/agent/context/${encodedSection}/blocks`);
|
|
1037
|
+
const lines = [`**Blocks in "${data.section}"** (${data.totalChars} chars total):\n`];
|
|
1038
|
+
for (const block of data.blocks) {
|
|
1039
|
+
const heading = block.heading || '(intro)';
|
|
1040
|
+
lines.push(`⢠${block.id}: ${heading} (${block.charCount} chars)`);
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
1044
|
+
structuredContent: data,
|
|
1045
|
+
};
|
|
1046
|
+
});
|
|
1047
|
+
// Tool: Get section block content
|
|
1048
|
+
server.registerTool('get_section_block_content', {
|
|
1049
|
+
title: 'Get Section Block Content',
|
|
1050
|
+
description: 'Get the full content of specific blocks within a section. Use `get_section_blocks` first ' +
|
|
1051
|
+
'to see available block IDs, then fetch only the ones you need.\n\n' +
|
|
1052
|
+
'**Example workflow:**\n' +
|
|
1053
|
+
'1. `get_section_blocks("api")` ā see list of blocks\n' +
|
|
1054
|
+
'2. `get_section_block_content("api", ["authentication", "agent-endpoints"])` ā get only relevant blocks',
|
|
1055
|
+
inputSchema: z.object({
|
|
1056
|
+
section: z.string().describe('Section name (e.g., "api", "testing")'),
|
|
1057
|
+
blockIds: z.array(z.string()).describe('Block IDs to fetch (from get_section_blocks)'),
|
|
1058
|
+
}),
|
|
1059
|
+
outputSchema: z.object({
|
|
1060
|
+
section: z.string(),
|
|
1061
|
+
blocks: z.array(z.object({
|
|
1062
|
+
id: z.string(),
|
|
1063
|
+
heading: z.string().nullable(),
|
|
1064
|
+
level: z.number(),
|
|
1065
|
+
content: z.string(),
|
|
1066
|
+
charCount: z.number(),
|
|
1067
|
+
})),
|
|
1068
|
+
totalChars: z.number(),
|
|
1069
|
+
}),
|
|
1070
|
+
annotations: {
|
|
1071
|
+
readOnlyHint: true,
|
|
1072
|
+
destructiveHint: false,
|
|
1073
|
+
idempotentHint: true,
|
|
1074
|
+
openWorldHint: false,
|
|
1075
|
+
},
|
|
1076
|
+
}, async ({ section, blockIds }) => {
|
|
1077
|
+
const encodedSection = encodeURIComponent(section);
|
|
1078
|
+
const encodedBlockIds = blockIds.map(id => encodeURIComponent(id)).join(',');
|
|
1079
|
+
const data = await api('GET', `/api/agent/context/${encodedSection}/blocks/${encodedBlockIds}`);
|
|
1080
|
+
const lines = [];
|
|
1081
|
+
for (const block of data.blocks) {
|
|
1082
|
+
lines.push(block.content);
|
|
1083
|
+
lines.push('');
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
content: [{ type: 'text', text: lines.join('\n').trim() }],
|
|
1087
|
+
structuredContent: data,
|
|
1088
|
+
};
|
|
1089
|
+
});
|
|
982
1090
|
// Tool: List feedback
|
|
983
1091
|
server.registerTool('list_feedback', {
|
|
984
1092
|
title: 'List Feedback',
|
|
@@ -1490,6 +1598,7 @@ server.registerTool('create_changelog', {
|
|
|
1490
1598
|
title: z.string().describe('Changelog title (e.g., "v2.1.0" or "January 2025 Release")'),
|
|
1491
1599
|
content: z.string().optional().describe('Initial changelog content (markdown)'),
|
|
1492
1600
|
version: z.string().optional().describe('Version number'),
|
|
1601
|
+
summary: z.string().max(280).optional().describe('Short summary for social sharing and email subject (max 280 chars)'),
|
|
1493
1602
|
status: z.enum(['draft', 'published']).optional().describe('Status (default: draft)'),
|
|
1494
1603
|
}),
|
|
1495
1604
|
outputSchema: z.object({
|
|
@@ -1515,13 +1624,14 @@ server.registerTool('create_changelog', {
|
|
|
1515
1624
|
// Tool: Update changelog
|
|
1516
1625
|
server.registerTool('update_changelog', {
|
|
1517
1626
|
title: 'Update Changelog',
|
|
1518
|
-
description: 'Update a changelog entry. Can update title, content, version, or status.\n\n' +
|
|
1519
|
-
'**Publishing:**
|
|
1627
|
+
description: 'Update a changelog entry. Can update title, content, version, summary, or status.\n\n' +
|
|
1628
|
+
'**Publishing:** Use `publish_changelog` instead to publish with email and Twitter options.',
|
|
1520
1629
|
inputSchema: z.object({
|
|
1521
1630
|
changelogId: z.string().describe('Changelog ID'),
|
|
1522
1631
|
title: z.string().optional().describe('New title'),
|
|
1523
1632
|
content: z.string().optional().describe('New content (markdown)'),
|
|
1524
1633
|
version: z.string().optional().describe('Version number'),
|
|
1634
|
+
summary: z.string().max(280).optional().describe('Short summary for social sharing and email subject (max 280 chars)'),
|
|
1525
1635
|
status: z.enum(['draft', 'published']).optional().describe('Status'),
|
|
1526
1636
|
}),
|
|
1527
1637
|
outputSchema: z.object({
|
|
@@ -1589,6 +1699,80 @@ server.registerTool('add_to_changelog', {
|
|
|
1589
1699
|
structuredContent: result,
|
|
1590
1700
|
};
|
|
1591
1701
|
});
|
|
1702
|
+
// Tool: Publish changelog
|
|
1703
|
+
server.registerTool('publish_changelog', {
|
|
1704
|
+
title: 'Publish Changelog',
|
|
1705
|
+
description: 'Publish a changelog with optional email and Twitter notifications.\n\n' +
|
|
1706
|
+
'**Options:**\n' +
|
|
1707
|
+
'- `sendEmail`: Send to subscribers who opted in (default: true if subscribers exist)\n' +
|
|
1708
|
+
'- `postToTwitter`: Post to connected Twitter account (default: false)\n' +
|
|
1709
|
+
'- `summary`: Short text for email subject and tweet (max 280 chars)\n\n' +
|
|
1710
|
+
'**Example workflow:**\n' +
|
|
1711
|
+
'1. Review draft with `list_changelogs`\n' +
|
|
1712
|
+
'2. Update content with `update_changelog` if needed\n' +
|
|
1713
|
+
'3. Publish with `publish_changelog` and notification options',
|
|
1714
|
+
inputSchema: z.object({
|
|
1715
|
+
changelogId: z.string().describe('Changelog ID to publish'),
|
|
1716
|
+
title: z.string().optional().describe('Update title before publishing'),
|
|
1717
|
+
version: z.string().optional().describe('Set version before publishing'),
|
|
1718
|
+
summary: z.string().max(280).optional().describe('Summary for email subject and tweet (max 280 chars)'),
|
|
1719
|
+
sendEmail: z.boolean().optional().describe('Send email to subscribers (default: true if subscribers exist)'),
|
|
1720
|
+
postToTwitter: z.boolean().optional().describe('Post to Twitter if connected (default: false)'),
|
|
1721
|
+
}),
|
|
1722
|
+
outputSchema: z.object({
|
|
1723
|
+
id: z.string(),
|
|
1724
|
+
title: z.string(),
|
|
1725
|
+
publishedAt: z.string(),
|
|
1726
|
+
emailsSent: z.number().optional(),
|
|
1727
|
+
twitterPost: z.object({
|
|
1728
|
+
success: z.boolean(),
|
|
1729
|
+
postUrl: z.string().optional(),
|
|
1730
|
+
error: z.string().optional(),
|
|
1731
|
+
}).optional(),
|
|
1732
|
+
}),
|
|
1733
|
+
annotations: {
|
|
1734
|
+
readOnlyHint: false,
|
|
1735
|
+
destructiveHint: false,
|
|
1736
|
+
idempotentHint: false,
|
|
1737
|
+
openWorldHint: false,
|
|
1738
|
+
},
|
|
1739
|
+
}, async ({ changelogId, title, version, summary, sendEmail, postToTwitter }) => {
|
|
1740
|
+
const body = {};
|
|
1741
|
+
if (title !== undefined)
|
|
1742
|
+
body.title = title;
|
|
1743
|
+
if (version !== undefined)
|
|
1744
|
+
body.version = version;
|
|
1745
|
+
if (summary !== undefined)
|
|
1746
|
+
body.summary = summary;
|
|
1747
|
+
if (sendEmail !== undefined)
|
|
1748
|
+
body.sendEmail = sendEmail;
|
|
1749
|
+
if (postToTwitter !== undefined)
|
|
1750
|
+
body.postToTwitter = postToTwitter;
|
|
1751
|
+
const result = await api('POST', `/api/agent/changelogs/${changelogId}/publish`, body);
|
|
1752
|
+
// Build response message
|
|
1753
|
+
const parts = [`ā
Published "${result.title}"`];
|
|
1754
|
+
if (result.emailsSent && result.emailsSent > 0) {
|
|
1755
|
+
parts.push(`š§ ${result.emailsSent} email${result.emailsSent !== 1 ? 's' : ''} sent`);
|
|
1756
|
+
}
|
|
1757
|
+
if (result.twitterPost) {
|
|
1758
|
+
if (result.twitterPost.success && result.twitterPost.postUrl) {
|
|
1759
|
+
parts.push(`š¦ Posted to Twitter: ${result.twitterPost.postUrl}`);
|
|
1760
|
+
}
|
|
1761
|
+
else if (!result.twitterPost.success && result.twitterPost.error) {
|
|
1762
|
+
parts.push(`ā ļø Twitter failed: ${result.twitterPost.error}`);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
return {
|
|
1766
|
+
content: [{ type: 'text', text: parts.join('\n') }],
|
|
1767
|
+
structuredContent: {
|
|
1768
|
+
id: result.id,
|
|
1769
|
+
title: result.title,
|
|
1770
|
+
publishedAt: result.publishedAt,
|
|
1771
|
+
emailsSent: result.emailsSent,
|
|
1772
|
+
twitterPost: result.twitterPost,
|
|
1773
|
+
},
|
|
1774
|
+
};
|
|
1775
|
+
});
|
|
1592
1776
|
// Start
|
|
1593
1777
|
async function main() {
|
|
1594
1778
|
const transport = new StdioServerTransport();
|