@effect/ai 0.19.2 → 0.19.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/McpServer.ts CHANGED
@@ -19,6 +19,7 @@ import * as Layer from "effect/Layer"
19
19
  import * as Logger from "effect/Logger"
20
20
  import * as Mailbox from "effect/Mailbox"
21
21
  import * as Option from "effect/Option"
22
+ import * as RcMap from "effect/RcMap"
22
23
  import * as Schema from "effect/Schema"
23
24
  import * as AST from "effect/SchemaAST"
24
25
  import type { Sink } from "effect/Sink"
@@ -27,13 +28,24 @@ import type * as Types from "effect/Types"
27
28
  import * as FindMyWay from "find-my-way-ts"
28
29
  import * as AiTool from "./AiTool.js"
29
30
  import type * as AiToolkit from "./AiToolkit.js"
30
- import type { CallTool, Complete, GetPrompt, Param, ServerCapabilities } from "./McpSchema.js"
31
- import {
31
+ import type {
32
32
  Annotations,
33
- BlobResourceContents,
33
+ CallTool,
34
+ Complete,
35
+ GetPrompt,
36
+ Param,
37
+ PromptArgument,
38
+ PromptMessage,
39
+ ReadResourceResult,
40
+ ServerCapabilities
41
+ } from "./McpSchema.js"
42
+ import {
34
43
  CallToolResult,
44
+ ClientNotificationRpcs,
35
45
  ClientRpcs,
36
46
  CompleteResult,
47
+ Elicit,
48
+ ElicitationDeclined,
37
49
  GetPromptResult,
38
50
  InternalError,
39
51
  InvalidParams,
@@ -41,16 +53,15 @@ import {
41
53
  ListResourcesResult,
42
54
  ListResourceTemplatesResult,
43
55
  ListToolsResult,
56
+ McpServerClient,
57
+ McpServerClientMiddleware,
44
58
  ParamAnnotation,
45
59
  Prompt,
46
- PromptArgument,
47
- PromptMessage,
48
- ReadResourceResult,
49
60
  Resource,
50
61
  ResourceTemplate,
51
62
  ServerNotificationRpcs,
63
+ ServerRequestRpcs,
52
64
  TextContent,
53
- TextResourceContents,
54
65
  Tool,
55
66
  ToolAnnotations
56
67
  } from "./McpSchema.js"
@@ -64,19 +75,22 @@ export class McpServer extends Context.Tag("@effect/ai/McpServer")<
64
75
  {
65
76
  readonly notifications: RpcClient.RpcClient<RpcGroup.Rpcs<typeof ServerNotificationRpcs>>
66
77
  readonly notificationsMailbox: Mailbox.ReadonlyMailbox<RpcMessage.Request<any>>
78
+
67
79
  readonly tools: ReadonlyArray<Tool>
68
80
  readonly addTool: (options: {
69
81
  readonly tool: Tool
70
- readonly handle: (payload: any) => Effect.Effect<CallToolResult>
82
+ readonly handle: (payload: any) => Effect.Effect<CallToolResult, never, McpServerClient>
71
83
  }) => Effect.Effect<void>
72
84
  readonly callTool: (
73
85
  requests: typeof CallTool.payloadSchema.Type
74
- ) => Effect.Effect<CallToolResult, InternalError | InvalidParams>
86
+ ) => Effect.Effect<CallToolResult, InternalError | InvalidParams, McpServerClient>
87
+
75
88
  readonly resources: ReadonlyArray<Resource>
76
89
  readonly addResource: (
77
90
  resource: Resource,
78
- handle: Effect.Effect<ReadResourceResult, InternalError>
91
+ handle: Effect.Effect<typeof ReadResourceResult.Type, InternalError, McpServerClient>
79
92
  ) => Effect.Effect<void>
93
+
80
94
  readonly resourceTemplates: ReadonlyArray<ResourceTemplate>
81
95
  readonly addResourceTemplate: (
82
96
  options: {
@@ -86,20 +100,32 @@ export class McpServer extends Context.Tag("@effect/ai/McpServer")<
86
100
  readonly handle: (
87
101
  uri: string,
88
102
  params: Array<string>
89
- ) => Effect.Effect<ReadResourceResult, InvalidParams | InternalError>
103
+ ) => Effect.Effect<typeof ReadResourceResult.Type, InvalidParams | InternalError, McpServerClient>
90
104
  }
91
105
  ) => Effect.Effect<void>
92
- readonly findResource: (uri: string) => Effect.Effect<ReadResourceResult, InvalidParams | InternalError>
106
+
107
+ readonly findResource: (
108
+ uri: string
109
+ ) => Effect.Effect<typeof ReadResourceResult.Type, InvalidParams | InternalError, McpServerClient>
110
+
111
+ readonly prompts: ReadonlyArray<Prompt>
93
112
  readonly addPrompt: (options: {
94
113
  readonly prompt: Prompt
95
- readonly completions: Record<string, (input: string) => Effect.Effect<CompleteResult, InternalError>>
96
- readonly handle: (params: Record<string, string>) => Effect.Effect<GetPromptResult, InternalError | InvalidParams>
114
+ readonly completions: Record<
115
+ string,
116
+ (input: string) => Effect.Effect<CompleteResult, InternalError, McpServerClient>
117
+ >
118
+ readonly handle: (
119
+ params: Record<string, string>
120
+ ) => Effect.Effect<GetPromptResult, InternalError | InvalidParams, McpServerClient>
97
121
  }) => Effect.Effect<void>
98
- readonly prompts: ReadonlyArray<Prompt>
99
122
  readonly getPromptResult: (
100
123
  request: typeof GetPrompt.payloadSchema.Type
101
- ) => Effect.Effect<GetPromptResult, InternalError | InvalidParams>
102
- readonly completion: (complete: typeof Complete.payloadSchema.Type) => Effect.Effect<CompleteResult, InternalError>
124
+ ) => Effect.Effect<GetPromptResult, InternalError | InvalidParams, McpServerClient>
125
+
126
+ readonly completion: (
127
+ complete: typeof Complete.payloadSchema.Type
128
+ ) => Effect.Effect<CompleteResult, InternalError, McpServerClient>
103
129
  }
104
130
  >() {
105
131
  /**
@@ -112,25 +138,29 @@ export class McpServer extends Context.Tag("@effect/ai/McpServer")<
112
138
  readonly handle: (
113
139
  uri: string,
114
140
  params: Array<string>
115
- ) => Effect.Effect<ReadResourceResult, InternalError | InvalidParams>
141
+ ) => Effect.Effect<typeof ReadResourceResult.Type, InternalError | InvalidParams, McpServerClient>
116
142
  } | {
117
143
  readonly _tag: "Resource"
118
- readonly effect: Effect.Effect<ReadResourceResult, InternalError>
144
+ readonly effect: Effect.Effect<typeof ReadResourceResult.Type, InternalError, McpServerClient>
119
145
  }
120
146
  >()
121
147
  const tools = Arr.empty<Tool>()
122
- const toolMap = new Map<string, (payload: any) => Effect.Effect<CallToolResult, InternalError>>()
148
+ const toolMap = new Map<string, (payload: any) => Effect.Effect<CallToolResult, InternalError, McpServerClient>>()
123
149
  const resources: Array<Resource> = []
124
150
  const resourceTemplates: Array<ResourceTemplate> = []
125
151
  const prompts: Array<Prompt> = []
126
152
  const promptMap = new Map<
127
153
  string,
128
- (params: Record<string, string>) => Effect.Effect<GetPromptResult, InternalError | InvalidParams>
154
+ (params: Record<string, string>) => Effect.Effect<GetPromptResult, InternalError | InvalidParams, McpServerClient>
155
+ >()
156
+ const completionsMap = new Map<
157
+ string,
158
+ (input: string) => Effect.Effect<CompleteResult, InternalError, McpServerClient>
129
159
  >()
130
- const completionsMap = new Map<string, (input: string) => Effect.Effect<CompleteResult, InternalError>>()
131
160
  const notificationsMailbox = yield* Mailbox.make<RpcMessage.Request<any>>()
132
161
  const listChangedHandles = new Map<string, any>()
133
162
  const notifications = yield* RpcClient.makeNoSerialization(ServerNotificationRpcs, {
163
+ spanPrefix: "McpServer/Notifications",
134
164
  onFromClient(options): Effect.Effect<void> {
135
165
  const message = options.message
136
166
  if (message._tag !== "Request") {
@@ -171,7 +201,7 @@ export class McpServer extends Context.Tag("@effect/ai/McpServer")<
171
201
  return notifications.client["notifications/tools/list_changed"]({})
172
202
  }),
173
203
  callTool: (request) =>
174
- Effect.suspend((): Effect.Effect<CallToolResult, InternalError | InvalidParams> => {
204
+ Effect.suspend((): Effect.Effect<CallToolResult, InternalError | InvalidParams, McpServerClient> => {
175
205
  const handle = toolMap.get(request.name)
176
206
  if (!handle) {
177
207
  return Effect.fail(new InvalidParams({ message: `Tool '${request.name}' not found` }))
@@ -203,11 +233,7 @@ export class McpServer extends Context.Tag("@effect/ai/McpServer")<
203
233
  Effect.suspend(() => {
204
234
  const match = matcher.find(uri)
205
235
  if (!match) {
206
- return Effect.succeed(
207
- new ReadResourceResult({
208
- contents: []
209
- })
210
- )
236
+ return Effect.succeed({ contents: [] })
211
237
  } else if (match.handler._tag === "Resource") {
212
238
  return match.handler.effect
213
239
  }
@@ -250,12 +276,13 @@ export class McpServer extends Context.Tag("@effect/ai/McpServer")<
250
276
  /**
251
277
  * @since 1.0.0
252
278
  */
253
- static readonly layer: Layer.Layer<McpServer> = Layer.scoped(McpServer, McpServer.make)
279
+ static readonly layer: Layer.Layer<McpServer | McpServerClient> = Layer.scoped(McpServer, McpServer.make) as any
254
280
  }
255
281
 
256
- const LATEST_PROTOCOL_VERSION = "2025-03-26"
282
+ const LATEST_PROTOCOL_VERSION = "2025-06-18"
257
283
  const SUPPORTED_PROTOCOL_VERSIONS = [
258
284
  LATEST_PROTOCOL_VERSION,
285
+ "2025-03-26",
259
286
  "2024-11-05",
260
287
  "2024-10-07"
261
288
  ]
@@ -264,7 +291,13 @@ const SUPPORTED_PROTOCOL_VERSIONS = [
264
291
  * @since 1.0.0
265
292
  * @category Constructors
266
293
  */
267
- export const run = Effect.fnUntraced(function*(options: {
294
+ export const run: (
295
+ options: { readonly name: string; readonly version: string }
296
+ ) => Effect.Effect<
297
+ never,
298
+ never,
299
+ McpServer | RpcServer.Protocol
300
+ > = Effect.fnUntraced(function*(options: {
268
301
  readonly name: string
269
302
  readonly version: string
270
303
  }) {
@@ -272,23 +305,86 @@ export const run = Effect.fnUntraced(function*(options: {
272
305
  const handlers = yield* Layer.build(layerHandlers(options))
273
306
  const server = yield* McpServer
274
307
 
308
+ const clients = yield* RcMap.make({
309
+ lookup: Effect.fnUntraced(function*(clientId: number) {
310
+ let write!: (message: RpcMessage.FromServerEncoded) => Effect.Effect<void>
311
+ const client = yield* RpcClient.make(ServerRequestRpcs, {
312
+ spanPrefix: "McpServer/Client"
313
+ }).pipe(
314
+ Effect.provideServiceEffect(
315
+ RpcClient.Protocol,
316
+ RpcClient.Protocol.make(Effect.fnUntraced(function*(writeResponse) {
317
+ write = writeResponse
318
+ return {
319
+ send(request, _transferables) {
320
+ return protocol.send(clientId, {
321
+ ...request,
322
+ headers: undefined,
323
+ traceId: undefined,
324
+ spanId: undefined,
325
+ sampled: undefined
326
+ } as any)
327
+ },
328
+ supportsAck: true,
329
+ supportsTransferables: false
330
+ }
331
+ }))
332
+ )
333
+ )
334
+
335
+ return { client, write } as const
336
+ }),
337
+ idleTimeToLive: 10000
338
+ })
339
+
340
+ const clientMiddleware = McpServerClientMiddleware.of(({ clientId }) =>
341
+ Effect.sync(() =>
342
+ McpServerClient.of({
343
+ clientId,
344
+ getClient: RcMap.get(clients, clientId).pipe(
345
+ Effect.map(({ client }) => client)
346
+ )
347
+ })
348
+ )
349
+ )
350
+
275
351
  const patchedProtocol = RpcServer.Protocol.of({
276
352
  ...protocol,
277
353
  run: (f) =>
278
- protocol.run((clientId, request) => {
279
- if (request._tag === "Request" && request.tag.startsWith("notifications/")) {
280
- if (request.tag === "notifications/cancelled") {
281
- return f(clientId, {
282
- _tag: "Interrupt",
283
- requestId: String((request.payload as any).requestId)
284
- })
354
+ protocol.run((clientId, request_) => {
355
+ const request = request_ as any as
356
+ | RpcMessage.FromServerEncoded
357
+ | RpcMessage.FromClientEncoded
358
+ switch (request._tag) {
359
+ case "Request": {
360
+ if (ClientNotificationRpcs.requests.has(request.tag)) {
361
+ if (request.tag === "notifications/cancelled") {
362
+ return f(clientId, {
363
+ _tag: "Interrupt",
364
+ requestId: String((request.payload as any).requestId)
365
+ })
366
+ }
367
+ const handler = handlers.unsafeMap.get(request.tag) as Rpc.Handler<string>
368
+ return handler
369
+ ? handler.handler(request.payload, Headers.fromInput(request.headers)) as Effect.Effect<void>
370
+ : Effect.void
371
+ }
372
+ return f(clientId, request)
285
373
  }
286
- const handler = handlers.unsafeMap.get(request.tag) as Rpc.Handler<string>
287
- return handler
288
- ? handler.handler(request.payload, Headers.fromInput(request.headers)) as Effect.Effect<void>
289
- : Effect.void
374
+ case "Ping":
375
+ case "Ack":
376
+ case "Interrupt":
377
+ case "Eof":
378
+ return f(clientId, request)
379
+ case "Pong":
380
+ case "Exit":
381
+ case "Chunk":
382
+ case "Defect":
383
+ return RcMap.get(clients, clientId).pipe(
384
+ Effect.flatMap(({ write }) => write(request)),
385
+ Effect.scoped
386
+ )
290
387
  }
291
- return f(clientId, request)
292
388
  })
293
389
  })
294
390
 
@@ -318,6 +414,7 @@ export const run = Effect.fnUntraced(function*(options: {
318
414
  disableFatalDefects: true
319
415
  }).pipe(
320
416
  Effect.provideService(RpcServer.Protocol, patchedProtocol),
417
+ Effect.provideService(McpServerClientMiddleware, clientMiddleware),
321
418
  Effect.provide(handlers)
322
419
  )
323
420
  }, Effect.scoped)
@@ -329,7 +426,7 @@ export const run = Effect.fnUntraced(function*(options: {
329
426
  export const layer = (options: {
330
427
  readonly name: string
331
428
  readonly version: string
332
- }): Layer.Layer<McpServer, never, RpcServer.Protocol> =>
429
+ }): Layer.Layer<McpServer | McpServerClient, never, RpcServer.Protocol> =>
333
430
  Layer.scopedDiscard(Effect.forkScoped(run(options))).pipe(
334
431
  Layer.provideMerge(McpServer.layer)
335
432
  )
@@ -396,7 +493,7 @@ export const layerStdio = <EIn, RIn, EOut, ROut>(options: {
396
493
  readonly version: string
397
494
  readonly stdin: Stream<Uint8Array, EIn, RIn>
398
495
  readonly stdout: Sink<unknown, Uint8Array | string, unknown, EOut, ROut>
399
- }): Layer.Layer<McpServer, never, RIn | ROut> =>
496
+ }): Layer.Layer<McpServer | McpServerClient, never, RIn | ROut> =>
400
497
  layer(options).pipe(
401
498
  Layer.provide(RpcServer.layerProtocolStdio({
402
499
  stdin: options.stdin,
@@ -471,7 +568,7 @@ export const layerHttp = <I = HttpRouter.Default>(options: {
471
568
  readonly version: string
472
569
  readonly path: HttpRouter.PathInput
473
570
  readonly routerTag?: HttpRouter.HttpRouter.TagClass<I, string, any, any>
474
- }): Layer.Layer<McpServer> =>
571
+ }): Layer.Layer<McpServer | McpServerClient> =>
475
572
  layer(options).pipe(
476
573
  Layer.provide(RpcServer.layerProtocolHttp(options)),
477
574
  Layer.provide(RpcSerialization.layerJsonRpc())
@@ -486,13 +583,17 @@ export const layerHttp = <I = HttpRouter.Default>(options: {
486
583
  export const registerToolkit: <Tools extends AiTool.Any>(toolkit: AiToolkit.AiToolkit<Tools>) => Effect.Effect<
487
584
  void,
488
585
  never,
489
- McpServer | AiTool.ToHandler<Tools>
586
+ McpServer | AiTool.ToHandler<Tools> | Exclude<AiTool.Context<Tools>, McpServerClient>
490
587
  > = Effect.fnUntraced(function*<Tools extends AiTool.Any>(
491
588
  toolkit: AiToolkit.AiToolkit<Tools>
492
589
  ) {
493
590
  const registry = yield* McpServer
494
- const built = yield* toolkit
495
- const context = yield* Effect.context<AiTool.Context<Tools>>()
591
+ const built = yield* (toolkit as any as Effect.Effect<
592
+ AiToolkit.ToHandler<Tools>,
593
+ never,
594
+ Exclude<AiTool.ToHandler<Tools>, McpServerClient>
595
+ >)
596
+ const context = yield* Effect.context<never>()
496
597
  for (const tool of built.tools) {
497
598
  const mcpTool = new Tool({
498
599
  name: tool.name,
@@ -513,25 +614,25 @@ export const registerToolkit: <Tools extends AiTool.Any>(toolkit: AiToolkit.AiTo
513
614
  tool: mcpTool,
514
615
  handle(payload) {
515
616
  return built.handle(tool.name as any, payload).pipe(
516
- Effect.provide(context),
617
+ Effect.provide(context as Context.Context<any>),
517
618
  Effect.match({
518
619
  onFailure: (error) =>
519
620
  new CallToolResult({
520
621
  isError: true,
521
- content: [
522
- new TextContent({
523
- text: JSON.stringify(error)
524
- })
525
- ]
622
+ structuredContent: typeof error === "object" ? error : undefined,
623
+ content: [{
624
+ type: "text",
625
+ text: JSON.stringify(error)
626
+ }]
526
627
  }),
527
628
  onSuccess: (result) =>
528
629
  new CallToolResult({
529
630
  isError: false,
530
- content: [
531
- new TextContent({
532
- text: JSON.stringify(result.encodedResult)
533
- })
534
- ]
631
+ structuredContent: typeof result.encodedResult === "object" ? result.encodedResult : undefined,
632
+ content: [{
633
+ type: "text",
634
+ text: JSON.stringify(result.encodedResult)
635
+ }]
535
636
  })
536
637
  })
537
638
  ) as any
@@ -548,7 +649,11 @@ export const registerToolkit: <Tools extends AiTool.Any>(toolkit: AiToolkit.AiTo
548
649
  */
549
650
  export const toolkit = <Tools extends AiTool.Any>(
550
651
  toolkit: AiToolkit.AiToolkit<Tools>
551
- ): Layer.Layer<never, never, AiTool.ToHandler<Tools>> =>
652
+ ): Layer.Layer<
653
+ never,
654
+ never,
655
+ AiTool.ToHandler<Tools> | Exclude<AiTool.Context<Tools>, McpServerClient>
656
+ > =>
552
657
  Layer.effectDiscard(registerToolkit(toolkit)).pipe(
553
658
  Layer.provide(McpServer.layer)
554
659
  )
@@ -594,12 +699,12 @@ export const registerResource: {
594
699
  readonly audience?: ReadonlyArray<"user" | "assistant"> | undefined
595
700
  readonly priority?: number | undefined
596
701
  readonly content: Effect.Effect<
597
- ReadResourceResult | string | Uint8Array,
702
+ typeof ReadResourceResult.Type | string | Uint8Array,
598
703
  E,
599
704
  R
600
705
  >
601
706
  }
602
- ): Effect.Effect<void, never, R | McpServer>
707
+ ): Effect.Effect<void, never, Exclude<R, McpServerClient> | McpServer>
603
708
  /**
604
709
  * Register a resource with the McpServer.
605
710
  *
@@ -626,23 +731,26 @@ export const registerResource: {
626
731
  readonly priority?: number | undefined
627
732
  readonly completion?: ValidateCompletions<Completions, keyof ResourceCompletions<Schemas>> | undefined
628
733
  readonly content: (uri: string, ...params: { readonly [K in keyof Schemas]: Schemas[K]["Type"] }) => Effect.Effect<
629
- ReadResourceResult | string | Uint8Array,
734
+ typeof ReadResourceResult.Type | string | Uint8Array,
630
735
  E,
631
736
  R
632
737
  >
633
738
  }) => Effect.Effect<
634
739
  void,
635
740
  never,
636
- | R
637
- | (Completions[keyof Completions] extends (input: string) => infer Ret ?
638
- Ret extends Effect.Effect<infer _A, infer _E, infer _R> ? _R : never
639
- : never)
741
+ | Exclude<
742
+ | R
743
+ | (Completions[keyof Completions] extends (input: string) => infer Ret ?
744
+ Ret extends Effect.Effect<infer _A, infer _E, infer _R> ? _R : never
745
+ : never),
746
+ McpServerClient
747
+ >
640
748
  | McpServer
641
749
  >
642
750
  } = function() {
643
751
  if (arguments.length === 1) {
644
- const options = arguments[0] as Resource & Annotations & {
645
- readonly content: Effect.Effect<ReadResourceResult | string | Uint8Array>
752
+ const options = arguments[0] as Resource & typeof Annotations.Type & {
753
+ readonly content: Effect.Effect<typeof ReadResourceResult.Type | string | Uint8Array>
646
754
  }
647
755
  return Effect.gen(function*() {
648
756
  const context = yield* Effect.context<any>()
@@ -650,7 +758,7 @@ export const registerResource: {
650
758
  yield* registry.addResource(
651
759
  new Resource({
652
760
  ...options,
653
- annotations: new Annotations(options)
761
+ annotations: options
654
762
  }),
655
763
  options.content.pipe(
656
764
  Effect.provide(context),
@@ -677,7 +785,7 @@ export const registerResource: {
677
785
  readonly priority?: number | undefined
678
786
  readonly completion?: Record<string, (input: string) => Effect.Effect<any>> | undefined
679
787
  readonly content: (uri: string, ...params: Array<any>) => Effect.Effect<
680
- ReadResourceResult | string | Uint8Array,
788
+ typeof ReadResourceResult.Type | string | Uint8Array,
681
789
  E,
682
790
  R
683
791
  >
@@ -688,7 +796,7 @@ export const registerResource: {
688
796
  const template = new ResourceTemplate({
689
797
  ...options,
690
798
  uriTemplate: uriPath,
691
- annotations: new Annotations(options)
799
+ annotations: options
692
800
  })
693
801
  const completions: Record<string, (input: string) => Effect.Effect<CompleteResult, InternalError>> = {}
694
802
  for (const [param, handle] of Object.entries(options.completion ?? {})) {
@@ -757,12 +865,12 @@ export const resource: {
757
865
  readonly audience?: ReadonlyArray<"user" | "assistant"> | undefined
758
866
  readonly priority?: number | undefined
759
867
  readonly content: Effect.Effect<
760
- ReadResourceResult | string | Uint8Array,
868
+ typeof ReadResourceResult.Type | string | Uint8Array,
761
869
  E,
762
870
  R
763
871
  >
764
872
  }
765
- ): Layer.Layer<never, never, R>
873
+ ): Layer.Layer<never, never, Exclude<R, McpServerClient>>
766
874
  /**
767
875
  * Register a resource with the McpServer.
768
876
  *
@@ -789,17 +897,20 @@ export const resource: {
789
897
  readonly priority?: number | undefined
790
898
  readonly completion?: ValidateCompletions<Completions, keyof ResourceCompletions<Schemas>> | undefined
791
899
  readonly content: (uri: string, ...params: { readonly [K in keyof Schemas]: Schemas[K]["Type"] }) => Effect.Effect<
792
- ReadResourceResult | string | Uint8Array,
900
+ typeof ReadResourceResult.Type | string | Uint8Array,
793
901
  E,
794
902
  R
795
903
  >
796
904
  }) => Layer.Layer<
797
905
  never,
798
906
  never,
799
- | R
800
- | (Completions[keyof Completions] extends (input: string) => infer Ret ?
801
- Ret extends Effect.Effect<infer _A, infer _E, infer _R> ? _R : never
802
- : never)
907
+ Exclude<
908
+ | R
909
+ | (Completions[keyof Completions] extends (input: string) => infer Ret ?
910
+ Ret extends Effect.Effect<infer _A, infer _E, infer _R> ? _R : never
911
+ : never),
912
+ McpServerClient
913
+ >
803
914
  >
804
915
  } = function() {
805
916
  if (arguments.length === 1) {
@@ -818,7 +929,7 @@ export const resource: {
818
929
  * Register a prompt with the McpServer.
819
930
  *
820
931
  * @since 1.0.0
821
- * @category Resources
932
+ * @category Prompts
822
933
  */
823
934
  export const registerPrompt = <
824
935
  E,
@@ -835,20 +946,18 @@ export const registerPrompt = <
835
946
  readonly description?: string | undefined
836
947
  readonly parameters?: Schema.Schema<Params, ParamsI, ParamsR> | undefined
837
948
  readonly completion?: ValidateCompletions<Completions, Extract<keyof Params, string>> | undefined
838
- readonly content: (params: Params) => Effect.Effect<Array<PromptMessage> | string, E, R>
949
+ readonly content: (params: Params) => Effect.Effect<Array<typeof PromptMessage.Type> | string, E, R>
839
950
  }
840
- ): Effect.Effect<void, never, ParamsR | R | McpServer> => {
841
- const args = Arr.empty<PromptArgument>()
951
+ ): Effect.Effect<void, never, Exclude<ParamsR | R, McpServerClient> | McpServer> => {
952
+ const args = Arr.empty<typeof PromptArgument.Type>()
842
953
  const props: Record<string, Schema.Schema.Any> = {}
843
954
  const propSignatures = options.parameters ? AST.getPropertySignatures(options.parameters.ast) : []
844
955
  for (const prop of propSignatures) {
845
- args.push(
846
- new PromptArgument({
847
- name: prop.name as string,
848
- description: Option.getOrUndefined(AST.getDescriptionAnnotation(prop)),
849
- required: !prop.isOptional
850
- })
851
- )
956
+ args.push({
957
+ name: prop.name as string,
958
+ description: Option.getOrUndefined(AST.getDescriptionAnnotation(prop)),
959
+ required: !prop.isOptional
960
+ })
852
961
  props[prop.name as string] = Schema.make(prop.type)
853
962
  }
854
963
  const prompt = new Prompt({
@@ -860,22 +969,23 @@ export const registerPrompt = <
860
969
  const completion: Record<string, (input: string) => Effect.Effect<any>> = options.completion ?? {}
861
970
  return Effect.gen(function*() {
862
971
  const registry = yield* McpServer
863
- const context = yield* Effect.context<R | ParamsR>()
864
- const completions: Record<string, (input: string) => Effect.Effect<CompleteResult, InternalError>> = {}
972
+ const context = yield* Effect.context<Exclude<R | ParamsR, McpServerClient>>()
973
+ const completions: Record<
974
+ string,
975
+ (input: string) => Effect.Effect<CompleteResult, InternalError, McpServerClient>
976
+ > = {}
865
977
  for (const [param, handle] of Object.entries(completion)) {
866
978
  const encodeArray = Schema.encodeUnknown(Schema.Array(props[param]))
867
979
  const handler = (input: string) =>
868
980
  handle(input).pipe(
869
981
  Effect.flatMap(encodeArray),
870
- Effect.map((values) =>
871
- new CompleteResult({
872
- completion: {
873
- values: values as Array<string>,
874
- total: values.length,
875
- hasMore: false
876
- }
877
- })
878
- ),
982
+ Effect.map((values) => ({
983
+ completion: {
984
+ values: values as Array<string>,
985
+ total: values.length,
986
+ hasMore: false
987
+ }
988
+ })),
879
989
  Effect.catchAllCause((cause) => {
880
990
  const prettyError = Cause.prettyErrors(cause)[0]
881
991
  return new InternalError({ message: prettyError.message })
@@ -893,12 +1003,10 @@ export const registerPrompt = <
893
1003
  Effect.flatMap((params) => options.content(params)),
894
1004
  Effect.map((messages) => {
895
1005
  messages = typeof messages === "string" ?
896
- [
897
- new PromptMessage({
898
- role: "user",
899
- content: new TextContent({ text: messages })
900
- })
901
- ] :
1006
+ [{
1007
+ role: "user",
1008
+ content: TextContent.make({ text: messages })
1009
+ }] :
902
1010
  messages
903
1011
  return new GetPromptResult({ messages, description: prompt.description })
904
1012
  }),
@@ -906,7 +1014,7 @@ export const registerPrompt = <
906
1014
  const prettyError = Cause.prettyErrors(cause)[0]
907
1015
  return new InternalError({ message: prettyError.message })
908
1016
  }),
909
- Effect.provide(context)
1017
+ Effect.provide(context as Context.Context<ParamsR | R>)
910
1018
  )
911
1019
  })
912
1020
  })
@@ -916,7 +1024,7 @@ export const registerPrompt = <
916
1024
  * Register a prompt with the McpServer.
917
1025
  *
918
1026
  * @since 1.0.0
919
- * @category Resources
1027
+ * @category Prompts
920
1028
  */
921
1029
  export const prompt = <
922
1030
  E,
@@ -933,13 +1041,49 @@ export const prompt = <
933
1041
  readonly description?: string | undefined
934
1042
  readonly parameters?: Schema.Schema<Params, ParamsI, ParamsR> | undefined
935
1043
  readonly completion?: ValidateCompletions<Completions, Extract<keyof Params, string>> | undefined
936
- readonly content: (params: Params) => Effect.Effect<Array<PromptMessage> | string, E, R>
1044
+ readonly content: (params: Params) => Effect.Effect<Array<typeof PromptMessage.Type> | string, E, R>
937
1045
  }
938
- ): Layer.Layer<never, never, ParamsR | R> =>
1046
+ ): Layer.Layer<never, never, Exclude<ParamsR | R, McpServerClient>> =>
939
1047
  Layer.effectDiscard(registerPrompt(options)).pipe(
940
1048
  Layer.provide(McpServer.layer)
941
1049
  )
942
1050
 
1051
+ /**
1052
+ * Create an elicitation request
1053
+ *
1054
+ * @since 1.0.0
1055
+ * @category Elicitation
1056
+ */
1057
+ export const elicit: <A, I extends Record<string, any>, R>(options: {
1058
+ readonly message: string
1059
+ readonly schema: Schema.Schema<A, I, R>
1060
+ }) => Effect.Effect<
1061
+ A,
1062
+ ElicitationDeclined,
1063
+ McpServerClient | R
1064
+ > = Effect.fnUntraced(function*<A, I extends Record<string, any>, R>(options: {
1065
+ readonly message: string
1066
+ readonly schema: Schema.Schema<A, I, R>
1067
+ }) {
1068
+ const { getClient } = yield* McpServerClient
1069
+ const client = yield* getClient
1070
+ const request = Elicit.payloadSchema.make({
1071
+ message: options.message,
1072
+ requestedSchema: makeJsonSchema(options.schema.ast)
1073
+ })
1074
+ const res = yield* client["elicitation/create"](request).pipe(
1075
+ Effect.catchAllCause((cause) => Effect.fail(new ElicitationDeclined({ cause: Cause.squash(cause), request })))
1076
+ )
1077
+ switch (res.action) {
1078
+ case "accept":
1079
+ return yield* Effect.orDie(Schema.decodeUnknown(options.schema)(res.content))
1080
+ case "cancel":
1081
+ return yield* Effect.interrupt
1082
+ case "decline":
1083
+ return yield* Effect.fail(new ElicitationDeclined({ request }))
1084
+ }
1085
+ }, Effect.scoped)
1086
+
943
1087
  // -----------------------------------------------------------------------------
944
1088
  // Internal
945
1089
  // -----------------------------------------------------------------------------
@@ -967,14 +1111,15 @@ const compileUriTemplate = (segments: TemplateStringsArray, ...schemas: Readonly
967
1111
  const arr: Array<Schema.Schema.Any> = []
968
1112
  for (let i = 0; i < schemas.length; i++) {
969
1113
  const schema = schemas[i]
1114
+ const segment = segments[i + 1]
970
1115
  const key = String(i)
971
1116
  arr.push(schema)
972
- routerPath += `:${key}${segments[i + 1].replace(":", "::")}`
1117
+ routerPath += `:${key}${segment.replace(":", "::")}`
973
1118
  const paramName = AST.getAnnotation(ParamAnnotation)(schema.ast).pipe(
974
1119
  Option.getOrElse(() => `param${key}`)
975
1120
  )
976
1121
  params[paramName as string] = schema
977
- uriPath += `{${paramName}}`
1122
+ uriPath += `{${paramName}}${segment}`
978
1123
  }
979
1124
  pathSchema = Schema.Tuple(...arr)
980
1125
  }
@@ -999,7 +1144,7 @@ const layerHandlers = (serverInfo: {
999
1144
  ping: () => Effect.succeed({}),
1000
1145
  initialize(params) {
1001
1146
  const requestedVersion = params.protocolVersion
1002
- const capabilities: Types.DeepMutable<ServerCapabilities> = {
1147
+ const capabilities: Types.DeepMutable<typeof ServerCapabilities.Type> = {
1003
1148
  completions: {}
1004
1149
  }
1005
1150
  if (server.tools.length > 0) {
@@ -1064,25 +1209,24 @@ const makeJsonSchema = (ast: AST.AST): JsonSchema.JsonSchema7 => {
1064
1209
  return schema
1065
1210
  }
1066
1211
 
1067
- const resolveResourceContent = (uri: string, content: ReadResourceResult | string | Uint8Array) => {
1212
+ const resolveResourceContent = (
1213
+ uri: string,
1214
+ content: typeof ReadResourceResult.Type | string | Uint8Array
1215
+ ): typeof ReadResourceResult.Type => {
1068
1216
  if (typeof content === "string") {
1069
- return new ReadResourceResult({
1070
- contents: [
1071
- new TextResourceContents({
1072
- uri,
1073
- text: content
1074
- })
1075
- ]
1076
- })
1217
+ return {
1218
+ contents: [{
1219
+ uri,
1220
+ text: content
1221
+ }]
1222
+ }
1077
1223
  } else if (content instanceof Uint8Array) {
1078
- return new ReadResourceResult({
1079
- contents: [
1080
- new BlobResourceContents({
1081
- uri,
1082
- blob: content
1083
- })
1084
- ]
1085
- })
1224
+ return {
1225
+ contents: [{
1226
+ uri,
1227
+ blob: content
1228
+ }]
1229
+ }
1086
1230
  }
1087
1231
  return content
1088
1232
  }