@expandai/mcp-server 0.1.1
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 +190 -0
- package/README.md +67 -0
- package/dist/main.cjs +814 -0
- package/package.json +61 -0
- package/src/ExpandClient.ts +26 -0
- package/src/generated/ExpandClient.ts +821 -0
- package/src/main.ts +24 -0
- package/src/resources/ExpandDocs.ts +14 -0
- package/src/tools/Fetch.ts +94 -0
- package/src/tools/index.ts +33 -0
package/src/main.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@effect/ai'
|
|
3
|
+
import { NodeRuntime, NodeSink, NodeStream } from '@effect/platform-node'
|
|
4
|
+
import { Config, Effect, Layer, Logger, LogLevel } from 'effect'
|
|
5
|
+
import packageJson from '../package.json' with { type: 'json' }
|
|
6
|
+
import { ExpandDocs } from './resources/ExpandDocs.js'
|
|
7
|
+
import { ExpandTools } from './tools/index.js'
|
|
8
|
+
|
|
9
|
+
const enableDocs = Config.withDefault(Config.boolean('EXPAND_ENABLE_DOCS'), () => false)
|
|
10
|
+
|
|
11
|
+
const program = Effect.gen(function* () {
|
|
12
|
+
const docsEnabled = yield* enableDocs
|
|
13
|
+
|
|
14
|
+
const layers = docsEnabled ? Layer.mergeAll(ExpandTools, ExpandDocs) : ExpandTools
|
|
15
|
+
|
|
16
|
+
yield* McpServer.layerStdio({
|
|
17
|
+
name: 'expandai-mcp-server',
|
|
18
|
+
version: packageJson.version,
|
|
19
|
+
stdin: NodeStream.stdin,
|
|
20
|
+
stdout: NodeSink.stdout,
|
|
21
|
+
}).pipe(Layer.provide(layers), Layer.provide(Logger.minimumLogLevel(LogLevel.None)), Layer.launch)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
program.pipe(NodeRuntime.runMain)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { McpServer } from '@effect/ai'
|
|
2
|
+
import { Effect } from 'effect'
|
|
3
|
+
|
|
4
|
+
export const ExpandDocs = McpServer.resource({
|
|
5
|
+
uri: 'expand://about',
|
|
6
|
+
name: 'About expand.ai',
|
|
7
|
+
description: 'Key links for the expand.ai platform',
|
|
8
|
+
content: Effect.succeed(`# expand.ai
|
|
9
|
+
|
|
10
|
+
- Website: https://expand.ai
|
|
11
|
+
- Quickstart: https://expand.ai/docs
|
|
12
|
+
- Dashboard: https://expand.ai/dashboard
|
|
13
|
+
`),
|
|
14
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Tool } from '@effect/ai'
|
|
2
|
+
import { Effect, Schema } from 'effect'
|
|
3
|
+
import { ExpandClient } from '../ExpandClient.js'
|
|
4
|
+
import { PageMeta } from '../generated/ExpandClient.js'
|
|
5
|
+
|
|
6
|
+
export class SnippetsFormat extends Schema.Class<SnippetsFormat>('SnippetsFormat')({
|
|
7
|
+
query: Schema.String.annotations({
|
|
8
|
+
description: 'Query to find relevant content snippets from the page',
|
|
9
|
+
}),
|
|
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)',
|
|
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)',
|
|
19
|
+
}),
|
|
20
|
+
}) {}
|
|
21
|
+
|
|
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
|
+
export class FetchParams extends Schema.Class<FetchParams>('FetchParams')({
|
|
38
|
+
url: Schema.String.annotations({
|
|
39
|
+
description: 'The URL to fetch content from',
|
|
40
|
+
}),
|
|
41
|
+
format: Format,
|
|
42
|
+
includeMeta: Schema.Boolean.annotations({
|
|
43
|
+
description: 'Whether to include page meta tags in the response',
|
|
44
|
+
}),
|
|
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
|
+
}) {}
|
|
52
|
+
|
|
53
|
+
export const FetchTool = Tool.make('fetch', {
|
|
54
|
+
description: 'Fetch and extract content from any URL.',
|
|
55
|
+
parameters: FetchParams.fields,
|
|
56
|
+
success: FetchResult,
|
|
57
|
+
})
|
|
58
|
+
.annotate(Tool.Readonly, true)
|
|
59
|
+
.annotate(Tool.Destructive, false)
|
|
60
|
+
|
|
61
|
+
export class Fetch extends Effect.Service<Fetch>()('Fetch', {
|
|
62
|
+
dependencies: [ExpandClient.Default],
|
|
63
|
+
scoped: Effect.gen(function* () {
|
|
64
|
+
const client = yield* ExpandClient
|
|
65
|
+
|
|
66
|
+
const fetch = Effect.fn('Fetch.fetch')(function* ({ url, format, includeMeta }: FetchParams) {
|
|
67
|
+
const result = yield* client.fetch({
|
|
68
|
+
url,
|
|
69
|
+
select: {
|
|
70
|
+
markdown: format === 'markdown',
|
|
71
|
+
html: format === 'html',
|
|
72
|
+
summary: format === 'summary',
|
|
73
|
+
snippets: format instanceof SnippetsFormat ? { ...format } : undefined,
|
|
74
|
+
meta: includeMeta,
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const content =
|
|
79
|
+
result.data.markdown ??
|
|
80
|
+
result.data.html ??
|
|
81
|
+
result.data.summary ??
|
|
82
|
+
result.data.snippets?.map((snippet) => `${snippet.text} (Score: ${snippet.score})`).join('\n') ??
|
|
83
|
+
''
|
|
84
|
+
|
|
85
|
+
return new FetchResult({
|
|
86
|
+
content,
|
|
87
|
+
url: result.data.response.url,
|
|
88
|
+
meta: result.data.meta,
|
|
89
|
+
})
|
|
90
|
+
}, Effect.orDie)
|
|
91
|
+
|
|
92
|
+
return { fetch } as const
|
|
93
|
+
}),
|
|
94
|
+
}) {}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { McpServer, Toolkit } from '@effect/ai'
|
|
2
|
+
import { NodeHttpClient } from '@effect/platform-node'
|
|
3
|
+
import { Effect, Layer } from 'effect'
|
|
4
|
+
import { ExpandClient } from '../ExpandClient.js'
|
|
5
|
+
import { Fetch, FetchTool } from './Fetch.js'
|
|
6
|
+
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Toolkit (add more tools here)
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
const toolkit = Toolkit.make(FetchTool)
|
|
12
|
+
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Toolkit Layer
|
|
15
|
+
// =============================================================================
|
|
16
|
+
|
|
17
|
+
const ToolkitLayer = toolkit
|
|
18
|
+
.toLayer(
|
|
19
|
+
Effect.gen(function* () {
|
|
20
|
+
const fetchService = yield* Fetch
|
|
21
|
+
|
|
22
|
+
return toolkit.of({
|
|
23
|
+
fetch: fetchService.fetch,
|
|
24
|
+
})
|
|
25
|
+
}),
|
|
26
|
+
)
|
|
27
|
+
.pipe(Layer.provide(Fetch.Default), Layer.provide(ExpandClient.Default), Layer.provide(NodeHttpClient.layerUndici))
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Export
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
export const ExpandTools = McpServer.toolkit(toolkit).pipe(Layer.provide(ToolkitLayer))
|