@objectstack/plugin-mcp-server 4.0.3 → 4.0.5
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/README.md +528 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +33 -7
- package/.turbo/turbo-build.log +0 -22
- package/CHANGELOG.md +0 -8
- package/src/__tests__/mcp-server-runtime.test.ts +0 -279
- package/src/__tests__/plugin.test.ts +0 -239
- package/src/index.ts +0 -15
- package/src/mcp-server-runtime.ts +0 -495
- package/src/plugin.ts +0 -139
- package/src/types.ts +0 -24
- package/tsconfig.json +0 -18
package/README.md
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# @objectstack/plugin-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP Runtime Server Plugin for ObjectStack — exposes AI tools, data resources, and agent prompts via the Model Context Protocol.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Model Context Protocol (MCP)**: Expose ObjectStack resources to AI models via MCP
|
|
8
|
+
- **AI Tools**: Auto-generate MCP tools from ObjectStack actions and flows
|
|
9
|
+
- **Data Resources**: Expose objects, records, and metadata as MCP resources
|
|
10
|
+
- **Agent Prompts**: Register prompt templates for AI agents
|
|
11
|
+
- **Type-Safe**: Full Zod schema validation for tool inputs/outputs
|
|
12
|
+
- **Auto-Discovery**: MCP clients automatically discover available tools and resources
|
|
13
|
+
- **Streaming Support**: Stream large datasets and real-time updates
|
|
14
|
+
- **Security**: Built-in permission checks for tool execution
|
|
15
|
+
|
|
16
|
+
## What is MCP?
|
|
17
|
+
|
|
18
|
+
Model Context Protocol (MCP) is an open protocol that standardizes how AI applications provide context to Large Language Models (LLMs). It allows AI models to:
|
|
19
|
+
|
|
20
|
+
- **Access Tools**: Execute functions and operations
|
|
21
|
+
- **Read Resources**: Access data and content
|
|
22
|
+
- **Use Prompts**: Leverage pre-defined prompt templates
|
|
23
|
+
|
|
24
|
+
Read more: [MCP Specification](https://modelcontextprotocol.io/)
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm add @objectstack/plugin-mcp-server
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Basic Usage
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { defineStack } from '@objectstack/spec';
|
|
36
|
+
import { PluginMCPServer } from '@objectstack/plugin-mcp-server';
|
|
37
|
+
|
|
38
|
+
const stack = defineStack({
|
|
39
|
+
plugins: [
|
|
40
|
+
PluginMCPServer.configure({
|
|
41
|
+
serverName: 'objectstack-server',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
autoRegisterTools: true,
|
|
44
|
+
}),
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface MCPServerConfig {
|
|
53
|
+
/** Server name (shown to AI clients) */
|
|
54
|
+
serverName?: string;
|
|
55
|
+
|
|
56
|
+
/** Server version */
|
|
57
|
+
version?: string;
|
|
58
|
+
|
|
59
|
+
/** Auto-register tools from actions and flows */
|
|
60
|
+
autoRegisterTools?: boolean;
|
|
61
|
+
|
|
62
|
+
/** Auto-expose objects as resources */
|
|
63
|
+
autoExposeObjects?: boolean;
|
|
64
|
+
|
|
65
|
+
/** Enable streaming for large responses */
|
|
66
|
+
enableStreaming?: boolean;
|
|
67
|
+
|
|
68
|
+
/** Transport mechanism ('stdio' | 'http') */
|
|
69
|
+
transport?: 'stdio' | 'http';
|
|
70
|
+
|
|
71
|
+
/** HTTP port (if transport is 'http') */
|
|
72
|
+
port?: number;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## MCP Tools
|
|
77
|
+
|
|
78
|
+
### Auto-Generated Tools
|
|
79
|
+
|
|
80
|
+
ObjectStack automatically exposes these operations as MCP tools:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// CRUD operations (auto-registered)
|
|
84
|
+
'objectstack_find' // Query records
|
|
85
|
+
'objectstack_findOne' // Get single record
|
|
86
|
+
'objectstack_create' // Create record
|
|
87
|
+
'objectstack_update' // Update record
|
|
88
|
+
'objectstack_delete' // Delete record
|
|
89
|
+
|
|
90
|
+
// Metadata operations
|
|
91
|
+
'objectstack_describeObject' // Get object schema
|
|
92
|
+
'objectstack_listObjects' // List all objects
|
|
93
|
+
'objectstack_listFields' // List object fields
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Custom Tools
|
|
97
|
+
|
|
98
|
+
Register custom tools that AI models can call:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { defineTool } from '@objectstack/spec';
|
|
102
|
+
|
|
103
|
+
const calculateRevenueTool = defineTool({
|
|
104
|
+
name: 'calculate_revenue',
|
|
105
|
+
description: 'Calculate total revenue for an account',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
accountId: { type: 'string', description: 'Account ID' },
|
|
110
|
+
startDate: { type: 'string', description: 'Start date (ISO 8601)' },
|
|
111
|
+
endDate: { type: 'string', description: 'End date (ISO 8601)' },
|
|
112
|
+
},
|
|
113
|
+
required: ['accountId'],
|
|
114
|
+
},
|
|
115
|
+
async execute({ accountId, startDate, endDate }) {
|
|
116
|
+
const opportunities = await kernel.getDriver().find({
|
|
117
|
+
object: 'opportunity',
|
|
118
|
+
filters: [
|
|
119
|
+
{ field: 'account_id', operator: 'eq', value: accountId },
|
|
120
|
+
{ field: 'stage', operator: 'eq', value: 'closed_won' },
|
|
121
|
+
{ field: 'close_date', operator: 'gte', value: startDate },
|
|
122
|
+
{ field: 'close_date', operator: 'lte', value: endDate },
|
|
123
|
+
],
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const total = opportunities.reduce((sum, opp) => sum + opp.amount, 0);
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
accountId,
|
|
130
|
+
totalRevenue: total,
|
|
131
|
+
opportunityCount: opportunities.length,
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Register with MCP server
|
|
137
|
+
kernel.getService('mcp').registerTool(calculateRevenueTool);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## MCP Resources
|
|
141
|
+
|
|
142
|
+
### Auto-Exposed Objects
|
|
143
|
+
|
|
144
|
+
All ObjectStack objects are automatically exposed as MCP resources:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
objectstack://objects/opportunity # Opportunity object schema
|
|
148
|
+
objectstack://objects/opportunity/records # All opportunity records
|
|
149
|
+
objectstack://objects/opportunity/123 # Specific opportunity record
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Custom Resources
|
|
153
|
+
|
|
154
|
+
Expose custom resources to AI models:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
kernel.getService('mcp').registerResource({
|
|
158
|
+
uri: 'objectstack://reports/sales-pipeline',
|
|
159
|
+
name: 'Sales Pipeline Report',
|
|
160
|
+
description: 'Current sales pipeline with stages and amounts',
|
|
161
|
+
mimeType: 'application/json',
|
|
162
|
+
async read() {
|
|
163
|
+
const opportunities = await kernel.getDriver().find({
|
|
164
|
+
object: 'opportunity',
|
|
165
|
+
filters: [
|
|
166
|
+
{ field: 'stage', operator: 'neq', value: 'closed_won' },
|
|
167
|
+
{ field: 'stage', operator: 'neq', value: 'closed_lost' },
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const pipeline = opportunities.reduce((acc, opp) => {
|
|
172
|
+
acc[opp.stage] = (acc[opp.stage] || 0) + opp.amount;
|
|
173
|
+
return acc;
|
|
174
|
+
}, {});
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: JSON.stringify(pipeline, null, 2),
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## MCP Prompts
|
|
189
|
+
|
|
190
|
+
Register prompt templates that AI models can use:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
kernel.getService('mcp').registerPrompt({
|
|
194
|
+
name: 'analyze_account',
|
|
195
|
+
description: 'Analyze an account and its opportunities',
|
|
196
|
+
arguments: [
|
|
197
|
+
{
|
|
198
|
+
name: 'accountId',
|
|
199
|
+
description: 'Account ID to analyze',
|
|
200
|
+
required: true,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
async render({ accountId }) {
|
|
204
|
+
const account = await kernel.getDriver().findOne({
|
|
205
|
+
object: 'account',
|
|
206
|
+
filters: [{ field: 'id', operator: 'eq', value: accountId }],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const opportunities = await kernel.getDriver().find({
|
|
210
|
+
object: 'opportunity',
|
|
211
|
+
filters: [{ field: 'account_id', operator: 'eq', value: accountId }],
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
messages: [
|
|
216
|
+
{
|
|
217
|
+
role: 'user',
|
|
218
|
+
content: {
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: `Analyze this account and provide insights:
|
|
221
|
+
|
|
222
|
+
Account: ${account.name}
|
|
223
|
+
Industry: ${account.industry}
|
|
224
|
+
Total Opportunities: ${opportunities.length}
|
|
225
|
+
Total Value: $${opportunities.reduce((sum, o) => sum + o.amount, 0)}
|
|
226
|
+
|
|
227
|
+
Opportunities:
|
|
228
|
+
${opportunities.map(o => `- ${o.name} (${o.stage}): $${o.amount}`).join('\n')}
|
|
229
|
+
|
|
230
|
+
Please provide:
|
|
231
|
+
1. Key insights about this account
|
|
232
|
+
2. Risk assessment
|
|
233
|
+
3. Recommendations for next steps`,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Using with AI Clients
|
|
243
|
+
|
|
244
|
+
### Claude Desktop
|
|
245
|
+
|
|
246
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"mcpServers": {
|
|
251
|
+
"objectstack": {
|
|
252
|
+
"command": "node",
|
|
253
|
+
"args": ["/path/to/your/objectstack/server.js"],
|
|
254
|
+
"env": {
|
|
255
|
+
"DATABASE_URL": "your-database-url"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Cursor IDE
|
|
263
|
+
|
|
264
|
+
Add to `.cursor/mcp.json`:
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"mcpServers": {
|
|
269
|
+
"objectstack": {
|
|
270
|
+
"command": "node",
|
|
271
|
+
"args": ["./server.js"]
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Cline VS Code Extension
|
|
278
|
+
|
|
279
|
+
Configure in Cline settings:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"cline.mcpServers": {
|
|
284
|
+
"objectstack": {
|
|
285
|
+
"command": "node",
|
|
286
|
+
"args": ["./server.js"]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Server Implementation
|
|
293
|
+
|
|
294
|
+
### Stdio Transport (Default)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// server.ts
|
|
298
|
+
import { defineStack } from '@objectstack/spec';
|
|
299
|
+
import { PluginMCPServer } from '@objectstack/plugin-mcp-server';
|
|
300
|
+
import { DriverTurso } from '@objectstack/driver-turso';
|
|
301
|
+
|
|
302
|
+
const stack = defineStack({
|
|
303
|
+
driver: DriverTurso.configure({
|
|
304
|
+
url: process.env.DATABASE_URL!,
|
|
305
|
+
authToken: process.env.TURSO_AUTH_TOKEN!,
|
|
306
|
+
}),
|
|
307
|
+
plugins: [
|
|
308
|
+
PluginMCPServer.configure({
|
|
309
|
+
serverName: 'my-crm',
|
|
310
|
+
transport: 'stdio', // Claude Desktop, Cursor, Cline
|
|
311
|
+
}),
|
|
312
|
+
],
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await stack.boot();
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### HTTP Transport
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
const stack = defineStack({
|
|
322
|
+
driver: DriverTurso.configure({ /* ... */ }),
|
|
323
|
+
plugins: [
|
|
324
|
+
PluginMCPServer.configure({
|
|
325
|
+
serverName: 'my-crm',
|
|
326
|
+
transport: 'http',
|
|
327
|
+
port: 3100,
|
|
328
|
+
}),
|
|
329
|
+
],
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await stack.boot();
|
|
333
|
+
// MCP server running on http://localhost:3100
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Advanced Features
|
|
337
|
+
|
|
338
|
+
### Streaming Resources
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
kernel.getService('mcp').registerResource({
|
|
342
|
+
uri: 'objectstack://exports/opportunities-csv',
|
|
343
|
+
name: 'Opportunities Export (CSV)',
|
|
344
|
+
mimeType: 'text/csv',
|
|
345
|
+
async *stream() {
|
|
346
|
+
// Stream header
|
|
347
|
+
yield 'Name,Stage,Amount,Close Date\n';
|
|
348
|
+
|
|
349
|
+
// Stream records in batches
|
|
350
|
+
let offset = 0;
|
|
351
|
+
const batchSize = 100;
|
|
352
|
+
|
|
353
|
+
while (true) {
|
|
354
|
+
const batch = await kernel.getDriver().find({
|
|
355
|
+
object: 'opportunity',
|
|
356
|
+
limit: batchSize,
|
|
357
|
+
offset,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
if (batch.length === 0) break;
|
|
361
|
+
|
|
362
|
+
for (const opp of batch) {
|
|
363
|
+
yield `${opp.name},${opp.stage},${opp.amount},${opp.close_date}\n`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
offset += batchSize;
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Tool Permissions
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
kernel.getService('mcp').registerTool({
|
|
376
|
+
name: 'delete_opportunity',
|
|
377
|
+
description: 'Delete an opportunity',
|
|
378
|
+
permissions: ['opportunity:delete'], // Require permission
|
|
379
|
+
inputSchema: {
|
|
380
|
+
type: 'object',
|
|
381
|
+
properties: {
|
|
382
|
+
id: { type: 'string' },
|
|
383
|
+
},
|
|
384
|
+
required: ['id'],
|
|
385
|
+
},
|
|
386
|
+
async execute({ id }, context) {
|
|
387
|
+
// context includes userId, permissions, etc.
|
|
388
|
+
if (!context.hasPermission('opportunity:delete')) {
|
|
389
|
+
throw new Error('Permission denied');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
await kernel.getDriver().delete({
|
|
393
|
+
object: 'opportunity',
|
|
394
|
+
filters: [{ field: 'id', operator: 'eq', value: id }],
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
return { success: true, deleted: id };
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Dynamic Tool Registration
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// Register tools from flow definitions
|
|
406
|
+
const flows = await kernel.getMetadata('flow');
|
|
407
|
+
|
|
408
|
+
for (const flow of flows) {
|
|
409
|
+
kernel.getService('mcp').registerTool({
|
|
410
|
+
name: `flow_${flow.name}`,
|
|
411
|
+
description: flow.description,
|
|
412
|
+
inputSchema: generateSchemaFromFlow(flow),
|
|
413
|
+
async execute(inputs) {
|
|
414
|
+
return await kernel.executeFlow(flow.name, inputs);
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Server Capabilities
|
|
421
|
+
|
|
422
|
+
The MCP server exposes these capabilities:
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"capabilities": {
|
|
427
|
+
"tools": {
|
|
428
|
+
"listChanged": true
|
|
429
|
+
},
|
|
430
|
+
"resources": {
|
|
431
|
+
"subscribe": true,
|
|
432
|
+
"listChanged": true
|
|
433
|
+
},
|
|
434
|
+
"prompts": {
|
|
435
|
+
"listChanged": true
|
|
436
|
+
},
|
|
437
|
+
"logging": {},
|
|
438
|
+
"experimental": {
|
|
439
|
+
"streaming": true
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Best Practices
|
|
446
|
+
|
|
447
|
+
1. **Tool Design**: Keep tools focused and well-documented
|
|
448
|
+
2. **Resource Naming**: Use clear, hierarchical URI schemes
|
|
449
|
+
3. **Prompt Templates**: Make prompts flexible with arguments
|
|
450
|
+
4. **Error Handling**: Always return helpful error messages
|
|
451
|
+
5. **Permissions**: Check permissions before tool execution
|
|
452
|
+
6. **Performance**: Use streaming for large datasets
|
|
453
|
+
7. **Versioning**: Version your server and tools
|
|
454
|
+
|
|
455
|
+
## Debugging
|
|
456
|
+
|
|
457
|
+
Enable debug logging:
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
PluginMCPServer.configure({
|
|
461
|
+
serverName: 'my-crm',
|
|
462
|
+
debug: true, // Log all MCP messages
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
View MCP messages in client:
|
|
467
|
+
- **Claude Desktop**: Check logs in `~/Library/Logs/Claude/mcp*.log`
|
|
468
|
+
- **Cursor**: Check Output panel → MCP Server
|
|
469
|
+
- **Cline**: Check extension logs
|
|
470
|
+
|
|
471
|
+
## Example: Complete CRM Server
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
import { defineStack, defineTool } from '@objectstack/spec';
|
|
475
|
+
import { PluginMCPServer } from '@objectstack/plugin-mcp-server';
|
|
476
|
+
|
|
477
|
+
const stack = defineStack({
|
|
478
|
+
driver: /* ... */,
|
|
479
|
+
plugins: [
|
|
480
|
+
PluginMCPServer.configure({
|
|
481
|
+
serverName: 'crm-assistant',
|
|
482
|
+
autoRegisterTools: true,
|
|
483
|
+
}),
|
|
484
|
+
],
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
await stack.boot();
|
|
488
|
+
|
|
489
|
+
const mcp = stack.kernel.getService('mcp');
|
|
490
|
+
|
|
491
|
+
// Register custom tools
|
|
492
|
+
mcp.registerTool(defineTool({
|
|
493
|
+
name: 'forecast_revenue',
|
|
494
|
+
description: 'Forecast revenue based on pipeline',
|
|
495
|
+
async execute() {
|
|
496
|
+
// Implementation
|
|
497
|
+
},
|
|
498
|
+
}));
|
|
499
|
+
|
|
500
|
+
// Register custom resources
|
|
501
|
+
mcp.registerResource({
|
|
502
|
+
uri: 'objectstack://dashboards/sales',
|
|
503
|
+
name: 'Sales Dashboard',
|
|
504
|
+
async read() {
|
|
505
|
+
// Implementation
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Register prompts
|
|
510
|
+
mcp.registerPrompt({
|
|
511
|
+
name: 'weekly_report',
|
|
512
|
+
description: 'Generate weekly sales report',
|
|
513
|
+
async render() {
|
|
514
|
+
// Implementation
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## License
|
|
520
|
+
|
|
521
|
+
Apache-2.0
|
|
522
|
+
|
|
523
|
+
## See Also
|
|
524
|
+
|
|
525
|
+
- [Model Context Protocol Specification](https://modelcontextprotocol.io/)
|
|
526
|
+
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
527
|
+
- [@objectstack/spec/ai](../../spec/src/ai/)
|
|
528
|
+
- [Building MCP Servers Guide](/content/docs/guides/mcp/)
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/mcp-server-runtime.ts","../src/plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/plugin-mcp-server\n *\n * MCP Runtime Server Plugin for ObjectStack.\n * Exposes all registered AI tools, data resources, and agent prompts\n * via the Model Context Protocol (MCP) for use by external AI clients\n * (Claude Desktop, Cursor, VS Code Copilot, etc.).\n */\n\nexport { MCPServerPlugin } from './plugin.js';\nexport type { MCPServerPluginOptions } from './plugin.js';\nexport { MCPServerRuntime } from './mcp-server-runtime.js';\nexport type { MCPServerRuntimeConfig } from './mcp-server-runtime.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport type { Logger, IMetadataService, IDataEngine, AIToolDefinition } from '@objectstack/spec/contracts';\nimport type { Agent } from '@objectstack/spec';\nimport type { ToolRegistry, ToolExecutionResult } from './types.js';\nimport { z } from 'zod';\n\n/**\n * Configuration for the MCP Server Runtime.\n */\nexport interface MCPServerRuntimeConfig {\n /** Human-readable server name. */\n name?: string;\n /** Server version (semver). */\n version?: string;\n /** Optional instructions describing how to use the server. */\n instructions?: string;\n /** Transport mode: 'stdio' (default) or 'http'. */\n transport?: 'stdio' | 'http';\n /** Logger instance. */\n logger?: Logger;\n}\n\n/**\n * Minimal shape of an object definition returned by IMetadataService.\n */\ninterface ObjectDef {\n name: string;\n label?: string;\n fields?: Record<string, { name?: string; type?: string; label?: string; required?: boolean }>;\n enable?: Record<string, boolean>;\n}\n\n/**\n * Names of tools that are read-only (no side effects).\n * Kept as a module-level constant for easy extension.\n */\nconst READ_ONLY_TOOLS = new Set([\n 'list_objects',\n 'describe_object',\n 'query_records',\n 'get_record',\n 'aggregate_data',\n]);\n\n/**\n * Names of tools that perform destructive mutations.\n */\nconst DESTRUCTIVE_TOOLS = new Set([\n 'delete_field',\n]);\n\n/**\n * MCPServerRuntime — Bridges ObjectStack kernel services to the Model Context Protocol.\n *\n * Responsibilities:\n * 1. Bridge ToolRegistry → MCP tools (all registered AI tools)\n * 2. Bridge IMetadataService → MCP resources (object schemas, metadata types)\n * 3. Bridge IDataEngine → MCP resources (record access by URI)\n * 4. Bridge Agent definitions → MCP prompts (agent instructions)\n *\n * Architecture:\n * ```\n * ToolRegistry (service-ai) ──┐\n * IMetadataService (metadata) ─┼──→ MCPServerRuntime ──→ McpServer (SDK)\n * IDataEngine (objectql) ──┤ │\n * Agent definitions ──┘ ├── stdio transport\n * └── http transport (future)\n * ```\n */\nexport class MCPServerRuntime {\n private readonly mcpServer: McpServer;\n private readonly config: Required<Pick<MCPServerRuntimeConfig, 'name' | 'version'>> & MCPServerRuntimeConfig;\n private transport: StdioServerTransport | undefined;\n private started = false;\n\n constructor(config: MCPServerRuntimeConfig = {}) {\n this.config = {\n name: 'objectstack',\n version: '1.0.0',\n transport: 'stdio',\n ...config,\n };\n\n this.mcpServer = new McpServer(\n {\n name: this.config.name,\n version: this.config.version,\n },\n {\n capabilities: {\n resources: {},\n tools: {},\n prompts: {},\n logging: {},\n },\n instructions: this.config.instructions ?? 'ObjectStack MCP Server — access data objects, AI tools, and agent prompts.',\n },\n );\n }\n\n /** The underlying McpServer instance (for advanced use cases). */\n get server(): McpServer {\n return this.mcpServer;\n }\n\n /** Whether the server is currently connected and running. */\n get isStarted(): boolean {\n return this.started;\n }\n\n // ── Helpers ─────────────────────────────────────────────────────\n\n /**\n * Extract the text value from a ToolExecutionResult's output.\n *\n * The output may be a `{ type: 'text', value: string }` object (from the\n * Vercel AI SDK ToolResultPart) or any serialisable value.\n */\n private static formatToolOutput(result: ToolExecutionResult): string {\n const output = result.output;\n if (output && typeof output === 'object' && 'value' in output) {\n return String((output as { value: unknown }).value);\n }\n return JSON.stringify(output ?? '');\n }\n\n // ── Tool Bridge ────────────────────────────────────────────────\n\n /**\n * Bridge all tools from the ToolRegistry to MCP tools.\n *\n * Each registered tool becomes an MCP tool with the same name, description,\n * and JSON Schema parameters. The handler delegates to the ToolRegistry's\n * execute path.\n */\n bridgeTools(toolRegistry: ToolRegistry): void {\n const tools = toolRegistry.getAll();\n const logger = this.config.logger;\n\n for (const tool of tools) {\n this.registerToolFromDefinition(tool, toolRegistry);\n }\n\n logger?.info(`[MCP] Bridged ${tools.length} tools from ToolRegistry`);\n }\n\n /**\n * Register a single tool on the MCP server from an AIToolDefinition.\n */\n private registerToolFromDefinition(tool: AIToolDefinition, toolRegistry: ToolRegistry): void {\n const logger = this.config.logger;\n\n // Convert JSON Schema parameters to Zod-compatible format for MCP SDK\n // The MCP SDK registerTool with inputSchema expects a Zod raw shape or AnySchema.\n // Since our tools use JSON Schema, we use the low-level .tool() with a raw callback\n // and pass the JSON Schema as annotations metadata.\n this.mcpServer.registerTool(\n tool.name,\n {\n description: tool.description,\n annotations: {\n // Mark tools with write side-effects for destructive operations\n destructiveHint: this.isDestructiveTool(tool.name),\n readOnlyHint: this.isReadOnlyTool(tool.name),\n openWorldHint: false,\n },\n },\n async (extra) => {\n // The MCP SDK passes tool arguments via the extra.arguments property\n // when registerTool is called without an inputSchema.\n const rawExtra = extra as Record<string, unknown>;\n const args = (rawExtra.arguments ?? {}) as Record<string, unknown>;\n\n try {\n const result = await toolRegistry.execute({\n type: 'tool-call',\n toolCallId: `mcp-${tool.name}-${Date.now()}`,\n toolName: tool.name,\n input: args,\n });\n\n const outputText = MCPServerRuntime.formatToolOutput(result);\n\n if (result.isError) {\n return {\n content: [{ type: 'text' as const, text: outputText }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: 'text' as const, text: outputText }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger?.warn(`[MCP] Tool \"${tool.name}\" execution failed:`, { error: message });\n return {\n content: [{ type: 'text' as const, text: message }],\n isError: true,\n };\n }\n },\n );\n }\n\n /**\n * Check if a tool is read-only (data query tools).\n */\n private isReadOnlyTool(name: string): boolean {\n return READ_ONLY_TOOLS.has(name);\n }\n\n /**\n * Check if a tool performs destructive operations.\n */\n private isDestructiveTool(name: string): boolean {\n return DESTRUCTIVE_TOOLS.has(name);\n }\n\n // ── Resource Bridge ────────────────────────────────────────────\n\n /**\n * Bridge metadata service and data engine to MCP resources.\n *\n * Exposes:\n * - `objectstack://objects` — List all data objects\n * - `objectstack://objects/{objectName}` — Get object schema\n * - `objectstack://objects/{objectName}/records/{recordId}` — Get a specific record\n * - `objectstack://metadata/types` — List all metadata types\n */\n bridgeResources(metadataService: IMetadataService, dataEngine?: IDataEngine): void {\n const logger = this.config.logger;\n let resourceCount = 0;\n\n // ── Static resource: List all objects ──\n this.mcpServer.registerResource(\n 'object_list',\n 'objectstack://objects',\n {\n description: 'List all data objects (tables) in the ObjectStack instance',\n mimeType: 'application/json',\n },\n async () => {\n const objects = await metadataService.listObjects();\n const summary = (objects as ObjectDef[]).map(o => ({\n name: o.name,\n label: o.label ?? o.name,\n fieldCount: o.fields ? Object.keys(o.fields).length : 0,\n }));\n\n return {\n contents: [{\n uri: 'objectstack://objects',\n mimeType: 'application/json',\n text: JSON.stringify({ objects: summary, totalCount: summary.length }, null, 2),\n }],\n };\n },\n );\n resourceCount++;\n\n // ── Template resource: Object schema ──\n this.mcpServer.registerResource(\n 'object_schema',\n new ResourceTemplate('objectstack://objects/{objectName}', { list: undefined }),\n {\n description: 'Get the full schema of a specific data object including fields and features',\n mimeType: 'application/json',\n },\n async (_uri, variables) => {\n const objectName = String(variables.objectName);\n const objectDef = await metadataService.getObject(objectName);\n\n if (!objectDef) {\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}`,\n mimeType: 'application/json',\n text: JSON.stringify({ error: `Object \"${objectName}\" not found` }),\n }],\n };\n }\n\n const def = objectDef as ObjectDef;\n const fields = def.fields ?? {};\n const fieldSummary = Object.entries(fields).map(([key, f]) => ({\n name: key,\n type: f.type,\n label: f.label ?? key,\n required: f.required ?? false,\n }));\n\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}`,\n mimeType: 'application/json',\n text: JSON.stringify({\n name: def.name,\n label: def.label ?? def.name,\n fields: fieldSummary,\n enableFeatures: def.enable ?? {},\n }, null, 2),\n }],\n };\n },\n );\n resourceCount++;\n\n // ── Template resource: Record by ID ──\n if (dataEngine) {\n this.mcpServer.registerResource(\n 'record_by_id',\n new ResourceTemplate('objectstack://objects/{objectName}/records/{recordId}', { list: undefined }),\n {\n description: 'Get a specific record by ID from a data object',\n mimeType: 'application/json',\n },\n async (_uri, variables) => {\n const objectName = String(variables.objectName);\n const recordId = String(variables.recordId);\n\n try {\n const record = await dataEngine.findOne(objectName, {\n where: { id: recordId },\n });\n\n if (!record) {\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}/records/${recordId}`,\n mimeType: 'application/json',\n text: JSON.stringify({ error: `Record \"${recordId}\" not found in \"${objectName}\"` }),\n }],\n };\n }\n\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}/records/${recordId}`,\n mimeType: 'application/json',\n text: JSON.stringify(record, null, 2),\n }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}/records/${recordId}`,\n mimeType: 'application/json',\n text: JSON.stringify({ error: message }),\n }],\n };\n }\n },\n );\n resourceCount++;\n }\n\n // ── Static resource: Metadata types ──\n if (metadataService.getRegisteredTypes) {\n this.mcpServer.registerResource(\n 'metadata_types',\n 'objectstack://metadata/types',\n {\n description: 'List all registered metadata types (object, app, view, agent, tool, etc.)',\n mimeType: 'application/json',\n },\n async () => {\n const types = await metadataService.getRegisteredTypes!();\n return {\n contents: [{\n uri: 'objectstack://metadata/types',\n mimeType: 'application/json',\n text: JSON.stringify({ types, totalCount: types.length }, null, 2),\n }],\n };\n },\n );\n resourceCount++;\n }\n\n logger?.info(`[MCP] Bridged ${resourceCount} resource endpoints`);\n }\n\n // ── Prompt Bridge ──────────────────────────────────────────────\n\n /**\n * Bridge registered agents to MCP prompts.\n *\n * Each active agent becomes an MCP prompt with:\n * - Name matching the agent name\n * - System message from agent instructions\n * - Optional context arguments (objectName, recordId, viewName)\n */\n bridgePrompts(metadataService: IMetadataService): void {\n const logger = this.config.logger;\n\n // Register a dynamic prompt that loads agents at call time\n this.mcpServer.registerPrompt(\n 'agent_prompt',\n {\n description: 'Load an agent\\'s system prompt with optional UI context. ' +\n 'Use the agentName argument to select which agent\\'s instructions to use.',\n argsSchema: {\n agentName: z.string().describe('Name of the agent to load (e.g. \"data_chat\", \"metadata_assistant\")'),\n objectName: z.string().optional().describe('Current object the user is viewing'),\n recordId: z.string().optional().describe('Currently selected record ID'),\n viewName: z.string().optional().describe('Current view name'),\n },\n },\n async (args) => {\n const agentName = String(args.agentName ?? '');\n if (!agentName) {\n return {\n messages: [{\n role: 'user' as const,\n content: { type: 'text' as const, text: 'Error: agentName argument is required' },\n }],\n };\n }\n\n const raw = await metadataService.get('agent', agentName);\n if (!raw) {\n return {\n messages: [{\n role: 'user' as const,\n content: { type: 'text' as const, text: `Error: Agent \"${agentName}\" not found` },\n }],\n };\n }\n\n const agent = raw as Agent;\n\n // Build system prompt from agent instructions + context\n const parts: string[] = [];\n parts.push(agent.instructions ?? '');\n\n const contextHints: string[] = [];\n if (args.objectName) contextHints.push(`Current object: ${args.objectName}`);\n if (args.recordId) contextHints.push(`Selected record ID: ${args.recordId}`);\n if (args.viewName) contextHints.push(`Current view: ${args.viewName}`);\n if (contextHints.length > 0) {\n parts.push('\\n--- Current Context ---\\n' + contextHints.join('\\n'));\n }\n\n return {\n messages: [{\n role: 'assistant' as const,\n content: { type: 'text' as const, text: parts.join('\\n') },\n }],\n };\n },\n );\n\n logger?.info('[MCP] Agent prompts bridged');\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────\n\n /**\n * Start the MCP server with the configured transport.\n *\n * For stdio transport, this connects to process stdin/stdout.\n */\n async start(): Promise<void> {\n if (this.started) return;\n\n const logger = this.config.logger;\n\n if (this.config.transport === 'stdio') {\n this.transport = new StdioServerTransport();\n await this.mcpServer.connect(this.transport);\n this.started = true;\n logger?.info(`[MCP] Server started (transport: stdio, name: ${this.config.name})`);\n } else {\n // HTTP transport support will be added in a future version\n logger?.warn('[MCP] HTTP transport is not yet supported. Use stdio transport.');\n }\n }\n\n /**\n * Stop the MCP server and disconnect the transport.\n */\n async stop(): Promise<void> {\n if (!this.started) return;\n\n await this.mcpServer.close();\n this.transport = undefined;\n this.started = false;\n this.config.logger?.info('[MCP] Server stopped');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { IAIService, IDataEngine, IMetadataService } from '@objectstack/spec/contracts';\nimport { MCPServerRuntime } from './mcp-server-runtime.js';\nimport type { MCPServerRuntimeConfig } from './mcp-server-runtime.js';\nimport type { ToolRegistry } from './types.js';\n\n/**\n * Configuration options for the MCPServerPlugin.\n */\nexport interface MCPServerPluginOptions {\n /** Override MCP server name. Defaults to 'objectstack'. */\n name?: string;\n /** Override MCP server version. Defaults to package version. */\n version?: string;\n /** Transport mode: 'stdio' (default). */\n transport?: 'stdio' | 'http';\n /** Whether to auto-start the MCP server. Defaults to false (manual start via env var). */\n autoStart?: boolean;\n /** Custom instructions for the MCP server. */\n instructions?: string;\n}\n\n/**\n * MCPServerPlugin — Kernel plugin that exposes ObjectStack as an MCP server.\n *\n * Lifecycle:\n * 1. **init** — Creates {@link MCPServerRuntime} and registers as `'mcp'` service.\n * 2. **start** — Bridges ToolRegistry, MetadataService, DataEngine, and Agents\n * to the MCP server. Starts the transport if `autoStart` is enabled or\n * the `MCP_SERVER_ENABLED` environment variable is set.\n * 3. **destroy** — Stops the MCP transport.\n *\n * Environment Variables:\n * - `MCP_SERVER_ENABLED=true` — Enable MCP server at startup\n * - `MCP_SERVER_NAME` — Override server name\n * - `MCP_SERVER_TRANSPORT` — Override transport ('stdio' | 'http')\n *\n * @example\n * ```ts\n * import { LiteKernel } from '@objectstack/core';\n * import { MCPServerPlugin } from '@objectstack/plugin-mcp-server';\n *\n * const kernel = new LiteKernel();\n * kernel.use(new MCPServerPlugin({ autoStart: true }));\n * await kernel.bootstrap();\n * ```\n */\nexport class MCPServerPlugin implements Plugin {\n name = 'com.objectstack.plugin-mcp-server';\n version = '1.0.0';\n type = 'standard' as const;\n dependencies: string[] = [];\n\n private runtime?: MCPServerRuntime;\n private readonly options: MCPServerPluginOptions;\n\n constructor(options: MCPServerPluginOptions = {}) {\n this.options = options;\n }\n\n async init(ctx: PluginContext): Promise<void> {\n const config: MCPServerRuntimeConfig = {\n name: process.env.MCP_SERVER_NAME ?? this.options.name ?? 'objectstack',\n version: this.options.version ?? '1.0.0',\n transport: (process.env.MCP_SERVER_TRANSPORT as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',\n instructions: this.options.instructions,\n logger: ctx.logger,\n };\n\n this.runtime = new MCPServerRuntime(config);\n ctx.registerService('mcp', this.runtime);\n\n ctx.logger.info('[MCP] Plugin initialized');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n if (!this.runtime) return;\n\n // ── Bridge tools from AIService ──\n // The IAIService contract does not formally include `toolRegistry` because\n // it is an implementation detail of AIService. We use duck-typing here to\n // avoid a hard dependency on @objectstack/service-ai while still bridging\n // tools when the full AIService implementation is present.\n try {\n const aiService = ctx.getService<IAIService & { toolRegistry?: ToolRegistry }>('ai');\n if (aiService?.toolRegistry) {\n this.runtime.bridgeTools(aiService.toolRegistry);\n } else {\n ctx.logger.debug('[MCP] AI service does not expose a toolRegistry, skipping tool bridging');\n }\n } catch {\n ctx.logger.debug('[MCP] AI service not available, skipping tool bridging');\n }\n\n // ── Bridge resources from MetadataService & DataEngine ──\n let metadataService: IMetadataService | undefined;\n let dataEngine: IDataEngine | undefined;\n\n try {\n metadataService = ctx.getService<IMetadataService>('metadata');\n } catch {\n ctx.logger.debug('[MCP] Metadata service not available, skipping resource bridging');\n }\n\n try {\n dataEngine = ctx.getService<IDataEngine>('data');\n } catch {\n ctx.logger.debug('[MCP] Data engine not available, skipping record resources');\n }\n\n if (metadataService) {\n this.runtime.bridgeResources(metadataService, dataEngine);\n this.runtime.bridgePrompts(metadataService);\n }\n\n // ── Auto-start if configured ──\n const shouldStart = this.options.autoStart || process.env.MCP_SERVER_ENABLED === 'true';\n if (shouldStart) {\n await this.runtime.start();\n ctx.logger.info('[MCP] Server started automatically');\n } else {\n ctx.logger.info(\n '[MCP] Server ready but not started. Set MCP_SERVER_ENABLED=true or use autoStart option.',\n );\n }\n\n // Trigger hook for other plugins to extend MCP\n await ctx.trigger('mcp:ready', this.runtime);\n }\n\n async destroy(): Promise<void> {\n if (this.runtime?.isStarted) {\n await this.runtime.stop();\n }\n this.runtime = undefined;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/mcp-server-runtime.ts","../src/plugin.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/plugin-mcp-server\n *\n * MCP Runtime Server Plugin for ObjectStack.\n * Exposes all registered AI tools, data resources, and agent prompts\n * via the Model Context Protocol (MCP) for use by external AI clients\n * (Claude Desktop, Cursor, VS Code Copilot, etc.).\n */\n\nexport { MCPServerPlugin } from './plugin.js';\nexport type { MCPServerPluginOptions } from './plugin.js';\nexport { MCPServerRuntime } from './mcp-server-runtime.js';\nexport type { MCPServerRuntimeConfig } from './mcp-server-runtime.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport type { Logger, IMetadataService, IDataEngine, AIToolDefinition } from '@objectstack/spec/contracts';\nimport type { Agent } from '@objectstack/spec/ai';\nimport type { ToolRegistry, ToolExecutionResult } from './types.js';\nimport { z } from 'zod';\n\n/**\n * Configuration for the MCP Server Runtime.\n */\nexport interface MCPServerRuntimeConfig {\n /** Human-readable server name. */\n name?: string;\n /** Server version (semver). */\n version?: string;\n /** Optional instructions describing how to use the server. */\n instructions?: string;\n /** Transport mode: 'stdio' (default) or 'http'. */\n transport?: 'stdio' | 'http';\n /** Logger instance. */\n logger?: Logger;\n}\n\n/**\n * Minimal shape of an object definition returned by IMetadataService.\n */\ninterface ObjectDef {\n name: string;\n label?: string;\n fields?: Record<string, { name?: string; type?: string; label?: string; required?: boolean }>;\n enable?: Record<string, boolean>;\n}\n\n/**\n * Names of tools that are read-only (no side effects).\n * Kept as a module-level constant for easy extension.\n */\nconst READ_ONLY_TOOLS = new Set([\n 'list_objects',\n 'describe_object',\n 'query_records',\n 'get_record',\n 'aggregate_data',\n]);\n\n/**\n * Names of tools that perform destructive mutations.\n */\nconst DESTRUCTIVE_TOOLS = new Set([\n 'delete_field',\n]);\n\n/**\n * MCPServerRuntime — Bridges ObjectStack kernel services to the Model Context Protocol.\n *\n * Responsibilities:\n * 1. Bridge ToolRegistry → MCP tools (all registered AI tools)\n * 2. Bridge IMetadataService → MCP resources (object schemas, metadata types)\n * 3. Bridge IDataEngine → MCP resources (record access by URI)\n * 4. Bridge Agent definitions → MCP prompts (agent instructions)\n *\n * Architecture:\n * ```\n * ToolRegistry (service-ai) ──┐\n * IMetadataService (metadata) ─┼──→ MCPServerRuntime ──→ McpServer (SDK)\n * IDataEngine (objectql) ──┤ │\n * Agent definitions ──┘ ├── stdio transport\n * └── http transport (future)\n * ```\n */\nexport class MCPServerRuntime {\n private readonly mcpServer: McpServer;\n private readonly config: Required<Pick<MCPServerRuntimeConfig, 'name' | 'version'>> & MCPServerRuntimeConfig;\n private transport: StdioServerTransport | undefined;\n private started = false;\n\n constructor(config: MCPServerRuntimeConfig = {}) {\n this.config = {\n name: 'objectstack',\n version: '1.0.0',\n transport: 'stdio',\n ...config,\n };\n\n this.mcpServer = new McpServer(\n {\n name: this.config.name,\n version: this.config.version,\n },\n {\n capabilities: {\n resources: {},\n tools: {},\n prompts: {},\n logging: {},\n },\n instructions: this.config.instructions ?? 'ObjectStack MCP Server — access data objects, AI tools, and agent prompts.',\n },\n );\n }\n\n /** The underlying McpServer instance (for advanced use cases). */\n get server(): McpServer {\n return this.mcpServer;\n }\n\n /** Whether the server is currently connected and running. */\n get isStarted(): boolean {\n return this.started;\n }\n\n // ── Helpers ─────────────────────────────────────────────────────\n\n /**\n * Extract the text value from a ToolExecutionResult's output.\n *\n * The output may be a `{ type: 'text', value: string }` object (from the\n * Vercel AI SDK ToolResultPart) or any serialisable value.\n */\n private static formatToolOutput(result: ToolExecutionResult): string {\n const output = result.output;\n if (output && typeof output === 'object' && 'value' in output) {\n return String((output as { value: unknown }).value);\n }\n return JSON.stringify(output ?? '');\n }\n\n // ── Tool Bridge ────────────────────────────────────────────────\n\n /**\n * Bridge all tools from the ToolRegistry to MCP tools.\n *\n * Each registered tool becomes an MCP tool with the same name, description,\n * and JSON Schema parameters. The handler delegates to the ToolRegistry's\n * execute path.\n */\n bridgeTools(toolRegistry: ToolRegistry): void {\n const tools = toolRegistry.getAll();\n const logger = this.config.logger;\n\n for (const tool of tools) {\n this.registerToolFromDefinition(tool, toolRegistry);\n }\n\n logger?.info(`[MCP] Bridged ${tools.length} tools from ToolRegistry`);\n }\n\n /**\n * Register a single tool on the MCP server from an AIToolDefinition.\n */\n private registerToolFromDefinition(tool: AIToolDefinition, toolRegistry: ToolRegistry): void {\n const logger = this.config.logger;\n\n // Convert JSON Schema parameters to Zod-compatible format for MCP SDK\n // The MCP SDK registerTool with inputSchema expects a Zod raw shape or AnySchema.\n // Since our tools use JSON Schema, we use the low-level .tool() with a raw callback\n // and pass the JSON Schema as annotations metadata.\n this.mcpServer.registerTool(\n tool.name,\n {\n description: tool.description,\n annotations: {\n // Mark tools with write side-effects for destructive operations\n destructiveHint: this.isDestructiveTool(tool.name),\n readOnlyHint: this.isReadOnlyTool(tool.name),\n openWorldHint: false,\n },\n },\n async (extra) => {\n // The MCP SDK passes tool arguments via the extra.arguments property\n // when registerTool is called without an inputSchema.\n const rawExtra = extra as Record<string, unknown>;\n const args = (rawExtra.arguments ?? {}) as Record<string, unknown>;\n\n try {\n const result = await toolRegistry.execute({\n type: 'tool-call',\n toolCallId: `mcp-${tool.name}-${Date.now()}`,\n toolName: tool.name,\n input: args,\n });\n\n const outputText = MCPServerRuntime.formatToolOutput(result);\n\n if (result.isError) {\n return {\n content: [{ type: 'text' as const, text: outputText }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: 'text' as const, text: outputText }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger?.warn(`[MCP] Tool \"${tool.name}\" execution failed:`, { error: message });\n return {\n content: [{ type: 'text' as const, text: message }],\n isError: true,\n };\n }\n },\n );\n }\n\n /**\n * Check if a tool is read-only (data query tools).\n */\n private isReadOnlyTool(name: string): boolean {\n return READ_ONLY_TOOLS.has(name);\n }\n\n /**\n * Check if a tool performs destructive operations.\n */\n private isDestructiveTool(name: string): boolean {\n return DESTRUCTIVE_TOOLS.has(name);\n }\n\n // ── Resource Bridge ────────────────────────────────────────────\n\n /**\n * Bridge metadata service and data engine to MCP resources.\n *\n * Exposes:\n * - `objectstack://objects` — List all data objects\n * - `objectstack://objects/{objectName}` — Get object schema\n * - `objectstack://objects/{objectName}/records/{recordId}` — Get a specific record\n * - `objectstack://metadata/types` — List all metadata types\n */\n bridgeResources(metadataService: IMetadataService, dataEngine?: IDataEngine): void {\n const logger = this.config.logger;\n let resourceCount = 0;\n\n // ── Static resource: List all objects ──\n this.mcpServer.registerResource(\n 'object_list',\n 'objectstack://objects',\n {\n description: 'List all data objects (tables) in the ObjectStack instance',\n mimeType: 'application/json',\n },\n async () => {\n const objects = await metadataService.listObjects();\n const summary = (objects as ObjectDef[]).map(o => ({\n name: o.name,\n label: o.label ?? o.name,\n fieldCount: o.fields ? Object.keys(o.fields).length : 0,\n }));\n\n return {\n contents: [{\n uri: 'objectstack://objects',\n mimeType: 'application/json',\n text: JSON.stringify({ objects: summary, totalCount: summary.length }, null, 2),\n }],\n };\n },\n );\n resourceCount++;\n\n // ── Template resource: Object schema ──\n this.mcpServer.registerResource(\n 'object_schema',\n new ResourceTemplate('objectstack://objects/{objectName}', { list: undefined }),\n {\n description: 'Get the full schema of a specific data object including fields and features',\n mimeType: 'application/json',\n },\n async (_uri, variables) => {\n const objectName = String(variables.objectName);\n const objectDef = await metadataService.getObject(objectName);\n\n if (!objectDef) {\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}`,\n mimeType: 'application/json',\n text: JSON.stringify({ error: `Object \"${objectName}\" not found` }),\n }],\n };\n }\n\n const def = objectDef as ObjectDef;\n const fields = def.fields ?? {};\n const fieldSummary = Object.entries(fields).map(([key, f]) => ({\n name: key,\n type: f.type,\n label: f.label ?? key,\n required: f.required ?? false,\n }));\n\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}`,\n mimeType: 'application/json',\n text: JSON.stringify({\n name: def.name,\n label: def.label ?? def.name,\n fields: fieldSummary,\n enableFeatures: def.enable ?? {},\n }, null, 2),\n }],\n };\n },\n );\n resourceCount++;\n\n // ── Template resource: Record by ID ──\n if (dataEngine) {\n this.mcpServer.registerResource(\n 'record_by_id',\n new ResourceTemplate('objectstack://objects/{objectName}/records/{recordId}', { list: undefined }),\n {\n description: 'Get a specific record by ID from a data object',\n mimeType: 'application/json',\n },\n async (_uri, variables) => {\n const objectName = String(variables.objectName);\n const recordId = String(variables.recordId);\n\n try {\n const record = await dataEngine.findOne(objectName, {\n where: { id: recordId },\n });\n\n if (!record) {\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}/records/${recordId}`,\n mimeType: 'application/json',\n text: JSON.stringify({ error: `Record \"${recordId}\" not found in \"${objectName}\"` }),\n }],\n };\n }\n\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}/records/${recordId}`,\n mimeType: 'application/json',\n text: JSON.stringify(record, null, 2),\n }],\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return {\n contents: [{\n uri: `objectstack://objects/${objectName}/records/${recordId}`,\n mimeType: 'application/json',\n text: JSON.stringify({ error: message }),\n }],\n };\n }\n },\n );\n resourceCount++;\n }\n\n // ── Static resource: Metadata types ──\n if (metadataService.getRegisteredTypes) {\n this.mcpServer.registerResource(\n 'metadata_types',\n 'objectstack://metadata/types',\n {\n description: 'List all registered metadata types (object, app, view, agent, tool, etc.)',\n mimeType: 'application/json',\n },\n async () => {\n const types = await metadataService.getRegisteredTypes!();\n return {\n contents: [{\n uri: 'objectstack://metadata/types',\n mimeType: 'application/json',\n text: JSON.stringify({ types, totalCount: types.length }, null, 2),\n }],\n };\n },\n );\n resourceCount++;\n }\n\n logger?.info(`[MCP] Bridged ${resourceCount} resource endpoints`);\n }\n\n // ── Prompt Bridge ──────────────────────────────────────────────\n\n /**\n * Bridge registered agents to MCP prompts.\n *\n * Each active agent becomes an MCP prompt with:\n * - Name matching the agent name\n * - System message from agent instructions\n * - Optional context arguments (objectName, recordId, viewName)\n */\n bridgePrompts(metadataService: IMetadataService): void {\n const logger = this.config.logger;\n\n // Register a dynamic prompt that loads agents at call time\n this.mcpServer.registerPrompt(\n 'agent_prompt',\n {\n description: 'Load an agent\\'s system prompt with optional UI context. ' +\n 'Use the agentName argument to select which agent\\'s instructions to use.',\n argsSchema: {\n agentName: z.string().describe('Name of the agent to load (e.g. \"data_chat\", \"metadata_assistant\")'),\n objectName: z.string().optional().describe('Current object the user is viewing'),\n recordId: z.string().optional().describe('Currently selected record ID'),\n viewName: z.string().optional().describe('Current view name'),\n },\n },\n async (args) => {\n const agentName = String(args.agentName ?? '');\n if (!agentName) {\n return {\n messages: [{\n role: 'user' as const,\n content: { type: 'text' as const, text: 'Error: agentName argument is required' },\n }],\n };\n }\n\n const raw = await metadataService.get('agent', agentName);\n if (!raw) {\n return {\n messages: [{\n role: 'user' as const,\n content: { type: 'text' as const, text: `Error: Agent \"${agentName}\" not found` },\n }],\n };\n }\n\n const agent = raw as Agent;\n\n // Build system prompt from agent instructions + context\n const parts: string[] = [];\n parts.push(agent.instructions ?? '');\n\n const contextHints: string[] = [];\n if (args.objectName) contextHints.push(`Current object: ${args.objectName}`);\n if (args.recordId) contextHints.push(`Selected record ID: ${args.recordId}`);\n if (args.viewName) contextHints.push(`Current view: ${args.viewName}`);\n if (contextHints.length > 0) {\n parts.push('\\n--- Current Context ---\\n' + contextHints.join('\\n'));\n }\n\n return {\n messages: [{\n role: 'assistant' as const,\n content: { type: 'text' as const, text: parts.join('\\n') },\n }],\n };\n },\n );\n\n logger?.info('[MCP] Agent prompts bridged');\n }\n\n // ── Lifecycle ──────────────────────────────────────────────────\n\n /**\n * Start the MCP server with the configured transport.\n *\n * For stdio transport, this connects to process stdin/stdout.\n */\n async start(): Promise<void> {\n if (this.started) return;\n\n const logger = this.config.logger;\n\n if (this.config.transport === 'stdio') {\n this.transport = new StdioServerTransport();\n await this.mcpServer.connect(this.transport);\n this.started = true;\n logger?.info(`[MCP] Server started (transport: stdio, name: ${this.config.name})`);\n } else {\n // HTTP transport support will be added in a future version\n logger?.warn('[MCP] HTTP transport is not yet supported. Use stdio transport.');\n }\n }\n\n /**\n * Stop the MCP server and disconnect the transport.\n */\n async stop(): Promise<void> {\n if (!this.started) return;\n\n await this.mcpServer.close();\n this.transport = undefined;\n this.started = false;\n this.config.logger?.info('[MCP] Server stopped');\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { IAIService, IDataEngine, IMetadataService } from '@objectstack/spec/contracts';\nimport { MCPServerRuntime } from './mcp-server-runtime.js';\nimport type { MCPServerRuntimeConfig } from './mcp-server-runtime.js';\nimport type { ToolRegistry } from './types.js';\n\n/**\n * Configuration options for the MCPServerPlugin.\n */\nexport interface MCPServerPluginOptions {\n /** Override MCP server name. Defaults to 'objectstack'. */\n name?: string;\n /** Override MCP server version. Defaults to package version. */\n version?: string;\n /** Transport mode: 'stdio' (default). */\n transport?: 'stdio' | 'http';\n /** Whether to auto-start the MCP server. Defaults to false (manual start via env var). */\n autoStart?: boolean;\n /** Custom instructions for the MCP server. */\n instructions?: string;\n}\n\n/**\n * MCPServerPlugin — Kernel plugin that exposes ObjectStack as an MCP server.\n *\n * Lifecycle:\n * 1. **init** — Creates {@link MCPServerRuntime} and registers as `'mcp'` service.\n * 2. **start** — Bridges ToolRegistry, MetadataService, DataEngine, and Agents\n * to the MCP server. Starts the transport if `autoStart` is enabled or\n * the `MCP_SERVER_ENABLED` environment variable is set.\n * 3. **destroy** — Stops the MCP transport.\n *\n * Environment Variables:\n * - `MCP_SERVER_ENABLED=true` — Enable MCP server at startup\n * - `MCP_SERVER_NAME` — Override server name\n * - `MCP_SERVER_TRANSPORT` — Override transport ('stdio' | 'http')\n *\n * @example\n * ```ts\n * import { LiteKernel } from '@objectstack/core';\n * import { MCPServerPlugin } from '@objectstack/plugin-mcp-server';\n *\n * const kernel = new LiteKernel();\n * kernel.use(new MCPServerPlugin({ autoStart: true }));\n * await kernel.bootstrap();\n * ```\n */\nexport class MCPServerPlugin implements Plugin {\n name = 'com.objectstack.plugin-mcp-server';\n version = '1.0.0';\n type = 'standard' as const;\n dependencies: string[] = [];\n\n private runtime?: MCPServerRuntime;\n private readonly options: MCPServerPluginOptions;\n\n constructor(options: MCPServerPluginOptions = {}) {\n this.options = options;\n }\n\n async init(ctx: PluginContext): Promise<void> {\n const config: MCPServerRuntimeConfig = {\n name: process.env.MCP_SERVER_NAME ?? this.options.name ?? 'objectstack',\n version: this.options.version ?? '1.0.0',\n transport: (process.env.MCP_SERVER_TRANSPORT as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',\n instructions: this.options.instructions,\n logger: ctx.logger,\n };\n\n this.runtime = new MCPServerRuntime(config);\n ctx.registerService('mcp', this.runtime);\n\n ctx.logger.info('[MCP] Plugin initialized');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n if (!this.runtime) return;\n\n // ── Bridge tools from AIService ──\n // The IAIService contract does not formally include `toolRegistry` because\n // it is an implementation detail of AIService. We use duck-typing here to\n // avoid a hard dependency on @objectstack/service-ai while still bridging\n // tools when the full AIService implementation is present.\n try {\n const aiService = ctx.getService<IAIService & { toolRegistry?: ToolRegistry }>('ai');\n if (aiService?.toolRegistry) {\n this.runtime.bridgeTools(aiService.toolRegistry);\n } else {\n ctx.logger.debug('[MCP] AI service does not expose a toolRegistry, skipping tool bridging');\n }\n } catch {\n ctx.logger.debug('[MCP] AI service not available, skipping tool bridging');\n }\n\n // ── Bridge resources from MetadataService & DataEngine ──\n let metadataService: IMetadataService | undefined;\n let dataEngine: IDataEngine | undefined;\n\n try {\n metadataService = ctx.getService<IMetadataService>('metadata');\n } catch {\n ctx.logger.debug('[MCP] Metadata service not available, skipping resource bridging');\n }\n\n try {\n dataEngine = ctx.getService<IDataEngine>('data');\n } catch {\n ctx.logger.debug('[MCP] Data engine not available, skipping record resources');\n }\n\n if (metadataService) {\n this.runtime.bridgeResources(metadataService, dataEngine);\n this.runtime.bridgePrompts(metadataService);\n }\n\n // ── Auto-start if configured ──\n const shouldStart = this.options.autoStart || process.env.MCP_SERVER_ENABLED === 'true';\n if (shouldStart) {\n await this.runtime.start();\n ctx.logger.info('[MCP] Server started automatically');\n } else {\n ctx.logger.info(\n '[MCP] Server ready but not started. Set MCP_SERVER_ENABLED=true or use autoStart option.',\n );\n }\n\n // Trigger hook for other plugins to extend MCP\n await ctx.trigger('mcp:ready', this.runtime);\n }\n\n async destroy(): Promise<void> {\n if (this.runtime?.isStarted) {\n await this.runtime.stop();\n }\n this.runtime = undefined;\n }\n}\n"],"mappings":"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","names":[]}
|