@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/dist/core/builder/GroupedToolBuilder.d.ts +1 -1
- package/dist/core/builder/GroupedToolBuilder.js +2 -2
- package/dist/core/builder/GroupedToolBuilder.js.map +1 -1
- package/dist/core/execution/ConcurrencyGuard.d.ts +1 -1
- package/dist/core/execution/ConcurrencyGuard.js +1 -1
- package/dist/prompt/CursorCodec.d.ts +4 -2
- package/dist/prompt/CursorCodec.d.ts.map +1 -1
- package/dist/prompt/CursorCodec.js +55 -11
- package/dist/prompt/CursorCodec.js.map +1 -1
- package/package.json +5 -18
- package/CHANGELOG.md +0 -616
- package/LICENSE +0 -190
- package/README.md +0 -341
- package/llms.txt +0 -593
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
|