@idea-base/mcp-server 1.0.3 → 1.2.0

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.
Files changed (3) hide show
  1. package/README.md +12 -2
  2. package/package.json +1 -1
  3. package/src/index.js +195 -10
package/README.md CHANGED
@@ -85,8 +85,18 @@ Ask Claude to manage your projects:
85
85
  | Tool | Description |
86
86
  |------|-------------|
87
87
  | `log_time` | Log time against a task with notes |
88
- | `start_working` | Mark yourself as actively working on a task |
89
- | `stop_working` | Stop active work on a task |
88
+ | `start_working` | Mark yourself as actively working on a task (surfaces the saved resume context + recent work notes so a cold session re-orients) |
89
+ | `stop_working` | Stop active work on a task (optionally capture a `note` and/or `resume_context` on the way out) |
90
+
91
+ ### Activity & Audit Trail
92
+
93
+ For AI agents, these leave a durable trail of *what was done and why* on each task — so a future session (or a human reviewer) can see the reasoning, not just the final state.
94
+
95
+ | Tool | Description |
96
+ |------|-------------|
97
+ | `add_work_note` | Append a timestamped progress note to a task's activity log (append-only journal) |
98
+ | `add_comment` | Add a comment to a task's discussion thread (customer-visible by default) |
99
+ | `set_resume_context` | Overwrite the task's pinned "where I left off" block, read first on `start_working` |
90
100
 
91
101
  ### Products
92
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idea-base/mcp-server",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
4
  "description": "MCP Server for IDEA Base - AI-powered project management. Integrates with Claude Code, Cursor, and Windsurf.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -66,7 +66,7 @@ const tools = [
66
66
  },
67
67
  {
68
68
  name: 'create_project',
69
- description: 'Create a new project. Returns the created project with its ID.',
69
+ description: 'Create a new project. A TOP-LEVEL project requires product_id; a sub-project (parent_project_id set) inherits its parent\'s product. Returns the created project with its ID.',
70
70
  inputSchema: {
71
71
  type: 'object',
72
72
  properties: {
@@ -80,7 +80,11 @@ const tools = [
80
80
  },
81
81
  parent_project_id: {
82
82
  type: 'number',
83
- description: 'Parent project ID if this is a sub-project',
83
+ description: 'Parent project ID if this is a sub-project (inherits the parent\'s product)',
84
+ },
85
+ product_id: {
86
+ type: 'number',
87
+ description: 'Product this project belongs to. Required for a top-level project (no parent); omit for a sub-project. Find ids via list_products.',
84
88
  },
85
89
  },
86
90
  required: ['name'],
@@ -342,7 +346,7 @@ const tools = [
342
346
  },
343
347
  {
344
348
  name: 'stop_working',
345
- description: 'Mark that you have stopped working on a task. Use this when taking a break or switching to another task.',
349
+ description: 'Mark that you have stopped working on a task (break or switching tasks). Optionally capture your state on the way out: pass `note` to append a work note and/or `resume_context` to overwrite the resume block, so the next session can pick up where you left off. Both are recorded as author_kind="ai".',
346
350
  inputSchema: {
347
351
  type: 'object',
348
352
  properties: {
@@ -350,10 +354,95 @@ const tools = [
350
354
  type: 'number',
351
355
  description: 'The ID of the task to stop working on',
352
356
  },
357
+ note: {
358
+ type: 'string',
359
+ description: 'Optional work note to append before stopping (what you did, what is next).',
360
+ },
361
+ resume_context: {
362
+ type: 'string',
363
+ description: 'Optional resume-context block to overwrite before stopping (current state / next steps / where to look).',
364
+ },
353
365
  },
354
366
  required: ['task_id'],
355
367
  },
356
368
  },
369
+ {
370
+ name: 'add_work_note',
371
+ description: 'Append a timestamped work note (status update) to a task\'s activity log. Use this to record progress so a future session can pick up where you left off (e.g. "finished auth handler, tests green, next: wire the callback"). Notes are append-only and never overwrite each other. Set author_kind="human" ONLY when you are relaying a note dictated by the human user; use "ai" (the default) when leaving your own status update.',
372
+ inputSchema: {
373
+ type: 'object',
374
+ properties: {
375
+ task_id: {
376
+ type: 'number',
377
+ description: 'The ID of the task to add a work note to',
378
+ },
379
+ note: {
380
+ type: 'string',
381
+ description: 'The work note / status update text',
382
+ },
383
+ author_kind: {
384
+ type: 'string',
385
+ enum: ['ai', 'human'],
386
+ description: 'Who authored this note: "ai" (default, your own update) or "human" (you are relaying something the user said).',
387
+ },
388
+ is_internal: {
389
+ type: 'boolean',
390
+ description: 'Whether the note is internal-only (hidden from customers). Defaults to true for work notes.',
391
+ },
392
+ },
393
+ required: ['task_id', 'note'],
394
+ },
395
+ },
396
+ {
397
+ name: 'add_comment',
398
+ description: 'Add a comment to a task\'s discussion thread. Unlike work notes (progress journal), comments are for communication and are customer-visible by default. Set author_kind="human" when relaying the user\'s words; use "ai" (default) for your own.',
399
+ inputSchema: {
400
+ type: 'object',
401
+ properties: {
402
+ task_id: {
403
+ type: 'number',
404
+ description: 'The ID of the task to comment on',
405
+ },
406
+ comment: {
407
+ type: 'string',
408
+ description: 'The comment text',
409
+ },
410
+ author_kind: {
411
+ type: 'string',
412
+ enum: ['ai', 'human'],
413
+ description: 'Who authored this comment: "ai" (default) or "human" (relaying the user).',
414
+ },
415
+ is_internal: {
416
+ type: 'boolean',
417
+ description: 'Whether the comment is internal-only (hidden from customers). Defaults to false.',
418
+ },
419
+ },
420
+ required: ['task_id', 'comment'],
421
+ },
422
+ },
423
+ {
424
+ name: 'set_resume_context',
425
+ description: 'Set (overwrite) the task\'s resume context — a single pinned block describing the current state, what is done, what is next, and where to look. This is read first when a work session restarts cold (e.g. via start_working) so you can re-orient immediately. Update it at the end of a work session. Set author_kind="human" only when relaying the user\'s words; default "ai".',
426
+ inputSchema: {
427
+ type: 'object',
428
+ properties: {
429
+ task_id: {
430
+ type: 'number',
431
+ description: 'The ID of the task to set resume context for',
432
+ },
433
+ context: {
434
+ type: 'string',
435
+ description: 'The resume-context text (markdown ok): current state / next steps / where to look.',
436
+ },
437
+ author_kind: {
438
+ type: 'string',
439
+ enum: ['ai', 'human'],
440
+ description: 'Who authored this context: "ai" (default) or "human".',
441
+ },
442
+ },
443
+ required: ['task_id', 'context'],
444
+ },
445
+ },
357
446
  // Products tools
358
447
  {
359
448
  name: 'list_products',
@@ -385,7 +474,7 @@ const tools = [
385
474
  },
386
475
  {
387
476
  name: 'create_product',
388
- description: 'Create a new product. Products are top-level containers that can have multiple projects linked to them.',
477
+ description: 'Create a new product. Products are top-level containers for projects. Every product belongs to a customer (customer_id is required) — use an internal/own-company customer for internal work. Find customer ids via list_products (each shows its customer_id).',
389
478
  inputSchema: {
390
479
  type: 'object',
391
480
  properties: {
@@ -401,8 +490,12 @@ const tools = [
401
490
  type: 'string',
402
491
  description: 'GitHub repository URL for the product',
403
492
  },
493
+ customer_id: {
494
+ type: 'number',
495
+ description: 'Customer this product is for. REQUIRED — every product belongs to a customer.',
496
+ },
404
497
  },
405
- required: ['name'],
498
+ required: ['name', 'customer_id'],
406
499
  },
407
500
  },
408
501
  {
@@ -449,13 +542,14 @@ const toolHandlers = {
449
542
  };
450
543
  },
451
544
 
452
- async create_project({ name, description, parent_project_id }) {
545
+ async create_project({ name, description, parent_project_id, product_id }) {
453
546
  const project = await apiRequest('/projects', {
454
547
  method: 'POST',
455
548
  body: JSON.stringify({
456
549
  name,
457
550
  description,
458
551
  parent_project_id,
552
+ product_id,
459
553
  }),
460
554
  });
461
555
  return {
@@ -668,26 +762,116 @@ const toolHandlers = {
668
762
  body: JSON.stringify({ is_active: true }),
669
763
  });
670
764
 
765
+ // Orient a cold-starting session: surface the resume context + recent work notes.
766
+ let orientation = '';
767
+ try {
768
+ const task = await apiRequest(`/tasks/${task_id}`);
769
+ const lines = [];
770
+ if (task.resume_context) {
771
+ const by = task.resume_context_author_kind === 'ai' ? 'AI Agent' : 'human';
772
+ lines.push(`\n--- Resume context (last set by ${by}) ---\n${task.resume_context}`);
773
+ }
774
+ const notes = task.recent_work_notes || [];
775
+ if (notes.length) {
776
+ lines.push('\n--- Recent work notes (newest first) ---');
777
+ for (const n of notes) {
778
+ let text = '';
779
+ try { text = JSON.parse(n.event_data || '{}').note || ''; } catch {}
780
+ const who = n.author_kind === 'ai' ? 'AI' : (n.author_name || 'human');
781
+ lines.push(`• [${n.created_at}] (${who}) ${text}`);
782
+ }
783
+ }
784
+ if (lines.length) {
785
+ orientation = `\n\nHere is where you left off:\n${lines.join('\n')}`;
786
+ } else {
787
+ orientation = `\n\nNo resume context or work notes yet — consider leaving some with add_work_note / set_resume_context before you stop.`;
788
+ }
789
+ } catch {
790
+ // Non-fatal: assignment already succeeded.
791
+ }
792
+
671
793
  return {
672
794
  content: [
673
795
  {
674
796
  type: 'text',
675
- text: `Started working on task #${task_id}. Other team members can now see you are actively working on this task.`,
797
+ text: `Started working on task #${task_id}. Other team members can now see you are actively working on this task.${orientation}`,
676
798
  },
677
799
  ],
678
800
  };
679
801
  },
680
802
 
681
- async stop_working({ task_id }) {
803
+ async stop_working({ task_id, note, resume_context }) {
804
+ const captured = [];
805
+ if (note && note.trim()) {
806
+ await apiRequest(`/tasks/${task_id}/activity`, {
807
+ method: 'POST',
808
+ body: JSON.stringify({ note, author_kind: 'ai', is_internal: true }),
809
+ });
810
+ captured.push('work note');
811
+ }
812
+ if (resume_context !== undefined && resume_context !== null) {
813
+ await apiRequest(`/tasks/${task_id}`, {
814
+ method: 'PUT',
815
+ body: JSON.stringify({ resume_context, resume_context_author_kind: 'ai' }),
816
+ });
817
+ captured.push('resume context');
818
+ }
819
+
682
820
  await apiRequest(`/tasks/${task_id}/assignments?stop_only=true`, {
683
821
  method: 'DELETE',
684
822
  });
685
823
 
824
+ const capturedText = captured.length ? ` Saved ${captured.join(' and ')} for next time.` : '';
825
+ return {
826
+ content: [
827
+ {
828
+ type: 'text',
829
+ text: `Stopped working on task #${task_id}. You are still assigned to the task but no longer marked as actively working.${capturedText}`,
830
+ },
831
+ ],
832
+ };
833
+ },
834
+
835
+ async add_work_note({ task_id, note, author_kind = 'ai', is_internal = true }) {
836
+ const saved = await apiRequest(`/tasks/${task_id}/activity`, {
837
+ method: 'POST',
838
+ body: JSON.stringify({ note, author_kind, is_internal }),
839
+ });
840
+ return {
841
+ content: [
842
+ {
843
+ type: 'text',
844
+ text: `Work note added to task #${task_id} (as ${author_kind === 'ai' ? 'AI Agent' : 'human'}). Note id: ${saved.id}.`,
845
+ },
846
+ ],
847
+ };
848
+ },
849
+
850
+ async add_comment({ task_id, comment, author_kind = 'ai', is_internal = false }) {
851
+ const saved = await apiRequest(`/tasks/${task_id}/comments`, {
852
+ method: 'POST',
853
+ body: JSON.stringify({ comment, author_kind, is_internal }),
854
+ });
855
+ return {
856
+ content: [
857
+ {
858
+ type: 'text',
859
+ text: `Comment added to task #${task_id} (as ${author_kind === 'ai' ? 'AI Agent' : 'human'}). Comment id: ${saved.id}.`,
860
+ },
861
+ ],
862
+ };
863
+ },
864
+
865
+ async set_resume_context({ task_id, context, author_kind = 'ai' }) {
866
+ await apiRequest(`/tasks/${task_id}`, {
867
+ method: 'PUT',
868
+ body: JSON.stringify({ resume_context: context, resume_context_author_kind: author_kind }),
869
+ });
686
870
  return {
687
871
  content: [
688
872
  {
689
873
  type: 'text',
690
- text: `Stopped working on task #${task_id}. You are still assigned to the task but no longer marked as actively working.`,
874
+ text: `Resume context updated for task #${task_id} (as ${author_kind === 'ai' ? 'AI Agent' : 'human'}). The next session that calls start_working will read this first.`,
691
875
  },
692
876
  ],
693
877
  };
@@ -721,13 +905,14 @@ const toolHandlers = {
721
905
  };
722
906
  },
723
907
 
724
- async create_product({ name, description, github_repo_url }) {
908
+ async create_product({ name, description, github_repo_url, customer_id }) {
725
909
  const product = await apiRequest('/products', {
726
910
  method: 'POST',
727
911
  body: JSON.stringify({
728
912
  name,
729
913
  description,
730
914
  github_repo_url,
915
+ customer_id,
731
916
  }),
732
917
  });
733
918
  return {