🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@ainote/mcp

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ainote/mcp - npm Package Compare versions

Comparing version
1.3.1
to
1.3.2
+211
lib/cli/call.js
/**
* `ainote-mcp call <tool> [--key=value ...] [--json '<obj>']`
*
* Direct shell invocation of an ainote MCP tool — no agent required. Useful for:
* - one-off CLI use, scripts, alfred/raycast workflows
* - debugging tool responses without spinning up Claude
* - automation that wants a single tool result, not an interactive loop
*
* Resolves the MCP key from (in order):
* 1. AINOTE_API_KEY env
* 2. token store (mcpKey written by `ainote-mcp login`)
*
* Posts JSON-RPC 2.0 `tools/call` to AINOTE_API_URL (default https://api.ainote.dev/api/mcp).
*
* Argument parsing supports two styles, in priority order:
* --json '{"due_today": true}' raw JSON object as arguments
* --key=value one flag per top-level field; values are
* JSON.parse-d (so `--limit=5` is 5, not "5";
* `--name=hello` falls back to the raw string
* if JSON.parse fails)
*
* Output:
* default — pretty JSON of the tool's result content
* --raw — entire JSON-RPC envelope
*/
import process from 'node:process';
import axios from 'axios';
import { getCredentials } from '../auth/token-store.js';
const DEFAULT_API_URL = 'https://api.ainote.dev';
function parseArgs(argv) {
// argv shape: [toolName, ...flags]
const toolName = argv[0];
const flags = argv.slice(1);
const out = { tool: toolName, args: {}, raw: false, help: false };
for (let i = 0; i < flags.length; i++) {
const a = flags[i];
if (a === '--raw') { out.raw = true; continue; }
if (a === '-h' || a === '--help') { out.help = true; continue; }
if (a === '--json') {
const next = flags[++i];
if (!next) throw new Error('--json requires a JSON object argument');
let parsed;
try { parsed = JSON.parse(next); }
catch (e) { throw new Error(`--json value is not valid JSON: ${e.message}`); }
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('--json value must be a JSON object');
}
out.args = { ...out.args, ...parsed };
continue;
}
if (a.startsWith('--')) {
const eq = a.indexOf('=');
let key, rawVal;
if (eq > 0) {
key = a.slice(2, eq);
rawVal = a.slice(eq + 1);
} else {
key = a.slice(2);
rawVal = flags[++i];
if (rawVal === undefined) {
out.args[key] = true;
continue;
}
}
// Try JSON.parse first (numbers, booleans, null, arrays/objects),
// fall back to raw string.
let value;
try { value = JSON.parse(rawVal); }
catch { value = rawVal; }
out.args[key] = value;
continue;
}
throw new Error(`unexpected positional argument: ${a}`);
}
return out;
}
function printHelp() {
const help = `\
Usage: ainote-mcp call <tool> [options]
Direct shell invocation of an ainote MCP tool over HTTP. Bypasses any
running MCP host (Claude Desktop, Cursor, etc.) and posts directly to
api.ainote.dev — useful for scripts and one-off queries.
Authentication:
- AINOTE_API_KEY env var, OR
- mcp key from \`ainote-mcp login\` (stored in OS keychain)
Options:
--key=value pass an argument to the tool (JSON.parse-d, falls
back to string). Repeatable.
--json '<obj>' pass the entire arguments object as JSON. Merged
with any --key=value flags (--key=value wins on
conflict).
--raw print the full JSON-RPC envelope instead of just
the result content.
-h, --help this help.
Examples:
ainote-mcp call list_tasks --due_today=true
ainote-mcp call list_tasks --limit=5 --status=pending
ainote-mcp call handoff_save \\
--json '{"project":"demo","topic":"poc","content":"..."}'
ainote-mcp call handoff_get --project=demo --topic=poc
ainote-mcp call list_dev_categories
`;
process.stdout.write(help);
}
export async function run(argv) {
// argv: everything after the `call` subcommand
if (!argv || argv.length === 0) {
printHelp();
process.exit(1);
return;
}
let parsed;
try { parsed = parseArgs(argv); }
catch (e) {
console.error(`error: ${e.message}\n`);
printHelp();
process.exit(2);
return;
}
if (parsed.help || !parsed.tool || parsed.tool.startsWith('-')) {
printHelp();
process.exit(parsed.help ? 0 : 1);
return;
}
const apiUrl = (process.env.AINOTE_API_URL || DEFAULT_API_URL).replace(/\/+$/, '');
let mcpKey = process.env.AINOTE_API_KEY;
if (!mcpKey) {
const creds = await getCredentials(apiUrl);
mcpKey = creds?.mcpKey;
}
if (!mcpKey) {
console.error('error: no MCP key. Run `ainote-mcp login` or set AINOTE_API_KEY.');
process.exit(3);
return;
}
const body = {
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: { name: parsed.tool, arguments: parsed.args }
};
let response;
try {
response = await axios.post(`${apiUrl}/api/mcp`, body, {
headers: {
'Content-Type': 'application/json',
Authorization: `McpKey ${mcpKey}`,
Accept: 'application/json'
},
timeout: 60000,
validateStatus: () => true
});
} catch (e) {
console.error(`error: request failed: ${e.message}`);
process.exit(4);
return;
}
if (response.status >= 400 && response.status !== 200) {
console.error(`error: HTTP ${response.status}`);
console.error(JSON.stringify(response.data, null, 2));
process.exit(5);
return;
}
const env = response.data || {};
if (env.error) {
console.error(`error: ${env.error.message || 'unknown'} (code ${env.error.code})`);
if (env.error.data) console.error(JSON.stringify(env.error.data, null, 2));
process.exit(6);
return;
}
if (parsed.raw) {
console.log(JSON.stringify(env, null, 2));
return;
}
// Default: print just the result.content[] payload(s) so shell pipelines
// can grep / jq directly without the JSON-RPC envelope noise.
const content = env?.result?.content;
if (Array.isArray(content)) {
for (const block of content) {
if (block && typeof block === 'object' && 'text' in block) {
console.log(block.text);
} else {
console.log(JSON.stringify(block, null, 2));
}
}
return;
}
// Fallback — some tools may emit a non-standard shape; dump result.
console.log(JSON.stringify(env.result ?? env, null, 2));
}
+5
-0

@@ -26,2 +26,7 @@ #!/usr/bin/env node

await run();
} else if (subcommand === 'call') {
// `ainote-mcp call <tool> [--key=value ...]` — direct shell invocation.
// Bypasses the stdio MCP host and posts JSON-RPC tools/call to the API.
const { run } = await import('./lib/cli/call.js');
await run(process.argv.slice(3));
} else {

@@ -28,0 +33,0 @@ // Default: start MCP stdio server

+1
-1
{
"name": "@ainote/mcp",
"version": "1.3.1",
"version": "1.3.2",
"mcpName": "io.github.seunghan91/ainote",

@@ -5,0 +5,0 @@ "description": "MCP server for AI Note - Tasks, Dev Docs, and multi-device file sync (CLAUDE.md, memory files, Cursor rules, Windsurf rules)",

@@ -129,2 +129,25 @@ # AI Note MCP Server

### CLI Tool Invocation (`call`)
Run any of the 26 ainote tools directly from the shell — no Claude / Cursor / MCP host required. Useful for scripts, alfred / raycast workflows, automation, and one-off queries.
```bash
# List all tasks due today
npx @ainote/mcp call list_tasks --due_today=true
# Save a handoff (JSON form)
npx @ainote/mcp call handoff_save \
--json '{"project":"demo","topic":"poc","content":"..."}'
# Get a handoff
npx @ainote/mcp call handoff_get --project=demo --topic=poc
# Read a resource URI as JSON (load all tasks at once)
npx @ainote/mcp call list_tasks --limit=500
```
Flag parsing: `--key=value` is JSON.parse-d (numbers/booleans/null/arrays/objects work directly; raw strings fall back). Pass the entire arguments object via `--json '<obj>'`. Add `--raw` for the full JSON-RPC envelope.
Auth resolution order: `AINOTE_API_KEY` env var → OS keychain (from `ainote-mcp login`).
### Onboarding MCP Tools

@@ -131,0 +154,0 @@