返回源码地图

packages/mcp/mcp-resources/src/index.ts

main snapshot · da00f7f5358f · 正文引用章节 12;完整原文可核对,不声称全文件人工逐行审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

1/**
2 * Scoped MCP resource providers and the shared model-facing resource tools.
3 *
4 * @module @deepseek-ai/dsh-mcp-resources
5 */
6
7import { Service, type Context } from '@deepseek-ai/cordis'
8import { createScope, NamedEntries, ScopedLayers, scopeOf, type ScopeKey, type ScopeLayer } from '@deepseek-ai/dsh-scope'
9import type { JsonValue } from '@deepseek-ai/dsh-util-values'
10import type { ToolExecution } from '@deepseek-ai/dsh-tools'
11import type {} from '@deepseek-ai/dsh-system-prompt'
12import { registerResourceTools } from './tools.ts'
13
14declare module '@deepseek-ai/cordis' {
15 interface Context {
16 mcpResources: McpResourceRuntime
17 }
18}
19
20/** One supported resource operation, with server-owned cursors and URIs. */
21export type McpResourceRequest =
22 | { method: 'resources/list' | 'resources/templates/list'; cursor?: string }
23 | { method: 'resources/read'; uri: string }
24
25/** One configured server's resource access, owned by its MCP connection plugin. */
26export interface McpResourceProvider {
27 /**
28 * Run an operation against one live connection generation.
29 * @param request - MCP resource method and parameters.
30 * @param exec - caller identity and cancellation for this invocation.
31 * @returns the protocol result as lossless JSON.
32 */
33 request(request: McpResourceRequest, exec: ToolExecution): Promise<JsonValue>
34}
35
36class ResourceLayer implements ScopeLayer {
37 readonly servers = new NamedEntries<McpResourceProvider>(name =>
38 new Error(`MCP resource server "${name}" is already registered in this scope`))
39 disposeTools: (() => void | Promise<void>) | undefined
40
41 isEmpty(): boolean {
42 return this.servers.isEmpty()
43 }
44}
45
46/** Scoped resource access plus three tools shared by configured MCP servers. */
47export class McpResourceRuntime extends Service {
48 /** Tool registry required by the resource consumer. */
49 static inject = ['tools']
50
51 private readonly layers = new ScopedLayers(() => new ResourceLayer(), () => undefined)
52 /** Shared tool registrations outlive any one server's registering context. */
53 private readonly selfCtx: Context
54
55 constructor(ctx: Context) {
56 super(ctx, 'mcpResources')
57 this.selfCtx = ctx
58
59 ctx.inject(['systemPrompt'], (inner) => {
60 inner.systemPrompt.section({
61 name: 'mcp-resource-servers',
62 order: inner.systemPrompt.getSectionOrder('MCP_SERVERS'),
63 interpolate: false,
64 text: ({ scope }) => {
65 const names = [...this.layers.merge(scope, layer => layer.servers).keys()].sort()
66 return names.length === 0 ? '' : '## MCP resource servers\n\n'
67 + 'Use list_mcp_resources, list_mcp_resource_templates, or read_mcp_resource with one of these names '
68 + `as the server argument: ${JSON.stringify(names)}.`
69 },
70 })
71 })
72 }
73
74 /**
75 * Register one server and expose resource tools while that scope has providers.
76 * @param server - configured server name, unique in this scope.
77 * @param provider - connection-owned resource operations.
78 * @returns the effect disposer for this exact registration.
79 */
80 register(server: string, provider: McpResourceProvider): () => void {
81 const ctx = this.ctx
82 const scope = scopeOf(ctx)
83 const dispose = ctx.effect(function* (this: McpResourceRuntime) {
84 let disposal: void | Promise<void>
85 // Tools disappear synchronously; Cordis owns any pending scoped-fiber teardown.
86 yield () => disposal
87 yield this.layers.effect(ctx, (layer) => {
88 const first = layer.servers.isEmpty()
89 const remove = layer.servers.insert(server, provider)
90 try {
91 if (first) layer.disposeTools = this.registerTools(scope)
92 } catch (error) {
93 remove()
94 throw error
95 }
96 return () => {
97 remove()
98 // oxlint-disable-next-line typescript/no-non-null-assertion -- successful provider registration owns the shared tools
99 if (layer.servers.isEmpty()) disposal = layer.disposeTools!()
100 }
101 }, { label: `mcpResources.provider(${server})` })
102 }.bind(this), `mcpResources.register(${server})`)
103 // oxlint-disable-next-line typescript/no-misused-promises -- visibility cleanup is synchronous; Cordis retains pending fiber disposal
104 return dispose
105 }
106
107 /** Own one scope's tools independently of its configured server plugins. */
108 private registerTools(scope: ScopeKey | undefined): () => void | Promise<void> {
109 const ctx = this.selfCtx
110 return ctx.effect(function* (this: McpResourceRuntime) {
111 let toolCtx = ctx
112 if (scope !== undefined) {
113 const owned = createScope(ctx, scope)
114 yield owned.rawDispose
115 toolCtx = owned.ctx
116 }
117 yield registerResourceTools(toolCtx, (server, request, exec) => this.request(server, request, exec))
118 }.bind(this), 'mcpResources.tools')
119 }
120
121 /** Resolve the caller-visible server before starting any network operation. */
122 private request(server: string, request: McpResourceRequest, exec: ToolExecution): Promise<JsonValue> {
123 const provider = this.layers.merge(exec.agent, layer => layer.servers).get(server)
124 if (!provider) throw new Error(`MCP resource server "${server}" is unavailable in this agent's scope`)
125 return provider.request(request, exec)
126 }
127}
128
129export default McpResourceRuntime