mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
123 lines
3.5 KiB
TypeScript
123 lines
3.5 KiB
TypeScript
// eslint-disable no-restricted-imports
|
|
import type { Response } from "node-fetch";
|
|
import { Scope } from "@shared/types";
|
|
import { buildAdmin, buildOAuthAuthentication, buildUser } from "./factories";
|
|
|
|
let nextId = 1;
|
|
|
|
/**
|
|
* Builds a user and an OAuth access token with read/write/create scopes for
|
|
* use with the MCP test helpers.
|
|
*
|
|
* @param overrides - optional team id and role overrides.
|
|
* @returns the created user and their access token.
|
|
*/
|
|
export async function buildOAuthUser(
|
|
overrides: { teamId?: string; role?: string } = {}
|
|
) {
|
|
const user =
|
|
overrides.role === "admin"
|
|
? await buildAdmin(overrides.teamId ? { teamId: overrides.teamId } : {})
|
|
: await buildUser(overrides.teamId ? { teamId: overrides.teamId } : {});
|
|
const auth = await buildOAuthAuthentication({
|
|
user,
|
|
scope: [Scope.Read, Scope.Write, Scope.Create],
|
|
});
|
|
return { user, accessToken: auth.accessToken! };
|
|
}
|
|
|
|
/**
|
|
* Returns HTTP headers required for MCP requests with OAuth authentication.
|
|
*
|
|
* @param accessToken - the OAuth access token.
|
|
* @returns headers object for use with TestServer.post().
|
|
*/
|
|
export function mcpHeaders(accessToken: string) {
|
|
return {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
Accept: "application/json, text/event-stream",
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Builds a JSON-RPC request object for MCP.
|
|
*
|
|
* @param method - the JSON-RPC method to call (e.g. "tools/call").
|
|
* @param params - the params object for the method.
|
|
* @returns an object with the JSON-RPC body and its id.
|
|
*/
|
|
export function mcpRequest(method: string, params?: Record<string, unknown>) {
|
|
const id = nextId++;
|
|
|
|
const body = {
|
|
jsonrpc: "2.0" as const,
|
|
id,
|
|
method,
|
|
...(params !== undefined ? { params } : {}),
|
|
};
|
|
|
|
return { body, resultId: id };
|
|
}
|
|
|
|
/**
|
|
* Parses an MCP HTTP response. The transport responds with
|
|
* `text/event-stream` (SSE). This helper extracts the JSON-RPC response
|
|
* from the SSE data lines.
|
|
*
|
|
* @param res - the node-fetch Response from TestServer.
|
|
* @returns the parsed JSON-RPC result object.
|
|
*/
|
|
export async function parseMcpResponse(res: Response) {
|
|
const contentType = res.headers.get("content-type") ?? "";
|
|
const text = await res.text();
|
|
|
|
if (contentType.includes("text/event-stream")) {
|
|
for (const line of text.split("\n")) {
|
|
if (line.startsWith("data: ")) {
|
|
const data = line.slice("data: ".length).trim();
|
|
if (data) {
|
|
return JSON.parse(data) as Record<string, unknown>;
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
return JSON.parse(text) as Record<string, unknown>;
|
|
}
|
|
|
|
/**
|
|
* Shorthand to call an MCP tool via the test server. Sends a single
|
|
* JSON-RPC `tools/call` request and returns the parsed result.
|
|
*
|
|
* @param server - the TestServer instance.
|
|
* @param accessToken - the OAuth access token.
|
|
* @param toolName - the name of the tool to call.
|
|
* @param args - the arguments to pass to the tool.
|
|
* @returns the parsed tool call result.
|
|
*/
|
|
export async function callMcpTool(
|
|
server: { post: (path: string, opts: unknown) => Promise<Response> },
|
|
accessToken: string,
|
|
toolName: string,
|
|
args: Record<string, unknown> = {}
|
|
) {
|
|
const { body } = mcpRequest("tools/call", {
|
|
name: toolName,
|
|
arguments: args,
|
|
});
|
|
|
|
const res = await server.post("/mcp/", {
|
|
headers: mcpHeaders(accessToken),
|
|
body,
|
|
});
|
|
|
|
const parsed = await parseMcpResponse(res);
|
|
return parsed as
|
|
| {
|
|
result?: { content?: { text?: string }[]; isError?: boolean };
|
|
error?: unknown;
|
|
}
|
|
| undefined;
|
|
}
|