@vinkius-core/mcp-fusion 1.11.0 → 2.3.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.
package/llms.txt DELETED
@@ -1,593 +0,0 @@
1
- # mcp-fusion
2
-
3
- > The MVA (Model-View-Agent) framework for building MCP servers where AI agents are first-class consumers.
4
-
5
- ## What is mcp-fusion?
6
-
7
- mcp-fusion is a TypeScript framework for the Model Context Protocol (MCP) that introduces the **MVA (Model-View-Agent)** architectural pattern — created by Renato Marinho at Vinkius Labs. Instead of dumping raw JSON and hoping the AI figures it out, MVA adds a **Presenter** (the View layer) that gives every response structure: validated data, domain rules, rendered charts, action affordances, and cognitive guardrails.
8
-
9
- ## Core Architecture: MVA (Model-View-Agent)
10
-
11
- ```
12
- Model (Zod Schema) → View (Presenter) → Agent (LLM)
13
- validates perceives acts
14
- ```
15
-
16
- The Presenter replaces the human-centric View with an agent-centric perception layer. Every tool response becomes a **structured perception package** — not raw JSON.
17
-
18
- ## Core Concepts
19
-
20
- - **MVA Pattern**: Model-View-Agent — the architectural foundation where Presenters replace Views
21
- - **Presenter**: Domain-level View layer — schema validation, system rules, UI blocks, affordances, guardrails
22
- - **GroupedToolBuilder**: Fluent builder that groups related actions into a single MCP tool
23
- - **createTool()**: Factory function to create a GroupedToolBuilder with full Zod power
24
- - **defineTool()**: JSON-first factory — define tools without Zod imports using plain strings/objects
25
- - **createPresenter()**: Fluent builder for domain-level Presenters
26
- - **ResponseBuilder**: Fine-grained manual response composition
27
- - **Action**: A single operation within a grouped tool (e.g., "list", "create", "delete")
28
- - **Group**: Hierarchical namespace for actions (e.g., "users.create", "billing.refund")
29
- - **Discriminator**: The field name the LLM uses to select the action (default: "action")
30
- - **CommonSchema**: Shared Zod schema injected into every action
31
- - **ToolRegistry**: Centralized registry for all tool builders
32
- - **Middleware**: Pre-compiled middleware chains following the next() pattern
33
- - **Tags**: Capability labels for selective tool exposure per session
34
- - **TOON**: Token-Oriented Object Notation for compact descriptions/responses
35
- - **State Sync**: RFC 7234-inspired cache-control signals to prevent temporal blindness
36
- - **Tool Exposition**: Compile-time topology compiler — choose flat (one tool per action) or grouped (one tool per builder with discriminator enum) at attach time. Same handlers, different wire format.
37
- - **Dynamic Manifest**: RBAC-filtered server capabilities exposed as a native MCP Resource
38
-
39
- ## Quick Start — MVA with Presenter
40
-
41
- ```typescript
42
- import { createPresenter, ui, defineTool, success } from '@vinkius-core/mcp-fusion';
43
- import { z } from 'zod';
44
-
45
- // 1. Define the Presenter — the MVA View Layer
46
- export const InvoicePresenter = createPresenter('Invoice')
47
- .schema(z.object({
48
- id: z.string(),
49
- amount_cents: z.number(),
50
- status: z.enum(['paid', 'pending', 'overdue']),
51
- }))
52
- .systemRules(['CRITICAL: amount_cents is in CENTS. Divide by 100.'])
53
- .uiBlocks((inv) => [
54
- ui.echarts({ series: [{ type: 'gauge', data: [{ value: inv.amount_cents / 100 }] }] }),
55
- ])
56
- .suggestActions((inv) =>
57
- inv.status === 'pending'
58
- ? [{ tool: 'billing.pay', reason: 'Process payment' }]
59
- : []
60
- );
61
-
62
- // 2. Attach to any tool — handler returns raw data, Presenter does the rest
63
- const billing = defineTool<AppContext>('billing', {
64
- actions: {
65
- get_invoice: {
66
- returns: InvoicePresenter,
67
- params: { id: 'string' },
68
- handler: async (ctx, args) => await ctx.db.invoices.findUnique(args.id),
69
- },
70
- },
71
- });
72
-
73
- const registry = new ToolRegistry<AppContext>();
74
- registry.register(billing);
75
- registry.attachToServer(server, {
76
- contextFactory: (extra) => createAppContext(extra),
77
- });
78
- ```
79
-
80
- ## Quick Start — defineTool() (No Zod Required)
81
-
82
- ```typescript
83
- import { defineTool, ToolRegistry, success, error } from '@vinkius-core/mcp-fusion';
84
-
85
- const projects = defineTool<AppContext>('projects', {
86
- description: 'Manage workspace projects',
87
- shared: { workspace_id: 'string' },
88
- actions: {
89
- list: {
90
- readOnly: true,
91
- params: { status: { enum: ['active', 'archived'] as const, optional: true } },
92
- handler: async (ctx, args) => success(await ctx.db.projects.findMany()),
93
- },
94
- create: {
95
- params: {
96
- name: { type: 'string', min: 1, max: 100 },
97
- email: { type: 'string', regex: '^[\\w-.]+@([\\w-]+\\.)+[\\w-]{2,4}$' },
98
- },
99
- handler: async (ctx, args) => success(await ctx.db.projects.create(args)),
100
- },
101
- delete: {
102
- destructive: true,
103
- params: { project_id: 'string' },
104
- handler: async (ctx, args) => {
105
- await ctx.db.projects.delete(args.project_id);
106
- return success('Project deleted');
107
- },
108
- },
109
- },
110
- });
111
- ```
112
-
113
- ## Quick Start — createTool() (Full Zod Power)
114
-
115
- ```typescript
116
- import { createTool, ToolRegistry, success, error } from '@vinkius-core/mcp-fusion';
117
- import { z } from 'zod';
118
-
119
- const projects = createTool<AppContext>('projects')
120
- .description('Manage workspace projects')
121
- .commonSchema(z.object({
122
- workspace_id: z.string().describe('Workspace identifier'),
123
- }))
124
- .action({
125
- name: 'list',
126
- readOnly: true,
127
- schema: z.object({ status: z.enum(['active', 'archived']).optional() }),
128
- handler: async (ctx, args) => success(await ctx.db.projects.findMany({ where: args })),
129
- })
130
- .action({
131
- name: 'create',
132
- schema: z.object({ name: z.string() }),
133
- handler: async (ctx, args) => success(await ctx.db.projects.create({ data: args })),
134
- })
135
- .action({
136
- name: 'delete',
137
- destructive: true,
138
- schema: z.object({ project_id: z.string() }),
139
- handler: async (ctx, args) => {
140
- await ctx.db.projects.delete({ where: { id: args.project_id } });
141
- return success('Deleted');
142
- },
143
- });
144
- ```
145
-
146
- ## Presenter API
147
-
148
- ```typescript
149
- import { createPresenter, ui } from '@vinkius-core/mcp-fusion';
150
-
151
- const UserPresenter = createPresenter('User')
152
- .schema(z.object({ id: z.string(), name: z.string(), role: z.string() }))
153
- .systemRules(['Display name in bold'])
154
- .systemRules((user, ctx) => ctx.isAdmin ? ['Show internal fields'] : ['Hide internal fields'])
155
- .uiBlocks((user) => [ui.summary({ total: 1, showing: 1 })])
156
- .agentLimit(50, { warningMessage: 'Showing {shown} of {total}. Use filters.' })
157
- .suggestActions((user) => [
158
- { tool: 'users.update', reason: 'Edit this user', args: { id: user.id } },
159
- ])
160
- .embed('team', TeamPresenter);
161
- ```
162
-
163
- ## Presenter Composition
164
-
165
- ```typescript
166
- const OrderPresenter = createPresenter('Order')
167
- .schema(OrderSchema)
168
- .embed('customer', CustomerPresenter)
169
- .embed('items', LineItemPresenter);
170
- // Child Presenters' rules, UI blocks, and suggestions are automatically merged.
171
- ```
172
-
173
- ## ResponseBuilder (Manual Composition)
174
-
175
- ```typescript
176
- import { ResponseBuilder } from '@vinkius-core/mcp-fusion';
177
-
178
- const response = ResponseBuilder.create(data)
179
- .systemRules(['Format currency in USD'])
180
- .uiBlock(ui.echarts({ /* chart config */ }))
181
- .llmHint('This invoice is overdue')
182
- .suggestActions([{ tool: 'billing.pay', reason: 'Pay now' }])
183
- .build();
184
- ```
185
-
186
- ## DX Shortcuts
187
-
188
- ```typescript
189
- import { response, ui } from '@vinkius-core/mcp-fusion';
190
-
191
- return response.ok(data); // Simple success
192
- return response.withRules(data, ['Rule 1']); // Data + rules
193
- ui.echarts({ series: [...] }); // ECharts chart
194
- ui.mermaid('graph TD; A-->B'); // Mermaid diagram
195
- ui.summary({ total: 100, showing: 10 }); // Collection summary
196
- ```
197
-
198
- ## Hierarchical Groups
199
-
200
- ```typescript
201
- const platform = createTool<AppContext>('platform')
202
- .tags('core')
203
- .group('users', 'User management', g => {
204
- g.use(requireAdmin)
205
- .action({ name: 'list', readOnly: true, handler: listUsers })
206
- .action({ name: 'ban', destructive: true, schema: banSchema, handler: banUser });
207
- })
208
- .group('billing', 'Billing operations', g => {
209
- g.action({ name: 'refund', destructive: true, schema: refundSchema, handler: issueRefund });
210
- });
211
- // Discriminator values: "users.list" | "users.ban" | "billing.refund"
212
- ```
213
-
214
- ## Middleware
215
-
216
- ```typescript
217
- const requireAuth: MiddlewareFn<AppContext> = async (ctx, args, next) => {
218
- if (!ctx.user) return error('Unauthorized');
219
- return next();
220
- };
221
-
222
- // tRPC-style context derivation
223
- const withDb = defineMiddleware(async (ctx) => ({
224
- ...ctx,
225
- db: await createDbConnection(ctx.tenantId),
226
- }));
227
-
228
- createTool<AppContext>('projects')
229
- .use(requireAuth)
230
- .use(withDb)
231
- .action({ name: 'list', handler: async (ctx, args) => success(await ctx.db.query()) });
232
- ```
233
-
234
- ## Self-Healing Errors
235
-
236
- ```typescript
237
- import { toolError } from '@vinkius-core/mcp-fusion';
238
-
239
- return toolError('INVALID_DATE_RANGE', {
240
- message: 'Start date must be before end date',
241
- recovery: { action: 'retry', suggestion: 'Swap the date values' },
242
- suggestedArgs: { start: args.end, end: args.start },
243
- });
244
- ```
245
-
246
- All error XML output is automatically escaped to prevent injection. Pipeline errors use structured codes: `MISSING_DISCRIMINATOR`, `UNKNOWN_ACTION`, `UNKNOWN_TOOL`, `MISSING_REQUIRED_FIELD`.
247
-
248
- ## State Sync
249
-
250
- ```typescript
251
- registry.attachToServer(server, {
252
- contextFactory: (extra) => createAppContext(extra),
253
- stateSync: {
254
- defaults: { cacheControl: 'no-store' },
255
- policies: [
256
- { match: 'sprints.update', invalidates: ['sprints.*'] },
257
- { match: 'tasks.update', invalidates: ['tasks.*', 'sprints.*'] },
258
- { match: 'countries.*', cacheControl: 'immutable' },
259
- ],
260
- },
261
- });
262
- ```
263
-
264
- ## Runtime Guards
265
-
266
- Built-in concurrency control and payload size limiting. Fulfills the MCP spec requirement: "Servers MUST rate limit tool invocations."
267
-
268
- ```typescript
269
- // Concurrency Bulkhead — limit simultaneous executions per tool
270
- const billing = createTool<AppContext>('billing')
271
- .concurrency({ maxActive: 5, maxQueue: 20 })
272
- .action({
273
- name: 'process_invoice',
274
- handler: async (ctx, args) => success(await ctx.stripe.charges.create(args)),
275
- });
276
- // 5 concurrent, 20 queued, rest rejected with SERVER_BUSY
277
-
278
- // Egress Guard — truncate oversized responses
279
- const logs = createTool<AppContext>('logs')
280
- .maxPayloadBytes(2 * 1024 * 1024) // 2MB
281
- .action({
282
- name: 'search',
283
- handler: async (ctx, args) => success(await ctx.db.logs.findMany(args)),
284
- });
285
- // Responses > 2MB are truncated + system intervention injected
286
-
287
- // Both combined
288
- const analytics = createTool<AppContext>('analytics')
289
- .concurrency({ maxActive: 3, maxQueue: 10 })
290
- .maxPayloadBytes(2 * 1024 * 1024)
291
- .action({
292
- name: 'query',
293
- handler: async (ctx, args) => success(await ctx.db.$queryRaw(args.sql)),
294
- });
295
-
296
- // Intent Mutex — anti-race condition guard
297
- const users = createTool<AppContext>('users')
298
- .action({
299
- name: 'delete',
300
- destructive: true, // Automatically serializes concurrent calls to this action key
301
- handler: async (ctx, args) => success(await ctx.db.users.delete(args.id)),
302
- });
303
- ```
304
-
305
- Load shedding returns `toolError('SERVER_BUSY')` — a self-healing error that causes the LLM to reduce its cadence. Egress truncation injects `[SYSTEM INTERVENTION: Payload truncated. You MUST use pagination.]`. Intent Mutex prevents LLM double-firing hallucinations. Zero overhead when not configured.
306
-
307
-
308
- ## Dynamic Manifest
309
-
310
- ```typescript
311
- registry.attachToServer(server, {
312
- contextFactory: (extra) => createAppContext(extra),
313
- serverName: 'my-platform',
314
- introspection: {
315
- enabled: process.env.NODE_ENV !== 'production',
316
- filter: (manifest, ctx) => {
317
- if (ctx.user.role !== 'admin') {
318
- delete manifest.capabilities.tools['admin'];
319
- }
320
- return manifest;
321
- },
322
- },
323
- });
324
- // Exposes fusion://manifest.json via MCP resources/list + resources/read
325
- // Manifest includes: all tools, actions (destructive/readOnly flags, required_fields),
326
- // input schemas, presenter references — RBAC-filtered per session
327
- ```
328
-
329
- ## Tag Filtering
330
-
331
- ```typescript
332
- registry.attachToServer(server, { filter: { tags: ['core'] } }); // Only core tools
333
- registry.attachToServer(server, { filter: { exclude: ['internal'] } }); // No internal tools
334
- ```
335
-
336
- ## Tool Exposition
337
-
338
- Two strategies for the same codebase — choose at attach time:
339
-
340
- ```typescript
341
- // Flat (default) — each action becomes an independent MCP tool
342
- // projects → projects_list, projects_create, projects_delete
343
- registry.attachToServer(server, {
344
- toolExposition: 'flat',
345
- actionSeparator: '_',
346
- });
347
-
348
- // Grouped — one MCP tool per builder with discriminator enum
349
- // projects → { action: 'list' | 'create' | 'delete' }
350
- registry.attachToServer(server, {
351
- toolExposition: 'grouped',
352
- });
353
- ```
354
-
355
- **Flat** — precision per action: isolated schemas, per-action MCP annotations, per-tool toggles in clients. Best for small-to-medium APIs and weaker LLMs.
356
-
357
- **Grouped** — density at scale: one schema, one description, shared params appear once. Best for large domain APIs (50+ actions) and token-constrained contexts.
358
-
359
- MCP annotation semantics: Fusion emits `destructiveHint: false` on non-destructive actions because the MCP spec defaults `destructiveHint` to `true` (assume dangerous). Read-only actions get both `readOnlyHint: true` and `destructiveHint: false`. Destructive actions get `destructiveHint: true`. `readOnlyHint: false` is never emitted (matches spec default).
360
-
361
- ## Response Helpers
362
-
363
- ```typescript
364
- import { success, error, required, toonSuccess, toolError } from '@vinkius-core/mcp-fusion';
365
-
366
- return success('Task completed');
367
- return success({ id: '123', name: 'Acme' });
368
- return error('Project not found');
369
- return required('workspace_id');
370
- return toonSuccess(users); // ~40% fewer tokens
371
- return toolError('NOT_FOUND', { message: 'Invoice not found', recovery: { action: 'list' } });
372
- ```
373
-
374
- ## Streaming Progress
375
-
376
- Generator handlers yield `progress()` events that are **automatically** forwarded to the MCP client as `notifications/progress` when the client includes a `progressToken` in its request `_meta`. Zero configuration, zero overhead when not used.
377
-
378
- ```typescript
379
- handler: async function* (ctx, args) {
380
- yield progress(25, 'Loading data...');
381
- const data = await ctx.db.query();
382
- yield progress(75, 'Processing...');
383
- return success(data);
384
- }
385
- // Wire format: { method: 'notifications/progress', params: { progressToken, progress: 25, total: 100, message: 'Loading data...' } }
386
- ```
387
-
388
- ## Prompt Engine
389
-
390
- Server-side hydrated prompt templates with schema-informed coercion and lifecycle sync. The `definePrompt()` factory enforces a **flat schema constraint** — prompt arguments must be primitives (string, number, boolean, enum) because MCP clients render them as forms.
391
-
392
- ```typescript
393
- import { definePrompt, PromptMessage } from '@vinkius-core/mcp-fusion';
394
-
395
- const SummarizePrompt = definePrompt<AppContext>('summarize', {
396
- title: 'Summarize Document',
397
- description: 'Generate a summary of a document.',
398
- args: {
399
- docId: 'string',
400
- length: { enum: ['short', 'medium', 'long'] as const },
401
- } as const,
402
- handler: async (ctx, { docId, length }) => {
403
- const doc = await ctx.db.documents.get(docId);
404
- return {
405
- messages: [
406
- PromptMessage.system('You are a Senior Technical Writer.'),
407
- PromptMessage.user(`Summarize this document (${length}):\n\n${doc.content}`),
408
- ],
409
- };
410
- },
411
- });
412
- ```
413
-
414
- ## Hydration Timeout Sandbox
415
-
416
- Prompt handlers fetch data from external sources (APIs, databases). If any source hangs, the UI freezes. The Hydration Timeout Sandbox wraps the handler in a strict `Promise.race` deadline. Three guarantees: (1) handler completes → normal result, (2) handler exceeds deadline → `<hydration_alert><status>TIMEOUT</status>` alert, (3) handler throws → `<hydration_alert><status>ERROR</status>` alert. The UI ALWAYS unblocks.
417
-
418
- ```typescript
419
- // Per-prompt deadline
420
- const Briefing = definePrompt<AppContext>('morning_briefing', {
421
- hydrationTimeout: 3000, // 3 seconds strict
422
- handler: async (ctx, args) => {
423
- // If Jira takes 15s, framework cuts at 3s → returns SYSTEM ALERT
424
- const tickets = await ctx.invokeTool('jira.get_assigned');
425
- return { messages: [PromptMessage.user(tickets.text)] };
426
- },
427
- });
428
-
429
- // Registry-level default (global safety net)
430
- const prompts = new PromptRegistry<AppContext>();
431
- prompts.setDefaultHydrationTimeout(5000); // 5s for all prompts
432
- prompts.register(Briefing); // overrides with 3s
433
- ```
434
-
435
- Zero overhead when no timeout configured. Timer cleanup via `finally` — no resource leaks. Interceptors still run after timeout.
436
-
437
- ## MVA-Driven Prompts — fromView()
438
-
439
- Bridge your Presenter layer into Prompts with zero duplication. `PromptMessage.fromView()` decomposes a `ResponseBuilder` into XML-tagged prompt messages (`<domain_rules>`, `<dataset>`, `<visual_context>`, `<system_guidance>`) optimized for frontier LLMs. Domain rules, UI blocks, and action suggestions from the Presenter are automatically extracted — single source of truth.
440
-
441
- ```typescript
442
- const AuditPrompt = definePrompt<AppContext>('audit', {
443
- args: { invoiceId: 'string' } as const,
444
- handler: async (ctx, { invoiceId }) => {
445
- const invoice = await ctx.db.getInvoice(invoiceId);
446
- return {
447
- messages: [
448
- PromptMessage.system('You are a Senior Financial Auditor.'),
449
- ...PromptMessage.fromView(InvoicePresenter.make(invoice, ctx)),
450
- PromptMessage.user('Begin the audit.'),
451
- ],
452
- };
453
- },
454
- });
455
- ```
456
-
457
- ## Stateless Cursor Pagination
458
-
459
- Prompt Registries support cryptographic, stateless cursor-based pagination out of the box. Cursors encode the current position securely (`globalThis.crypto.subtle`) ensuring O(1) memory overhead.
460
-
461
- ```typescript
462
- const registry = new PromptRegistry<AppContext>();
463
- registry.configurePagination({ pageSize: 50 }); // enable pagination
464
- ```
465
-
466
- ## Important Rules
467
-
468
- 1. `.action()` and `.group()` are MUTUALLY EXCLUSIVE on the same builder
469
- 2. Action names must NOT contain dots (dots are reserved for group.action keys)
470
- 3. After `buildToolDefinition()`, the builder is FROZEN — no more modifications
471
- 4. `execute()` auto-calls `buildToolDefinition()` if not already called
472
- 5. `commonSchema` fields are marked `(always required)` in auto-generated descriptions
473
- 6. Zod `.merge().strip()` is used at runtime — unknown fields are silently stripped
474
- 7. Middleware chains are pre-compiled at build time — zero runtime allocation
475
- 8. Tool names must be unique across a ToolRegistry
476
- 9. Presenters are immutable after creation — define once, reuse across tools
477
- 10. `.agentLimit()` automatically truncates and injects guidance blocks
478
-
479
- ## Public API (all from '@vinkius-core/mcp-fusion')
480
-
481
- ### Builder
482
- - `createTool<TContext>(name)` → GroupedToolBuilder (fluent builder, requires Zod)
483
- - `defineTool<TContext>(name, config)` → GroupedToolBuilder (JSON-first, no Zod required)
484
- - `GroupedToolBuilder<TContext>` — .description(), .commonSchema(), .discriminator(), .tags(), .annotations(), .toonDescription(), .concurrency(), .maxPayloadBytes(), .use(), .action(), .group(), .buildToolDefinition(), .execute(), .previewPrompt(), .getName(), .getTags(), .getActionNames(), .getActionMetadata()
485
- - `ActionGroupBuilder<TContext>` — .use(), .action()
486
-
487
- ### Presenter (MVA View Layer)
488
- - `createPresenter(name)` → PresenterBuilder — .schema(), .systemRules(), .uiBlocks(), .agentLimit(), .suggestActions(), .embed()
489
- - `ResponseBuilder.create(data)` → ResponseBuilder — .systemRules(), .uiBlock(), .llmHint(), .suggestActions(), .build()
490
- - `ui.echarts(config)` → UIBlock (ECharts chart)
491
- - `ui.mermaid(code)` → UIBlock (Mermaid diagram)
492
- - `ui.summary(stats)` → UIBlock (Collection summary)
493
- - `response.ok(data)` → ToolResponse (success shortcut)
494
- - `response.withRules(data, rules)` → ToolResponse (data + system rules)
495
-
496
- ### Registry
497
- - `ToolRegistry<TContext>` — .register(), .registerAll(), .getAllTools(), .getTools(filter), .routeCall(), .attachToServer(), .has(), .clear(), .size
498
-
499
- ### FusionClient (tRPC-style)
500
- - `createFusionClient<TRouter>(transport)` → FusionClient — type-safe client with autocomplete
501
- - `FusionClient<TRouter>` — .execute(action, args) with full type inference
502
-
503
- ### Response Helpers
504
- - `success(data)` → ToolResponse
505
- - `error(message)` → ToolResponse (isError: true)
506
- - `required(field)` → ToolResponse (isError: true)
507
- - `toolError(code, options)` → ToolResponse (structured recovery hints for LLM agents)
508
- - `toonSuccess(data, options?)` → ToolResponse (TOON-encoded)
509
-
510
- ### State Sync
511
- - `cacheSignal(data, options)` → ToolResponse with cache-control metadata
512
- - `invalidates(data, scopes)` → ToolResponse with invalidation signals
513
-
514
- ### Dynamic Manifest
515
- - `registerIntrospectionResource(server, config, serverName, builders, contextFactory?)` — registers MCP resources/list + resources/read handlers
516
- - `compileManifest(serverName, builders)` → ManifestPayload (structured server capabilities)
517
- - `cloneManifest(manifest)` → deep clone for RBAC isolation
518
- - `Presenter.getSchemaKeys()` → string[] (read-only accessor)
519
- - `Presenter.getUiBlockTypes()` → string[] (read-only accessor)
520
- - `Presenter.hasContextualRules()` → boolean (read-only accessor)
521
- - `ToolRegistry.getBuilders()` → Iterable<ToolBuilder> (for introspection)
522
-
523
- ### Streaming Progress
524
- - `progress(percent, message)` → ProgressEvent (yield from generator handlers)
525
- - `isProgressEvent(value)` → type guard
526
- - `ProgressSink` — callback type `(event: ProgressEvent) => void` — auto-wired by `attachToServer()`, or pass manually to `execute()` / `routeCall()` for testing
527
-
528
- ### Prompt Engine
529
- - `definePrompt<TContext>(name, config)` → PromptBuilder — JSON-first or Zod schema for args, flat primitives only
530
- - `PromptMessage.system(text)` → PromptMessagePayload (system instruction as MCP user role)
531
- - `PromptMessage.user(text)` → PromptMessagePayload
532
- - `PromptMessage.assistant(text)` → PromptMessagePayload (multi-turn seeding)
533
- - `PromptMessage.image(role, data, mimeType)` → PromptMessagePayload
534
- - `PromptMessage.audio(role, data, mimeType)` → PromptMessagePayload
535
- - `PromptMessage.resource(role, uri, options?)` → PromptMessagePayload
536
- - `PromptMessage.fromView(builder)` → PromptMessagePayload[] — decomposes ResponseBuilder into XML-tagged messages (<domain_rules>, <dataset>, <visual_context>, <system_guidance>)
537
- - `PromptRegistry<TContext>` — .register(), .registerAll(), .getAllPrompts(), .getPrompts(filter), .routeGet(), .setDefaultHydrationTimeout(), .notifyChanged(), .has(), .clear(), .size
538
-
539
- ### Result Monad
540
- - `succeed<T>(value)` → Success<T>
541
- - `fail(response)` → Failure
542
- - `Result<T>` = Success<T> | Failure
543
-
544
- ### Middleware
545
- - `defineMiddleware(deriveFn)` → MiddlewareDefinition (tRPC-style context derivation)
546
- - `resolveMiddleware(input)` → MiddlewareFn
547
-
548
- ### Observability
549
- - `createDebugObserver(options?)` → Observer for runtime debugging
550
- - Event types: tool:start, tool:end, tool:error, middleware:start, middleware:end
551
-
552
- ### Tracing (OpenTelemetry-Compatible)
553
- - `FusionTracer` / `FusionSpan` — structural subtyping interfaces matching OpenTelemetry (no `@opentelemetry/api` dependency)
554
- - `SpanStatusCode` — exported constants: `UNSET` (0), `OK` (1), `ERROR` (2)
555
- - `.tracing(tracer)` on builders — per-tool tracing
556
- - `ToolRegistry.enableTracing(tracer)` — propagate to all builders
557
- - `AttachOptions.tracing` — pass tracer to `attachToServer()` for full server tracing
558
- - **Error classification**: AI errors (`validation_failed`, `missing_discriminator`, `unknown_action`, `unknown_tool`, `handler_returned_error`) → `SpanStatusCode.UNSET` (no PagerDuty). System errors (handler `throw`) → `SpanStatusCode.ERROR` (triggers alerts).
559
- - **Enterprise attributes**: `mcp.system`, `mcp.tool`, `mcp.action`, `mcp.durationMs`, `mcp.isError`, `mcp.error_type`, `mcp.tags`, `mcp.description`, `mcp.response_size`
560
- - **Span events**: `mcp.route`, `mcp.validate` (with `mcp.valid`, `mcp.durationMs`), `mcp.middleware` (with `mcp.chainLength`)
561
- - Zero overhead when no tracer is set — separate fast path
562
-
563
- ### Types
564
- - `ToolResponse` — { content: [{ type: "text", text: string }], isError?: boolean }
565
- - `MiddlewareFn<TContext>` — (ctx, args, next) => Promise<ToolResponse>
566
- - `ActionConfig<TContext>` — { name, description?, schema?, destructive?, readOnly?, handler, returns? }
567
- - `PresenterConfig` — { schema, systemRules?, uiBlocks?, agentLimit?, suggestActions?, embeds? }
568
- - `ToolBuilder<TContext>` — Interface for custom builders
569
- - `ActionMetadata` — { key, actionName, groupName, description, destructive, idempotent, readOnly, requiredFields, hasMiddleware, presenterName?, presenterSchemaKeys?, presenterUiBlockTypes?, presenterHasContextualRules? }
570
- - `ToolFilter` — { tags?: string[], exclude?: string[] }
571
- - `AttachOptions<TContext>` — { contextFactory?, filter?, debug?, stateSync?, introspection?, serverName?, toolExposition?, actionSeparator?, tracing? }
572
- - `ConcurrencyConfig` — { maxActive: number, maxQueue?: number }
573
- - `EgressConfig` — { maxPayloadBytes: number }
574
- - `ToolExposition` — 'flat' | 'grouped'
575
- - `IntrospectionConfig<TContext>` — { enabled, uri?, filter? }
576
- - `ManifestPayload` — { server, mcp_fusion_version, architecture, capabilities: { tools, presenters } }
577
- - `ManifestTool` — { description, tags, actions, input_schema }
578
- - `ManifestAction` — { description, destructive, idempotent, readOnly, required_fields, returns_presenter }
579
- - `ManifestPresenter` — { schema_keys, ui_blocks_supported, has_contextual_rules }
580
- - `PromptResult` — { description?: string, messages: PromptMessagePayload[] }
581
- - `PromptMessagePayload` — { role: 'user' | 'assistant', content: PromptContentBlock }
582
- - `PromptContentBlock` — PromptTextContent | PromptImageContent | PromptAudioContent | PromptResourceContent
583
- - `PromptBuilder<TContext>` — DIP interface: .name, .getDefinition(), .handleGet(), .tags
584
- - `PromptConfig<TContext>` — { title?, description?, args?, tags?, middleware?, hydrationTimeout?, handler }
585
-
586
- ### Domain Models
587
- - `BaseModel` — abstract base (name, title, description, meta, icons)
588
- - `GroupItem` — leaf with multi-parent groups
589
- - `Group` — tree node (addChildGroup, addChildTool, addChildPrompt, addChildResource)
590
- - `Tool` — inputSchema, outputSchema, toolAnnotations
591
- - `Resource` — uri, size, mimeType, annotations
592
- - `Prompt` — promptArguments
593
- - `PromptArgument` — required: boolean