Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 3 additions & 29 deletions packages/core/src/adapters/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* Uses camelCase naming and _meta.ui.* namespace.
*/

import type { ToolDef, ToolAnnotations } from "../types/tools";
import type { ToolDef } from "../types/tools";
import type { UIDef } from "../types/ui";
import type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from "./types";
import { mapVisibilityToMcp } from "../utils/metadata";
import { mapVisibilityToMcp, buildAnnotations } from "../utils/metadata";
import { generateMcpCSPMetadata } from "../utils/csp";

// =============================================================================
Expand Down Expand Up @@ -37,7 +37,7 @@ export class McpAdapter implements ProtocolAdapter {
};

// Build annotations if specified
const annotations = this.buildAnnotations(toolDef.annotations);
const annotations = buildAnnotations(toolDef.annotations);

// Compatibility notes:
// - ext-apps / MCP Apps reference shape commonly uses nested `_meta.ui.resourceUri`.
Expand All @@ -55,32 +55,6 @@ export class McpAdapter implements ProtocolAdapter {
};
}

/**
* Build MCP annotations from tool annotations
*/
private buildAnnotations(annotations?: ToolAnnotations): Record<string, unknown> | undefined {
if (!annotations) {
return undefined;
}

const result: Record<string, unknown> = {};

if (annotations.readOnlyHint !== undefined) {
result.readOnlyHint = annotations.readOnlyHint;
}
if (annotations.destructiveHint !== undefined) {
result.destructiveHint = annotations.destructiveHint;
}
if (annotations.openWorldHint !== undefined) {
result.openWorldHint = annotations.openWorldHint;
}
if (annotations.idempotentHint !== undefined) {
result.idempotentHint = annotations.idempotentHint;
}

return Object.keys(result).length > 0 ? result : undefined;
}

/**
* Build UI resource metadata for MCP protocol
*/
Expand Down
34 changes: 3 additions & 31 deletions packages/core/src/adapters/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* Uses snake_case naming and openai/* prefixed keys.
*/

import type { ToolDef, ToolAnnotations } from "../types/tools";
import type { ToolDef } from "../types/tools";
import type { UIDef } from "../types/ui";
import type { ProtocolAdapter, ToolMetaResult, UIResourceMetaResult } from "./types";
import { mapVisibilityToOpenAI } from "../utils/metadata";
import { mapVisibilityToOpenAI, buildAnnotations } from "../utils/metadata";
import { generateOpenAICSPMetadata } from "../utils/csp";

// =============================================================================
Expand Down Expand Up @@ -60,42 +60,14 @@ export class OpenAIAdapter implements ProtocolAdapter {
}

// Build annotations if specified
const annotations = this.buildAnnotations(toolDef.annotations);
const annotations = buildAnnotations(toolDef.annotations);

return {
annotations,
_meta: Object.keys(meta).length > 0 ? meta : undefined,
};
}

/**
* Build OpenAI annotations from tool annotations
*
* OpenAI uses the same annotation names as MCP spec
*/
private buildAnnotations(annotations?: ToolAnnotations): Record<string, unknown> | undefined {
if (!annotations) {
return undefined;
}

const result: Record<string, unknown> = {};

if (annotations.readOnlyHint !== undefined) {
result.readOnlyHint = annotations.readOnlyHint;
}
if (annotations.destructiveHint !== undefined) {
result.destructiveHint = annotations.destructiveHint;
}
if (annotations.openWorldHint !== undefined) {
result.openWorldHint = annotations.openWorldHint;
}
if (annotations.idempotentHint !== undefined) {
result.idempotentHint = annotations.idempotentHint;
}

return Object.keys(result).length > 0 ? result : undefined;
}

/**
* Build UI resource metadata for OpenAI protocol
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/events/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @module events/types
*/

import type { ToolContext, ToolDefs as _ToolDefs } from "../types/tools";
import type { ToolContext } from "../types/tools";
import type { AppConfig } from "../types/config";

// =============================================================================
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export type { ErrorCodeType } from "./utils/errors";
export {
mapVisibilityToMcp,
mapVisibilityToOpenAI,
buildAnnotations,
generateToolMetadata,
generateAllToolsMetadata,
} from "./utils/metadata";
Expand Down
32 changes: 1 addition & 31 deletions packages/core/src/plugins/builtin/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import { z } from "zod";
import { createPlugin } from "../types";
import type { ToolCallContext, PluginInitContext, PluginStartContext } from "../types";
import { safeStringify } from "../../debug/logger";

/**
* Log level enumeration
Expand Down Expand Up @@ -59,37 +60,6 @@ function timestamp(): string {
return new Date().toISOString();
}

/**
* Safely stringify objects, handling circular references
*/
function safeStringify(obj: unknown): string {
try {
return JSON.stringify(obj, null, 2);
} catch {
try {
return JSON.stringify(obj, getCircularReplacer(), 2);
} catch {
return String(obj);
}
}
}

/**
* JSON replacer function that handles circular references
*/
function getCircularReplacer() {
const seen = new WeakSet();
return (_key: string, value: unknown): unknown => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
}
return value;
};
}

/**
* Built-in logging plugin
*
Expand Down
4 changes: 1 addition & 3 deletions packages/core/src/utils/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @module utils/csp
*/

import type { CSPConfig } from "../types/ui";
import type { CSPConfig, UIDef } from "../types/ui";

// =============================================================================
// MCP CSP METADATA
Expand Down Expand Up @@ -105,8 +105,6 @@ export function generateOpenAICSPMetadata(csp: CSPConfig): OpenAICSPMetadata {
// UI RESOURCE METADATA GENERATION
// =============================================================================

import type { UIDef } from "../types/ui";

/**
* MCP UI resource metadata format
*
Expand Down
42 changes: 41 additions & 1 deletion packages/core/src/utils/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @module utils/metadata
*/

import type { ToolDef, Visibility } from "../types/tools";
import type { ToolDef, Visibility, ToolAnnotations } from "../types/tools";
import { zodToJsonSchema } from "./schema";

// =============================================================================
Expand Down Expand Up @@ -102,6 +102,46 @@ export function mapVisibilityToOpenAI(visibility?: Visibility): OpenAIVisibility
}
}

// =============================================================================
// TOOL ANNOTATIONS
// =============================================================================

/**
* Build protocol-agnostic annotations from tool annotations.
*
* Both MCP and OpenAI use the same annotation property names,
* so this function is shared between adapters.
*
* @param annotations - Tool annotations from tool definition
* @returns Record of annotation properties, or undefined if no annotations
*
* @internal
*/
export function buildAnnotations(
annotations?: ToolAnnotations
): Record<string, unknown> | undefined {
if (!annotations) {
return undefined;
}

const result: Record<string, unknown> = {};

if (annotations.readOnlyHint !== undefined) {
result.readOnlyHint = annotations.readOnlyHint;
}
if (annotations.destructiveHint !== undefined) {
result.destructiveHint = annotations.destructiveHint;
}
if (annotations.openWorldHint !== undefined) {
result.openWorldHint = annotations.openWorldHint;
}
if (annotations.idempotentHint !== undefined) {
result.idempotentHint = annotations.idempotentHint;
}

return Object.keys(result).length > 0 ? result : undefined;
}

// =============================================================================
// TOOL METADATA GENERATION
// =============================================================================
Expand Down