bear-notes-mcp-xq7k 2.0.1
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/LICENSE.md +21 -0
- package/README.md +109 -0
- package/README.npm.md +109 -0
- package/dist/bear-urls.js +87 -0
- package/dist/config.js +11 -0
- package/dist/database.js +40 -0
- package/dist/main.js +422 -0
- package/dist/notes.js +224 -0
- package/dist/tags.js +170 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +184 -0
- package/package.json +78 -0
package/dist/tags.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { convertCoreDataTimestamp, logAndThrow, logger } from './utils.js';
|
|
2
|
+
import { openBearDatabase } from './database.js';
|
|
3
|
+
/**
|
|
4
|
+
* Decodes and normalizes Bear tag names.
|
|
5
|
+
* - Replaces '+' with spaces (Bear's URL encoding)
|
|
6
|
+
* - Converts to lowercase (matches Bear UI behavior)
|
|
7
|
+
* - Trims whitespace
|
|
8
|
+
*/
|
|
9
|
+
function decodeTagName(encodedName) {
|
|
10
|
+
return encodedName.replace(/\+/g, ' ').trim().toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extracts the display name (leaf) from a full tag path.
|
|
14
|
+
* For "career/content/blog" returns "blog", for "career" returns "career".
|
|
15
|
+
*/
|
|
16
|
+
function getTagDisplayName(fullPath) {
|
|
17
|
+
const parts = fullPath.split('/');
|
|
18
|
+
return parts[parts.length - 1];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Builds a hierarchical tree from a flat list of tags.
|
|
22
|
+
* Tags with paths like "career/content" become children of "career".
|
|
23
|
+
* Tags with 0 notes are excluded (matches Bear UI behavior).
|
|
24
|
+
*/
|
|
25
|
+
function buildTagHierarchy(flatTags) {
|
|
26
|
+
// Filter out tags with no notes (hidden in Bear UI)
|
|
27
|
+
const activeTags = flatTags.filter((t) => t.noteCount > 0);
|
|
28
|
+
const tagMap = new Map();
|
|
29
|
+
// Two-pass approach: first create nodes, then link parent-child relationships
|
|
30
|
+
for (const tag of activeTags) {
|
|
31
|
+
tagMap.set(tag.name, {
|
|
32
|
+
name: tag.name,
|
|
33
|
+
displayName: tag.displayName,
|
|
34
|
+
noteCount: tag.noteCount,
|
|
35
|
+
children: [],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const roots = [];
|
|
39
|
+
// Build parent-child relationships
|
|
40
|
+
for (const tag of activeTags) {
|
|
41
|
+
const tagNode = tagMap.get(tag.name);
|
|
42
|
+
if (tag.isRoot) {
|
|
43
|
+
roots.push(tagNode);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Subtags use path notation (e.g., "career/content"), so extract parent path
|
|
47
|
+
const lastSlash = tag.name.lastIndexOf('/');
|
|
48
|
+
if (lastSlash > 0) {
|
|
49
|
+
const parentName = tag.name.substring(0, lastSlash);
|
|
50
|
+
const parent = tagMap.get(parentName);
|
|
51
|
+
if (parent) {
|
|
52
|
+
parent.children.push(tagNode);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Orphan subtag - parent has 0 notes or doesn't exist, treat as root
|
|
56
|
+
roots.push(tagNode);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Sort children alphabetically at each level
|
|
62
|
+
const sortChildren = (tags) => {
|
|
63
|
+
tags.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
64
|
+
for (const tag of tags) {
|
|
65
|
+
sortChildren(tag.children);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
sortChildren(roots);
|
|
69
|
+
return roots;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Retrieves all tags from Bear database as a hierarchical tree.
|
|
73
|
+
* Each tag includes note count and nested children.
|
|
74
|
+
*
|
|
75
|
+
* @returns Object with tags array (tree structure) and total count
|
|
76
|
+
*/
|
|
77
|
+
export function listTags() {
|
|
78
|
+
logger.info('listTags called');
|
|
79
|
+
const db = openBearDatabase();
|
|
80
|
+
try {
|
|
81
|
+
const query = `
|
|
82
|
+
SELECT t.ZTITLE as name,
|
|
83
|
+
t.ZISROOT as isRoot,
|
|
84
|
+
COUNT(nt.Z_5NOTES) as noteCount
|
|
85
|
+
FROM ZSFNOTETAG t
|
|
86
|
+
LEFT JOIN Z_5TAGS nt ON nt.Z_13TAGS = t.Z_PK
|
|
87
|
+
GROUP BY t.Z_PK
|
|
88
|
+
ORDER BY t.ZTITLE
|
|
89
|
+
`;
|
|
90
|
+
const stmt = db.prepare(query);
|
|
91
|
+
const rows = stmt.all();
|
|
92
|
+
if (!rows || rows.length === 0) {
|
|
93
|
+
logger.info('No tags found in database');
|
|
94
|
+
return { tags: [], totalCount: 0 };
|
|
95
|
+
}
|
|
96
|
+
// Transform rows: decode names and extract display names
|
|
97
|
+
const flatTags = rows.map((row) => {
|
|
98
|
+
const decodedName = decodeTagName(row.name);
|
|
99
|
+
return {
|
|
100
|
+
name: decodedName,
|
|
101
|
+
displayName: getTagDisplayName(decodedName),
|
|
102
|
+
noteCount: row.noteCount,
|
|
103
|
+
isRoot: row.isRoot === 1,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
const hierarchy = buildTagHierarchy(flatTags);
|
|
107
|
+
logger.info(`Retrieved ${rows.length} tags, ${hierarchy.length} root tags`);
|
|
108
|
+
return { tags: hierarchy, totalCount: rows.length };
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
logAndThrow(`Database error: Failed to retrieve tags: ${error instanceof Error ? error.message : String(error)}`);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
try {
|
|
115
|
+
db.close();
|
|
116
|
+
logger.debug('Database connection closed');
|
|
117
|
+
}
|
|
118
|
+
catch (closeError) {
|
|
119
|
+
logger.error(`Failed to close database connection: ${closeError}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { tags: [], totalCount: 0 };
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Finds notes that have no tags assigned.
|
|
126
|
+
*
|
|
127
|
+
* @param limit - Maximum number of results (default: 50)
|
|
128
|
+
* @returns Array of untagged notes
|
|
129
|
+
*/
|
|
130
|
+
export function findUntaggedNotes(limit = 50) {
|
|
131
|
+
logger.info(`findUntaggedNotes called with limit: ${limit}`);
|
|
132
|
+
const db = openBearDatabase();
|
|
133
|
+
try {
|
|
134
|
+
const query = `
|
|
135
|
+
SELECT ZTITLE as title,
|
|
136
|
+
ZUNIQUEIDENTIFIER as identifier,
|
|
137
|
+
ZCREATIONDATE as creationDate,
|
|
138
|
+
ZMODIFICATIONDATE as modificationDate
|
|
139
|
+
FROM ZSFNOTE
|
|
140
|
+
WHERE ZARCHIVED = 0 AND ZTRASHED = 0 AND ZENCRYPTED = 0
|
|
141
|
+
AND Z_PK NOT IN (SELECT Z_5NOTES FROM Z_5TAGS)
|
|
142
|
+
ORDER BY ZMODIFICATIONDATE DESC
|
|
143
|
+
LIMIT ?
|
|
144
|
+
`;
|
|
145
|
+
const stmt = db.prepare(query);
|
|
146
|
+
const rows = stmt.all(limit);
|
|
147
|
+
const notes = rows.map((row) => ({
|
|
148
|
+
title: row.title || 'Untitled',
|
|
149
|
+
identifier: row.identifier,
|
|
150
|
+
creation_date: convertCoreDataTimestamp(row.creationDate),
|
|
151
|
+
modification_date: convertCoreDataTimestamp(row.modificationDate),
|
|
152
|
+
pin: 'no',
|
|
153
|
+
}));
|
|
154
|
+
logger.info(`Found ${notes.length} untagged notes`);
|
|
155
|
+
return notes;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
logAndThrow(`Database error: Failed to find untagged notes: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
try {
|
|
162
|
+
db.close();
|
|
163
|
+
logger.debug('Database connection closed');
|
|
164
|
+
}
|
|
165
|
+
catch (closeError) {
|
|
166
|
+
logger.error(`Failed to close database connection: ${closeError}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return [];
|
|
170
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import createDebug from 'debug';
|
|
2
|
+
import { CORE_DATA_EPOCH_OFFSET, ERROR_MESSAGES } from './config.js';
|
|
3
|
+
import { getNoteContent } from './notes.js';
|
|
4
|
+
import { buildBearUrl, executeBearXCallbackApi } from './bear-urls.js';
|
|
5
|
+
export const logger = {
|
|
6
|
+
debug: createDebug('bear-notes-mcp:debug'),
|
|
7
|
+
info: createDebug('bear-notes-mcp:info'),
|
|
8
|
+
error: createDebug('bear-notes-mcp:error'),
|
|
9
|
+
};
|
|
10
|
+
// Convert UI_DEBUG_TOGGLE boolean set from UI to DEBUG string for debug package
|
|
11
|
+
// MCPB has no way to make this in one step with manifest.json
|
|
12
|
+
if (process.env.UI_DEBUG_TOGGLE === 'true') {
|
|
13
|
+
process.env.DEBUG = 'bear-notes-mcp:*';
|
|
14
|
+
logger.debug.enabled = true;
|
|
15
|
+
}
|
|
16
|
+
// Always enable error and info logs
|
|
17
|
+
logger.error.enabled = true;
|
|
18
|
+
logger.info.enabled = true;
|
|
19
|
+
/**
|
|
20
|
+
* Logs an error message and throws an Error to halt execution.
|
|
21
|
+
* Centralizes error handling to ensure consistent logging before failures.
|
|
22
|
+
*
|
|
23
|
+
* @param message - The error message to log and throw
|
|
24
|
+
* @throws Always throws Error with the provided message
|
|
25
|
+
*/
|
|
26
|
+
export function logAndThrow(message) {
|
|
27
|
+
logger.error(message);
|
|
28
|
+
throw new Error(message);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Cleans base64 string by removing whitespace/newlines added by base64 command.
|
|
32
|
+
* URLSearchParams in buildBearUrl will handle URL encoding of special characters.
|
|
33
|
+
*
|
|
34
|
+
* @param base64String - Raw base64 string (may contain whitespace/newlines)
|
|
35
|
+
* @returns Cleaned base64 string without whitespace
|
|
36
|
+
*/
|
|
37
|
+
export function cleanBase64(base64String) {
|
|
38
|
+
// Remove all whitespace/newlines from base64 (base64 command adds line breaks)
|
|
39
|
+
return base64String.trim().replace(/\s+/g, '');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Converts Bear's Core Data timestamp to ISO string format.
|
|
43
|
+
* Bear stores timestamps in seconds since Core Data epoch (2001-01-01).
|
|
44
|
+
*
|
|
45
|
+
* @param coreDataTimestamp - Timestamp in seconds since Core Data epoch
|
|
46
|
+
* @returns ISO string representation of the timestamp
|
|
47
|
+
*/
|
|
48
|
+
export function convertCoreDataTimestamp(coreDataTimestamp) {
|
|
49
|
+
const unixTimestamp = coreDataTimestamp + CORE_DATA_EPOCH_OFFSET;
|
|
50
|
+
return new Date(unixTimestamp * 1000).toISOString();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Converts a JavaScript Date object to Bear's Core Data timestamp format.
|
|
54
|
+
* Core Data timestamps are in seconds since 2001-01-01 00:00:00 UTC.
|
|
55
|
+
*
|
|
56
|
+
* @param date - JavaScript Date object
|
|
57
|
+
* @returns Core Data timestamp in seconds
|
|
58
|
+
*/
|
|
59
|
+
export function convertDateToCoreDataTimestamp(date) {
|
|
60
|
+
const unixTimestamp = Math.floor(date.getTime() / 1000);
|
|
61
|
+
return unixTimestamp - CORE_DATA_EPOCH_OFFSET;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parses a date string and returns a JavaScript Date object.
|
|
65
|
+
* Supports relative dates ("today", "yesterday", "last week", "last month") and ISO date strings.
|
|
66
|
+
*
|
|
67
|
+
* @param dateString - Date string to parse (e.g., "today", "2024-01-15", "last week")
|
|
68
|
+
* @returns Parsed Date object
|
|
69
|
+
* @throws Error if the date string is invalid
|
|
70
|
+
*/
|
|
71
|
+
export function parseDateString(dateString) {
|
|
72
|
+
const lowerDateString = dateString.trim().toLowerCase();
|
|
73
|
+
const now = new Date();
|
|
74
|
+
// Handle relative dates to provide user-friendly natural language date input
|
|
75
|
+
switch (lowerDateString) {
|
|
76
|
+
case 'today': {
|
|
77
|
+
const today = new Date(now);
|
|
78
|
+
today.setHours(0, 0, 0, 0);
|
|
79
|
+
return today;
|
|
80
|
+
}
|
|
81
|
+
case 'yesterday': {
|
|
82
|
+
const yesterday = new Date(now);
|
|
83
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
84
|
+
yesterday.setHours(0, 0, 0, 0);
|
|
85
|
+
return yesterday;
|
|
86
|
+
}
|
|
87
|
+
case 'last week':
|
|
88
|
+
case 'week ago': {
|
|
89
|
+
const lastWeek = new Date(now);
|
|
90
|
+
lastWeek.setDate(lastWeek.getDate() - 7);
|
|
91
|
+
lastWeek.setHours(0, 0, 0, 0);
|
|
92
|
+
return lastWeek;
|
|
93
|
+
}
|
|
94
|
+
case 'last month':
|
|
95
|
+
case 'month ago':
|
|
96
|
+
case 'start of last month': {
|
|
97
|
+
// Calculate the first day of last month; month arithmetic handles year transitions correctly via JavaScript Date constructor
|
|
98
|
+
const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
|
99
|
+
lastMonth.setHours(0, 0, 0, 0);
|
|
100
|
+
return lastMonth;
|
|
101
|
+
}
|
|
102
|
+
case 'end of last month': {
|
|
103
|
+
// Calculate the last day of last month; day 0 of current month equals last day of previous month
|
|
104
|
+
const endOfLastMonth = new Date(now.getFullYear(), now.getMonth(), 0);
|
|
105
|
+
endOfLastMonth.setHours(23, 59, 59, 999);
|
|
106
|
+
return endOfLastMonth;
|
|
107
|
+
}
|
|
108
|
+
default: {
|
|
109
|
+
// Try parsing as ISO date or other standard formats as fallback for user-provided explicit dates
|
|
110
|
+
const parsed = new Date(dateString);
|
|
111
|
+
if (isNaN(parsed.getTime())) {
|
|
112
|
+
logAndThrow(`Invalid date format: "${dateString}". Use ISO format (YYYY-MM-DD) or relative dates (today, yesterday, last week, last month, start of last month, end of last month).`);
|
|
113
|
+
}
|
|
114
|
+
return parsed;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Creates a standardized MCP tool response with consistent formatting.
|
|
120
|
+
* Centralizes response structure to follow DRY principles.
|
|
121
|
+
*
|
|
122
|
+
* @param text - The response text content
|
|
123
|
+
* @returns Formatted CallToolResult for MCP tools
|
|
124
|
+
*/
|
|
125
|
+
export function createToolResponse(text) {
|
|
126
|
+
return {
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: 'text',
|
|
130
|
+
text,
|
|
131
|
+
annotations: { audience: ['user', 'assistant'] },
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Shared handler for adding text to Bear notes (append or prepend).
|
|
138
|
+
* Consolidates common validation, execution, and response logic.
|
|
139
|
+
*
|
|
140
|
+
* @param mode - Whether to append or prepend text
|
|
141
|
+
* @param params - Note ID, text content, and optional header
|
|
142
|
+
* @returns Formatted response indicating success or failure
|
|
143
|
+
*/
|
|
144
|
+
export async function handleAddText(mode, { id, text, header }) {
|
|
145
|
+
const action = mode === 'append' ? 'appended' : 'prepended';
|
|
146
|
+
logger.info(`bear-add-text-${mode} called with id: ${id}, text length: ${text.length}, header: ${header || 'none'}`);
|
|
147
|
+
if (!id || !id.trim()) {
|
|
148
|
+
throw new Error(ERROR_MESSAGES.MISSING_NOTE_ID);
|
|
149
|
+
}
|
|
150
|
+
if (!text || !text.trim()) {
|
|
151
|
+
throw new Error(ERROR_MESSAGES.MISSING_TEXT_PARAM);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
const existingNote = getNoteContent(id.trim());
|
|
155
|
+
if (!existingNote) {
|
|
156
|
+
return createToolResponse(`Note with ID '${id}' not found. The note may have been deleted, archived, or the ID may be incorrect.
|
|
157
|
+
|
|
158
|
+
Use bear-search-notes to find the correct note identifier.`);
|
|
159
|
+
}
|
|
160
|
+
// Strip markdown header syntax from header parameter for Bear API
|
|
161
|
+
const cleanHeader = header?.trim().replace(/^#+\s*/, '');
|
|
162
|
+
const url = buildBearUrl('add-text', {
|
|
163
|
+
id: id.trim(),
|
|
164
|
+
text: text.trim(),
|
|
165
|
+
header: cleanHeader,
|
|
166
|
+
mode,
|
|
167
|
+
});
|
|
168
|
+
logger.debug(`Executing Bear URL: ${url}`);
|
|
169
|
+
await executeBearXCallbackApi(url);
|
|
170
|
+
const responseLines = [`Text ${action} to note "${existingNote.title}" successfully!`, ''];
|
|
171
|
+
responseLines.push(`Text: ${text.trim().length} characters`);
|
|
172
|
+
if (header?.trim()) {
|
|
173
|
+
responseLines.push(`Section: ${header.trim()}`);
|
|
174
|
+
}
|
|
175
|
+
responseLines.push(`Note ID: ${id.trim()}`);
|
|
176
|
+
return createToolResponse(`${responseLines.join('\n')}
|
|
177
|
+
|
|
178
|
+
The text has been added to your Bear note.`);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
logger.error(`bear-add-text-${mode} failed: ${error}`);
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bear-notes-mcp-xq7k",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Bear Notes MCP server with TypeScript and native SQLite",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/main.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -p tsconfig.build.json",
|
|
9
|
+
"dev": "tsx src/main.ts --debug",
|
|
10
|
+
"start": "node --experimental-sqlite dist/main.js",
|
|
11
|
+
"lint": "eslint src --fix",
|
|
12
|
+
"lint:check": "eslint src",
|
|
13
|
+
"format": "prettier --write src",
|
|
14
|
+
"format:check": "prettier --check src",
|
|
15
|
+
"check": "npm run lint:check && npm run format:check",
|
|
16
|
+
"fix": "npm run lint && npm run format",
|
|
17
|
+
"prepublishOnly": "npm run build && cp README.npm.md README.md",
|
|
18
|
+
"postpublish": "git checkout README.md"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
22
|
+
"debug": "^4.4.3",
|
|
23
|
+
"zod": "^3.25.76",
|
|
24
|
+
"zod-to-json-schema": "^3.24.6"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@anthropic-ai/mcpb": "^2.1.2",
|
|
28
|
+
"@types/debug": "^4.1.12",
|
|
29
|
+
"@types/node": "^24.10.4",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^8.46.3",
|
|
31
|
+
"@typescript-eslint/parser": "^8.46.3",
|
|
32
|
+
"eslint": "^9.39.2",
|
|
33
|
+
"eslint-plugin-import": "^2.32.0",
|
|
34
|
+
"prettier": "^3.7.4",
|
|
35
|
+
"tsx": "^4.21.0",
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.5.0"
|
|
40
|
+
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"body-parser": "2.2.1"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"bear app",
|
|
46
|
+
"bear notes",
|
|
47
|
+
"markdown notes",
|
|
48
|
+
"notes management",
|
|
49
|
+
"productivity",
|
|
50
|
+
"typescript",
|
|
51
|
+
"mcp"
|
|
52
|
+
],
|
|
53
|
+
"author": {
|
|
54
|
+
"name": "Serhii Vasylenko",
|
|
55
|
+
"email": "serhii@vasylenko.info",
|
|
56
|
+
"url": "https://devdosvid.blog"
|
|
57
|
+
},
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "https://github.com/vasylenko/claude-desktop-extension-bear-notes.git"
|
|
62
|
+
},
|
|
63
|
+
"bugs": {
|
|
64
|
+
"url": "https://github.com/vasylenko/claude-desktop-extension-bear-notes/issues"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://github.com/vasylenko/claude-desktop-extension-bear-notes#readme",
|
|
67
|
+
"bin": {
|
|
68
|
+
"bear-notes-mcp": "dist/main.js"
|
|
69
|
+
},
|
|
70
|
+
"files": [
|
|
71
|
+
"dist",
|
|
72
|
+
"README.md",
|
|
73
|
+
"LICENSE.md"
|
|
74
|
+
],
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"access": "public"
|
|
77
|
+
}
|
|
78
|
+
}
|