1## Introduction
2
3The `McpServer.ts` module provides an implementation of an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/docs/getting-started/intro) server using the
4[Effect](https://effect.website) eco system.
5
6## Getting Started
7
8It's important to understand the architecture of the Effect MCP server.
9Here is an example of a MCP server implementation:
10
11```typescript
12import { NodeRuntime, NodeStdio } from "@effect/platform-node"
13import { Effect, Layer, Logger, Schema } from "effect"
14import { McpProtocol, McpServer, Tool, Toolkit } from "effect/ai"
15
16// Define a simple tool
17const DemoTool = Tool.make("DemoTool", {
18 description: "A demo tool that echoes back the input",
19 parameters: Schema.Struct({
20 message: Schema.String
21 }),
22 success: Schema.String
23})
24
25const MyToolkit = Toolkit.make(DemoTool)
26
27const DemoResource = McpServer.resource({
28 uri: "file:///demo.txt",
29 name: "Demo Resource",
30 content: Effect.succeed("# Demo Content\nThis is a demo resource.")
31})
32
33const DemoPrompt = McpServer.prompt({
34 name: "Demo Prompt",
35 description: "A demo prompt",
36 parameters: {
37 topic: Schema.String
38 },
39 completion: {
40 topic: () => Effect.succeed(["AI", "programming", "Effect"])
41 },
42 content: ({ topic }) => Effect.succeed(`Tell me about ${topic}`)
43})
44
45const ServerLayer = Layer.mergeAll(
46 DemoResource,
47 DemoPrompt,
48 McpServer.toolkit(MyToolkit).pipe(
49 Layer.provideMerge(
50 MyToolkit.toLayer({
51 DemoTool: ({ message }) => Effect.succeed(`Echo: ${message}`)
52 })
53 )
54 )
55).pipe(
56 Layer.provide(
57 McpServer.layerStdio({
58 name: "Demo MCP Server",
59 version: "1.0.0",
60 protocols: [McpProtocol.v2025_06_18]
61 })
62 ),
63 Layer.provide(NodeStdio.layer),
64 Layer.provide(Logger.layer([Logger.consolePretty()])),
65 Layer.provideMerge(Layer.succeed(Logger.LogToStderr, true))
66)
67
68Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)
69```
70
71The server exposes three main parts:
72
73- **`Resource`**, which represents a readable MCP resource such as a file accessible to the client
74- **`Prompt`**, which defines a prompt template that can be used by the client and should not be
75 confused with `Prompt.ts`
76- **`ToolkitLayer`**, which contains the definitions of all tools the server exposes, provided with
77 their implementations through `ToolImplLayer`.
78
79The part layers are merged into one layer that has a MCP server implementation as dependency.
80`McpServer.layerStdio` is used to create a standard I/O–based MCP server identified by its name and
81version. Its ordered, non-empty `protocols` declaration names implemented protocol adapters rather
82than arbitrary version strings. This release supports `McpProtocol.v2024_11_05`,
83`McpProtocol.v2025_03_26`, and `McpProtocol.v2025_06_18`. The `v2024_11_05` adapter implements that
84revision's RPC schemas and stdio framing, including its batch policy. It does not implement the
85historical two-endpoint HTTP+SSE transport. `McpServer.layerHttp` instead offers the 2024 RPC schema
86through the same single-endpoint HTTP compatibility transport used by the 2025 adapters. Because of
87the layer architecture the server implementation can be easily exchanged with this HTTP-based
88implementation. Finally, a logging layer is added with
89`Logger.layer([Logger.consolePretty({ stderr: true })])`, ensuring logs are written to `stderr`.
90This is essential when using stdio, as any output to `stdout` would interfere with the protocol
91communication.
92
93## Resources
94
95Resources in the MCP server represent files or data that can be accessed by an MCP client. Each
96resource is defined as a template that specifies its location, behavior, and metadata. The
97`McpServer.resource` helper allows you to declaratively define such resources with dynamic
98parameters, completions, and content generation.
99
100```typescript
101import { Effect, Schema } from "effect"
102import { McpSchema, McpServer } from "effect/ai"
103
104const SimpleResource = McpServer.resource({
105 uri: "file:///demo.txt",
106 name: "Demo Resource",
107 description: "A simple demo resource",
108 mimeType: "text/plain",
109 content: Effect.succeed("This is demo content")
110})
111
112const idParam = McpSchema.param("id", Schema.NumberFromString)
113
114const TemplateResource = McpServer.resource`file://path/to/file/${idParam}`({
115 name: "Demo Resource Template",
116 description: "A parameterized resource template",
117 completion: {
118 id: (_: string) => Effect.succeed([1, 2, 3, 4, 5])
119 },
120 content: Effect.fn(function*(_uri, id) {
121 return `# MCP Server Demo - ID: ${id}`
122 }),
123 mimeType: "text/x-markdown",
124 audience: ["assistant", "user"]
125})
126```
127
128In this example, the resource is parameterized by an `id` that forms part of the URI. The
129`completion` function enables clients to request valid parameter values dynamically. The `content`
130function defines how the resource's data is generated at runtime—in this case, returning a Markdown
131string containing the provided `id`. The `mimeType` specifies the format of the resource, while the
132`audience` property determines who can access it (either `"assistant"` and/or `"user"`).
133
134## Prompts
135
136Prompts define reusable templates that an MCP client can invoke with parameters. They serve as
137structured, parameterized instructions or messages that the client can send to the server. Using
138`McpServer.prompt`, you can describe the prompt's schema, auto-completion behavior, and content
139generation logic in a declarative way.
140
141```typescript
142import { Effect, Schema } from "effect"
143import { McpServer } from "effect/ai"
144
145const DemoPrompt = McpServer.prompt({
146 name: "Demo Prompt",
147 description: "A demo prompt to demonstrate MCP server capabilities",
148 parameters: {
149 name: Schema.String
150 },
151 completion: {
152 name: () => Effect.succeed(["Tom", "Tim", "Jerry"])
153 },
154 content: ({ name }) => Effect.succeed(`Use the greetings tool to write a greeting for ${name}.`)
155})
156```
157
158In this example, the prompt defines a single parameter, `name`. The `completion` property provides
159an auto-completion mechanism, allowing the client to suggest or autofill common names. The `content`
160function then generates the actual prompt text dynamically based on the provided parameter.
161
162## Tools and Toolkit
163
164Tools define executable capabilities that the MCP server exposes to clients. Each tool describes a
165contract while the actual logic is provided separately through an implementation layer. Tools are
166grouped into toolkits, which can be combined and converted into layers.
167
168```typescript
169import { Effect, Layer, Schema } from "effect"
170import { McpServer, Tool, Toolkit } from "effect/ai"
171
172const DemoTool = Tool.make("DemoTool", {
173 description: "This is a demo tool for the documentation",
174 parameters: Schema.Struct({
175 demoId: Schema.Number,
176 demoName: Schema.String
177 }),
178 success: Schema.String
179})
180
181const OtherDemoTool = Tool.make("OtherDemoTool", {
182 description: "Another demo tool",
183 parameters: Schema.Struct({
184 value: Schema.Number
185 }),
186 success: Schema.String
187})
188
189const MyToolkit = Toolkit.make(DemoTool, OtherDemoTool)
190
191const ToolkitLayer = McpServer.toolkit(MyToolkit).pipe(
192 Layer.provideMerge(
193 MyToolkit.toLayer({
194 DemoTool: ({ demoId, demoName }) => Effect.succeed(`Processed ${demoName} with ID ${demoId}`),
195 OtherDemoTool: ({ value }) => Effect.succeed(`Other tool result: ${value * 2}`)
196 })
197 )
198)
199```
200
201In this example, `Tool.make` defines new tools with typed parameters and result schemas for success
202outcomes. Multiple tools can be grouped into a single `Toolkit` using `Toolkit.make`.
203
204The toolkit is then transformed into a layer defining the interface of the tools using
205`McpServer.toolkit()`. The corresponding implementations are attached using `.toLayer`, which binds
206each tool definition to its concrete logic. Finally, the completed toolkit layer can be merged with
207other layers to create the MCP server.
208
209## Elicitation requests
210
211Elicitation requests are used to request additional input directly from the user. An elicitation
212defines both the message shown to the user and the expected response schema, ensuring structured and
213validated user input.
214
215```typescript
216import { Effect, Schema } from "effect"
217import { McpServer } from "effect/ai"
218
219const DemoElicitation = McpServer.elicit({
220 message: `Please answer the question ("yes" | "no") (default "no"):`,
221 schema: Schema.Struct({
222 answer: Schema.Union([Schema.Literal("yes"), Schema.Literal("no")])
223 })
224}).pipe(
225 Effect.catchTag("ElicitationDeclined", (_error) => {
226 return Effect.succeed({ answer: "no" })
227 })
228)
229```
230
231In this example, the server poses a simple yes/no question to the user. The input is validated
232against the defined schema, ensuring that only `"yes"` or `"no"` responses are accepted. If the user
233declines to answer or the elicitation fails, a fallback value is provided—here, the default answer
234is `"no"`.
235
236## Complete Working Example
237
238Here's a complete, copy/pastable MCP server example that combines all the concepts:
239
240```typescript
241import { NodeRuntime, NodeStdio } from "@effect/platform-node"
242import { Effect, Layer, Logger, Schema } from "effect"
243import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from "effect/ai"
244
245// Define tools
246const GreetTool = Tool.make("GreetTool", {
247 description: "Generate a greeting message",
248 parameters: Schema.Struct({
249 name: Schema.String,
250 style: Schema.Union([Schema.Literal("formal"), Schema.Literal("casual")])
251 }),
252 success: Schema.String
253})
254
255const CalculatorTool = Tool.make("CalculatorTool", {
256 description: "Perform basic arithmetic operations",
257 parameters: Schema.Struct({
258 operation: Schema.Union([
259 Schema.Literal("add"),
260 Schema.Literal("subtract"),
261 Schema.Literal("multiply"),
262 Schema.Literal("divide")
263 ]),
264 a: Schema.Number,
265 b: Schema.Number
266 }),
267 success: Schema.Number
268})
269
270// Create toolkit
271const MyToolkit = Toolkit.make(GreetTool, CalculatorTool)
272
273// Define a resource
274const ReadmeResource = McpServer.resource({
275 uri: "file:///README.md",
276 name: "README",
277 description: "Project README file",
278 mimeType: "text/markdown",
279 content: Effect.succeed("# MCP Server Demo\n\nThis is a demo MCP server built with Effect.")
280})
281
282// Define a parameterized resource
283const idParam = McpSchema.param("id", Schema.NumberFromString)
284
285const UserResource = McpServer.resource`file://users/${idParam}.json`({
286 name: "User Data",
287 description: "User information by ID",
288 completion: {
289 id: (_: string) => Effect.succeed([1, 2, 3, 4, 5])
290 },
291 content: Effect.fn(function*(_uri, id) {
292 return JSON.stringify(
293 {
294 id,
295 name: `User ${id}`,
296 email: `user${id}@example.com`
297 },
298 null,
299 2
300 )
301 }),
302 mimeType: "application/json"
303})
304
305// Define a prompt
306const AnalysisPrompt = McpServer.prompt({
307 name: "Analyze Data",
308 description: "Analyze data and provide insights",
309 parameters: {
310 dataType: Schema.String,
311 focus: Schema.Union([Schema.Literal("summary"), Schema.Literal("details")])
312 },
313 completion: {
314 dataType: () => Effect.succeed(["sales", "users", "metrics"]),
315 focus: () => Effect.succeed(["summary" as const, "details" as const])
316 },
317 content: ({ dataType, focus }) =>
318 Effect.succeed(
319 `Please analyze the ${dataType} data and provide a ${focus} analysis. Use available tools to gather information.`
320 )
321})
322
323// Create the server layer
324const ServerLayer = Layer.mergeAll(
325 ReadmeResource,
326 UserResource,
327 AnalysisPrompt,
328 McpServer.toolkit(MyToolkit).pipe(
329 Layer.provideMerge(
330 MyToolkit.toLayer({
331 GreetTool: ({ name, style }) => {
332 const greeting = style === "formal"
333 ? `Good day, ${name}. It is a pleasure to meet you.`
334 : `Hey ${name}! What's up?`
335 return Effect.succeed(greeting)
336 },
337 CalculatorTool: ({ operation, a, b }) => {
338 let result: number
339 switch (operation) {
340 case "add":
341 result = a + b
342 break
343 case "subtract":
344 result = a - b
345 break
346 case "multiply":
347 result = a * b
348 break
349 case "divide":
350 result = a / b
351 break
352 }
353 return Effect.succeed(result)
354 }
355 })
356 )
357 )
358).pipe(
359 Layer.provide(
360 McpServer.layerStdio({
361 name: "Demo MCP Server",
362 version: "1.0.0",
363 protocols: [McpProtocol.v2025_06_18]
364 })
365 ),
366 Layer.provide(NodeStdio.layer),
367 Layer.provideMerge(Layer.succeed(Logger.LogToStderr, true))
368)
369
370// Run the server
371Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)
372```