agent-bridge-mcp 1.1.0 → 1.2.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 +44 -0
- package/package.json +1 -1
- package/server.mjs +169 -24
package/README.md
CHANGED
|
@@ -154,6 +154,50 @@ Pass env vars through your MCP config:
|
|
|
154
154
|
}
|
|
155
155
|
```
|
|
156
156
|
|
|
157
|
+
## Automatic Context Sharing (Stop Hook)
|
|
158
|
+
|
|
159
|
+
The `--ingest` flag lets you auto-capture every conversation exchange into the context directory — no manual `share_context` calls needed.
|
|
160
|
+
|
|
161
|
+
**How it works:** Claude Code fires a [Stop hook](https://docs.anthropic.com/en/docs/claude-code/hooks) after every agent response. The hook pipes `session_id` and `last_assistant_message` via stdin. `agent-bridge-mcp --ingest` reads this, pairs it with the user prompt (saved by a `UserPromptSubmit` hook), strips system tags, and appends the exchange to the agent's context file.
|
|
162
|
+
|
|
163
|
+
**Setup:**
|
|
164
|
+
|
|
165
|
+
1. Add a `UserPromptSubmit` hook to save user prompts (required for `--ingest` to capture both sides):
|
|
166
|
+
|
|
167
|
+
Add to `~/.claude/settings.json`:
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"hooks": {
|
|
171
|
+
"UserPromptSubmit": [
|
|
172
|
+
{
|
|
173
|
+
"hooks": [
|
|
174
|
+
{
|
|
175
|
+
"type": "command",
|
|
176
|
+
"command": "npx agent-bridge-mcp --capture-prompt",
|
|
177
|
+
"timeout": 5
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
"Stop": [
|
|
183
|
+
{
|
|
184
|
+
"hooks": [
|
|
185
|
+
{
|
|
186
|
+
"type": "command",
|
|
187
|
+
"command": "npx agent-bridge-mcp --ingest",
|
|
188
|
+
"timeout": 10
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Set `AGENT_BRIDGE_NAME` in your project's `.mcp.json` so the ingest knows which agent name to write as.
|
|
198
|
+
|
|
199
|
+
Context is stored as the last 20 exchanges per agent, accessible via `get_context`.
|
|
200
|
+
|
|
157
201
|
## Requirements
|
|
158
202
|
|
|
159
203
|
- Node.js >= 18
|
package/package.json
CHANGED
package/server.mjs
CHANGED
|
@@ -586,7 +586,7 @@ async function handleToolCall(name, args) {
|
|
|
586
586
|
// ============================================================
|
|
587
587
|
|
|
588
588
|
const server = new Server(
|
|
589
|
-
{ name: 'agent-bridge', version: '1.
|
|
589
|
+
{ name: 'agent-bridge', version: '1.2.0' },
|
|
590
590
|
{ capabilities: { tools: {} } }
|
|
591
591
|
);
|
|
592
592
|
|
|
@@ -621,34 +621,179 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
621
621
|
});
|
|
622
622
|
|
|
623
623
|
// ============================================================
|
|
624
|
-
//
|
|
624
|
+
// CLI: --capture-prompt (UserPromptSubmit hook mode)
|
|
625
625
|
// ============================================================
|
|
626
|
+
// Saves user prompt to temp file for --ingest to pair with.
|
|
627
|
+
// Usage: node server.mjs --capture-prompt
|
|
628
|
+
|
|
629
|
+
async function runCapturePrompt() {
|
|
630
|
+
const chunks = [];
|
|
631
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
632
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
633
|
+
|
|
634
|
+
let data;
|
|
635
|
+
try { data = JSON.parse(raw); } catch { process.exit(0); }
|
|
636
|
+
|
|
637
|
+
const sessionId = data.session_id || '';
|
|
638
|
+
const prompt = data.prompt || '';
|
|
639
|
+
if (!sessionId || !prompt) process.exit(0);
|
|
626
640
|
|
|
627
|
-
|
|
641
|
+
// Strip system tags
|
|
642
|
+
const tagPatterns = [
|
|
643
|
+
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
644
|
+
/<ide_opened_file>[\s\S]*?<\/ide_opened_file>/g,
|
|
645
|
+
/<ide_selection>[\s\S]*?<\/ide_selection>/g,
|
|
646
|
+
/<task-notification>[\s\S]*?<\/task-notification>/g,
|
|
647
|
+
];
|
|
648
|
+
let clean = prompt;
|
|
649
|
+
for (const pat of tagPatterns) clean = clean.replace(pat, '');
|
|
650
|
+
clean = clean.replace(/\n\s*\n/g, '\n').trim();
|
|
651
|
+
|
|
652
|
+
if (clean.length < 5) process.exit(0);
|
|
653
|
+
|
|
654
|
+
const tmp = tmpdir();
|
|
655
|
+
writeFileSync(resolve(tmp, `agent-bridge-prompt-${sessionId}`), clean, 'utf8');
|
|
656
|
+
process.exit(0);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ============================================================
|
|
660
|
+
// CLI: --ingest (Stop hook mode)
|
|
661
|
+
// ============================================================
|
|
662
|
+
// Called by Claude Code Stop hook. Reads stdin JSON with
|
|
663
|
+
// session_id + last_assistant_message, writes to context dir.
|
|
664
|
+
// Usage: node server.mjs --ingest
|
|
665
|
+
|
|
666
|
+
async function runIngest() {
|
|
628
667
|
ensureDirs();
|
|
629
|
-
cleanOldMessages();
|
|
630
|
-
writeAgentFile();
|
|
631
|
-
startHeartbeat();
|
|
632
668
|
|
|
633
|
-
//
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
669
|
+
// Read stdin
|
|
670
|
+
const chunks = [];
|
|
671
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
672
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
673
|
+
|
|
674
|
+
let data;
|
|
675
|
+
try {
|
|
676
|
+
data = JSON.parse(raw);
|
|
677
|
+
} catch {
|
|
678
|
+
process.exit(0);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const sessionId = data.session_id || '';
|
|
682
|
+
const assistantMsg = data.last_assistant_message || '';
|
|
683
|
+
if (!sessionId || !assistantMsg) process.exit(0);
|
|
684
|
+
|
|
685
|
+
// Read user prompt saved by --capture-prompt hook
|
|
686
|
+
const tmp = tmpdir();
|
|
687
|
+
const promptPath = resolve(tmp, `agent-bridge-prompt-${sessionId}`);
|
|
688
|
+
let userMsg = '';
|
|
689
|
+
try {
|
|
690
|
+
userMsg = readFileSync(promptPath, 'utf8');
|
|
691
|
+
} catch {}
|
|
692
|
+
|
|
693
|
+
// Strip system tags
|
|
694
|
+
const tagPatterns = [
|
|
695
|
+
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
696
|
+
/<task-notification>[\s\S]*?<\/task-notification>/g,
|
|
697
|
+
/<ide_opened_file>[\s\S]*?<\/ide_opened_file>/g,
|
|
698
|
+
/<ide_selection>[\s\S]*?<\/ide_selection>/g,
|
|
699
|
+
];
|
|
700
|
+
let cleanAssistant = assistantMsg;
|
|
701
|
+
for (const pat of tagPatterns) cleanAssistant = cleanAssistant.replace(pat, '');
|
|
702
|
+
cleanAssistant = cleanAssistant.replace(/\n\s*\n/g, '\n').trim();
|
|
703
|
+
|
|
704
|
+
if (!cleanAssistant || cleanAssistant.length < 50) process.exit(0);
|
|
705
|
+
|
|
706
|
+
// Determine agent name from env or from active agent files
|
|
707
|
+
const name = process.env.AGENT_BRIDGE_NAME || findAgentNameForSession(sessionId);
|
|
708
|
+
if (!name) process.exit(0);
|
|
709
|
+
|
|
710
|
+
// Read existing context and append new exchange
|
|
711
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
|
|
712
|
+
const ctxPath = resolve(CONTEXT_DIR, `${safeName}.json`);
|
|
713
|
+
let existing = { exchanges: [] };
|
|
714
|
+
try {
|
|
715
|
+
const prev = JSON.parse(readFileSync(ctxPath, 'utf8'));
|
|
716
|
+
if (prev.exchanges) existing = prev;
|
|
717
|
+
else if (prev.content) existing = { exchanges: [{ role: 'context', content: prev.content }] };
|
|
718
|
+
} catch {}
|
|
719
|
+
|
|
720
|
+
// Keep last 20 exchanges to avoid unbounded growth
|
|
721
|
+
existing.exchanges.push({
|
|
722
|
+
timestamp: new Date().toISOString(),
|
|
723
|
+
user: userMsg.slice(0, 2000),
|
|
724
|
+
assistant: cleanAssistant.slice(0, 5000)
|
|
725
|
+
});
|
|
726
|
+
if (existing.exchanges.length > 20) {
|
|
727
|
+
existing.exchanges = existing.exchanges.slice(-20);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const ctxData = JSON.stringify({
|
|
731
|
+
agentName: name,
|
|
732
|
+
project: process.cwd().split(/[\\/]/).pop(),
|
|
733
|
+
updatedAt: new Date().toISOString(),
|
|
734
|
+
sessionId,
|
|
735
|
+
exchanges: existing.exchanges
|
|
736
|
+
}, null, 2);
|
|
641
737
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
738
|
+
const tmpPath = ctxPath + '.tmp';
|
|
739
|
+
try {
|
|
740
|
+
writeFileSync(tmpPath, ctxData);
|
|
741
|
+
renameSync(tmpPath, ctxPath);
|
|
742
|
+
} catch {
|
|
743
|
+
try { writeFileSync(ctxPath, ctxData); } catch {}
|
|
744
|
+
}
|
|
645
745
|
|
|
646
|
-
|
|
647
|
-
console.error(`[AgentBridge] Agent ID: ${agentId}`);
|
|
648
|
-
console.error(`[AgentBridge] Data dir: ${BRIDGE_DIR}`);
|
|
746
|
+
process.exit(0);
|
|
649
747
|
}
|
|
650
748
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
749
|
+
function findAgentNameForSession(sessionId) {
|
|
750
|
+
// Look through agent files to find one from this process's cwd
|
|
751
|
+
try {
|
|
752
|
+
const files = readdirSync(AGENTS_DIR).filter(f => f.endsWith('.json'));
|
|
753
|
+
const cwd = process.cwd();
|
|
754
|
+
for (const file of files) {
|
|
755
|
+
const agent = readAgentFile(resolve(AGENTS_DIR, file));
|
|
756
|
+
if (agent && agent.name && agent.cwd === cwd) return agent.name;
|
|
757
|
+
}
|
|
758
|
+
} catch {}
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// ============================================================
|
|
763
|
+
// Main
|
|
764
|
+
// ============================================================
|
|
765
|
+
|
|
766
|
+
if (process.argv.includes('--capture-prompt')) {
|
|
767
|
+
runCapturePrompt().catch(() => process.exit(0));
|
|
768
|
+
} else if (process.argv.includes('--ingest')) {
|
|
769
|
+
runIngest().catch(() => process.exit(0));
|
|
770
|
+
} else {
|
|
771
|
+
async function main() {
|
|
772
|
+
ensureDirs();
|
|
773
|
+
cleanOldMessages();
|
|
774
|
+
writeAgentFile();
|
|
775
|
+
startHeartbeat();
|
|
776
|
+
|
|
777
|
+
// Cleanup on exit
|
|
778
|
+
const cleanup = () => {
|
|
779
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
780
|
+
removeAgentFile();
|
|
781
|
+
};
|
|
782
|
+
process.on('exit', cleanup);
|
|
783
|
+
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
784
|
+
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
785
|
+
|
|
786
|
+
// Connect stdio transport
|
|
787
|
+
const transport = new StdioServerTransport();
|
|
788
|
+
await server.connect(transport);
|
|
789
|
+
|
|
790
|
+
console.error(`[AgentBridge] Server running`);
|
|
791
|
+
console.error(`[AgentBridge] Agent ID: ${agentId}`);
|
|
792
|
+
console.error(`[AgentBridge] Data dir: ${BRIDGE_DIR}`);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
main().catch(err => {
|
|
796
|
+
console.error(`[AgentBridge] Fatal: ${err.message}`);
|
|
797
|
+
process.exit(1);
|
|
798
|
+
});
|
|
799
|
+
}
|