@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 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/)
@@ -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":[]}