@vinkius-core/mcp-fusion 0.1.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 (211) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +391 -0
  3. package/dist/src/AbstractBase.d.ts +24 -0
  4. package/dist/src/AbstractBase.d.ts.map +1 -0
  5. package/dist/src/AbstractBase.js +63 -0
  6. package/dist/src/AbstractBase.js.map +1 -0
  7. package/dist/src/AbstractLeaf.d.ts +12 -0
  8. package/dist/src/AbstractLeaf.d.ts.map +1 -0
  9. package/dist/src/AbstractLeaf.js +32 -0
  10. package/dist/src/AbstractLeaf.js.map +1 -0
  11. package/dist/src/Annotations.d.ts +15 -0
  12. package/dist/src/Annotations.d.ts.map +1 -0
  13. package/dist/src/Annotations.js +29 -0
  14. package/dist/src/Annotations.js.map +1 -0
  15. package/dist/src/Group.d.ts +32 -0
  16. package/dist/src/Group.d.ts.map +1 -0
  17. package/dist/src/Group.js +131 -0
  18. package/dist/src/Group.js.map +1 -0
  19. package/dist/src/Icon.d.ts +19 -0
  20. package/dist/src/Icon.d.ts.map +1 -0
  21. package/dist/src/Icon.js +33 -0
  22. package/dist/src/Icon.js.map +1 -0
  23. package/dist/src/Prompt.d.ts +11 -0
  24. package/dist/src/Prompt.d.ts.map +1 -0
  25. package/dist/src/Prompt.js +28 -0
  26. package/dist/src/Prompt.js.map +1 -0
  27. package/dist/src/PromptArgument.d.ts +10 -0
  28. package/dist/src/PromptArgument.d.ts.map +1 -0
  29. package/dist/src/PromptArgument.js +20 -0
  30. package/dist/src/PromptArgument.js.map +1 -0
  31. package/dist/src/Resource.d.ts +19 -0
  32. package/dist/src/Resource.d.ts.map +1 -0
  33. package/dist/src/Resource.js +34 -0
  34. package/dist/src/Resource.js.map +1 -0
  35. package/dist/src/Role.d.ts +5 -0
  36. package/dist/src/Role.d.ts.map +1 -0
  37. package/dist/src/Role.js +6 -0
  38. package/dist/src/Role.js.map +1 -0
  39. package/dist/src/Tool.d.ts +16 -0
  40. package/dist/src/Tool.d.ts.map +1 -0
  41. package/dist/src/Tool.js +28 -0
  42. package/dist/src/Tool.js.map +1 -0
  43. package/dist/src/ToolAnnotations.d.ts +23 -0
  44. package/dist/src/ToolAnnotations.d.ts.map +1 -0
  45. package/dist/src/ToolAnnotations.js +44 -0
  46. package/dist/src/ToolAnnotations.js.map +1 -0
  47. package/dist/src/converters/GroupConverter.d.ts +14 -0
  48. package/dist/src/converters/GroupConverter.d.ts.map +1 -0
  49. package/dist/src/converters/GroupConverter.js +13 -0
  50. package/dist/src/converters/GroupConverter.js.map +1 -0
  51. package/dist/src/converters/PromptConverter.d.ts +14 -0
  52. package/dist/src/converters/PromptConverter.d.ts.map +1 -0
  53. package/dist/src/converters/PromptConverter.js +13 -0
  54. package/dist/src/converters/PromptConverter.js.map +1 -0
  55. package/dist/src/converters/ResourceConverter.d.ts +14 -0
  56. package/dist/src/converters/ResourceConverter.d.ts.map +1 -0
  57. package/dist/src/converters/ResourceConverter.js +13 -0
  58. package/dist/src/converters/ResourceConverter.js.map +1 -0
  59. package/dist/src/converters/ToolAnnotationsConverter.d.ts +16 -0
  60. package/dist/src/converters/ToolAnnotationsConverter.d.ts.map +1 -0
  61. package/dist/src/converters/ToolAnnotationsConverter.js +23 -0
  62. package/dist/src/converters/ToolAnnotationsConverter.js.map +1 -0
  63. package/dist/src/converters/ToolConverter.d.ts +14 -0
  64. package/dist/src/converters/ToolConverter.d.ts.map +1 -0
  65. package/dist/src/converters/ToolConverter.js +13 -0
  66. package/dist/src/converters/ToolConverter.js.map +1 -0
  67. package/dist/src/converters/index.d.ts +6 -0
  68. package/dist/src/converters/index.d.ts.map +1 -0
  69. package/dist/src/converters/index.js +6 -0
  70. package/dist/src/converters/index.js.map +1 -0
  71. package/dist/src/framework/GroupedToolBuilder.d.ts +137 -0
  72. package/dist/src/framework/GroupedToolBuilder.d.ts.map +1 -0
  73. package/dist/src/framework/GroupedToolBuilder.js +289 -0
  74. package/dist/src/framework/GroupedToolBuilder.js.map +1 -0
  75. package/dist/src/framework/ResponseHelper.d.ts +43 -0
  76. package/dist/src/framework/ResponseHelper.d.ts.map +1 -0
  77. package/dist/src/framework/ResponseHelper.js +49 -0
  78. package/dist/src/framework/ResponseHelper.js.map +1 -0
  79. package/dist/src/framework/ToolBuilder.d.ts +46 -0
  80. package/dist/src/framework/ToolBuilder.d.ts.map +1 -0
  81. package/dist/src/framework/ToolBuilder.js +2 -0
  82. package/dist/src/framework/ToolBuilder.js.map +1 -0
  83. package/dist/src/framework/ToolRegistry.d.ts +85 -0
  84. package/dist/src/framework/ToolRegistry.d.ts.map +1 -0
  85. package/dist/src/framework/ToolRegistry.js +153 -0
  86. package/dist/src/framework/ToolRegistry.js.map +1 -0
  87. package/dist/src/framework/index.d.ts +9 -0
  88. package/dist/src/framework/index.d.ts.map +1 -0
  89. package/dist/src/framework/index.js +8 -0
  90. package/dist/src/framework/index.js.map +1 -0
  91. package/dist/src/framework/strategies/AnnotationAggregator.d.ts +11 -0
  92. package/dist/src/framework/strategies/AnnotationAggregator.d.ts.map +1 -0
  93. package/dist/src/framework/strategies/AnnotationAggregator.js +25 -0
  94. package/dist/src/framework/strategies/AnnotationAggregator.js.map +1 -0
  95. package/dist/src/framework/strategies/DescriptionGenerator.d.ts +12 -0
  96. package/dist/src/framework/strategies/DescriptionGenerator.d.ts.map +1 -0
  97. package/dist/src/framework/strategies/DescriptionGenerator.js +70 -0
  98. package/dist/src/framework/strategies/DescriptionGenerator.js.map +1 -0
  99. package/dist/src/framework/strategies/MiddlewareCompiler.d.ts +13 -0
  100. package/dist/src/framework/strategies/MiddlewareCompiler.d.ts.map +1 -0
  101. package/dist/src/framework/strategies/MiddlewareCompiler.js +24 -0
  102. package/dist/src/framework/strategies/MiddlewareCompiler.js.map +1 -0
  103. package/dist/src/framework/strategies/SchemaGenerator.d.ts +15 -0
  104. package/dist/src/framework/strategies/SchemaGenerator.d.ts.map +1 -0
  105. package/dist/src/framework/strategies/SchemaGenerator.js +97 -0
  106. package/dist/src/framework/strategies/SchemaGenerator.js.map +1 -0
  107. package/dist/src/framework/strategies/SchemaUtils.d.ts +7 -0
  108. package/dist/src/framework/strategies/SchemaUtils.d.ts.map +1 -0
  109. package/dist/src/framework/strategies/SchemaUtils.js +17 -0
  110. package/dist/src/framework/strategies/SchemaUtils.js.map +1 -0
  111. package/dist/src/framework/strategies/ToonDescriptionGenerator.d.ts +3 -0
  112. package/dist/src/framework/strategies/ToonDescriptionGenerator.d.ts.map +1 -0
  113. package/dist/src/framework/strategies/ToonDescriptionGenerator.js +53 -0
  114. package/dist/src/framework/strategies/ToonDescriptionGenerator.js.map +1 -0
  115. package/dist/src/framework/strategies/Types.d.ts +34 -0
  116. package/dist/src/framework/strategies/Types.d.ts.map +1 -0
  117. package/dist/src/framework/strategies/Types.js +2 -0
  118. package/dist/src/framework/strategies/Types.js.map +1 -0
  119. package/dist/src/framework/strategies/index.d.ts +12 -0
  120. package/dist/src/framework/strategies/index.d.ts.map +1 -0
  121. package/dist/src/framework/strategies/index.js +11 -0
  122. package/dist/src/framework/strategies/index.js.map +1 -0
  123. package/dist/src/index.d.ts +15 -0
  124. package/dist/src/index.d.ts.map +1 -0
  125. package/dist/src/index.js +15 -0
  126. package/dist/src/index.js.map +1 -0
  127. package/dist/tests/AbstractBase.test.d.ts +2 -0
  128. package/dist/tests/AbstractBase.test.d.ts.map +1 -0
  129. package/dist/tests/AbstractBase.test.js +130 -0
  130. package/dist/tests/AbstractBase.test.js.map +1 -0
  131. package/dist/tests/AbstractLeaf.test.d.ts +2 -0
  132. package/dist/tests/AbstractLeaf.test.d.ts.map +1 -0
  133. package/dist/tests/AbstractLeaf.test.js +65 -0
  134. package/dist/tests/AbstractLeaf.test.js.map +1 -0
  135. package/dist/tests/Annotations.test.d.ts +2 -0
  136. package/dist/tests/Annotations.test.d.ts.map +1 -0
  137. package/dist/tests/Annotations.test.js +34 -0
  138. package/dist/tests/Annotations.test.js.map +1 -0
  139. package/dist/tests/BarrelExport.test.d.ts +2 -0
  140. package/dist/tests/BarrelExport.test.d.ts.map +1 -0
  141. package/dist/tests/BarrelExport.test.js +42 -0
  142. package/dist/tests/BarrelExport.test.js.map +1 -0
  143. package/dist/tests/Converters.test.d.ts +2 -0
  144. package/dist/tests/Converters.test.d.ts.map +1 -0
  145. package/dist/tests/Converters.test.js +193 -0
  146. package/dist/tests/Converters.test.js.map +1 -0
  147. package/dist/tests/Group.test.d.ts +2 -0
  148. package/dist/tests/Group.test.d.ts.map +1 -0
  149. package/dist/tests/Group.test.js +257 -0
  150. package/dist/tests/Group.test.js.map +1 -0
  151. package/dist/tests/Icon.test.d.ts +2 -0
  152. package/dist/tests/Icon.test.d.ts.map +1 -0
  153. package/dist/tests/Icon.test.js +44 -0
  154. package/dist/tests/Icon.test.js.map +1 -0
  155. package/dist/tests/Prompt.test.d.ts +2 -0
  156. package/dist/tests/Prompt.test.d.ts.map +1 -0
  157. package/dist/tests/Prompt.test.js +63 -0
  158. package/dist/tests/Prompt.test.js.map +1 -0
  159. package/dist/tests/PromptArgument.test.d.ts +2 -0
  160. package/dist/tests/PromptArgument.test.d.ts.map +1 -0
  161. package/dist/tests/PromptArgument.test.js +37 -0
  162. package/dist/tests/PromptArgument.test.js.map +1 -0
  163. package/dist/tests/Resource.test.d.ts +2 -0
  164. package/dist/tests/Resource.test.d.ts.map +1 -0
  165. package/dist/tests/Resource.test.js +61 -0
  166. package/dist/tests/Resource.test.js.map +1 -0
  167. package/dist/tests/Role.test.d.ts +2 -0
  168. package/dist/tests/Role.test.d.ts.map +1 -0
  169. package/dist/tests/Role.test.js +17 -0
  170. package/dist/tests/Role.test.js.map +1 -0
  171. package/dist/tests/Tool.test.d.ts +2 -0
  172. package/dist/tests/Tool.test.d.ts.map +1 -0
  173. package/dist/tests/Tool.test.js +62 -0
  174. package/dist/tests/Tool.test.js.map +1 -0
  175. package/dist/tests/ToolAnnotations.test.d.ts +2 -0
  176. package/dist/tests/ToolAnnotations.test.d.ts.map +1 -0
  177. package/dist/tests/ToolAnnotations.test.js +55 -0
  178. package/dist/tests/ToolAnnotations.test.js.map +1 -0
  179. package/dist/tests/framework/AdversarialQA.test.d.ts +2 -0
  180. package/dist/tests/framework/AdversarialQA.test.d.ts.map +1 -0
  181. package/dist/tests/framework/AdversarialQA.test.js +906 -0
  182. package/dist/tests/framework/AdversarialQA.test.js.map +1 -0
  183. package/dist/tests/framework/GroupedToolBuilder.test.d.ts +2 -0
  184. package/dist/tests/framework/GroupedToolBuilder.test.d.ts.map +1 -0
  185. package/dist/tests/framework/GroupedToolBuilder.test.js +712 -0
  186. package/dist/tests/framework/GroupedToolBuilder.test.js.map +1 -0
  187. package/dist/tests/framework/LargeScaleScenarios.test.d.ts +2 -0
  188. package/dist/tests/framework/LargeScaleScenarios.test.d.ts.map +1 -0
  189. package/dist/tests/framework/LargeScaleScenarios.test.js +1043 -0
  190. package/dist/tests/framework/LargeScaleScenarios.test.js.map +1 -0
  191. package/dist/tests/framework/McpServerAdapter.test.d.ts +2 -0
  192. package/dist/tests/framework/McpServerAdapter.test.d.ts.map +1 -0
  193. package/dist/tests/framework/McpServerAdapter.test.js +380 -0
  194. package/dist/tests/framework/McpServerAdapter.test.js.map +1 -0
  195. package/dist/tests/framework/ResponseHelper.test.d.ts +2 -0
  196. package/dist/tests/framework/ResponseHelper.test.d.ts.map +1 -0
  197. package/dist/tests/framework/ResponseHelper.test.js +202 -0
  198. package/dist/tests/framework/ResponseHelper.test.js.map +1 -0
  199. package/dist/tests/framework/SecurityDeep.test.d.ts +2 -0
  200. package/dist/tests/framework/SecurityDeep.test.d.ts.map +1 -0
  201. package/dist/tests/framework/SecurityDeep.test.js +825 -0
  202. package/dist/tests/framework/SecurityDeep.test.js.map +1 -0
  203. package/dist/tests/framework/ToolRegistry.test.d.ts +2 -0
  204. package/dist/tests/framework/ToolRegistry.test.d.ts.map +1 -0
  205. package/dist/tests/framework/ToolRegistry.test.js +152 -0
  206. package/dist/tests/framework/ToolRegistry.test.js.map +1 -0
  207. package/dist/tests/framework/ToonDescription.test.d.ts +2 -0
  208. package/dist/tests/framework/ToonDescription.test.d.ts.map +1 -0
  209. package/dist/tests/framework/ToonDescription.test.js +287 -0
  210. package/dist/tests/framework/ToonDescription.test.js.map +1 -0
  211. package/package.json +64 -0
@@ -0,0 +1,1043 @@
1
+ /**
2
+ * LargeScaleScenarios.test.ts
3
+ *
4
+ * Simulates thousands of MCP endpoints consolidated through the grouping
5
+ * framework, exercising tag-based selective exposure, mass registration,
6
+ * routing, and schema validation at enterprise scale.
7
+ *
8
+ * Domains modeled:
9
+ * - Project Management (tasks, sprints, boards, labels, epics)
10
+ * - CRM (contacts, deals, pipelines, activities, companies)
11
+ * - DevOps (deployments, pipelines, artifacts, environments, monitors)
12
+ * - Collaboration (channels, messages, threads, reactions, files)
13
+ * - Analytics (dashboards, reports, metrics, exports, schedules)
14
+ * - Finance (invoices, payments, subscriptions, refunds, taxes)
15
+ * - Identity (users, roles, permissions, tokens, sessions)
16
+ * - Storage (buckets, objects, versions, policies, lifecycles)
17
+ * - Notifications (templates, channels, preferences, logs, rules)
18
+ * - Integrations (webhooks, connections, transforms, mappings, syncs)
19
+ */
20
+ import { describe, it, expect, beforeAll } from 'vitest';
21
+ import { z } from 'zod';
22
+ import { GroupedToolBuilder } from '../../src/framework/GroupedToolBuilder.js';
23
+ import { ToolRegistry } from '../../src/framework/ToolRegistry.js';
24
+ import { success } from '../../src/framework/ResponseHelper.js';
25
+ // ============================================================================
26
+ // Helpers — Factory functions to generate realistic domain builders
27
+ // ============================================================================
28
+ /** Standard CRUD + 2 extra actions per entity = 7 actions each */
29
+ const CRUD_ACTIONS = ['list', 'get', 'create', 'update', 'delete', 'archive', 'export'];
30
+ const DOMAINS = [
31
+ {
32
+ domain: 'project_management',
33
+ tags: ['pm', 'core'],
34
+ entities: ['tasks', 'sprints', 'boards', 'labels', 'epics', 'milestones', 'comments', 'attachments', 'time_entries', 'checklists'],
35
+ },
36
+ {
37
+ domain: 'crm',
38
+ tags: ['crm', 'sales'],
39
+ entities: ['contacts', 'deals', 'pipelines', 'activities', 'companies', 'emails', 'notes', 'tags', 'segments', 'campaigns'],
40
+ },
41
+ {
42
+ domain: 'devops',
43
+ tags: ['devops', 'infra'],
44
+ entities: ['deployments', 'ci_pipelines', 'artifacts', 'environments', 'monitors', 'alerts', 'logs', 'configs', 'secrets', 'clusters'],
45
+ },
46
+ {
47
+ domain: 'collaboration',
48
+ tags: ['collab', 'core'],
49
+ entities: ['channels', 'messages', 'threads', 'reactions', 'files', 'mentions', 'bookmarks', 'pins', 'polls', 'events'],
50
+ },
51
+ {
52
+ domain: 'analytics',
53
+ tags: ['analytics', 'reporting'],
54
+ entities: ['dashboards', 'reports', 'metrics', 'exports', 'schedules', 'widgets', 'filters', 'datasets', 'annotations', 'alerts'],
55
+ },
56
+ {
57
+ domain: 'finance',
58
+ tags: ['finance', 'billing'],
59
+ entities: ['invoices', 'payments', 'subscriptions', 'refunds', 'taxes', 'credits', 'plans', 'coupons', 'receipts', 'ledger'],
60
+ },
61
+ {
62
+ domain: 'identity',
63
+ tags: ['identity', 'security'],
64
+ entities: ['users', 'roles', 'permissions', 'tokens', 'sessions', 'groups', 'policies', 'audits', 'mfa', 'invitations'],
65
+ },
66
+ {
67
+ domain: 'storage',
68
+ tags: ['storage', 'infra'],
69
+ entities: ['buckets', 'objects', 'versions', 'acl_policies', 'lifecycles', 'transfers', 'archives', 'quotas', 'replications', 'snapshots'],
70
+ },
71
+ {
72
+ domain: 'notifications',
73
+ tags: ['notifications', 'core'],
74
+ entities: ['templates', 'channels', 'preferences', 'delivery_logs', 'rules', 'batches', 'schedules', 'providers', 'suppressions', 'digests'],
75
+ },
76
+ {
77
+ domain: 'integrations',
78
+ tags: ['integrations', 'core'],
79
+ entities: ['webhooks', 'connections', 'transforms', 'mappings', 'syncs', 'oauth_apps', 'api_keys', 'rate_limits', 'event_bus', 'schemas'],
80
+ },
81
+ ];
82
+ // Total endpoints = 10 domains × 10 entities × 7 actions = 700 actions
83
+ // Consolidated into 10 domains × 10 entities = 100 grouped tools
84
+ // Each tool consolidates 7 REST-equivalent endpoints into 1 MCP tool
85
+ /**
86
+ * Build a GroupedToolBuilder for a domain entity.
87
+ * Uses flat mode: entity name as tool name, actions as CRUD verbs.
88
+ */
89
+ function buildEntityTool(domain, entity, domainTags) {
90
+ const builder = new GroupedToolBuilder(`${domain}_${entity}`)
91
+ .description(`Manage ${entity} within ${domain}`)
92
+ .tags(...domainTags, entity);
93
+ for (const action of CRUD_ACTIONS) {
94
+ const isWrite = ['create', 'update', 'delete', 'archive'].includes(action);
95
+ const isRead = ['list', 'get', 'export'].includes(action);
96
+ builder.action({
97
+ name: action,
98
+ description: `${action} ${entity}`,
99
+ readOnly: isRead,
100
+ schema: action === 'get'
101
+ ? z.object({ id: z.string().describe(`ID of the ${entity.slice(0, -1)}`) })
102
+ : action === 'create'
103
+ ? z.object({ name: z.string().describe('Name for the new record') })
104
+ : action === 'update'
105
+ ? z.object({
106
+ id: z.string().describe('ID to update'),
107
+ data: z.string().describe('JSON payload'),
108
+ })
109
+ : action === 'delete' || action === 'archive'
110
+ ? z.object({ id: z.string().describe('ID to process') })
111
+ : action === 'export'
112
+ ? z.object({ format: z.enum(['csv', 'json', 'xlsx']).describe('Export format') })
113
+ : undefined, // 'list' has no extra params
114
+ handler: async (_ctx, args) => {
115
+ return success(`[${domain}/${entity}] ${action} executed ${JSON.stringify(args)}`);
116
+ },
117
+ });
118
+ }
119
+ return builder;
120
+ }
121
+ /**
122
+ * Build all entity tools for a domain and return them.
123
+ */
124
+ function buildDomainTools(def) {
125
+ return def.entities.map(entity => buildEntityTool(def.domain, entity, def.tags));
126
+ }
127
+ // ============================================================================
128
+ // Test Suites
129
+ // ============================================================================
130
+ describe('Large-Scale Scenarios — Mass Endpoint Registration', () => {
131
+ const registry = new ToolRegistry();
132
+ const allBuilders = [];
133
+ // Register all domains
134
+ for (const domain of DOMAINS) {
135
+ const builders = buildDomainTools(domain);
136
+ allBuilders.push(...builders);
137
+ }
138
+ it('should register 100 grouped tools (700 consolidated endpoints)', () => {
139
+ for (const builder of allBuilders) {
140
+ registry.register(builder);
141
+ }
142
+ expect(registry.size).toBe(100); // 10 domains × 10 entities
143
+ });
144
+ it('should expose all 100 tool definitions via getAllTools()', () => {
145
+ const tools = registry.getAllTools();
146
+ expect(tools).toHaveLength(100);
147
+ // Every tool must have a valid JSON schema with the 'action' discriminator
148
+ for (const tool of tools) {
149
+ expect(tool.name).toBeTruthy();
150
+ expect(tool.inputSchema).toBeDefined();
151
+ expect(tool.inputSchema.properties).toHaveProperty('action');
152
+ }
153
+ });
154
+ it('each tool should enumerate exactly 7 actions in its enum', () => {
155
+ const tools = registry.getAllTools();
156
+ for (const tool of tools) {
157
+ const actionProp = tool.inputSchema.properties['action'];
158
+ const enumValues = actionProp['enum'];
159
+ expect(enumValues).toHaveLength(CRUD_ACTIONS.length);
160
+ expect(enumValues).toEqual(expect.arrayContaining([...CRUD_ACTIONS]));
161
+ }
162
+ });
163
+ it('should reject duplicate tool registrations', () => {
164
+ const duplicate = buildEntityTool('project_management', 'tasks', ['pm']);
165
+ expect(() => registry.register(duplicate)).toThrow('already registered');
166
+ });
167
+ });
168
+ // ============================================================================
169
+ // Tag-Based Filtering at Scale
170
+ // ============================================================================
171
+ describe('Large-Scale Scenarios — Tag-Based Selective Exposure', () => {
172
+ let registry;
173
+ // Re-create registry for isolation
174
+ beforeAll(() => {
175
+ registry = new ToolRegistry();
176
+ for (const domain of DOMAINS) {
177
+ for (const builder of buildDomainTools(domain)) {
178
+ registry.register(builder);
179
+ }
180
+ }
181
+ });
182
+ it('should filter by single domain tag → 10 tools', () => {
183
+ const pmTools = registry.getTools({ tags: ['pm'] });
184
+ expect(pmTools).toHaveLength(10);
185
+ for (const tool of pmTools) {
186
+ expect(tool.name).toContain('project_management');
187
+ }
188
+ });
189
+ it('should filter by "core" tag → 40 tools (pm + collab + notif + integrations)', () => {
190
+ const coreTools = registry.getTools({ tags: ['core'] });
191
+ // pm(10) + collab(10) + notifications(10) + integrations(10) = 40
192
+ expect(coreTools).toHaveLength(40);
193
+ });
194
+ it('should filter by "infra" tag → 20 tools (devops + storage)', () => {
195
+ const infraTools = registry.getTools({ tags: ['infra'] });
196
+ expect(infraTools).toHaveLength(20);
197
+ });
198
+ it('should intersect multiple tags → narrow selection', () => {
199
+ // "core" AND "tasks" entity tag → only project_management_tasks
200
+ const narrow = registry.getTools({ tags: ['core', 'tasks'] });
201
+ expect(narrow).toHaveLength(1);
202
+ expect(narrow[0].name).toBe('project_management_tasks');
203
+ });
204
+ it('should exclude specific tags', () => {
205
+ // All 100 tools minus finance(10) and identity(10) = 80
206
+ const filtered = registry.getTools({
207
+ exclude: ['finance', 'identity'],
208
+ });
209
+ expect(filtered).toHaveLength(80);
210
+ for (const tool of filtered) {
211
+ expect(tool.name).not.toContain('finance');
212
+ expect(tool.name).not.toContain('identity');
213
+ }
214
+ });
215
+ it('should combine include + exclude tags', () => {
216
+ // "core" tagged (40 tools) minus "notifications" (10) = 30
217
+ const filtered = registry.getTools({
218
+ tags: ['core'],
219
+ exclude: ['notifications'],
220
+ });
221
+ expect(filtered).toHaveLength(30);
222
+ });
223
+ it('should return empty set for non-existent tag', () => {
224
+ const empty = registry.getTools({ tags: ['nonexistent-tag-xyz'] });
225
+ expect(empty).toHaveLength(0);
226
+ });
227
+ it('should return all tools when filter is empty', () => {
228
+ const all = registry.getTools({});
229
+ expect(all).toHaveLength(100);
230
+ });
231
+ it('should handle overlapping domain tags correctly', () => {
232
+ // "infra" appears in both devops and storage
233
+ const devopsOnly = registry.getTools({ tags: ['infra', 'devops'] });
234
+ expect(devopsOnly).toHaveLength(10);
235
+ for (const tool of devopsOnly) {
236
+ expect(tool.name).toContain('devops');
237
+ }
238
+ });
239
+ });
240
+ // ============================================================================
241
+ // Routing at Scale — Dispatch to Correct Tool + Action
242
+ // ============================================================================
243
+ describe('Large-Scale Scenarios — Routing & Execution', () => {
244
+ let registry;
245
+ beforeAll(() => {
246
+ registry = new ToolRegistry();
247
+ for (const domain of DOMAINS) {
248
+ for (const builder of buildDomainTools(domain)) {
249
+ registry.register(builder);
250
+ }
251
+ }
252
+ });
253
+ it('should route to the correct tool and action', async () => {
254
+ const result = await registry.routeCall(undefined, 'crm_contacts', { action: 'get', id: 'contact-42' });
255
+ expect(result.isError).toBeUndefined();
256
+ const text = result.content[0].text;
257
+ expect(text).toContain('crm/contacts');
258
+ expect(text).toContain('get');
259
+ });
260
+ it('should validate schema — missing required `id` on get action', async () => {
261
+ const result = await registry.routeCall(undefined, 'devops_deployments', { action: 'get' } // missing 'id'
262
+ );
263
+ expect(result.isError).toBe(true);
264
+ const text = result.content[0].text;
265
+ expect(text).toContain('id');
266
+ });
267
+ it('should return error for unknown tool', async () => {
268
+ const result = await registry.routeCall(undefined, 'nonexistent_tool', { action: 'list' });
269
+ expect(result.isError).toBe(true);
270
+ const text = result.content[0].text;
271
+ expect(text).toContain('Unknown tool');
272
+ });
273
+ it('should return error for unknown action within valid tool', async () => {
274
+ const result = await registry.routeCall(undefined, 'finance_invoices', { action: 'teleport' });
275
+ expect(result.isError).toBe(true);
276
+ const text = result.content[0].text;
277
+ expect(text).toContain('teleport');
278
+ });
279
+ it('should successfully execute CRUD actions across different domains', async () => {
280
+ const scenarios = [
281
+ { tool: 'project_management_tasks', action: 'list', args: {} },
282
+ { tool: 'crm_deals', action: 'create', args: { name: 'Big Deal' } },
283
+ { tool: 'devops_ci_pipelines', action: 'get', args: { id: 'pipe-7' } },
284
+ { tool: 'collaboration_messages', action: 'delete', args: { id: 'msg-99' } },
285
+ { tool: 'analytics_reports', action: 'export', args: { format: 'csv' } },
286
+ { tool: 'finance_payments', action: 'archive', args: { id: 'pay-123' } },
287
+ { tool: 'identity_users', action: 'update', args: { id: 'usr-1', data: '{"name":"Alice"}' } },
288
+ { tool: 'storage_buckets', action: 'list', args: {} },
289
+ { tool: 'notifications_templates', action: 'get', args: { id: 'tpl-42' } },
290
+ { tool: 'integrations_webhooks', action: 'create', args: { name: 'My Hook' } },
291
+ ];
292
+ for (const { tool, action, args } of scenarios) {
293
+ const result = await registry.routeCall(undefined, tool, { action, ...args });
294
+ expect(result.isError).toBeUndefined();
295
+ }
296
+ });
297
+ });
298
+ // ============================================================================
299
+ // Stress Test — Programmatic Generation of N Builders
300
+ // ============================================================================
301
+ describe('Large-Scale Scenarios — Stress: 500 Grouped Tools', () => {
302
+ it('should register and query 500 builders (3500 endpoints) efficiently', () => {
303
+ const stressRegistry = new ToolRegistry();
304
+ // Generate 50 synthetic domains × 10 entities each = 500 tools
305
+ for (let d = 0; d < 50; d++) {
306
+ const domainName = `domain_${String(d).padStart(3, '0')}`;
307
+ const domainTag = d < 25 ? 'tier_a' : 'tier_b';
308
+ for (let e = 0; e < 10; e++) {
309
+ const entityName = `entity_${String(e).padStart(2, '0')}`;
310
+ const builder = buildEntityTool(domainName, entityName, [domainTag, `d${d}`]);
311
+ stressRegistry.register(builder);
312
+ }
313
+ }
314
+ expect(stressRegistry.size).toBe(500);
315
+ // All tools
316
+ const all = stressRegistry.getAllTools();
317
+ expect(all).toHaveLength(500);
318
+ // Tag filtering — tier_a has first 25 domains × 10 entities = 250
319
+ const tierA = stressRegistry.getTools({ tags: ['tier_a'] });
320
+ expect(tierA).toHaveLength(250);
321
+ const tierB = stressRegistry.getTools({ tags: ['tier_b'] });
322
+ expect(tierB).toHaveLength(250);
323
+ // Single-domain filter
324
+ const d7 = stressRegistry.getTools({ tags: ['d7'] });
325
+ expect(d7).toHaveLength(10);
326
+ // Exclude tier_b → only tier_a remains
327
+ const noTierB = stressRegistry.getTools({ exclude: ['tier_b'] });
328
+ expect(noTierB).toHaveLength(250);
329
+ });
330
+ });
331
+ // ============================================================================
332
+ // Schema Introspection — Verify Generated Descriptions at Scale
333
+ // ============================================================================
334
+ describe('Large-Scale Scenarios — Schema Introspection', () => {
335
+ it('should generate meaningful descriptions for every tool', () => {
336
+ const registry = new ToolRegistry();
337
+ for (const domain of DOMAINS) {
338
+ for (const builder of buildDomainTools(domain)) {
339
+ registry.register(builder);
340
+ }
341
+ }
342
+ const tools = registry.getAllTools();
343
+ for (const tool of tools) {
344
+ // Description must mention the entity and domain
345
+ expect(tool.description).toBeTruthy();
346
+ expect(tool.description.length).toBeGreaterThan(10);
347
+ }
348
+ });
349
+ it('should include workflow lines listing all CRUD actions', () => {
350
+ const builder = buildEntityTool('test', 'widgets', ['test']);
351
+ const def = builder.buildToolDefinition();
352
+ // Description should list all 7 action capabilities
353
+ for (const action of CRUD_ACTIONS) {
354
+ expect(def.description).toContain(action);
355
+ }
356
+ });
357
+ it('should correctly aggregate readOnlyHint across mixed actions', () => {
358
+ var _a;
359
+ const builder = buildEntityTool('test', 'samples', ['test']);
360
+ const def = builder.buildToolDefinition();
361
+ // Since this tool has both read and write actions,
362
+ // readOnlyHint must be false (not all actions are read-only)
363
+ expect((_a = def.annotations) === null || _a === void 0 ? void 0 : _a.readOnlyHint).toBe(false);
364
+ });
365
+ it('should produce unique tool names across all domains', () => {
366
+ const registry = new ToolRegistry();
367
+ for (const domain of DOMAINS) {
368
+ for (const builder of buildDomainTools(domain)) {
369
+ registry.register(builder);
370
+ }
371
+ }
372
+ const names = registry.getAllTools().map(t => t.name);
373
+ const uniqueNames = new Set(names);
374
+ expect(uniqueNames.size).toBe(names.length);
375
+ });
376
+ });
377
+ // ============================================================================
378
+ // Grouped Mode at Scale — Hierarchical Actions
379
+ // ============================================================================
380
+ describe('Large-Scale Scenarios — Grouped Mode Hierarchical', () => {
381
+ it('should support grouped tools with multiple subgroups', () => {
382
+ const builder = new GroupedToolBuilder('enterprise_api')
383
+ .description('Full enterprise API surface')
384
+ .tags('enterprise', 'all')
385
+ .group('users', 'User management', g => g
386
+ .action({ name: 'list', handler: async () => success('users listed') })
387
+ .action({ name: 'create', handler: async () => success('user created') })
388
+ .action({ name: 'delete', handler: async () => success('user deleted') }))
389
+ .group('projects', 'Project management', g => g
390
+ .action({ name: 'list', handler: async () => success('projects listed') })
391
+ .action({ name: 'create', handler: async () => success('project created') })
392
+ .action({ name: 'archive', handler: async () => success('project archived') }))
393
+ .group('billing', 'Billing operations', g => g
394
+ .action({ name: 'invoices', handler: async () => success('invoices listed') })
395
+ .action({ name: 'payments', handler: async () => success('payments listed') }));
396
+ const def = builder.buildToolDefinition();
397
+ // Should produce compound action enum: users.list, users.create, etc.
398
+ const actionProp = def.inputSchema.properties['action'];
399
+ const enumValues = actionProp['enum'];
400
+ expect(enumValues).toContain('users.list');
401
+ expect(enumValues).toContain('users.create');
402
+ expect(enumValues).toContain('users.delete');
403
+ expect(enumValues).toContain('projects.list');
404
+ expect(enumValues).toContain('projects.create');
405
+ expect(enumValues).toContain('projects.archive');
406
+ expect(enumValues).toContain('billing.invoices');
407
+ expect(enumValues).toContain('billing.payments');
408
+ expect(enumValues).toHaveLength(8);
409
+ });
410
+ it('should correctly route grouped hierarchical actions', async () => {
411
+ const builder = new GroupedToolBuilder('enterprise_api')
412
+ .description('API')
413
+ .group('users', 'Users', g => g
414
+ .action({
415
+ name: 'get',
416
+ schema: z.object({ id: z.string() }),
417
+ handler: async (_ctx, args) => success(`user ${args.id}`),
418
+ }))
419
+ .group('billing', 'Billing', g => g
420
+ .action({
421
+ name: 'charge',
422
+ schema: z.object({ amount: z.number() }),
423
+ handler: async (_ctx, args) => success(`charged ${args.amount}`),
424
+ }));
425
+ builder.buildToolDefinition();
426
+ const userResult = await builder.execute(undefined, {
427
+ action: 'users.get',
428
+ id: 'usr-42',
429
+ });
430
+ expect(userResult.isError).toBeUndefined();
431
+ expect(userResult.content[0].text).toContain('user usr-42');
432
+ const billingResult = await builder.execute(undefined, {
433
+ action: 'billing.charge',
434
+ amount: 99.99,
435
+ });
436
+ expect(billingResult.isError).toBeUndefined();
437
+ expect(billingResult.content[0].text).toContain('charged 99.99');
438
+ });
439
+ });
440
+ // ============================================================================
441
+ // Edge Cases in Large Registries
442
+ // ============================================================================
443
+ describe('Large-Scale Scenarios — Edge Cases', () => {
444
+ it('should handle registry clear and re-registration', () => {
445
+ const registry = new ToolRegistry();
446
+ // Register some tools
447
+ for (const builder of buildDomainTools(DOMAINS[0])) {
448
+ registry.register(builder);
449
+ }
450
+ expect(registry.size).toBe(10);
451
+ // Clear
452
+ registry.clear();
453
+ expect(registry.size).toBe(0);
454
+ expect(registry.getAllTools()).toHaveLength(0);
455
+ // Re-register (same names should work after clear)
456
+ for (const builder of buildDomainTools(DOMAINS[0])) {
457
+ registry.register(builder);
458
+ }
459
+ expect(registry.size).toBe(10);
460
+ });
461
+ it('should report has() correctly for all registered tools', () => {
462
+ const registry = new ToolRegistry();
463
+ for (const builder of buildDomainTools(DOMAINS[1])) {
464
+ registry.register(builder);
465
+ }
466
+ expect(registry.has('crm_contacts')).toBe(true);
467
+ expect(registry.has('crm_deals')).toBe(true);
468
+ expect(registry.has('nonexistent_tool')).toBe(false);
469
+ });
470
+ it('should exclude all tools when exclude tag matches all', () => {
471
+ const registry = new ToolRegistry();
472
+ // Register only PM tools (tagged 'pm', 'core')
473
+ for (const builder of buildDomainTools(DOMAINS[0])) {
474
+ registry.register(builder);
475
+ }
476
+ const filtered = registry.getTools({ exclude: ['pm'] });
477
+ expect(filtered).toHaveLength(0);
478
+ });
479
+ it('should handle empty registry gracefully', () => {
480
+ const registry = new ToolRegistry();
481
+ expect(registry.getAllTools()).toHaveLength(0);
482
+ expect(registry.getTools({ tags: ['any'] })).toHaveLength(0);
483
+ expect(registry.size).toBe(0);
484
+ });
485
+ });
486
+ // ============================================================================
487
+ // ENTERPRISE CHAOS SCENARIOS
488
+ // These simulate real-world abuse patterns: LLMs sending garbage, malformed
489
+ // schemas, injection attempts, handler explosions, race-like registration,
490
+ // unicode madness, and middleware chains under pressure.
491
+ // ============================================================================
492
+ // ── Chaos 1: LLM Sends Garbage ─────────────────────────────────────────────
493
+ // Real scenario: LLM hallucinates action names, sends wrong types, injects
494
+ // extra fields, omits required params, or sends completely empty payloads.
495
+ describe('Enterprise Chaos — LLM Garbage Input', () => {
496
+ let registry;
497
+ beforeAll(() => {
498
+ registry = new ToolRegistry();
499
+ const builder = new GroupedToolBuilder('ticket_system')
500
+ .description('Manage support tickets')
501
+ .tags('support')
502
+ .commonSchema(z.object({
503
+ workspace_id: z.string().describe('Workspace identifier'),
504
+ }))
505
+ .action({
506
+ name: 'create',
507
+ description: 'Create a new ticket',
508
+ schema: z.object({
509
+ title: z.string().min(1).max(500),
510
+ priority: z.enum(['low', 'medium', 'high', 'critical']),
511
+ description: z.string().optional(),
512
+ }),
513
+ handler: async (_ctx, args) => success(`ticket created: ${args.title}`),
514
+ })
515
+ .action({
516
+ name: 'list',
517
+ description: 'List tickets',
518
+ readOnly: true,
519
+ handler: async (_ctx, args) => success(`listed for ${args.workspace_id}`),
520
+ })
521
+ .action({
522
+ name: 'close',
523
+ description: 'Close a ticket',
524
+ schema: z.object({
525
+ ticket_id: z.string(),
526
+ resolution: z.string().min(10),
527
+ }),
528
+ handler: async (_ctx, args) => success(`closed ${args.ticket_id}`),
529
+ });
530
+ registry.register(builder);
531
+ });
532
+ it('should reject completely empty payload', async () => {
533
+ const result = await registry.routeCall(undefined, 'ticket_system', {});
534
+ expect(result.isError).toBe(true);
535
+ });
536
+ it('should reject payload with no action field', async () => {
537
+ const result = await registry.routeCall(undefined, 'ticket_system', {
538
+ workspace_id: 'ws-1',
539
+ title: 'Bug report',
540
+ });
541
+ expect(result.isError).toBe(true);
542
+ });
543
+ it('should reject hallucinated action name', async () => {
544
+ const result = await registry.routeCall(undefined, 'ticket_system', {
545
+ action: 'reopen_and_escalate_to_manager',
546
+ workspace_id: 'ws-1',
547
+ });
548
+ expect(result.isError).toBe(true);
549
+ const text = result.content[0].text;
550
+ expect(text).toContain('reopen_and_escalate_to_manager');
551
+ });
552
+ it('should reject wrong types — number instead of string for workspace_id', async () => {
553
+ const result = await registry.routeCall(undefined, 'ticket_system', {
554
+ action: 'create',
555
+ workspace_id: 12345, // should be string
556
+ title: 'Test',
557
+ priority: 'high',
558
+ });
559
+ expect(result.isError).toBe(true);
560
+ });
561
+ it('should reject invalid enum value for priority', async () => {
562
+ const result = await registry.routeCall(undefined, 'ticket_system', {
563
+ action: 'create',
564
+ workspace_id: 'ws-1',
565
+ title: 'Test',
566
+ priority: 'ULTRA_MEGA_CRITICAL', // not in enum
567
+ });
568
+ expect(result.isError).toBe(true);
569
+ });
570
+ it('should strip unknown fields (LLM adds extra hallucinated params)', async () => {
571
+ const result = await registry.routeCall(undefined, 'ticket_system', {
572
+ action: 'list',
573
+ workspace_id: 'ws-1',
574
+ hallucinated_filter: 'open',
575
+ sort_by_moon_phase: true,
576
+ __internal_admin_override: true,
577
+ });
578
+ // Should succeed — extra fields are stripped by .strip()
579
+ expect(result.isError).toBeUndefined();
580
+ });
581
+ it('should reject too-short resolution on close', async () => {
582
+ const result = await registry.routeCall(undefined, 'ticket_system', {
583
+ action: 'close',
584
+ workspace_id: 'ws-1',
585
+ ticket_id: 'TKT-42',
586
+ resolution: 'done', // too short, min 10
587
+ });
588
+ expect(result.isError).toBe(true);
589
+ });
590
+ it('should reject empty string title', async () => {
591
+ const result = await registry.routeCall(undefined, 'ticket_system', {
592
+ action: 'create',
593
+ workspace_id: 'ws-1',
594
+ title: '', // min 1 char
595
+ priority: 'low',
596
+ });
597
+ expect(result.isError).toBe(true);
598
+ });
599
+ it('should accept valid input after multiple failures', async () => {
600
+ const result = await registry.routeCall(undefined, 'ticket_system', {
601
+ action: 'create',
602
+ workspace_id: 'ws-1',
603
+ title: 'Login page returns 500 after OAuth redirect',
604
+ priority: 'critical',
605
+ description: 'Steps to reproduce: ...',
606
+ });
607
+ expect(result.isError).toBeUndefined();
608
+ const text = result.content[0].text;
609
+ expect(text).toContain('Login page');
610
+ });
611
+ });
612
+ // ── Chaos 2: Handler Explosions ─────────────────────────────────────────────
613
+ // Real scenario: handlers throw sync errors, async rejections, return
614
+ // undefined, throw non-Error objects, or timeout.
615
+ describe('Enterprise Chaos — Handler Explosions', () => {
616
+ it('should catch sync throw in handler', async () => {
617
+ const builder = new GroupedToolBuilder('exploding_service')
618
+ .action({
619
+ name: 'boom',
620
+ handler: () => { throw new Error('DATABASE_CONNECTION_REFUSED'); },
621
+ });
622
+ builder.buildToolDefinition();
623
+ const result = await builder.execute(undefined, { action: 'boom' });
624
+ expect(result.isError).toBe(true);
625
+ expect(result.content[0].text).toContain('DATABASE_CONNECTION_REFUSED');
626
+ });
627
+ it('should catch async rejection in handler', async () => {
628
+ const builder = new GroupedToolBuilder('async_fail')
629
+ .action({
630
+ name: 'fetch',
631
+ handler: async () => { throw new Error('ECONNRESET: connection reset by peer'); },
632
+ });
633
+ builder.buildToolDefinition();
634
+ const result = await builder.execute(undefined, { action: 'fetch' });
635
+ expect(result.isError).toBe(true);
636
+ expect(result.content[0].text).toContain('ECONNRESET');
637
+ });
638
+ it('should handle throwing a string (non-Error throw)', async () => {
639
+ const builder = new GroupedToolBuilder('string_throw')
640
+ .action({
641
+ name: 'fail',
642
+ handler: async () => { throw 'RATE_LIMIT_EXCEEDED'; },
643
+ });
644
+ builder.buildToolDefinition();
645
+ const result = await builder.execute(undefined, { action: 'fail' });
646
+ expect(result.isError).toBe(true);
647
+ expect(result.content[0].text).toBe('[string_throw/fail] RATE_LIMIT_EXCEEDED');
648
+ });
649
+ it('should handle throwing an object (non-Error throw)', async () => {
650
+ const builder = new GroupedToolBuilder('object_throw')
651
+ .action({
652
+ name: 'process',
653
+ handler: async () => { throw { code: 503, message: 'Service Unavailable' }; },
654
+ });
655
+ builder.buildToolDefinition();
656
+ const result = await builder.execute(undefined, { action: 'process' });
657
+ expect(result.isError).toBe(true);
658
+ });
659
+ it('should handle throwing null', async () => {
660
+ const builder = new GroupedToolBuilder('null_throw')
661
+ .action({
662
+ name: 'crash',
663
+ handler: async () => { throw null; },
664
+ });
665
+ builder.buildToolDefinition();
666
+ const result = await builder.execute(undefined, { action: 'crash' });
667
+ expect(result.isError).toBe(true);
668
+ });
669
+ it('should handle throwing undefined', async () => {
670
+ const builder = new GroupedToolBuilder('undef_throw')
671
+ .action({
672
+ name: 'ghost',
673
+ handler: async () => { throw undefined; },
674
+ });
675
+ builder.buildToolDefinition();
676
+ const result = await builder.execute(undefined, { action: 'ghost' });
677
+ expect(result.isError).toBe(true);
678
+ });
679
+ });
680
+ // ── Chaos 3: Deeply Nested Schemas ──────────────────────────────────────────
681
+ // Real scenario: enterprise APIs have deeply nested config objects,
682
+ // arrays of objects, optional nested blocks, etc.
683
+ describe('Enterprise Chaos — Complex Nested Schemas', () => {
684
+ it('should validate deeply nested config objects', async () => {
685
+ const builder = new GroupedToolBuilder('deployment_manager')
686
+ .action({
687
+ name: 'deploy',
688
+ schema: z.object({
689
+ environment: z.enum(['staging', 'production']),
690
+ config: z.object({
691
+ replicas: z.number().int().min(1).max(100),
692
+ resources: z.object({
693
+ cpu: z.string().regex(/^\d+m$/),
694
+ memory: z.string().regex(/^\d+Mi$/),
695
+ }),
696
+ env_vars: z.array(z.object({
697
+ name: z.string(),
698
+ value: z.string(),
699
+ })).optional(),
700
+ }),
701
+ rollback_on_failure: z.boolean().default(true),
702
+ }),
703
+ handler: async (_ctx, args) => success(`deploying to ${args.environment} with ${args.config.replicas} replicas`),
704
+ });
705
+ builder.buildToolDefinition();
706
+ // Valid complex nested input
707
+ const result = await builder.execute(undefined, {
708
+ action: 'deploy',
709
+ environment: 'staging',
710
+ config: {
711
+ replicas: 3,
712
+ resources: { cpu: '500m', memory: '256Mi' },
713
+ env_vars: [
714
+ { name: 'NODE_ENV', value: 'staging' },
715
+ { name: 'LOG_LEVEL', value: 'debug' },
716
+ ],
717
+ },
718
+ });
719
+ expect(result.isError).toBeUndefined();
720
+ expect(result.content[0].text).toContain('3 replicas');
721
+ });
722
+ it('should reject invalid nested resource format', async () => {
723
+ const builder = new GroupedToolBuilder('deploy_v2')
724
+ .action({
725
+ name: 'deploy',
726
+ schema: z.object({
727
+ config: z.object({
728
+ resources: z.object({
729
+ cpu: z.string().regex(/^\d+m$/),
730
+ }),
731
+ }),
732
+ }),
733
+ handler: async () => success('ok'),
734
+ });
735
+ builder.buildToolDefinition();
736
+ const result = await builder.execute(undefined, {
737
+ action: 'deploy',
738
+ config: {
739
+ resources: { cpu: '500cores' }, // wrong format
740
+ },
741
+ });
742
+ expect(result.isError).toBe(true);
743
+ });
744
+ it('should handle optional nested blocks as absent', async () => {
745
+ const builder = new GroupedToolBuilder('pipeline_config')
746
+ .action({
747
+ name: 'run',
748
+ schema: z.object({
749
+ pipeline: z.string(),
750
+ hooks: z.object({
751
+ before: z.string().optional(),
752
+ after: z.string().optional(),
753
+ }).optional(),
754
+ }),
755
+ handler: async (_ctx, args) => success(`running ${args.pipeline}`),
756
+ });
757
+ builder.buildToolDefinition();
758
+ // No hooks at all — should work
759
+ const result = await builder.execute(undefined, {
760
+ action: 'run',
761
+ pipeline: 'ci-main',
762
+ });
763
+ expect(result.isError).toBeUndefined();
764
+ });
765
+ });
766
+ // ── Chaos 4: Unicode & i18n ─────────────────────────────────────────────────
767
+ // Real scenario: international teams use unicode in names, descriptions,
768
+ // and field values. Emoji in identifiers. Multi-byte characters everywhere.
769
+ describe('Enterprise Chaos — Unicode & i18n', () => {
770
+ it('should handle unicode in tool descriptions and action values', async () => {
771
+ const builder = new GroupedToolBuilder('intl_service')
772
+ .description('Serviço de gerenciamento internacional 🌍')
773
+ .action({
774
+ name: 'create',
775
+ description: 'Criar novo registro — inclui suporte a múltiplos idiomas',
776
+ schema: z.object({
777
+ nome: z.string().describe('Nome do registro em qualquer idioma'),
778
+ descrição: z.string().optional().describe('Descrição detalhada'),
779
+ }),
780
+ handler: async (_ctx, args) => success(`criado: ${args.nome}`),
781
+ })
782
+ .action({
783
+ name: 'search',
784
+ description: '検索 — 日本語対応',
785
+ schema: z.object({
786
+ query: z.string(),
787
+ }),
788
+ handler: async (_ctx, args) => success(`results for: ${args.query}`),
789
+ });
790
+ builder.buildToolDefinition();
791
+ const result = await builder.execute(undefined, {
792
+ action: 'create',
793
+ nome: '项目管理工具 🚀',
794
+ descrição: 'Описание на русском языке с эмодзи 💼',
795
+ });
796
+ expect(result.isError).toBeUndefined();
797
+ expect(result.content[0].text).toContain('项目管理工具');
798
+ });
799
+ it('should handle emoji and special chars in search queries', async () => {
800
+ const builder = new GroupedToolBuilder('emoji_search')
801
+ .action({
802
+ name: 'find',
803
+ schema: z.object({
804
+ q: z.string(),
805
+ }),
806
+ handler: async (_ctx, args) => success(`found: ${args.q}`),
807
+ });
808
+ builder.buildToolDefinition();
809
+ const result = await builder.execute(undefined, {
810
+ action: 'find',
811
+ q: '🎯 café résumé naïve über straße 日本語テスト',
812
+ });
813
+ expect(result.isError).toBeUndefined();
814
+ });
815
+ });
816
+ // ── Chaos 5: Middleware Under Pressure ──────────────────────────────────────
817
+ // Real scenario: multiple middleware layers processing security, logging,
818
+ // rate limiting, audit trails. Middleware can short-circuit, modify context,
819
+ // or throw.
820
+ describe('Enterprise Chaos — Middleware Chains Under Pressure', () => {
821
+ it('should execute middleware chain in order with audit trail', async () => {
822
+ const auditLog = [];
823
+ const builder = new GroupedToolBuilder('audit_service')
824
+ .use(async (ctx, args, next) => {
825
+ auditLog.push(`auth:${ctx.userId}`);
826
+ return next();
827
+ })
828
+ .use(async (_ctx, args, next) => {
829
+ auditLog.push(`validate:${args.action}`);
830
+ return next();
831
+ })
832
+ .use(async (_ctx, _args, next) => {
833
+ auditLog.push('rate_limit:pass');
834
+ return next();
835
+ })
836
+ .action({
837
+ name: 'sensitive_operation',
838
+ handler: async (ctx) => {
839
+ auditLog.push(`execute:${ctx.userId}`);
840
+ return success('done');
841
+ },
842
+ });
843
+ builder.buildToolDefinition();
844
+ const result = await builder.execute({ userId: 'admin-42' }, { action: 'sensitive_operation' });
845
+ expect(result.isError).toBeUndefined();
846
+ expect(auditLog).toEqual([
847
+ 'auth:admin-42',
848
+ 'validate:sensitive_operation',
849
+ 'rate_limit:pass',
850
+ 'execute:admin-42',
851
+ ]);
852
+ });
853
+ it('should short-circuit middleware when unauthorized', async () => {
854
+ const builder = new GroupedToolBuilder('admin_only')
855
+ .use(async (ctx, _args, next) => {
856
+ if (ctx.role !== 'admin') {
857
+ return { isError: true, content: [{ type: 'text', text: 'FORBIDDEN: admin role required' }] };
858
+ }
859
+ return next();
860
+ })
861
+ .action({
862
+ name: 'nuke_database',
863
+ description: 'Delete everything',
864
+ handler: async () => success('kaboom 💥'),
865
+ });
866
+ builder.buildToolDefinition();
867
+ // Non-admin should be blocked
868
+ const blocked = await builder.execute({ role: 'viewer' }, { action: 'nuke_database' });
869
+ expect(blocked.isError).toBe(true);
870
+ expect(blocked.content[0].text).toContain('FORBIDDEN');
871
+ // Admin should pass
872
+ const allowed = await builder.execute({ role: 'admin' }, { action: 'nuke_database' });
873
+ expect(allowed.isError).toBeUndefined();
874
+ });
875
+ it('should handle middleware that throws', async () => {
876
+ const builder = new GroupedToolBuilder('mw_crash')
877
+ .use(async () => {
878
+ throw new Error('MIDDLEWARE_PANIC: certificate expired');
879
+ })
880
+ .action({
881
+ name: 'any',
882
+ handler: async () => success('unreachable'),
883
+ });
884
+ builder.buildToolDefinition();
885
+ const result = await builder.execute(undefined, { action: 'any' });
886
+ expect(result.isError).toBe(true);
887
+ expect(result.content[0].text).toContain('certificate expired');
888
+ });
889
+ });
890
+ // ── Chaos 6: Multi-Tenant Context Routing ───────────────────────────────────
891
+ // Real scenario: SaaS multi-tenant system where context carries tenant info
892
+ // and different tenants have different configurations.
893
+ describe('Enterprise Chaos — Multi-Tenant Context', () => {
894
+ it('should route based on tenant context with plan enforcement', async () => {
895
+ const builder = new GroupedToolBuilder('billing_api')
896
+ .use(async (ctx, _args, next) => {
897
+ // Free plan can only list, not create
898
+ if (ctx.plan === 'free' && _args.action !== 'list') {
899
+ return {
900
+ isError: true,
901
+ content: [{ type: 'text', text: `UPGRADE_REQUIRED: ${_args.action} is not available on free plan` }],
902
+ };
903
+ }
904
+ return next();
905
+ })
906
+ .commonSchema(z.object({
907
+ currency: z.enum(['USD', 'EUR', 'BRL']).default('USD'),
908
+ }))
909
+ .action({
910
+ name: 'list',
911
+ readOnly: true,
912
+ handler: async (ctx) => success(`${ctx.tenantId}@${ctx.region}: invoices listed`),
913
+ })
914
+ .action({
915
+ name: 'create',
916
+ schema: z.object({
917
+ amount: z.number().positive(),
918
+ customer_email: z.string().email(),
919
+ }),
920
+ handler: async (ctx, args) => success(`${ctx.tenantId}: invoice $${args.amount} for ${args.customer_email}`),
921
+ });
922
+ builder.buildToolDefinition();
923
+ // Free tenant can list
924
+ const freeList = await builder.execute({ tenantId: 'acme', plan: 'free', region: 'us-east-1' }, { action: 'list', currency: 'USD' });
925
+ expect(freeList.isError).toBeUndefined();
926
+ // Free tenant cannot create
927
+ const freeCreate = await builder.execute({ tenantId: 'acme', plan: 'free', region: 'us-east-1' }, { action: 'create', currency: 'BRL', amount: 100, customer_email: 'test@example.com' });
928
+ expect(freeCreate.isError).toBe(true);
929
+ expect(freeCreate.content[0].text).toContain('UPGRADE_REQUIRED');
930
+ // Enterprise tenant can create
931
+ const entCreate = await builder.execute({ tenantId: 'megacorp', plan: 'enterprise', region: 'eu-west-1' }, { action: 'create', currency: 'EUR', amount: 50000, customer_email: 'cfo@megacorp.com' });
932
+ expect(entCreate.isError).toBeUndefined();
933
+ expect(entCreate.content[0].text).toContain('megacorp');
934
+ });
935
+ });
936
+ // ── Chaos 7: Rapid Re-registration & Hot Reload ────────────────────────────
937
+ // Real scenario: microservice hot-reloads during deployment, tools
938
+ // get cleared and re-registered rapidly. Tests state integrity.
939
+ describe('Enterprise Chaos — Hot Reload & Re-registration', () => {
940
+ it('should survive 100 clear+re-register cycles', () => {
941
+ const registry = new ToolRegistry();
942
+ for (let cycle = 0; cycle < 100; cycle++) {
943
+ registry.clear();
944
+ expect(registry.size).toBe(0);
945
+ const builder = new GroupedToolBuilder(`service_v${cycle}`)
946
+ .tags('versioned')
947
+ .action({
948
+ name: 'health',
949
+ handler: async () => success(`v${cycle} healthy`),
950
+ });
951
+ registry.register(builder);
952
+ expect(registry.size).toBe(1);
953
+ expect(registry.has(`service_v${cycle}`)).toBe(true);
954
+ }
955
+ // Final state should have only the last version
956
+ expect(registry.size).toBe(1);
957
+ expect(registry.has('service_v99')).toBe(true);
958
+ });
959
+ it('should maintain tag filtering correctness across multiple registrations', () => {
960
+ const registry = new ToolRegistry();
961
+ // Simulate microservices registering one by one
962
+ const services = [
963
+ { name: 'auth_service', tags: ['auth', 'critical'] },
964
+ { name: 'user_service', tags: ['users', 'critical'] },
965
+ { name: 'payment_service', tags: ['payments', 'pci'] },
966
+ { name: 'notification_service', tags: ['notifications'] },
967
+ { name: 'analytics_service', tags: ['analytics'] },
968
+ { name: 'search_service', tags: ['search', 'critical'] },
969
+ ];
970
+ for (const svc of services) {
971
+ const builder = new GroupedToolBuilder(svc.name)
972
+ .tags(...svc.tags)
973
+ .action({ name: 'status', handler: async () => success(`${svc.name} ok`) });
974
+ registry.register(builder);
975
+ }
976
+ expect(registry.size).toBe(6);
977
+ // Critical services
978
+ const critical = registry.getTools({ tags: ['critical'] });
979
+ expect(critical).toHaveLength(3);
980
+ // PCI scope
981
+ const pci = registry.getTools({ tags: ['pci'] });
982
+ expect(pci).toHaveLength(1);
983
+ expect(pci[0].name).toBe('payment_service');
984
+ // Exclude analytics and search
985
+ const core = registry.getTools({ exclude: ['analytics', 'search'] });
986
+ expect(core).toHaveLength(4);
987
+ });
988
+ });
989
+ // ── Chaos 8: Schema Accumulation & Cross-Action Field Conflicts ─────────────
990
+ // Real scenario: different actions define the same field name with
991
+ // different types or constraints. The framework must handle this correctly.
992
+ describe('Enterprise Chaos — Schema Field Conflicts', () => {
993
+ it('should merge commonSchema + actionSchema correctly', async () => {
994
+ const builder = new GroupedToolBuilder('data_pipeline')
995
+ .commonSchema(z.object({
996
+ pipeline_id: z.string().uuid(),
997
+ dry_run: z.boolean().default(false),
998
+ }))
999
+ .action({
1000
+ name: 'trigger',
1001
+ schema: z.object({
1002
+ source: z.enum(['s3', 'gcs', 'azure_blob']),
1003
+ partition_key: z.string().optional(),
1004
+ }),
1005
+ handler: async (_ctx, args) => success(`triggered ${args.pipeline_id} from ${args.source}`),
1006
+ })
1007
+ .action({
1008
+ name: 'status',
1009
+ readOnly: true,
1010
+ handler: async (_ctx, args) => success(`pipeline ${args.pipeline_id} status: running`),
1011
+ });
1012
+ builder.buildToolDefinition();
1013
+ // Trigger with all fields
1014
+ const triggerResult = await builder.execute(undefined, {
1015
+ action: 'trigger',
1016
+ pipeline_id: '550e8400-e29b-41d4-a716-446655440000',
1017
+ source: 's3',
1018
+ partition_key: '2024-01-15',
1019
+ });
1020
+ expect(triggerResult.isError).toBeUndefined();
1021
+ // Status with only common fields
1022
+ const statusResult = await builder.execute(undefined, {
1023
+ action: 'status',
1024
+ pipeline_id: '550e8400-e29b-41d4-a716-446655440000',
1025
+ });
1026
+ expect(statusResult.isError).toBeUndefined();
1027
+ // Trigger with invalid UUID
1028
+ const badUuid = await builder.execute(undefined, {
1029
+ action: 'trigger',
1030
+ pipeline_id: 'not-a-uuid',
1031
+ source: 's3',
1032
+ });
1033
+ expect(badUuid.isError).toBe(true);
1034
+ // Trigger with invalid source enum
1035
+ const badSource = await builder.execute(undefined, {
1036
+ action: 'trigger',
1037
+ pipeline_id: '550e8400-e29b-41d4-a716-446655440000',
1038
+ source: 'local_disk',
1039
+ });
1040
+ expect(badSource.isError).toBe(true);
1041
+ });
1042
+ });
1043
+ //# sourceMappingURL=LargeScaleScenarios.test.js.map