mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:30:42 +00:00
fix: support draft 2020 mcp tool schemas
This commit is contained in:
@@ -70,6 +70,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Sessions: honor configured `session.maintenance` settings during load-time maintenance instead of falling back to default entry caps. Fixes #71356. Thanks @comolago.
|
||||
- Browser/sandbox: pass the resolved `browser.ssrfPolicy` into sandbox browser bridges and refresh cached bridges when the effective policy changes, so sandboxed browser navigation honors private-network opt-ins. Fixes #45153 and #57055. Thanks @jzakirov, @zuoanCo, and @kybrcore.
|
||||
- Browser/proxy: keep Gateway/provider proxy environment variables from proxying the OpenClaw-managed browser, so `HTTP_PROXY` and `HTTPS_PROXY` no longer block ordinary browser navigation. Fixes #71358. Thanks @Sanjays2402.
|
||||
- Agents/MCP: validate draft-2020-12 MCP tool output schemas with a draft-aware bundle-MCP client validator, so external MCP servers no longer fail catalog/tool execution with missing schema refs. Fixes #68772 and #70196. Thanks @mwiesen.
|
||||
- Dashboard/Windows: open Control UI and OAuth URLs through the system URL handler without `cmd.exe` parsing or PATH-based `rundll32` lookup, and reject non-HTTP browser-open inputs. Fixes #71098. Thanks @Sanjays2402.
|
||||
- Config/doctor: reject legacy `secretref-env:<ENV_VAR>` marker strings on SecretRef credential paths and migrate valid markers to structured env SecretRefs with `openclaw doctor --fix`. Fixes #51794. Thanks @halointellicore.
|
||||
- Providers/OpenAI: separate API-key and Codex sign-in onboarding groups, and avoid replaying stale OpenAI Responses reasoning blocks after a model route switch.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { createBundleMcpJsonSchemaValidator } from "./pi-bundle-mcp-runtime.js";
|
||||
import { cleanupBundleMcpHarness } from "./pi-bundle-mcp-test-harness.js";
|
||||
import {
|
||||
__testing,
|
||||
@@ -69,6 +70,25 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe("session MCP runtime", () => {
|
||||
it("accepts draft-2020-12 tool output schemas from external MCP catalogs", () => {
|
||||
const validator = createBundleMcpJsonSchemaValidator().getValidator<{ url: string }>({
|
||||
$schema: "https://json-schema.org/draft/2020-12/schema",
|
||||
type: "object",
|
||||
properties: {
|
||||
url: { type: "string" },
|
||||
},
|
||||
required: ["url"],
|
||||
additionalProperties: false,
|
||||
});
|
||||
|
||||
expect(validator({ url: "https://example.com" })).toEqual({
|
||||
valid: true,
|
||||
data: { url: "https://example.com" },
|
||||
errorMessage: undefined,
|
||||
});
|
||||
expect(validator({ url: 42 }).valid).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps colliding sanitized tool definitions stable across catalog order changes", async () => {
|
||||
const catalogA = [
|
||||
{ toolName: "alpha?", description: "question" },
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import crypto from "node:crypto";
|
||||
import { createRequire } from "node:module";
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/ajv-provider.js";
|
||||
import type {
|
||||
JsonSchemaType,
|
||||
JsonSchemaValidator,
|
||||
jsonSchemaValidator,
|
||||
} from "@modelcontextprotocol/sdk/validation/types.js";
|
||||
import type { ErrorObject, ValidateFunction } from "ajv";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { logWarn } from "../logger.js";
|
||||
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
|
||||
@@ -34,7 +42,53 @@ type CreateSessionMcpRuntime = (
|
||||
params: Parameters<typeof createSessionMcpRuntime>[0] & { configFingerprint?: string },
|
||||
) => SessionMcpRuntime;
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const SESSION_MCP_RUNTIME_MANAGER_KEY = Symbol.for("openclaw.sessionMcpRuntimeManager");
|
||||
const DRAFT_2020_12_SCHEMA = "https://json-schema.org/draft/2020-12/schema";
|
||||
|
||||
type Ajv2020Like = {
|
||||
compile: (schema: JsonSchemaType) => ValidateFunction;
|
||||
errorsText: (errors?: ErrorObject[] | null) => string;
|
||||
};
|
||||
|
||||
function isDraft202012Schema(schema: JsonSchemaType): boolean {
|
||||
return (schema as { $schema?: unknown }).$schema === DRAFT_2020_12_SCHEMA;
|
||||
}
|
||||
|
||||
export function createBundleMcpJsonSchemaValidator(): jsonSchemaValidator {
|
||||
const defaultValidator = new AjvJsonSchemaValidator();
|
||||
const Ajv2020Ctor = require("ajv/dist/2020") as new (opts?: object) => Ajv2020Like;
|
||||
const ajv2020 = new Ajv2020Ctor({
|
||||
strict: false,
|
||||
validateFormats: false,
|
||||
validateSchema: false,
|
||||
allErrors: true,
|
||||
});
|
||||
|
||||
return {
|
||||
getValidator<T>(schema: JsonSchemaType): JsonSchemaValidator<T> {
|
||||
if (!isDraft202012Schema(schema)) {
|
||||
return defaultValidator.getValidator<T>(schema);
|
||||
}
|
||||
const ajvValidator = ajv2020.compile(schema);
|
||||
return (input: unknown) => {
|
||||
const valid = ajvValidator(input);
|
||||
if (valid) {
|
||||
return {
|
||||
valid: true,
|
||||
data: input as T,
|
||||
errorMessage: undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: false,
|
||||
data: undefined,
|
||||
errorMessage: ajv2020.errorsText(ajvValidator.errors),
|
||||
};
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function connectWithTimeout(
|
||||
client: Client,
|
||||
@@ -178,7 +232,9 @@ export function createSessionMcpRuntime(params: {
|
||||
name: "openclaw-bundle-mcp",
|
||||
version: "0.0.0",
|
||||
},
|
||||
{},
|
||||
{
|
||||
jsonSchemaValidator: createBundleMcpJsonSchemaValidator(),
|
||||
},
|
||||
);
|
||||
const session: BundleMcpSession = {
|
||||
serverName,
|
||||
|
||||
Reference in New Issue
Block a user