remnote-mcp-server 0.13.1 → 0.14.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/CHANGELOG.md +40 -0
- package/README.md +44 -23
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/remnote-cli/cli.d.ts +3 -0
- package/dist/remnote-cli/cli.js +37 -0
- package/dist/remnote-cli/cli.js.map +1 -0
- package/dist/remnote-cli/client/command-client.d.ts +3 -0
- package/dist/remnote-cli/client/command-client.js +9 -0
- package/dist/remnote-cli/client/command-client.js.map +1 -0
- package/dist/remnote-cli/client/mcp-server-client.d.ts +18 -0
- package/dist/remnote-cli/client/mcp-server-client.js +116 -0
- package/dist/remnote-cli/client/mcp-server-client.js.map +1 -0
- package/dist/remnote-cli/commands/arg-utils.d.ts +33 -0
- package/dist/remnote-cli/commands/arg-utils.js +89 -0
- package/dist/remnote-cli/commands/arg-utils.js.map +1 -0
- package/dist/remnote-cli/commands/content-input.d.ts +31 -0
- package/dist/remnote-cli/commands/content-input.js +81 -0
- package/dist/remnote-cli/commands/content-input.js.map +1 -0
- package/dist/remnote-cli/commands/create.d.ts +2 -0
- package/dist/remnote-cli/commands/create.js +61 -0
- package/dist/remnote-cli/commands/create.js.map +1 -0
- package/dist/remnote-cli/commands/journal.d.ts +2 -0
- package/dist/remnote-cli/commands/journal.js +52 -0
- package/dist/remnote-cli/commands/journal.js.map +1 -0
- package/dist/remnote-cli/commands/read.d.ts +2 -0
- package/dist/remnote-cli/commands/read.js +74 -0
- package/dist/remnote-cli/commands/read.js.map +1 -0
- package/dist/remnote-cli/commands/search.d.ts +3 -0
- package/dist/remnote-cli/commands/search.js +107 -0
- package/dist/remnote-cli/commands/search.js.map +1 -0
- package/dist/remnote-cli/commands/status.d.ts +2 -0
- package/dist/remnote-cli/commands/status.js +41 -0
- package/dist/remnote-cli/commands/status.js.map +1 -0
- package/dist/remnote-cli/commands/table.d.ts +2 -0
- package/dist/remnote-cli/commands/table.js +79 -0
- package/dist/remnote-cli/commands/table.js.map +1 -0
- package/dist/remnote-cli/commands/update.d.ts +2 -0
- package/dist/remnote-cli/commands/update.js +62 -0
- package/dist/remnote-cli/commands/update.js.map +1 -0
- package/dist/remnote-cli/config.d.ts +9 -0
- package/dist/remnote-cli/config.js +10 -0
- package/dist/remnote-cli/config.js.map +1 -0
- package/dist/remnote-cli/index.d.ts +2 -0
- package/dist/remnote-cli/index.js +4 -0
- package/dist/remnote-cli/index.js.map +1 -0
- package/dist/remnote-cli/output/formatter.d.ts +9 -0
- package/dist/remnote-cli/output/formatter.js +28 -0
- package/dist/remnote-cli/output/formatter.js.map +1 -0
- package/dist/remnote-cli/version-compat.d.ts +7 -0
- package/dist/remnote-cli/version-compat.js +28 -0
- package/dist/remnote-cli/version-compat.js.map +1 -0
- package/mcpb/remnote-local/.mcpbignore +5 -0
- package/mcpb/remnote-local/README.md +25 -0
- package/mcpb/remnote-local/manifest.json +87 -0
- package/mcpb/remnote-local/package.json +11 -0
- package/mcpb/remnote-local/remnote-local.mcpb +0 -0
- package/mcpb/remnote-local/server/index.js +300 -0
- package/package.json +16 -9
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { realpathSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath, URL, pathToFileURL } from 'node:url';
|
|
6
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
7
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
8
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_MCP_URL = 'http://127.0.0.1:3001/mcp';
|
|
13
|
+
export const SERVER_INFO = { name: 'remnote-mcp-stdio', version: '0.14.1' };
|
|
14
|
+
|
|
15
|
+
export const FALLBACK_TOOLS = [
|
|
16
|
+
{
|
|
17
|
+
name: 'remnote_create_note',
|
|
18
|
+
description:
|
|
19
|
+
'Create a new note in RemNote with optional content, parent, and tags. Supports hierarchical markdown in content and flashcard syntax.',
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
title: { type: 'string', description: 'The title of the note' },
|
|
24
|
+
content: { type: 'string', description: 'Content as plain text or hierarchical markdown' },
|
|
25
|
+
parentId: { type: 'string', description: 'Parent Rem ID' },
|
|
26
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Tags to apply' },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'remnote_search',
|
|
32
|
+
description: 'Search the RemNote knowledge base.',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
query: { type: 'string', description: 'Search query text' },
|
|
37
|
+
limit: { type: 'number', description: 'Maximum results' },
|
|
38
|
+
includeContent: { type: 'string', enum: ['none', 'markdown', 'structured'] },
|
|
39
|
+
depth: { type: 'number', description: 'Depth of child hierarchy to render' },
|
|
40
|
+
childLimit: { type: 'number', description: 'Maximum children per level' },
|
|
41
|
+
maxContentLength: { type: 'number', description: 'Maximum rendered content length' },
|
|
42
|
+
},
|
|
43
|
+
required: ['query'],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'remnote_search_by_tag',
|
|
48
|
+
description: 'Find notes by tag.',
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
tag: { type: 'string', description: 'Tag name to search' },
|
|
53
|
+
limit: { type: 'number', description: 'Maximum results' },
|
|
54
|
+
includeContent: { type: 'string', enum: ['none', 'markdown', 'structured'] },
|
|
55
|
+
depth: { type: 'number', description: 'Depth of child hierarchy to render' },
|
|
56
|
+
childLimit: { type: 'number', description: 'Maximum children per level' },
|
|
57
|
+
maxContentLength: { type: 'number', description: 'Maximum rendered content length' },
|
|
58
|
+
},
|
|
59
|
+
required: ['tag'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'remnote_read_note',
|
|
64
|
+
description: 'Read a specific note from RemNote by its Rem ID.',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
remId: { type: 'string', description: 'The Rem ID to read' },
|
|
69
|
+
depth: { type: 'number', description: 'Depth of child hierarchy to render' },
|
|
70
|
+
includeContent: { type: 'string', enum: ['none', 'markdown', 'structured'] },
|
|
71
|
+
childLimit: { type: 'number', description: 'Maximum children per level' },
|
|
72
|
+
maxContentLength: { type: 'number', description: 'Maximum rendered content length' },
|
|
73
|
+
},
|
|
74
|
+
required: ['remId'],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'remnote_update_note',
|
|
79
|
+
description: 'Update an existing note in RemNote.',
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
remId: { type: 'string', description: 'The Rem ID to update' },
|
|
84
|
+
title: { type: 'string', description: 'New title' },
|
|
85
|
+
appendContent: { type: 'string', description: 'Content to append as children' },
|
|
86
|
+
replaceContent: { type: 'string', description: 'Content to replace direct children' },
|
|
87
|
+
addTags: { type: 'array', items: { type: 'string' }, description: 'Tags to add' },
|
|
88
|
+
removeTags: { type: 'array', items: { type: 'string' }, description: 'Tags to remove' },
|
|
89
|
+
},
|
|
90
|
+
required: ['remId'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'remnote_append_journal',
|
|
95
|
+
description: "Append content to today's daily document in RemNote.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
content: { type: 'string', description: "Content to append to today's daily document" },
|
|
100
|
+
timestamp: { type: 'boolean', description: 'Include timestamp' },
|
|
101
|
+
},
|
|
102
|
+
required: ['content'],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'remnote_read_table',
|
|
107
|
+
description: 'Read an Advanced Table from RemNote by exact title or Rem ID.',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
tableRemId: { type: 'string', description: 'Table Rem ID' },
|
|
112
|
+
tableTitle: { type: 'string', description: 'Exact Advanced Table title' },
|
|
113
|
+
limit: { type: 'number', description: 'Maximum rows to return' },
|
|
114
|
+
offset: { type: 'number', description: '0-based row offset for pagination' },
|
|
115
|
+
propertyFilter: {
|
|
116
|
+
type: 'array',
|
|
117
|
+
items: { type: 'string' },
|
|
118
|
+
description: 'Only return these property/column names',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'remnote_get_playbook',
|
|
125
|
+
description: 'Get an operations playbook for MCP agents.',
|
|
126
|
+
inputSchema: { type: 'object', properties: {} },
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'remnote_status',
|
|
130
|
+
description:
|
|
131
|
+
'Check bridge connection health, compatibility warnings, and write-policy capabilities.',
|
|
132
|
+
inputSchema: { type: 'object', properties: {} },
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
export function normalizeMcpUrl(value) {
|
|
137
|
+
const trimmed = String(value || DEFAULT_MCP_URL).trim();
|
|
138
|
+
if (trimmed.endsWith('/mcp')) {
|
|
139
|
+
return trimmed;
|
|
140
|
+
}
|
|
141
|
+
return `${trimmed.replace(/\/+$/, '')}/mcp`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function createSdkHttpClient(mcpUrl, clientInfo = SERVER_INFO) {
|
|
145
|
+
const transport = new StreamableHTTPClientTransport(new URL(mcpUrl));
|
|
146
|
+
const client = new Client(clientInfo);
|
|
147
|
+
return { client, transport };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class RemNoteLocalProxy {
|
|
151
|
+
constructor(options = {}) {
|
|
152
|
+
this.mcpUrl = normalizeMcpUrl(options.mcpUrl ?? process.env.REMNOTE_MCP_URL);
|
|
153
|
+
this.createClient = options.createClient ?? createSdkHttpClient;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async listTools() {
|
|
157
|
+
try {
|
|
158
|
+
return await this.withClient((client) => client.listTools());
|
|
159
|
+
} catch {
|
|
160
|
+
return { tools: FALLBACK_TOOLS };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async callTool(params) {
|
|
165
|
+
try {
|
|
166
|
+
return await this.withClient((client) => client.callTool(params));
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
isError: true,
|
|
170
|
+
content: [{ type: 'text', text: this.formatConnectionError(error) }],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
registerHandlers(server) {
|
|
176
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => this.listTools());
|
|
177
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) =>
|
|
178
|
+
this.callTool({
|
|
179
|
+
name: request.params.name,
|
|
180
|
+
arguments: request.params.arguments ?? {},
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async withClient(operation) {
|
|
186
|
+
const { client, transport } = this.createClient(this.mcpUrl, SERVER_INFO);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
await client.connect(transport);
|
|
190
|
+
return await operation(client);
|
|
191
|
+
} finally {
|
|
192
|
+
await closeBestEffort(transport, client);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
formatConnectionError(error) {
|
|
197
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
198
|
+
return [
|
|
199
|
+
`Cannot connect to local RemNote MCP Server at ${this.mcpUrl}.`,
|
|
200
|
+
'Start remnote-mcp-server, open RemNote, and ensure the Automation Bridge plugin is connected.',
|
|
201
|
+
`Details: ${detail}`,
|
|
202
|
+
].join(' ');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function createStdioServer(options = {}) {
|
|
207
|
+
const server = new Server(SERVER_INFO, { capabilities: { tools: {} } });
|
|
208
|
+
const proxy = new RemNoteLocalProxy(options);
|
|
209
|
+
proxy.registerHandlers(server);
|
|
210
|
+
return server;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function run() {
|
|
214
|
+
const server = createStdioServer();
|
|
215
|
+
await server.connect(new StdioServerTransport());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function formatUsage() {
|
|
219
|
+
return [
|
|
220
|
+
'remnote-mcp-stdio - stdio MCP proxy for a local RemNote MCP Server',
|
|
221
|
+
'',
|
|
222
|
+
'Usage:',
|
|
223
|
+
' remnote-mcp-stdio Run stdio MCP transport for an MCP client',
|
|
224
|
+
' remnote-mcp-stdio --help, -h Print this help',
|
|
225
|
+
' remnote-mcp-stdio --version, -V Print version',
|
|
226
|
+
'',
|
|
227
|
+
'This command is normally launched by a stdio MCP client.',
|
|
228
|
+
'Start remnote-mcp-server separately first, then let the RemNote Automation Bridge connect to it.',
|
|
229
|
+
`Default target: ${DEFAULT_MCP_URL}`,
|
|
230
|
+
'',
|
|
231
|
+
'Environment:',
|
|
232
|
+
' REMNOTE_MCP_URL Override the local MCP endpoint',
|
|
233
|
+
].join('\n');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function handleUtilityCommand(argv = process.argv) {
|
|
237
|
+
if (argv.includes('--version') || argv.includes('-V') || argv.includes('-v')) {
|
|
238
|
+
process.stdout.write(`${SERVER_INFO.version}\n`);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
243
|
+
process.stdout.write(`${formatUsage()}\n`);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function isInteractiveTerminalInvocation(
|
|
251
|
+
argv = process.argv,
|
|
252
|
+
stdin = process.stdin,
|
|
253
|
+
stdout = process.stdout
|
|
254
|
+
) {
|
|
255
|
+
return argv.length <= 2 && Boolean(stdin.isTTY) && Boolean(stdout.isTTY);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function isMainModule(argv = process.argv, moduleUrl = import.meta.url) {
|
|
259
|
+
if (!argv[1]) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
return realpathSync(argv[1]) === realpathSync(fileURLToPath(moduleUrl));
|
|
265
|
+
} catch {
|
|
266
|
+
return pathToFileURL(argv[1]).href === moduleUrl;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function closeBestEffort(transport, client) {
|
|
271
|
+
try {
|
|
272
|
+
if (typeof transport.terminateSession === 'function') {
|
|
273
|
+
await transport.terminateSession();
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
// Best-effort cleanup: the proxy process can continue serving future calls.
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
await client.close();
|
|
281
|
+
} catch {
|
|
282
|
+
// Best-effort cleanup: failed closes should not mask the original operation result.
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (isMainModule()) {
|
|
287
|
+
if (handleUtilityCommand()) {
|
|
288
|
+
process.exit(0);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (isInteractiveTerminalInvocation()) {
|
|
292
|
+
process.stderr.write(`${formatUsage()}\n`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
run().catch((error) => {
|
|
297
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
});
|
|
300
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "remnote-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "MCP server bridge for RemNote knowledge base",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"remnote-mcp-server": "dist/index.js"
|
|
8
|
+
"remnote-mcp-server": "dist/index.js",
|
|
9
|
+
"remnote-cli": "dist/remnote-cli/index.js",
|
|
10
|
+
"remnote-mcp-stdio": "mcpb/remnote-local/server/index.js"
|
|
9
11
|
},
|
|
10
12
|
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
13
|
+
"build": "tsc && node scripts/chmod-bins.mjs && node scripts/build-mcpb.mjs",
|
|
14
|
+
"build:mcpb": "node scripts/build-mcpb.mjs",
|
|
12
15
|
"dev": "tsx watch src/index.ts",
|
|
13
16
|
"start": "node dist/index.js",
|
|
14
17
|
"typecheck": "tsc --noEmit",
|
|
@@ -16,12 +19,15 @@
|
|
|
16
19
|
"test": "vitest run",
|
|
17
20
|
"test:watch": "vitest",
|
|
18
21
|
"test:coverage": "vitest run --coverage",
|
|
19
|
-
"test:integration": "
|
|
22
|
+
"test:integration": "./run-integration-test.sh",
|
|
23
|
+
"test:integration:mcp": "tsx test/integration/run-integration.ts",
|
|
24
|
+
"test:integration:mcpb": "tsx test/integration/run-integration.ts --transport mcpb",
|
|
25
|
+
"test:integration:cli": "tsx test/integration/cli/run-integration.ts",
|
|
20
26
|
"test:ui": "vitest --ui",
|
|
21
|
-
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
22
|
-
"lint:fix": "eslint \"src/**/*.ts\" \"test/**/*.ts\" --fix",
|
|
23
|
-
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
24
|
-
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
27
|
+
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\" \"mcpb/**/*.js\" \"scripts/**/*.mjs\"",
|
|
28
|
+
"lint:fix": "eslint \"src/**/*.ts\" \"test/**/*.ts\" \"mcpb/**/*.js\" \"scripts/**/*.mjs\" --fix",
|
|
29
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"mcpb/**/*.js\" \"mcpb/**/*.json\" \"mcpb/**/*.md\" \"scripts/**/*.mjs\"",
|
|
30
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" \"mcpb/**/*.js\" \"mcpb/**/*.json\" \"mcpb/**/*.md\" \"scripts/**/*.mjs\"",
|
|
25
31
|
"quality": "./code-quality.sh",
|
|
26
32
|
"precommit": "./code-quality.sh"
|
|
27
33
|
},
|
|
@@ -36,10 +42,11 @@
|
|
|
36
42
|
"websocket",
|
|
37
43
|
"bridge"
|
|
38
44
|
],
|
|
39
|
-
"author": "
|
|
45
|
+
"author": "Robert Spiegel <nightingale7@gmail.com>",
|
|
40
46
|
"license": "MIT",
|
|
41
47
|
"files": [
|
|
42
48
|
"dist/",
|
|
49
|
+
"mcpb/",
|
|
43
50
|
"README.md",
|
|
44
51
|
"LICENSE",
|
|
45
52
|
"CHANGELOG.md"
|