Merge branch 'main' into server-authorization-proxy
This commit is contained in:
commit
1899d5cc52
10 changed files with 251 additions and 29 deletions
27
README.md
27
README.md
|
|
@ -211,7 +211,7 @@ await server.connect(transport);
|
|||
For remote servers, start a web server with a Server-Sent Events (SSE) endpoint, and a separate endpoint for the client to send its messages to:
|
||||
|
||||
```typescript
|
||||
import express from "express";
|
||||
import express, { Request, Response } from "express";
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
|
||||
|
|
@ -224,16 +224,27 @@ const server = new McpServer({
|
|||
|
||||
const app = express();
|
||||
|
||||
app.get("/sse", async (req, res) => {
|
||||
const transport = new SSEServerTransport("/messages", res);
|
||||
// to support multiple simultaneous connections we have a lookup object from
|
||||
// sessionId to transport
|
||||
const transports: {[sessionId: string]: SSEServerTransport} = {};
|
||||
|
||||
app.get("/sse", async (_: Request, res: Response) => {
|
||||
const transport = new SSEServerTransport('/messages', res);
|
||||
transports[transport.sessionId] = transport;
|
||||
res.on("close", () => {
|
||||
delete transports[transport.sessionId];
|
||||
});
|
||||
await server.connect(transport);
|
||||
});
|
||||
|
||||
app.post("/messages", async (req, res) => {
|
||||
// Note: to support multiple simultaneous connections, these messages will
|
||||
// need to be routed to a specific matching transport. (This logic isn't
|
||||
// implemented here, for simplicity.)
|
||||
await transport.handlePostMessage(req, res);
|
||||
app.post("/messages", async (req: Request, res: Response) => {
|
||||
const sessionId = req.query.sessionId as string;
|
||||
const transport = transports[sessionId];
|
||||
if (transport) {
|
||||
await transport.handlePostMessage(req, res);
|
||||
} else {
|
||||
res.status(400).send('No transport found for sessionId');
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3001);
|
||||
|
|
|
|||
20
package-lock.json
generated
20
package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
|||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"cors": "^2.8.5",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"eventsource": "^3.0.2",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
"@jest-mock/express": "^3.0.0",
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/cross-spawn": "^6.0.6",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/eventsource": "^1.1.15",
|
||||
"@types/express": "^5.0.0",
|
||||
|
|
@ -1693,6 +1695,16 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cross-spawn": {
|
||||
"version": "6.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz",
|
||||
"integrity": "sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
|
|
@ -2809,7 +2821,6 @@
|
|||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz",
|
||||
"integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
|
|
@ -4156,8 +4167,7 @@
|
|||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
"version": "3.2.2",
|
||||
|
|
@ -5359,7 +5369,6 @@
|
|||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -5871,7 +5880,6 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
|
|
@ -5883,7 +5891,6 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -6480,7 +6487,6 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/sdk",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"description": "Model Context Protocol implementation for TypeScript",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
|
|
@ -48,6 +48,7 @@
|
|||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"cors": "^2.8.5",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"eventsource": "^3.0.2",
|
||||
"express": "^5.0.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
|
|
@ -61,6 +62,7 @@
|
|||
"@jest-mock/express": "^3.0.0",
|
||||
"@types/content-type": "^1.1.8",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/cross-spawn": "^6.0.6",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/eventsource": "^1.1.15",
|
||||
"@types/express": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -46,11 +46,11 @@ describe("OAuth Authorization", () => {
|
|||
it("returns metadata when first fetch fails but second without MCP header succeeds", async () => {
|
||||
// Set up a counter to control behavior
|
||||
let callCount = 0;
|
||||
|
||||
|
||||
// Mock implementation that changes behavior based on call count
|
||||
mockFetch.mockImplementation((_url, _options) => {
|
||||
callCount++;
|
||||
|
||||
|
||||
if (callCount === 1) {
|
||||
// First call with MCP header - fail with TypeError (simulating CORS error)
|
||||
// We need to use TypeError specifically because that's what the implementation checks for
|
||||
|
|
@ -68,10 +68,10 @@ describe("OAuth Authorization", () => {
|
|||
// Should succeed with the second call
|
||||
const metadata = await discoverOAuthMetadata("https://auth.example.com");
|
||||
expect(metadata).toEqual(validMetadata);
|
||||
|
||||
|
||||
// Verify both calls were made
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2);
|
||||
|
||||
|
||||
// Verify first call had MCP header
|
||||
expect(mockFetch.mock.calls[0][1]?.headers).toHaveProperty("MCP-Protocol-Version");
|
||||
});
|
||||
|
|
@ -79,11 +79,11 @@ describe("OAuth Authorization", () => {
|
|||
it("throws an error when all fetch attempts fail", async () => {
|
||||
// Set up a counter to control behavior
|
||||
let callCount = 0;
|
||||
|
||||
|
||||
// Mock implementation that changes behavior based on call count
|
||||
mockFetch.mockImplementation((_url, _options) => {
|
||||
callCount++;
|
||||
|
||||
|
||||
if (callCount === 1) {
|
||||
// First call - fail with TypeError
|
||||
return Promise.reject(new TypeError("First failure"));
|
||||
|
|
@ -96,7 +96,7 @@ describe("OAuth Authorization", () => {
|
|||
// Should fail with the second error
|
||||
await expect(discoverOAuthMetadata("https://auth.example.com"))
|
||||
.rejects.toThrow("Second failure");
|
||||
|
||||
|
||||
// Verify both calls were made
|
||||
expect(mockFetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
|
@ -250,6 +250,7 @@ describe("OAuth Authorization", () => {
|
|||
clientInformation: validClientInfo,
|
||||
authorizationCode: "code123",
|
||||
codeVerifier: "verifier123",
|
||||
redirectUri: "http://localhost:3000/callback",
|
||||
});
|
||||
|
||||
expect(tokens).toEqual(validTokens);
|
||||
|
|
@ -271,6 +272,7 @@ describe("OAuth Authorization", () => {
|
|||
expect(body.get("code_verifier")).toBe("verifier123");
|
||||
expect(body.get("client_id")).toBe("client123");
|
||||
expect(body.get("client_secret")).toBe("secret123");
|
||||
expect(body.get("redirect_uri")).toBe("http://localhost:3000/callback");
|
||||
});
|
||||
|
||||
it("validates token response schema", async () => {
|
||||
|
|
@ -288,6 +290,7 @@ describe("OAuth Authorization", () => {
|
|||
clientInformation: validClientInfo,
|
||||
authorizationCode: "code123",
|
||||
codeVerifier: "verifier123",
|
||||
redirectUri: "http://localhost:3000/callback",
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
|
@ -303,6 +306,7 @@ describe("OAuth Authorization", () => {
|
|||
clientInformation: validClientInfo,
|
||||
authorizationCode: "code123",
|
||||
codeVerifier: "verifier123",
|
||||
redirectUri: "http://localhost:3000/callback",
|
||||
})
|
||||
).rejects.toThrow("Token exchange failed");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ export async function auth(
|
|||
clientInformation,
|
||||
authorizationCode,
|
||||
codeVerifier,
|
||||
redirectUri: provider.redirectUrl,
|
||||
});
|
||||
|
||||
await provider.saveTokens(tokens);
|
||||
|
|
@ -259,11 +260,13 @@ export async function exchangeAuthorization(
|
|||
clientInformation,
|
||||
authorizationCode,
|
||||
codeVerifier,
|
||||
redirectUri,
|
||||
}: {
|
||||
metadata?: OAuthMetadata;
|
||||
clientInformation: OAuthClientInformation;
|
||||
authorizationCode: string;
|
||||
codeVerifier: string;
|
||||
redirectUri: string | URL;
|
||||
},
|
||||
): Promise<OAuthTokens> {
|
||||
const grantType = "authorization_code";
|
||||
|
|
@ -290,6 +293,7 @@ export async function exchangeAuthorization(
|
|||
client_id: clientInformation.client_id,
|
||||
code: authorizationCode,
|
||||
code_verifier: codeVerifier,
|
||||
redirect_uri: String(redirectUri),
|
||||
});
|
||||
|
||||
if (clientInformation.client_secret) {
|
||||
|
|
|
|||
131
src/client/cross-spawn.test.ts
Normal file
131
src/client/cross-spawn.test.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { StdioClientTransport } from "./stdio.js";
|
||||
import spawn from "cross-spawn";
|
||||
import { JSONRPCMessage } from "../types.js";
|
||||
import { ChildProcess } from "node:child_process";
|
||||
|
||||
// mock cross-spawn
|
||||
jest.mock("cross-spawn");
|
||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>;
|
||||
|
||||
describe("StdioClientTransport using cross-spawn", () => {
|
||||
beforeEach(() => {
|
||||
// mock cross-spawn's return value
|
||||
mockSpawn.mockImplementation(() => {
|
||||
const mockProcess: {
|
||||
on: jest.Mock;
|
||||
stdin?: { on: jest.Mock; write: jest.Mock };
|
||||
stdout?: { on: jest.Mock };
|
||||
stderr?: null;
|
||||
} = {
|
||||
on: jest.fn((event: string, callback: () => void) => {
|
||||
if (event === "spawn") {
|
||||
callback();
|
||||
}
|
||||
return mockProcess;
|
||||
}),
|
||||
stdin: {
|
||||
on: jest.fn(),
|
||||
write: jest.fn().mockReturnValue(true)
|
||||
},
|
||||
stdout: {
|
||||
on: jest.fn()
|
||||
},
|
||||
stderr: null
|
||||
};
|
||||
return mockProcess as unknown as ChildProcess;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test("should call cross-spawn correctly", async () => {
|
||||
const transport = new StdioClientTransport({
|
||||
command: "test-command",
|
||||
args: ["arg1", "arg2"]
|
||||
});
|
||||
|
||||
await transport.start();
|
||||
|
||||
// verify spawn is called correctly
|
||||
expect(mockSpawn).toHaveBeenCalledWith(
|
||||
"test-command",
|
||||
["arg1", "arg2"],
|
||||
expect.objectContaining({
|
||||
shell: false
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("should pass environment variables correctly", async () => {
|
||||
const customEnv = { TEST_VAR: "test-value" };
|
||||
const transport = new StdioClientTransport({
|
||||
command: "test-command",
|
||||
env: customEnv
|
||||
});
|
||||
|
||||
await transport.start();
|
||||
|
||||
// verify environment variables are passed correctly
|
||||
expect(mockSpawn).toHaveBeenCalledWith(
|
||||
"test-command",
|
||||
[],
|
||||
expect.objectContaining({
|
||||
env: customEnv
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("should send messages correctly", async () => {
|
||||
const transport = new StdioClientTransport({
|
||||
command: "test-command"
|
||||
});
|
||||
|
||||
// get the mock process object
|
||||
const mockProcess: {
|
||||
on: jest.Mock;
|
||||
stdin: {
|
||||
on: jest.Mock;
|
||||
write: jest.Mock;
|
||||
once: jest.Mock;
|
||||
};
|
||||
stdout: {
|
||||
on: jest.Mock;
|
||||
};
|
||||
stderr: null;
|
||||
} = {
|
||||
on: jest.fn((event: string, callback: () => void) => {
|
||||
if (event === "spawn") {
|
||||
callback();
|
||||
}
|
||||
return mockProcess;
|
||||
}),
|
||||
stdin: {
|
||||
on: jest.fn(),
|
||||
write: jest.fn().mockReturnValue(true),
|
||||
once: jest.fn()
|
||||
},
|
||||
stdout: {
|
||||
on: jest.fn()
|
||||
},
|
||||
stderr: null
|
||||
};
|
||||
|
||||
mockSpawn.mockReturnValue(mockProcess as unknown as ChildProcess);
|
||||
|
||||
await transport.start();
|
||||
|
||||
// 关键修复:确保 jsonrpc 是字面量 "2.0"
|
||||
const message: JSONRPCMessage = {
|
||||
jsonrpc: "2.0",
|
||||
id: "test-id",
|
||||
method: "test-method"
|
||||
};
|
||||
|
||||
await transport.send(message);
|
||||
|
||||
// verify message is sent correctly
|
||||
expect(mockProcess.stdin.write).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { ChildProcess, IOType, spawn } from "node:child_process";
|
||||
import { ChildProcess, IOType } from "node:child_process";
|
||||
import spawn from "cross-spawn";
|
||||
import process from "node:process";
|
||||
import { Stream } from "node:stream";
|
||||
import { ReadBuffer, serializeMessage } from "../shared/stdio.js";
|
||||
|
|
|
|||
|
|
@ -71,6 +71,44 @@ describe("protocol tests", () => {
|
|||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
test("should not reset timeout when resetTimeoutOnProgress is false", async () => {
|
||||
await protocol.connect(transport);
|
||||
const request = { method: "example", params: {} };
|
||||
const mockSchema: ZodType<{ result: string }> = z.object({
|
||||
result: z.string(),
|
||||
});
|
||||
const onProgressMock = jest.fn();
|
||||
const requestPromise = protocol.request(request, mockSchema, {
|
||||
timeout: 1000,
|
||||
resetTimeoutOnProgress: false,
|
||||
onprogress: onProgressMock,
|
||||
});
|
||||
|
||||
jest.advanceTimersByTime(800);
|
||||
|
||||
if (transport.onmessage) {
|
||||
transport.onmessage({
|
||||
jsonrpc: "2.0",
|
||||
method: "notifications/progress",
|
||||
params: {
|
||||
progressToken: 0,
|
||||
progress: 50,
|
||||
total: 100,
|
||||
},
|
||||
});
|
||||
}
|
||||
await Promise.resolve();
|
||||
|
||||
expect(onProgressMock).toHaveBeenCalledWith({
|
||||
progress: 50,
|
||||
total: 100,
|
||||
});
|
||||
|
||||
jest.advanceTimersByTime(201);
|
||||
|
||||
await expect(requestPromise).rejects.toThrow("Request timed out");
|
||||
});
|
||||
|
||||
test("should reset timeout when progress notification is received", async () => {
|
||||
await protocol.connect(transport);
|
||||
const request = { method: "example", params: {} };
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ type TimeoutInfo = {
|
|||
startTime: number;
|
||||
timeout: number;
|
||||
maxTotalTimeout?: number;
|
||||
resetTimeoutOnProgress: boolean;
|
||||
onTimeout: () => void;
|
||||
};
|
||||
|
||||
|
|
@ -184,13 +185,15 @@ export abstract class Protocol<
|
|||
messageId: number,
|
||||
timeout: number,
|
||||
maxTotalTimeout: number | undefined,
|
||||
onTimeout: () => void
|
||||
onTimeout: () => void,
|
||||
resetTimeoutOnProgress: boolean = false
|
||||
) {
|
||||
this._timeoutInfo.set(messageId, {
|
||||
timeoutId: setTimeout(onTimeout, timeout),
|
||||
startTime: Date.now(),
|
||||
timeout,
|
||||
maxTotalTimeout,
|
||||
resetTimeoutOnProgress,
|
||||
onTimeout
|
||||
});
|
||||
}
|
||||
|
|
@ -369,7 +372,9 @@ export abstract class Protocol<
|
|||
}
|
||||
|
||||
const responseHandler = this._responseHandlers.get(messageId);
|
||||
if (this._timeoutInfo.has(messageId) && responseHandler) {
|
||||
const timeoutInfo = this._timeoutInfo.get(messageId);
|
||||
|
||||
if (timeoutInfo && responseHandler && timeoutInfo.resetTimeoutOnProgress) {
|
||||
try {
|
||||
this._resetTimeout(messageId);
|
||||
} catch (error) {
|
||||
|
|
@ -531,7 +536,7 @@ export abstract class Protocol<
|
|||
{ timeout }
|
||||
));
|
||||
|
||||
this._setupTimeout(messageId, timeout, options?.maxTotalTimeout, timeoutHandler);
|
||||
this._setupTimeout(messageId, timeout, options?.maxTotalTimeout, timeoutHandler, options?.resetTimeoutOnProgress ?? false);
|
||||
|
||||
this._transport.send(jsonrpcRequest).catch((error) => {
|
||||
this._cleanupTimeout(messageId);
|
||||
|
|
|
|||
24
src/types.ts
24
src/types.ts
|
|
@ -666,6 +666,23 @@ export const ImageContentSchema = z
|
|||
})
|
||||
.passthrough();
|
||||
|
||||
/**
|
||||
* An Audio provided to or from an LLM.
|
||||
*/
|
||||
export const AudioContentSchema = z
|
||||
.object({
|
||||
type: z.literal("audio"),
|
||||
/**
|
||||
* The base64-encoded audio data.
|
||||
*/
|
||||
data: z.string().base64(),
|
||||
/**
|
||||
* The MIME type of the audio. Different providers may support different audio types.
|
||||
*/
|
||||
mimeType: z.string(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
/**
|
||||
* The contents of a resource, embedded into a prompt or tool call result.
|
||||
*/
|
||||
|
|
@ -685,6 +702,7 @@ export const PromptMessageSchema = z
|
|||
content: z.union([
|
||||
TextContentSchema,
|
||||
ImageContentSchema,
|
||||
AudioContentSchema,
|
||||
EmbeddedResourceSchema,
|
||||
]),
|
||||
})
|
||||
|
|
@ -753,7 +771,7 @@ export const ListToolsResultSchema = PaginatedResultSchema.extend({
|
|||
*/
|
||||
export const CallToolResultSchema = ResultSchema.extend({
|
||||
content: z.array(
|
||||
z.union([TextContentSchema, ImageContentSchema, EmbeddedResourceSchema]),
|
||||
z.union([TextContentSchema, ImageContentSchema, AudioContentSchema, EmbeddedResourceSchema]),
|
||||
),
|
||||
isError: z.boolean().default(false).optional(),
|
||||
});
|
||||
|
|
@ -877,7 +895,7 @@ export const ModelPreferencesSchema = z
|
|||
export const SamplingMessageSchema = z
|
||||
.object({
|
||||
role: z.enum(["user", "assistant"]),
|
||||
content: z.union([TextContentSchema, ImageContentSchema]),
|
||||
content: z.union([TextContentSchema, ImageContentSchema, AudioContentSchema]),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
|
|
@ -931,6 +949,7 @@ export const CreateMessageResultSchema = ResultSchema.extend({
|
|||
content: z.discriminatedUnion("type", [
|
||||
TextContentSchema,
|
||||
ImageContentSchema,
|
||||
AudioContentSchema
|
||||
]),
|
||||
});
|
||||
|
||||
|
|
@ -1195,6 +1214,7 @@ export type ListPromptsResult = Infer<typeof ListPromptsResultSchema>;
|
|||
export type GetPromptRequest = Infer<typeof GetPromptRequestSchema>;
|
||||
export type TextContent = Infer<typeof TextContentSchema>;
|
||||
export type ImageContent = Infer<typeof ImageContentSchema>;
|
||||
export type AudioContent = Infer<typeof AudioContentSchema>;
|
||||
export type EmbeddedResource = Infer<typeof EmbeddedResourceSchema>;
|
||||
export type PromptMessage = Infer<typeof PromptMessageSchema>;
|
||||
export type GetPromptResult = Infer<typeof GetPromptResultSchema>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue