@idea-base/mcp-server 1.3.0 → 2.0.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +29 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idea-base/mcp-server",
3
- "version": "1.3.0",
3
+ "version": "2.0.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
@@ -53,6 +53,19 @@ function resolveBaseUrl() {
53
53
  const API_BASE_URL = resolveBaseUrl();
54
54
  const API_KEY = (process.env.IDEA_BASE_API_KEY || '').trim();
55
55
 
56
+ // The agent identity this server declares on every request (X-On-Behalf-Of).
57
+ //
58
+ // It answers "who did this?" and nothing else — it carries no permission, no
59
+ // delegation and no elevation. Authorization stays entirely with the API key and its
60
+ // owner. Without it, work done by an agent is recorded as the key owner, which makes
61
+ // the owner the actor of his own notifications and silently suppresses them.
62
+ //
63
+ // Freeform by design, so a session may declare a different agent. The identity must
64
+ // already exist server-side as a non-human user, or EVERY call fails closed with 403
65
+ // AGENT_IDENTITY_NOT_RESOLVABLE / AGENT_IDENTITY_NOT_PERMITTED — deliberately loud,
66
+ // never a silent fallback to the key owner.
67
+ const AGENT_ID = (process.env.IDEA_BASE_AGENT_ID || 'claude_ai').trim();
68
+
56
69
  if (!API_KEY) {
57
70
  console.error('Error: IDEA_BASE_API_KEY environment variable is required');
58
71
  process.exit(1);
@@ -190,6 +203,7 @@ async function apiRequest(endpoint, options = {}) {
190
203
  signal: controller.signal,
191
204
  headers: {
192
205
  'Authorization': `Bearer ${API_KEY}`,
206
+ 'X-On-Behalf-Of': AGENT_ID,
193
207
  'Content-Type': 'application/json',
194
208
  'Accept': 'application/json',
195
209
  ...options.headers,
@@ -622,7 +636,7 @@ const tools = [
622
636
  },
623
637
  {
624
638
  name: 'stop_working',
625
- 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".',
639
+ 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.',
626
640
  inputSchema: {
627
641
  type: 'object',
628
642
  properties: {
@@ -644,7 +658,7 @@ const tools = [
644
658
  },
645
659
  {
646
660
  name: 'add_work_note',
647
- 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.',
661
+ 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.',
648
662
  inputSchema: {
649
663
  type: 'object',
650
664
  properties: {
@@ -656,11 +670,6 @@ const tools = [
656
670
  type: 'string',
657
671
  description: 'The work note / status update text',
658
672
  },
659
- author_kind: {
660
- type: 'string',
661
- enum: ['ai', 'human'],
662
- description: 'Who authored this note: "ai" (default, your own update) or "human" (you are relaying something the user said).',
663
- },
664
673
  is_internal: {
665
674
  type: 'boolean',
666
675
  description: 'Whether the note is internal-only (hidden from customers). Defaults to true for work notes.',
@@ -671,7 +680,7 @@ const tools = [
671
680
  },
672
681
  {
673
682
  name: 'add_comment',
674
- 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.',
683
+ 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.',
675
684
  inputSchema: {
676
685
  type: 'object',
677
686
  properties: {
@@ -683,11 +692,6 @@ const tools = [
683
692
  type: 'string',
684
693
  description: 'The comment text',
685
694
  },
686
- author_kind: {
687
- type: 'string',
688
- enum: ['ai', 'human'],
689
- description: 'Who authored this comment: "ai" (default) or "human" (relaying the user).',
690
- },
691
695
  is_internal: {
692
696
  type: 'boolean',
693
697
  description: 'Whether the comment is internal-only (hidden from customers). Defaults to false.',
@@ -698,7 +702,7 @@ const tools = [
698
702
  },
699
703
  {
700
704
  name: 'set_resume_context',
701
- 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".',
705
+ 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.',
702
706
  inputSchema: {
703
707
  type: 'object',
704
708
  properties: {
@@ -710,11 +714,6 @@ const tools = [
710
714
  type: 'string',
711
715
  description: 'The resume-context text (markdown ok): current state / next steps / where to look.',
712
716
  },
713
- author_kind: {
714
- type: 'string',
715
- enum: ['ai', 'human'],
716
- description: 'Who authored this context: "ai" (default) or "human".',
717
- },
718
717
  },
719
718
  required: ['task_id', 'context'],
720
719
  },
@@ -1100,14 +1099,14 @@ const toolHandlers = {
1100
1099
  if (note && note.trim()) {
1101
1100
  await apiRequest(`/tasks/${task_id}/activity`, {
1102
1101
  method: 'POST',
1103
- body: JSON.stringify({ note, author_kind: 'ai', is_internal: true }),
1102
+ body: JSON.stringify({ note, is_internal: true }),
1104
1103
  });
1105
1104
  captured.push('work note');
1106
1105
  }
1107
1106
  if (resume_context !== undefined && resume_context !== null) {
1108
1107
  await apiRequest(`/tasks/${task_id}`, {
1109
1108
  method: 'PUT',
1110
- body: JSON.stringify({ resume_context, resume_context_author_kind: 'ai' }),
1109
+ body: JSON.stringify({ resume_context }),
1111
1110
  });
1112
1111
  captured.push('resume context');
1113
1112
  }
@@ -1127,46 +1126,46 @@ const toolHandlers = {
1127
1126
  };
1128
1127
  },
1129
1128
 
1130
- async add_work_note({ task_id, note, author_kind = 'ai', is_internal = true }) {
1129
+ async add_work_note({ task_id, note, is_internal = true }) {
1131
1130
  const saved = await apiRequest(`/tasks/${task_id}/activity`, {
1132
1131
  method: 'POST',
1133
- body: JSON.stringify({ note, author_kind, is_internal }),
1132
+ body: JSON.stringify({ note, is_internal }),
1134
1133
  });
1135
1134
  return {
1136
1135
  content: [
1137
1136
  {
1138
1137
  type: 'text',
1139
- text: `Work note added to task #${task_id} (as ${author_kind === 'ai' ? 'AI Agent' : 'human'}). Note id: ${saved.id}.`,
1138
+ text: `Work note added to task #${task_id} (as ${AGENT_ID}). Note id: ${saved.id}.`,
1140
1139
  },
1141
1140
  ],
1142
1141
  };
1143
1142
  },
1144
1143
 
1145
- async add_comment({ task_id, comment, author_kind = 'ai', is_internal = false }) {
1144
+ async add_comment({ task_id, comment, is_internal = false }) {
1146
1145
  const saved = await apiRequest(`/tasks/${task_id}/comments`, {
1147
1146
  method: 'POST',
1148
- body: JSON.stringify({ comment, author_kind, is_internal }),
1147
+ body: JSON.stringify({ comment, is_internal }),
1149
1148
  });
1150
1149
  return {
1151
1150
  content: [
1152
1151
  {
1153
1152
  type: 'text',
1154
- text: `Comment added to task #${task_id} (as ${author_kind === 'ai' ? 'AI Agent' : 'human'}). Comment id: ${saved.id}.`,
1153
+ text: `Comment added to task #${task_id} (as ${AGENT_ID}). Comment id: ${saved.id}.`,
1155
1154
  },
1156
1155
  ],
1157
1156
  };
1158
1157
  },
1159
1158
 
1160
- async set_resume_context({ task_id, context, author_kind = 'ai' }) {
1159
+ async set_resume_context({ task_id, context }) {
1161
1160
  await apiRequest(`/tasks/${task_id}`, {
1162
1161
  method: 'PUT',
1163
- body: JSON.stringify({ resume_context: context, resume_context_author_kind: author_kind }),
1162
+ body: JSON.stringify({ resume_context: context }),
1164
1163
  });
1165
1164
  return {
1166
1165
  content: [
1167
1166
  {
1168
1167
  type: 'text',
1169
- 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.`,
1168
+ text: `Resume context updated for task #${task_id} (as ${AGENT_ID}). The next session that calls start_working will read this first.`,
1170
1169
  },
1171
1170
  ],
1172
1171
  };