@ainote/mcp 1.3.3 → 1.3.4
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 +2 -2
- package/lib/auth/mcp-key.js +2 -1
- package/lib/auth/token-store.js +25 -1
- package/lib/cli/call.js +7 -5
- package/lib/cli/login.js +2 -2
- package/lib/cli/logout.js +2 -3
- package/lib/cli/whoami.js +2 -3
- package/lib/core/config-manager.js +22 -2
- package/package.json +1 -1
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://
|
|
196
|
+
- `AINOTE_API_URL` — backend URL (default `https://ainote-5muq.onrender.com`; `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://
|
|
440
|
+
`AINOTE_API_URL` | Target AI Note API base URL | `https://ainote-5muq.onrender.com`
|
|
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/mcp-key.js
CHANGED
|
@@ -29,7 +29,8 @@ export async function authenticateWithMcpKeyAsync() {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
const { getCredentials } = await import('./token-store.js');
|
|
32
|
-
const
|
|
32
|
+
const { resolveApiUrl } = await import('../core/config-manager.js');
|
|
33
|
+
const baseUrl = resolveApiUrl();
|
|
33
34
|
const creds = await getCredentials(baseUrl);
|
|
34
35
|
if (creds && creds.mcpKey) {
|
|
35
36
|
return { type: 'mcpKey', apiKey: creds.mcpKey };
|
package/lib/auth/token-store.js
CHANGED
|
@@ -82,8 +82,25 @@ function apiHost(apiUrl) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Keychain account name for a (host, field) pair.
|
|
87
|
+
*
|
|
88
|
+
* Same cross-keychain constraint as keychainService(): account names must match
|
|
89
|
+
* /^[a-zA-Z0-9._@-]+$/ on every backend. The previous `${host}:${field}` form
|
|
90
|
+
* was rejected outright (the ':' separator, plus any ':port' in the host), so
|
|
91
|
+
* every keychain read/write threw and credentials always fell back to the file.
|
|
92
|
+
*
|
|
93
|
+
* Format: `${sanitizedHost}@${field}`. The host is reduced to [a-zA-Z0-9.-]
|
|
94
|
+
* (any other char, e.g. a port ':' or IPv6 brackets, becomes '_'), so it can
|
|
95
|
+
* never contain '@'; the '@' separator therefore keeps host and field
|
|
96
|
+
* unambiguously distinct. Deterministic for a given apiUrl.
|
|
97
|
+
*
|
|
98
|
+
* No migration: since the old ':'-based names were always rejected, nothing
|
|
99
|
+
* was ever stored under them in any keychain.
|
|
100
|
+
*/
|
|
85
101
|
function accountFor(apiUrl, field) {
|
|
86
|
-
|
|
102
|
+
const host = String(apiHost(apiUrl)).replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
103
|
+
return `${host}@${field}`;
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
function readFileStore() {
|
|
@@ -300,3 +317,10 @@ export function _credentialsLocations() {
|
|
|
300
317
|
file: getCredentialsFile()
|
|
301
318
|
};
|
|
302
319
|
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Internal helper exposing keychain account names for tests. Not public API.
|
|
323
|
+
*/
|
|
324
|
+
export function _keychainAccount(apiUrl, field) {
|
|
325
|
+
return accountFor(apiUrl, field);
|
|
326
|
+
}
|
package/lib/cli/call.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* 1. AINOTE_API_KEY env
|
|
11
11
|
* 2. token store (mcpKey written by `ainote-mcp login`)
|
|
12
12
|
*
|
|
13
|
-
* Posts JSON-RPC 2.0 `tools/call` to AINOTE_API_URL
|
|
13
|
+
* Posts JSON-RPC 2.0 `tools/call` to AINOTE_API_URL
|
|
14
|
+
* (default DEFAULT_API_URL from lib/core/config-manager.js, i.e. the same host
|
|
15
|
+
* `ainote-mcp login` stores credentials under), path /api/mcp.
|
|
14
16
|
*
|
|
15
17
|
* Argument parsing supports two styles, in priority order:
|
|
16
18
|
* --json '{"due_today": true}' raw JSON object as arguments
|
|
@@ -26,8 +28,7 @@
|
|
|
26
28
|
import process from 'node:process';
|
|
27
29
|
import axios from 'axios';
|
|
28
30
|
import { getCredentials } from '../auth/token-store.js';
|
|
29
|
-
|
|
30
|
-
const DEFAULT_API_URL = 'https://api.ainote.dev';
|
|
31
|
+
import { DEFAULT_API_URL, resolveApiUrl } from '../core/config-manager.js';
|
|
31
32
|
|
|
32
33
|
function parseArgs(argv) {
|
|
33
34
|
// argv shape: [toolName, ...flags]
|
|
@@ -85,7 +86,8 @@ Usage: ainote-mcp call <tool> [options]
|
|
|
85
86
|
|
|
86
87
|
Direct shell invocation of an ainote MCP tool over HTTP. Bypasses any
|
|
87
88
|
running MCP host (Claude Desktop, Cursor, etc.) and posts directly to
|
|
88
|
-
|
|
89
|
+
the AI Note API (AINOTE_API_URL, default ${DEFAULT_API_URL}) — useful
|
|
90
|
+
for scripts and one-off queries.
|
|
89
91
|
|
|
90
92
|
Authentication:
|
|
91
93
|
- AINOTE_API_KEY env var, OR
|
|
@@ -135,7 +137,7 @@ export async function run(argv) {
|
|
|
135
137
|
return;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
const apiUrl = (
|
|
140
|
+
const apiUrl = resolveApiUrl();
|
|
139
141
|
|
|
140
142
|
let mcpKey = process.env.AINOTE_API_KEY;
|
|
141
143
|
if (!mcpKey) {
|
package/lib/cli/login.js
CHANGED
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
DeviceNetworkError
|
|
28
28
|
} from '../auth/device-flow.js';
|
|
29
29
|
import { setCredentials, _credentialsLocations } from '../auth/token-store.js';
|
|
30
|
+
import { resolveApiUrl } from '../core/config-manager.js';
|
|
30
31
|
|
|
31
|
-
const DEFAULT_API_URL = 'https://ainote-5muq.onrender.com';
|
|
32
32
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
33
33
|
|
|
34
34
|
function parseArgs(argv) {
|
|
@@ -117,7 +117,7 @@ async function tryOpenBrowser(uri) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
export async function run(argv = process.argv.slice(3)) {
|
|
120
|
-
const apiUrl = (
|
|
120
|
+
const apiUrl = resolveApiUrl();
|
|
121
121
|
const args = parseArgs(argv);
|
|
122
122
|
const scopes = args.scope.split(',').map((s) => s.trim()).filter(Boolean);
|
|
123
123
|
|
package/lib/cli/logout.js
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import axios from 'axios';
|
|
6
6
|
import { getCredentials, clearCredentials } from '../auth/token-store.js';
|
|
7
|
-
|
|
8
|
-
const DEFAULT_API_URL = 'https://ainote-5muq.onrender.com';
|
|
7
|
+
import { resolveApiUrl } from '../core/config-manager.js';
|
|
9
8
|
|
|
10
9
|
export async function run() {
|
|
11
|
-
const apiUrl = (
|
|
10
|
+
const apiUrl = resolveApiUrl();
|
|
12
11
|
|
|
13
12
|
const creds = await getCredentials(apiUrl);
|
|
14
13
|
|
package/lib/cli/whoami.js
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
import process from 'node:process';
|
|
13
13
|
import axios from 'axios';
|
|
14
14
|
import { getCredentials } from '../auth/token-store.js';
|
|
15
|
-
|
|
16
|
-
const DEFAULT_API_URL = 'https://ainote-5muq.onrender.com';
|
|
15
|
+
import { resolveApiUrl } from '../core/config-manager.js';
|
|
17
16
|
|
|
18
17
|
function summarizeUser(user) {
|
|
19
18
|
if (!user || typeof user !== 'object') return null;
|
|
@@ -25,7 +24,7 @@ function summarizeUser(user) {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export async function run() {
|
|
28
|
-
const apiUrl = (
|
|
27
|
+
const apiUrl = resolveApiUrl();
|
|
29
28
|
const creds = await getCredentials(apiUrl);
|
|
30
29
|
|
|
31
30
|
if (!creds) {
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the default AI Note backend URL.
|
|
3
|
+
*
|
|
4
|
+
* Every entry point (MCP server, `login`, `logout`, `whoami`, `call`) must
|
|
5
|
+
* resolve the API URL through resolveApiUrl(). Credentials written by
|
|
6
|
+
* `ainote-mcp login` are namespaced by API host in the token store, so any
|
|
7
|
+
* divergence here makes the other commands look up the wrong host.
|
|
8
|
+
*
|
|
9
|
+
* (api.ainote.dev is currently a CNAME to this same Render service, but it is
|
|
10
|
+
* a different token-store key, so the default must stay identical everywhere.)
|
|
11
|
+
*/
|
|
12
|
+
export const DEFAULT_API_URL = 'https://ainote-5muq.onrender.com';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the effective API base URL: AINOTE_API_URL env override, else the
|
|
16
|
+
* default. Trailing slashes are stripped so "${url}/api/..." joins cleanly.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveApiUrl(env = process.env) {
|
|
19
|
+
return (env.AINOTE_API_URL || DEFAULT_API_URL).replace(/\/+$/, '');
|
|
20
|
+
}
|
|
21
|
+
|
|
2
22
|
const DEFAULT_HTTP_PORT = 3030;
|
|
3
23
|
|
|
4
24
|
const parseBoolean = (value, fallback) => {
|
|
@@ -57,7 +77,7 @@ export function getConfig() {
|
|
|
57
77
|
return {
|
|
58
78
|
api: {
|
|
59
79
|
key: process.env.AINOTE_API_KEY,
|
|
60
|
-
url:
|
|
80
|
+
url: resolveApiUrl()
|
|
61
81
|
},
|
|
62
82
|
http: {
|
|
63
83
|
port: parseNumber(process.env.AINOTE_MCP_HTTP_PORT, DEFAULT_HTTP_PORT),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainote/mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
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",
|