@ainote/mcp 1.3.2 → 1.3.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/LICENSE +20 -20
- package/README-ko.md +374 -374
- package/README.md +800 -800
- package/index.js +61 -61
- package/lib/auth/device-flow.js +285 -274
- package/lib/auth/mcp-key.js +40 -40
- package/lib/auth/oauth.js +18 -18
- package/lib/auth/refresh.js +78 -78
- package/lib/auth/strategy-factory.js +31 -31
- package/lib/auth/token-store.js +302 -286
- package/lib/cli/call.js +211 -211
- package/lib/cli/login.js +238 -238
- package/lib/cli/logout.js +39 -39
- package/lib/cli/signup.js +118 -118
- package/lib/cli/whoami.js +77 -77
- package/lib/core/api-client.js +1 -1
- package/lib/core/config-manager.js +106 -106
- package/lib/core/config.js +1 -1
- package/lib/core/mcp-server.js +2 -2
- package/lib/core/server-factory.js +82 -82
- package/lib/core/tool-registry.js +31 -31
- package/lib/tools/chatgpt-tools.js +93 -93
- package/lib/tools/dev-doc-tools.js +643 -643
- package/lib/tools/index.js +5 -5
- package/lib/tools/internal/claude-scanner.js +150 -150
- package/lib/tools/internal/encryption.js +88 -88
- package/lib/tools/internal/formatters.js +80 -80
- package/lib/tools/internal/keychain.js +98 -98
- package/lib/tools/internal/path-mapper.js +221 -221
- package/lib/tools/onboarding-tools.js +124 -124
- package/lib/tools/shared-tools.js +280 -280
- package/lib/utils/api-client.js +152 -152
- package/package.json +101 -101
- package/server.js +99 -99
package/lib/cli/call.js
CHANGED
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `ainote-mcp call <tool> [--key=value ...] [--json '<obj>']`
|
|
3
|
-
*
|
|
4
|
-
* Direct shell invocation of an ainote MCP tool — no agent required. Useful for:
|
|
5
|
-
* - one-off CLI use, scripts, alfred/raycast workflows
|
|
6
|
-
* - debugging tool responses without spinning up Claude
|
|
7
|
-
* - automation that wants a single tool result, not an interactive loop
|
|
8
|
-
*
|
|
9
|
-
* Resolves the MCP key from (in order):
|
|
10
|
-
* 1. AINOTE_API_KEY env
|
|
11
|
-
* 2. token store (mcpKey written by `ainote-mcp login`)
|
|
12
|
-
*
|
|
13
|
-
* Posts JSON-RPC 2.0 `tools/call` to AINOTE_API_URL (default https://api.ainote.dev/api/mcp).
|
|
14
|
-
*
|
|
15
|
-
* Argument parsing supports two styles, in priority order:
|
|
16
|
-
* --json '{"due_today": true}' raw JSON object as arguments
|
|
17
|
-
* --key=value one flag per top-level field; values are
|
|
18
|
-
* JSON.parse-d (so `--limit=5` is 5, not "5";
|
|
19
|
-
* `--name=hello` falls back to the raw string
|
|
20
|
-
* if JSON.parse fails)
|
|
21
|
-
*
|
|
22
|
-
* Output:
|
|
23
|
-
* default — pretty JSON of the tool's result content
|
|
24
|
-
* --raw — entire JSON-RPC envelope
|
|
25
|
-
*/
|
|
26
|
-
import process from 'node:process';
|
|
27
|
-
import axios from 'axios';
|
|
28
|
-
import { getCredentials } from '../auth/token-store.js';
|
|
29
|
-
|
|
30
|
-
const DEFAULT_API_URL = 'https://api.ainote.dev';
|
|
31
|
-
|
|
32
|
-
function parseArgs(argv) {
|
|
33
|
-
// argv shape: [toolName, ...flags]
|
|
34
|
-
const toolName = argv[0];
|
|
35
|
-
const flags = argv.slice(1);
|
|
36
|
-
const out = { tool: toolName, args: {}, raw: false, help: false };
|
|
37
|
-
|
|
38
|
-
for (let i = 0; i < flags.length; i++) {
|
|
39
|
-
const a = flags[i];
|
|
40
|
-
if (a === '--raw') { out.raw = true; continue; }
|
|
41
|
-
if (a === '-h' || a === '--help') { out.help = true; continue; }
|
|
42
|
-
if (a === '--json') {
|
|
43
|
-
const next = flags[++i];
|
|
44
|
-
if (!next) throw new Error('--json requires a JSON object argument');
|
|
45
|
-
let parsed;
|
|
46
|
-
try { parsed = JSON.parse(next); }
|
|
47
|
-
catch (e) { throw new Error(`--json value is not valid JSON: ${e.message}`); }
|
|
48
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
49
|
-
throw new Error('--json value must be a JSON object');
|
|
50
|
-
}
|
|
51
|
-
out.args = { ...out.args, ...parsed };
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (a.startsWith('--')) {
|
|
55
|
-
const eq = a.indexOf('=');
|
|
56
|
-
let key, rawVal;
|
|
57
|
-
if (eq > 0) {
|
|
58
|
-
key = a.slice(2, eq);
|
|
59
|
-
rawVal = a.slice(eq + 1);
|
|
60
|
-
} else {
|
|
61
|
-
key = a.slice(2);
|
|
62
|
-
rawVal = flags[++i];
|
|
63
|
-
if (rawVal === undefined) {
|
|
64
|
-
out.args[key] = true;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Try JSON.parse first (numbers, booleans, null, arrays/objects),
|
|
69
|
-
// fall back to raw string.
|
|
70
|
-
let value;
|
|
71
|
-
try { value = JSON.parse(rawVal); }
|
|
72
|
-
catch { value = rawVal; }
|
|
73
|
-
out.args[key] = value;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
throw new Error(`unexpected positional argument: ${a}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return out;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function printHelp() {
|
|
83
|
-
const help = `\
|
|
84
|
-
Usage: ainote-mcp call <tool> [options]
|
|
85
|
-
|
|
86
|
-
Direct shell invocation of an ainote MCP tool over HTTP. Bypasses any
|
|
87
|
-
running MCP host (Claude Desktop, Cursor, etc.) and posts directly to
|
|
88
|
-
api.ainote.dev — useful for scripts and one-off queries.
|
|
89
|
-
|
|
90
|
-
Authentication:
|
|
91
|
-
- AINOTE_API_KEY env var, OR
|
|
92
|
-
- mcp key from \`ainote-mcp login\` (stored in OS keychain)
|
|
93
|
-
|
|
94
|
-
Options:
|
|
95
|
-
--key=value pass an argument to the tool (JSON.parse-d, falls
|
|
96
|
-
back to string). Repeatable.
|
|
97
|
-
--json '<obj>' pass the entire arguments object as JSON. Merged
|
|
98
|
-
with any --key=value flags (--key=value wins on
|
|
99
|
-
conflict).
|
|
100
|
-
--raw print the full JSON-RPC envelope instead of just
|
|
101
|
-
the result content.
|
|
102
|
-
-h, --help this help.
|
|
103
|
-
|
|
104
|
-
Examples:
|
|
105
|
-
ainote-mcp call list_tasks --due_today=true
|
|
106
|
-
ainote-mcp call list_tasks --limit=5 --status=pending
|
|
107
|
-
ainote-mcp call handoff_save \\
|
|
108
|
-
--json '{"project":"demo","topic":"poc","content":"..."}'
|
|
109
|
-
ainote-mcp call handoff_get --project=demo --topic=poc
|
|
110
|
-
ainote-mcp call list_dev_categories
|
|
111
|
-
`;
|
|
112
|
-
process.stdout.write(help);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export async function run(argv) {
|
|
116
|
-
// argv: everything after the `call` subcommand
|
|
117
|
-
if (!argv || argv.length === 0) {
|
|
118
|
-
printHelp();
|
|
119
|
-
process.exit(1);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
let parsed;
|
|
124
|
-
try { parsed = parseArgs(argv); }
|
|
125
|
-
catch (e) {
|
|
126
|
-
console.error(`error: ${e.message}\n`);
|
|
127
|
-
printHelp();
|
|
128
|
-
process.exit(2);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (parsed.help || !parsed.tool || parsed.tool.startsWith('-')) {
|
|
133
|
-
printHelp();
|
|
134
|
-
process.exit(parsed.help ? 0 : 1);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const apiUrl = (process.env.AINOTE_API_URL || DEFAULT_API_URL).replace(/\/+$/, '');
|
|
139
|
-
|
|
140
|
-
let mcpKey = process.env.AINOTE_API_KEY;
|
|
141
|
-
if (!mcpKey) {
|
|
142
|
-
const creds = await getCredentials(apiUrl);
|
|
143
|
-
mcpKey = creds?.mcpKey;
|
|
144
|
-
}
|
|
145
|
-
if (!mcpKey) {
|
|
146
|
-
console.error('error: no MCP key. Run `ainote-mcp login` or set AINOTE_API_KEY.');
|
|
147
|
-
process.exit(3);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const body = {
|
|
152
|
-
jsonrpc: '2.0',
|
|
153
|
-
id: 1,
|
|
154
|
-
method: 'tools/call',
|
|
155
|
-
params: { name: parsed.tool, arguments: parsed.args }
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
let response;
|
|
159
|
-
try {
|
|
160
|
-
response = await axios.post(`${apiUrl}/api/mcp`, body, {
|
|
161
|
-
headers: {
|
|
162
|
-
'Content-Type': 'application/json',
|
|
163
|
-
Authorization: `McpKey ${mcpKey}`,
|
|
164
|
-
Accept: 'application/json'
|
|
165
|
-
},
|
|
166
|
-
timeout: 60000,
|
|
167
|
-
validateStatus: () => true
|
|
168
|
-
});
|
|
169
|
-
} catch (e) {
|
|
170
|
-
console.error(`error: request failed: ${e.message}`);
|
|
171
|
-
process.exit(4);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (response.status >= 400 && response.status !== 200) {
|
|
176
|
-
console.error(`error: HTTP ${response.status}`);
|
|
177
|
-
console.error(JSON.stringify(response.data, null, 2));
|
|
178
|
-
process.exit(5);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const env = response.data || {};
|
|
183
|
-
if (env.error) {
|
|
184
|
-
console.error(`error: ${env.error.message || 'unknown'} (code ${env.error.code})`);
|
|
185
|
-
if (env.error.data) console.error(JSON.stringify(env.error.data, null, 2));
|
|
186
|
-
process.exit(6);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (parsed.raw) {
|
|
191
|
-
console.log(JSON.stringify(env, null, 2));
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Default: print just the result.content[] payload(s) so shell pipelines
|
|
196
|
-
// can grep / jq directly without the JSON-RPC envelope noise.
|
|
197
|
-
const content = env?.result?.content;
|
|
198
|
-
if (Array.isArray(content)) {
|
|
199
|
-
for (const block of content) {
|
|
200
|
-
if (block && typeof block === 'object' && 'text' in block) {
|
|
201
|
-
console.log(block.text);
|
|
202
|
-
} else {
|
|
203
|
-
console.log(JSON.stringify(block, null, 2));
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Fallback — some tools may emit a non-standard shape; dump result.
|
|
210
|
-
console.log(JSON.stringify(env.result ?? env, null, 2));
|
|
211
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `ainote-mcp call <tool> [--key=value ...] [--json '<obj>']`
|
|
3
|
+
*
|
|
4
|
+
* Direct shell invocation of an ainote MCP tool — no agent required. Useful for:
|
|
5
|
+
* - one-off CLI use, scripts, alfred/raycast workflows
|
|
6
|
+
* - debugging tool responses without spinning up Claude
|
|
7
|
+
* - automation that wants a single tool result, not an interactive loop
|
|
8
|
+
*
|
|
9
|
+
* Resolves the MCP key from (in order):
|
|
10
|
+
* 1. AINOTE_API_KEY env
|
|
11
|
+
* 2. token store (mcpKey written by `ainote-mcp login`)
|
|
12
|
+
*
|
|
13
|
+
* Posts JSON-RPC 2.0 `tools/call` to AINOTE_API_URL (default https://api.ainote.dev/api/mcp).
|
|
14
|
+
*
|
|
15
|
+
* Argument parsing supports two styles, in priority order:
|
|
16
|
+
* --json '{"due_today": true}' raw JSON object as arguments
|
|
17
|
+
* --key=value one flag per top-level field; values are
|
|
18
|
+
* JSON.parse-d (so `--limit=5` is 5, not "5";
|
|
19
|
+
* `--name=hello` falls back to the raw string
|
|
20
|
+
* if JSON.parse fails)
|
|
21
|
+
*
|
|
22
|
+
* Output:
|
|
23
|
+
* default — pretty JSON of the tool's result content
|
|
24
|
+
* --raw — entire JSON-RPC envelope
|
|
25
|
+
*/
|
|
26
|
+
import process from 'node:process';
|
|
27
|
+
import axios from 'axios';
|
|
28
|
+
import { getCredentials } from '../auth/token-store.js';
|
|
29
|
+
|
|
30
|
+
const DEFAULT_API_URL = 'https://api.ainote.dev';
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
// argv shape: [toolName, ...flags]
|
|
34
|
+
const toolName = argv[0];
|
|
35
|
+
const flags = argv.slice(1);
|
|
36
|
+
const out = { tool: toolName, args: {}, raw: false, help: false };
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < flags.length; i++) {
|
|
39
|
+
const a = flags[i];
|
|
40
|
+
if (a === '--raw') { out.raw = true; continue; }
|
|
41
|
+
if (a === '-h' || a === '--help') { out.help = true; continue; }
|
|
42
|
+
if (a === '--json') {
|
|
43
|
+
const next = flags[++i];
|
|
44
|
+
if (!next) throw new Error('--json requires a JSON object argument');
|
|
45
|
+
let parsed;
|
|
46
|
+
try { parsed = JSON.parse(next); }
|
|
47
|
+
catch (e) { throw new Error(`--json value is not valid JSON: ${e.message}`); }
|
|
48
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
49
|
+
throw new Error('--json value must be a JSON object');
|
|
50
|
+
}
|
|
51
|
+
out.args = { ...out.args, ...parsed };
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (a.startsWith('--')) {
|
|
55
|
+
const eq = a.indexOf('=');
|
|
56
|
+
let key, rawVal;
|
|
57
|
+
if (eq > 0) {
|
|
58
|
+
key = a.slice(2, eq);
|
|
59
|
+
rawVal = a.slice(eq + 1);
|
|
60
|
+
} else {
|
|
61
|
+
key = a.slice(2);
|
|
62
|
+
rawVal = flags[++i];
|
|
63
|
+
if (rawVal === undefined) {
|
|
64
|
+
out.args[key] = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Try JSON.parse first (numbers, booleans, null, arrays/objects),
|
|
69
|
+
// fall back to raw string.
|
|
70
|
+
let value;
|
|
71
|
+
try { value = JSON.parse(rawVal); }
|
|
72
|
+
catch { value = rawVal; }
|
|
73
|
+
out.args[key] = value;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`unexpected positional argument: ${a}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function printHelp() {
|
|
83
|
+
const help = `\
|
|
84
|
+
Usage: ainote-mcp call <tool> [options]
|
|
85
|
+
|
|
86
|
+
Direct shell invocation of an ainote MCP tool over HTTP. Bypasses any
|
|
87
|
+
running MCP host (Claude Desktop, Cursor, etc.) and posts directly to
|
|
88
|
+
api.ainote.dev — useful for scripts and one-off queries.
|
|
89
|
+
|
|
90
|
+
Authentication:
|
|
91
|
+
- AINOTE_API_KEY env var, OR
|
|
92
|
+
- mcp key from \`ainote-mcp login\` (stored in OS keychain)
|
|
93
|
+
|
|
94
|
+
Options:
|
|
95
|
+
--key=value pass an argument to the tool (JSON.parse-d, falls
|
|
96
|
+
back to string). Repeatable.
|
|
97
|
+
--json '<obj>' pass the entire arguments object as JSON. Merged
|
|
98
|
+
with any --key=value flags (--key=value wins on
|
|
99
|
+
conflict).
|
|
100
|
+
--raw print the full JSON-RPC envelope instead of just
|
|
101
|
+
the result content.
|
|
102
|
+
-h, --help this help.
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
ainote-mcp call list_tasks --due_today=true
|
|
106
|
+
ainote-mcp call list_tasks --limit=5 --status=pending
|
|
107
|
+
ainote-mcp call handoff_save \\
|
|
108
|
+
--json '{"project":"demo","topic":"poc","content":"..."}'
|
|
109
|
+
ainote-mcp call handoff_get --project=demo --topic=poc
|
|
110
|
+
ainote-mcp call list_dev_categories
|
|
111
|
+
`;
|
|
112
|
+
process.stdout.write(help);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function run(argv) {
|
|
116
|
+
// argv: everything after the `call` subcommand
|
|
117
|
+
if (!argv || argv.length === 0) {
|
|
118
|
+
printHelp();
|
|
119
|
+
process.exit(1);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let parsed;
|
|
124
|
+
try { parsed = parseArgs(argv); }
|
|
125
|
+
catch (e) {
|
|
126
|
+
console.error(`error: ${e.message}\n`);
|
|
127
|
+
printHelp();
|
|
128
|
+
process.exit(2);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (parsed.help || !parsed.tool || parsed.tool.startsWith('-')) {
|
|
133
|
+
printHelp();
|
|
134
|
+
process.exit(parsed.help ? 0 : 1);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const apiUrl = (process.env.AINOTE_API_URL || DEFAULT_API_URL).replace(/\/+$/, '');
|
|
139
|
+
|
|
140
|
+
let mcpKey = process.env.AINOTE_API_KEY;
|
|
141
|
+
if (!mcpKey) {
|
|
142
|
+
const creds = await getCredentials(apiUrl);
|
|
143
|
+
mcpKey = creds?.mcpKey;
|
|
144
|
+
}
|
|
145
|
+
if (!mcpKey) {
|
|
146
|
+
console.error('error: no MCP key. Run `ainote-mcp login` or set AINOTE_API_KEY.');
|
|
147
|
+
process.exit(3);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const body = {
|
|
152
|
+
jsonrpc: '2.0',
|
|
153
|
+
id: 1,
|
|
154
|
+
method: 'tools/call',
|
|
155
|
+
params: { name: parsed.tool, arguments: parsed.args }
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
let response;
|
|
159
|
+
try {
|
|
160
|
+
response = await axios.post(`${apiUrl}/api/mcp`, body, {
|
|
161
|
+
headers: {
|
|
162
|
+
'Content-Type': 'application/json',
|
|
163
|
+
Authorization: `McpKey ${mcpKey}`,
|
|
164
|
+
Accept: 'application/json'
|
|
165
|
+
},
|
|
166
|
+
timeout: 60000,
|
|
167
|
+
validateStatus: () => true
|
|
168
|
+
});
|
|
169
|
+
} catch (e) {
|
|
170
|
+
console.error(`error: request failed: ${e.message}`);
|
|
171
|
+
process.exit(4);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (response.status >= 400 && response.status !== 200) {
|
|
176
|
+
console.error(`error: HTTP ${response.status}`);
|
|
177
|
+
console.error(JSON.stringify(response.data, null, 2));
|
|
178
|
+
process.exit(5);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const env = response.data || {};
|
|
183
|
+
if (env.error) {
|
|
184
|
+
console.error(`error: ${env.error.message || 'unknown'} (code ${env.error.code})`);
|
|
185
|
+
if (env.error.data) console.error(JSON.stringify(env.error.data, null, 2));
|
|
186
|
+
process.exit(6);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (parsed.raw) {
|
|
191
|
+
console.log(JSON.stringify(env, null, 2));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Default: print just the result.content[] payload(s) so shell pipelines
|
|
196
|
+
// can grep / jq directly without the JSON-RPC envelope noise.
|
|
197
|
+
const content = env?.result?.content;
|
|
198
|
+
if (Array.isArray(content)) {
|
|
199
|
+
for (const block of content) {
|
|
200
|
+
if (block && typeof block === 'object' && 'text' in block) {
|
|
201
|
+
console.log(block.text);
|
|
202
|
+
} else {
|
|
203
|
+
console.log(JSON.stringify(block, null, 2));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Fallback — some tools may emit a non-standard shape; dump result.
|
|
210
|
+
console.log(JSON.stringify(env.result ?? env, null, 2));
|
|
211
|
+
}
|