remnote-mcp-server 0.13.0 → 0.14.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/CHANGELOG.md +22 -0
- package/README.md +30 -21
- 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/dist/websocket-server.d.ts +1 -0
- package/dist/websocket-server.js +2 -1
- package/dist/websocket-server.js.map +1 -1
- package/package.json +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.14.0] - 2026-05-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Bundled the `remnote-cli` executable into the `remnote-mcp-server` package. The package now provides both
|
|
15
|
+
`remnote-mcp-server` and `remnote-cli`.
|
|
16
|
+
- Added local `link-cli.sh` and `unlink-cli.sh` helpers for linking both executables during development.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Consolidated CLI installation guidance around `npm install -g remnote-mcp-server`; the old standalone
|
|
21
|
+
`remnote-cli` package is now treated as a legacy migration path.
|
|
22
|
+
- Updated agent-assisted and manual integration workflows so the direct MCP and bundled CLI paths run through the
|
|
23
|
+
unified MCP server, with `--suite mcp|cli|all` for targeted reruns.
|
|
24
|
+
- Added server-owned CLI command, troubleshooting, demo, and skill documentation.
|
|
25
|
+
|
|
26
|
+
## [0.13.1] - 2026-05-06
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Increased the RemNote bridge request timeout from 5 seconds to 15 seconds.
|
|
31
|
+
|
|
10
32
|
## [0.13.0] - 2026-04-24
|
|
11
33
|
|
|
12
34
|
### Added
|
package/README.md
CHANGED
|
@@ -5,23 +5,19 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/remnote-mcp-server)
|
|
6
6
|
[](https://codecov.io/gh/robert7/remnote-mcp-server)
|
|
7
7
|
|
|
8
|
-
MCP server that bridges AI agents
|
|
9
|
-
|
|
8
|
+
MCP server and CLI package that bridges AI agents, local scripts, and coding harnesses to
|
|
9
|
+
[RemNote](https://remnote.com/) via the [RemNote Automation Bridge
|
|
10
|
+
plugin](https://github.com/robert7/remnote-mcp-bridge).
|
|
10
11
|
|
|
11
12
|
> If you run into any issues, please [report them here](https://github.com/robert7/remnote-mcp-server/issues).
|
|
12
13
|
|
|
13
14
|
## What is This?
|
|
14
15
|
|
|
15
16
|
The RemNote MCP Server enables AI assistants like Claude Code to interact directly with your RemNote knowledge base
|
|
16
|
-
through the Model Context Protocol (MCP).
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
For some agentic workflows or CLI-first automation, the companion app
|
|
21
|
-
**[remnote-cli](https://github.com/robert7/remnote-cli)** may be a better fit than running a full MCP server.
|
|
22
|
-
In particular, **coding harnesses** (Claude Code, GitHub Copilot CLI, Codex CLI, etc.) can use `remnote-cli` with
|
|
23
|
-
**zero config** — paste one prompt that loads the skill file and the agent handles the rest. See
|
|
24
|
-
[Use RemNote from Any Coding Harness](https://github.com/robert7/remnote-cli/blob/main/docs/demo.md#use-remnote-from-any-coding-harness).
|
|
17
|
+
through the Model Context Protocol (MCP). The same npm package also provides `remnote-cli`, a command-line MCP client
|
|
18
|
+
for local scripts and coding harnesses. Create notes, hierarchical markdown trees, and RemNote-native flashcards;
|
|
19
|
+
search and read your knowledge base; update existing notes; and maintain your daily journal through MCP tools or shell
|
|
20
|
+
commands.
|
|
25
21
|
|
|
26
22
|
## Demo
|
|
27
23
|
|
|
@@ -29,14 +25,14 @@ See AI agent examples in action with RemNote: **[View Demo →](docs/demo.md)**
|
|
|
29
25
|
|
|
30
26
|
### Two-Component Architecture
|
|
31
27
|
|
|
32
|
-
This system consists of **two separate components** that work together:
|
|
28
|
+
This system consists of **two separate runtime components** that work together:
|
|
33
29
|
|
|
34
30
|
1. **[RemNote Automation Bridge](https://github.com/robert7/remnote-mcp-bridge)** - A RemNote plugin that runs in your
|
|
35
31
|
browser or RemNote desktop app and exposes RemNote API functionality via WebSocket
|
|
36
|
-
2. **RemNote MCP Server** (this project) - A standalone server that
|
|
37
|
-
|
|
32
|
+
2. **RemNote MCP Server** (this project) - A standalone server package that provides the `remnote-mcp-server`
|
|
33
|
+
executable for MCP clients and the `remnote-cli` executable for command-line workflows
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
The `remnote-cli` command is not a second server. It calls the MCP endpoint exposed by `remnote-mcp-server`.
|
|
40
36
|
|
|
41
37
|
For the detailed bridge connection lifecycle, retry phases, and wake-up triggers, use the bridge repo as the source of
|
|
42
38
|
truth: [Connection Lifecycle Guide](https://github.com/robert7/remnote-mcp-bridge/blob/main/docs/guides/connection-lifecycle.md).
|
|
@@ -44,12 +40,14 @@ truth: [Connection Lifecycle Guide](https://github.com/robert7/remnote-mcp-bridg
|
|
|
44
40
|
### How It Works
|
|
45
41
|
|
|
46
42
|
```text
|
|
47
|
-
AI agents (HTTP)
|
|
43
|
+
AI agents (HTTP) -> MCP HTTP Server :3001 -> WebSocket Server :3002 -> RemNote Plugin -> RemNote
|
|
44
|
+
CLI commands -> remnote-cli -> MCP HTTP Server :3001 -> WebSocket Server :3002 -> RemNote Plugin -> RemNote
|
|
48
45
|
```
|
|
49
46
|
|
|
50
47
|
The server acts as a bridge:
|
|
51
48
|
|
|
52
49
|
- Communicates with AI agents via Streamable HTTP transport (MCP protocol) - supports both local and remote access
|
|
50
|
+
- Provides `remnote-cli` as a bundled command-line MCP client for local automation
|
|
53
51
|
- HTTP server (port 3001) manages MCP sessions for multiple concurrent agents
|
|
54
52
|
- WebSocket server (port 3002) connects to the RemNote browser plugin
|
|
55
53
|
- Translates MCP tool calls into RemNote API actions
|
|
@@ -81,6 +79,13 @@ connection always stays local for security. See [Remote Access Guide](docs/guide
|
|
|
81
79
|
npm install -g remnote-mcp-server
|
|
82
80
|
```
|
|
83
81
|
|
|
82
|
+
The package installs both commands:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
remnote-mcp-server --version
|
|
86
|
+
remnote-cli --version
|
|
87
|
+
```
|
|
88
|
+
|
|
84
89
|
### 2. Install the RemNote Plugin
|
|
85
90
|
|
|
86
91
|
Install the [RemNote Automation Bridge plugin](https://github.com/robert7/remnote-mcp-bridge) in your RemNote app.
|
|
@@ -96,7 +101,7 @@ remnote-mcp-server
|
|
|
96
101
|
Expected output:
|
|
97
102
|
|
|
98
103
|
```text
|
|
99
|
-
RemNote MCP Server v0.
|
|
104
|
+
RemNote MCP Server v0.14.0 listening { wsPort: 3002, httpPort: 3001 }
|
|
100
105
|
```
|
|
101
106
|
|
|
102
107
|
Keep this terminal running.
|
|
@@ -125,6 +130,7 @@ Keep this terminal running.
|
|
|
125
130
|
### Usage
|
|
126
131
|
|
|
127
132
|
- **[CLI Options Reference](docs/guides/cli-options.md)** - Command-line options and environment variables
|
|
133
|
+
- **[remnote-cli Command Reference](docs/guides/cli-command-reference.md)** - Shell command reference for the bundled CLI
|
|
128
134
|
- **[MCP Tools Reference](docs/guides/tools-reference.md)** - Detailed reference for all 9 RemNote tools
|
|
129
135
|
- **[Remote Access Setup](docs/guides/remote-access.md)** - Expose server for Claude Desktop / Cowork (ngrok, etc.)
|
|
130
136
|
|
|
@@ -165,6 +171,7 @@ See the [Tools Reference](docs/guides/tools-reference.md) for detailed usage and
|
|
|
165
171
|
- **Claude Desktop / Cowork** - Remote connector clients (require [remote access](docs/guides/remote-access.md))
|
|
166
172
|
- **[Accomplish](https://github.com/accomplish-ai/accomplish)** - Task-based MCP client (formerly Openwork)
|
|
167
173
|
- **Any MCP client** supporting Streamable HTTP transport
|
|
174
|
+
- **Any local command runner** that can call `remnote-cli`
|
|
168
175
|
|
|
169
176
|
## Example Usage
|
|
170
177
|
|
|
@@ -245,9 +252,9 @@ See the [Troubleshooting Guide](docs/guides/troubleshooting.md) for detailed sol
|
|
|
245
252
|
```bash
|
|
246
253
|
git clone https://github.com/robert7/remnote-mcp-server.git
|
|
247
254
|
cd remnote-mcp-server
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
255
|
+
./link-cli.sh
|
|
256
|
+
# Later, remove the local links for both remnote-mcp-server and remnote-cli:
|
|
257
|
+
./unlink-cli.sh
|
|
251
258
|
```
|
|
252
259
|
|
|
253
260
|
**Development workflow:**
|
|
@@ -260,13 +267,15 @@ npm test # Run test suite
|
|
|
260
267
|
|
|
261
268
|
See the [Development Setup Guide](docs/guides/development-setup.md) for complete instructions.
|
|
262
269
|
|
|
263
|
-
Pull requests that affect bridge-consumer behavior should follow the shared PR rules in the bridge repo: [Pull Request Guide](https://github.com/robert7/remnote-mcp-bridge/blob/main/docs/guides/pull-request-guide.md). In particular, keep bridge
|
|
270
|
+
Pull requests that affect bridge-consumer behavior should follow the shared PR rules in the bridge repo: [Pull Request Guide](https://github.com/robert7/remnote-mcp-bridge/blob/main/docs/guides/pull-request-guide.md). In particular, keep bridge and server-package behavior aligned for shared functionality changes.
|
|
264
271
|
|
|
265
272
|
For the canonical workflow for updating and running shared live integration coverage, see the [Integration Testing Guide](docs/guides/integration-testing.md).
|
|
266
273
|
|
|
267
274
|
## Related Projects
|
|
268
275
|
|
|
269
276
|
- [RemNote Automation Bridge](https://github.com/robert7/remnote-mcp-bridge) - Browser plugin for RemNote integration
|
|
277
|
+
- [Legacy remnote-cli repository](https://github.com/robert7/remnote-cli) - Historical source; the maintained CLI now
|
|
278
|
+
ships from this package
|
|
270
279
|
- [Model Context Protocol](https://modelcontextprotocol.io/) - Open protocol for AI-application integration
|
|
271
280
|
|
|
272
281
|
## License
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { DEFAULT_MCP_URL } from './config.js';
|
|
4
|
+
import { registerCreateCommand } from './commands/create.js';
|
|
5
|
+
import { registerSearchByTagCommand, registerSearchCommand } from './commands/search.js';
|
|
6
|
+
import { registerReadCommand } from './commands/read.js';
|
|
7
|
+
import { registerUpdateCommand } from './commands/update.js';
|
|
8
|
+
import { registerJournalCommand } from './commands/journal.js';
|
|
9
|
+
import { registerStatusCommand } from './commands/status.js';
|
|
10
|
+
import { registerReadTableCommand } from './commands/table.js';
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
const packageJson = require('../../package.json');
|
|
13
|
+
export function createProgram(version) {
|
|
14
|
+
const program = new Command();
|
|
15
|
+
program
|
|
16
|
+
.name('remnote-cli')
|
|
17
|
+
.description('CLI client for RemNote MCP Server')
|
|
18
|
+
.version(version)
|
|
19
|
+
.option('--json', 'JSON output (default)')
|
|
20
|
+
.option('--text', 'Human-readable output')
|
|
21
|
+
.option('--mcp-url <url>', 'RemNote MCP server URL', process.env.REMNOTE_MCP_URL || DEFAULT_MCP_URL)
|
|
22
|
+
.option('--verbose', 'Enable verbose stderr logging');
|
|
23
|
+
registerCreateCommand(program);
|
|
24
|
+
registerSearchCommand(program);
|
|
25
|
+
registerSearchByTagCommand(program);
|
|
26
|
+
registerReadCommand(program);
|
|
27
|
+
registerUpdateCommand(program);
|
|
28
|
+
registerJournalCommand(program);
|
|
29
|
+
registerStatusCommand(program);
|
|
30
|
+
registerReadTableCommand(program);
|
|
31
|
+
return program;
|
|
32
|
+
}
|
|
33
|
+
export function runCli(argv = process.argv) {
|
|
34
|
+
const program = createProgram(packageJson.version);
|
|
35
|
+
program.parse(argv);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/remnote-cli/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,0BAA0B,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAwB,CAAC;AAEzE,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,aAAa,CAAC;SACnB,WAAW,CAAC,mCAAmC,CAAC;SAChD,OAAO,CAAC,OAAO,CAAC;SAChB,MAAM,CAAC,QAAQ,EAAE,uBAAuB,CAAC;SACzC,MAAM,CAAC,QAAQ,EAAE,uBAAuB,CAAC;SACzC,MAAM,CACL,iBAAiB,EACjB,wBAAwB,EACxB,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,eAAe,CAC/C;SACA,MAAM,CAAC,WAAW,EAAE,+BAA+B,CAAC,CAAC;IAExD,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,0BAA0B,CAAC,OAAO,CAAC,CAAC;IACpC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAElC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI;IACxC,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { McpServerClient } from './mcp-server-client.js';
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const packageJson = require('../../../package.json');
|
|
5
|
+
export function createCommandClient(program) {
|
|
6
|
+
const opts = program.opts();
|
|
7
|
+
return new McpServerClient(opts.mcpUrl, { name: 'remnote-cli', version: packageJson.version });
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=command-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-client.js","sourceRoot":"","sources":["../../../src/remnote-cli/client/command-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,uBAAuB,CAAwB,CAAC;AAE5E,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAsB,CAAC;IAChD,OAAO,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC;AACjG,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Short-lived MCP client used by CLI commands to call remnote-mcp-server.
|
|
3
|
+
*/
|
|
4
|
+
export declare class McpServerClient {
|
|
5
|
+
private readonly mcpUrl;
|
|
6
|
+
private readonly clientInfo;
|
|
7
|
+
private client;
|
|
8
|
+
private transport;
|
|
9
|
+
constructor(mcpUrl: string, clientInfo?: {
|
|
10
|
+
name: string;
|
|
11
|
+
version: string;
|
|
12
|
+
});
|
|
13
|
+
execute(action: string, payload: Record<string, unknown>): Promise<unknown>;
|
|
14
|
+
close(): Promise<void>;
|
|
15
|
+
private connect;
|
|
16
|
+
private extractText;
|
|
17
|
+
private parseResult;
|
|
18
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import { checkVersionCompatibility } from '../version-compat.js';
|
|
4
|
+
const ACTION_TO_TOOL = {
|
|
5
|
+
create_note: 'remnote_create_note',
|
|
6
|
+
search: 'remnote_search',
|
|
7
|
+
search_by_tag: 'remnote_search_by_tag',
|
|
8
|
+
read_note: 'remnote_read_note',
|
|
9
|
+
update_note: 'remnote_update_note',
|
|
10
|
+
append_journal: 'remnote_append_journal',
|
|
11
|
+
get_status: 'remnote_status',
|
|
12
|
+
read_table: 'remnote_read_table',
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Short-lived MCP client used by CLI commands to call remnote-mcp-server.
|
|
16
|
+
*/
|
|
17
|
+
export class McpServerClient {
|
|
18
|
+
mcpUrl;
|
|
19
|
+
clientInfo;
|
|
20
|
+
client = null;
|
|
21
|
+
transport = null;
|
|
22
|
+
constructor(mcpUrl, clientInfo = { name: 'remnote-cli', version: '0.14.0' }) {
|
|
23
|
+
this.mcpUrl = normalizeMcpUrl(mcpUrl);
|
|
24
|
+
this.clientInfo = clientInfo;
|
|
25
|
+
}
|
|
26
|
+
async execute(action, payload) {
|
|
27
|
+
const toolName = ACTION_TO_TOOL[action];
|
|
28
|
+
if (!toolName) {
|
|
29
|
+
throw new Error(`Unknown bridge action: ${action}`);
|
|
30
|
+
}
|
|
31
|
+
await this.connect();
|
|
32
|
+
const result = await this.client.callTool({ name: toolName, arguments: payload });
|
|
33
|
+
if (result.isError) {
|
|
34
|
+
throw new Error(this.extractText(result));
|
|
35
|
+
}
|
|
36
|
+
const parsed = this.parseResult(result);
|
|
37
|
+
if (action === 'get_status' && parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
38
|
+
const status = { ...parsed, cliVersion: this.clientInfo.version };
|
|
39
|
+
const serverVersion = status.serverVersion;
|
|
40
|
+
if (typeof serverVersion === 'string') {
|
|
41
|
+
const warning = checkVersionCompatibility(this.clientInfo.version, serverVersion, 'CLI', 'MCP server');
|
|
42
|
+
if (warning) {
|
|
43
|
+
status.version_warning =
|
|
44
|
+
typeof status.version_warning === 'string'
|
|
45
|
+
? `${status.version_warning}\n${warning}`
|
|
46
|
+
: warning;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return status;
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
async close() {
|
|
54
|
+
try {
|
|
55
|
+
if (this.transport) {
|
|
56
|
+
await this.transport.terminateSession();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Ignore shutdown errors; CLI commands are already done at this point.
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
if (this.client) {
|
|
64
|
+
await this.client.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Ignore shutdown errors; connection cleanup is best-effort.
|
|
69
|
+
}
|
|
70
|
+
this.client = null;
|
|
71
|
+
this.transport = null;
|
|
72
|
+
}
|
|
73
|
+
async connect() {
|
|
74
|
+
if (this.client) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.transport = new StreamableHTTPClientTransport(new URL(this.mcpUrl));
|
|
78
|
+
this.client = new Client(this.clientInfo);
|
|
79
|
+
try {
|
|
80
|
+
await this.client.connect(this.transport);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.client = null;
|
|
84
|
+
this.transport = null;
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
throw new Error(`Cannot connect to MCP server at ${this.mcpUrl}. Is remnote-mcp-server running? ${message}`, { cause: error });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
extractText(result) {
|
|
90
|
+
const content = result.content;
|
|
91
|
+
const text = content?.find((item) => item.type === 'text' && item.text)?.text;
|
|
92
|
+
return text ?? JSON.stringify(result);
|
|
93
|
+
}
|
|
94
|
+
parseResult(result) {
|
|
95
|
+
if (result.structuredContent &&
|
|
96
|
+
typeof result.structuredContent === 'object' &&
|
|
97
|
+
!Array.isArray(result.structuredContent)) {
|
|
98
|
+
return result.structuredContent;
|
|
99
|
+
}
|
|
100
|
+
const text = this.extractText(result);
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(text);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { _raw: text };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function normalizeMcpUrl(value) {
|
|
110
|
+
const trimmed = value.trim();
|
|
111
|
+
if (trimmed.endsWith('/mcp')) {
|
|
112
|
+
return trimmed;
|
|
113
|
+
}
|
|
114
|
+
return `${trimmed.replace(/\/+$/, '')}/mcp`;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=mcp-server-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-server-client.js","sourceRoot":"","sources":["../../../src/remnote-cli/client/mcp-server-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Determines if a given string value looks like a CLI flag instead of a standard argument.
|
|
4
|
+
* This is used to detect "argument shifting" (e.g., when an empty string "" is swallowed by the shell,
|
|
5
|
+
* causing the next flag to be incorrectly parsed as the value for the current option).
|
|
6
|
+
*
|
|
7
|
+
* @param value - The string argument to evaluate.
|
|
8
|
+
* @param cmd - (Optional) The Command instance. If provided, enables strict matching against registered flags.
|
|
9
|
+
* @returns True if the value is likely a shifted flag; otherwise, false.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isFlag(value: string | undefined, cmd?: Command): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Validates an entire record of parsed arguments to ensure none of them are shifted flags.
|
|
14
|
+
* Useful for running a bulk check after Commander has finished parsing,
|
|
15
|
+
* especially when using dynamic commands or positional arguments.
|
|
16
|
+
*
|
|
17
|
+
* @param fields - A key-value record of parsed arguments (e.g., { title: '--content', parentId: '123' }).
|
|
18
|
+
* @param cmd - (Optional) The Command instance for strict flag matching.
|
|
19
|
+
* @throws {Error} If any value is identified as a shifted flag.
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkPayloadForFlags(fields: Record<string, string | undefined>, cmd?: Command): void;
|
|
22
|
+
/**
|
|
23
|
+
* A higher-order function designed to be used as a custom processing function in Commander's `.option()`.
|
|
24
|
+
* It binds the Command context to the validation logic.
|
|
25
|
+
*
|
|
26
|
+
* @param cmd - The current Commander instance.
|
|
27
|
+
* @returns A validation function that throws an InvalidArgumentError if the value is a shifted flag.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* const check = validateNotFlag(program);
|
|
31
|
+
* program.option('--title <text>', 'Note title', check);
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateNotFlag(value: string, cmd?: Command): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { InvalidArgumentError } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* A memory-safe cache that stores the flattened list of registered flags for each Command.
|
|
4
|
+
* Using a WeakMap ensures that once a Command object is garbage collected (e.g., after a test run),
|
|
5
|
+
* its associated cache is also freed, preventing memory leaks.
|
|
6
|
+
*/
|
|
7
|
+
const flagCache = new WeakMap();
|
|
8
|
+
/**
|
|
9
|
+
* Traverses the current Command and all its parent commands to collect every registered flag.
|
|
10
|
+
* Uses caching to ensure this expensive traversal only happens once per Command instance.
|
|
11
|
+
*
|
|
12
|
+
* @param cmd - The Commander.js Command instance to inspect.
|
|
13
|
+
* @returns An array of all registered short and long flags (e.g., ['-c', '--content', ...]).
|
|
14
|
+
*/
|
|
15
|
+
const getAllRegisteredFlags = (cmd) => {
|
|
16
|
+
if (flagCache.has(cmd)) {
|
|
17
|
+
return flagCache.get(cmd);
|
|
18
|
+
}
|
|
19
|
+
let flags = [];
|
|
20
|
+
let current = cmd;
|
|
21
|
+
while (current) {
|
|
22
|
+
if (current.options) {
|
|
23
|
+
const currentFlags = current.options.flatMap((opt) => [opt.short, opt.long].filter(Boolean));
|
|
24
|
+
flags = [...flags, ...currentFlags];
|
|
25
|
+
}
|
|
26
|
+
// Move up the command tree to catch globally registered options
|
|
27
|
+
current = current.parent;
|
|
28
|
+
}
|
|
29
|
+
flagCache.set(cmd, flags);
|
|
30
|
+
return flags;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Determines if a given string value looks like a CLI flag instead of a standard argument.
|
|
34
|
+
* This is used to detect "argument shifting" (e.g., when an empty string "" is swallowed by the shell,
|
|
35
|
+
* causing the next flag to be incorrectly parsed as the value for the current option).
|
|
36
|
+
*
|
|
37
|
+
* @param value - The string argument to evaluate.
|
|
38
|
+
* @param cmd - (Optional) The Command instance. If provided, enables strict matching against registered flags.
|
|
39
|
+
* @returns True if the value is likely a shifted flag; otherwise, false.
|
|
40
|
+
*/
|
|
41
|
+
export function isFlag(value, cmd) {
|
|
42
|
+
if (!value)
|
|
43
|
+
return false;
|
|
44
|
+
// Strict Match: If a Command context is provided, check against actually registered flags.
|
|
45
|
+
if (cmd) {
|
|
46
|
+
const knownFlags = getAllRegisteredFlags(cmd);
|
|
47
|
+
if (knownFlags.includes(value))
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validates an entire record of parsed arguments to ensure none of them are shifted flags.
|
|
54
|
+
* Useful for running a bulk check after Commander has finished parsing,
|
|
55
|
+
* especially when using dynamic commands or positional arguments.
|
|
56
|
+
*
|
|
57
|
+
* @param fields - A key-value record of parsed arguments (e.g., { title: '--content', parentId: '123' }).
|
|
58
|
+
* @param cmd - (Optional) The Command instance for strict flag matching.
|
|
59
|
+
* @throws {Error} If any value is identified as a shifted flag.
|
|
60
|
+
*/
|
|
61
|
+
export function checkPayloadForFlags(fields, cmd) {
|
|
62
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
63
|
+
if (isFlag(value, cmd)) {
|
|
64
|
+
throw new Error(`Argument shifting detected: "${value}" was misinterpreted as the value for "${name}". ` +
|
|
65
|
+
`This usually indicates argument shifting (e.g. the shell swallowed an empty string ""). ` +
|
|
66
|
+
`To fix this, use explicit flags (e.g. --title="", --content="") or check your quoting.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* A higher-order function designed to be used as a custom processing function in Commander's `.option()`.
|
|
72
|
+
* It binds the Command context to the validation logic.
|
|
73
|
+
*
|
|
74
|
+
* @param cmd - The current Commander instance.
|
|
75
|
+
* @returns A validation function that throws an InvalidArgumentError if the value is a shifted flag.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const check = validateNotFlag(program);
|
|
79
|
+
* program.option('--title <text>', 'Note title', check);
|
|
80
|
+
*/
|
|
81
|
+
export function validateNotFlag(value, cmd) {
|
|
82
|
+
if (isFlag(value, cmd)) {
|
|
83
|
+
throw new InvalidArgumentError(`"${value}" looks like a flag but was passed as an option value. ` +
|
|
84
|
+
`This usually indicates argument shifting (e.g. the shell swallowed an empty string ""). ` +
|
|
85
|
+
`To fix this, use explicit flags (e.g. --title="", --content="") or check your quoting.`);
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=arg-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arg-utils.js","sourceRoot":"","sources":["../../../src/remnote-cli/commands/arg-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAgB,MAAM,WAAW,CAAC;AAE/D;;;;GAIG;AACH,MAAM,SAAS,GAAG,IAAI,OAAO,EAAqB,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,CAAC,GAAY,EAAY,EAAE;IACvD,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,SAAS,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;IAC7B,CAAC;IAED,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,OAAO,GAAmB,GAAG,CAAC;IAElC,OAAO,OAAO,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAC1C,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAC3D,CAAC;YACF,KAAK,GAAG,CAAC,GAAG,KAAK,EAAE,GAAG,YAAY,CAAC,CAAC;QACtC,CAAC;QACD,gEAAgE;QAChE,OAAO,GAAG,OAAO,CAAC,MAAwB,CAAC;IAC7C,CAAC;IAED,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1B,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAC,KAAyB,EAAE,GAAa;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAEzB,2FAA2F;IAC3F,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,UAAU,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;IAC9C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAA0C,EAC1C,GAAa;IAEb,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,IAAI,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,gCAAgC,KAAK,0CAA0C,IAAI,KAAK;gBACtF,0FAA0F;gBAC1F,wFAAwF,CAC3F,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,GAAa;IAC1D,IAAI,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,oBAAoB,CAC5B,IAAI,KAAK,yDAAyD;YAChE,0FAA0F;YAC1F,wFAAwF,CAC3F,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
export declare const MAX_WRITE_CONTENT_BYTES: number;
|
|
3
|
+
export declare function readContentFileOrStdin(pathOrDash: string, stdin?: Readable): Promise<string>;
|
|
4
|
+
interface OptionalContentArgs {
|
|
5
|
+
inlineText: string | undefined;
|
|
6
|
+
filePath: string | undefined;
|
|
7
|
+
inlineFlag: string;
|
|
8
|
+
fileFlag: string;
|
|
9
|
+
stdin?: Readable;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveOptionalInlineOrFileContent({ inlineText, filePath, inlineFlag, fileFlag, stdin, }: OptionalContentArgs): Promise<string | undefined>;
|
|
12
|
+
interface UpdateContentArgs {
|
|
13
|
+
appendText: string | undefined;
|
|
14
|
+
appendFile: string | undefined;
|
|
15
|
+
replaceText: string | undefined;
|
|
16
|
+
replaceFile: string | undefined;
|
|
17
|
+
stdin?: Readable;
|
|
18
|
+
}
|
|
19
|
+
interface ResolvedUpdateContent {
|
|
20
|
+
appendContent: string | undefined;
|
|
21
|
+
replaceContent: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
export declare function resolveUpdateContent({ appendText, appendFile, replaceText, replaceFile, stdin, }: UpdateContentArgs): Promise<ResolvedUpdateContent>;
|
|
24
|
+
interface JournalContentArgs {
|
|
25
|
+
positionalContent: string | undefined;
|
|
26
|
+
optionContent: string | undefined;
|
|
27
|
+
contentFile: string | undefined;
|
|
28
|
+
stdin?: Readable;
|
|
29
|
+
}
|
|
30
|
+
export declare function resolveJournalContent({ positionalContent, optionContent, contentFile, stdin, }: JournalContentArgs): Promise<string>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
export const MAX_WRITE_CONTENT_BYTES = 100 * 1024;
|
|
3
|
+
function formatLimit() {
|
|
4
|
+
return `${MAX_WRITE_CONTENT_BYTES / 1024} KB`;
|
|
5
|
+
}
|
|
6
|
+
function ensureWithinLimit(byteLength, sourceLabel) {
|
|
7
|
+
if (byteLength > MAX_WRITE_CONTENT_BYTES) {
|
|
8
|
+
throw new Error(`${sourceLabel} exceeds ${formatLimit()} limit`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
async function readUtf8FromStream(stream, sourceLabel) {
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let totalBytes = 0;
|
|
14
|
+
for await (const chunk of stream) {
|
|
15
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
16
|
+
totalBytes += buffer.byteLength;
|
|
17
|
+
ensureWithinLimit(totalBytes, sourceLabel);
|
|
18
|
+
chunks.push(buffer);
|
|
19
|
+
}
|
|
20
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
21
|
+
}
|
|
22
|
+
export async function readContentFileOrStdin(pathOrDash, stdin = process.stdin) {
|
|
23
|
+
if (pathOrDash === '-') {
|
|
24
|
+
return readUtf8FromStream(stdin, 'Stdin content');
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const buffer = await readFile(pathOrDash);
|
|
28
|
+
ensureWithinLimit(buffer.byteLength, `Content file "${pathOrDash}"`);
|
|
29
|
+
return buffer.toString('utf8');
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
throw new Error(`Failed to read content file "${pathOrDash}": ${message}`, {
|
|
34
|
+
cause: error,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function resolveOptionalInlineOrFileContent({ inlineText, filePath, inlineFlag, fileFlag, stdin, }) {
|
|
39
|
+
if (inlineText !== undefined && filePath !== undefined) {
|
|
40
|
+
throw new Error(`Cannot use ${inlineFlag} and ${fileFlag} together`);
|
|
41
|
+
}
|
|
42
|
+
if (filePath !== undefined) {
|
|
43
|
+
return readContentFileOrStdin(filePath, stdin);
|
|
44
|
+
}
|
|
45
|
+
// convert literal \n strings into actual newline characters
|
|
46
|
+
return inlineText?.replace(/\\n/g, '\n');
|
|
47
|
+
}
|
|
48
|
+
export async function resolveUpdateContent({ appendText, appendFile, replaceText, replaceFile, stdin, }) {
|
|
49
|
+
const appendContent = await resolveOptionalInlineOrFileContent({
|
|
50
|
+
inlineText: appendText,
|
|
51
|
+
filePath: appendFile,
|
|
52
|
+
inlineFlag: '--append',
|
|
53
|
+
fileFlag: '--append-file',
|
|
54
|
+
stdin,
|
|
55
|
+
});
|
|
56
|
+
const replaceContent = await resolveOptionalInlineOrFileContent({
|
|
57
|
+
inlineText: replaceText,
|
|
58
|
+
filePath: replaceFile,
|
|
59
|
+
inlineFlag: '--replace',
|
|
60
|
+
fileFlag: '--replace-file',
|
|
61
|
+
stdin,
|
|
62
|
+
});
|
|
63
|
+
if (appendContent !== undefined && replaceContent !== undefined) {
|
|
64
|
+
throw new Error('Cannot combine append and replace content options (--append/--append-file with --replace/--replace-file)');
|
|
65
|
+
}
|
|
66
|
+
return { appendContent, replaceContent };
|
|
67
|
+
}
|
|
68
|
+
export async function resolveJournalContent({ positionalContent, optionContent, contentFile, stdin, }) {
|
|
69
|
+
const providedCount = [positionalContent, optionContent, contentFile].filter((value) => value !== undefined).length;
|
|
70
|
+
if (providedCount !== 1) {
|
|
71
|
+
throw new Error('Provide exactly one journal content source: positional <content>, --content <text>, or --content-file <path|->');
|
|
72
|
+
}
|
|
73
|
+
if (contentFile !== undefined) {
|
|
74
|
+
return readContentFileOrStdin(contentFile, stdin);
|
|
75
|
+
}
|
|
76
|
+
if (optionContent !== undefined) {
|
|
77
|
+
return optionContent;
|
|
78
|
+
}
|
|
79
|
+
return positionalContent;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=content-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-input.js","sourceRoot":"","sources":["../../../src/remnote-cli/commands/content-input.ts"],"names":[],"mappings":"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"}
|