feat(mcp): add resource template listing (#33546)
This commit is contained in:
parent
dcf7b4e792
commit
c6cc13e183
6 changed files with 151 additions and 8 deletions
|
|
@ -129,6 +129,14 @@ export function resources(client: Client, timeout?: number) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resourceTemplates(client: Client, timeout?: number) {
|
||||||
|
if (!client.getServerCapabilities()?.resources) return Promise.resolve([])
|
||||||
|
return paginate(
|
||||||
|
(cursor) => client.listResourceTemplates(cursor === undefined ? undefined : { cursor }, { timeout }),
|
||||||
|
(result) => result.resourceTemplates,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function listTools(client: Client, timeout: number) {
|
function listTools(client: Client, timeout: number) {
|
||||||
return Effect.tryPromise({
|
return Effect.tryPromise({
|
||||||
try: () =>
|
try: () =>
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ const pendingOAuthTransports = new Map<string, TransportWithAuth>()
|
||||||
// Prompt cache types
|
// Prompt cache types
|
||||||
type PromptInfo = Awaited<ReturnType<MCPClient["listPrompts"]>>["prompts"][number]
|
type PromptInfo = Awaited<ReturnType<MCPClient["listPrompts"]>>["prompts"][number]
|
||||||
type ResourceInfo = Awaited<ReturnType<MCPClient["listResources"]>>["resources"][number]
|
type ResourceInfo = Awaited<ReturnType<MCPClient["listResources"]>>["resources"][number]
|
||||||
|
type ResourceTemplateInfo = Awaited<ReturnType<MCPClient["listResourceTemplates"]>>["resourceTemplates"][number]
|
||||||
type McpEntry = NonNullable<ConfigV1.Info["mcp"]>[string]
|
type McpEntry = NonNullable<ConfigV1.Info["mcp"]>[string]
|
||||||
|
|
||||||
function isMcpConfigured(entry: McpEntry): entry is ConfigMCPV1.Info {
|
function isMcpConfigured(entry: McpEntry): entry is ConfigMCPV1.Info {
|
||||||
|
|
@ -162,6 +163,9 @@ export interface Interface {
|
||||||
readonly tools: () => Effect.Effect<Record<string, Tool>>
|
readonly tools: () => Effect.Effect<Record<string, Tool>>
|
||||||
readonly prompts: () => Effect.Effect<Record<string, PromptInfo & { client: string }>>
|
readonly prompts: () => Effect.Effect<Record<string, PromptInfo & { client: string }>>
|
||||||
readonly resources: (clientName?: string) => Effect.Effect<Record<string, ResourceInfo & { client: string }>>
|
readonly resources: (clientName?: string) => Effect.Effect<Record<string, ResourceInfo & { client: string }>>
|
||||||
|
readonly resourceTemplates: (
|
||||||
|
clientName?: string,
|
||||||
|
) => Effect.Effect<Record<string, ResourceTemplateInfo & { client: string }>>
|
||||||
readonly add: (name: string, mcp: ConfigMCPV1.Info) => Effect.Effect<{ status: Record<string, Status> | Status }>
|
readonly add: (name: string, mcp: ConfigMCPV1.Info) => Effect.Effect<{ status: Record<string, Status> | Status }>
|
||||||
readonly connect: (name: string) => Effect.Effect<void, NotFoundError>
|
readonly connect: (name: string) => Effect.Effect<void, NotFoundError>
|
||||||
readonly disconnect: (name: string) => Effect.Effect<void, NotFoundError>
|
readonly disconnect: (name: string) => Effect.Effect<void, NotFoundError>
|
||||||
|
|
@ -690,6 +694,16 @@ export const layer = Layer.effect(
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const resourceTemplates = Effect.fn("MCP.resourceTemplates")(function* (clientName?: string) {
|
||||||
|
return yield* collectFromConnected(
|
||||||
|
yield* InstanceState.get(state),
|
||||||
|
McpCatalog.resourceTemplates,
|
||||||
|
"resource templates",
|
||||||
|
(template) => template.uriTemplate,
|
||||||
|
clientName,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const withClient = Effect.fnUntraced(function* <A>(
|
const withClient = Effect.fnUntraced(function* <A>(
|
||||||
clientName: string,
|
clientName: string,
|
||||||
fn: (client: MCPClient, timeout?: number) => Promise<A>,
|
fn: (client: MCPClient, timeout?: number) => Promise<A>,
|
||||||
|
|
@ -931,6 +945,7 @@ export const layer = Layer.effect(
|
||||||
tools,
|
tools,
|
||||||
prompts,
|
prompts,
|
||||||
resources,
|
resources,
|
||||||
|
resourceTemplates,
|
||||||
add,
|
add,
|
||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,11 @@ import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { isRecord } from "@/util/record"
|
import { isRecord } from "@/util/record"
|
||||||
|
|
||||||
const LIST_MCP_RESOURCES_TOOL = "list_mcp_resources"
|
const MCP_RESOURCE_TOOLS = {
|
||||||
const READ_MCP_RESOURCE_TOOL = "read_mcp_resource"
|
list: "list_mcp_resources",
|
||||||
|
listTemplates: "list_mcp_resource_templates",
|
||||||
|
read: "read_mcp_resource",
|
||||||
|
} as const
|
||||||
const MAX_MCP_RESOURCE_BLOB_BYTES = 10 * 1024 * 1024
|
const MAX_MCP_RESOURCE_BLOB_BYTES = 10 * 1024 * 1024
|
||||||
const SUPPORTED_MCP_RESOURCE_ATTACHMENT_MIMES = new Set([
|
const SUPPORTED_MCP_RESOURCE_ATTACHMENT_MIMES = new Set([
|
||||||
"application/pdf",
|
"application/pdf",
|
||||||
|
|
@ -130,7 +133,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||||
(client) => !!client.getServerCapabilities()?.resources,
|
(client) => !!client.getServerCapabilities()?.resources,
|
||||||
)
|
)
|
||||||
if (hasMcpResourceServer) {
|
if (hasMcpResourceServer) {
|
||||||
tools[LIST_MCP_RESOURCES_TOOL] = tool({
|
tools[MCP_RESOURCE_TOOLS.list] = tool({
|
||||||
description:
|
description:
|
||||||
"Lists resources provided by connected MCP servers. Resources provide context such as files, database schemas, or application-specific information.",
|
"Lists resources provided by connected MCP servers. Resources provide context such as files, database schemas, or application-specific information.",
|
||||||
inputSchema: jsonSchema(
|
inputSchema: jsonSchema(
|
||||||
|
|
@ -167,7 +170,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||||
: resourceServers.map((server) => `mcp:${server}:*`)
|
: resourceServers.map((server) => `mcp:${server}:*`)
|
||||||
yield* plugin.trigger(
|
yield* plugin.trigger(
|
||||||
"tool.execute.before",
|
"tool.execute.before",
|
||||||
{ tool: LIST_MCP_RESOURCES_TOOL, sessionID: ctx.sessionID, callID: opts.toolCallId },
|
{ tool: MCP_RESOURCE_TOOLS.list, sessionID: ctx.sessionID, callID: opts.toolCallId },
|
||||||
{ args },
|
{ args },
|
||||||
)
|
)
|
||||||
yield* ctx.ask({
|
yield* ctx.ask({
|
||||||
|
|
@ -200,7 +203,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||||
}
|
}
|
||||||
yield* plugin.trigger(
|
yield* plugin.trigger(
|
||||||
"tool.execute.after",
|
"tool.execute.after",
|
||||||
{ tool: LIST_MCP_RESOURCES_TOOL, sessionID: ctx.sessionID, callID: opts.toolCallId, args },
|
{ tool: MCP_RESOURCE_TOOLS.list, sessionID: ctx.sessionID, callID: opts.toolCallId, args },
|
||||||
output,
|
output,
|
||||||
)
|
)
|
||||||
if (opts.abortSignal?.aborted) {
|
if (opts.abortSignal?.aborted) {
|
||||||
|
|
@ -212,7 +215,89 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
tools[READ_MCP_RESOURCE_TOOL] = tool({
|
tools[MCP_RESOURCE_TOOLS.listTemplates] = tool({
|
||||||
|
description:
|
||||||
|
"Lists resource templates provided by connected MCP servers. Resource templates are parameterized resources that can be read after filling in their URI template.",
|
||||||
|
inputSchema: jsonSchema(
|
||||||
|
ProviderTransform.schema(input.model, {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
server: {
|
||||||
|
type: "string",
|
||||||
|
description: "Optional MCP server name. When omitted, lists resource templates from every connected server.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
additionalProperties: false,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
execute(args, opts) {
|
||||||
|
return run.promise(
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const parsed = parseListMcpResourcesArgs(args)
|
||||||
|
const ctx = context(toRecord(args), opts)
|
||||||
|
const clients = yield* mcp.clients()
|
||||||
|
const resourceServers = Object.entries(clients)
|
||||||
|
.filter((entry) => !!entry[1].getServerCapabilities()?.resources)
|
||||||
|
.map((entry) => entry[0])
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
if (parsed.server && !resourceServers.includes(parsed.server)) {
|
||||||
|
throw new Error(
|
||||||
|
resourceServers.length === 0
|
||||||
|
? `MCP server "${parsed.server}" does not support resources`
|
||||||
|
: `MCP server "${parsed.server}" does not support resources. Available resource servers: ${resourceServers.join(", ")}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const permissionPatterns = parsed.server
|
||||||
|
? [`mcp:${parsed.server}:*`]
|
||||||
|
: resourceServers.map((server) => `mcp:${server}:*`)
|
||||||
|
yield* plugin.trigger(
|
||||||
|
"tool.execute.before",
|
||||||
|
{ tool: MCP_RESOURCE_TOOLS.listTemplates, sessionID: ctx.sessionID, callID: opts.toolCallId },
|
||||||
|
{ args },
|
||||||
|
)
|
||||||
|
yield* ctx.ask({
|
||||||
|
permission: "read",
|
||||||
|
metadata: parsed.server ? { server: parsed.server } : {},
|
||||||
|
patterns: permissionPatterns,
|
||||||
|
always: permissionPatterns,
|
||||||
|
})
|
||||||
|
|
||||||
|
const templates = Object.values(yield* mcp.resourceTemplates(parsed.server))
|
||||||
|
const filtered = templates
|
||||||
|
.filter((template) => !parsed.server || template.client === parsed.server)
|
||||||
|
.toSorted((a, b) =>
|
||||||
|
(a.client + "\u0000" + a.name + "\u0000" + a.uriTemplate).localeCompare(
|
||||||
|
b.client + "\u0000" + b.name + "\u0000" + b.uriTemplate,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
const content = JSON.stringify({ resourceTemplates: filtered.map(formatMcpResourceTemplate) }, null, 2)
|
||||||
|
const truncated = yield* truncate.output(content, {}, input.agent)
|
||||||
|
const output = {
|
||||||
|
title: parsed.server ? `MCP resource templates: ${parsed.server}` : "MCP resource templates",
|
||||||
|
metadata: {
|
||||||
|
count: filtered.length,
|
||||||
|
servers: resourceServers,
|
||||||
|
...(parsed.server ? { server: parsed.server } : {}),
|
||||||
|
truncated: truncated.truncated,
|
||||||
|
...(truncated.truncated && { outputPath: truncated.outputPath }),
|
||||||
|
},
|
||||||
|
output: truncated.content,
|
||||||
|
}
|
||||||
|
yield* plugin.trigger(
|
||||||
|
"tool.execute.after",
|
||||||
|
{ tool: MCP_RESOURCE_TOOLS.listTemplates, sessionID: ctx.sessionID, callID: opts.toolCallId, args },
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
if (opts.abortSignal?.aborted) {
|
||||||
|
yield* input.processor.completeToolCall(opts.toolCallId, output)
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
tools[MCP_RESOURCE_TOOLS.read] = tool({
|
||||||
description:
|
description:
|
||||||
"Read a specific resource from an MCP server using the server name and resource URI. The URI is an MCP identifier and does not need to be a file URL.",
|
"Read a specific resource from an MCP server using the server name and resource URI. The URI is an MCP identifier and does not need to be a file URL.",
|
||||||
inputSchema: jsonSchema(
|
inputSchema: jsonSchema(
|
||||||
|
|
@ -247,7 +332,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||||
}
|
}
|
||||||
yield* plugin.trigger(
|
yield* plugin.trigger(
|
||||||
"tool.execute.before",
|
"tool.execute.before",
|
||||||
{ tool: READ_MCP_RESOURCE_TOOL, sessionID: ctx.sessionID, callID: opts.toolCallId },
|
{ tool: MCP_RESOURCE_TOOLS.read, sessionID: ctx.sessionID, callID: opts.toolCallId },
|
||||||
{ args },
|
{ args },
|
||||||
)
|
)
|
||||||
yield* ctx.ask({
|
yield* ctx.ask({
|
||||||
|
|
@ -282,7 +367,7 @@ export const resolve = Effect.fn("SessionTools.resolve")(function* (input: {
|
||||||
}
|
}
|
||||||
yield* plugin.trigger(
|
yield* plugin.trigger(
|
||||||
"tool.execute.after",
|
"tool.execute.after",
|
||||||
{ tool: READ_MCP_RESOURCE_TOOL, sessionID: ctx.sessionID, callID: opts.toolCallId, args },
|
{ tool: MCP_RESOURCE_TOOLS.read, sessionID: ctx.sessionID, callID: opts.toolCallId, args },
|
||||||
output,
|
output,
|
||||||
)
|
)
|
||||||
if (opts.abortSignal?.aborted) {
|
if (opts.abortSignal?.aborted) {
|
||||||
|
|
@ -432,6 +517,11 @@ function formatMcpResource(resource: MCP.Resource) {
|
||||||
return { ...result, server: resource.client }
|
return { ...result, server: resource.client }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatMcpResourceTemplate(template: Record<string, unknown> & { client: string }) {
|
||||||
|
const result = Object.fromEntries(Object.entries(template).filter((entry) => entry[0] !== "client"))
|
||||||
|
return { ...result, server: template.client }
|
||||||
|
}
|
||||||
|
|
||||||
function formatMcpResourceContent(server: string, uri: string, content: { contents: unknown }) {
|
function formatMcpResourceContent(server: string, uri: string, content: { contents: unknown }) {
|
||||||
const items = (Array.isArray(content.contents) ? content.contents : [content.contents]).filter(isRecord)
|
const items = (Array.isArray(content.contents) ? content.contents : [content.contents]).filter(isRecord)
|
||||||
const text: string[] = []
|
const text: string[] = []
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ interface MockClientState {
|
||||||
listToolsCalls: number
|
listToolsCalls: number
|
||||||
listPromptsCalls: number
|
listPromptsCalls: number
|
||||||
listResourcesCalls: number
|
listResourcesCalls: number
|
||||||
|
listResourceTemplatesCalls: number
|
||||||
getPromptTimeout?: number
|
getPromptTimeout?: number
|
||||||
readResourceTimeout?: number
|
readResourceTimeout?: number
|
||||||
requestCalls: number
|
requestCalls: number
|
||||||
|
|
@ -26,6 +27,7 @@ interface MockClientState {
|
||||||
listResourcesShouldFail: boolean
|
listResourcesShouldFail: boolean
|
||||||
prompts: Array<{ name: string; description?: string }>
|
prompts: Array<{ name: string; description?: string }>
|
||||||
resources: Array<{ name: string; uri: string; description?: string }>
|
resources: Array<{ name: string; uri: string; description?: string }>
|
||||||
|
resourceTemplates: Array<{ name: string; uriTemplate: string; description?: string }>
|
||||||
toolPages: Record<
|
toolPages: Record<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
|
|
@ -38,6 +40,10 @@ interface MockClientState {
|
||||||
string,
|
string,
|
||||||
{ resources: Array<{ name: string; uri: string; description?: string }>; nextCursor?: string }
|
{ resources: Array<{ name: string; uri: string; description?: string }>; nextCursor?: string }
|
||||||
>
|
>
|
||||||
|
resourceTemplatePages: Record<
|
||||||
|
string,
|
||||||
|
{ resourceTemplates: Array<{ name: string; uriTemplate: string; description?: string }>; nextCursor?: string }
|
||||||
|
>
|
||||||
closed: boolean
|
closed: boolean
|
||||||
clientOptions?: { capabilities?: { roots?: { listChanged?: boolean } } }
|
clientOptions?: { capabilities?: { roots?: { listChanged?: boolean } } }
|
||||||
requestHandlers: Map<unknown, (...args: any[]) => Promise<any>>
|
requestHandlers: Map<unknown, (...args: any[]) => Promise<any>>
|
||||||
|
|
@ -67,6 +73,7 @@ function getOrCreateClientState(name?: string): MockClientState {
|
||||||
listToolsCalls: 0,
|
listToolsCalls: 0,
|
||||||
listPromptsCalls: 0,
|
listPromptsCalls: 0,
|
||||||
listResourcesCalls: 0,
|
listResourcesCalls: 0,
|
||||||
|
listResourceTemplatesCalls: 0,
|
||||||
requestCalls: 0,
|
requestCalls: 0,
|
||||||
listToolsShouldFail: false,
|
listToolsShouldFail: false,
|
||||||
listToolsError: "listTools failed",
|
listToolsError: "listTools failed",
|
||||||
|
|
@ -74,9 +81,11 @@ function getOrCreateClientState(name?: string): MockClientState {
|
||||||
listResourcesShouldFail: false,
|
listResourcesShouldFail: false,
|
||||||
prompts: [],
|
prompts: [],
|
||||||
resources: [],
|
resources: [],
|
||||||
|
resourceTemplates: [],
|
||||||
toolPages: {},
|
toolPages: {},
|
||||||
promptPages: {},
|
promptPages: {},
|
||||||
resourcePages: {},
|
resourcePages: {},
|
||||||
|
resourceTemplatePages: {},
|
||||||
closed: false,
|
closed: false,
|
||||||
requestHandlers: new Map(),
|
requestHandlers: new Map(),
|
||||||
notificationHandlers: new Map(),
|
notificationHandlers: new Map(),
|
||||||
|
|
@ -224,6 +233,13 @@ void mock.module("@modelcontextprotocol/sdk/client/index.js", () => ({
|
||||||
return { resources: this._state?.resources ?? [] }
|
return { resources: this._state?.resources ?? [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listResourceTemplates(params?: { cursor?: string }) {
|
||||||
|
if (this._state) this._state.listResourceTemplatesCalls++
|
||||||
|
const page = this._state?.resourceTemplatePages[params === undefined ? "initial" : (params.cursor ?? "")]
|
||||||
|
if (page) return page
|
||||||
|
return { resourceTemplates: this._state?.resourceTemplates ?? [] }
|
||||||
|
}
|
||||||
|
|
||||||
async getPrompt(_params: unknown, options?: { timeout?: number }) {
|
async getPrompt(_params: unknown, options?: { timeout?: number }) {
|
||||||
if (this._state) this._state.getPromptTimeout = options?.timeout
|
if (this._state) this._state.getPromptTimeout = options?.timeout
|
||||||
return { messages: [] }
|
return { messages: [] }
|
||||||
|
|
@ -353,6 +369,13 @@ it.instance(
|
||||||
initial: { resources: [{ name: "resource-one", uri: "test://one" }], nextCursor: "resources-2" },
|
initial: { resources: [{ name: "resource-one", uri: "test://one" }], nextCursor: "resources-2" },
|
||||||
"resources-2": { resources: [{ name: "resource-two", uri: "test://two" }] },
|
"resources-2": { resources: [{ name: "resource-two", uri: "test://two" }] },
|
||||||
}
|
}
|
||||||
|
serverState.resourceTemplatePages = {
|
||||||
|
initial: {
|
||||||
|
resourceTemplates: [{ name: "template-one", uriTemplate: "test://one/{id}" }],
|
||||||
|
nextCursor: "resource-templates-2",
|
||||||
|
},
|
||||||
|
"resource-templates-2": { resourceTemplates: [{ name: "template-two", uriTemplate: "test://two/{id}" }] },
|
||||||
|
}
|
||||||
|
|
||||||
yield* mcp.add("paged-server", {
|
yield* mcp.add("paged-server", {
|
||||||
type: "local",
|
type: "local",
|
||||||
|
|
@ -362,9 +385,14 @@ it.instance(
|
||||||
expect(Object.keys(yield* mcp.tools())).toEqual(["mcp__paged-server__tool-one", "mcp__paged-server__tool-two"])
|
expect(Object.keys(yield* mcp.tools())).toEqual(["mcp__paged-server__tool-one", "mcp__paged-server__tool-two"])
|
||||||
expect(Object.keys(yield* mcp.prompts())).toEqual(["paged-server:prompt-one", "paged-server:prompt-two"])
|
expect(Object.keys(yield* mcp.prompts())).toEqual(["paged-server:prompt-one", "paged-server:prompt-two"])
|
||||||
expect(Object.keys(yield* mcp.resources())).toEqual(["paged-server:test://one", "paged-server:test://two"])
|
expect(Object.keys(yield* mcp.resources())).toEqual(["paged-server:test://one", "paged-server:test://two"])
|
||||||
|
expect(Object.keys(yield* mcp.resourceTemplates())).toEqual([
|
||||||
|
"paged-server:test://one/{id}",
|
||||||
|
"paged-server:test://two/{id}",
|
||||||
|
])
|
||||||
expect(serverState.listToolsCalls).toBe(2)
|
expect(serverState.listToolsCalls).toBe(2)
|
||||||
expect(serverState.listPromptsCalls).toBe(2)
|
expect(serverState.listPromptsCalls).toBe(2)
|
||||||
expect(serverState.listResourcesCalls).toBe(2)
|
expect(serverState.listResourcesCalls).toBe(2)
|
||||||
|
expect(serverState.listResourceTemplatesCalls).toBe(2)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
{ config: { mcp: {} } },
|
{ config: { mcp: {} } },
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ const mcp = Layer.succeed(
|
||||||
tools: () => Effect.succeed({}),
|
tools: () => Effect.succeed({}),
|
||||||
prompts: () => Effect.succeed({}),
|
prompts: () => Effect.succeed({}),
|
||||||
resources: () => Effect.succeed({}),
|
resources: () => Effect.succeed({}),
|
||||||
|
resourceTemplates: () => Effect.succeed({}),
|
||||||
add: () => Effect.succeed({ status: { status: "disabled" as const } }),
|
add: () => Effect.succeed({ status: { status: "disabled" as const } }),
|
||||||
connect: () => Effect.void,
|
connect: () => Effect.void,
|
||||||
disconnect: () => Effect.void,
|
disconnect: () => Effect.void,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ const mcp = Layer.succeed(
|
||||||
tools: () => Effect.succeed({}),
|
tools: () => Effect.succeed({}),
|
||||||
prompts: () => Effect.succeed({}),
|
prompts: () => Effect.succeed({}),
|
||||||
resources: () => Effect.succeed({}),
|
resources: () => Effect.succeed({}),
|
||||||
|
resourceTemplates: () => Effect.succeed({}),
|
||||||
add: () => Effect.succeed({ status: { status: "disabled" as const } }),
|
add: () => Effect.succeed({ status: { status: "disabled" as const } }),
|
||||||
connect: () => Effect.void,
|
connect: () => Effect.void,
|
||||||
disconnect: () => Effect.void,
|
disconnect: () => Effect.void,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue