refactor(ts): make port effect native

This commit is contained in:
elpresidank 2026-06-06 10:33:10 -05:00
parent 2868ced2d3
commit b6759e75df
113 changed files with 4140 additions and 4554 deletions

View file

@ -1,22 +1,21 @@
import { describe, expect, it, vi } from "vitest";
import * as Predicate from "effect/Predicate";
import { createMcpServer } from "../server.js";
import { describe, expect, it } from "@effect/vitest";
import type { BaseApi } from "@trustgraph/client";
import { Effect, Layer, Stream } from "effect";
import * as S from "effect/Schema";
import { LanguageModel, McpServer } from "effect/unstable/ai";
import * as McpSchema from "effect/unstable/ai/McpSchema";
import { FetchHttpClient, HttpRouter } from "effect/unstable/http";
import { RpcSerialization } from "effect/unstable/rpc";
import * as RpcClient from "effect/unstable/rpc/RpcClient";
import {
makeTrustGraphMcpStdioLayer,
runStdio,
TrustGraphMcpConfig,
TrustGraphMcpToolkit,
TrustGraphMcpToolkitLive,
TrustGraphSocket,
} from "../server-effect.js";
const clientMock = vi.hoisted(() => ({
createTrustGraphSocket: vi.fn(() => ({
close: vi.fn(),
})),
}));
vi.mock("@trustgraph/client", () => ({
createTrustGraphSocket: clientMock.createTrustGraphSocket,
}));
const expectedToolNames = [
"text_completion",
"graph_rag",
@ -43,41 +42,290 @@ const expectedToolNames = [
"load_kg_core",
];
const registeredToolNames = (value: unknown): Array<string> => {
if (!Predicate.isObject(value) || !Predicate.hasProperty(value, "_registeredTools")) {
return [];
}
return Predicate.isObject(value._registeredTools)
? Object.keys(value._registeredTools)
: [];
interface FakeSocketCalls {
readonly flowIds: Array<string>;
readonly graphRag: Array<{
readonly query: string;
readonly options: unknown;
readonly collection: string | undefined;
}>;
}
interface NativeTestClientOptions {
readonly languageText?: string | undefined;
readonly graphRag?: (() => Promise<string>) | undefined;
}
const decodeJsonText = S.decodeUnknownSync(S.UnknownFromJsonString);
const makeFakeSocket = (
options: {
readonly graphRag?: (() => Promise<string>) | undefined;
} = {},
) => {
const calls: FakeSocketCalls = {
flowIds: [],
graphRag: [],
};
const socket = {
close: () => {},
flow: (flowId: string) => {
calls.flowIds.push(flowId);
return {
textCompletion: () => Promise.resolve("legacy text completion should not be used"),
graphRag: (query: string, ragOptions: unknown, collection?: string) => {
calls.graphRag.push({ query, options: ragOptions, collection });
return options.graphRag === undefined
? Promise.resolve("graph rag answer")
: options.graphRag();
},
documentRag: () => Promise.resolve("document rag answer"),
agent: (
_question: string,
_onThought: () => void,
_onObservation: () => void,
onAnswer: (chunk: string, complete: boolean) => void,
) => onAnswer("agent answer", true),
embeddings: () => Promise.resolve([[0.25, 0.75]]),
triplesQuery: () => Promise.resolve([]),
graphEmbeddingsQuery: () => Promise.resolve([]),
};
},
config: () => ({
getConfigAll: () => Promise.resolve({}),
getConfig: () => Promise.resolve({}),
putConfig: () => Promise.resolve({ ok: true }),
deleteConfig: () => Promise.resolve({ ok: true }),
getPrompts: () => Promise.resolve([]),
getPrompt: () => Promise.resolve({}),
}),
flows: () => ({
getFlows: () => Promise.resolve(["default"]),
getFlow: () => Promise.resolve({}),
startFlow: () => Promise.resolve({ ok: true }),
stopFlow: () => Promise.resolve({ ok: true }),
}),
librarian: () => ({
getDocuments: () => Promise.resolve([]),
loadDocument: () => Promise.resolve({ ok: true }),
removeDocument: () => Promise.resolve({ ok: true }),
}),
knowledge: () => ({
getKnowledgeCores: () => Promise.resolve([]),
deleteKgCore: () => Promise.resolve({ ok: true }),
loadKgCore: () => Promise.resolve({ ok: true }),
}),
} as unknown as BaseApi;
return { socket, calls };
};
const makeLanguageModelLayer = (text: string) =>
Layer.effect(
LanguageModel.LanguageModel,
LanguageModel.make({
generateText: () => Effect.succeed([{ type: "text", text }]),
streamText: () => Stream.empty,
}),
);
const testConfig = TrustGraphMcpConfig.of({
gatewayUrl: "ws://localhost:8088/api/v1/rpc",
user: "mcp-test",
token: undefined,
flowId: "default",
name: "trustgraph",
version: "0.1.0-test",
mcpPath: "/mcp",
openAiModel: "test-model",
openAiApiKey: undefined,
port: 3000,
});
const makeNativeTestClient = (
options: NativeTestClientOptions = {},
) =>
makeNativeTestClientEffect(options);
const makeNativeTestClientEffect = Effect.fn("makeNativeTestClient")(function*(
options: NativeTestClientOptions,
) {
const { socket, calls } = makeFakeSocket({ graphRag: options.graphRag });
const serverLayer = McpServer.toolkit(TrustGraphMcpToolkit).pipe(
Layer.provide(TrustGraphMcpToolkitLive),
Layer.provide(makeLanguageModelLayer(options.languageText ?? "direct ai answer")),
Layer.provide(Layer.succeed(TrustGraphSocket, TrustGraphSocket.of(socket))),
Layer.provide(Layer.succeed(TrustGraphMcpConfig, testConfig)),
Layer.provide(McpServer.layerHttp({
name: "trustgraph",
version: "0.1.0-test",
path: "/mcp",
})),
);
const { handler, dispose } = HttpRouter.toWebHandler(serverLayer, { disableLogger: true });
yield* Effect.addFinalizer(() => Effect.promise(() => dispose()));
let sessionId: string | null = null;
const customFetch = Object.assign(
(input: RequestInfo | URL, init?: RequestInit) => {
const request = input instanceof Request ? input : new Request(input, init);
if (sessionId !== null) {
request.headers.set("Mcp-Session-Id", sessionId);
}
return handler(request).then((response) => {
sessionId = response.headers.get("Mcp-Session-Id");
return response;
});
},
{ preconnect: fetch.preconnect },
) as typeof fetch;
const clientLayer = RpcClient.layerProtocolHttp({ url: "http://localhost/mcp" }).pipe(
Layer.provideMerge([FetchHttpClient.layer, RpcSerialization.layerJsonRpc()]),
Layer.provide(Layer.succeed(FetchHttpClient.Fetch, customFetch)),
);
const client = yield* RpcClient.make(McpSchema.ClientRpcs).pipe(
// @effect-diagnostics-next-line strictEffectProvide:off
Effect.provide(clientLayer),
);
yield* client.initialize({
protocolVersion: "9999-01-01",
capabilities: {},
clientInfo: {
name: "trustgraph-mcp-test-client",
version: "0.1.0-test",
},
});
return { client, calls };
});
const textContent = (result: McpSchema.CallToolResult): string => {
const [content] = result.content;
expect(content?.type).toBe("text");
return "text" in content! ? content.text : "";
};
describe("Effect MCP server", () => {
it("keeps the canonical Effect toolkit names stable", () => {
expect(Object.keys(TrustGraphMcpToolkit.tools)).toEqual(expectedToolNames);
});
it.effect(
"keeps the canonical Effect toolkit names stable",
Effect.fnUntraced(function*() {
expect(Object.keys(TrustGraphMcpToolkit.tools)).toEqual(expectedToolNames);
}),
);
it("keeps legacy SDK stdio tools aligned with the Effect toolkit", () => {
const { server, socket } = createMcpServer({
gatewayUrl: "ws://localhost:8088/api/v1/rpc",
user: "mcp-test",
flowId: "default",
});
it.effect(
"exposes an Effect stdio layer and process entrypoint",
Effect.fnUntraced(function*() {
expect(
makeTrustGraphMcpStdioLayer({
gatewayUrl: "ws://localhost:8088/api/v1/rpc",
user: "mcp-test",
flowId: "default",
openAiApiKey: "test-key",
}),
).toBeDefined();
expect(registeredToolNames(server)).toEqual(Object.keys(TrustGraphMcpToolkit.tools));
expect(socket.close).toEqual(expect.any(Function));
});
expect(runStdio).toEqual(expect.any(Function));
}),
);
it("exposes an Effect stdio layer and process entrypoint", () => {
expect(
makeTrustGraphMcpStdioLayer({
gatewayUrl: "ws://localhost:8088/api/v1/rpc",
user: "mcp-test",
flowId: "default",
openAiApiKey: "test-key",
}),
).toBeDefined();
it.effect(
"lists native MCP tools through the protocol bridge",
Effect.fnUntraced(function*() {
yield* Effect.scoped(Effect.gen(function*() {
const { client } = yield* makeNativeTestClient();
expect(runStdio).toEqual(expect.any(Function));
});
const result = yield* client["tools/list"]({});
expect(result.tools.map((tool) => tool.name)).toEqual(expectedToolNames);
expect(result.tools.find((tool) => tool.name === "graph_rag")?.annotations).toMatchObject({
title: "Graph RAG",
readOnlyHint: true,
destructiveHint: false,
openWorldHint: true,
});
}));
}),
);
it.effect(
"calls text_completion through the direct Effect language model",
Effect.fnUntraced(function*() {
yield* Effect.scoped(Effect.gen(function*() {
const { client, calls } = yield* makeNativeTestClient({
languageText: "direct model response",
});
const result = yield* client["tools/call"]({
name: "text_completion",
arguments: {
system: "You are concise.",
prompt: "Say hello.",
},
});
expect(result.isError).toBe(false);
expect(result.structuredContent).toEqual({ text: "direct model response" });
expect(decodeJsonText(textContent(result))).toEqual({ text: "direct model response" });
expect(calls.flowIds).toEqual([]);
}));
}),
);
it.effect(
"calls gateway-backed tools through the native MCP bridge",
Effect.fnUntraced(function*() {
yield* Effect.scoped(Effect.gen(function*() {
const { client, calls } = yield* makeNativeTestClient();
const result = yield* client["tools/call"]({
name: "graph_rag",
arguments: {
query: "Who knows Alice?",
entity_limit: 4,
triple_limit: 8,
collection: "qa",
},
});
expect(result.isError).toBe(false);
expect(result.structuredContent).toEqual({ text: "graph rag answer" });
expect(calls.graphRag).toEqual([
{
query: "Who knows Alice?",
options: { entityLimit: 4, tripleLimit: 8 },
collection: "qa",
},
]);
}));
}),
);
it.effect(
"returns JSON-safe structured failures for expected tool errors",
Effect.fnUntraced(function*() {
yield* Effect.scoped(Effect.gen(function*() {
const { client } = yield* makeNativeTestClient({
graphRag: () => Promise.reject(new Error("gateway unavailable")),
});
const result = yield* client["tools/call"]({
name: "graph_rag",
arguments: {
query: "Will this fail?",
},
});
expect(result.structuredContent).toEqual({
_tag: "GraphRagError",
message: "gateway unavailable",
});
expect(result.structuredContent).not.toHaveProperty("cause");
expect(decodeJsonText(textContent(result))).toEqual(result.structuredContent);
}));
}),
);
});

View file

@ -1,2 +1,2 @@
export { createMcpServer, run } from "./server.js";
export { runStdio as run } from "./server-effect.js";
export * from "./server-effect.js";

View file

@ -113,10 +113,6 @@ const TrustGraphJsonPayload = S.Json.annotateKey({
description: "JSON-safe payload returned by the TrustGraph gateway",
})
const ToolErrorCause = S.DefectWithStack.annotateKey({
description: "Original exception, schema decoding failure, or gateway error that caused the tool call to fail",
})
const ToolErrorMessage = S.String.annotateKey({
description: "Concise human-readable error message suitable for explaining the failure to a user",
})
@ -141,7 +137,6 @@ export class TextCompletionSuccess extends S.Class<TextCompletionSuccess>("TextC
export class TextCompletionError extends S.TaggedErrorClass<TextCompletionError>()(
"TextCompletionError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -165,6 +160,7 @@ export const TextCompletionTool = annotateTool(
parameters: TextCompletionParameters,
success: TextCompletionSuccess,
failure: TextCompletionError,
failureMode: "return",
}),
{
title: "Text Completion",
@ -185,7 +181,6 @@ export class GraphRagSuccess extends S.Class<GraphRagSuccess>("GraphRagSuccess")
export class GraphRagError extends S.TaggedErrorClass<GraphRagError>()(
"GraphRagError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -215,6 +210,7 @@ export const GraphRagTool = annotateTool(
parameters: GraphRagParameters,
success: GraphRagSuccess,
failure: GraphRagError,
failureMode: "return",
}),
{
title: "Graph RAG",
@ -235,7 +231,6 @@ export class DocumentRagSuccess extends S.Class<DocumentRagSuccess>("DocumentRag
export class DocumentRagError extends S.TaggedErrorClass<DocumentRagError>()(
"DocumentRagError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -263,6 +258,7 @@ export const DocumentRagTool = annotateTool(
parameters: DocumentRagParameters,
success: DocumentRagSuccess,
failure: DocumentRagError,
failureMode: "return",
}),
{
title: "Document RAG",
@ -283,7 +279,6 @@ export class AgentSuccess extends S.Class<AgentSuccess>("AgentSuccess")(
export class AgentError extends S.TaggedErrorClass<AgentError>()(
"AgentError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -303,6 +298,7 @@ export const AgentTool = annotateTool(
parameters: AgentParameters,
success: AgentSuccess,
failure: AgentError,
failureMode: "return",
description: "Ask the TrustGraph agent a question"
}),
{
@ -326,7 +322,6 @@ export class EmbeddingsSuccess extends S.Class<EmbeddingsSuccess>("EmbeddingsSuc
export class EmbeddingsError extends S.TaggedErrorClass<EmbeddingsError>()(
"EmbeddingsError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -346,6 +341,7 @@ export const EmbeddingsTool = annotateTool(
parameters: EmbeddingsParameters,
success: EmbeddingsSuccess,
failure: EmbeddingsError,
failureMode: "return",
description: "Generate text embeddings"
}),
{
@ -369,7 +365,6 @@ export class TriplesQuerySuccess extends S.Class<TriplesQuerySuccess>("TriplesQu
export class TriplesQueryError extends S.TaggedErrorClass<TriplesQueryError>()(
"TriplesQueryError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -402,6 +397,7 @@ export const TriplesQueryTool = annotateTool(
parameters: TriplesQueryParameters,
success: TriplesQuerySuccess,
failure: TriplesQueryError,
failureMode: "return",
description: "Query the knowledge graph for triples matching a pattern"
}),
{
@ -434,7 +430,6 @@ export class GraphEmbeddingsQuerySuccess extends S.Class<GraphEmbeddingsQuerySuc
export class GraphEmbeddingsQueryError extends S.TaggedErrorClass<GraphEmbeddingsQueryError>()(
"GraphEmbeddingsQueryError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -461,6 +456,7 @@ export const GraphEmbeddingsQueryTool = annotateTool(
parameters: GraphEmbeddingsQueryParameters,
success: GraphEmbeddingsQuerySuccess,
failure: GraphEmbeddingsQueryError,
failureMode: "return",
description: "Find entities similar to a text query using vector embeddings"
}),
{
@ -482,7 +478,6 @@ export class GetConfigAllSuccess extends S.Class<GetConfigAllSuccess>("GetConfig
export class GetConfigAllError extends S.TaggedErrorClass<GetConfigAllError>()(
"GetConfigAllError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -498,6 +493,7 @@ export const GetConfigAllTool = annotateTool(
parameters: GetConfigAllParameters,
success: GetConfigAllSuccess,
failure: GetConfigAllError,
failureMode: "return",
description: "Get all configuration values"
}),
{
@ -520,7 +516,6 @@ export class GetConfigSuccess extends S.Class<GetConfigSuccess>("GetConfigSucces
export class GetConfigError extends S.TaggedErrorClass<GetConfigError>()(
"GetConfigError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -549,6 +544,7 @@ export const GetConfigTool = annotateTool(
parameters: GetConfigParameters,
success: GetConfigSuccess,
failure: GetConfigError,
failureMode: "return",
description: "Get specific configuration values"
}),
{
@ -570,7 +566,6 @@ export class PutConfigSuccess extends S.Class<PutConfigSuccess>("PutConfigSucces
export class PutConfigError extends S.TaggedErrorClass<PutConfigError>()(
"PutConfigError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -602,6 +597,7 @@ export const PutConfigTool = annotateTool(
parameters: PutConfigParameters,
success: PutConfigSuccess,
failure: PutConfigError,
failureMode: "return",
description: "Set configuration values"
}),
{
@ -623,7 +619,6 @@ export class DeleteConfigSuccess extends S.Class<DeleteConfigSuccess>("DeleteCon
export class DeleteConfigError extends S.TaggedErrorClass<DeleteConfigError>()(
"DeleteConfigError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -646,6 +641,7 @@ export const DeleteConfigTool = annotateTool(
parameters: DeleteConfigParameters,
success: DeleteConfigSuccess,
failure: DeleteConfigError,
failureMode: "return",
description: "Delete a configuration entry"
}),
{
@ -667,7 +663,6 @@ export class GetFlowSuccess extends S.Class<GetFlowSuccess>("GetFlowSuccess")(
export class GetFlowError extends S.TaggedErrorClass<GetFlowError>()(
"GetFlowError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -687,6 +682,7 @@ export const GetFlowTool = annotateTool(
parameters: GetFlowParameters,
success: GetFlowSuccess,
failure: GetFlowError,
failureMode: "return",
description: "Get a specific flow definition"
}),
{
@ -710,7 +706,6 @@ export class GetFlowsSuccess extends S.Class<GetFlowsSuccess>("GetFlowsSuccess")
export class GetFlowsError extends S.TaggedErrorClass<GetFlowsError>()(
"GetFlowsError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -726,6 +721,7 @@ export const GetFlowsTool = annotateTool(
parameters: GetFlowsParameters,
success: GetFlowsSuccess,
failure: GetFlowsError,
failureMode: "return",
description: "List all available flows"
}),
{
@ -747,7 +743,6 @@ export class StartFlowSuccess extends S.Class<StartFlowSuccess>("StartFlowSucces
export class StartFlowError extends S.TaggedErrorClass<StartFlowError>()(
"StartFlowError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -776,6 +771,7 @@ export const StartFlowTool = annotateTool(
parameters: StartFlowParameters,
success: StartFlowSuccess,
failure: StartFlowError,
failureMode: "return",
description: "Start a flow instance"
}),
{
@ -797,7 +793,6 @@ export class StopFlowSuccess extends S.Class<StopFlowSuccess>("StopFlowSuccess")
export class StopFlowError extends S.TaggedErrorClass<StopFlowError>()(
"StopFlowError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -817,6 +812,7 @@ export const StopFlowTool = annotateTool(
parameters: StopFlowParameters,
success: StopFlowSuccess,
failure: StopFlowError,
failureMode: "return",
description: "Stop a running flow"
}),
{
@ -840,7 +836,6 @@ export class GetDocumentsSuccess extends S.Class<GetDocumentsSuccess>("GetDocume
export class GetDocumentsError extends S.TaggedErrorClass<GetDocumentsError>()(
"GetDocumentsError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -856,6 +851,7 @@ export const GetDocumentsTool = annotateTool(
parameters: GetDocumentsParameters,
success: GetDocumentsSuccess,
failure: GetDocumentsError,
failureMode: "return",
description: "List all documents in the library"
}),
{
@ -877,7 +873,6 @@ export class LoadDocumentSuccess extends S.Class<LoadDocumentSuccess>("LoadDocum
export class LoadDocumentError extends S.TaggedErrorClass<LoadDocumentError>()(
"LoadDocumentError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -912,6 +907,7 @@ export const LoadDocumentTool = annotateTool(
parameters: LoadDocumentParameters,
success: LoadDocumentSuccess,
failure: LoadDocumentError,
failureMode: "return",
description: "Upload a document to the library"
}),
{
@ -933,7 +929,6 @@ export class RemoveDocumentSuccess extends S.Class<RemoveDocumentSuccess>("Remov
export class RemoveDocumentError extends S.TaggedErrorClass<RemoveDocumentError>()(
"RemoveDocumentError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -956,6 +951,7 @@ export const RemoveDocumentTool = annotateTool(
parameters: RemoveDocumentParameters,
success: RemoveDocumentSuccess,
failure: RemoveDocumentError,
failureMode: "return",
description: "Remove a document from the library"
}),
{
@ -979,7 +975,6 @@ export class GetPromptsSuccess extends S.Class<GetPromptsSuccess>("GetPromptsSuc
export class GetPromptsError extends S.TaggedErrorClass<GetPromptsError>()(
"GetPromptsError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -995,6 +990,7 @@ export const GetPromptsTool = annotateTool(
parameters: GetPromptsParameters,
success: GetPromptsSuccess,
failure: GetPromptsError,
failureMode: "return",
description: "List available prompt templates"
}),
{
@ -1016,7 +1012,6 @@ export class GetPromptSuccess extends S.Class<GetPromptSuccess>("GetPromptSucces
export class GetPromptError extends S.TaggedErrorClass<GetPromptError>()(
"GetPromptError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -1036,6 +1031,7 @@ export const GetPromptTool = annotateTool(
parameters: GetPromptParameters,
success: GetPromptSuccess,
failure: GetPromptError,
failureMode: "return",
description: "Get a specific prompt template"
}),
{
@ -1059,7 +1055,6 @@ export class GetKnowledgeCoresSuccess extends S.Class<GetKnowledgeCoresSuccess>(
export class GetKnowledgeCoresError extends S.TaggedErrorClass<GetKnowledgeCoresError>()(
"GetKnowledgeCoresError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -1075,6 +1070,7 @@ export const GetKnowledgeCoresTool = annotateTool(
parameters: GetKnowledgeCoresParameters,
success: GetKnowledgeCoresSuccess,
failure: GetKnowledgeCoresError,
failureMode: "return",
description: "List available knowledge graph cores"
}),
{
@ -1096,7 +1092,6 @@ export class DeleteKgCoreSuccess extends S.Class<DeleteKgCoreSuccess>("DeleteKgC
export class DeleteKgCoreError extends S.TaggedErrorClass<DeleteKgCoreError>()(
"DeleteKgCoreError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -1119,6 +1114,7 @@ export const DeleteKgCoreTool = annotateTool(
parameters: DeleteKgCoreParameters,
success: DeleteKgCoreSuccess,
failure: DeleteKgCoreError,
failureMode: "return",
description: "Delete a knowledge graph core"
}),
{
@ -1140,7 +1136,6 @@ export class LoadKgCoreSuccess extends S.Class<LoadKgCoreSuccess>("LoadKgCoreSuc
export class LoadKgCoreError extends S.TaggedErrorClass<LoadKgCoreError>()(
"LoadKgCoreError",
{
cause: ToolErrorCause,
message: ToolErrorMessage,
}
) {
@ -1166,6 +1161,7 @@ export const LoadKgCoreTool = annotateTool(
parameters: LoadKgCoreParameters,
success: LoadKgCoreSuccess,
failure: LoadKgCoreError,
failureMode: "return",
description: "Load a knowledge graph core"
}),
{
@ -1384,7 +1380,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
},
collection,
),
catch: (cause) => GraphRagError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GraphRagError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.map((text) => GraphRagSuccess.make({text})),
),
@ -1392,7 +1388,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
document_rag: ({query, doc_limit, collection}) =>
Effect.tryPromise({
try: () => socket.flow(config.flowId).documentRag(query, doc_limit, collection),
catch: (cause) => DocumentRagError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => DocumentRagError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.map((text) => DocumentRagSuccess.make({text})),
),
@ -1410,14 +1406,14 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
resume(Effect.succeed(AgentSuccess.make({text: fullAnswer})))
}
},
(cause) => resume(Effect.fail(AgentError.make({cause, message: toErrorMessage(cause)}))),
(cause) => resume(Effect.fail(AgentError.make({message: toErrorMessage(cause)}))),
)
}),
embeddings: ({text}) =>
Effect.tryPromise({
try: () => socket.flow(config.flowId).embeddings([...text]),
catch: (cause) => EmbeddingsError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => EmbeddingsError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.map((vectors) => EmbeddingsSuccess.make({vectors})),
),
@ -1432,7 +1428,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
limit,
collection,
),
catch: (cause) => TriplesQueryError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => TriplesQueryError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.map((triples) => TriplesQuerySuccess.make({triples})),
),
@ -1440,7 +1436,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
graph_embeddings_query: ({query, limit, collection}) =>
Effect.tryPromise({
try: () => socket.flow(config.flowId).embeddings([query]),
catch: (cause) => GraphEmbeddingsQueryError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GraphEmbeddingsQueryError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((vectors) =>
Effect.tryPromise({
@ -1449,7 +1445,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
limit ?? 10,
collection,
),
catch: (cause) => GraphEmbeddingsQueryError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GraphEmbeddingsQueryError.make({message: toErrorMessage(cause)}),
})
),
Effect.map((entities) => GraphEmbeddingsQuerySuccess.make({entities})),
@ -1458,12 +1454,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_config_all: () =>
Effect.tryPromise({
try: () => socket.config().getConfigAll(),
catch: (cause) => GetConfigAllError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetConfigAllError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => GetConfigAllError.make({cause, message: toErrorMessage(cause)}),
(cause) => GetConfigAllError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((config) => GetConfigAllSuccess.make({config})),
)
@ -1473,12 +1469,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_config: ({keys}) =>
Effect.tryPromise({
try: () => socket.config().getConfig(keys.map(({type, key}) => ({type, key}))),
catch: (cause) => GetConfigError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetConfigError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => GetConfigError.make({cause, message: toErrorMessage(cause)}),
(cause) => GetConfigError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((config) => GetConfigSuccess.make({config})),
)
@ -1488,12 +1484,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
put_config: ({values}) =>
Effect.tryPromise({
try: () => socket.config().putConfig(values.map(({type, key, value}) => ({type, key, value}))),
catch: (cause) => PutConfigError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => PutConfigError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => PutConfigError.make({cause, message: toErrorMessage(cause)}),
(cause) => PutConfigError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => PutConfigSuccess.make({response})),
)
@ -1503,12 +1499,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
delete_config: ({type, key}) =>
Effect.tryPromise({
try: () => socket.config().deleteConfig({type, key}),
catch: (cause) => DeleteConfigError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => DeleteConfigError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => DeleteConfigError.make({cause, message: toErrorMessage(cause)}),
(cause) => DeleteConfigError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => DeleteConfigSuccess.make({response})),
)
@ -1518,7 +1514,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_flows: () =>
Effect.tryPromise({
try: () => socket.flows().getFlows(),
catch: (cause) => GetFlowsError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetFlowsError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.map((flow_ids) => GetFlowsSuccess.make({flow_ids})),
),
@ -1526,12 +1522,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_flow: ({flow_id}) =>
Effect.tryPromise({
try: () => socket.flows().getFlow(flow_id),
catch: (cause) => GetFlowError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetFlowError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => GetFlowError.make({cause, message: toErrorMessage(cause)}),
(cause) => GetFlowError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((flow) => GetFlowSuccess.make({flow})),
)
@ -1547,12 +1543,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
description,
parameters === undefined ? undefined : {...parameters},
),
catch: (cause) => StartFlowError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => StartFlowError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => StartFlowError.make({cause, message: toErrorMessage(cause)}),
(cause) => StartFlowError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => StartFlowSuccess.make({response})),
)
@ -1562,12 +1558,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
stop_flow: ({flow_id}) =>
Effect.tryPromise({
try: () => socket.flows().stopFlow(flow_id),
catch: (cause) => StopFlowError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => StopFlowError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => StopFlowError.make({cause, message: toErrorMessage(cause)}),
(cause) => StopFlowError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => StopFlowSuccess.make({response})),
)
@ -1577,12 +1573,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_documents: () =>
Effect.tryPromise({
try: () => socket.librarian().getDocuments(),
catch: (cause) => GetDocumentsError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetDocumentsError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonArrayOrFail(
value,
(cause) => GetDocumentsError.make({cause, message: toErrorMessage(cause)}),
(cause) => GetDocumentsError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((documents) => GetDocumentsSuccess.make({documents})),
)
@ -1600,12 +1596,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
tags === undefined ? [] : [...tags],
id,
),
catch: (cause) => LoadDocumentError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => LoadDocumentError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => LoadDocumentError.make({cause, message: toErrorMessage(cause)}),
(cause) => LoadDocumentError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => LoadDocumentSuccess.make({response})),
)
@ -1615,12 +1611,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
remove_document: ({id, collection}) =>
Effect.tryPromise({
try: () => socket.librarian().removeDocument(id, collection),
catch: (cause) => RemoveDocumentError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => RemoveDocumentError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => RemoveDocumentError.make({cause, message: toErrorMessage(cause)}),
(cause) => RemoveDocumentError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => RemoveDocumentSuccess.make({response})),
)
@ -1630,7 +1626,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_prompts: () =>
Effect.tryPromise({
try: () => socket.config().getPrompts(),
catch: (cause) => GetPromptsError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetPromptsError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.map((prompts) => GetPromptsSuccess.make({prompts})),
),
@ -1638,12 +1634,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_prompt: ({id}) =>
Effect.tryPromise({
try: () => socket.config().getPrompt(id),
catch: (cause) => GetPromptError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetPromptError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => GetPromptError.make({cause, message: toErrorMessage(cause)}),
(cause) => GetPromptError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((prompt) => GetPromptSuccess.make({prompt})),
)
@ -1653,7 +1649,7 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
get_knowledge_cores: () =>
Effect.tryPromise({
try: () => socket.knowledge().getKnowledgeCores(),
catch: (cause) => GetKnowledgeCoresError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => GetKnowledgeCoresError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.map((ids) => GetKnowledgeCoresSuccess.make({ids})),
),
@ -1661,12 +1657,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
delete_kg_core: ({id, collection}) =>
Effect.tryPromise({
try: () => socket.knowledge().deleteKgCore(id, collection),
catch: (cause) => DeleteKgCoreError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => DeleteKgCoreError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => DeleteKgCoreError.make({cause, message: toErrorMessage(cause)}),
(cause) => DeleteKgCoreError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => DeleteKgCoreSuccess.make({response})),
)
@ -1676,12 +1672,12 @@ export const TrustGraphMcpToolkitLive = TrustGraphMcpToolkit.toLayer(
load_kg_core: ({id, flow, collection}) =>
Effect.tryPromise({
try: () => socket.knowledge().loadKgCore(id, flow, collection),
catch: (cause) => LoadKgCoreError.make({cause, message: toErrorMessage(cause)}),
catch: (cause) => LoadKgCoreError.make({message: toErrorMessage(cause)}),
}).pipe(
Effect.flatMap((value) =>
decodeJsonOrFail(
value,
(cause) => LoadKgCoreError.make({cause, message: toErrorMessage(cause)}),
(cause) => LoadKgCoreError.make({message: toErrorMessage(cause)}),
).pipe(
Effect.map((response) => LoadKgCoreSuccess.make({response})),
)

View file

@ -1,442 +0,0 @@
/**
* TrustGraph MCP stdio compatibility server.
*
* This keeps the original @modelcontextprotocol/sdk entry points available,
* while moving gateway calls, callback bridging, JSON encoding, and config
* reads behind Effect values.
*/
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
import {NodeRuntime} from "@effect/platform-node";
import {createTrustGraphSocket, type BaseApi, type Term} from "@trustgraph/client";
import {Effect, Layer, ManagedRuntime} from "effect";
import * as Predicate from "effect/Predicate";
import * as S from "effect/Schema";
import * as z from "zod";
import {loadTrustGraphMcpConfig} from "./server-effect.js";
interface ToolTextContent {
readonly type: "text"
readonly text: string
}
interface ToolTextResult extends Record<string, unknown> {
readonly content: Array<ToolTextContent>
}
class StdioMcpError extends S.TaggedErrorClass<StdioMcpError>()(
"StdioMcpError",
{
cause: S.DefectWithStack,
message: S.String,
},
) {
}
const encodeJsonText = S.encodeUnknownEffect(S.UnknownFromJsonString);
const toErrorMessage = (cause: unknown): string => {
if (Predicate.isError(cause) && cause.message.length > 0) {
return cause.message;
}
if (Predicate.isString(cause) && cause.length > 0) {
return cause;
}
if (Predicate.isObject(cause) && Predicate.hasProperty(cause, "message") && Predicate.isString(cause.message) && cause.message.length > 0) {
return cause.message;
}
return "TrustGraph MCP stdio operation failed";
};
const stdioMcpError = (cause: unknown) =>
StdioMcpError.make({cause, message: toErrorMessage(cause)});
const textResult = (text: string): ToolTextResult => ({
content: [{type: "text", text}],
});
const gatewayRequest = <A>(request: () => Promise<A>) =>
Effect.tryPromise({
try: request,
catch: stdioMcpError,
});
const jsonText = (value: unknown) =>
encodeJsonText(value).pipe(
Effect.mapError(stdioMcpError),
);
const runTextTool = (effect: Effect.Effect<string, StdioMcpError>) =>
Effect.runPromise(effect.pipe(Effect.map(textResult)));
const runJsonTool = (effect: Effect.Effect<unknown, StdioMcpError>) =>
Effect.runPromise(effect.pipe(Effect.flatMap(jsonText), Effect.map(textResult)));
export function createMcpServer(config: {
gatewayUrl: string;
user?: string;
token?: string;
flowId?: string;
}) {
const server = new McpServer({
name: "trustgraph",
version: "0.1.0",
});
const user = config.user ?? "mcp";
const socket: BaseApi = createTrustGraphSocket(
user,
config.token,
config.gatewayUrl,
);
const flowId = config.flowId ?? "default";
// ===================== Flow-scoped tools =====================
server.tool(
"text_completion",
"Run a text completion using the configured LLM",
{
system: z.string().describe("System prompt"),
prompt: z.string().describe("User prompt"),
},
({system, prompt}) =>
runTextTool(gatewayRequest(() => socket.flow(flowId).textCompletion(system, prompt))),
);
server.tool(
"graph_rag",
"Query the knowledge graph using RAG",
{
query: z.string().describe("Natural language query"),
entity_limit: z.number().optional().describe("Max entities to retrieve"),
triple_limit: z.number().optional().describe("Max triples per entity"),
collection: z.string().optional().describe("Collection name"),
},
({query, entity_limit, triple_limit, collection}) =>
runTextTool(
gatewayRequest(() =>
socket.flow(flowId).graphRag(
query,
{
...(entity_limit !== undefined ? {entityLimit: entity_limit} : {}),
...(triple_limit !== undefined ? {tripleLimit: triple_limit} : {}),
},
collection,
)
),
),
);
server.tool(
"document_rag",
"Query documents using RAG",
{
query: z.string().describe("Natural language query"),
doc_limit: z.number().optional().describe("Max documents to retrieve"),
collection: z.string().optional().describe("Collection name"),
},
({query, doc_limit, collection}) =>
runTextTool(gatewayRequest(() => socket.flow(flowId).documentRag(query, doc_limit, collection))),
);
server.tool(
"agent",
"Ask the TrustGraph agent a question",
{
question: z.string().describe("Question for the agent"),
},
({question}) =>
runTextTool(
Effect.callback<string, StdioMcpError>((resume) => {
let fullAnswer = "";
socket.flow(flowId).agent(
question,
() => {},
() => {},
(chunk, complete) => {
fullAnswer += chunk;
if (complete) {
resume(Effect.succeed(fullAnswer));
}
},
(cause) => resume(Effect.fail(stdioMcpError(cause))),
);
}),
),
);
server.tool(
"embeddings",
"Generate text embeddings",
{
text: z.array(z.string()).describe("Texts to embed"),
},
({text}) => runJsonTool(gatewayRequest(() => socket.flow(flowId).embeddings(text))),
);
server.tool(
"triples_query",
"Query the knowledge graph for triples matching a pattern",
{
s: z.string().optional().describe("Subject IRI"),
p: z.string().optional().describe("Predicate IRI"),
o: z.string().optional().describe("Object IRI or literal"),
limit: z.number().optional().describe("Max results"),
collection: z.string().optional().describe("Collection name"),
},
({s, p, o, limit, collection}) => {
const sTerm: Term | undefined = s !== undefined && s.length > 0 ? {t: "i", i: s} : undefined;
const pTerm: Term | undefined = p !== undefined && p.length > 0 ? {t: "i", i: p} : undefined;
const oTerm: Term | undefined = o !== undefined && o.length > 0 ? {t: "i", i: o} : undefined;
return runJsonTool(
gatewayRequest(() => socket.flow(flowId).triplesQuery(sTerm, pTerm, oTerm, limit, collection)),
);
},
);
server.tool(
"graph_embeddings_query",
"Find entities similar to a text query using vector embeddings",
{
query: z.string().describe("Text to find similar entities for"),
limit: z.number().optional().describe("Max results"),
collection: z.string().optional().describe("Collection name"),
},
({query, limit, collection}) =>
runJsonTool(
gatewayRequest(() => socket.flow(flowId).embeddings([query])).pipe(
Effect.flatMap((vectors) =>
gatewayRequest(() =>
socket.flow(flowId).graphEmbeddingsQuery(
vectors[0] ?? [],
limit ?? 10,
collection,
)
)
),
),
),
);
// ===================== Config tools =====================
server.tool(
"get_config_all",
"Get all configuration values",
{},
() => runJsonTool(gatewayRequest(() => socket.config().getConfigAll())),
);
server.tool(
"get_config",
"Get specific configuration values",
{
keys: z.array(
z.object({
type: z.string().describe("Config type"),
key: z.string().describe("Config key"),
}),
).describe("Config keys to retrieve"),
},
({keys}) => runJsonTool(gatewayRequest(() => socket.config().getConfig(keys))),
);
server.tool(
"put_config",
"Set configuration values",
{
values: z.array(
z.object({
type: z.string().describe("Config type"),
key: z.string().describe("Config key"),
value: z.string().describe("Config value (JSON-encoded)"),
}),
).describe("Key-value entries to set"),
},
({values}) => runJsonTool(gatewayRequest(() => socket.config().putConfig(values))),
);
server.tool(
"delete_config",
"Delete a configuration entry",
{
type: z.string().describe("Config type"),
key: z.string().describe("Config key"),
},
({type, key}) => runJsonTool(gatewayRequest(() => socket.config().deleteConfig({type, key}))),
);
// ===================== Flow management tools =====================
server.tool(
"get_flows",
"List all available flows",
{},
() => runJsonTool(gatewayRequest(() => socket.flows().getFlows())),
);
server.tool(
"get_flow",
"Get a specific flow definition",
{
flow_id: z.string().describe("Flow ID to retrieve"),
},
({flow_id}) => runJsonTool(gatewayRequest(() => socket.flows().getFlow(flow_id))),
);
server.tool(
"start_flow",
"Start a flow instance",
{
flow_id: z.string().describe("Flow ID"),
blueprint_name: z.string().describe("Blueprint name"),
description: z.string().describe("Flow description"),
parameters: z.record(z.unknown()).optional().describe("Optional flow parameters"),
},
({flow_id, blueprint_name, description, parameters}) =>
runJsonTool(
gatewayRequest(() => socket.flows().startFlow(flow_id, blueprint_name, description, parameters)),
),
);
server.tool(
"stop_flow",
"Stop a running flow",
{
flow_id: z.string().describe("Flow ID to stop"),
},
({flow_id}) => runJsonTool(gatewayRequest(() => socket.flows().stopFlow(flow_id))),
);
// ===================== Library (document) tools =====================
server.tool(
"get_documents",
"List all documents in the library",
{},
() => runJsonTool(gatewayRequest(() => socket.librarian().getDocuments())),
);
server.tool(
"load_document",
"Upload a document to the library",
{
document: z.string().describe("Base64-encoded document content"),
mime_type: z.string().describe("Document MIME type"),
title: z.string().describe("Document title"),
comments: z.string().optional().describe("Additional comments"),
tags: z.array(z.string()).optional().describe("Document tags"),
id: z.string().optional().describe("Optional document ID"),
},
({document, mime_type, title, comments, tags, id}) =>
runJsonTool(
gatewayRequest(() =>
socket.librarian().loadDocument(
document,
mime_type,
title,
comments ?? "",
tags ?? [],
id,
)
),
),
);
server.tool(
"remove_document",
"Remove a document from the library",
{
id: z.string().describe("Document ID to remove"),
collection: z.string().optional().describe("Collection name"),
},
({id, collection}) => runJsonTool(gatewayRequest(() => socket.librarian().removeDocument(id, collection))),
);
// ===================== Prompt tools =====================
server.tool(
"get_prompts",
"List available prompt templates",
{},
() => runJsonTool(gatewayRequest(() => socket.config().getPrompts())),
);
server.tool(
"get_prompt",
"Get a specific prompt template",
{
id: z.string().describe("Prompt template ID"),
},
({id}) => runJsonTool(gatewayRequest(() => socket.config().getPrompt(id))),
);
// ===================== Knowledge core tools =====================
server.tool(
"get_knowledge_cores",
"List available knowledge graph cores",
{},
() => runJsonTool(gatewayRequest(() => socket.knowledge().getKnowledgeCores())),
);
server.tool(
"delete_kg_core",
"Delete a knowledge graph core",
{
id: z.string().describe("Knowledge core ID"),
collection: z.string().optional().describe("Collection name"),
},
({id, collection}) => runJsonTool(gatewayRequest(() => socket.knowledge().deleteKgCore(id, collection))),
);
server.tool(
"load_kg_core",
"Load a knowledge graph core",
{
id: z.string().describe("Knowledge core ID"),
flow: z.string().describe("Flow to use for loading"),
collection: z.string().optional().describe("Collection name"),
},
({id, flow, collection}) => runJsonTool(gatewayRequest(() => socket.knowledge().loadKgCore(id, flow, collection))),
);
return {server, socket};
}
export const runProgram = Effect.gen(function*() {
const config = yield* loadTrustGraphMcpConfig();
const serverConfig = {
gatewayUrl: config.gatewayUrl,
user: config.user,
flowId: config.flowId,
...(config.token === undefined ? {} : {token: config.token}),
};
const {server, socket} = createMcpServer(serverConfig);
const transport = new StdioServerTransport();
yield* Effect.tryPromise({
try: () => server.connect(transport),
catch: stdioMcpError,
});
yield* Effect.sync(() => {
process.on("SIGINT", () => {
socket.close();
process.exit(0);
});
});
});
const stdioRuntime = ManagedRuntime.make(Layer.empty);
export function run(): Promise<void> {
return stdioRuntime.runPromise(runProgram);
}
export function runMain(): void {
NodeRuntime.runMain(runProgram);
}