@tiongcreative/assistant-mcp 0.1.1 → 0.2.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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0] - 2026-10-19
4
+
5
+ ### Added
6
+
7
+ - `npx @tiongcreative/assistant-mcp install` sets the assistant up in Claude Desktop: adds one entry to its config file with the full path to npx (a desktop app cannot see the shell's PATH), keeps a backup, refuses to touch a file it cannot parse, and prints the next steps. `uninstall` removes the entry; `status` reports it. `--allow-fresh` passes npm's release-age override into the entry for Macs that refuse packages younger than a few days. Unit tests cover create, merge, idempotence, corrupt files and removal.
8
+
9
+ ### Changed
10
+
11
+ - README setup is now the one-command path.
12
+
3
13
  ## [0.1.1] - 2026-10-19
4
14
 
5
15
  ### Changed
package/README.md CHANGED
@@ -21,23 +21,24 @@ More apps follow, and the assistant can also open an app for you, but only after
21
21
 
22
22
  ## Setup
23
23
 
24
- You need Node.js 20 or later and Claude Desktop. Add this to Claude Desktop's config file
25
- (Settings → Developer → Edit Config), then restart Claude Desktop:
26
-
27
- ```json
28
- {
29
- "mcpServers": {
30
- "tiongcreative": {
31
- "command": "npx",
32
- "args": ["-y", "@tiongcreative/assistant-mcp"]
33
- }
34
- }
35
- }
24
+ You need Node.js and Claude Desktop. If you do not have Node.js, install it from
25
+ nodejs.org (the LTS download), which also gives you the `npx` command used below.
26
+
27
+ Open Terminal and run:
28
+
29
+ ```
30
+ npx @tiongcreative/assistant-mcp install
36
31
  ```
37
32
 
38
- In each app, turn on **Allow assistant access** in Settings. Open the app, then ask Claude
39
- what's on. The assistant only offers tools for the apps installed on your Mac, checked when it
40
- starts, so after installing another app restart Claude Desktop once.
33
+ It adds the assistant to Claude Desktop's settings and keeps a backup of the previous file.
34
+ Then quit Claude Desktop completely, open it again, and allow the "tiongcreative" server the
35
+ first time it asks. In each app, turn on **Allow assistant access** in Settings. With the apps
36
+ open, ask Claude what's on this week.
37
+
38
+ The assistant only offers tools for the apps installed on your Mac, checked when Claude
39
+ Desktop starts it, so after installing another app restart Claude Desktop once.
40
+
41
+ To remove it: `npx @tiongcreative/assistant-mcp uninstall`. To check: `npx @tiongcreative/assistant-mcp status`.
41
42
 
42
43
  For Claude Code: `claude mcp add tiongcreative -- npx -y @tiongcreative/assistant-mcp`.
43
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiongcreative/assistant-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server that lets an AI assistant on your Mac use your Tiong Creative apps while they are open.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,7 +34,7 @@
34
34
  "prepare": "husky",
35
35
  "validate": "npm run lint && npm run format:check",
36
36
  "quality": "npm run validate && npm run sast && npm run security && npm run secrets && npm run licenses && npm run deps:check && npm run review",
37
- "test:unit": "node --test tests/dates.test.js",
37
+ "test:unit": "node --test tests/dates.test.js tests/install.test.js",
38
38
  "test:integration": "node --test tests/due-again.test.js tests/gift-keeper.test.js tests/client-manager.test.js tests/lists-and-notes.test.js"
39
39
  },
40
40
  "license": "MIT",
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
11
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12
12
  import { z } from 'zod';
13
13
  import { ApiError, isInstalled } from './connection.js';
14
+ import { runCli } from './install.js';
14
15
  import * as dueAgain from './apps/due-again.js';
15
16
  import * as giftKeeper from './apps/gift-keeper.js';
16
17
  import * as clientManager from './apps/client-manager.js';
@@ -18,6 +19,12 @@ import * as listsAndNotes from './apps/lists-and-notes.js';
18
19
 
19
20
  const { version } = createRequire(import.meta.url)('../package.json');
20
21
 
22
+ // `npx @tiongcreative/assistant-mcp install|uninstall|status` is the member's setup path.
23
+ // With no command, this process is the MCP server Claude Desktop launches.
24
+ if (process.argv.length > 2) {
25
+ process.exit(runCli(process.argv.slice(2)));
26
+ }
27
+
21
28
  // One entry per app. Add an app by adding a module with the same shape.
22
29
  const ALL_APPS = [dueAgain, giftKeeper, clientManager, listsAndNotes];
23
30
  // Only apps present on this Mac get tools, so the assistant never points the user at an
package/src/install.js ADDED
@@ -0,0 +1,156 @@
1
+ // `npx @tiongcreative/assistant-mcp install`: sets the assistant up in Claude Desktop so a
2
+ // member never edits JSON by hand. It adds one entry to Claude Desktop's config file (and
3
+ // nothing else), keeps a backup, and records the full path to npx, because a desktop app
4
+ // does not see the shell's PATH and would otherwise fail to find it.
5
+ //
6
+ // `uninstall` removes the entry. `status` says whether it is set up.
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+
11
+ export const SERVER_KEY = 'tiongcreative';
12
+ const PACKAGE = '@tiongcreative/assistant-mcp';
13
+
14
+ /** Claude Desktop's config file on this Mac (TC_CLAUDE_CONFIG overrides it, for tests). */
15
+ export function configPath() {
16
+ if (process.env.TC_CLAUDE_CONFIG) return process.env.TC_CLAUDE_CONFIG;
17
+ if (process.platform !== 'darwin') {
18
+ throw new Error('The Tiong Creative apps run on macOS, so this installer does too.');
19
+ }
20
+ return path.join(
21
+ os.homedir(),
22
+ 'Library',
23
+ 'Application Support',
24
+ 'Claude',
25
+ 'claude_desktop_config.json',
26
+ );
27
+ }
28
+
29
+ /** The npx beside the node that is running this command: the one Claude Desktop will use. */
30
+ export function npxPath() {
31
+ const candidate = path.join(path.dirname(process.execPath), 'npx');
32
+ if (fs.existsSync(candidate)) return candidate;
33
+ throw new Error(`Could not find npx next to node at ${process.execPath}.`);
34
+ }
35
+
36
+ function readConfig(file) {
37
+ if (!fs.existsSync(file)) return { config: {}, existed: false };
38
+ const raw = fs.readFileSync(file, 'utf8');
39
+ if (!raw.trim()) return { config: {}, existed: true };
40
+ try {
41
+ const config = JSON.parse(raw);
42
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
43
+ throw new Error('not an object');
44
+ }
45
+ return { config, existed: true };
46
+ } catch (err) {
47
+ throw new Error(
48
+ `Claude Desktop's config file could not be read as JSON (${err.message}), so nothing was changed. File: ${file}`,
49
+ );
50
+ }
51
+ }
52
+
53
+ function writeConfig(file, config, existed) {
54
+ fs.mkdirSync(path.dirname(file), { recursive: true });
55
+ let backup = null;
56
+ if (existed) {
57
+ backup = `${file}.backup-${new Date().toISOString().replace(/[:.]/g, '-')}`;
58
+ fs.copyFileSync(file, backup);
59
+ }
60
+ const tmp = `${file}.tmp`;
61
+ fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n');
62
+ fs.renameSync(tmp, file);
63
+ return backup;
64
+ }
65
+
66
+ export function install({ env } = {}) {
67
+ const file = configPath();
68
+ const { config, existed } = readConfig(file);
69
+ const entry = { command: npxPath(), args: ['-y', PACKAGE] };
70
+ if (env && Object.keys(env).length) entry.env = env;
71
+ const servers =
72
+ config.mcpServers && typeof config.mcpServers === 'object' ? config.mcpServers : {};
73
+ const already = JSON.stringify(servers[SERVER_KEY]) === JSON.stringify(entry);
74
+ config.mcpServers = { ...servers, [SERVER_KEY]: entry };
75
+ const backup = already ? null : writeConfig(file, config, existed);
76
+ return { file, backup, already, entry };
77
+ }
78
+
79
+ export function uninstall() {
80
+ const file = configPath();
81
+ const { config, existed } = readConfig(file);
82
+ if (!existed || !config.mcpServers || !(SERVER_KEY in config.mcpServers)) {
83
+ return { file, removed: false };
84
+ }
85
+ const rest = { ...config.mcpServers };
86
+ delete rest[SERVER_KEY];
87
+ config.mcpServers = rest;
88
+ const backup = writeConfig(file, config, true);
89
+ return { file, removed: true, backup };
90
+ }
91
+
92
+ export function status() {
93
+ const file = configPath();
94
+ try {
95
+ const { config, existed } = readConfig(file);
96
+ const entry = existed ? config.mcpServers?.[SERVER_KEY] : undefined;
97
+ return { file, installed: !!entry, entry: entry ?? null };
98
+ } catch (err) {
99
+ return { file, installed: false, error: err.message };
100
+ }
101
+ }
102
+
103
+ const NEXT_STEPS = [
104
+ '1. Quit Claude Desktop completely and open it again.',
105
+ '2. The first time you chat, allow the "tiongcreative" server when Claude Desktop asks.',
106
+ '3. In each Tiong Creative app, open Settings and turn on "Allow assistant access".',
107
+ '4. With the apps open, ask Claude what is on this week.',
108
+ ];
109
+
110
+ /** Runs the command and returns the process exit code. Prints with the given writer. */
111
+ export function runCli(args, print = console.log) {
112
+ const [command] = args;
113
+ try {
114
+ if (command === 'install') {
115
+ const env = {};
116
+ // Developer convenience: `install --allow-fresh` passes npm's release-age override
117
+ // through to the server entry, for Macs that refuse packages younger than a few days.
118
+ if (args.includes('--allow-fresh')) env.npm_config_min_release_age = '0';
119
+ const r = install({ env });
120
+ print(
121
+ r.already
122
+ ? `The Tiong Creative assistant is already set up in Claude Desktop (${r.file}).`
123
+ : `Added the Tiong Creative assistant to Claude Desktop.\n Config: ${r.file}${r.backup ? `\n Backup of the previous file: ${r.backup}` : ''}`,
124
+ );
125
+ print('\nNext:\n' + NEXT_STEPS.join('\n'));
126
+ return 0;
127
+ }
128
+ if (command === 'uninstall') {
129
+ const r = uninstall();
130
+ print(
131
+ r.removed
132
+ ? `Removed the Tiong Creative assistant from Claude Desktop (${r.file}). Restart Claude Desktop to finish.`
133
+ : `The Tiong Creative assistant was not set up in Claude Desktop (${r.file}), so nothing changed.`,
134
+ );
135
+ return 0;
136
+ }
137
+ if (command === 'status') {
138
+ const r = status();
139
+ if (r.error) print(r.error);
140
+ else
141
+ print(
142
+ r.installed
143
+ ? `Set up in Claude Desktop: ${r.file}`
144
+ : `Not set up in Claude Desktop (${r.file}).`,
145
+ );
146
+ return 0;
147
+ }
148
+ print(
149
+ `Usage:\n npx ${PACKAGE} install add the assistant to Claude Desktop\n npx ${PACKAGE} uninstall remove it\n npx ${PACKAGE} status say whether it is set up\n\nWith no command, this runs the MCP server itself (Claude Desktop does that for you).`,
150
+ );
151
+ return command === 'help' || command === '--help' ? 0 : 2;
152
+ } catch (err) {
153
+ print(`Could not finish: ${err.message}`);
154
+ return 1;
155
+ }
156
+ }