@expandai/mcp-server 0.1.1 → 0.1.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/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/Server.cjs +831 -0
- package/dist/Server.d.cts +15 -0
- package/dist/Server.d.ts +15 -0
- package/dist/Server.js +1 -0
- package/dist/chunk-TUBB4OP4.js +802 -0
- package/dist/main.cjs +66 -11
- package/dist/main.d.cts +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +47 -0
- package/package.json +14 -3
- package/src/ExpandClient.ts +19 -4
- package/src/{tools/Fetch.ts → Fetch.ts} +7 -2
- package/src/Server.ts +17 -0
- package/src/Toolkit.ts +23 -0
- package/src/main.ts +40 -9
- package/src/tools/index.ts +0 -33
- /package/src/{resources/ExpandDocs.ts → ExpandDocs.ts} +0 -0
- /package/src/{generated/ExpandClient.ts → Generated.ts} +0 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
import { Effect, Config, Schema, Layer, Context, Option, Redacted } from 'effect';
|
|
2
|
+
import { McpServer, Tool, Toolkit } from '@effect/ai';
|
|
3
|
+
import { NodeHttpClient } from '@effect/platform-node';
|
|
4
|
+
import { HttpClient, HttpClientRequest as HttpClientRequest$1 } from '@effect/platform';
|
|
5
|
+
import * as HttpClientError from '@effect/platform/HttpClientError';
|
|
6
|
+
import * as HttpClientRequest from '@effect/platform/HttpClientRequest';
|
|
7
|
+
import * as HttpClientResponse from '@effect/platform/HttpClientResponse';
|
|
8
|
+
import * as Data from 'effect/Data';
|
|
9
|
+
import * as Effect2 from 'effect/Effect';
|
|
10
|
+
import * as S from 'effect/Schema';
|
|
11
|
+
|
|
12
|
+
// src/Server.ts
|
|
13
|
+
|
|
14
|
+
// package.json
|
|
15
|
+
var package_default = {
|
|
16
|
+
version: "0.1.4"};
|
|
17
|
+
var ExpandDocs = McpServer.resource({
|
|
18
|
+
uri: "expand://about",
|
|
19
|
+
name: "About expand.ai",
|
|
20
|
+
description: "Key links for the expand.ai platform",
|
|
21
|
+
content: Effect.succeed(`# expand.ai
|
|
22
|
+
|
|
23
|
+
- Website: https://expand.ai
|
|
24
|
+
- Quickstart: https://expand.ai/docs
|
|
25
|
+
- Dashboard: https://expand.ai/dashboard
|
|
26
|
+
`)
|
|
27
|
+
});
|
|
28
|
+
(class extends S.Struct({
|
|
29
|
+
url: S.String
|
|
30
|
+
}) {
|
|
31
|
+
});
|
|
32
|
+
var IssueTag = class extends S.Literal(
|
|
33
|
+
"Pointer",
|
|
34
|
+
"Unexpected",
|
|
35
|
+
"Missing",
|
|
36
|
+
"Composite",
|
|
37
|
+
"Refinement",
|
|
38
|
+
"Transformation",
|
|
39
|
+
"Type",
|
|
40
|
+
"Forbidden"
|
|
41
|
+
) {
|
|
42
|
+
};
|
|
43
|
+
var PropertyKeyEnumTag = class extends S.Literal("symbol") {
|
|
44
|
+
};
|
|
45
|
+
var PropertyKey = class extends S.Union(
|
|
46
|
+
S.String,
|
|
47
|
+
S.Number,
|
|
48
|
+
/**
|
|
49
|
+
* an object to be decoded into a globally shared symbol
|
|
50
|
+
*/
|
|
51
|
+
S.Struct({
|
|
52
|
+
_tag: PropertyKeyEnumTag,
|
|
53
|
+
key: S.String
|
|
54
|
+
})
|
|
55
|
+
) {
|
|
56
|
+
};
|
|
57
|
+
var Issue = class extends S.Class("Issue")({
|
|
58
|
+
/**
|
|
59
|
+
* The tag identifying the type of parse issue
|
|
60
|
+
*/
|
|
61
|
+
_tag: IssueTag,
|
|
62
|
+
/**
|
|
63
|
+
* The path to the property where the issue occurred
|
|
64
|
+
*/
|
|
65
|
+
path: S.Array(PropertyKey),
|
|
66
|
+
/**
|
|
67
|
+
* A descriptive message explaining the issue
|
|
68
|
+
*/
|
|
69
|
+
message: S.String
|
|
70
|
+
}) {
|
|
71
|
+
};
|
|
72
|
+
var HttpApiDecodeErrorTag = class extends S.Literal("HttpApiDecodeError") {
|
|
73
|
+
};
|
|
74
|
+
var HttpApiDecodeError = class extends S.Class("HttpApiDecodeError")({
|
|
75
|
+
issues: S.Array(Issue),
|
|
76
|
+
message: S.String,
|
|
77
|
+
_tag: HttpApiDecodeErrorTag
|
|
78
|
+
}) {
|
|
79
|
+
};
|
|
80
|
+
var UnauthorizedAccessTag = class extends S.Literal("UnauthorizedAccess") {
|
|
81
|
+
};
|
|
82
|
+
var UnauthorizedAccess = class extends S.Class("UnauthorizedAccess")({
|
|
83
|
+
_tag: UnauthorizedAccessTag
|
|
84
|
+
}) {
|
|
85
|
+
};
|
|
86
|
+
var AssetNotFoundTag = class extends S.Literal("AssetNotFound") {
|
|
87
|
+
};
|
|
88
|
+
var AssetNotFound = class extends S.Class("AssetNotFound")({
|
|
89
|
+
browserSessionId: S.optionalWith(S.String, { nullable: true }),
|
|
90
|
+
filename: S.String,
|
|
91
|
+
_tag: AssetNotFoundTag
|
|
92
|
+
}) {
|
|
93
|
+
};
|
|
94
|
+
var AssetNotInWaczTag = class extends S.Literal("AssetNotInWacz") {
|
|
95
|
+
};
|
|
96
|
+
var AssetNotInWacz = class extends S.Class("AssetNotInWacz")({
|
|
97
|
+
url: S.String,
|
|
98
|
+
_tag: AssetNotInWaczTag
|
|
99
|
+
}) {
|
|
100
|
+
};
|
|
101
|
+
var AssetsGetByUrl404 = class extends S.Union(AssetNotFound, AssetNotInWacz) {
|
|
102
|
+
};
|
|
103
|
+
var AssetReadErrorTag = class extends S.Literal("AssetReadError") {
|
|
104
|
+
};
|
|
105
|
+
var AssetReadError = class extends S.Class("AssetReadError")({
|
|
106
|
+
browserSessionId: S.optionalWith(S.String, { nullable: true }),
|
|
107
|
+
filename: S.String,
|
|
108
|
+
_tag: AssetReadErrorTag
|
|
109
|
+
}) {
|
|
110
|
+
};
|
|
111
|
+
var InvalidWaczFormatTag = class extends S.Literal("InvalidWaczFormat") {
|
|
112
|
+
};
|
|
113
|
+
var InvalidWaczFormat = class extends S.Class("InvalidWaczFormat")({
|
|
114
|
+
message: S.String,
|
|
115
|
+
_tag: InvalidWaczFormatTag
|
|
116
|
+
}) {
|
|
117
|
+
};
|
|
118
|
+
var AssetRetrievalErrorTag = class extends S.Literal("AssetRetrievalError") {
|
|
119
|
+
};
|
|
120
|
+
var AssetRetrievalError = class extends S.Class("AssetRetrievalError")({
|
|
121
|
+
browserSessionId: S.String,
|
|
122
|
+
url: S.String,
|
|
123
|
+
_tag: AssetRetrievalErrorTag
|
|
124
|
+
}) {
|
|
125
|
+
};
|
|
126
|
+
var AssetsGetByUrl500 = class extends S.Union(AssetReadError, InvalidWaczFormat, AssetRetrievalError) {
|
|
127
|
+
};
|
|
128
|
+
var CurlErrorInputMethod = class extends S.Literal(
|
|
129
|
+
"GET",
|
|
130
|
+
"POST",
|
|
131
|
+
"HEAD",
|
|
132
|
+
"PUT",
|
|
133
|
+
"DELETE",
|
|
134
|
+
"CONNECT",
|
|
135
|
+
"OPTIONS",
|
|
136
|
+
"TRACE",
|
|
137
|
+
"PATCH"
|
|
138
|
+
) {
|
|
139
|
+
};
|
|
140
|
+
var CurlErrorTag = class extends S.Literal("CurlError") {
|
|
141
|
+
};
|
|
142
|
+
var CurlError = class extends S.Class("CurlError")({
|
|
143
|
+
status: S.Number,
|
|
144
|
+
input: S.Struct({
|
|
145
|
+
method: CurlErrorInputMethod,
|
|
146
|
+
url: S.String,
|
|
147
|
+
body: S.optionalWith(S.String, { nullable: true }),
|
|
148
|
+
headers: S.optionalWith(S.Struct({}), { nullable: true }),
|
|
149
|
+
proxy: S.optionalWith(
|
|
150
|
+
S.Struct({
|
|
151
|
+
server: S.String,
|
|
152
|
+
username: S.optionalWith(S.String, { nullable: true }),
|
|
153
|
+
password: S.optionalWith(S.String, { nullable: true })
|
|
154
|
+
}),
|
|
155
|
+
{ nullable: true }
|
|
156
|
+
),
|
|
157
|
+
timeout: S.optionalWith(S.Number, { nullable: true }),
|
|
158
|
+
connectTimeout: S.optionalWith(S.Number, { nullable: true })
|
|
159
|
+
}),
|
|
160
|
+
_tag: CurlErrorTag
|
|
161
|
+
}) {
|
|
162
|
+
};
|
|
163
|
+
var AssetListItem = class extends S.Class("AssetListItem")({
|
|
164
|
+
url: S.String,
|
|
165
|
+
mime: S.String,
|
|
166
|
+
status: S.Number
|
|
167
|
+
}) {
|
|
168
|
+
};
|
|
169
|
+
var AssetListResponse = class extends S.Class("AssetListResponse")({
|
|
170
|
+
browserSessionId: S.String,
|
|
171
|
+
assets: S.Array(AssetListItem)
|
|
172
|
+
}) {
|
|
173
|
+
};
|
|
174
|
+
var AssetsList500 = class extends S.Union(AssetReadError, InvalidWaczFormat) {
|
|
175
|
+
};
|
|
176
|
+
var URL = class extends S.String {
|
|
177
|
+
};
|
|
178
|
+
var SelectHtmlConfig = class extends S.Record({ key: S.String, value: S.Unknown }) {
|
|
179
|
+
};
|
|
180
|
+
var SelectMarkdownConfig = class extends S.Record({ key: S.String, value: S.Unknown }) {
|
|
181
|
+
};
|
|
182
|
+
var SelectScreenshotConfig = class extends S.Class("SelectScreenshotConfig")({
|
|
183
|
+
/**
|
|
184
|
+
* Whether to capture the full page including content below the fold
|
|
185
|
+
*/
|
|
186
|
+
fullPage: S.optionalWith(S.Boolean, { nullable: true })
|
|
187
|
+
}) {
|
|
188
|
+
};
|
|
189
|
+
var Trimmed = class extends S.String.pipe(S.pattern(/^\S[\s\S]*\S$|^\S$|^$/)) {
|
|
190
|
+
};
|
|
191
|
+
var Int2 = class extends S.Int {
|
|
192
|
+
};
|
|
193
|
+
(class extends S.Class("FetchRequest")({
|
|
194
|
+
url: URL,
|
|
195
|
+
/**
|
|
196
|
+
* Specifies which content formats to include in the response
|
|
197
|
+
*/
|
|
198
|
+
select: S.optionalWith(
|
|
199
|
+
S.Struct({
|
|
200
|
+
html: S.optionalWith(
|
|
201
|
+
S.Union(
|
|
202
|
+
S.Boolean,
|
|
203
|
+
SelectHtmlConfig
|
|
204
|
+
),
|
|
205
|
+
{ nullable: true }
|
|
206
|
+
),
|
|
207
|
+
/**
|
|
208
|
+
* Include markdown-formatted content in the response
|
|
209
|
+
*/
|
|
210
|
+
markdown: S.optionalWith(
|
|
211
|
+
S.Union(
|
|
212
|
+
S.Boolean,
|
|
213
|
+
SelectMarkdownConfig
|
|
214
|
+
),
|
|
215
|
+
{ nullable: true }
|
|
216
|
+
),
|
|
217
|
+
screenshot: S.optionalWith(
|
|
218
|
+
S.Union(
|
|
219
|
+
S.Boolean,
|
|
220
|
+
SelectScreenshotConfig
|
|
221
|
+
),
|
|
222
|
+
{ nullable: true }
|
|
223
|
+
),
|
|
224
|
+
summary: S.optionalWith(
|
|
225
|
+
S.Union(
|
|
226
|
+
S.Boolean,
|
|
227
|
+
/**
|
|
228
|
+
* Options for AI-powered page summarization
|
|
229
|
+
*/
|
|
230
|
+
S.Struct({
|
|
231
|
+
/**
|
|
232
|
+
* Custom prompt for AI summarization (max 5000 characters)
|
|
233
|
+
*/
|
|
234
|
+
prompt: S.optionalWith(S.String.pipe(S.maxLength(5e3)), {
|
|
235
|
+
nullable: true,
|
|
236
|
+
default: () => "You are a helpful assistant that summarizes web page content.\nGiven the markdown content of a web page, provide a clear and concise summary.\nFocus on the main points and key information.\nKeep the summary informative but brief."
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
),
|
|
240
|
+
{ nullable: true }
|
|
241
|
+
),
|
|
242
|
+
/**
|
|
243
|
+
* Options for extracting relevant snippets from page content using semantic search
|
|
244
|
+
*/
|
|
245
|
+
snippets: S.optionalWith(
|
|
246
|
+
S.Struct({
|
|
247
|
+
/**
|
|
248
|
+
* Query to find relevant content snippets from the page (required, non-empty)
|
|
249
|
+
*/
|
|
250
|
+
query: Trimmed,
|
|
251
|
+
/**
|
|
252
|
+
* Maximum number of snippets to return (1-50)
|
|
253
|
+
*/
|
|
254
|
+
maxSnippets: S.optionalWith(Int2, { nullable: true, default: () => 5 }),
|
|
255
|
+
/**
|
|
256
|
+
* Minimum relevance score threshold (0-1). Snippets below this score are filtered out.
|
|
257
|
+
*/
|
|
258
|
+
minScore: S.optionalWith(S.Number.pipe(S.greaterThanOrEqualTo(0), S.lessThanOrEqualTo(1)), {
|
|
259
|
+
nullable: true,
|
|
260
|
+
default: () => 0.5
|
|
261
|
+
}),
|
|
262
|
+
/**
|
|
263
|
+
* Target snippet size in characters (100-2000)
|
|
264
|
+
*/
|
|
265
|
+
targetSnippetSize: S.optionalWith(Int2, { nullable: true, default: () => 384 })
|
|
266
|
+
}),
|
|
267
|
+
{ nullable: true }
|
|
268
|
+
),
|
|
269
|
+
/**
|
|
270
|
+
* Include page metadata in the response
|
|
271
|
+
*/
|
|
272
|
+
meta: S.optionalWith(S.Boolean, { nullable: true, default: () => true }),
|
|
273
|
+
/**
|
|
274
|
+
* Configure response info options (headers inclusion)
|
|
275
|
+
*/
|
|
276
|
+
response: S.optionalWith(
|
|
277
|
+
S.Struct({
|
|
278
|
+
/**
|
|
279
|
+
* Whether to include HTTP response headers
|
|
280
|
+
*/
|
|
281
|
+
includeHeaders: S.optionalWith(S.Boolean, { nullable: true, default: () => false })
|
|
282
|
+
}),
|
|
283
|
+
{ nullable: true }
|
|
284
|
+
)
|
|
285
|
+
}),
|
|
286
|
+
{ nullable: true }
|
|
287
|
+
),
|
|
288
|
+
/**
|
|
289
|
+
* Configuration options for browser behavior during the fetch
|
|
290
|
+
*/
|
|
291
|
+
browserConfig: S.optionalWith(
|
|
292
|
+
S.Struct({
|
|
293
|
+
/**
|
|
294
|
+
* Whether to scroll the entire page to capture lazy-loaded content
|
|
295
|
+
*/
|
|
296
|
+
scrollFullPage: S.optionalWith(S.Boolean, { nullable: true, default: () => false })
|
|
297
|
+
}),
|
|
298
|
+
{ nullable: true }
|
|
299
|
+
)
|
|
300
|
+
}) {
|
|
301
|
+
});
|
|
302
|
+
var ResponseInfo = class extends S.Class("ResponseInfo")({
|
|
303
|
+
/**
|
|
304
|
+
* The URL that was fetched
|
|
305
|
+
*/
|
|
306
|
+
url: S.String,
|
|
307
|
+
/**
|
|
308
|
+
* HTTP status code of the response
|
|
309
|
+
*/
|
|
310
|
+
statusCode: S.Number,
|
|
311
|
+
/**
|
|
312
|
+
* Response headers from the fetch operation (keys are lower-cased HTTP header names)
|
|
313
|
+
*/
|
|
314
|
+
headers: S.optionalWith(S.Struct({}), { nullable: true })
|
|
315
|
+
}) {
|
|
316
|
+
};
|
|
317
|
+
var IconMeta = class extends S.Class("IconMeta")({
|
|
318
|
+
/**
|
|
319
|
+
* Icon URL or path
|
|
320
|
+
*/
|
|
321
|
+
href: S.String,
|
|
322
|
+
/**
|
|
323
|
+
* Link relationship type
|
|
324
|
+
*/
|
|
325
|
+
rel: S.String,
|
|
326
|
+
/**
|
|
327
|
+
* MIME type of the icon
|
|
328
|
+
*/
|
|
329
|
+
type: S.optionalWith(S.String, { nullable: true }),
|
|
330
|
+
/**
|
|
331
|
+
* Icon dimensions
|
|
332
|
+
*/
|
|
333
|
+
sizes: S.optionalWith(S.String, { nullable: true })
|
|
334
|
+
}) {
|
|
335
|
+
};
|
|
336
|
+
var OgImage = class extends S.Class("OgImage")({
|
|
337
|
+
/**
|
|
338
|
+
* Image URL
|
|
339
|
+
*/
|
|
340
|
+
url: S.String,
|
|
341
|
+
/**
|
|
342
|
+
* Image width in pixels
|
|
343
|
+
*/
|
|
344
|
+
width: S.optionalWith(S.Number, { nullable: true }),
|
|
345
|
+
/**
|
|
346
|
+
* Image height in pixels
|
|
347
|
+
*/
|
|
348
|
+
height: S.optionalWith(S.Number, { nullable: true }),
|
|
349
|
+
/**
|
|
350
|
+
* Image alt text for accessibility
|
|
351
|
+
*/
|
|
352
|
+
alt: S.optionalWith(S.String, { nullable: true })
|
|
353
|
+
}) {
|
|
354
|
+
};
|
|
355
|
+
var OpenGraphMeta = class extends S.Class("OpenGraphMeta")({
|
|
356
|
+
/**
|
|
357
|
+
* Open Graph title (og:title)
|
|
358
|
+
*/
|
|
359
|
+
title: S.optionalWith(S.String, { nullable: true }),
|
|
360
|
+
/**
|
|
361
|
+
* Open Graph description (og:description)
|
|
362
|
+
*/
|
|
363
|
+
description: S.optionalWith(S.String, { nullable: true }),
|
|
364
|
+
/**
|
|
365
|
+
* Open Graph type (og:type) - website, article, product, etc.
|
|
366
|
+
*/
|
|
367
|
+
type: S.optionalWith(S.String, { nullable: true }),
|
|
368
|
+
/**
|
|
369
|
+
* Canonical URL for the content (og:url)
|
|
370
|
+
*/
|
|
371
|
+
url: S.optionalWith(S.String, { nullable: true }),
|
|
372
|
+
/**
|
|
373
|
+
* Site name (og:site_name)
|
|
374
|
+
*/
|
|
375
|
+
siteName: S.optionalWith(S.String, { nullable: true }),
|
|
376
|
+
/**
|
|
377
|
+
* Locale in language_TERRITORY format (og:locale)
|
|
378
|
+
*/
|
|
379
|
+
locale: S.optionalWith(S.String, { nullable: true }),
|
|
380
|
+
/**
|
|
381
|
+
* Open Graph images (og:image and related properties)
|
|
382
|
+
*/
|
|
383
|
+
images: S.optionalWith(S.Array(OgImage), { nullable: true })
|
|
384
|
+
}) {
|
|
385
|
+
};
|
|
386
|
+
var TwitterCardMeta = class extends S.Class("TwitterCardMeta")({
|
|
387
|
+
/**
|
|
388
|
+
* Twitter card type (twitter:card)
|
|
389
|
+
*/
|
|
390
|
+
card: S.optionalWith(S.String, { nullable: true }),
|
|
391
|
+
/**
|
|
392
|
+
* Twitter @username of the website (twitter:site)
|
|
393
|
+
*/
|
|
394
|
+
site: S.optionalWith(S.String, { nullable: true }),
|
|
395
|
+
/**
|
|
396
|
+
* Twitter @username of content creator (twitter:creator)
|
|
397
|
+
*/
|
|
398
|
+
creator: S.optionalWith(S.String, { nullable: true }),
|
|
399
|
+
/**
|
|
400
|
+
* Title for Twitter card (twitter:title)
|
|
401
|
+
*/
|
|
402
|
+
title: S.optionalWith(S.String, { nullable: true }),
|
|
403
|
+
/**
|
|
404
|
+
* Description for Twitter card (twitter:description)
|
|
405
|
+
*/
|
|
406
|
+
description: S.optionalWith(S.String, { nullable: true }),
|
|
407
|
+
/**
|
|
408
|
+
* Image URL for Twitter card (twitter:image)
|
|
409
|
+
*/
|
|
410
|
+
image: S.optionalWith(S.String, { nullable: true })
|
|
411
|
+
}) {
|
|
412
|
+
};
|
|
413
|
+
var PageMeta = class extends S.Class("PageMeta")({
|
|
414
|
+
/**
|
|
415
|
+
* Page title from <title> tag
|
|
416
|
+
*/
|
|
417
|
+
title: S.optionalWith(S.String, { nullable: true }),
|
|
418
|
+
/**
|
|
419
|
+
* Meta description from <meta name="description">
|
|
420
|
+
*/
|
|
421
|
+
description: S.optionalWith(S.String, { nullable: true }),
|
|
422
|
+
/**
|
|
423
|
+
* Canonical URL from <link rel="canonical">
|
|
424
|
+
*/
|
|
425
|
+
canonicalUrl: S.optionalWith(S.String, { nullable: true }),
|
|
426
|
+
/**
|
|
427
|
+
* Page language from <html lang="...">
|
|
428
|
+
*/
|
|
429
|
+
language: S.optionalWith(S.String, { nullable: true }),
|
|
430
|
+
/**
|
|
431
|
+
* Character encoding from <meta charset="...">
|
|
432
|
+
*/
|
|
433
|
+
charset: S.optionalWith(S.String, { nullable: true }),
|
|
434
|
+
/**
|
|
435
|
+
* Primary favicon URL (first icon found)
|
|
436
|
+
*/
|
|
437
|
+
favicon: S.optionalWith(S.String, { nullable: true }),
|
|
438
|
+
/**
|
|
439
|
+
* All icon links (favicons, apple-touch-icons, etc.)
|
|
440
|
+
*/
|
|
441
|
+
icons: S.optionalWith(S.Array(IconMeta), { nullable: true }),
|
|
442
|
+
/**
|
|
443
|
+
* Open Graph metadata from og:* meta tags
|
|
444
|
+
*/
|
|
445
|
+
openGraph: S.optionalWith(OpenGraphMeta, { nullable: true }),
|
|
446
|
+
/**
|
|
447
|
+
* Twitter Card metadata from twitter:* meta tags
|
|
448
|
+
*/
|
|
449
|
+
twitter: S.optionalWith(TwitterCardMeta, { nullable: true })
|
|
450
|
+
}) {
|
|
451
|
+
};
|
|
452
|
+
var FetchData = class extends S.Class("FetchData")({
|
|
453
|
+
response: ResponseInfo,
|
|
454
|
+
/**
|
|
455
|
+
* Page metadata extracted from HTML head (title, description, Open Graph, Twitter Card, icons)
|
|
456
|
+
*/
|
|
457
|
+
meta: S.optionalWith(PageMeta, { nullable: true }),
|
|
458
|
+
/**
|
|
459
|
+
* The HTML content of the fetched page
|
|
460
|
+
*/
|
|
461
|
+
html: S.optionalWith(S.String, { nullable: true }),
|
|
462
|
+
/**
|
|
463
|
+
* The markdown-formatted content extracted from the page
|
|
464
|
+
*/
|
|
465
|
+
markdown: S.optionalWith(S.String, { nullable: true }),
|
|
466
|
+
/**
|
|
467
|
+
* Base64-encoded data URI of the screenshot image
|
|
468
|
+
*/
|
|
469
|
+
screenshot: S.optionalWith(S.String, { nullable: true }),
|
|
470
|
+
/**
|
|
471
|
+
* AI-generated summary of the page content
|
|
472
|
+
*/
|
|
473
|
+
summary: S.optionalWith(S.String, { nullable: true }),
|
|
474
|
+
/**
|
|
475
|
+
* Relevant snippets extracted from the page based on the search query
|
|
476
|
+
*/
|
|
477
|
+
snippets: S.optionalWith(
|
|
478
|
+
S.Array(
|
|
479
|
+
S.Struct({
|
|
480
|
+
/**
|
|
481
|
+
* Type identifier for TextPart compatibility
|
|
482
|
+
*/
|
|
483
|
+
type: S.optionalWith(S.Literal("text"), { nullable: true, default: () => "text" }),
|
|
484
|
+
/**
|
|
485
|
+
* The text content of the snippet
|
|
486
|
+
*/
|
|
487
|
+
text: S.String,
|
|
488
|
+
/**
|
|
489
|
+
* Relevance score from the reranker (0-1)
|
|
490
|
+
*/
|
|
491
|
+
score: S.Number,
|
|
492
|
+
/**
|
|
493
|
+
* Original chunk index
|
|
494
|
+
*/
|
|
495
|
+
index: S.Number
|
|
496
|
+
})
|
|
497
|
+
),
|
|
498
|
+
{ nullable: true }
|
|
499
|
+
)
|
|
500
|
+
}) {
|
|
501
|
+
};
|
|
502
|
+
var FetchResult = class extends S.Class("FetchResult")({
|
|
503
|
+
data: FetchData
|
|
504
|
+
}) {
|
|
505
|
+
};
|
|
506
|
+
var FetchErrorTag = class extends S.Literal("FetchError") {
|
|
507
|
+
};
|
|
508
|
+
var FetchError = class extends S.Class("FetchError")({
|
|
509
|
+
_tag: FetchErrorTag
|
|
510
|
+
}) {
|
|
511
|
+
};
|
|
512
|
+
(class extends S.Class("MarkdownRequest")({
|
|
513
|
+
/**
|
|
514
|
+
* The URL to fetch markdown content from
|
|
515
|
+
*/
|
|
516
|
+
url: S.String,
|
|
517
|
+
/**
|
|
518
|
+
* Configuration options for browser behavior during the fetch
|
|
519
|
+
*/
|
|
520
|
+
browserConfig: S.optionalWith(
|
|
521
|
+
S.Struct({
|
|
522
|
+
/**
|
|
523
|
+
* Whether to scroll the entire page to capture lazy-loaded content
|
|
524
|
+
*/
|
|
525
|
+
scrollFullPage: S.optionalWith(S.Boolean, { nullable: true, default: () => false })
|
|
526
|
+
}),
|
|
527
|
+
{ nullable: true }
|
|
528
|
+
)
|
|
529
|
+
}) {
|
|
530
|
+
});
|
|
531
|
+
var InvalidAssetHashTag = class extends S.Literal("InvalidAssetHash") {
|
|
532
|
+
};
|
|
533
|
+
var InvalidAssetHash = class extends S.Class("InvalidAssetHash")({
|
|
534
|
+
message: S.String,
|
|
535
|
+
_tag: InvalidAssetHashTag
|
|
536
|
+
}) {
|
|
537
|
+
};
|
|
538
|
+
var AssetNotFoundInHarTag = class extends S.Literal("AssetNotFoundInHar") {
|
|
539
|
+
};
|
|
540
|
+
var AssetNotFoundInHar = class extends S.Class("AssetNotFoundInHar")({
|
|
541
|
+
snapshotDir: S.String,
|
|
542
|
+
assetUrl: S.String,
|
|
543
|
+
_tag: AssetNotFoundInHarTag
|
|
544
|
+
}) {
|
|
545
|
+
};
|
|
546
|
+
var HarNotFoundTag = class extends S.Literal("HarNotFound") {
|
|
547
|
+
};
|
|
548
|
+
var HarNotFound = class extends S.Class("HarNotFound")({
|
|
549
|
+
snapshotDir: S.String,
|
|
550
|
+
_tag: HarNotFoundTag
|
|
551
|
+
}) {
|
|
552
|
+
};
|
|
553
|
+
var AssetFetchErrorTag = class extends S.Literal("AssetFetchError") {
|
|
554
|
+
};
|
|
555
|
+
var AssetFetchError = class extends S.Class("AssetFetchError")({
|
|
556
|
+
snapshotDir: S.String,
|
|
557
|
+
assetUrl: S.String,
|
|
558
|
+
_tag: AssetFetchErrorTag
|
|
559
|
+
}) {
|
|
560
|
+
};
|
|
561
|
+
var LocalAssetsGetAsset500 = class extends S.Union(
|
|
562
|
+
InvalidAssetHash,
|
|
563
|
+
AssetNotFoundInHar,
|
|
564
|
+
HarNotFound,
|
|
565
|
+
AssetFetchError
|
|
566
|
+
) {
|
|
567
|
+
};
|
|
568
|
+
var make = (httpClient, options = {}) => {
|
|
569
|
+
const unexpectedStatus = (response) => Effect2.flatMap(
|
|
570
|
+
Effect2.orElseSucceed(response.json, () => "Unexpected status code"),
|
|
571
|
+
(description) => Effect2.fail(
|
|
572
|
+
new HttpClientError.ResponseError({
|
|
573
|
+
request: response.request,
|
|
574
|
+
response,
|
|
575
|
+
reason: "StatusCode",
|
|
576
|
+
description: typeof description === "string" ? description : JSON.stringify(description)
|
|
577
|
+
})
|
|
578
|
+
)
|
|
579
|
+
);
|
|
580
|
+
const withResponse = options.transformClient ? (f) => (request) => Effect2.flatMap(
|
|
581
|
+
Effect2.flatMap(options.transformClient(httpClient), (client) => client.execute(request)),
|
|
582
|
+
f
|
|
583
|
+
) : (f) => (request) => Effect2.flatMap(httpClient.execute(request), f);
|
|
584
|
+
const decodeSuccess = (schema) => (response) => HttpClientResponse.schemaBodyJson(schema)(response);
|
|
585
|
+
const decodeError = (tag, schema) => (response) => Effect2.flatMap(
|
|
586
|
+
HttpClientResponse.schemaBodyJson(schema)(response),
|
|
587
|
+
(cause) => Effect2.fail(ClientError(tag, cause, response))
|
|
588
|
+
);
|
|
589
|
+
return {
|
|
590
|
+
httpClient,
|
|
591
|
+
assetsGetByUrl: (browserSessionId, options2) => HttpClientRequest.get(`/v1/assets/${browserSessionId}/asset`).pipe(
|
|
592
|
+
HttpClientRequest.setUrlParams({ url: options2?.["url"] }),
|
|
593
|
+
withResponse(
|
|
594
|
+
HttpClientResponse.matchStatus({
|
|
595
|
+
"400": decodeError("HttpApiDecodeError", HttpApiDecodeError),
|
|
596
|
+
"401": decodeError("UnauthorizedAccess", UnauthorizedAccess),
|
|
597
|
+
"404": decodeError("AssetsGetByUrl404", AssetsGetByUrl404),
|
|
598
|
+
"500": decodeError("AssetsGetByUrl500", AssetsGetByUrl500),
|
|
599
|
+
"502": decodeError("CurlError", CurlError),
|
|
600
|
+
"429": () => Effect2.void,
|
|
601
|
+
orElse: unexpectedStatus
|
|
602
|
+
})
|
|
603
|
+
)
|
|
604
|
+
),
|
|
605
|
+
assetsList: (browserSessionId) => HttpClientRequest.get(`/v1/assets/${browserSessionId}`).pipe(
|
|
606
|
+
withResponse(
|
|
607
|
+
HttpClientResponse.matchStatus({
|
|
608
|
+
"2xx": decodeSuccess(AssetListResponse),
|
|
609
|
+
"400": decodeError("HttpApiDecodeError", HttpApiDecodeError),
|
|
610
|
+
"401": decodeError("UnauthorizedAccess", UnauthorizedAccess),
|
|
611
|
+
"404": decodeError("AssetNotFound", AssetNotFound),
|
|
612
|
+
"500": decodeError("AssetsList500", AssetsList500),
|
|
613
|
+
"429": () => Effect2.void,
|
|
614
|
+
orElse: unexpectedStatus
|
|
615
|
+
})
|
|
616
|
+
)
|
|
617
|
+
),
|
|
618
|
+
fetch: (options2) => HttpClientRequest.post(`/v1/fetch`).pipe(
|
|
619
|
+
HttpClientRequest.bodyUnsafeJson(options2),
|
|
620
|
+
withResponse(
|
|
621
|
+
HttpClientResponse.matchStatus({
|
|
622
|
+
"2xx": decodeSuccess(FetchResult),
|
|
623
|
+
"400": decodeError("HttpApiDecodeError", HttpApiDecodeError),
|
|
624
|
+
"401": decodeError("UnauthorizedAccess", UnauthorizedAccess),
|
|
625
|
+
"500": decodeError("FetchError", FetchError),
|
|
626
|
+
"429": () => Effect2.void,
|
|
627
|
+
orElse: unexpectedStatus
|
|
628
|
+
})
|
|
629
|
+
)
|
|
630
|
+
),
|
|
631
|
+
markdown: (options2) => HttpClientRequest.post(`/v1/fetch/markdown`).pipe(
|
|
632
|
+
HttpClientRequest.bodyUnsafeJson(options2),
|
|
633
|
+
withResponse(
|
|
634
|
+
HttpClientResponse.matchStatus({
|
|
635
|
+
"400": decodeError("HttpApiDecodeError", HttpApiDecodeError),
|
|
636
|
+
"401": decodeError("UnauthorizedAccess", UnauthorizedAccess),
|
|
637
|
+
"500": decodeError("FetchError", FetchError),
|
|
638
|
+
"429": () => Effect2.void,
|
|
639
|
+
orElse: unexpectedStatus
|
|
640
|
+
})
|
|
641
|
+
)
|
|
642
|
+
),
|
|
643
|
+
localAssetsGetAsset: (assetHash) => HttpClientRequest.get(`/local-asset/${assetHash}`).pipe(
|
|
644
|
+
withResponse(
|
|
645
|
+
HttpClientResponse.matchStatus({
|
|
646
|
+
"400": decodeError("HttpApiDecodeError", HttpApiDecodeError),
|
|
647
|
+
"500": decodeError("LocalAssetsGetAsset500", LocalAssetsGetAsset500),
|
|
648
|
+
"204": () => Effect2.void,
|
|
649
|
+
"429": () => Effect2.void,
|
|
650
|
+
orElse: unexpectedStatus
|
|
651
|
+
})
|
|
652
|
+
)
|
|
653
|
+
)
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
var ClientErrorImpl = class extends Data.Error {
|
|
657
|
+
};
|
|
658
|
+
var ClientError = (tag, cause, response) => new ClientErrorImpl({
|
|
659
|
+
_tag: tag,
|
|
660
|
+
cause,
|
|
661
|
+
response,
|
|
662
|
+
request: response.request
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// src/ExpandClient.ts
|
|
666
|
+
var ExpandConfig = Config.all({
|
|
667
|
+
apiKey: Config.option(Config.redacted("EXPAND_API_KEY")),
|
|
668
|
+
baseUrl: Config.withDefault(Config.string("EXPAND_BASE_URL"), "https://api.expand.ai")
|
|
669
|
+
});
|
|
670
|
+
var ExpandClient = class extends Effect.Service()("ExpandClient", {
|
|
671
|
+
dependencies: [],
|
|
672
|
+
scoped: Effect.gen(function* () {
|
|
673
|
+
const config = yield* ExpandConfig;
|
|
674
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
675
|
+
const requestHeaders = yield* Effect.serviceOption(CurrentRequestHeaders);
|
|
676
|
+
yield* Effect.logInfo(`ExpandClient connected to ${config.baseUrl}`);
|
|
677
|
+
return make(httpClient, {
|
|
678
|
+
transformClient: (client) => Effect.succeed(
|
|
679
|
+
client.pipe(
|
|
680
|
+
HttpClient.mapRequest(HttpClientRequest$1.prependUrl(config.baseUrl)),
|
|
681
|
+
HttpClient.mapRequest((req) => {
|
|
682
|
+
if (Option.isSome(requestHeaders)) {
|
|
683
|
+
const auth = requestHeaders.value["authorization"];
|
|
684
|
+
if (auth) {
|
|
685
|
+
return HttpClientRequest$1.setHeader("authorization", auth)(req);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (Option.isSome(config.apiKey)) {
|
|
689
|
+
return HttpClientRequest$1.setHeader("x-expand-api-key", Redacted.value(config.apiKey.value))(req);
|
|
690
|
+
}
|
|
691
|
+
return req;
|
|
692
|
+
})
|
|
693
|
+
)
|
|
694
|
+
)
|
|
695
|
+
});
|
|
696
|
+
})
|
|
697
|
+
}) {
|
|
698
|
+
};
|
|
699
|
+
var SnippetsFormat = class extends Schema.Class("SnippetsFormat")({
|
|
700
|
+
query: Schema.String.annotations({
|
|
701
|
+
description: "Query to find relevant content snippets from the page"
|
|
702
|
+
}),
|
|
703
|
+
maxSnippets: Schema.optional(
|
|
704
|
+
Schema.Int.pipe(Schema.greaterThanOrEqualTo(1), Schema.lessThanOrEqualTo(50))
|
|
705
|
+
).annotations({
|
|
706
|
+
description: "Maximum number of snippets to return (1-50, default: 5)"
|
|
707
|
+
}),
|
|
708
|
+
targetSnippetSize: Schema.optional(
|
|
709
|
+
Schema.Int.pipe(Schema.greaterThanOrEqualTo(100), Schema.lessThanOrEqualTo(2e3))
|
|
710
|
+
).annotations({
|
|
711
|
+
description: "Target snippet size in characters (100-2000, default: 384)"
|
|
712
|
+
})
|
|
713
|
+
}) {
|
|
714
|
+
};
|
|
715
|
+
var Format = Schema.Union(
|
|
716
|
+
Schema.Literal("markdown").annotations({
|
|
717
|
+
description: "Return as cleaned markdown content"
|
|
718
|
+
}),
|
|
719
|
+
Schema.Literal("html").annotations({
|
|
720
|
+
description: "Return as raw HTML content"
|
|
721
|
+
}),
|
|
722
|
+
Schema.Literal("summary").annotations({
|
|
723
|
+
description: "Return AI-generated summary of the page content"
|
|
724
|
+
}),
|
|
725
|
+
SnippetsFormat
|
|
726
|
+
).annotations({
|
|
727
|
+
description: "Output format for the fetched content"
|
|
728
|
+
});
|
|
729
|
+
var FetchParams = class extends Schema.Class("FetchParams")({
|
|
730
|
+
url: Schema.String.annotations({
|
|
731
|
+
description: "The URL to fetch content from"
|
|
732
|
+
}),
|
|
733
|
+
format: Format,
|
|
734
|
+
includeMeta: Schema.Boolean.annotations({
|
|
735
|
+
description: "Whether to include page meta tags in the response"
|
|
736
|
+
})
|
|
737
|
+
}) {
|
|
738
|
+
};
|
|
739
|
+
var FetchResult2 = class extends Schema.Class("FetchResult")({
|
|
740
|
+
content: Schema.String,
|
|
741
|
+
url: Schema.String,
|
|
742
|
+
meta: Schema.optional(PageMeta)
|
|
743
|
+
}) {
|
|
744
|
+
};
|
|
745
|
+
var FetchTool = Tool.make("fetch", {
|
|
746
|
+
description: "Fetch and extract content from any URL.",
|
|
747
|
+
parameters: FetchParams.fields,
|
|
748
|
+
success: FetchResult2
|
|
749
|
+
}).annotate(Tool.Readonly, true).annotate(Tool.Destructive, false);
|
|
750
|
+
var Fetch = class extends Effect.Service()("Fetch", {
|
|
751
|
+
dependencies: [ExpandClient.Default],
|
|
752
|
+
scoped: Effect.gen(function* () {
|
|
753
|
+
const client = yield* ExpandClient;
|
|
754
|
+
const fetch = Effect.fn("Fetch.fetch")(function* ({ url, format, includeMeta }) {
|
|
755
|
+
const formatLabel = format instanceof SnippetsFormat ? "snippets" : format;
|
|
756
|
+
yield* Effect.logDebug(`Fetching: ${url}`).pipe(Effect.annotateLogs({ format: formatLabel, includeMeta }));
|
|
757
|
+
const result = yield* client.fetch({
|
|
758
|
+
url,
|
|
759
|
+
select: {
|
|
760
|
+
markdown: format === "markdown",
|
|
761
|
+
html: format === "html",
|
|
762
|
+
summary: format === "summary",
|
|
763
|
+
snippets: format instanceof SnippetsFormat ? { ...format } : void 0,
|
|
764
|
+
meta: includeMeta
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
const content = result.data.markdown ?? result.data.html ?? result.data.summary ?? result.data.snippets?.map((snippet) => `${snippet.text} (Score: ${snippet.score})`).join("\n") ?? "";
|
|
768
|
+
yield* Effect.logDebug(`\u{1F4E4} Fetched successfully: ${result.data.response.url}`);
|
|
769
|
+
return new FetchResult2({
|
|
770
|
+
content,
|
|
771
|
+
url: result.data.response.url,
|
|
772
|
+
meta: result.data.meta
|
|
773
|
+
});
|
|
774
|
+
}, Effect.orDie);
|
|
775
|
+
return { fetch };
|
|
776
|
+
})
|
|
777
|
+
}) {
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
// src/Toolkit.ts
|
|
781
|
+
var toolkit = Toolkit.make(FetchTool);
|
|
782
|
+
var ToolkitLayer = toolkit.toLayer(
|
|
783
|
+
Effect.gen(function* () {
|
|
784
|
+
yield* Effect.logInfo("Registering tools: fetch");
|
|
785
|
+
const fetchService = yield* Fetch;
|
|
786
|
+
return toolkit.of({
|
|
787
|
+
fetch: fetchService.fetch
|
|
788
|
+
});
|
|
789
|
+
})
|
|
790
|
+
).pipe(Layer.provide(Fetch.Default), Layer.provide(ExpandClient.Default), Layer.provide(NodeHttpClient.layerUndici));
|
|
791
|
+
var ExpandToolKit = McpServer.toolkit(toolkit).pipe(Layer.provide(ToolkitLayer));
|
|
792
|
+
|
|
793
|
+
// src/Server.ts
|
|
794
|
+
var ServerInfo = {
|
|
795
|
+
name: "expandai-mcp-server",
|
|
796
|
+
version: package_default.version
|
|
797
|
+
};
|
|
798
|
+
var CurrentRequestHeaders = class extends Context.Tag("CurrentRequestHeaders")() {
|
|
799
|
+
};
|
|
800
|
+
var makeServerLayer = (options = {}) => options.includeDocs ? Layer.mergeAll(ExpandToolKit, ExpandDocs) : ExpandToolKit;
|
|
801
|
+
|
|
802
|
+
export { CurrentRequestHeaders, ServerInfo, makeServerLayer };
|