@open-mercato/ai-assistant 0.6.8-develop.7100.1.fbf66fca35 → 0.7.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 (41) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -1
  3. package/dist/modules/ai_assistant/ai-tools/search-pack.js +3 -93
  4. package/dist/modules/ai_assistant/ai-tools/search-pack.js.map +3 -3
  5. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js +0 -2
  6. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js.map +2 -2
  7. package/dist/modules/ai_assistant/lib/codemode-tools.js +6 -14
  8. package/dist/modules/ai_assistant/lib/codemode-tools.js.map +2 -2
  9. package/dist/modules/ai_assistant/lib/generated-registry-loader.js +2 -10
  10. package/dist/modules/ai_assistant/lib/generated-registry-loader.js.map +2 -2
  11. package/dist/modules/ai_assistant/lib/http-server.js +1 -3
  12. package/dist/modules/ai_assistant/lib/http-server.js.map +2 -2
  13. package/dist/modules/ai_assistant/lib/in-process-client.js +1 -3
  14. package/dist/modules/ai_assistant/lib/in-process-client.js.map +2 -2
  15. package/dist/modules/ai_assistant/lib/mcp-client.js +1 -2
  16. package/dist/modules/ai_assistant/lib/mcp-client.js.map +2 -2
  17. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +1 -3
  18. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +2 -2
  19. package/dist/modules/ai_assistant/lib/mcp-server.js +1 -3
  20. package/dist/modules/ai_assistant/lib/mcp-server.js.map +2 -2
  21. package/package.json +7 -8
  22. package/src/modules/ai_assistant/__tests__/integration/ws-c-tool-pack-coverage.test.ts +0 -5
  23. package/src/modules/ai_assistant/ai-tools/__tests__/search-pack.test.ts +5 -211
  24. package/src/modules/ai_assistant/ai-tools/search-pack.ts +4 -110
  25. package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx +0 -3
  26. package/src/modules/ai_assistant/lib/__tests__/generated-registry-loader.test.ts +0 -16
  27. package/src/modules/ai_assistant/lib/__tests__/mcp-client.test.ts +0 -30
  28. package/src/modules/ai_assistant/lib/codemode-tools.ts +7 -21
  29. package/src/modules/ai_assistant/lib/generated-registry-loader.ts +2 -10
  30. package/src/modules/ai_assistant/lib/http-server.ts +0 -2
  31. package/src/modules/ai_assistant/lib/in-process-client.ts +0 -2
  32. package/src/modules/ai_assistant/lib/mcp-client.ts +0 -1
  33. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +0 -2
  34. package/src/modules/ai_assistant/lib/mcp-server.ts +0 -2
  35. package/src/modules/ai_assistant/lib/types.ts +0 -11
  36. package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js +0 -18
  37. package/dist/modules/ai_assistant/lib/mcp-tool-annotations.js.map +0 -7
  38. package/src/modules/ai_assistant/lib/__tests__/codemode-tool-annotations.test.ts +0 -58
  39. package/src/modules/ai_assistant/lib/__tests__/mcp-server-tool-annotations.test.ts +0 -120
  40. package/src/modules/ai_assistant/lib/__tests__/mcp-tool-annotations.test.ts +0 -57
  41. package/src/modules/ai_assistant/lib/mcp-tool-annotations.ts +0 -35
@@ -5,7 +5,6 @@ import {
5
5
  CallToolRequestSchema,
6
6
  } from '@modelcontextprotocol/sdk/types.js'
7
7
  import { toolInputJsonSchema } from './tool-input-schema'
8
- import { buildMcpToolAnnotations } from './mcp-tool-annotations'
9
8
  import { getToolRegistry } from './tool-registry'
10
9
  import { executeTool } from './tool-executor'
11
10
  import { loadAllModuleTools, indexToolsForSearch } from './tool-loader'
@@ -134,7 +133,6 @@ export async function createMcpServer(options: McpServerOptions): Promise<Server
134
133
  name: tool.name,
135
134
  description: tool.description,
136
135
  inputSchema: toolInputJsonSchema(tool.inputSchema),
137
- annotations: buildMcpToolAnnotations(tool),
138
136
  })),
139
137
  }
140
138
  })
@@ -249,17 +249,6 @@ export type ToolInfo = {
249
249
  name: string
250
250
  description: string
251
251
  inputSchema: Record<string, unknown>
252
- /** MCP capability hints advertised for the tool (see `buildMcpToolAnnotations`). */
253
- annotations?: McpToolAnnotations
254
- }
255
-
256
- /** MCP `ToolAnnotations` values carried through remote and in-process clients. */
257
- export type McpToolAnnotations = {
258
- title?: string
259
- readOnlyHint?: boolean
260
- destructiveHint?: boolean
261
- idempotentHint?: boolean
262
- openWorldHint?: boolean
263
252
  }
264
253
 
265
254
  /**
@@ -1,18 +0,0 @@
1
- function buildMcpToolAnnotations(tool) {
2
- const definition = tool;
3
- if (definition.isMutation !== true) {
4
- return { readOnlyHint: true };
5
- }
6
- const { isDestructive } = definition;
7
- if (isDestructive === void 0) {
8
- return { readOnlyHint: false };
9
- }
10
- return {
11
- readOnlyHint: false,
12
- destructiveHint: typeof isDestructive === "function" ? true : isDestructive
13
- };
14
- }
15
- export {
16
- buildMcpToolAnnotations
17
- };
18
- //# sourceMappingURL=mcp-tool-annotations.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/modules/ai_assistant/lib/mcp-tool-annotations.ts"],
4
- "sourcesContent": ["import type { AiToolDefinition, McpToolAnnotations, McpToolDefinition } from './types'\n\n/**\n * Build the MCP annotations advertised for a registered tool.\n *\n * `readOnlyHint` follows the registry's existing mutation contract: a tool is\n * read-only unless it declares `isMutation: true`. Every write tool is required\n * to set that flag (see the module AGENTS.md) and the agent mutation-policy gate\n * already reads it the same way.\n *\n * `destructiveHint` only carries meaning for mutating tools. A predicate\n * `isDestructive` cannot be evaluated without call input, so it resolves to\n * `true` rather than under-warning the client. When the flag is absent the hint\n * is omitted so clients fall back to the conservative MCP default (`true`).\n *\n * `idempotentHint` is deliberately not emitted: no tool-definition field\n * carries that information, so clients keep the MCP default (`false`).\n */\nexport function buildMcpToolAnnotations(tool: McpToolDefinition): McpToolAnnotations {\n const definition = tool as AiToolDefinition\n\n if (definition.isMutation !== true) {\n return { readOnlyHint: true }\n }\n\n const { isDestructive } = definition\n if (isDestructive === undefined) {\n return { readOnlyHint: false }\n }\n\n return {\n readOnlyHint: false,\n destructiveHint: typeof isDestructive === 'function' ? true : isDestructive,\n }\n}\n"],
5
- "mappings": "AAkBO,SAAS,wBAAwB,MAA6C;AACnF,QAAM,aAAa;AAEnB,MAAI,WAAW,eAAe,MAAM;AAClC,WAAO,EAAE,cAAc,KAAK;AAAA,EAC9B;AAEA,QAAM,EAAE,cAAc,IAAI;AAC1B,MAAI,kBAAkB,QAAW;AAC/B,WAAO,EAAE,cAAc,MAAM;AAAA,EAC/B;AAEA,SAAO;AAAA,IACL,cAAc;AAAA,IACd,iBAAiB,OAAO,kBAAkB,aAAa,OAAO;AAAA,EAChE;AACF;",
6
- "names": []
7
- }
@@ -1,58 +0,0 @@
1
- import { loadCodeModeTools } from '../codemode-tools'
2
- import { buildMcpToolAnnotations } from '../mcp-tool-annotations'
3
- import { registerMcpTool } from '../tool-registry'
4
- import type { McpToolDefinition } from '../types'
5
-
6
- jest.mock('../api-endpoint-index', () => ({
7
- getApiEndpoints: jest.fn(async () => []),
8
- getRawOpenApiSpec: jest.fn(async () => null),
9
- }))
10
-
11
- jest.mock('../tool-registry', () => ({
12
- registerMcpTool: jest.fn(),
13
- }))
14
-
15
- const mockedRegisterMcpTool = jest.mocked(registerMcpTool)
16
-
17
- async function loadRegisteredCodeModeTools(): Promise<Map<string, McpToolDefinition>> {
18
- await loadCodeModeTools()
19
- const tools = new Map<string, McpToolDefinition>()
20
- for (const call of mockedRegisterMcpTool.mock.calls) {
21
- const tool = call[0] as McpToolDefinition
22
- tools.set(tool.name, tool)
23
- }
24
- return tools
25
- }
26
-
27
- describe('issue #5283 — Code Mode tools declare their mutation surface', () => {
28
- beforeEach(() => {
29
- mockedRegisterMcpTool.mockClear()
30
- })
31
-
32
- it('advertises the spec-querying search tool as read-only', async () => {
33
- const tools = await loadRegisteredCodeModeTools()
34
- const search = tools.get('search')
35
-
36
- expect(search).toBeDefined()
37
- expect(buildMcpToolAnnotations(search!)).toEqual({ readOnlyHint: true })
38
- })
39
-
40
- it('never advertises the api.request() execute tool as read-only', async () => {
41
- const tools = await loadRegisteredCodeModeTools()
42
- const execute = tools.get('execute')
43
-
44
- expect(execute).toBeDefined()
45
- expect(buildMcpToolAnnotations(execute!)).toEqual({
46
- readOnlyHint: false,
47
- destructiveHint: true,
48
- })
49
- })
50
-
51
- it('registers both Code Mode tools under the codemode module id', async () => {
52
- await loadRegisteredCodeModeTools()
53
-
54
- for (const call of mockedRegisterMcpTool.mock.calls) {
55
- expect(call[1]).toEqual({ moduleId: 'codemode' })
56
- }
57
- })
58
- })
@@ -1,120 +0,0 @@
1
- import { z } from 'zod'
2
- import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
3
- import { createMcpServer } from '../mcp-server'
4
- import type { AiToolDefinition, McpServerOptions, McpToolDefinition } from '../types'
5
-
6
- const mockCapturedHandlers = new Map<unknown, (request: unknown) => Promise<unknown>>()
7
- const mockRegisteredTools = new Map<string, McpToolDefinition>()
8
-
9
- jest.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
10
- Server: class {
11
- setRequestHandler(schema: unknown, handler: (request: unknown) => Promise<unknown>) {
12
- mockCapturedHandlers.set(schema, handler)
13
- }
14
- },
15
- }))
16
- jest.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
17
- StdioServerTransport: class {},
18
- }))
19
- jest.mock('@modelcontextprotocol/sdk/types.js', () => ({
20
- ListToolsRequestSchema: { id: 'tools/list' },
21
- CallToolRequestSchema: { id: 'tools/call' },
22
- }))
23
- jest.mock('../tool-registry', () => ({
24
- getToolRegistry: () => ({
25
- getTools: () => mockRegisteredTools,
26
- listToolNames: () => Array.from(mockRegisteredTools.keys()),
27
- }),
28
- }))
29
- jest.mock('../tool-executor', () => ({ executeTool: jest.fn() }))
30
- jest.mock('../tool-loader', () => ({
31
- loadAllModuleTools: jest.fn(),
32
- indexToolsForSearch: jest.fn(),
33
- }))
34
- jest.mock('../auth', () => ({
35
- authenticateMcpRequest: jest.fn(),
36
- hasRequiredFeatures: jest.fn(() => true),
37
- }))
38
-
39
- function registerTool(overrides: Partial<AiToolDefinition> & { name: string }): void {
40
- mockRegisteredTools.set(overrides.name, {
41
- description: `${overrides.name} description`,
42
- inputSchema: z.object({ id: z.string() }),
43
- handler: async () => ({}),
44
- ...overrides,
45
- } as McpToolDefinition)
46
- }
47
-
48
- function makeContainer(): McpServerOptions['container'] {
49
- return {
50
- resolve: () => ({ loadAcl: jest.fn() }),
51
- } as unknown as McpServerOptions['container']
52
- }
53
-
54
- type ListedTool = {
55
- name: string
56
- description: string
57
- inputSchema: unknown
58
- annotations?: { readOnlyHint?: boolean; destructiveHint?: boolean }
59
- }
60
-
61
- async function listTools(): Promise<ListedTool[]> {
62
- await createMcpServer({
63
- config: { name: 'test-mcp', version: '0.0.0' },
64
- container: makeContainer(),
65
- allowUnauthenticatedSuperadmin: true,
66
- })
67
- const handler = mockCapturedHandlers.get(ListToolsRequestSchema)
68
- if (!handler) throw new Error('[internal] tools/list handler was not registered')
69
- const response = (await handler({})) as { tools: ListedTool[] }
70
- return response.tools
71
- }
72
-
73
- describe('issue #5283 — MCP tools/list publishes readOnlyHint annotations', () => {
74
- beforeEach(() => {
75
- mockRegisteredTools.clear()
76
- mockCapturedHandlers.clear()
77
- jest.spyOn(process.stderr, 'write').mockImplementation(() => true)
78
- })
79
-
80
- afterEach(() => {
81
- jest.restoreAllMocks()
82
- })
83
-
84
- it('marks read operations as read-only so clients can skip approval', async () => {
85
- registerTool({ name: 'customers.get_company' })
86
- registerTool({ name: 'customers.list_people' })
87
- registerTool({ name: 'search_status', isMutation: false })
88
-
89
- const tools = await listTools()
90
-
91
- expect(tools).toHaveLength(3)
92
- for (const tool of tools) {
93
- expect(tool.annotations?.readOnlyHint).toBe(true)
94
- }
95
- })
96
-
97
- it('never marks mutating operations as read-only', async () => {
98
- registerTool({ name: 'catalog.update_product', isMutation: true })
99
- registerTool({ name: 'customers.manage_deal_comment', isMutation: true, isDestructive: true })
100
-
101
- const tools = await listTools()
102
-
103
- const byName = new Map(tools.map((tool) => [tool.name, tool]))
104
- expect(byName.get('catalog.update_product')?.annotations?.readOnlyHint).toBe(false)
105
- expect(byName.get('customers.manage_deal_comment')?.annotations).toEqual({
106
- readOnlyHint: false,
107
- destructiveHint: true,
108
- })
109
- })
110
-
111
- it('keeps the existing name / description / inputSchema fields intact', async () => {
112
- registerTool({ name: 'customers.get_company' })
113
-
114
- const [tool] = await listTools()
115
-
116
- expect(tool.name).toBe('customers.get_company')
117
- expect(tool.description).toBe('customers.get_company description')
118
- expect(tool.inputSchema).toBeDefined()
119
- })
120
- })
@@ -1,57 +0,0 @@
1
- import { z } from 'zod'
2
- import { buildMcpToolAnnotations } from '../mcp-tool-annotations'
3
- import type { AiToolDefinition } from '../types'
4
-
5
- function makeTool(overrides: Partial<AiToolDefinition> = {}): AiToolDefinition {
6
- return {
7
- name: 'customers.get_company',
8
- description: 'Fetch a single company record.',
9
- inputSchema: z.object({ id: z.string() }),
10
- handler: async () => ({}),
11
- ...overrides,
12
- } as AiToolDefinition
13
- }
14
-
15
- describe('buildMcpToolAnnotations', () => {
16
- it('advertises readOnlyHint for tools that do not declare a mutation', () => {
17
- expect(buildMcpToolAnnotations(makeTool())).toEqual({ readOnlyHint: true })
18
- })
19
-
20
- it('advertises readOnlyHint for tools that explicitly declare isMutation: false', () => {
21
- expect(buildMcpToolAnnotations(makeTool({ isMutation: false }))).toEqual({ readOnlyHint: true })
22
- })
23
-
24
- it('never advertises readOnlyHint for mutating tools', () => {
25
- expect(buildMcpToolAnnotations(makeTool({ isMutation: true }))).toEqual({ readOnlyHint: false })
26
- })
27
-
28
- it('advertises destructiveHint when the mutation declares isDestructive', () => {
29
- expect(buildMcpToolAnnotations(makeTool({ isMutation: true, isDestructive: true }))).toEqual({
30
- readOnlyHint: false,
31
- destructiveHint: true,
32
- })
33
- })
34
-
35
- it('carries an explicit non-destructive declaration through', () => {
36
- expect(buildMcpToolAnnotations(makeTool({ isMutation: true, isDestructive: false }))).toEqual({
37
- readOnlyHint: false,
38
- destructiveHint: false,
39
- })
40
- })
41
-
42
- it('resolves a per-input isDestructive predicate to true rather than under-warning', () => {
43
- const annotations = buildMcpToolAnnotations(
44
- makeTool({ isMutation: true, isDestructive: (input: unknown) => (input as { op: string }).op === 'delete' }),
45
- )
46
- expect(annotations).toEqual({ readOnlyHint: false, destructiveHint: true })
47
- })
48
-
49
- it('omits destructiveHint when a mutation does not declare isDestructive', () => {
50
- expect(buildMcpToolAnnotations(makeTool({ isMutation: true }))).not.toHaveProperty('destructiveHint')
51
- })
52
-
53
- it('never advertises idempotentHint, which no tool definition carries', () => {
54
- expect(buildMcpToolAnnotations(makeTool())).not.toHaveProperty('idempotentHint')
55
- expect(buildMcpToolAnnotations(makeTool({ isMutation: true }))).not.toHaveProperty('idempotentHint')
56
- })
57
- })
@@ -1,35 +0,0 @@
1
- import type { AiToolDefinition, McpToolAnnotations, McpToolDefinition } from './types'
2
-
3
- /**
4
- * Build the MCP annotations advertised for a registered tool.
5
- *
6
- * `readOnlyHint` follows the registry's existing mutation contract: a tool is
7
- * read-only unless it declares `isMutation: true`. Every write tool is required
8
- * to set that flag (see the module AGENTS.md) and the agent mutation-policy gate
9
- * already reads it the same way.
10
- *
11
- * `destructiveHint` only carries meaning for mutating tools. A predicate
12
- * `isDestructive` cannot be evaluated without call input, so it resolves to
13
- * `true` rather than under-warning the client. When the flag is absent the hint
14
- * is omitted so clients fall back to the conservative MCP default (`true`).
15
- *
16
- * `idempotentHint` is deliberately not emitted: no tool-definition field
17
- * carries that information, so clients keep the MCP default (`false`).
18
- */
19
- export function buildMcpToolAnnotations(tool: McpToolDefinition): McpToolAnnotations {
20
- const definition = tool as AiToolDefinition
21
-
22
- if (definition.isMutation !== true) {
23
- return { readOnlyHint: true }
24
- }
25
-
26
- const { isDestructive } = definition
27
- if (isDestructive === undefined) {
28
- return { readOnlyHint: false }
29
- }
30
-
31
- return {
32
- readOnlyHint: false,
33
- destructiveHint: typeof isDestructive === 'function' ? true : isDestructive,
34
- }
35
- }