@ainote/mcp 1.3.5 → 1.3.7
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-ko.md +3 -3
- package/README.md +2 -2
- package/lib/auth/token-store.js +51 -0
- package/lib/core/config-manager.js +29 -5
- package/package.json +2 -1
package/README-ko.md
CHANGED
|
@@ -156,7 +156,7 @@ npm install
|
|
|
156
156
|
export AINOTE_API_KEY="여기에-api-키-입력"
|
|
157
157
|
|
|
158
158
|
# 선택사항 (기본값: 프로덕션 URL)
|
|
159
|
-
export AINOTE_API_URL="https://ainote
|
|
159
|
+
export AINOTE_API_URL="https://api.ainote.dev"
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
### 2. Claude Desktop 설정
|
|
@@ -177,7 +177,7 @@ Claude Desktop 설정 파일에 다음 내용을 추가하세요:
|
|
|
177
177
|
],
|
|
178
178
|
"env": {
|
|
179
179
|
"AINOTE_API_KEY": "YOUR_API_KEY",
|
|
180
|
-
"AINOTE_API_URL": "https://ainote
|
|
180
|
+
"AINOTE_API_URL": "https://api.ainote.dev"
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
}
|
|
@@ -193,7 +193,7 @@ Claude Desktop 설정 파일에 다음 내용을 추가하세요:
|
|
|
193
193
|
"command": "ainote-mcp",
|
|
194
194
|
"env": {
|
|
195
195
|
"AINOTE_API_KEY": "YOUR_API_KEY",
|
|
196
|
-
"AINOTE_API_URL": "https://ainote
|
|
196
|
+
"AINOTE_API_URL": "https://api.ainote.dev"
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
}
|
package/README.md
CHANGED
|
@@ -193,7 +193,7 @@ The fallback file is only used when no system keychain is available (e.g. headle
|
|
|
193
193
|
|
|
194
194
|
### Environment overrides
|
|
195
195
|
|
|
196
|
-
- `AINOTE_API_URL` — backend URL (default `https://ainote
|
|
196
|
+
- `AINOTE_API_URL` — backend URL (default `https://api.ainote.dev`; `login`, `call`, and the MCP server all share it, since stored credentials are keyed by host)
|
|
197
197
|
- `AINOTE_API_KEY` — pre-provisioned MCP key. When set, the login flow is bypassed entirely and this key is used as-is.
|
|
198
198
|
|
|
199
199
|
### Headless / CI usage
|
|
@@ -437,7 +437,7 @@ By default the server listens on `http://localhost:3030` and exposes two endpoin
|
|
|
437
437
|
Environment variable | Description | Default
|
|
438
438
|
---|---|---
|
|
439
439
|
`AINOTE_API_KEY` | API key used for MCP key authentication | **required**
|
|
440
|
-
`AINOTE_API_URL` | Target AI Note API base URL | `https://ainote
|
|
440
|
+
`AINOTE_API_URL` | Target AI Note API base URL | `https://api.ainote.dev`
|
|
441
441
|
`AINOTE_MCP_HTTP_PORT` | Local port for the SSE server | `3030`
|
|
442
442
|
`AINOTE_MCP_ALLOWED_ORIGINS` | Comma-separated list of allowed browser origins | _any_
|
|
443
443
|
`AINOTE_MCP_ALLOWED_HOSTS` | Comma-separated list of allowed `Host` headers | _any_
|
package/lib/auth/token-store.js
CHANGED
|
@@ -26,6 +26,7 @@ import path from 'node:path';
|
|
|
26
26
|
import os from 'node:os';
|
|
27
27
|
import url from 'node:url';
|
|
28
28
|
import { createRequire } from 'node:module';
|
|
29
|
+
import { DEFAULT_API_URL, LEGACY_API_HOSTS } from '../core/config-manager.js';
|
|
29
30
|
|
|
30
31
|
const require = createRequire(import.meta.url);
|
|
31
32
|
|
|
@@ -188,6 +189,47 @@ async function keychainDelete(apiUrl, field) {
|
|
|
188
189
|
* refreshToken?: string, expiresAt?: number, source: 'keychain'|'file'|'mixed'}>}
|
|
189
190
|
*/
|
|
190
191
|
export async function getCredentials(apiUrl) {
|
|
192
|
+
const own = await readCredentials(apiUrl);
|
|
193
|
+
// Credentials saved while the default was a legacy host still count for the
|
|
194
|
+
// default host, so a host move does not force everyone to log in again.
|
|
195
|
+
// Merge per field, own values winning: a token refresh stores only the new
|
|
196
|
+
// access/refresh tokens under the current host, and the MCP key saved under
|
|
197
|
+
// the legacy host must stay visible after that.
|
|
198
|
+
let merged = own;
|
|
199
|
+
for (const legacyUrl of legacyUrlsFor(apiUrl)) {
|
|
200
|
+
const legacy = await readCredentials(legacyUrl);
|
|
201
|
+
if (!legacy) continue;
|
|
202
|
+
merged = merged ? mergeCredentials(merged, legacy) : legacy;
|
|
203
|
+
}
|
|
204
|
+
return merged;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function mergeCredentials(primary, fallback) {
|
|
208
|
+
const out = { ...primary };
|
|
209
|
+
for (const key of ['mcpKey', 'accessToken', 'refreshToken', 'expiresAt']) {
|
|
210
|
+
if (out[key] === undefined && fallback[key] !== undefined) out[key] = fallback[key];
|
|
211
|
+
}
|
|
212
|
+
if (out.source !== fallback.source) out.source = 'mixed';
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Legacy API URLs whose stored credentials belong to `apiUrl`. Only the exact
|
|
218
|
+
* canonical HTTPS origin qualifies, so a plain-http or other-port URL on the
|
|
219
|
+
* same host never receives them.
|
|
220
|
+
*/
|
|
221
|
+
function legacyUrlsFor(apiUrl) {
|
|
222
|
+
let origin;
|
|
223
|
+
try {
|
|
224
|
+
origin = new url.URL(apiUrl).origin;
|
|
225
|
+
} catch {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
if (origin !== new url.URL(DEFAULT_API_URL).origin) return [];
|
|
229
|
+
return LEGACY_API_HOSTS.map((host) => `https://${host}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function readCredentials(apiUrl) {
|
|
191
233
|
const host = apiHost(apiUrl);
|
|
192
234
|
|
|
193
235
|
const fileStore = readFileStore();
|
|
@@ -287,6 +329,15 @@ export async function setCredentials(apiUrl, creds) {
|
|
|
287
329
|
* Best-effort: errors are swallowed and the caller can assume cleared state.
|
|
288
330
|
*/
|
|
289
331
|
export async function clearCredentials(apiUrl) {
|
|
332
|
+
// Logging out of the current host also drops legacy-host credentials;
|
|
333
|
+
// otherwise getCredentials() would fall back to them and resurrect the login.
|
|
334
|
+
for (const legacyUrl of legacyUrlsFor(apiUrl)) {
|
|
335
|
+
await clearHostCredentials(legacyUrl);
|
|
336
|
+
}
|
|
337
|
+
await clearHostCredentials(apiUrl);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function clearHostCredentials(apiUrl) {
|
|
290
341
|
const host = apiHost(apiUrl);
|
|
291
342
|
|
|
292
343
|
for (const field of FIELDS) {
|
|
@@ -6,17 +6,41 @@
|
|
|
6
6
|
* `ainote-mcp login` are namespaced by API host in the token store, so any
|
|
7
7
|
* divergence here makes the other commands look up the wrong host.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* The backend moved off Render (2026-11); the old Render host now answers 503.
|
|
10
|
+
* api.ainote.dev is the canonical domain, matching the iOS and Android apps.
|
|
11
11
|
*/
|
|
12
|
-
export const DEFAULT_API_URL = 'https://ainote
|
|
12
|
+
export const DEFAULT_API_URL = 'https://api.ainote.dev';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hosts that used to serve the API. A URL on one of these is rewritten to
|
|
16
|
+
* DEFAULT_API_URL (path kept), so configs that pinned the old host via
|
|
17
|
+
* AINOTE_API_URL keep working, and the token store can still find
|
|
18
|
+
* credentials saved under them.
|
|
19
|
+
*/
|
|
20
|
+
export const LEGACY_API_HOSTS = ['ainote-5muq.onrender.com'];
|
|
21
|
+
|
|
22
|
+
function rewriteLegacyHost(raw) {
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = new URL(raw);
|
|
26
|
+
} catch {
|
|
27
|
+
return raw;
|
|
28
|
+
}
|
|
29
|
+
if (!LEGACY_API_HOSTS.includes(parsed.host)) return raw;
|
|
30
|
+
const target = new URL(DEFAULT_API_URL);
|
|
31
|
+
parsed.protocol = target.protocol;
|
|
32
|
+
parsed.host = target.host;
|
|
33
|
+
return parsed.toString();
|
|
34
|
+
}
|
|
13
35
|
|
|
14
36
|
/**
|
|
15
37
|
* Resolve the effective API base URL: AINOTE_API_URL env override, else the
|
|
16
|
-
* default.
|
|
38
|
+
* default. A legacy host is rewritten to the default host. Trailing slashes
|
|
39
|
+
* are stripped so "${url}/api/..." joins cleanly.
|
|
17
40
|
*/
|
|
18
41
|
export function resolveApiUrl(env = process.env) {
|
|
19
|
-
|
|
42
|
+
const raw = env.AINOTE_API_URL || DEFAULT_API_URL;
|
|
43
|
+
return rewriteLegacyHost(raw).replace(/\/+$/, '');
|
|
20
44
|
}
|
|
21
45
|
|
|
22
46
|
const DEFAULT_HTTP_PORT = 3030;
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainote/mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
4
4
|
"mcpName": "io.github.seunghan91/ainote",
|
|
5
5
|
"description": "MCP server for AI Note - Tasks, Dev Docs, and multi-device file sync (CLAUDE.md, memory files, Cursor rules, Windsurf rules)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
|
+
"mcp": "./index.js",
|
|
9
10
|
"ainote-mcp": "./index.js",
|
|
10
11
|
"ainote-mcp-http": "./server.js"
|
|
11
12
|
},
|