@freeflow-im/mcp-docs 0.2.24 → 0.2.26
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 +4 -4
- package/cli.js +80 -19
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @freeflow-im/mcp-docs
|
|
2
2
|
|
|
3
|
-
Installs **MCP Docs**, a Mac app for Word, PowerPoint and Excel files, Markdown, CSV, PDFs and pictures
|
|
4
|
-
folder on your
|
|
3
|
+
Installs **MCP Docs**, a Mac and Windows app for Word, PowerPoint and Excel files, Markdown, CSV, PDFs and pictures
|
|
4
|
+
in a folder on your computer, and the **mcp-docs** plugin for [Claude Code](https://claude.com/claude-code) and
|
|
5
5
|
[Codex](https://developers.openai.com/codex), which lets your coding agent open and edit those files while you watch
|
|
6
6
|
them change in the app. Any other MCP client can connect to the app too.
|
|
7
7
|
|
|
@@ -12,7 +12,7 @@ npx @freeflow-im/mcp-docs
|
|
|
12
12
|
This:
|
|
13
13
|
|
|
14
14
|
1. Downloads the latest MCP Docs release from GitHub and puts it in `/Applications` (or `~/Applications`),
|
|
15
|
-
replacing an older version.
|
|
15
|
+
replacing an older version. On Windows it runs the release's setup program, which installs it for you.
|
|
16
16
|
2. For each of Claude Code and Codex it finds, asks whether to add the `mcp-docs` plugin, and if so adds the
|
|
17
17
|
`mcp-docs` plugin marketplace and installs the plugin: the app's MCP server and a skill describing how to use it.
|
|
18
18
|
3. Starts the app, and prints its MCP address, `http://127.0.0.1:39217/mcp`.
|
|
@@ -39,4 +39,4 @@ and its marketplace). It lists what it
|
|
|
39
39
|
will remove and asks first (`--yes` skips the question). Your documents, in the folders you opened, are not
|
|
40
40
|
touched.
|
|
41
41
|
|
|
42
|
-
Requires a Mac with Apple silicon and Node.js 18 or later.
|
|
42
|
+
Requires a Mac with Apple silicon or Windows (x64), and Node.js 18 or later.
|
package/cli.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// .claude-plugin/marketplace.json there) and a Codex plugin (.agents/plugins/marketplace.json). Any other MCP client
|
|
6
6
|
// can connect to the app's address itself. With --uninstall, remove all of it again: the app, its data and settings,
|
|
7
7
|
// and what it added to the agents.
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
9
|
-
import { accessSync, constants, createWriteStream, existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
|
|
8
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
9
|
+
import { accessSync, constants, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
10
10
|
import { homedir, tmpdir } from 'node:os';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { createInterface } from 'node:readline/promises';
|
|
@@ -15,7 +15,10 @@ import { pipeline } from 'node:stream/promises';
|
|
|
15
15
|
|
|
16
16
|
const REPO = 'freeflow-im/mcp-docs';
|
|
17
17
|
const APP_NAME = 'MCP Docs';
|
|
18
|
-
|
|
18
|
+
/** Windows (x64, which Windows on Arm runs too) or a Mac with Apple silicon. */
|
|
19
|
+
const WIN = process.platform === 'win32';
|
|
20
|
+
const ASSET = WIN ? /^MCP-Docs-(\d+\.\d+\.\d+)-win-x64-setup\.exe$/ : /^MCP-Docs-(\d+\.\d+\.\d+)-mac-arm64\.zip$/;
|
|
21
|
+
const EXE = `${APP_NAME}.exe`;
|
|
19
22
|
const MARKETPLACE = 'mcp-docs';
|
|
20
23
|
const PLUGIN = `mcp-docs@${MARKETPLACE}`;
|
|
21
24
|
/** The app's MCP server (desktop/main.js's fixed port), for any MCP client. */
|
|
@@ -28,7 +31,8 @@ if (flag('help') || flag('h') || args.some((a) => !a.startsWith('--'))) {
|
|
|
28
31
|
console.log(`Usage: npx @freeflow-im/mcp-docs [--claude] [--codex] [--skip-app] [--skip-plugins] [--yes]
|
|
29
32
|
npx @freeflow-im/mcp-docs --uninstall [--yes]
|
|
30
33
|
|
|
31
|
-
Installs the latest MCP Docs desktop app into /Applications
|
|
34
|
+
Installs the latest MCP Docs desktop app (on a Mac into /Applications or ~/Applications, on Windows into
|
|
35
|
+
%LOCALAPPDATA%\\Programs\\MCP Docs), and the mcp-docs plugin
|
|
32
36
|
into the coding agents you choose, Claude Code and Codex, which connects them to the app. It asks for each
|
|
33
37
|
agent it finds. Run it again to update them all. Any other MCP client can connect to the app at
|
|
34
38
|
${MCP_URL}.
|
|
@@ -51,6 +55,12 @@ const fail = (msg) => {
|
|
|
51
55
|
};
|
|
52
56
|
/** Run a program; its output is returned, not shown. */
|
|
53
57
|
const run = (cmd, argv) => spawnSync(cmd, argv, { encoding: 'utf8' });
|
|
58
|
+
/** Run a command found on the PATH, as a terminal would: on Windows the agents' commands are often `.cmd` scripts,
|
|
59
|
+
* which only a shell runs (the arguments here have no spaces or quotes). */
|
|
60
|
+
const runCommand = (cmd, argv) => (WIN ? spawnSync([cmd, ...argv].join(' '), { encoding: 'utf8', shell: true }) : run(cmd, argv));
|
|
61
|
+
|
|
62
|
+
/** Where Windows installs the app: for the person (the installer's default), or for everyone. */
|
|
63
|
+
const winAppDirs = () => [path.join(process.env.LOCALAPPDATA ?? path.join(homedir(), 'AppData', 'Local'), 'Programs', APP_NAME), path.join(process.env.ProgramFiles ?? 'C:\\Program Files', APP_NAME)];
|
|
54
64
|
|
|
55
65
|
/** Where the app goes: /Applications when it can be written to, else the person's own Applications folder. */
|
|
56
66
|
function appsDir() {
|
|
@@ -64,8 +74,22 @@ function appsDir() {
|
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
|
|
67
|
-
/** The app already installed, in either place, and its version. */
|
|
77
|
+
/** The app already installed, in either place, and its version. On Windows `app` is its program. */
|
|
68
78
|
function installedApp() {
|
|
79
|
+
if (WIN) {
|
|
80
|
+
for (const dir of winAppDirs()) {
|
|
81
|
+
const app = path.join(dir, EXE);
|
|
82
|
+
if (!existsSync(app)) continue;
|
|
83
|
+
let version = null;
|
|
84
|
+
try {
|
|
85
|
+
version = JSON.parse(readFileSync(path.join(dir, 'resources', 'app', 'package.json'), 'utf8')).version ?? null;
|
|
86
|
+
} catch {
|
|
87
|
+
// Unreadable: updated below as an older version.
|
|
88
|
+
}
|
|
89
|
+
return { app, version };
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
69
93
|
for (const dir of ['/Applications', path.join(homedir(), 'Applications')]) {
|
|
70
94
|
const app = path.join(dir, `${APP_NAME}.app`);
|
|
71
95
|
if (!existsSync(app)) continue;
|
|
@@ -86,10 +110,15 @@ async function latestRelease() {
|
|
|
86
110
|
if (m) return { version: m[1], url: asset.browser_download_url, name: asset.name, size: asset.size };
|
|
87
111
|
}
|
|
88
112
|
}
|
|
89
|
-
fail(`No release of ${APP_NAME} for macOS was found on GitHub.`);
|
|
113
|
+
fail(`No release of ${APP_NAME} for ${WIN ? 'Windows' : 'macOS'} was found on GitHub.`);
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
const appPids = (app) => {
|
|
117
|
+
if (WIN) {
|
|
118
|
+
// Every MCP Docs.exe, the app and the servers it starts for its folders (one installed copy is assumed).
|
|
119
|
+
const r = run('tasklist', ['/FI', `IMAGENAME eq ${EXE}`, '/FO', 'CSV', '/NH']);
|
|
120
|
+
return r.status === 0 ? [...r.stdout.matchAll(/^"[^"]+","(\d+)"/gm)].map((m) => m[1]) : [];
|
|
121
|
+
}
|
|
93
122
|
const r = run('pgrep', ['-f', `${app}/Contents/MacOS/`]);
|
|
94
123
|
return r.status === 0 ? r.stdout.trim().split('\n').filter(Boolean) : [];
|
|
95
124
|
};
|
|
@@ -98,7 +127,9 @@ const appPids = (app) => {
|
|
|
98
127
|
async function quitApp(app, why = 'to update it') {
|
|
99
128
|
if (!appPids(app).length) return false;
|
|
100
129
|
say(` Quitting ${APP_NAME} ${why}…`);
|
|
101
|
-
|
|
130
|
+
// On Windows its windows are asked to close, and it quits (and stops its servers) when the last one has.
|
|
131
|
+
if (WIN) run('taskkill', ['/IM', EXE]);
|
|
132
|
+
else run('osascript', ['-e', `tell application "${app}" to quit`]);
|
|
102
133
|
for (let i = 0; i < 30 && appPids(app).length; i++) await new Promise((r) => setTimeout(r, 500));
|
|
103
134
|
if (appPids(app).length) fail(`${APP_NAME} is still running. Quit it and run this again.`);
|
|
104
135
|
return true;
|
|
@@ -120,6 +151,7 @@ async function installApp() {
|
|
|
120
151
|
const res = await fetch(latest.url);
|
|
121
152
|
if (!res.ok || !res.body) fail(`The download failed (${res.status} ${res.statusText}).`);
|
|
122
153
|
await pipeline(Readable.fromWeb(res.body), createWriteStream(zip));
|
|
154
|
+
if (WIN) return await runWindowsSetup(zip, latest.version, current);
|
|
123
155
|
const out = path.join(work, 'app');
|
|
124
156
|
if (run('ditto', ['-x', '-k', zip, out]).status !== 0) fail('The download could not be unpacked.');
|
|
125
157
|
const unpacked = path.join(out, `${APP_NAME}.app`);
|
|
@@ -141,15 +173,31 @@ async function installApp() {
|
|
|
141
173
|
}
|
|
142
174
|
}
|
|
143
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Install the downloaded Windows setup program without its questions: for the person, or over the copy installed for
|
|
178
|
+
* everyone (which asks Windows for permission). It closes a running app itself; returns the installed program.
|
|
179
|
+
*/
|
|
180
|
+
async function runWindowsSetup(setup, version, current) {
|
|
181
|
+
const forAll = current && path.dirname(current.app) === winAppDirs()[1];
|
|
182
|
+
const wasRunning = current ? await quitApp(current.app) : false;
|
|
183
|
+
say(' Running the setup program…');
|
|
184
|
+
const r = run(setup, ['/S', ...(forAll ? ['/allusers'] : [])]);
|
|
185
|
+
const app = installedApp();
|
|
186
|
+
if (r.status !== 0 || app?.version !== version) fail(`The setup program did not install ${APP_NAME} ${version}${r.status ? ` (exit code ${r.status})` : ''}. Run ${path.basename(setup)} from the release yourself: github.com/${REPO}/releases/latest`);
|
|
187
|
+
say(` Installed ${APP_NAME} ${version} in ${path.dirname(app.app)}.`);
|
|
188
|
+
if (wasRunning) say(' It was running: it starts again below.');
|
|
189
|
+
return app.app;
|
|
190
|
+
}
|
|
191
|
+
|
|
144
192
|
/** Run `claude` with these arguments, showing what it says when it fails. */
|
|
145
193
|
function claude(argv) {
|
|
146
|
-
const r =
|
|
194
|
+
const r = runCommand('claude', argv);
|
|
147
195
|
return { ok: r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}`.trim() };
|
|
148
196
|
}
|
|
149
197
|
|
|
150
198
|
/** Run `codex` with these arguments, as `claude` above. */
|
|
151
199
|
function codex(argv) {
|
|
152
|
-
const r =
|
|
200
|
+
const r = runCommand('codex', argv);
|
|
153
201
|
return { ok: r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}`.trim() };
|
|
154
202
|
}
|
|
155
203
|
|
|
@@ -161,7 +209,7 @@ const AGENTS = [
|
|
|
161
209
|
{
|
|
162
210
|
key: 'claude',
|
|
163
211
|
name: 'Claude Code',
|
|
164
|
-
found: () =>
|
|
212
|
+
found: () => runCommand('claude', ['--version']).status === 0,
|
|
165
213
|
install: installClaudePlugin,
|
|
166
214
|
next: {
|
|
167
215
|
installed: 'In Claude Code: restart it (or start a new session) to load the plugin, then run /mcp, choose plugin:mcp-docs:mcp-docs and sign in.',
|
|
@@ -171,7 +219,7 @@ const AGENTS = [
|
|
|
171
219
|
{
|
|
172
220
|
key: 'codex',
|
|
173
221
|
name: 'Codex',
|
|
174
|
-
found: () =>
|
|
222
|
+
found: () => runCommand('codex', ['--version']).status === 0,
|
|
175
223
|
install: installCodexPlugin,
|
|
176
224
|
next: {
|
|
177
225
|
installed: 'In Codex: sign in once with `codex mcp login mcp-docs`, then start a new session.',
|
|
@@ -278,6 +326,10 @@ function installCodexPlugin() {
|
|
|
278
326
|
* tokens, each folder's index, deleted files), under each name it has had, and macOS's files for it.
|
|
279
327
|
*/
|
|
280
328
|
function appData() {
|
|
329
|
+
if (WIN) {
|
|
330
|
+
const roaming = process.env.APPDATA ?? path.join(homedir(), 'AppData', 'Roaming');
|
|
331
|
+
return ['MCP Docs', 'Universal Docs', 'FreeFlow Docs'].map((name) => path.join(roaming, name)).filter((p) => existsSync(p));
|
|
332
|
+
}
|
|
281
333
|
const lib = path.join(homedir(), 'Library');
|
|
282
334
|
const BUNDLE_ID = 'im.freeflow.docs';
|
|
283
335
|
return [
|
|
@@ -290,12 +342,13 @@ function appData() {
|
|
|
290
342
|
].filter((p) => existsSync(p));
|
|
291
343
|
}
|
|
292
344
|
|
|
293
|
-
/** The apps installed, in either place. */
|
|
294
|
-
const installedApps = () =>
|
|
345
|
+
/** The apps installed, in either place (on Windows, their programs). */
|
|
346
|
+
const installedApps = () =>
|
|
347
|
+
(WIN ? winAppDirs().map((dir) => path.join(dir, EXE)) : ['/Applications', path.join(homedir(), 'Applications')].map((dir) => path.join(dir, `${APP_NAME}.app`))).filter((p) => existsSync(p));
|
|
295
348
|
|
|
296
349
|
/** What it added to Claude Code: the plugin, its marketplace, and an MCP server added by hand (by either name it has had). */
|
|
297
350
|
function claudeThings() {
|
|
298
|
-
if (
|
|
351
|
+
if (runCommand('claude', ['--version']).status !== 0) return { plugin: false, marketplace: false, servers: [] };
|
|
299
352
|
let plugin = false;
|
|
300
353
|
let marketplace = false;
|
|
301
354
|
try {
|
|
@@ -315,7 +368,7 @@ function claudeThings() {
|
|
|
315
368
|
|
|
316
369
|
/** What it added to Codex: the plugin and its marketplace. */
|
|
317
370
|
function codexThings() {
|
|
318
|
-
if (
|
|
371
|
+
if (runCommand('codex', ['--version']).status !== 0) return { plugin: false, marketplace: false };
|
|
319
372
|
let plugin = false;
|
|
320
373
|
let marketplace = false;
|
|
321
374
|
try {
|
|
@@ -363,6 +416,13 @@ async function uninstall() {
|
|
|
363
416
|
|
|
364
417
|
for (const app of apps) {
|
|
365
418
|
await quitApp(app, 'to remove it');
|
|
419
|
+
if (WIN) {
|
|
420
|
+
// Its own uninstaller, which also takes it off Settings › Apps and the Start menu.
|
|
421
|
+
const dir = path.dirname(app);
|
|
422
|
+
const r = run(path.join(dir, `Uninstall ${APP_NAME}.exe`), ['/S', ...(dir === winAppDirs()[1] ? ['/allusers'] : [])]);
|
|
423
|
+
say(r.status === 0 ? ` Removed ${dir}` : ` Could not remove ${dir}: uninstall ${APP_NAME} in Settings › Apps instead.`);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
366
426
|
rmSync(app, { recursive: true, force: true });
|
|
367
427
|
say(` Removed ${app}`);
|
|
368
428
|
}
|
|
@@ -394,8 +454,8 @@ async function uninstall() {
|
|
|
394
454
|
say(`\n${APP_NAME} is uninstalled. Restart Claude Code and Codex to let go of the plugin.`);
|
|
395
455
|
}
|
|
396
456
|
|
|
397
|
-
if (process.platform !== 'darwin' || process.arch !== 'arm64') {
|
|
398
|
-
fail(`${APP_NAME} is only available for Macs with Apple silicon so far.`);
|
|
457
|
+
if (!WIN && (process.platform !== 'darwin' || process.arch !== 'arm64')) {
|
|
458
|
+
fail(`${APP_NAME} is only available for Windows and Macs with Apple silicon so far.`);
|
|
399
459
|
}
|
|
400
460
|
|
|
401
461
|
if (flag('uninstall')) {
|
|
@@ -412,10 +472,11 @@ const done = agents.map((agent) => ({ agent, step: agent.install() }));
|
|
|
412
472
|
|
|
413
473
|
if (app && !appPids(app).length) {
|
|
414
474
|
// The MCP server is the running app.
|
|
415
|
-
|
|
475
|
+
if (WIN) spawn(app, [], { detached: true, stdio: 'ignore' }).unref();
|
|
476
|
+
else run('open', ['-a', app]);
|
|
416
477
|
say(`\nStarted ${APP_NAME}.`);
|
|
417
478
|
}
|
|
418
479
|
say(`
|
|
419
|
-
While ${APP_NAME} is running, its MCP server is at ${MCP_URL}. Any MCP client on this
|
|
480
|
+
While ${APP_NAME} is running, its MCP server is at ${MCP_URL}. Any MCP client on this computer that
|
|
420
481
|
supports streamable HTTP and OAuth can connect to it there; ${APP_NAME} asks you to allow each one.`);
|
|
421
482
|
if (done.length) say(`\nNext:\n${done.map(({ agent, step }) => ` - ${agent.next[step]}`).join('\n')}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@freeflow-im/mcp-docs",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Install the MCP Docs desktop app and its plugin for Claude Code and Codex: office files on your Mac, edited by your coding agent or any MCP client.",
|
|
3
|
+
"version": "0.2.26",
|
|
4
|
+
"description": "Install the MCP Docs desktop app and its plugin for Claude Code and Codex: office files on your Mac or Windows PC, edited by your coding agent or any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcp-docs": "cli.js"
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"node": ">=18"
|
|
15
15
|
},
|
|
16
16
|
"os": [
|
|
17
|
-
"darwin"
|
|
17
|
+
"darwin",
|
|
18
|
+
"win32"
|
|
18
19
|
],
|
|
19
20
|
"keywords": [
|
|
20
21
|
"mcp",
|