@ainote/mcp 1.0.3 → 1.1.1

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 CHANGED
@@ -52,17 +52,86 @@ All transports expose the same tool catalog. Choose the option that matches your
52
52
  ## Features
53
53
 
54
54
  - 📝 **Task Management**: Create, update, delete, and list tasks directly from Claude Desktop
55
+ - 📄 **Dev Docs**: Manage CLAUDE.md, Cursor rules, Windsurf rules, and dev documents centrally via MCP
56
+ - 🗂ïļ **Dev Category Hierarchy**: Auto-organized under `dev/` with subcategories (claude, cursor, windsurf, copilot, docs)
55
57
  - 🏷ïļ **Category Support**: Organize tasks with categories
56
- - 🔍 **Advanced Search**: Search tasks by content, status, and more
58
+ - 🔍 **Advanced Filtering**: 18+ filter options including date ranges, location, overdue tasks
57
59
  - ⭐ **Priority Management**: Mark tasks as important
58
- - 📅 **Due Date Support**: Set and manage task due dates
59
- - 🔒 **Secure API Integration**: Uses API key authentication for secure access
60
+ - 📅 **Smart Due Dates**: Human-readable format with relative time (e.g., `'26.1.25 14:30 - ė˜Ī늘`)
61
+ - 🔒 **Dual Authentication**: Support for both User API Key (24-char) and MCP Key (64-char)
62
+ - 📊 **Usage Tracking**: Automatic API usage statistics per MCP key
63
+ - 🌊 **Streaming API**: Real-time streaming for bulk operations and large datasets
64
+ - ðŸĪ– **Bot Integration**: Telegram bot support via Clawdbot + mcporter
65
+
66
+ ## Quick Start (No Account Needed)
67
+
68
+ You can start using AI Note MCP **without an existing account**. The MCP server includes onboarding tools that let you sign up and get an API key directly from Claude:
69
+
70
+ ### 1. Add to your MCP config (no API key yet)
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "ainote": {
76
+ "command": "npx",
77
+ "args": ["-y", "@ainote/mcp"]
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ### 2. Restart Claude Desktop
84
+
85
+ ### 3. Ask Claude to sign you up
86
+
87
+ > "Sign me up for AI Note with email user@example.com and password mypassword123"
88
+
89
+ Claude will call the `signup_and_get_key` tool and return your MCP API key.
90
+
91
+ ### 4. Add the API key to your config
92
+
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "ainote": {
97
+ "command": "npx",
98
+ "args": ["-y", "@ainote/mcp"],
99
+ "env": {
100
+ "AINOTE_API_KEY": "<your-key-from-step-3>"
101
+ }
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### 5. Restart Claude Desktop again
108
+
109
+ Now all tools (tasks, dev docs, etc.) are available.
110
+
111
+ ### CLI Signup
112
+
113
+ You can also sign up directly from the terminal:
114
+
115
+ ```bash
116
+ npx @ainote/mcp signup
117
+ ```
118
+
119
+ This will interactively ask for email, password, and name, then return your MCP API key.
120
+
121
+ ### Onboarding MCP Tools
122
+
123
+ These tools also work inside Claude (no API key needed):
124
+
125
+ | Tool | Description |
126
+ |------|-------------|
127
+ | `signup_and_get_key` | Create account + get MCP key |
128
+ | `login_and_get_key` | Login + get MCP key (existing account) |
129
+ | `get_setup_guide` | Setup instructions |
60
130
 
61
131
  ## Prerequisites
62
132
 
63
133
  - Node.js >= 18.0.0
64
134
  - npm or yarn
65
- - AI Note API access (API key required)
66
135
  - Claude Desktop with MCP support enabled
67
136
 
68
137
  ## Installation
@@ -217,6 +286,44 @@ Authorization: Bearer YOUR_API_KEY
217
286
 
218
287
  This HTTP endpoint provides identical functionality to the stdio version, making AI Note MCP accessible to a broader range of platforms and integration scenarios.
219
288
 
289
+ ### 4. **Streaming API** - For Large Data Operations
290
+ - **URL**: `POST https://api.ainote.dev/mcp/stream`
291
+ - **Protocol**: Server-Sent Events (SSE)
292
+ - **Usage**: Bulk operations, large task lists, real-time progress
293
+ - **Best for**: Applications that need to handle large datasets or show progress indicators
294
+
295
+ #### Streaming Methods
296
+ | Method | Description |
297
+ |--------|-------------|
298
+ | `tasks/list` | Stream task list with filters |
299
+ | `tasks/create` | Create single task with progress |
300
+ | `tasks/analyze` | Get task statistics and insights |
301
+ | `tasks/bulk_update` | Update multiple tasks with progress |
302
+ | `tasks/bulk_create` | Create up to 1000 tasks at once |
303
+
304
+ #### Streaming Events
305
+ ```
306
+ event: progress
307
+ data: {"status": "starting", "total": 100}
308
+
309
+ event: data
310
+ data: {"type": "task", "data": {...}}
311
+
312
+ event: result
313
+ data: {"status": "success", "count": 100}
314
+ ```
315
+
316
+ ### 5. **Telegram Bot Integration** - Via Clawdbot
317
+ - **Setup Guide**: [CLAWDBOT_SETUP.md](./CLAWDBOT_SETUP.md)
318
+ - **Tool**: mcporter for MCP server bridging
319
+ - **Usage**: Natural language task management in Telegram
320
+
321
+ ```bash
322
+ # Quick setup
323
+ npm install -g mcporter
324
+ mcporter call ainote.list_tasks
325
+ ```
326
+
220
327
  ### Local Development Bridge (`ainote-mcp-http`)
221
328
 
222
329
  Run the bundled HTTP/SSE bridge when you need a local endpoint for ChatGPT or other MCP clients that speak SSE:
@@ -248,12 +355,31 @@ When OAuth is enabled the server expects `Authorization: Bearer ...` headers fro
248
355
 
249
356
  ### list_tasks
250
357
 
251
- List tasks from AI Note with filtering options.
358
+ List tasks from AI Note with advanced filtering options.
252
359
 
253
- **Parameters:**
360
+ **Basic Parameters:**
254
361
  - `status` (optional): Filter by status - "pending" or "completed"
255
362
  - `limit` (optional): Maximum number of tasks (default: 25, max: 500)
256
363
  - `search` (optional): Search keyword in task content
364
+ - `is_important` (optional): Filter important tasks only
365
+
366
+ **Location & Category:**
367
+ - `location` (optional): Filter by location (partial match, e.g., "ė—Žė˜ë„")
368
+ - `category_id` (optional): Filter by category UUID
369
+
370
+ **Date Range Filters:**
371
+ - `due_date_start` / `due_date_end`: Filter by due date range
372
+ - `completed_date_start` / `completed_date_end`: Filter by completion date
373
+ - `created_date_start` / `created_date_end`: Filter by creation date
374
+
375
+ **Special Filters:**
376
+ - `overdue` (optional): Filter overdue incomplete tasks
377
+ - `due_today` (optional): Filter tasks due today
378
+ - `has_notification` (optional): Filter by notification status
379
+
380
+ **Sorting:**
381
+ - `sort_by` (optional): Sort field - "due_date", "created_at", "completed_at", "updated_at", "is_important"
382
+ - `sort_order` (optional): Sort direction - "asc" or "desc"
257
383
 
258
384
  ### create_task
259
385
 
@@ -288,6 +414,80 @@ List all available categories.
288
414
 
289
415
  **Parameters:** None
290
416
 
417
+ ---
418
+
419
+ ### Dev Doc Tools (v1.1.0+)
420
+
421
+ Manage AI coding tool configuration files and dev documents centrally. All docs are organized under the `dev/` category hierarchy.
422
+
423
+ #### list_dev_docs
424
+
425
+ List dev documents with optional filtering.
426
+
427
+ **Parameters:**
428
+ - `category` (optional): Subcategory filter (claude, cursor, windsurf, copilot, docs)
429
+ - `search` (optional): Search keyword in document title
430
+ - `content_type` (optional): Filter by type - "markdown", "json", "yaml", "text"
431
+
432
+ #### get_dev_doc
433
+
434
+ Get a single dev document by title or id.
435
+
436
+ **Parameters:**
437
+ - `title` or `id` (one required): Document identifier
438
+ - `category` (optional): Subcategory to disambiguate title
439
+ - `include_versions` (optional): Include version history
440
+
441
+ #### create_dev_doc
442
+
443
+ Create a new dev document under `dev/` category.
444
+
445
+ **Parameters:**
446
+ - `title` (required): Document title (e.g., "project-a-claude.md")
447
+ - `content` (required): Document content
448
+ - `category` (optional): Subcategory (default: "docs")
449
+ - `content_type` (optional): Auto-detected from title extension
450
+
451
+ #### update_dev_doc
452
+
453
+ Update a dev document with replace, append, or prepend modes.
454
+
455
+ **Parameters:**
456
+ - `title` or `id` (one required): Document identifier
457
+ - `content` (required): New content
458
+ - `mode` (optional): "replace" (default), "append", or "prepend"
459
+
460
+ #### delete_dev_doc
461
+
462
+ Soft delete a dev document.
463
+
464
+ **Parameters:**
465
+ - `title` or `id` (one required): Document identifier
466
+
467
+ #### list_dev_categories
468
+
469
+ List all subcategories under `dev/` with document counts.
470
+
471
+ **Parameters:** None
472
+
473
+ #### Example: Store CLAUDE.md in ainote
474
+
475
+ ```
476
+ You: "Save this project's CLAUDE.md content to ainote"
477
+ Claude: I'll create a dev doc in the claude category...
478
+ → create_dev_doc(title: "myproject-claude.md", content: "...", category: "claude")
479
+
480
+ You: "Update my cursor rules in ainote"
481
+ Claude: I'll update the cursor rules doc...
482
+ → update_dev_doc(title: "api-rules.mdc", category: "cursor", content: "...")
483
+
484
+ You: "Show me all my dev docs"
485
+ Claude: I'll list all documents under dev/...
486
+ → list_dev_docs()
487
+ ```
488
+
489
+ ---
490
+
291
491
  ## API Reference
292
492
 
293
493
  The MCP server communicates with the AI Note API using the following endpoints:
package/index.js CHANGED
@@ -1,25 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * AI Note MCP Server - Claude Desktop Integration (stdio transport)
4
+ * Also supports CLI subcommands: signup
4
5
  */
5
6
 
6
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
- import { resolveAuthContext } from './lib/auth/strategy-factory.js';
8
- import { createClaudeServer } from './lib/core/server-factory.js';
7
+ const subcommand = process.argv[2];
9
8
 
10
- const transport = new StdioServerTransport();
9
+ if (subcommand === 'signup') {
10
+ const { runSignup } = await import('./lib/cli/signup.js');
11
+ await runSignup();
12
+ } else {
13
+ // Default: start MCP stdio server
14
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
15
+ const { resolveAuthContext } = await import('./lib/auth/strategy-factory.js');
16
+ const { createClaudeServer } = await import('./lib/core/server-factory.js');
11
17
 
12
- let defaultAuthContext;
18
+ const transport = new StdioServerTransport();
19
+ const defaultAuthContext = resolveAuthContext('stdio');
13
20
 
14
- try {
15
- defaultAuthContext = resolveAuthContext('stdio');
16
- } catch (error) {
17
- console.error(`[ainote-mcp] Authentication error: ${error.message}`);
18
- process.exitCode = 1;
19
- throw error;
20
- }
21
+ if (defaultAuthContext.type === 'anonymous') {
22
+ console.error('[ainote-mcp] No AINOTE_API_KEY found. Starting in onboarding mode.');
23
+ console.error('[ainote-mcp] Use signup_and_get_key or login_and_get_key to get started.');
24
+ console.error('[ainote-mcp] Or run: npx @ainote/mcp signup');
25
+ }
21
26
 
22
- const { server } = createClaudeServer({ defaultAuthContext });
27
+ const { server } = createClaudeServer({ defaultAuthContext });
23
28
 
24
- await server.connect(transport);
25
- console.error('AI Note MCP stdio server started (Claude Desktop compatible)');
29
+ await server.connect(transport);
30
+ console.error('AI Note MCP stdio server started (Claude Desktop compatible)');
31
+ }
@@ -4,15 +4,28 @@ import { getFeatureFlags } from '../core/config.js';
4
4
 
5
5
  export function resolveAuthContext(transportType, headers = {}) {
6
6
  if (transportType === 'stdio') {
7
- return authenticateWithMcpKey();
7
+ try {
8
+ return authenticateWithMcpKey();
9
+ } catch {
10
+ // No API key configured — allow anonymous mode for onboarding tools
11
+ return { type: 'anonymous' };
12
+ }
8
13
  }
9
14
 
10
15
  const features = getFeatureFlags();
11
16
 
12
17
  if (transportType === 'sse' && features.oauthAuth) {
13
- return authenticateWithOAuth(headers);
18
+ try {
19
+ return authenticateWithOAuth(headers);
20
+ } catch {
21
+ // Fall through to MCP key or anonymous
22
+ }
14
23
  }
15
24
 
16
- // Default fallback to MCP key to maintain backward compatibility.
17
- return authenticateWithMcpKey();
25
+ // Default fallback to MCP key, then anonymous for onboarding
26
+ try {
27
+ return authenticateWithMcpKey();
28
+ } catch {
29
+ return { type: 'anonymous' };
30
+ }
18
31
  }
@@ -0,0 +1,118 @@
1
+ import * as readline from 'node:readline/promises';
2
+ import { stdin as input, stdout as output } from 'node:process';
3
+ import axios from 'axios';
4
+ import { getConfig } from '../core/config-manager.js';
5
+
6
+ /**
7
+ * Interactive CLI signup for AI Note MCP.
8
+ * Usage: ainote-mcp signup
9
+ */
10
+ export async function runSignup() {
11
+ const config = getConfig();
12
+ const rl = readline.createInterface({ input, output });
13
+
14
+ console.log('\n AI Note MCP — Sign Up\n');
15
+
16
+ try {
17
+ const email = await rl.question(' Email: ');
18
+ const password = await rl.question(' Password (min 6 chars): ');
19
+ const name = await rl.question(' Name (optional, press Enter to skip): ');
20
+
21
+ if (!email.trim() || !password.trim()) {
22
+ console.error('\n Error: Email and password are required.\n');
23
+ process.exit(1);
24
+ }
25
+
26
+ if (password.trim().length < 6) {
27
+ console.error('\n Error: Password must be at least 6 characters.\n');
28
+ process.exit(1);
29
+ }
30
+
31
+ console.log('\n Creating account...');
32
+
33
+ const rpcRequest = {
34
+ jsonrpc: '2.0',
35
+ id: 1,
36
+ method: 'tools/call',
37
+ params: {
38
+ name: 'signup_and_get_key',
39
+ arguments: {
40
+ email: email.trim(),
41
+ password: password.trim(),
42
+ ...(name.trim() ? { name: name.trim() } : {})
43
+ }
44
+ }
45
+ };
46
+
47
+ const response = await axios.post(`${config.api.url}/api/mcp`, rpcRequest, {
48
+ headers: { 'Content-Type': 'application/json' },
49
+ timeout: 30000
50
+ });
51
+
52
+ const data = response.data?.data;
53
+
54
+ // Check for JSON-RPC error
55
+ if (data?.error) {
56
+ console.error(`\n Error: ${data.error.message}`);
57
+ if (data.error.data?.suggestion) {
58
+ console.log(` Hint: ${data.error.data.suggestion}`);
59
+ }
60
+ console.log();
61
+ process.exit(1);
62
+ }
63
+
64
+ // Extract MCP key from structured resource data
65
+ const content = data?.result?.content;
66
+ let mcpKey = null;
67
+
68
+ if (Array.isArray(content)) {
69
+ for (const item of content) {
70
+ if (item.type === 'resource' && item.resource?.text) {
71
+ try {
72
+ const resourceData = JSON.parse(item.resource.text);
73
+ mcpKey = resourceData.mcp_key;
74
+ } catch { /* ignore */ }
75
+ }
76
+ }
77
+ }
78
+
79
+ if (mcpKey) {
80
+ console.log('\n Account created successfully!\n');
81
+ console.log(` Your MCP API Key:\n`);
82
+ console.log(` ${mcpKey}\n`);
83
+ console.log(' Add this to your MCP config:\n');
84
+ console.log(' {');
85
+ console.log(' "mcpServers": {');
86
+ console.log(' "ainote": {');
87
+ console.log(' "command": "npx",');
88
+ console.log(' "args": ["-y", "@ainote/mcp"],');
89
+ console.log(' "env": {');
90
+ console.log(` "AINOTE_API_KEY": "${mcpKey}"`);
91
+ console.log(' }');
92
+ console.log(' }');
93
+ console.log(' }');
94
+ console.log(' }\n');
95
+ console.log(' Then restart Claude Desktop.\n');
96
+ } else {
97
+ // Fallback: print text content
98
+ if (Array.isArray(content)) {
99
+ for (const item of content) {
100
+ if (item.type === 'text') {
101
+ console.log(`\n${item.text}\n`);
102
+ }
103
+ }
104
+ } else {
105
+ console.log('\n Account created! Check the response for your API key.\n');
106
+ }
107
+ }
108
+ } catch (error) {
109
+ if (error.response?.data) {
110
+ console.error(`\n Error: ${JSON.stringify(error.response.data)}\n`);
111
+ } else {
112
+ console.error(`\n Error: ${error.message}\n`);
113
+ }
114
+ process.exit(1);
115
+ } finally {
116
+ rl.close();
117
+ }
118
+ }
@@ -5,6 +5,8 @@ import { getConfig } from './config-manager.js';
5
5
  import { createApiClient } from '../utils/api-client.js';
6
6
  import { getSharedTools } from '../tools/shared-tools.js';
7
7
  import { getChatGptTools } from '../tools/chatgpt-tools.js';
8
+ import { getDevDocTools } from '../tools/dev-doc-tools.js';
9
+ import { getOnboardingTools } from '../tools/onboarding-tools.js';
8
10
  import { toErrorContent } from '../tools/internal/formatters.js';
9
11
 
10
12
  function buildServerInfo(profile) {
@@ -15,7 +17,10 @@ function buildServerInfo(profile) {
15
17
  }
16
18
 
17
19
  function registerTools(registry, { includeChatGpt }) {
20
+ // Onboarding tools are always registered (work without API key)
21
+ registry.registerMany(getOnboardingTools());
18
22
  registry.registerMany(getSharedTools());
23
+ registry.registerMany(getDevDocTools());
19
24
 
20
25
  if (includeChatGpt) {
21
26
  registry.registerMany(getChatGptTools());
@@ -0,0 +1,255 @@
1
+ import { toSuccessContent } from './internal/formatters.js';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ function listDevDocsDefinition() {
6
+ return {
7
+ name: 'list_dev_docs',
8
+ description: `List your synced dev documents stored in AI Note cloud.
9
+
10
+ Shows all documents under dev/ category including memory files, AI configs, and project docs.
11
+ Use this to see what's synced and which files have local_path set for multi-device sync.
12
+
13
+ Categories: memory, claude, cursor, windsurf, env, docs, or custom subcategories.`,
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ category: {
18
+ type: 'string',
19
+ description: 'Subcategory filter (claude, cursor, windsurf, copilot, docs, etc.)'
20
+ },
21
+ search: {
22
+ type: 'string',
23
+ description: 'Search keyword in document title'
24
+ },
25
+ content_type: {
26
+ type: 'string',
27
+ enum: ['markdown', 'json', 'yaml', 'text'],
28
+ description: 'Filter by content type'
29
+ }
30
+ }
31
+ }
32
+ };
33
+ }
34
+
35
+ function getDevDocDefinition() {
36
+ return {
37
+ name: 'get_dev_doc',
38
+ description: 'Get a single dev document by title or id. Returns full content.',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ title: { type: 'string', description: 'Document title (e.g., project-a-memory.md)' },
43
+ id: { type: 'string', description: 'Document UUID' },
44
+ category: { type: 'string', description: 'Subcategory (claude, cursor, etc.) to disambiguate' },
45
+ include_versions: { type: 'boolean', description: 'Include version history (default: false)' }
46
+ }
47
+ }
48
+ };
49
+ }
50
+
51
+ function createDevDocDefinition() {
52
+ return {
53
+ name: 'create_dev_doc',
54
+ description: `Save a file to AI Note cloud for multi-device sync.
55
+
56
+ PRIMARY USE CASES:
57
+ - Memory files: ~/.claude/projects/.../memory/MEMORY.md
58
+ - AI configs: CLAUDE.md, .cursorrules, .windsurfrules (not in git)
59
+ - Project docs: architecture notes, planning docs
60
+
61
+ Set local_path to enable pull_dev_docs auto-sync on other devices.
62
+ Categories: memory | claude | cursor | env | docs`,
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ title: { type: 'string', description: 'Document title (e.g., project-a-claude.md)' },
67
+ content: { type: 'string', description: 'Document content (markdown, json, yaml)' },
68
+ category: {
69
+ type: 'string',
70
+ description: 'Subcategory (claude, cursor, windsurf, copilot, docs). Default: docs'
71
+ },
72
+ content_type: {
73
+ type: 'string',
74
+ enum: ['markdown', 'json', 'yaml', 'text'],
75
+ description: 'Content type. Auto-detected from title extension if omitted.'
76
+ },
77
+ local_path: {
78
+ type: 'string',
79
+ description: 'Absolute local file path for multi-device sync (e.g., ~/.claude/projects/.../MEMORY.md)'
80
+ }
81
+ },
82
+ required: ['title', 'content']
83
+ }
84
+ };
85
+ }
86
+
87
+ function updateDevDocDefinition() {
88
+ return {
89
+ name: 'update_dev_doc',
90
+ description: 'Update a dev document. Supports replace, append, or prepend modes.',
91
+ inputSchema: {
92
+ type: 'object',
93
+ properties: {
94
+ title: { type: 'string', description: 'Document title' },
95
+ id: { type: 'string', description: 'Document UUID' },
96
+ category: { type: 'string', description: 'Subcategory to disambiguate' },
97
+ content: { type: 'string', description: 'New content' },
98
+ mode: {
99
+ type: 'string',
100
+ enum: ['replace', 'append', 'prepend'],
101
+ description: 'Update mode (default: replace)'
102
+ },
103
+ local_path: {
104
+ type: 'string',
105
+ description: 'Update the local file path for multi-device sync'
106
+ }
107
+ },
108
+ required: ['content']
109
+ }
110
+ };
111
+ }
112
+
113
+ function deleteDevDocDefinition() {
114
+ return {
115
+ name: 'delete_dev_doc',
116
+ description: 'Delete a dev document (soft delete).',
117
+ inputSchema: {
118
+ type: 'object',
119
+ properties: {
120
+ title: { type: 'string', description: 'Document title' },
121
+ id: { type: 'string', description: 'Document UUID' },
122
+ category: { type: 'string', description: 'Subcategory to disambiguate' }
123
+ }
124
+ }
125
+ };
126
+ }
127
+
128
+ function pullDevDocsDefinition() {
129
+ return {
130
+ name: 'pull_dev_docs',
131
+ description: `Restore all synced files to THIS device. Writes files to their local paths on disk.
132
+
133
+ USE THIS WHEN:
134
+ - Setting up a new machine (desktop, laptop)
135
+ - Reinstalled macOS / fresh environment
136
+ - Want to sync latest versions of memory/config files from cloud
137
+
138
+ WHAT HAPPENS:
139
+ 1. Fetches all dev docs with local_path set
140
+ 2. Creates missing parent directories
141
+ 3. Writes content to each local_path on this machine
142
+
143
+ Run once after installing ainote MCP on a new device to restore everything.`,
144
+ inputSchema: {
145
+ type: 'object',
146
+ properties: {
147
+ category: {
148
+ type: 'string',
149
+ description: 'Subcategory filter. Omit to pull all docs with local_path.'
150
+ }
151
+ }
152
+ }
153
+ };
154
+ }
155
+
156
+ function listDevCategoriesDefinition() {
157
+ return {
158
+ name: 'list_dev_categories',
159
+ description: 'List all subcategories under dev/. Shows document count per category.',
160
+ inputSchema: {
161
+ type: 'object',
162
+ properties: {}
163
+ }
164
+ };
165
+ }
166
+
167
+ export function getDevDocTools() {
168
+ return [
169
+ {
170
+ definition: listDevDocsDefinition(),
171
+ handler: async (args, { apiClient }) => {
172
+ const result = await apiClient.callTool('list_dev_docs', args);
173
+ return result;
174
+ }
175
+ },
176
+ {
177
+ definition: getDevDocDefinition(),
178
+ handler: async (args, { apiClient }) => {
179
+ const result = await apiClient.callTool('get_dev_doc', args);
180
+ return result;
181
+ }
182
+ },
183
+ {
184
+ definition: createDevDocDefinition(),
185
+ handler: async (args, { apiClient }) => {
186
+ const result = await apiClient.callTool('create_dev_doc', args);
187
+ return result;
188
+ }
189
+ },
190
+ {
191
+ definition: updateDevDocDefinition(),
192
+ handler: async (args, { apiClient }) => {
193
+ const result = await apiClient.callTool('update_dev_doc', args);
194
+ return result;
195
+ }
196
+ },
197
+ {
198
+ definition: deleteDevDocDefinition(),
199
+ handler: async (args, { apiClient }) => {
200
+ const result = await apiClient.callTool('delete_dev_doc', args);
201
+ return result;
202
+ }
203
+ },
204
+ {
205
+ definition: pullDevDocsDefinition(),
206
+ handler: async (args, { apiClient }) => {
207
+ const result = await apiClient.callTool('pull_dev_docs', args);
208
+
209
+ // Extract docs from resource data and write to local paths
210
+ let written = [];
211
+ let skipped = [];
212
+ let errors = [];
213
+
214
+ try {
215
+ const resourceText = result?.content?.find(c => c.type === 'resource')?.resource?.text;
216
+ if (resourceText) {
217
+ const data = JSON.parse(resourceText);
218
+ for (const doc of data.docs || []) {
219
+ if (!doc.local_path || !doc.content) {
220
+ skipped.push(doc.title);
221
+ continue;
222
+ }
223
+ const expandedPath = doc.local_path.replace(/^~/, process.env.HOME || '');
224
+ try {
225
+ const dir = path.dirname(expandedPath);
226
+ fs.mkdirSync(dir, { recursive: true });
227
+ fs.writeFileSync(expandedPath, doc.content, 'utf8');
228
+ written.push(`✅ ${doc.title} → ${expandedPath}`);
229
+ } catch (e) {
230
+ errors.push(`❌ ${doc.title}: ${e.message}`);
231
+ }
232
+ }
233
+ }
234
+ } catch (e) {
235
+ errors.push(`Parse error: ${e.message}`);
236
+ }
237
+
238
+ const summary = [
239
+ written.length ? `Written (${written.length}):\n${written.join('\n')}` : null,
240
+ skipped.length ? `Skipped (no local_path): ${skipped.join(', ')}` : null,
241
+ errors.length ? `Errors:\n${errors.join('\n')}` : null,
242
+ ].filter(Boolean).join('\n\n');
243
+
244
+ return [{ type: 'text', text: summary || 'No docs with local_path found.' }];
245
+ }
246
+ },
247
+ {
248
+ definition: listDevCategoriesDefinition(),
249
+ handler: async (_args, { apiClient }) => {
250
+ const result = await apiClient.callTool('list_dev_categories', {});
251
+ return result;
252
+ }
253
+ }
254
+ ];
255
+ }
@@ -1,3 +1,5 @@
1
1
  export { getSharedTools } from './shared-tools.js';
2
2
  export { getChatGptTools } from './chatgpt-tools.js';
3
+ export { getDevDocTools } from './dev-doc-tools.js';
4
+ export { getOnboardingTools } from './onboarding-tools.js';
3
5
  export { toSuccessContent, toErrorContent } from './internal/formatters.js';
@@ -35,3 +35,46 @@ export function toErrorContent(error) {
35
35
  isError: true
36
36
  };
37
37
  }
38
+
39
+ /**
40
+ * Parse structured data from MCP response
41
+ * Looks for JSON resource in content array and extracts structured data
42
+ */
43
+ export function parseStructuredData(result) {
44
+ if (!result?.content || !Array.isArray(result.content)) {
45
+ return null;
46
+ }
47
+
48
+ // Look for resource content with JSON data
49
+ for (const item of result.content) {
50
+ if (item.type === 'resource' && item.resource?.mimeType === 'application/json') {
51
+ try {
52
+ return JSON.parse(item.resource.text);
53
+ } catch (error) {
54
+ console.error('Failed to parse structured data:', error);
55
+ return null;
56
+ }
57
+ }
58
+ }
59
+
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Enhance result with structured data annotation
65
+ * Adds structured data to the response while preserving original text
66
+ */
67
+ export function enhanceWithStructuredData(result) {
68
+ const structuredData = parseStructuredData(result);
69
+
70
+ if (structuredData) {
71
+ // Add metadata about structured data availability
72
+ return {
73
+ ...result,
74
+ _structuredData: structuredData,
75
+ _hasStructuredData: true
76
+ };
77
+ }
78
+
79
+ return result;
80
+ }
@@ -0,0 +1,124 @@
1
+ import axios from 'axios';
2
+ import { getConfig } from '../core/config-manager.js';
3
+ import { toErrorContent } from './internal/formatters.js';
4
+
5
+ /**
6
+ * Onboarding tools for signup/login without API key.
7
+ * These tools call the Rails API directly (no auth headers).
8
+ */
9
+
10
+ function signupDefinition() {
11
+ return {
12
+ name: 'signup_and_get_key',
13
+ description: `Create a new AI Note account and get an MCP API key. No authentication required.
14
+ Use this if you don't have an account yet. After getting the key, add it to your MCP config and restart.`,
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ email: { type: 'string', description: 'Email address for the new account' },
19
+ password: { type: 'string', description: 'Password (minimum 6 characters)' },
20
+ name: { type: 'string', description: 'Display name (optional)' }
21
+ },
22
+ required: ['email', 'password']
23
+ }
24
+ };
25
+ }
26
+
27
+ function loginDefinition() {
28
+ return {
29
+ name: 'login_and_get_key',
30
+ description: `Log in to an existing AI Note account and get an MCP API key. No authentication required.
31
+ Use this if you already have an account but need your MCP key.`,
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ email: { type: 'string', description: 'Your account email address' },
36
+ password: { type: 'string', description: 'Your account password' }
37
+ },
38
+ required: ['email', 'password']
39
+ }
40
+ };
41
+ }
42
+
43
+ function setupGuideDefinition() {
44
+ return {
45
+ name: 'get_setup_guide',
46
+ description: 'Get instructions for setting up AI Note MCP in Claude Desktop, Cursor, or other MCP clients. Includes CLI signup method (npx @ainote/mcp signup). No authentication required.',
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {}
50
+ }
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Call Rails MCP API directly without auth headers (for onboarding tools).
56
+ */
57
+ async function callOnboardingTool(toolName, args = {}) {
58
+ const config = getConfig();
59
+ const rpcRequest = {
60
+ jsonrpc: '2.0',
61
+ id: Date.now(),
62
+ method: 'tools/call',
63
+ params: { name: toolName, arguments: args }
64
+ };
65
+
66
+ const response = await axios.post(`${config.api.url}/api/mcp`, rpcRequest, {
67
+ headers: { 'Content-Type': 'application/json' },
68
+ timeout: 30000
69
+ });
70
+
71
+ if (response.data?.data?.error) {
72
+ const err = response.data.data.error;
73
+ throw new Error(err.message || 'Onboarding tool call failed');
74
+ }
75
+
76
+ // Rails wraps in { success: true, data: { jsonrpc, id, result } }
77
+ const rpcResult = response.data?.data?.result;
78
+ if (rpcResult) {
79
+ return rpcResult;
80
+ }
81
+
82
+ // Fallback: return the raw response content
83
+ return {
84
+ content: [{
85
+ type: 'text',
86
+ text: JSON.stringify(response.data, null, 2)
87
+ }]
88
+ };
89
+ }
90
+
91
+ export function getOnboardingTools() {
92
+ return [
93
+ {
94
+ definition: signupDefinition(),
95
+ handler: async (args) => {
96
+ try {
97
+ return await callOnboardingTool('signup_and_get_key', args);
98
+ } catch (error) {
99
+ return toErrorContent(error);
100
+ }
101
+ }
102
+ },
103
+ {
104
+ definition: loginDefinition(),
105
+ handler: async (args) => {
106
+ try {
107
+ return await callOnboardingTool('login_and_get_key', args);
108
+ } catch (error) {
109
+ return toErrorContent(error);
110
+ }
111
+ }
112
+ },
113
+ {
114
+ definition: setupGuideDefinition(),
115
+ handler: async () => {
116
+ try {
117
+ return await callOnboardingTool('get_setup_guide');
118
+ } catch (error) {
119
+ return toErrorContent(error);
120
+ }
121
+ }
122
+ }
123
+ ];
124
+ }
@@ -1,9 +1,40 @@
1
- import { toSuccessContent } from './internal/formatters.js';
1
+ import { toSuccessContent, enhanceWithStructuredData } from './internal/formatters.js';
2
2
 
3
3
  function listTasksDefinition() {
4
4
  return {
5
5
  name: 'list_tasks',
6
- description: 'List tasks from AI Note',
6
+ description: `List tasks from AI Note with natural language support and advanced filtering.
7
+
8
+ NATURAL LANGUAGE EXAMPLES:
9
+ â€Ē Location: "ė—Žė˜ë„ė—ė„œ", "ė„œėšļ뗐 ėžˆëŠ”", "강ë‚Ļ ęī€ë Ļ"
10
+ â€Ē Time: "ė˜Ī늘", "ėīëēˆ ėĢž", "ë‹ĪėŒ ėĢž", "ė§€ë‚œë‹Ž", "1뛔뗐"
11
+ â€Ē Importance: "ėĪ‘ėš”í•œ", "ėš°ė„ ėˆœėœ„ ë†’ė€", "ęļ‰í•œ"
12
+ â€Ē Status: "뙄ëĢŒí•œ", "ëŊļ뙄ë̌", "ė•ˆ 끝난"
13
+ â€Ē Special: "ë§ˆę°ėž ė§€ë‚œ", "ëŠĶė–īė§„", "ė˜Ī늘 마감", "ė•ŒëĶž ė„Īė •ëœ"
14
+ â€Ē Sort: "ë§ˆę°ėž ėˆœėœžëĄœ", "ėĩœė‹ ėˆœėœžëĄœ", "ė˜Ī래된 ėˆœėœžëĄœ"
15
+
16
+ QUERY EXAMPLES:
17
+ 1. "ė—Žė˜ë„ė—ė„œ ėīëēˆ ėĢž 마감ėļ ėĪ‘ėš”í•œ ëŊļ뙄ë̌ í• ėž"
18
+ → {location: "ė—Žė˜ë„", due_date_start: "2025-01-27", due_date_end: "2025-02-02",
19
+ is_important: true, status: "pending"}
20
+
21
+ 2. "ė§€ë‚œë‹Ž 뙄ëĢŒí•œ ė—…ëŽī ėđī테ęģ ëĶŽ í• ėžë“Īė„ 뙄ëĢŒėž ėˆœėœžëĄœ"
22
+ → {category_id: "...", status: "completed",
23
+ completed_date_start: "2024-12-01", completed_date_end: "2024-12-31",
24
+ sort_by: "completed_at", sort_order: "asc"}
25
+
26
+ 3. "ë§ˆę°ėž ė§€ë‚ŽëŠ”ë° 땄링 ė•ˆ 끝난 í• ėžë“Ī ë§ˆę°ėž ëđ ëĨļ ėˆœėœžëĄœ"
27
+ → {overdue: true, sort_by: "due_date", sort_order: "asc"}
28
+
29
+ TIME CALCULATIONS (today = 2025-01-27):
30
+ â€Ē "ė˜Ī늘" → due_today: true
31
+ â€Ē "ėīëēˆ ėĢž" → due_date_start: "2025-01-27", due_date_end: "2025-02-02"
32
+ â€Ē "ë‹ĪėŒ ėĢž" → due_date_start: "2025-02-03", due_date_end: "2025-02-09"
33
+ â€Ē "ėīëēˆ ë‹Ž" → due_date_start: "2025-01-01", due_date_end: "2025-01-31"
34
+ â€Ē "ė§€ë‚œ ėĢž" → completed_date_start: "2025-01-20", completed_date_end: "2025-01-26"
35
+ â€Ē "ė§€ë‚œ 닮" → completed_date_start: "2024-12-01", completed_date_end: "2024-12-31"
36
+
37
+ Returns structured data with all task fields including location, dates, and categories.`,
7
38
  inputSchema: {
8
39
  type: 'object',
9
40
  properties: {
@@ -12,6 +43,10 @@ function listTasksDefinition() {
12
43
  enum: ['pending', 'completed'],
13
44
  description: 'Filter by task status'
14
45
  },
46
+ is_important: {
47
+ type: 'boolean',
48
+ description: 'Filter by important tasks only'
49
+ },
15
50
  limit: {
16
51
  type: 'number',
17
52
  description: 'Maximum number of tasks to return (default: 25, max: 500)'
@@ -19,6 +54,60 @@ function listTasksDefinition() {
19
54
  search: {
20
55
  type: 'string',
21
56
  description: 'Search keyword in task content'
57
+ },
58
+ location: {
59
+ type: 'string',
60
+ description: 'Filter by location (partial match, e.g., "ė—Žė˜ë„", "ė„œėšļ")'
61
+ },
62
+ category_id: {
63
+ type: 'string',
64
+ description: 'Filter by category UUID'
65
+ },
66
+ due_date_start: {
67
+ type: 'string',
68
+ description: 'Filter tasks with due_date >= this date (ISO 8601 format)'
69
+ },
70
+ due_date_end: {
71
+ type: 'string',
72
+ description: 'Filter tasks with due_date <= this date (ISO 8601 format)'
73
+ },
74
+ completed_date_start: {
75
+ type: 'string',
76
+ description: 'Filter tasks completed >= this date (ISO 8601 format)'
77
+ },
78
+ completed_date_end: {
79
+ type: 'string',
80
+ description: 'Filter tasks completed <= this date (ISO 8601 format)'
81
+ },
82
+ created_date_start: {
83
+ type: 'string',
84
+ description: 'Filter tasks created >= this date (ISO 8601 format)'
85
+ },
86
+ created_date_end: {
87
+ type: 'string',
88
+ description: 'Filter tasks created <= this date (ISO 8601 format)'
89
+ },
90
+ overdue: {
91
+ type: 'boolean',
92
+ description: 'Filter overdue incomplete tasks (due_date < today)'
93
+ },
94
+ due_today: {
95
+ type: 'boolean',
96
+ description: 'Filter tasks due today'
97
+ },
98
+ has_notification: {
99
+ type: 'boolean',
100
+ description: 'Filter by notification enabled status'
101
+ },
102
+ sort_by: {
103
+ type: 'string',
104
+ enum: ['due_date', 'created_at', 'completed_at', 'updated_at', 'is_important'],
105
+ description: 'Sort field (default: created_at)'
106
+ },
107
+ sort_order: {
108
+ type: 'string',
109
+ enum: ['asc', 'desc'],
110
+ description: 'Sort order (default: desc)'
22
111
  }
23
112
  }
24
113
  }
@@ -28,7 +117,7 @@ function listTasksDefinition() {
28
117
  function createTaskDefinition() {
29
118
  return {
30
119
  name: 'create_task',
31
- description: 'Create a new task in AI Note',
120
+ description: 'Create a new task in AI Note with optional notification',
32
121
  inputSchema: {
33
122
  type: 'object',
34
123
  properties: {
@@ -47,6 +136,10 @@ function createTaskDefinition() {
47
136
  category_id: {
48
137
  type: 'string',
49
138
  description: 'Category ID'
139
+ },
140
+ notification_minutes_before: {
141
+ type: 'number',
142
+ description: 'Set notification X minutes before due_date (e.g., 5 for 5 minutes before, 60 for 1 hour before). Requires due_date to be set.'
50
143
  }
51
144
  },
52
145
  required: ['content']
@@ -117,7 +210,8 @@ export function getSharedTools() {
117
210
  definition: listTasksDefinition(),
118
211
  handler: async (args, { apiClient }) => {
119
212
  const result = await apiClient.callTool('list_tasks', args);
120
- return result; // Return full result with { content: [...] }
213
+ // Enhance result with parsed structured data
214
+ return enhanceWithStructuredData(result);
121
215
  }
122
216
  },
123
217
  {
@@ -8,6 +8,11 @@ function buildHeaders(authContext, config) {
8
8
 
9
9
  const contextType = authContext?.type ?? 'mcpKey';
10
10
 
11
+ if (contextType === 'anonymous') {
12
+ // No auth headers for anonymous mode (onboarding tools)
13
+ return headers;
14
+ }
15
+
11
16
  if (contextType === 'oauth') {
12
17
  const bearerToken = authContext?.token;
13
18
  if (!bearerToken) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ainote/mcp",
3
- "version": "1.0.3",
4
- "description": "MCP server for AI Note - Connect Claude Desktop to your AI Note tasks",
3
+ "version": "1.1.1",
4
+ "description": "MCP server for AI Note - Tasks, Dev Docs, and multi-device file sync (CLAUDE.md, memory files, Cursor rules, Windsurf rules)",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -21,9 +21,16 @@
21
21
  "modelcontextprotocol",
22
22
  "model-context-protocol",
23
23
  "claude",
24
+ "claude-code",
24
25
  "claude-desktop",
26
+ "cursor",
27
+ "windsurf",
28
+ "copilot",
25
29
  "ainote",
26
30
  "ai-note",
31
+ "dev-docs",
32
+ "claude-md",
33
+ "memory",
27
34
  "tasks",
28
35
  "task-management",
29
36
  "todo",
package/server.js CHANGED
@@ -80,12 +80,8 @@ app.post(config.http.messageEndpoint, async (req, res) => {
80
80
  return res.status(404).json({ error: `No transport found for sessionId ${sessionId}` });
81
81
  }
82
82
 
83
- try {
84
- const authContext = resolveAuthContext('sse', req.headers);
85
- req.auth = authContext;
86
- } catch (error) {
87
- return res.status(401).json({ error: error.message });
88
- }
83
+ const authContext = resolveAuthContext('sse', req.headers);
84
+ req.auth = authContext;
89
85
 
90
86
  try {
91
87
  await session.transport.handlePostMessage(req, res, req.body);