@hanhnd/agent-kit 1.0.40 → 1.0.41

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.
@@ -1,6 +1,6 @@
1
- export declare const HANDOFF_TYPES: readonly ["brainstorm", "clarification", "plan", "ticket", "research", "scenario", "investigation", "code"];
1
+ export declare const HANDOFF_TYPES: readonly ["brainstorm", "clarification", "plan", "ticket", "research", "investigation", "code"];
2
2
  type HandoffType = (typeof HANDOFF_TYPES)[number];
3
- type CanonicalHandoffType = 'brainstorm' | 'clarification' | 'plan' | 'ticket' | 'research' | 'scenario' | 'investigation' | 'code';
3
+ type CanonicalHandoffType = 'brainstorm' | 'clarification' | 'plan' | 'ticket' | 'research' | 'investigation' | 'code';
4
4
  export interface SavedHandoffFolderLocation {
5
5
  featureSlug: string;
6
6
  canonicalType: CanonicalHandoffType;
@@ -5,7 +5,6 @@ export const HANDOFF_TYPES = [
5
5
  'plan',
6
6
  'ticket',
7
7
  'research',
8
- 'scenario',
9
8
  'investigation',
10
9
  'code',
11
10
  ];
package/dist/mcp/agent.js CHANGED
@@ -49,11 +49,18 @@ function initAgentLog(workspaceRoot, slug, agent) {
49
49
  return { logPath: path.relative(workspaceRoot, logFile), logStream };
50
50
  }
51
51
  export function registerAgentTools(server) {
52
- server.tool('kit_trigger_agent', '!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff folder (.agent-kit/handoffs/feature-slug/plan).', {
53
- agent: z.enum(['gemini', 'claude', 'codex']).describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
54
- task: z
55
- .string()
56
- .describe('Task message or path to a handoff folder (e.g., ".agent-kit/handoffs/feature-slug/plan")'),
52
+ server.registerTool('kit_trigger_agent', {
53
+ title: 'Delegate to External Agent',
54
+ description: '!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff folder (.agent-kit/handoffs/feature-slug/plan).',
55
+ inputSchema: {
56
+ agent: z
57
+ .enum(['gemini', 'claude', 'codex'])
58
+ .describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
59
+ task: z
60
+ .string()
61
+ .describe('Task message or path to a handoff folder (e.g., ".agent-kit/handoffs/feature-slug/plan")'),
62
+ },
63
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
57
64
  }, async ({ agent, task }, extra) => {
58
65
  const workspaceRoot = getWorkspaceRoot();
59
66
  let job;
package/dist/mcp/core.js CHANGED
@@ -46,14 +46,19 @@ export function registerCoreTools(server) {
46
46
  // Writes handoff artifacts to the workspace as a folder of files
47
47
  // Returns the saved folder path to use in next-step instructions
48
48
  // ═══════════════════════════════════════════════════════════════
49
- server.tool('kit_save_handoff', `Save a handoff artifact to .agent-kit/handoffs/. Returns the saved folder path to use in next-step instructions. Do NOT append version numbers (v2, v3, etc.) to the slug.`, {
50
- type: z.enum(HANDOFF_TYPES).describe('Handoff type'),
51
- files: z
52
- .record(z.string(), z.string())
53
- .describe('Markdown files to write into the handoff folder. May include one or many files per call; repeated calls with the same type and slug add or overwrite the provided files. Filenames must match /^[A-Za-z0-9_-]+\\.md$/.'),
54
- slug: z
55
- .string()
56
- .describe('Short identifier for the folder, e.g. "user-auth" or "PROJ-123". Do NOT append version numbers (v2, v3, etc.).'),
49
+ server.registerTool('kit_save_handoff', {
50
+ title: 'Save Handoff Artifact',
51
+ description: `Save a handoff artifact to .agent-kit/handoffs/. Returns the saved folder path to use in next-step instructions. Do NOT append version numbers (v2, v3, etc.) to the slug.`,
52
+ inputSchema: {
53
+ type: z.enum(HANDOFF_TYPES).describe('Handoff type'),
54
+ files: z
55
+ .record(z.string(), z.string())
56
+ .describe('Markdown files to write into the handoff folder. May include one or many files per call; repeated calls with the same type and slug add or overwrite the provided files. Filenames must match /^[A-Za-z0-9_-]+\\.md$/.'),
57
+ slug: z
58
+ .string()
59
+ .describe('Short identifier for the folder, e.g. "user-auth" or "PROJ-123". Do NOT append version numbers (v2, v3, etc.).'),
60
+ },
61
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
57
62
  }, async (args) => handleSaveHandoff(args, getWorkspaceRoot()));
58
63
  }
59
64
  // Export DEFAULT_EXTENSIONS for backward compatibility
@@ -74,14 +74,19 @@ async function callRestApi(url, auth, accept = 'application/json') {
74
74
  }
75
75
  export function registerIntegrationTools(server) {
76
76
  // TOOL: GET BITBUCKET PR
77
- server.tool('kit_get_bitbucket_pr', 'Get Bitbucket PR details and optionally the diff. Accepts a full PR URL or a numeric PR ID with workspace + repoSlug.', {
78
- input: z.string().describe('Bitbucket PR URL or numeric PR ID'),
79
- workspace: z
80
- .string()
81
- .optional()
82
- .describe('Bitbucket workspace slug (required for numeric ID if BITBUCKET_DEFAULT_WORKSPACE not set)'),
83
- repoSlug: z.string().optional().describe('Bitbucket repo slug (required for numeric ID)'),
84
- includeDiff: z.boolean().optional().default(true).describe('Include unified diff in response'),
77
+ server.registerTool('kit_get_bitbucket_pr', {
78
+ title: 'Get Bitbucket PR',
79
+ description: 'Get Bitbucket PR details and optionally the diff. Accepts a full PR URL or a numeric PR ID with workspace + repoSlug.',
80
+ inputSchema: {
81
+ input: z.string().describe('Bitbucket PR URL or numeric PR ID'),
82
+ workspace: z
83
+ .string()
84
+ .optional()
85
+ .describe('Bitbucket workspace slug (required for numeric ID if BITBUCKET_DEFAULT_WORKSPACE not set)'),
86
+ repoSlug: z.string().optional().describe('Bitbucket repo slug (required for numeric ID)'),
87
+ includeDiff: z.boolean().optional().default(true).describe('Include unified diff in response'),
88
+ },
89
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
85
90
  }, async ({ input, workspace, repoSlug, includeDiff }) => {
86
91
  try {
87
92
  let ws;
@@ -145,8 +150,13 @@ ${pr.description || 'No description'}`;
145
150
  }
146
151
  });
147
152
  // TOOL: JIRA GET TICKET
148
- server.tool('kit_jira_get_ticket', 'Get ticket details from Jira using the Atlassian REST API', {
149
- ticketId: z.string().describe('Jira ticket ID (e.g., PROJ-123)'),
153
+ server.registerTool('kit_jira_get_ticket', {
154
+ title: 'Get Jira Ticket',
155
+ description: 'Get ticket details from Jira using the Atlassian REST API',
156
+ inputSchema: {
157
+ ticketId: z.string().describe('Jira ticket ID (e.g., PROJ-123)'),
158
+ },
159
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
150
160
  }, async ({ ticketId }) => {
151
161
  try {
152
162
  const safeTicketId = ticketId.match(/^[A-Z]+-\d+$/)?.[0];
@@ -12,9 +12,14 @@ import { loadGlobalSettings, resolveConversationDigestConfig } from '../core/con
12
12
  */
13
13
  function registerMemoryToolHandlers(server, subsystem, workspaceRoot = process.cwd()) {
14
14
  const { config } = subsystem;
15
- server.tool('kit_memory_search', 'Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.', {
16
- query: z.string().min(1).describe('Keyword-only search query with stopwords removed'),
17
- top_k: z.number().int().positive().optional().describe('Number of results to return'),
15
+ server.registerTool('kit_memory_search', {
16
+ title: 'Search Memory',
17
+ description: 'Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.',
18
+ inputSchema: {
19
+ query: z.string().min(1).describe('Keyword-only search query with stopwords removed'),
20
+ top_k: z.number().int().positive().optional().describe('Number of results to return'),
21
+ },
22
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
18
23
  }, async ({ query, top_k }) => {
19
24
  try {
20
25
  if (!subsystem.indexer || !subsystem.store) {
@@ -44,9 +49,14 @@ function registerMemoryToolHandlers(server, subsystem, workspaceRoot = process.c
44
49
  return mcpText(`kit_memory_search failed: ${formatError(err)}`);
45
50
  }
46
51
  });
47
- server.tool('kit_memory_recent', 'Return the N most recently updated memory sources. Optionally filter by source type. Use for temporal questions like "what did we do last session" or "recent".', {
48
- n: z.number().int().positive().max(50).optional().describe('How many recent sources to return (default 5)'),
49
- source_type: z.enum(SOURCE_TYPES).optional().describe('Optional source type filter'),
52
+ server.registerTool('kit_memory_recent', {
53
+ title: 'Recent Memory Sources',
54
+ description: 'Return the N most recently updated memory sources. Optionally filter by source type. Use for temporal questions like "what did we do last session" or "recent".',
55
+ inputSchema: {
56
+ n: z.number().int().positive().max(50).optional().describe('How many recent sources to return (default 5)'),
57
+ source_type: z.enum(SOURCE_TYPES).optional().describe('Optional source type filter'),
58
+ },
59
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
50
60
  }, async ({ n, source_type }) => {
51
61
  try {
52
62
  if (!subsystem.store) {
@@ -75,8 +85,13 @@ function registerMemoryToolHandlers(server, subsystem, workspaceRoot = process.c
75
85
  return mcpText(`kit_memory_recent failed: ${formatError(err)}`);
76
86
  }
77
87
  });
78
- server.tool('kit_memory_save', 'Save content to wiki/raw for inclusion after the next /wiki compile.', {
79
- content: z.string().min(1).describe('Content to save to memory'),
88
+ server.registerTool('kit_memory_save', {
89
+ title: 'Save to Memory',
90
+ description: 'Save content to wiki/raw for inclusion after the next /wiki compile.',
91
+ inputSchema: {
92
+ content: z.string().min(1).describe('Content to save to memory'),
93
+ },
94
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
80
95
  }, async ({ content }) => {
81
96
  try {
82
97
  if (!subsystem.indexer) {
@@ -100,9 +115,14 @@ function registerMemoryToolHandlers(server, subsystem, workspaceRoot = process.c
100
115
  });
101
116
  }
102
117
  });
103
- server.tool('kit_memory_digest_init', 'Initialize or toggle local conversation digesting. Downloads/cache-loads the pinned local model only when explicitly invoked.', {
104
- model_id: z.string().optional().describe('Digest model id'),
105
- enabled: z.boolean().optional().describe('Enable or disable conversation digesting'),
118
+ server.registerTool('kit_memory_digest_init', {
119
+ title: 'Initialize Conversation Digest',
120
+ description: 'Initialize or toggle local conversation digesting. Downloads/cache-loads the pinned local model only when explicitly invoked.',
121
+ inputSchema: {
122
+ model_id: z.string().optional().describe('Digest model id'),
123
+ enabled: z.boolean().optional().describe('Enable or disable conversation digesting'),
124
+ },
125
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
106
126
  }, async ({ model_id, enabled }) => {
107
127
  try {
108
128
  const existingConfig = resolveConversationDigestConfig(loadGlobalSettings());
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import * as assert from 'node:assert/strict';
2
+ import { describe, test } from 'node:test';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
6
+ import { registerCoreTools } from './core.js';
7
+ import { registerAgentTools } from './agent.js';
8
+ import { registerIntegrationTools } from './integration.js';
9
+ const EXPECTED_NON_MEMORY_TOOLS = [
10
+ 'kit_save_handoff',
11
+ 'kit_trigger_agent',
12
+ 'kit_get_bitbucket_pr',
13
+ 'kit_jira_get_ticket',
14
+ ];
15
+ async function buildClientWithTools() {
16
+ const server = new McpServer({ name: 'test-kit', version: '0.0.0' });
17
+ registerIntegrationTools(server);
18
+ registerCoreTools(server);
19
+ registerAgentTools(server);
20
+ const [serverTransport, clientTransport] = InMemoryTransport.createLinkedPair();
21
+ const client = new Client({ name: 'test-client', version: '0.0.0' });
22
+ await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
23
+ return client;
24
+ }
25
+ describe('MCP tool registration smoke tests', () => {
26
+ test('R1: all 4 non-memory tool names are registered', async () => {
27
+ const client = await buildClientWithTools();
28
+ const { tools } = await client.listTools();
29
+ const names = tools.map((t) => t.name);
30
+ for (const expected of EXPECTED_NON_MEMORY_TOOLS) {
31
+ assert.ok(names.includes(expected), `expected tool "${expected}" to be registered, got: ${names.join(', ')}`);
32
+ }
33
+ });
34
+ test('R2: each non-memory tool has a non-empty description and inputSchema (BC3)', async () => {
35
+ const client = await buildClientWithTools();
36
+ const { tools } = await client.listTools();
37
+ const byName = Object.fromEntries(tools.map((t) => [t.name, t]));
38
+ for (const name of EXPECTED_NON_MEMORY_TOOLS) {
39
+ const tool = byName[name];
40
+ assert.ok(tool, `tool "${name}" not found`);
41
+ assert.ok(tool.description && tool.description.length > 0, `tool "${name}" must have a non-empty description`);
42
+ assert.ok(tool.inputSchema, `tool "${name}" must have an inputSchema`);
43
+ }
44
+ });
45
+ test('R3: kit_trigger_agent (readOnlyHint:false) has correct annotations (BC5)', async () => {
46
+ const client = await buildClientWithTools();
47
+ const { tools } = await client.listTools();
48
+ const tool = tools.find((t) => t.name === 'kit_trigger_agent');
49
+ assert.ok(tool, 'kit_trigger_agent must be registered');
50
+ assert.ok(tool.annotations, 'kit_trigger_agent must have annotations');
51
+ assert.equal(tool.annotations.readOnlyHint, false, 'readOnlyHint must be false');
52
+ assert.equal(tool.annotations.destructiveHint, true, 'destructiveHint must be true');
53
+ assert.equal(tool.annotations.idempotentHint, false, 'idempotentHint must be false');
54
+ assert.equal(tool.annotations.openWorldHint, true, 'openWorldHint must be true');
55
+ });
56
+ test('R4: kit_save_handoff (readOnlyHint:false) has correct annotations (BC5)', async () => {
57
+ const client = await buildClientWithTools();
58
+ const { tools } = await client.listTools();
59
+ const tool = tools.find((t) => t.name === 'kit_save_handoff');
60
+ assert.ok(tool, 'kit_save_handoff must be registered');
61
+ assert.ok(tool.annotations, 'kit_save_handoff must have annotations');
62
+ assert.equal(tool.annotations.readOnlyHint, false, 'readOnlyHint must be false');
63
+ assert.equal(tool.annotations.destructiveHint, false, 'destructiveHint must be false');
64
+ assert.equal(tool.annotations.idempotentHint, true, 'idempotentHint must be true');
65
+ assert.equal(tool.annotations.openWorldHint, false, 'openWorldHint must be false');
66
+ });
67
+ test('R5: kit_get_bitbucket_pr (readOnlyHint:true) has correct annotations (BC5)', async () => {
68
+ const client = await buildClientWithTools();
69
+ const { tools } = await client.listTools();
70
+ const tool = tools.find((t) => t.name === 'kit_get_bitbucket_pr');
71
+ assert.ok(tool, 'kit_get_bitbucket_pr must be registered');
72
+ assert.ok(tool.annotations, 'kit_get_bitbucket_pr must have annotations');
73
+ assert.equal(tool.annotations.readOnlyHint, true, 'readOnlyHint must be true');
74
+ assert.equal(tool.annotations.idempotentHint, true, 'idempotentHint must be true');
75
+ assert.equal(tool.annotations.openWorldHint, true, 'openWorldHint must be true');
76
+ });
77
+ });
package/dist/server.js CHANGED
@@ -36078,7 +36078,6 @@ var HANDOFF_TYPES = [
36078
36078
  "plan",
36079
36079
  "ticket",
36080
36080
  "research",
36081
- "scenario",
36082
36081
  "investigation",
36083
36082
  "code"
36084
36083
  ];
@@ -36161,17 +36160,21 @@ function handleSaveHandoff(args, workspaceRoot2) {
36161
36160
  }
36162
36161
  }
36163
36162
  function registerCoreTools(server2) {
36164
- server2.tool(
36163
+ server2.registerTool(
36165
36164
  "kit_save_handoff",
36166
- `Save a handoff artifact to .agent-kit/handoffs/. Returns the saved folder path to use in next-step instructions. Do NOT append version numbers (v2, v3, etc.) to the slug.`,
36167
36165
  {
36168
- type: external_exports.enum(HANDOFF_TYPES).describe("Handoff type"),
36169
- files: external_exports.record(external_exports.string(), external_exports.string()).describe(
36170
- "Markdown files to write into the handoff folder. May include one or many files per call; repeated calls with the same type and slug add or overwrite the provided files. Filenames must match /^[A-Za-z0-9_-]+\\.md$/."
36171
- ),
36172
- slug: external_exports.string().describe(
36173
- 'Short identifier for the folder, e.g. "user-auth" or "PROJ-123". Do NOT append version numbers (v2, v3, etc.).'
36174
- )
36166
+ title: "Save Handoff Artifact",
36167
+ description: `Save a handoff artifact to .agent-kit/handoffs/. Returns the saved folder path to use in next-step instructions. Do NOT append version numbers (v2, v3, etc.) to the slug.`,
36168
+ inputSchema: {
36169
+ type: external_exports.enum(HANDOFF_TYPES).describe("Handoff type"),
36170
+ files: external_exports.record(external_exports.string(), external_exports.string()).describe(
36171
+ "Markdown files to write into the handoff folder. May include one or many files per call; repeated calls with the same type and slug add or overwrite the provided files. Filenames must match /^[A-Za-z0-9_-]+\\.md$/."
36172
+ ),
36173
+ slug: external_exports.string().describe(
36174
+ 'Short identifier for the folder, e.g. "user-auth" or "PROJ-123". Do NOT append version numbers (v2, v3, etc.).'
36175
+ )
36176
+ },
36177
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
36175
36178
  },
36176
36179
  async (args) => handleSaveHandoff(args, getWorkspaceRoot())
36177
36180
  );
@@ -36281,12 +36284,16 @@ ${"\u2500".repeat(60)}
36281
36284
  return { logPath: path6.relative(workspaceRoot2, logFile), logStream };
36282
36285
  }
36283
36286
  function registerAgentTools(server2) {
36284
- server2.tool(
36287
+ server2.registerTool(
36285
36288
  "kit_trigger_agent",
36286
- "!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff folder (.agent-kit/handoffs/feature-slug/plan).",
36287
36289
  {
36288
- agent: external_exports.enum(["gemini", "claude", "codex"]).describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
36289
- task: external_exports.string().describe('Task message or path to a handoff folder (e.g., ".agent-kit/handoffs/feature-slug/plan")')
36290
+ title: "Delegate to External Agent",
36291
+ description: "!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff folder (.agent-kit/handoffs/feature-slug/plan).",
36292
+ inputSchema: {
36293
+ agent: external_exports.enum(["gemini", "claude", "codex"]).describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
36294
+ task: external_exports.string().describe('Task message or path to a handoff folder (e.g., ".agent-kit/handoffs/feature-slug/plan")')
36295
+ },
36296
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
36290
36297
  },
36291
36298
  async ({ agent, task }, extra) => {
36292
36299
  const workspaceRoot2 = getWorkspaceRoot();
@@ -36641,14 +36648,18 @@ async function callRestApi(url, auth, accept = "application/json") {
36641
36648
  return accept === "text/plain" ? resp.text() : resp.json();
36642
36649
  }
36643
36650
  function registerIntegrationTools(server2) {
36644
- server2.tool(
36651
+ server2.registerTool(
36645
36652
  "kit_get_bitbucket_pr",
36646
- "Get Bitbucket PR details and optionally the diff. Accepts a full PR URL or a numeric PR ID with workspace + repoSlug.",
36647
36653
  {
36648
- input: external_exports.string().describe("Bitbucket PR URL or numeric PR ID"),
36649
- workspace: external_exports.string().optional().describe("Bitbucket workspace slug (required for numeric ID if BITBUCKET_DEFAULT_WORKSPACE not set)"),
36650
- repoSlug: external_exports.string().optional().describe("Bitbucket repo slug (required for numeric ID)"),
36651
- includeDiff: external_exports.boolean().optional().default(true).describe("Include unified diff in response")
36654
+ title: "Get Bitbucket PR",
36655
+ description: "Get Bitbucket PR details and optionally the diff. Accepts a full PR URL or a numeric PR ID with workspace + repoSlug.",
36656
+ inputSchema: {
36657
+ input: external_exports.string().describe("Bitbucket PR URL or numeric PR ID"),
36658
+ workspace: external_exports.string().optional().describe("Bitbucket workspace slug (required for numeric ID if BITBUCKET_DEFAULT_WORKSPACE not set)"),
36659
+ repoSlug: external_exports.string().optional().describe("Bitbucket repo slug (required for numeric ID)"),
36660
+ includeDiff: external_exports.boolean().optional().default(true).describe("Include unified diff in response")
36661
+ },
36662
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }
36652
36663
  },
36653
36664
  async ({ input, workspace, repoSlug, includeDiff }) => {
36654
36665
  try {
@@ -36725,11 +36736,15 @@ ${diff}
36725
36736
  }
36726
36737
  }
36727
36738
  );
36728
- server2.tool(
36739
+ server2.registerTool(
36729
36740
  "kit_jira_get_ticket",
36730
- "Get ticket details from Jira using the Atlassian REST API",
36731
36741
  {
36732
- ticketId: external_exports.string().describe("Jira ticket ID (e.g., PROJ-123)")
36742
+ title: "Get Jira Ticket",
36743
+ description: "Get ticket details from Jira using the Atlassian REST API",
36744
+ inputSchema: {
36745
+ ticketId: external_exports.string().describe("Jira ticket ID (e.g., PROJ-123)")
36746
+ },
36747
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true }
36733
36748
  },
36734
36749
  async ({ ticketId }) => {
36735
36750
  try {
@@ -38143,12 +38158,16 @@ async function initializeConversationDigestModel(input) {
38143
38158
  // src/mcp/memory.ts
38144
38159
  function registerMemoryToolHandlers(server2, subsystem, workspaceRoot2 = process.cwd()) {
38145
38160
  const { config: config2 } = subsystem;
38146
- server2.tool(
38161
+ server2.registerTool(
38147
38162
  "kit_memory_search",
38148
- "Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.",
38149
38163
  {
38150
- query: external_exports.string().min(1).describe("Keyword-only search query with stopwords removed"),
38151
- top_k: external_exports.number().int().positive().optional().describe("Number of results to return")
38164
+ title: "Search Memory",
38165
+ description: "Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.",
38166
+ inputSchema: {
38167
+ query: external_exports.string().min(1).describe("Keyword-only search query with stopwords removed"),
38168
+ top_k: external_exports.number().int().positive().optional().describe("Number of results to return")
38169
+ },
38170
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
38152
38171
  },
38153
38172
  async ({ query, top_k }) => {
38154
38173
  try {
@@ -38175,12 +38194,16 @@ ${content}`;
38175
38194
  }
38176
38195
  }
38177
38196
  );
38178
- server2.tool(
38197
+ server2.registerTool(
38179
38198
  "kit_memory_recent",
38180
- 'Return the N most recently updated memory sources. Optionally filter by source type. Use for temporal questions like "what did we do last session" or "recent".',
38181
38199
  {
38182
- n: external_exports.number().int().positive().max(50).optional().describe("How many recent sources to return (default 5)"),
38183
- source_type: external_exports.enum(SOURCE_TYPES).optional().describe("Optional source type filter")
38200
+ title: "Recent Memory Sources",
38201
+ description: 'Return the N most recently updated memory sources. Optionally filter by source type. Use for temporal questions like "what did we do last session" or "recent".',
38202
+ inputSchema: {
38203
+ n: external_exports.number().int().positive().max(50).optional().describe("How many recent sources to return (default 5)"),
38204
+ source_type: external_exports.enum(SOURCE_TYPES).optional().describe("Optional source type filter")
38205
+ },
38206
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false }
38184
38207
  },
38185
38208
  async ({ n, source_type }) => {
38186
38209
  try {
@@ -38208,11 +38231,15 @@ ${content}`);
38208
38231
  }
38209
38232
  }
38210
38233
  );
38211
- server2.tool(
38234
+ server2.registerTool(
38212
38235
  "kit_memory_save",
38213
- "Save content to wiki/raw for inclusion after the next /wiki compile.",
38214
38236
  {
38215
- content: external_exports.string().min(1).describe("Content to save to memory")
38237
+ title: "Save to Memory",
38238
+ description: "Save content to wiki/raw for inclusion after the next /wiki compile.",
38239
+ inputSchema: {
38240
+ content: external_exports.string().min(1).describe("Content to save to memory")
38241
+ },
38242
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
38216
38243
  },
38217
38244
  async ({ content }) => {
38218
38245
  try {
@@ -38237,12 +38264,16 @@ ${content}`);
38237
38264
  }
38238
38265
  }
38239
38266
  );
38240
- server2.tool(
38267
+ server2.registerTool(
38241
38268
  "kit_memory_digest_init",
38242
- "Initialize or toggle local conversation digesting. Downloads/cache-loads the pinned local model only when explicitly invoked.",
38243
38269
  {
38244
- model_id: external_exports.string().optional().describe("Digest model id"),
38245
- enabled: external_exports.boolean().optional().describe("Enable or disable conversation digesting")
38270
+ title: "Initialize Conversation Digest",
38271
+ description: "Initialize or toggle local conversation digesting. Downloads/cache-loads the pinned local model only when explicitly invoked.",
38272
+ inputSchema: {
38273
+ model_id: external_exports.string().optional().describe("Digest model id"),
38274
+ enabled: external_exports.boolean().optional().describe("Enable or disable conversation digesting")
38275
+ },
38276
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
38246
38277
  },
38247
38278
  async ({ model_id, enabled }) => {
38248
38279
  try {
@@ -11,8 +11,9 @@ function makeMockServer() {
11
11
  const tools = new Map();
12
12
  const descriptions = new Map();
13
13
  const server = {
14
- tool(name, desc, _schema, handler) {
15
- descriptions.set(name, desc);
14
+ registerTool(name, config, handler) {
15
+ if (config.description)
16
+ descriptions.set(name, config.description);
16
17
  tools.set(name, handler);
17
18
  },
18
19
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanhnd/agent-kit",
3
- "version": "1.0.40",
3
+ "version": "1.0.41",
4
4
  "description": "Agent Kit MCP server for software development agent workflows",
5
5
  "type": "module",
6
6
  "main": "dist/server.js",