mcp-server-markview 1.1.3
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 +115 -0
- package/bin/mcp-server-markview +70 -0
- package/package.json +48 -0
- package/scripts/postinstall.js +205 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# mcp-server-markview
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for [MarkView](https://github.com/paulhkang94/markview) — a native macOS Markdown previewer.
|
|
4
|
+
|
|
5
|
+
Lets AI assistants (Claude, etc.) preview Markdown files and open them in the MarkView app directly from the conversation.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- macOS (arm64 or x86_64)
|
|
10
|
+
- Node.js 18+
|
|
11
|
+
- MarkView.app (auto-fetched on install, or install manually from [Releases](https://github.com/paulhkang94/markview/releases))
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
Run without installing:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx mcp-server-markview
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or install globally:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g mcp-server-markview
|
|
25
|
+
mcp-server-markview
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The binary is downloaded automatically during installation. If the download fails (e.g. offline install), the wrapper falls back to a locally installed MarkView.app at `/Applications` or `~/Applications`.
|
|
29
|
+
|
|
30
|
+
## Claude Code Configuration
|
|
31
|
+
|
|
32
|
+
Add to your Claude Code MCP config (usually `~/.claude/mcp.json` or via `claude mcp add`):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"markview": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "mcp-server-markview"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or if you have installed it globally:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"markview": {
|
|
51
|
+
"command": "mcp-server-markview"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Using `claude mcp add`
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
claude mcp add markview -- npx -y mcp-server-markview
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Available Tools
|
|
64
|
+
|
|
65
|
+
### `preview_markdown`
|
|
66
|
+
|
|
67
|
+
Renders a Markdown string and opens a live preview in MarkView.
|
|
68
|
+
|
|
69
|
+
| Parameter | Type | Description |
|
|
70
|
+
|-----------|--------|---------------------------------|
|
|
71
|
+
| `content` | string | Markdown source text to preview |
|
|
72
|
+
| `title` | string | Optional window title |
|
|
73
|
+
|
|
74
|
+
### `open_file`
|
|
75
|
+
|
|
76
|
+
Opens a Markdown file from disk in MarkView.
|
|
77
|
+
|
|
78
|
+
| Parameter | Type | Description |
|
|
79
|
+
|-----------|--------|--------------------------------------|
|
|
80
|
+
| `path` | string | Absolute path to the `.md` file |
|
|
81
|
+
|
|
82
|
+
## Transport
|
|
83
|
+
|
|
84
|
+
The server uses **stdio transport** (JSON-RPC 2.0 over stdin/stdout), which is the standard MCP transport and compatible with all MCP clients.
|
|
85
|
+
|
|
86
|
+
## How It Works
|
|
87
|
+
|
|
88
|
+
1. `npm install` runs `scripts/postinstall.js`, which downloads the prebuilt `MarkView` release archive from GitHub and extracts the `markview-mcp-server` binary.
|
|
89
|
+
2. The binary is placed at `bin/markview-mcp-server-binary` inside the package.
|
|
90
|
+
3. `bin/mcp-server-markview` (the shell wrapper registered in `bin`) locates the binary and `exec`s it, preserving stdio.
|
|
91
|
+
|
|
92
|
+
## Troubleshooting
|
|
93
|
+
|
|
94
|
+
**Binary not found after install**
|
|
95
|
+
|
|
96
|
+
Re-run the postinstall script manually:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
node "$(npm root -g)/mcp-server-markview/scripts/postinstall.js"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Download failed (corporate proxy / offline)**
|
|
103
|
+
|
|
104
|
+
Install MarkView.app manually from the [Releases page](https://github.com/paulhkang94/markview/releases) and place it in `/Applications`. The wrapper will find it automatically.
|
|
105
|
+
|
|
106
|
+
**Permission denied**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
chmod +x "$(npm root -g)/mcp-server-markview/bin/markview-mcp-server-binary"
|
|
110
|
+
chmod +x "$(npm root -g)/mcp-server-markview/bin/mcp-server-markview"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT — see [LICENSE](https://github.com/paulhkang94/markview/blob/main/LICENSE).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# mcp-server-markview
|
|
3
|
+
#
|
|
4
|
+
# Shell wrapper that locates the MarkView MCP server binary and exec's it,
|
|
5
|
+
# passing through all arguments and preserving stdio (required for JSON-RPC
|
|
6
|
+
# over stdio transport).
|
|
7
|
+
#
|
|
8
|
+
# Resolution order:
|
|
9
|
+
# 1. Downloaded binary next to this script (placed by postinstall.js)
|
|
10
|
+
# 2. MarkView.app installed at /Applications (system install)
|
|
11
|
+
# 3. MarkView.app installed in ~/Applications (user install)
|
|
12
|
+
|
|
13
|
+
set -e
|
|
14
|
+
|
|
15
|
+
# Resolve the real directory of this script, following symlinks.
|
|
16
|
+
# This is important when the script is invoked via `npx` which may symlink bin/.
|
|
17
|
+
resolve_dir() {
|
|
18
|
+
local target="$1"
|
|
19
|
+
# Loop until target is not a symlink.
|
|
20
|
+
while [ -L "$target" ]; do
|
|
21
|
+
local link
|
|
22
|
+
link="$(readlink "$target")"
|
|
23
|
+
case "$link" in
|
|
24
|
+
/*) target="$link" ;;
|
|
25
|
+
*) target="$(dirname "$target")/$link" ;;
|
|
26
|
+
esac
|
|
27
|
+
done
|
|
28
|
+
dirname "$target"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
SCRIPT_DIR="$(resolve_dir "$0")"
|
|
32
|
+
|
|
33
|
+
# --- Candidate 1: binary downloaded by postinstall ---
|
|
34
|
+
DOWNLOADED_BINARY="$SCRIPT_DIR/markview-mcp-server-binary"
|
|
35
|
+
|
|
36
|
+
if [ -x "$DOWNLOADED_BINARY" ]; then
|
|
37
|
+
exec "$DOWNLOADED_BINARY" "$@"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# --- Candidate 2: /Applications (system-wide install) ---
|
|
41
|
+
SYSTEM_APP_BINARY="/Applications/MarkView.app/Contents/MacOS/markview-mcp-server"
|
|
42
|
+
|
|
43
|
+
if [ -x "$SYSTEM_APP_BINARY" ]; then
|
|
44
|
+
exec "$SYSTEM_APP_BINARY" "$@"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# --- Candidate 3: ~/Applications (user install) ---
|
|
48
|
+
USER_APP_BINARY="$HOME/Applications/MarkView.app/Contents/MacOS/markview-mcp-server"
|
|
49
|
+
|
|
50
|
+
if [ -x "$USER_APP_BINARY" ]; then
|
|
51
|
+
exec "$USER_APP_BINARY" "$@"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# --- Not found ---
|
|
55
|
+
cat >&2 <<'EOF'
|
|
56
|
+
mcp-server-markview: could not find the MarkView MCP server binary.
|
|
57
|
+
|
|
58
|
+
To fix this, try one of the following:
|
|
59
|
+
|
|
60
|
+
1. Re-run postinstall to download the binary:
|
|
61
|
+
node "$(npm root -g)/mcp-server-markview/scripts/postinstall.js"
|
|
62
|
+
|
|
63
|
+
2. Install MarkView.app from:
|
|
64
|
+
https://github.com/paulhkang94/markview/releases
|
|
65
|
+
|
|
66
|
+
3. If postinstall failed during `npm install`, re-install the package:
|
|
67
|
+
npm install -g mcp-server-markview
|
|
68
|
+
|
|
69
|
+
EOF
|
|
70
|
+
exit 1
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-server-markview",
|
|
3
|
+
"version": "1.1.3",
|
|
4
|
+
"description": "MCP server for MarkView — preview Markdown files in a native macOS viewer",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Paul Kang <contact@paulkang.dev>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/paulhkang94/markview.git",
|
|
10
|
+
"directory": "npm"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/paulhkang94/markview#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/paulhkang94/markview/issues"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"mcp-server-markview": "bin/mcp-server-markview"
|
|
18
|
+
},
|
|
19
|
+
"mcpName": "io.github.paulhkang94/markview",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"postinstall": "node scripts/postinstall.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin/",
|
|
25
|
+
"scripts/",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"mcp-server",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"markdown",
|
|
33
|
+
"markview",
|
|
34
|
+
"preview",
|
|
35
|
+
"macos",
|
|
36
|
+
"claude"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"os": [
|
|
42
|
+
"darwin"
|
|
43
|
+
],
|
|
44
|
+
"cpu": [
|
|
45
|
+
"arm64",
|
|
46
|
+
"x64"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall.js
|
|
4
|
+
*
|
|
5
|
+
* Downloads the MarkView MCP server binary from GitHub Releases and places it
|
|
6
|
+
* at ./bin/markview-mcp-server-binary so the shell wrapper can find it.
|
|
7
|
+
*
|
|
8
|
+
* Uses only Node.js built-ins — zero runtime dependencies.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
const https = require("https");
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const os = require("os");
|
|
17
|
+
const { execFileSync } = require("child_process");
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Configuration
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const GITHUB_OWNER = "paulhkang94";
|
|
24
|
+
const GITHUB_REPO = "markview";
|
|
25
|
+
const VERSION = "1.1.3";
|
|
26
|
+
const ARCHIVE_NAME = `MarkView-${VERSION}.tar.gz`;
|
|
27
|
+
const DOWNLOAD_URL = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/v${VERSION}/${ARCHIVE_NAME}`;
|
|
28
|
+
|
|
29
|
+
// Path inside the tar.gz where the MCP server binary lives
|
|
30
|
+
const BINARY_IN_ARCHIVE = `MarkView.app/Contents/MacOS/markview-mcp-server`;
|
|
31
|
+
|
|
32
|
+
// Destination: placed next to the shell wrapper in bin/
|
|
33
|
+
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
34
|
+
const DEST_BINARY = path.join(PKG_ROOT, "bin", "markview-mcp-server-binary");
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Platform guard
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
if (process.platform !== "darwin") {
|
|
41
|
+
console.error(
|
|
42
|
+
"[mcp-server-markview] MarkView is a macOS-only application. " +
|
|
43
|
+
"This package is not supported on " +
|
|
44
|
+
process.platform +
|
|
45
|
+
".",
|
|
46
|
+
);
|
|
47
|
+
// Exit 0 so npm install does not fail on non-macOS CI environments.
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Helpers
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Follow HTTP redirects and return a Promise that resolves with the final
|
|
57
|
+
* IncomingMessage once we land on a non-redirect response.
|
|
58
|
+
*/
|
|
59
|
+
function followRedirects(url, maxRedirects) {
|
|
60
|
+
maxRedirects = maxRedirects === undefined ? 10 : maxRedirects;
|
|
61
|
+
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
if (maxRedirects === 0) {
|
|
64
|
+
return reject(new Error("Too many redirects"));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
https
|
|
68
|
+
.get(
|
|
69
|
+
url,
|
|
70
|
+
{ headers: { "User-Agent": "mcp-server-markview-postinstall" } },
|
|
71
|
+
(res) => {
|
|
72
|
+
if (
|
|
73
|
+
res.statusCode >= 300 &&
|
|
74
|
+
res.statusCode < 400 &&
|
|
75
|
+
res.headers.location
|
|
76
|
+
) {
|
|
77
|
+
res.resume(); // drain the response body so the socket is freed
|
|
78
|
+
resolve(followRedirects(res.headers.location, maxRedirects - 1));
|
|
79
|
+
} else {
|
|
80
|
+
resolve(res);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
)
|
|
84
|
+
.on("error", reject);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Download a URL to a local file. Returns a Promise.
|
|
90
|
+
*/
|
|
91
|
+
function downloadFile(url, destPath) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
followRedirects(url)
|
|
94
|
+
.then((res) => {
|
|
95
|
+
if (res.statusCode !== 200) {
|
|
96
|
+
res.resume();
|
|
97
|
+
return reject(new Error(`HTTP ${res.statusCode} downloading ${url}`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const total = parseInt(res.headers["content-length"] || "0", 10);
|
|
101
|
+
let received = 0;
|
|
102
|
+
let lastPct = -1;
|
|
103
|
+
|
|
104
|
+
const out = fs.createWriteStream(destPath);
|
|
105
|
+
|
|
106
|
+
res.on("data", (chunk) => {
|
|
107
|
+
received += chunk.length;
|
|
108
|
+
if (total > 0) {
|
|
109
|
+
const pct = Math.floor((received / total) * 100);
|
|
110
|
+
if (pct !== lastPct && pct % 10 === 0) {
|
|
111
|
+
process.stdout.write(`\r ${pct}%`);
|
|
112
|
+
lastPct = pct;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
res.pipe(out);
|
|
118
|
+
|
|
119
|
+
out.on("finish", () => {
|
|
120
|
+
process.stdout.write("\r \r"); // clear progress line
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
out.on("error", reject);
|
|
125
|
+
res.on("error", reject);
|
|
126
|
+
})
|
|
127
|
+
.catch(reject);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Main
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
async function main() {
|
|
136
|
+
// Skip if the binary is already present (e.g. re-running postinstall).
|
|
137
|
+
if (fs.existsSync(DEST_BINARY)) {
|
|
138
|
+
console.log(
|
|
139
|
+
"[mcp-server-markview] Binary already present, skipping download.",
|
|
140
|
+
);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Ensure the bin/ directory exists (it should, since it ships with the package).
|
|
145
|
+
fs.mkdirSync(path.dirname(DEST_BINARY), { recursive: true });
|
|
146
|
+
|
|
147
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "mcp-server-markview-"));
|
|
148
|
+
const archivePath = path.join(tmpDir, ARCHIVE_NAME);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
console.log(`[mcp-server-markview] Downloading MarkView v${VERSION}...`);
|
|
152
|
+
console.log(` ${DOWNLOAD_URL}`);
|
|
153
|
+
|
|
154
|
+
await downloadFile(DOWNLOAD_URL, archivePath);
|
|
155
|
+
|
|
156
|
+
console.log("[mcp-server-markview] Extracting MCP server binary...");
|
|
157
|
+
|
|
158
|
+
// Extract only the MCP server binary from the archive.
|
|
159
|
+
// Path is MarkView.app/Contents/MacOS/markview-mcp-server (3 dirs deep).
|
|
160
|
+
execFileSync(
|
|
161
|
+
"tar",
|
|
162
|
+
[
|
|
163
|
+
"-xzf",
|
|
164
|
+
archivePath,
|
|
165
|
+
"--strip-components=3",
|
|
166
|
+
"-C",
|
|
167
|
+
path.dirname(DEST_BINARY),
|
|
168
|
+
BINARY_IN_ARCHIVE,
|
|
169
|
+
],
|
|
170
|
+
{ stdio: "pipe" },
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// The extracted file will be named "markview-mcp-server"; rename if needed.
|
|
174
|
+
const extractedName = path.join(
|
|
175
|
+
path.dirname(DEST_BINARY),
|
|
176
|
+
"markview-mcp-server",
|
|
177
|
+
);
|
|
178
|
+
if (fs.existsSync(extractedName) && extractedName !== DEST_BINARY) {
|
|
179
|
+
fs.renameSync(extractedName, DEST_BINARY);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Make executable.
|
|
183
|
+
fs.chmodSync(DEST_BINARY, 0o755);
|
|
184
|
+
|
|
185
|
+
console.log("[mcp-server-markview] Binary installed successfully.");
|
|
186
|
+
console.log(` Location: ${DEST_BINARY}`);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error("[mcp-server-markview] Installation failed:", err.message);
|
|
189
|
+
console.error(
|
|
190
|
+
"\nYou can still use MarkView's MCP server if MarkView.app is installed at /Applications.\n" +
|
|
191
|
+
"Run `npx mcp-server-markview` and the wrapper will fall back to the app bundle automatically.",
|
|
192
|
+
);
|
|
193
|
+
// Exit 0 — a postinstall failure should not block npm install entirely.
|
|
194
|
+
process.exit(0);
|
|
195
|
+
} finally {
|
|
196
|
+
// Clean up temp directory.
|
|
197
|
+
try {
|
|
198
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
199
|
+
} catch (_) {
|
|
200
|
+
// Ignore cleanup errors.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
main();
|