@andespindola/brainlink 1.6.14 → 1.6.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/CHANGELOG.md +4 -0
- package/dist/application/get-image.js +63 -0
- package/dist/mcp/server.js +6 -1
- package/dist/mcp/tools/read-tools.js +26 -0
- package/dist/mcp/tools.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.15
|
|
4
|
+
|
|
5
|
+
- **Retrieve image bytes (`brainlink_get_image`).** A new MCP tool + `get-image` use-case reads an image previously stored by `brainlink_add_image` back out of the vault as base64 bytes + mime, decrypting when the vault is encrypted. Path is validated to stay inside `.brainlink/assets` (no traversal). This closes the cross-agent loop: one agent adds an image to the shared vault, another fetches and uses it.
|
|
6
|
+
|
|
3
7
|
## 1.6.14
|
|
4
8
|
|
|
5
9
|
- **Images in notes and context.** New `brainlink_add_image` MCP tool (and application use-case) stores an image inside the vault (content-addressed, encrypted at rest when the vault is), and can attach it to a note that embeds it. Markdown image references (`![[image.png]]` embeds and ``) are now extracted per document without leaking into wiki-link graph edges, and image descriptors (path, mime, alt) are surfaced alongside text in retrieved context — never the raw bytes. Additive: image-free notes are unchanged.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { imageMimeForRef } from '../domain/image-refs.js';
|
|
4
|
+
import { ensureVault } from '../infrastructure/file-system-vault.js';
|
|
5
|
+
import { decryptVaultContent, isEncryptedVaultPayload, readOrCreateVaultKey } from '../infrastructure/vault-crypto.js';
|
|
6
|
+
// The assets tree images are stored under; the only location get-image is
|
|
7
|
+
// allowed to read from. Mirrors the constant used by add-image.
|
|
8
|
+
const assetsRoot = '.brainlink/assets';
|
|
9
|
+
const isInside = (parent, child) => {
|
|
10
|
+
const rel = relative(parent, child);
|
|
11
|
+
return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
|
|
12
|
+
};
|
|
13
|
+
// Normalize the requested path to the logical (plaintext) asset path so the
|
|
14
|
+
// returned descriptor is stable regardless of whether the caller passed the
|
|
15
|
+
// `.enc` mirror or the plaintext path.
|
|
16
|
+
const toLogicalPath = (assetPath) => assetPath.toLowerCase().endsWith('.enc') ? assetPath.slice(0, -'.enc'.length) : assetPath;
|
|
17
|
+
// Read the raw bytes for a stored image, decrypting transparently when the
|
|
18
|
+
// bytes on disk are an encrypted-at-rest payload. Prefers the plaintext file
|
|
19
|
+
// and falls back to the `.enc` mirror, so it works whether the vault is
|
|
20
|
+
// encrypted or not.
|
|
21
|
+
const readAssetBytes = async (vaultRoot, logicalAbsolute) => {
|
|
22
|
+
const decryptIfNeeded = async (raw) => {
|
|
23
|
+
if (!isEncryptedVaultPayload(raw)) {
|
|
24
|
+
return { bytes: raw, encrypted: false };
|
|
25
|
+
}
|
|
26
|
+
// Only touch the key when the on-disk bytes are actually ciphertext, so a
|
|
27
|
+
// plaintext vault never mints a versioning key just to read an image.
|
|
28
|
+
const vaultKey = await readOrCreateVaultKey(vaultRoot);
|
|
29
|
+
return { bytes: decryptVaultContent(vaultKey, raw), encrypted: true };
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
return await decryptIfNeeded(await readFile(logicalAbsolute));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return decryptIfNeeded(await readFile(`${logicalAbsolute}.enc`));
|
|
40
|
+
};
|
|
41
|
+
// Retrieve a stored image's raw bytes (base64) and mime so a second agent can
|
|
42
|
+
// pull an image another agent added to the shared vault. The path is validated
|
|
43
|
+
// to live inside `.brainlink/assets` (no traversal), and encrypted-at-rest
|
|
44
|
+
// payloads are decrypted with the existing vault key.
|
|
45
|
+
export const getImage = async (input) => {
|
|
46
|
+
const vaultRoot = await ensureVault(input.vaultPath);
|
|
47
|
+
const logicalPath = toLogicalPath(input.path);
|
|
48
|
+
const logicalAbsolute = resolve(vaultRoot, logicalPath);
|
|
49
|
+
const assetsAbsolute = join(vaultRoot, assetsRoot);
|
|
50
|
+
if (!isInside(assetsAbsolute, logicalAbsolute)) {
|
|
51
|
+
throw new Error(`Refusing to read image outside the vault assets tree: ${input.path}`);
|
|
52
|
+
}
|
|
53
|
+
const { bytes, encrypted } = await readAssetBytes(vaultRoot, logicalAbsolute);
|
|
54
|
+
const mime = imageMimeForRef(logicalPath) ?? 'application/octet-stream';
|
|
55
|
+
const relativePath = relative(vaultRoot, logicalAbsolute).split('\\').join('/');
|
|
56
|
+
return {
|
|
57
|
+
path: relativePath,
|
|
58
|
+
mime,
|
|
59
|
+
bytes: bytes.toString('base64'),
|
|
60
|
+
encrypted,
|
|
61
|
+
...(input.alt?.trim() ? { alt: input.alt.trim() } : {})
|
|
62
|
+
};
|
|
63
|
+
};
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { addNoteInputSchema, addFileInputSchema, addFileTool, addImageInputSchema, addImageTool, addNoteTool, addNotesInputSchema, addNotesTool, deleteNoteInputSchema, deleteNoteTool, deleteNotesInputSchema, deleteNotesTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, canonicalizeContextLinksInputSchema, canonicalizeContextLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, doctorActionsInputSchema, doctorActionsTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, explainInputSchema, explainTool, indexInputSchema, indexTool, inboxAddInputSchema, inboxAddTool, inboxListInputSchema, inboxListTool, inboxProcessInputSchema, inboxProcessTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, projectInitInputSchema, projectInitTool, recommendationsInputSchema, recommendationsTool, rememberInputSchema, rememberTool, repairLinksInputSchema, repairLinksTool, searchInputSchema, searchTool, similarNotesInputSchema, similarNotesTool, sessionCloseInputSchema, sessionCloseTool, statsInputSchema, statsTool, suggestLinksInputSchema, suggestLinksTool, syncInputSchema, syncTool, validateInputSchema, validateTool, vaultBundleListInputSchema, vaultBundleListTool, vaultCommitInputSchema, vaultCommitTool, vaultExportBundleInputSchema, vaultExportBundleTool, vaultExportInputSchema, vaultExportTool, vaultHistoryInputSchema, vaultHistoryTool, vaultImportBundleInputSchema, vaultImportBundleTool, vaultImportInputSchema, vaultImportTool, vaultInitInputSchema, vaultInitTool, vaultMergeAgentsInputSchema, vaultMergeAgentsTool, vaultProvisionInputSchema, vaultProvisionTool, vaultPullInputSchema, vaultPullTool, vaultRestoreInputSchema, vaultRestoreTool, vaultStatusInputSchema, vaultStatusTool, versionInputSchema, versionTool } from './tools.js';
|
|
2
|
+
import { addNoteInputSchema, addFileInputSchema, addFileTool, addImageInputSchema, addImageTool, addNoteTool, addNotesInputSchema, addNotesTool, deleteNoteInputSchema, deleteNoteTool, deleteNotesInputSchema, deleteNotesTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, brokenLinksInputSchema, brokenLinksTool, bootstrapInputSchema, bootstrapTool, canonicalizeContextLinksInputSchema, canonicalizeContextLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, doctorActionsInputSchema, doctorActionsTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, getImageInputSchema, getImageTool, explainInputSchema, explainTool, indexInputSchema, indexTool, inboxAddInputSchema, inboxAddTool, inboxListInputSchema, inboxListTool, inboxProcessInputSchema, inboxProcessTool, orphansInputSchema, orphansTool, policyInputSchema, policyTool, projectInitInputSchema, projectInitTool, recommendationsInputSchema, recommendationsTool, rememberInputSchema, rememberTool, repairLinksInputSchema, repairLinksTool, searchInputSchema, searchTool, similarNotesInputSchema, similarNotesTool, sessionCloseInputSchema, sessionCloseTool, statsInputSchema, statsTool, suggestLinksInputSchema, suggestLinksTool, syncInputSchema, syncTool, validateInputSchema, validateTool, vaultBundleListInputSchema, vaultBundleListTool, vaultCommitInputSchema, vaultCommitTool, vaultExportBundleInputSchema, vaultExportBundleTool, vaultExportInputSchema, vaultExportTool, vaultHistoryInputSchema, vaultHistoryTool, vaultImportBundleInputSchema, vaultImportBundleTool, vaultImportInputSchema, vaultImportTool, vaultInitInputSchema, vaultInitTool, vaultMergeAgentsInputSchema, vaultMergeAgentsTool, vaultProvisionInputSchema, vaultProvisionTool, vaultPullInputSchema, vaultPullTool, vaultRestoreInputSchema, vaultRestoreTool, vaultStatusInputSchema, vaultStatusTool, versionInputSchema, versionTool } from './tools.js';
|
|
3
3
|
import { getRuntimeVersion } from './runtime.js';
|
|
4
4
|
import { brainlinkServerInstructions } from './server-instructions.js';
|
|
5
5
|
import { guardToolHandler } from './tool-guard.js';
|
|
@@ -131,6 +131,11 @@ export const createBrainlinkMcpServer = () => {
|
|
|
131
131
|
description: 'Store an image in the vault (encrypted at rest when vault encryption is on) and address it by a stable vault-relative path. Provide imageBase64 or filePath. Optionally pass noteTitle to create a note that embeds the image so retrieval can surface it in context. Alt text is included in the returned context descriptor.',
|
|
132
132
|
inputSchema: addImageInputSchema
|
|
133
133
|
}, addImageTool);
|
|
134
|
+
server.registerTool('brainlink_get_image', {
|
|
135
|
+
title: 'Get Brainlink Image',
|
|
136
|
+
description: 'Retrieve the raw bytes of an image stored in the vault by a stable vault-relative asset path (as returned by brainlink_add_image). Returns the descriptor plus base64-encoded bytes so a second agent can fetch and use an image another agent added to the shared vault. Decrypts transparently when the vault stores images encrypted at rest; the path must resolve inside .brainlink/assets.',
|
|
137
|
+
inputSchema: getImageInputSchema
|
|
138
|
+
}, getImageTool);
|
|
134
139
|
server.registerTool('brainlink_canonicalize_context_links', {
|
|
135
140
|
title: 'Canonicalize Brainlink Context Links',
|
|
136
141
|
description: 'Ensure notes have canonical Context Links to inferred context hubs. Supports dry-run and can create missing hub notes.',
|
|
@@ -3,6 +3,7 @@ import { getBrokenLinksReport, getOrphansReport, getStats, validateVault } from
|
|
|
3
3
|
import { buildContextPackage, readContextDataSignature, readVolatileSignature } from '../../application/build-context.js';
|
|
4
4
|
import { getGraph } from '../../application/get-graph.js';
|
|
5
5
|
import { getGraphContexts } from '../../application/get-graph-contexts.js';
|
|
6
|
+
import { getImage } from '../../application/get-image.js';
|
|
6
7
|
import { explainSearchResults, suggestBrokenLinkFixes, suggestContextLinks } from '../../application/memory-suggestions.js';
|
|
7
8
|
import { buildActionableDoctor } from '../../application/operational-workflows.js';
|
|
8
9
|
import { searchKnowledge } from '../../application/search-knowledge.js';
|
|
@@ -539,3 +540,28 @@ export const recommendationsTool = async (input) => {
|
|
|
539
540
|
recommendations
|
|
540
541
|
});
|
|
541
542
|
};
|
|
543
|
+
export const getImageInputSchema = {
|
|
544
|
+
...vaultInput,
|
|
545
|
+
...agentInput,
|
|
546
|
+
path: z
|
|
547
|
+
.string()
|
|
548
|
+
.min(1)
|
|
549
|
+
.describe('Vault-relative asset path to fetch, as returned by brainlink_add_image (e.g. ".brainlink/assets/shared/<sha>.png"). Must resolve inside the vault .brainlink/assets tree.'),
|
|
550
|
+
alt: z.string().min(1).optional().describe('Optional alt text / description to echo back in the descriptor.')
|
|
551
|
+
};
|
|
552
|
+
// Fetch the raw bytes of a stored image so another agent can retrieve and use an
|
|
553
|
+
// image a first agent added to the shared vault. Decrypts transparently when the
|
|
554
|
+
// bytes are stored encrypted-at-rest, and rejects paths outside .brainlink/assets.
|
|
555
|
+
export const getImageTool = async (input) => {
|
|
556
|
+
const context = await resolveExecutionContext(input);
|
|
557
|
+
const image = await getImage({
|
|
558
|
+
vaultPath: context.vault,
|
|
559
|
+
path: input.path,
|
|
560
|
+
alt: input.alt
|
|
561
|
+
});
|
|
562
|
+
return jsonResult({
|
|
563
|
+
vault: context.vault,
|
|
564
|
+
agent: context.agent,
|
|
565
|
+
image
|
|
566
|
+
});
|
|
567
|
+
};
|
package/dist/mcp/tools.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, doctorActionsInputSchema, doctorActionsTool, explainInputSchema, explainTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, orphansInputSchema, orphansTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, similarNotesInputSchema, similarNotesTool, statsInputSchema, statsTool, suggestLinksInputSchema, suggestLinksTool, validateInputSchema, validateTool, versionInputSchema, versionTool } from './tools/read-tools.js';
|
|
1
|
+
export { brokenLinksInputSchema, brokenLinksTool, contextInputSchema, contextPacksInputSchema, contextPacksTool, contextTool, doctorActionsInputSchema, doctorActionsTool, explainInputSchema, explainTool, graphContextsInputSchema, graphContextsTool, graphInputSchema, graphTool, getImageInputSchema, getImageTool, orphansInputSchema, orphansTool, recommendationsInputSchema, recommendationsTool, searchInputSchema, searchTool, similarNotesInputSchema, similarNotesTool, statsInputSchema, statsTool, suggestLinksInputSchema, suggestLinksTool, validateInputSchema, validateTool, versionInputSchema, versionTool } from './tools/read-tools.js';
|
|
2
2
|
export { addFileInputSchema, addFileTool, addImageInputSchema, addImageTool, addNoteInputSchema, addNoteTool, addNotesInputSchema, addNotesTool, deleteNoteInputSchema, deleteNoteTool, deleteNotesInputSchema, deleteNotesTool, inboxAddInputSchema, inboxAddTool, inboxListInputSchema, inboxListTool, inboxProcessInputSchema, inboxProcessTool, rememberInputSchema, rememberTool, volatileAddInputSchema, volatileAddTool, volatileClearInputSchema, volatileClearTool } from './tools/write-tools.js';
|
|
3
3
|
export { bootstrapInputSchema, bootstrapTool, canonicalizeContextLinksInputSchema, canonicalizeContextLinksTool, dedupeInputSchema, dedupeResolveInputSchema, dedupeResolveTool, dedupeTool, indexInputSchema, indexTool, policyInputSchema, policyTool, projectInitInputSchema, projectInitTool, repairLinksInputSchema, repairLinksTool, sessionCloseInputSchema, sessionCloseTool, syncInputSchema, syncTool } from './tools/maintenance-tools.js';
|
|
4
4
|
export { vaultBundleListInputSchema, vaultBundleListTool, vaultCommitInputSchema, vaultCommitTool, vaultExportBundleInputSchema, vaultExportBundleTool, vaultExportInputSchema, vaultExportTool, vaultHistoryInputSchema, vaultHistoryTool, vaultImportBundleInputSchema, vaultImportBundleTool, vaultImportInputSchema, vaultImportTool, vaultInitInputSchema, vaultInitTool, vaultMergeAgentsInputSchema, vaultMergeAgentsTool, vaultProvisionInputSchema, vaultProvisionTool, vaultPullInputSchema, vaultPullTool, vaultRestoreInputSchema, vaultRestoreTool, vaultStatusInputSchema, vaultStatusTool } from './tools/vault-tools.js';
|
package/package.json
CHANGED