@expandai/mcp-server 0.1.4 → 0.2.0

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/dist/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { ServerInfo, makeServerLayer } from './chunk-TUBB4OP4.js';
2
+ import { ServerInfo, makeServerLayer } from './chunk-MEO7UHLG.js';
3
3
  import { McpServer } from '@effect/ai';
4
4
  import { NodeSink, NodeStream, NodeRuntime } from '@effect/platform-node';
5
5
  import { Config, Effect, Layer, Logger, LogLevel } from 'effect';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expandai/mcp-server",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for expand.ai - Give AI agents access to the web",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/src/Fetch.ts CHANGED
@@ -1,59 +1,51 @@
1
1
  import { Tool } from '@effect/ai'
2
2
  import { Effect, Schema } from 'effect'
3
3
  import { ExpandClient } from './ExpandClient.js'
4
- import { PageMeta } from './Generated.js'
5
4
 
6
- export class SnippetsFormat extends Schema.Class<SnippetsFormat>('SnippetsFormat')({
5
+ export class SearchParams extends Schema.Class<SearchParams>('SearchParams')({
7
6
  query: Schema.String.annotations({
8
- description: 'Query to find relevant content snippets from the page',
7
+ description: 'Query to find relevant content snippets',
9
8
  }),
10
- maxSnippets: Schema.optional(
11
- Schema.Int.pipe(Schema.greaterThanOrEqualTo(1), Schema.lessThanOrEqualTo(50)),
12
- ).annotations({
13
- description: 'Maximum number of snippets to return (1-50, default: 5)',
9
+ minScore: Schema.optionalWith(Schema.Number.pipe(Schema.greaterThanOrEqualTo(0), Schema.lessThanOrEqualTo(1)), {
10
+ default: () => 0.6,
11
+ }).annotations({
12
+ description: 'Minimum relevance score (0-1)',
13
+ default: 0.6,
14
14
  }),
15
- targetSnippetSize: Schema.optional(
16
- Schema.Int.pipe(Schema.greaterThanOrEqualTo(100), Schema.lessThanOrEqualTo(2000)),
17
- ).annotations({
18
- description: 'Target snippet size in characters (100-2000, default: 384)',
15
+ maxResults: Schema.optionalWith(Schema.Int.pipe(Schema.greaterThanOrEqualTo(1), Schema.lessThanOrEqualTo(50)), {
16
+ default: () => 5,
17
+ }).annotations({
18
+ description: 'Maximum snippets to return',
19
+ default: 5,
19
20
  }),
20
21
  }) {}
21
22
 
22
- export const Format = Schema.Union(
23
- Schema.Literal('markdown').annotations({
24
- description: 'Return as cleaned markdown content',
25
- }),
26
- Schema.Literal('html').annotations({
27
- description: 'Return as raw HTML content',
28
- }),
29
- Schema.Literal('summary').annotations({
30
- description: 'Return AI-generated summary of the page content',
31
- }),
32
- SnippetsFormat,
33
- ).annotations({
34
- description: 'Output format for the fetched content',
35
- })
36
-
37
23
  export class FetchParams extends Schema.Class<FetchParams>('FetchParams')({
38
24
  url: Schema.String.annotations({
39
25
  description: 'The URL to fetch content from',
40
26
  }),
41
- format: Format,
42
- includeMeta: Schema.Boolean.annotations({
43
- description: 'Whether to include page meta tags in the response',
27
+ includeMeta: Schema.optionalWith(Schema.Boolean, { default: () => false }).annotations({
28
+ description: 'Include page meta tags (title, description, Open Graph)',
29
+ default: false,
30
+ }),
31
+ includeAppendix: Schema.optionalWith(Schema.Boolean, { default: () => false }).annotations({
32
+ description: 'Include extracted links and sidebar content',
33
+ default: false,
34
+ }),
35
+ includeJson: Schema.optionalWith(Schema.Boolean, { default: () => false }).annotations({
36
+ description: 'Include JSON extracted from network responses and DOM',
37
+ default: false,
38
+ }),
39
+ search: Schema.optional(SearchParams).annotations({
40
+ description: 'Semantic search. When provided, returns snippets instead of markdown.',
44
41
  }),
45
- }) {}
46
-
47
- export class FetchResult extends Schema.Class<FetchResult>('FetchResult')({
48
- content: Schema.String,
49
- url: Schema.String,
50
- meta: Schema.optional(PageMeta),
51
42
  }) {}
52
43
 
53
44
  export const FetchTool = Tool.make('fetch', {
54
- description: 'Fetch and extract content from any URL.',
45
+ description: `Fetch and extract content from any URL.
46
+
47
+ Returns markdown by default. When \`search\` is provided, returns semantically relevant snippets instead.`,
55
48
  parameters: FetchParams.fields,
56
- success: FetchResult,
57
49
  })
58
50
  .annotate(Tool.Readonly, true)
59
51
  .annotate(Tool.Destructive, false)
@@ -63,35 +55,38 @@ export class Fetch extends Effect.Service<Fetch>()('Fetch', {
63
55
  scoped: Effect.gen(function* () {
64
56
  const client = yield* ExpandClient
65
57
 
66
- const fetch = Effect.fn('Fetch.fetch')(function* ({ url, format, includeMeta }: FetchParams) {
67
- const formatLabel = format instanceof SnippetsFormat ? 'snippets' : format
68
- yield* Effect.logDebug(`Fetching: ${url}`).pipe(Effect.annotateLogs({ format: formatLabel, includeMeta }))
58
+ const fetch = Effect.fn('Fetch.fetch')(function* ({
59
+ url,
60
+ includeMeta,
61
+ includeAppendix,
62
+ includeJson,
63
+ search,
64
+ }: FetchParams) {
65
+ const hasSearch = search !== undefined
66
+ yield* Effect.logDebug(`Fetching: ${url}`).pipe(
67
+ Effect.annotateLogs({ hasSearch, includeMeta, includeAppendix, includeJson }),
68
+ )
69
69
 
70
70
  const result = yield* client.fetch({
71
71
  url,
72
72
  select: {
73
- markdown: format === 'markdown',
74
- html: format === 'html',
75
- summary: format === 'summary',
76
- snippets: format instanceof SnippetsFormat ? { ...format } : undefined,
73
+ markdown: !hasSearch,
74
+ snippets: hasSearch
75
+ ? {
76
+ query: search.query,
77
+ maxSnippets: search.maxResults,
78
+ minScore: search.minScore,
79
+ }
80
+ : undefined,
77
81
  meta: includeMeta,
82
+ json: includeJson,
83
+ appendix: includeAppendix,
78
84
  },
79
85
  })
80
86
 
81
- const content =
82
- result.data.markdown ??
83
- result.data.html ??
84
- result.data.summary ??
85
- result.data.snippets?.map((snippet) => `${snippet.text} (Score: ${snippet.score})`).join('\n') ??
86
- ''
87
-
88
- yield* Effect.logDebug(`📤 Fetched successfully: ${result.data.response.url}`)
87
+ yield* Effect.logDebug(`Fetched successfully: ${result.data.response.url}`)
89
88
 
90
- return new FetchResult({
91
- content,
92
- url: result.data.response.url,
93
- meta: result.data.meta,
94
- })
89
+ return result.data
95
90
  }, Effect.orDie)
96
91
 
97
92
  return { fetch } as const