Skip to content

feat: add MCP Runtime Server Plugin (plugin-mcp-server)#1108

Merged
hotlong merged 4 commits intomainfrom
copilot/research-repository-architecture
Apr 10, 2026
Merged

feat: add MCP Runtime Server Plugin (plugin-mcp-server)#1108
hotlong merged 4 commits intomainfrom
copilot/research-repository-architecture

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 10, 2026

Bridges all ObjectStack kernel services to the Model Context Protocol so external AI clients (Claude Desktop, Cursor, VS Code Copilot, etc.) can consume platform tools, data, and agent prompts via MCP's standardized interface.

ToolRegistry (service-ai)  ──┐
IMetadataService (metadata) ─┼──→  MCPServerRuntime  ──→  McpServer (@modelcontextprotocol/sdk)
IDataEngine (objectql)     ──┤                              │
Agent definitions          ──┘                              └── stdio transport

Tool Bridge

All ToolRegistry tools (9 built-in: list_objects, describe_object, query_records, get_record, aggregate_data, create_object, add_field, modify_field, delete_field) automatically exposed as MCP tools with readOnlyHint/destructiveHint annotations.

Resource Bridge

Four MCP resource endpoints:

  • objectstack://objects — object list
  • objectstack://objects/{objectName} — object schema via ResourceTemplate
  • objectstack://objects/{objectName}/records/{recordId} — record by ID
  • objectstack://metadata/types — registered metadata types

Prompt Bridge

agent_prompt MCP prompt with Zod-typed args (agentName, objectName, recordId, viewName) loads agent instructions + UI context dynamically from IMetadataService.

Plugin Lifecycle

  • init: creates MCPServerRuntime, registers as 'mcp' service
  • start: auto-detects ai, metadata, data services; gracefully degrades when missing; fires mcp:ready hook
  • destroy: stops transport

Configuration

MCP_SERVER_ENABLED=true   # auto-start on bootstrap
MCP_SERVER_NAME=my-stack  # override server name
MCP_SERVER_TRANSPORT=stdio # transport mode
kernel.use(new MCPServerPlugin({ autoStart: true }));

Studio frontend unaffected

Studio AI continues using REST/SSE via Vercel Data Stream Protocol. MCP is a separate channel for external clients.

Dependencies

  • @modelcontextprotocol/sdk@^1.29.0 (no advisories)
  • zod@^4.3.6 (aligned with @objectstack/spec)
  • Decoupled from @objectstack/service-ai via duck-typed ToolRegistry interface in types.ts

Copilot AI and others added 4 commits April 10, 2026 14:36
Implements the MCP Runtime Server plugin that bridges ObjectStack kernel
services to the Model Context Protocol (MCP). Features:
- Tool bridge: all ToolRegistry tools → MCP tools
- Resource bridge: MetadataService → object schemas, DataEngine → records
- Prompt bridge: Agent definitions → MCP prompts
- StdioServerTransport for local AI client integration
- Environment variable configuration (MCP_SERVER_ENABLED, MCP_SERVER_NAME)

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/5eb90a65-846f-4f24-947f-1c0e7ea5090e

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
25 tests covering:
- MCPServerRuntime: constructor, bridgeTools, bridgeResources, bridgePrompts, lifecycle
- MCPServerPlugin: metadata, init, start (with/without services), destroy
- Graceful degradation when services are missing

Also updates CHANGELOG.md with new MCP Runtime Server Plugin feature.

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/5eb90a65-846f-4f24-947f-1c0e7ea5090e

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…mas, fix type safety

- Extract formatToolOutput() static helper for output transformation
- Replace `as any` casts in tool handler with explicit Record<string,unknown>
- Use Zod schemas for prompt argsSchema (z.string().describe() vs raw objects)
- Add zod@^4.3.6 dependency aligned with @objectstack/spec
- Document duck-typing pattern for IAIService.toolRegistry access

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/5eb90a65-846f-4f24-947f-1c0e7ea5090e

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Moves READ_ONLY_TOOLS and DESTRUCTIVE_TOOLS from inline arrays to
module-level Set constants for better maintainability.

Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/5eb90a65-846f-4f24-947f-1c0e7ea5090e

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectstack-play Ready Ready Preview, Comment Apr 10, 2026 2:51pm
spec Ready Ready Preview, Comment Apr 10, 2026 2:51pm

Request Review

@hotlong hotlong marked this pull request as ready for review April 10, 2026 14:56
Copilot AI review requested due to automatic review settings April 10, 2026 14:56
@hotlong hotlong merged commit 3609a80 into main Apr 10, 2026
5 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new kernel plugin package (@objectstack/plugin-mcp-server) that exposes ObjectStack services (AI tools, metadata resources, and agent prompts) via the Model Context Protocol (MCP) for consumption by external AI clients over a stdio-based MCP server runtime.

Changes:

  • Introduces plugin-mcp-server package (runtime + kernel plugin) bridging ToolRegistry → MCP tools, Metadata/DataEngine → MCP resources, and Agents → MCP prompts.
  • Adds Vitest coverage for plugin lifecycle and runtime bridging behavior.
  • Updates lockfile and changelog to include the new plugin and MCP SDK dependency.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pnpm-lock.yaml Adds @modelcontextprotocol/sdk and transitive deps for the new plugin package.
packages/plugins/plugin-mcp-server/package.json Defines the new plugin package, build/test scripts, and dependencies.
packages/plugins/plugin-mcp-server/tsconfig.json TypeScript build config for the new plugin package.
packages/plugins/plugin-mcp-server/src/types.ts Duck-typed ToolRegistry + ToolExecutionResult interface used by the MCP bridge.
packages/plugins/plugin-mcp-server/src/plugin.ts Kernel plugin lifecycle: init/register service, bridge services on start, optional auto-start, trigger mcp:ready.
packages/plugins/plugin-mcp-server/src/mcp-server-runtime.ts MCP runtime: registers tools/resources/prompts and manages stdio transport lifecycle.
packages/plugins/plugin-mcp-server/src/index.ts Public exports for the plugin and runtime.
packages/plugins/plugin-mcp-server/src/tests/plugin.test.ts Tests plugin lifecycle behavior and graceful degradation when services are missing.
packages/plugins/plugin-mcp-server/src/tests/mcp-server-runtime.test.ts Tests tool/resource/prompt bridging and lifecycle warnings.
CHANGELOG.md Documents the new MCP plugin and its capabilities under Unreleased.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +64 to +75
const config: MCPServerRuntimeConfig = {
name: process.env.MCP_SERVER_NAME ?? this.options.name ?? 'objectstack',
version: this.options.version ?? '1.0.0',
transport: (process.env.MCP_SERVER_TRANSPORT as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',
instructions: this.options.instructions,
logger: ctx.logger,
};

this.runtime = new MCPServerRuntime(config);
ctx.registerService('mcp', this.runtime);

ctx.logger.info('[MCP] Plugin initialized');
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using MCP stdio transport, the protocol stream is written over process.stdout. Passing ctx.logger into the runtime and logging during startup will also write to stdout by default (ObjectLogger’s pino transport uses destination 1), which can corrupt the MCP message stream for clients. Consider routing MCP-related logs to stderr/a file, or disabling stdout logging while the stdio transport is connected.

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +68
version: this.options.version ?? '1.0.0',
transport: (process.env.MCP_SERVER_TRANSPORT as 'stdio' | 'http') ?? this.options.transport ?? 'stdio',
instructions: this.options.instructions,
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MCP_SERVER_TRANSPORT is cast to 'stdio' | 'http' without validation. If the env var contains any other value, it will still be used at runtime and start() will fall into the non-stdio branch (with an HTTP-specific warning that may be misleading). Please validate/normalize the env var to the allowed set and fall back to the default when invalid.

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +170
// Convert JSON Schema parameters to Zod-compatible format for MCP SDK
// The MCP SDK registerTool with inputSchema expects a Zod raw shape or AnySchema.
// Since our tools use JSON Schema, we use the low-level .tool() with a raw callback
// and pass the JSON Schema as annotations metadata.
this.mcpServer.registerTool(
tool.name,
{
description: tool.description,
annotations: {
// Mark tools with write side-effects for destructive operations
destructiveHint: this.isDestructiveTool(tool.name),
readOnlyHint: this.isReadOnlyTool(tool.name),
openWorldHint: false,
},
},
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tool parameter schemas from AIToolDefinition.parameters are currently ignored when registering MCP tools. That means MCP clients can’t discover/validate the expected arguments, which also contradicts the inline comment about passing JSON Schema metadata. Consider converting the JSON Schema to an MCP-compatible schema (or at least attaching the JSON Schema in a documented metadata field) so clients can generate correct tool UIs and calls.

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +183
const result = await toolRegistry.execute({
type: 'tool-call',
toolCallId: `mcp-${tool.name}-${Date.now()}`,
toolName: tool.name,
input: args,
});
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toolCallId is generated with Date.now(), which can collide under high-frequency or parallel tool calls and makes debugging/replay harder. Prefer a UUID (e.g., crypto.randomUUID()) or a monotonic counter scoped to the runtime instance to guarantee uniqueness.

Copilot uses AI. Check for mistakes.
Comment on lines +321 to +328
async (_uri, variables) => {
const objectName = String(variables.objectName);
const recordId = String(variables.recordId);

try {
const record = await dataEngine.findOne(objectName, {
where: { id: recordId },
});
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The record resource (objectstack://objects/{objectName}/records/{recordId}) exposes raw records via IDataEngine.findOne() with no auth/user context. Since IDataEngine doesn’t take a request principal, this can effectively run with system-level access and leak sensitive fields when MCP is enabled. Consider requiring an explicit opt-in/allowlist for record access, or integrating with the platform’s security/auth layer before returning record data.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants