@livestore/cli 0.4.0 → 0.5.0-dev.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/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/fixtures/mock-config.d.ts +8 -9
- package/dist/__tests__/fixtures/mock-config.d.ts.map +1 -1
- package/dist/__tests__/fixtures/mock-config.js +18 -8
- package/dist/__tests__/fixtures/mock-config.js.map +1 -1
- package/dist/__tests__/sync-operations.test.js +32 -22
- package/dist/__tests__/sync-operations.test.js.map +1 -1
- package/dist/bin.js +7 -6
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +2 -23
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/__fixtures__/mcp-protocol-server.d.ts +2 -0
- package/dist/commands/__fixtures__/mcp-protocol-server.d.ts.map +1 -0
- package/dist/commands/__fixtures__/mcp-protocol-server.js +10 -0
- package/dist/commands/__fixtures__/mcp-protocol-server.js.map +1 -0
- package/dist/commands/import-export.d.ts +5 -19
- package/dist/commands/import-export.d.ts.map +1 -1
- package/dist/commands/import-export.js +13 -13
- package/dist/commands/import-export.js.map +1 -1
- package/dist/commands/mcp-coach.d.ts +8 -7
- package/dist/commands/mcp-coach.d.ts.map +1 -1
- package/dist/commands/mcp-coach.js +11 -11
- package/dist/commands/mcp-coach.js.map +1 -1
- package/dist/commands/mcp-protocols.d.ts +7 -0
- package/dist/commands/mcp-protocols.d.ts.map +1 -0
- package/dist/commands/mcp-protocols.js +12 -0
- package/dist/commands/mcp-protocols.js.map +1 -0
- package/dist/commands/mcp-protocols.test.d.ts +2 -0
- package/dist/commands/mcp-protocols.test.d.ts.map +1 -0
- package/dist/commands/mcp-protocols.test.js +60 -0
- package/dist/commands/mcp-protocols.test.js.map +1 -0
- package/dist/commands/mcp-stdio.test.d.ts +2 -0
- package/dist/commands/mcp-stdio.test.d.ts.map +1 -0
- package/dist/commands/mcp-stdio.test.js +79 -0
- package/dist/commands/mcp-stdio.test.js.map +1 -0
- package/dist/commands/mcp-tool-handlers.d.ts +1 -1
- package/dist/commands/mcp-tool-handlers.d.ts.map +1 -1
- package/dist/commands/mcp-tool-handlers.js +9 -8
- package/dist/commands/mcp-tool-handlers.js.map +1 -1
- package/dist/commands/mcp-tools-defs.d.ts +74 -73
- package/dist/commands/mcp-tools-defs.d.ts.map +1 -1
- package/dist/commands/mcp-tools-defs.js +64 -57
- package/dist/commands/mcp-tools-defs.js.map +1 -1
- package/dist/commands/mcp-tools-defs.test.d.ts +2 -0
- package/dist/commands/mcp-tools-defs.test.d.ts.map +1 -0
- package/dist/commands/mcp-tools-defs.test.js +11 -0
- package/dist/commands/mcp-tools-defs.test.js.map +1 -0
- package/dist/commands/mcp.d.ts +1 -3
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +15 -14
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/new-project.d.ts +19 -27
- package/dist/commands/new-project.d.ts.map +1 -1
- package/dist/commands/new-project.js +19 -18
- package/dist/commands/new-project.js.map +1 -1
- package/dist/mcp-content/architecture.d.ts +1 -1
- package/dist/mcp-content/architecture.d.ts.map +1 -1
- package/dist/mcp-content/architecture.js +3 -3
- package/dist/mcp-content/features.d.ts +1 -1
- package/dist/mcp-content/features.d.ts.map +1 -1
- package/dist/mcp-content/features.js +1 -1
- package/dist/mcp-content/getting-started.d.ts +1 -1
- package/dist/mcp-content/getting-started.d.ts.map +1 -1
- package/dist/mcp-content/getting-started.js +37 -37
- package/dist/mcp-content/overview.d.ts +1 -1
- package/dist/mcp-content/overview.d.ts.map +1 -1
- package/dist/mcp-content/overview.js +6 -6
- package/dist/mcp-content/schemas/blog.d.ts +1 -1
- package/dist/mcp-content/schemas/blog.d.ts.map +1 -1
- package/dist/mcp-content/schemas/blog.js +25 -25
- package/dist/mcp-content/schemas/ecommerce.d.ts +1 -1
- package/dist/mcp-content/schemas/ecommerce.d.ts.map +1 -1
- package/dist/mcp-content/schemas/ecommerce.js +67 -67
- package/dist/mcp-content/schemas/social.d.ts +1 -1
- package/dist/mcp-content/schemas/social.d.ts.map +1 -1
- package/dist/mcp-content/schemas/social.js +48 -48
- package/dist/mcp-content/schemas/todo.d.ts +1 -1
- package/dist/mcp-content/schemas/todo.d.ts.map +1 -1
- package/dist/mcp-content/schemas/todo.js +35 -35
- package/dist/mcp-runtime/runtime.d.ts +5 -5
- package/dist/mcp-runtime/runtime.d.ts.map +1 -1
- package/dist/mcp-runtime/runtime.js +5 -5
- package/dist/mcp-runtime/runtime.js.map +1 -1
- package/dist/mod.js +1 -1
- package/dist/module-loader.d.ts +2 -2
- package/dist/module-loader.d.ts.map +1 -1
- package/dist/module-loader.js +8 -8
- package/dist/module-loader.js.map +1 -1
- package/dist/package-manager.d.ts +3 -3
- package/dist/package-manager.d.ts.map +1 -1
- package/dist/package-manager.test.js +1 -1
- package/dist/sync-operations.d.ts +24 -30
- package/dist/sync-operations.d.ts.map +1 -1
- package/dist/sync-operations.js +15 -15
- package/dist/sync-operations.js.map +1 -1
- package/package.json +45 -59
- package/src/__tests__/fixtures/.gitignore +1 -0
- package/src/__tests__/fixtures/mock-config.ts +21 -10
- package/src/__tests__/sync-operations.test.ts +36 -35
- package/src/bin.ts +7 -6
- package/src/cli.ts +1 -1
- package/src/commands/__fixtures__/mcp-protocol-server.ts +12 -0
- package/src/commands/import-export.ts +29 -29
- package/src/commands/mcp-coach.ts +13 -12
- package/src/commands/mcp-protocols.test.ts +76 -0
- package/src/commands/mcp-protocols.ts +12 -0
- package/src/commands/mcp-stdio.test.ts +93 -0
- package/src/commands/mcp-tool-handlers.ts +11 -8
- package/src/commands/mcp-tools-defs.test.ts +13 -0
- package/src/commands/mcp-tools-defs.ts +57 -55
- package/src/commands/mcp.ts +9 -4
- package/src/commands/new-project.ts +50 -37
- package/src/mcp-content/architecture.ts +3 -3
- package/src/mcp-content/features.ts +1 -1
- package/src/mcp-content/getting-started.ts +37 -37
- package/src/mcp-content/overview.ts +6 -6
- package/src/mcp-content/schemas/blog.ts +25 -25
- package/src/mcp-content/schemas/ecommerce.ts +67 -67
- package/src/mcp-content/schemas/social.ts +48 -48
- package/src/mcp-content/schemas/todo.ts +35 -35
- package/src/mcp-runtime/runtime.ts +7 -7
- package/src/module-loader.ts +11 -12
- package/src/sync-operations.ts +26 -19
package/src/bin.ts
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { liveStoreVersion } from '@livestore/common'
|
|
4
|
-
import { Console, Effect, FetchHttpClient, Layer, Logger,
|
|
4
|
+
import { Console, Effect, FetchHttpClient, Layer, Logger, References } from '@livestore/utils/effect'
|
|
5
5
|
import { Cli, PlatformNode } from '@livestore/utils/node'
|
|
6
6
|
|
|
7
7
|
import { command } from './cli.ts'
|
|
8
8
|
|
|
9
9
|
const cli = Cli.Command.run(command, {
|
|
10
|
-
name: 'livestore',
|
|
11
10
|
version: liveStoreVersion,
|
|
12
11
|
})
|
|
13
12
|
|
|
14
|
-
const showExperimentalWarning = Console.
|
|
13
|
+
const showExperimentalWarning = Console.error('⚠️ Warning: LiveStore CLI is experimental and under active development')
|
|
15
14
|
|
|
16
15
|
const layer = Layer.mergeAll(
|
|
17
|
-
PlatformNode.
|
|
16
|
+
PlatformNode.NodeServices.layer,
|
|
18
17
|
FetchHttpClient.layer,
|
|
19
|
-
|
|
18
|
+
Layer.succeed(References.MinimumLogLevel, 'Info'),
|
|
19
|
+
// Keep stdout available for command data and protocol transports such as MCP stdio.
|
|
20
|
+
Layer.succeed(Logger.LogToStderr, true),
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
Effect.gen(function* () {
|
|
23
24
|
yield* showExperimentalWarning
|
|
24
|
-
return yield* cli
|
|
25
|
+
return yield* cli
|
|
25
26
|
}).pipe(Effect.provide(layer), PlatformNode.NodeRuntime.runMain)
|
package/src/cli.ts
CHANGED
|
@@ -5,5 +5,5 @@ import { mcpCommand } from './commands/mcp.ts'
|
|
|
5
5
|
import { createCommand } from './commands/new-project.ts'
|
|
6
6
|
|
|
7
7
|
export const command = Cli.Command.make('livestore', {
|
|
8
|
-
verbose: Cli.
|
|
8
|
+
verbose: Cli.Flag.Boolean('verbose').pipe(Cli.Flag.withDefault(false)),
|
|
9
9
|
}).pipe(Cli.Command.withSubcommands([mcpCommand, createCommand, syncCommand]))
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from 'effect/ai'
|
|
2
|
+
|
|
3
|
+
import { Effect, Layer } from '@livestore/utils/effect'
|
|
4
|
+
import { PlatformNode } from '@livestore/utils/node'
|
|
5
|
+
|
|
6
|
+
import { supportedMcpProtocols } from '../mcp-protocols.ts'
|
|
7
|
+
|
|
8
|
+
McpServer.layerStdio({
|
|
9
|
+
name: 'livestore-mcp-protocol-test',
|
|
10
|
+
version: '0.0.0',
|
|
11
|
+
protocols: supportedMcpProtocols,
|
|
12
|
+
}).pipe(Layer.provide(PlatformNode.NodeStdio.layer), Layer.launch, Effect.scoped, PlatformNode.NodeRuntime.runMain)
|
|
@@ -6,8 +6,8 @@ import { Cli } from '@livestore/utils/node'
|
|
|
6
6
|
|
|
7
7
|
import * as SyncOps from '../sync-operations.ts'
|
|
8
8
|
|
|
9
|
-
const jsonStringifyPretty =
|
|
10
|
-
const jsonParse = Schema.decodeUnknownSync(Schema.
|
|
9
|
+
const jsonStringifyPretty = (value: SyncOps.ExportFile) => JSON.stringify(value, null, 2)
|
|
10
|
+
const jsonParse = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown))
|
|
11
11
|
|
|
12
12
|
const LARGE_EVENT_WARNING_THRESHOLD = 100_000
|
|
13
13
|
|
|
@@ -171,19 +171,19 @@ const importEvents = ({
|
|
|
171
171
|
export const exportCommand = Cli.Command.make(
|
|
172
172
|
'export',
|
|
173
173
|
{
|
|
174
|
-
config: Cli.
|
|
175
|
-
Cli.
|
|
176
|
-
Cli.
|
|
174
|
+
config: Cli.Flag.String('config').pipe(
|
|
175
|
+
Cli.Flag.withAlias('c'),
|
|
176
|
+
Cli.Flag.withDescription('Path to the config module that exports schema and syncBackend'),
|
|
177
177
|
),
|
|
178
|
-
storeId: Cli.
|
|
179
|
-
Cli.
|
|
180
|
-
Cli.
|
|
178
|
+
storeId: Cli.Flag.String('store-id').pipe(
|
|
179
|
+
Cli.Flag.withAlias('i'),
|
|
180
|
+
Cli.Flag.withDescription('Store identifier'),
|
|
181
181
|
),
|
|
182
|
-
clientId: Cli.
|
|
183
|
-
Cli.
|
|
184
|
-
Cli.
|
|
182
|
+
clientId: Cli.Flag.String('client-id').pipe(
|
|
183
|
+
Cli.Flag.withDefault('cli-export'),
|
|
184
|
+
Cli.Flag.withDescription('Client identifier for the sync connection'),
|
|
185
185
|
),
|
|
186
|
-
output: Cli.
|
|
186
|
+
output: Cli.Argument.String('file').pipe(Cli.Argument.withDescription('Output JSON file path')),
|
|
187
187
|
},
|
|
188
188
|
Effect.fn(function* ({
|
|
189
189
|
config,
|
|
@@ -218,28 +218,28 @@ export const exportCommand = Cli.Command.make(
|
|
|
218
218
|
export const importCommand = Cli.Command.make(
|
|
219
219
|
'import',
|
|
220
220
|
{
|
|
221
|
-
config: Cli.
|
|
222
|
-
Cli.
|
|
223
|
-
Cli.
|
|
221
|
+
config: Cli.Flag.String('config').pipe(
|
|
222
|
+
Cli.Flag.withAlias('c'),
|
|
223
|
+
Cli.Flag.withDescription('Path to the config module that exports schema and syncBackend'),
|
|
224
224
|
),
|
|
225
|
-
storeId: Cli.
|
|
226
|
-
Cli.
|
|
227
|
-
Cli.
|
|
225
|
+
storeId: Cli.Flag.String('store-id').pipe(
|
|
226
|
+
Cli.Flag.withAlias('i'),
|
|
227
|
+
Cli.Flag.withDescription('Store identifier'),
|
|
228
228
|
),
|
|
229
|
-
clientId: Cli.
|
|
230
|
-
Cli.
|
|
231
|
-
Cli.
|
|
229
|
+
clientId: Cli.Flag.String('client-id').pipe(
|
|
230
|
+
Cli.Flag.withDefault('cli-import'),
|
|
231
|
+
Cli.Flag.withDescription('Client identifier for the sync connection'),
|
|
232
232
|
),
|
|
233
|
-
force: Cli.
|
|
234
|
-
Cli.
|
|
235
|
-
Cli.
|
|
236
|
-
Cli.
|
|
233
|
+
force: Cli.Flag.Boolean('force').pipe(
|
|
234
|
+
Cli.Flag.withAlias('f'),
|
|
235
|
+
Cli.Flag.withDefault(false),
|
|
236
|
+
Cli.Flag.withDescription('Force import even if store ID does not match'),
|
|
237
237
|
),
|
|
238
|
-
dryRun: Cli.
|
|
239
|
-
Cli.
|
|
240
|
-
Cli.
|
|
238
|
+
dryRun: Cli.Flag.Boolean('dry-run').pipe(
|
|
239
|
+
Cli.Flag.withDefault(false),
|
|
240
|
+
Cli.Flag.withDescription('Validate the import file without actually importing'),
|
|
241
241
|
),
|
|
242
|
-
input: Cli.
|
|
242
|
+
input: Cli.Argument.String('file').pipe(Cli.Argument.withDescription('Input JSON file to import')),
|
|
243
243
|
},
|
|
244
244
|
Effect.fn(function* ({
|
|
245
245
|
config,
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { LanguageModel } from '
|
|
1
|
+
import { AiError, LanguageModel, Prompt, Tool } from 'effect/ai'
|
|
2
2
|
import { OpenAiClient, OpenAiLanguageModel } from '@effect/ai-openai'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { Config, Effect, FetchHttpClient, Layer, Schema } from '@livestore/utils/effect'
|
|
5
5
|
|
|
6
6
|
// Define the coach tool that analyzes LiveStore usage
|
|
7
7
|
export const coachTool = Tool.make('livestore_coach', {
|
|
8
8
|
description:
|
|
9
9
|
'Analyze LiveStore code (schemas, queries, mutations, etc.) and provide AI-powered feedback on best practices, performance, and improvements.',
|
|
10
|
-
parameters: {
|
|
11
|
-
code: Schema.String.
|
|
10
|
+
parameters: Schema.Struct({
|
|
11
|
+
code: Schema.String.annotate({
|
|
12
12
|
description: 'The LiveStore code to analyze (TypeScript/JavaScript)',
|
|
13
13
|
}),
|
|
14
14
|
codeType: Schema.optional(
|
|
15
|
-
Schema.String.
|
|
15
|
+
Schema.String.annotate({
|
|
16
16
|
description: "Type of code being analyzed: 'schema', 'query', 'mutation', 'component', or 'general'",
|
|
17
17
|
}),
|
|
18
18
|
),
|
|
19
|
-
},
|
|
19
|
+
}),
|
|
20
20
|
success: Schema.Struct({
|
|
21
|
-
feedback: Schema.String.
|
|
21
|
+
feedback: Schema.String.annotate({
|
|
22
22
|
description: 'AI-generated feedback and recommendations for the code',
|
|
23
23
|
}),
|
|
24
24
|
score: Schema.optional(
|
|
25
|
-
Schema.
|
|
25
|
+
Schema.Finite.annotate({
|
|
26
26
|
description: 'Code quality score from 1-10 (optional)',
|
|
27
27
|
}),
|
|
28
28
|
),
|
|
29
29
|
suggestions: Schema.Array(
|
|
30
|
-
Schema.String.
|
|
30
|
+
Schema.String.annotate({
|
|
31
31
|
description: 'Specific actionable suggestions for improvement',
|
|
32
32
|
}),
|
|
33
|
-
).
|
|
33
|
+
).annotate({
|
|
34
34
|
description: 'List of specific improvement suggestions',
|
|
35
35
|
}),
|
|
36
36
|
}),
|
|
@@ -39,7 +39,7 @@ export const coachTool = Tool.make('livestore_coach', {
|
|
|
39
39
|
|
|
40
40
|
/** Composed layer: HttpClient → OpenAiClient → LanguageModel */
|
|
41
41
|
const LanguageModelLayer = OpenAiLanguageModel.layer({ model: 'gpt-4o-mini' }).pipe(
|
|
42
|
-
Layer.provide(OpenAiClient.layerConfig({ apiKey: Config.
|
|
42
|
+
Layer.provide(OpenAiClient.layerConfig({ apiKey: Config.Redacted('OPENAI_API_KEY') })),
|
|
43
43
|
Layer.provide(FetchHttpClient.layer),
|
|
44
44
|
)
|
|
45
45
|
|
|
@@ -58,7 +58,8 @@ type CoachToolResult = {
|
|
|
58
58
|
export const coachToolHandler: (input: CoachToolHandlerInput) => Effect.Effect<CoachToolResult, AiError.AiError> =
|
|
59
59
|
Effect.fn('mcp-coach-handler')(({ code, codeType }) => {
|
|
60
60
|
const effect = Effect.gen(function* () {
|
|
61
|
-
const codeTypeContext =
|
|
61
|
+
const codeTypeContext =
|
|
62
|
+
codeType !== undefined ? `This is ${codeType} code using LiveStore. ` : 'This is LiveStore code. '
|
|
62
63
|
|
|
63
64
|
const prompt = Prompt.makeMessage('user', {
|
|
64
65
|
content: [
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
4
|
+
|
|
5
|
+
import { supportedMcpProtocols } from './mcp-protocols.ts'
|
|
6
|
+
|
|
7
|
+
const protocolVersions = ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'] as const
|
|
8
|
+
|
|
9
|
+
Vitest.describe('supportedMcpProtocols', () => {
|
|
10
|
+
Vitest.it('keeps the fallback protocol newest-first', () => {
|
|
11
|
+
Vitest.expect(supportedMcpProtocols.map((protocol) => protocol.protocolVersion)).toEqual(protocolVersions)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
Vitest.it.each(protocolVersions)('negotiates an exact %s stdio initialize offer', async (protocolVersion) => {
|
|
15
|
+
const response = await initialize(protocolVersion)
|
|
16
|
+
|
|
17
|
+
Vitest.expect(response.result?.protocolVersion).toBe(protocolVersion)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const initialize = (protocolVersion: (typeof protocolVersions)[number]) =>
|
|
22
|
+
new Promise<InitializeResponse>((resolve, reject) => {
|
|
23
|
+
const child = spawn('bun', [new URL('./__fixtures__/mcp-protocol-server.ts', import.meta.url).pathname], {
|
|
24
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
25
|
+
})
|
|
26
|
+
let stdout = ''
|
|
27
|
+
let stderr = ''
|
|
28
|
+
|
|
29
|
+
const timeout = setTimeout(() => {
|
|
30
|
+
child.kill('SIGTERM')
|
|
31
|
+
reject(new Error(`MCP initialize timed out. stdout=${stdout} stderr=${stderr}`))
|
|
32
|
+
}, 5_000)
|
|
33
|
+
|
|
34
|
+
child.stdout.setEncoding('utf8')
|
|
35
|
+
child.stderr.setEncoding('utf8')
|
|
36
|
+
child.stdout.on('data', (chunk: string) => {
|
|
37
|
+
stdout += chunk
|
|
38
|
+
if (stdout.endsWith('\n') === false) return
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const response = JSON.parse(stdout.trim()) as InitializeResponse
|
|
42
|
+
clearTimeout(timeout)
|
|
43
|
+
child.kill('SIGTERM')
|
|
44
|
+
resolve(response)
|
|
45
|
+
} catch (error) {
|
|
46
|
+
clearTimeout(timeout)
|
|
47
|
+
child.kill('SIGTERM')
|
|
48
|
+
reject(error)
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
child.stderr.on('data', (chunk: string) => {
|
|
52
|
+
stderr += chunk
|
|
53
|
+
})
|
|
54
|
+
child.on('error', (error) => {
|
|
55
|
+
clearTimeout(timeout)
|
|
56
|
+
reject(error)
|
|
57
|
+
})
|
|
58
|
+
child.stdin.write(
|
|
59
|
+
`${JSON.stringify({
|
|
60
|
+
jsonrpc: '2.0',
|
|
61
|
+
id: 1,
|
|
62
|
+
method: 'initialize',
|
|
63
|
+
params: {
|
|
64
|
+
protocolVersion,
|
|
65
|
+
capabilities: {},
|
|
66
|
+
clientInfo: { name: 'livestore-mcp-protocol-test', version: '0.0.0' },
|
|
67
|
+
},
|
|
68
|
+
})}\n`,
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
interface InitializeResponse {
|
|
73
|
+
readonly result?: {
|
|
74
|
+
readonly protocolVersion?: string
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpProtocol } from 'effect/ai'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP stdio selects an exact offered version when available and otherwise
|
|
5
|
+
* falls back to the first adapter, so keep every supported adapter newest-first.
|
|
6
|
+
*/
|
|
7
|
+
export const supportedMcpProtocols = [
|
|
8
|
+
McpProtocol.v2025_11_25,
|
|
9
|
+
McpProtocol.v2025_06_18,
|
|
10
|
+
McpProtocol.v2025_03_26,
|
|
11
|
+
McpProtocol.v2024_11_05,
|
|
12
|
+
] as const
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
4
|
+
|
|
5
|
+
Vitest.describe('mcp server stdio', () => {
|
|
6
|
+
Vitest.it('reserves stdout for JSON-RPC messages', async () => {
|
|
7
|
+
const response = await initialize()
|
|
8
|
+
|
|
9
|
+
Vitest.expect(response.result?.protocolVersion).toBe('2025-06-18')
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
/** Exercises the real CLI entrypoint so root-level warnings and logs cannot corrupt MCP framing. */
|
|
14
|
+
const initialize = () =>
|
|
15
|
+
new Promise<InitializeResponse>((resolve, reject) => {
|
|
16
|
+
const child = spawn('bun', [new URL('../bin.ts', import.meta.url).pathname, 'mcp', 'server'], {
|
|
17
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
18
|
+
})
|
|
19
|
+
let stdoutBuffer = ''
|
|
20
|
+
let stderr = ''
|
|
21
|
+
let settled = false
|
|
22
|
+
|
|
23
|
+
const finish = (result: { readonly response: InitializeResponse } | { readonly error: unknown }) => {
|
|
24
|
+
if (settled === true) return
|
|
25
|
+
settled = true
|
|
26
|
+
clearTimeout(timeout)
|
|
27
|
+
child.kill('SIGTERM')
|
|
28
|
+
if ('error' in result) reject(result.error)
|
|
29
|
+
else resolve(result.response)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const timeout = setTimeout(() => {
|
|
33
|
+
finish({ error: new Error(`MCP initialize timed out. stdout=${stdoutBuffer} stderr=${stderr}`) })
|
|
34
|
+
}, 5_000)
|
|
35
|
+
|
|
36
|
+
child.stdout.setEncoding('utf8')
|
|
37
|
+
child.stderr.setEncoding('utf8')
|
|
38
|
+
child.stdout.on('data', (chunk: string) => {
|
|
39
|
+
stdoutBuffer += chunk
|
|
40
|
+
const lines = stdoutBuffer.split('\n')
|
|
41
|
+
stdoutBuffer = lines.pop() ?? ''
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (line.length === 0) continue
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const message = JSON.parse(line) as InitializeResponse
|
|
48
|
+
if (message.jsonrpc !== '2.0') {
|
|
49
|
+
finish({ error: new Error(`MCP stdout contained a non-JSON-RPC message: ${line}`) })
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
if (message.id === 1) {
|
|
53
|
+
finish({ response: message })
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
finish({ error: new Error(`MCP stdout contained invalid JSON: ${line}`, { cause: error }) })
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
child.stderr.on('data', (chunk: string) => {
|
|
63
|
+
stderr += chunk
|
|
64
|
+
})
|
|
65
|
+
child.on('error', (error) => {
|
|
66
|
+
finish({ error })
|
|
67
|
+
})
|
|
68
|
+
child.on('exit', (code) => {
|
|
69
|
+
if (settled === false) {
|
|
70
|
+
finish({ error: new Error(`MCP server exited before initialize completed (code ${code}). stderr=${stderr}`) })
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
child.stdin.write(
|
|
74
|
+
`${JSON.stringify({
|
|
75
|
+
jsonrpc: '2.0',
|
|
76
|
+
id: 1,
|
|
77
|
+
method: 'initialize',
|
|
78
|
+
params: {
|
|
79
|
+
protocolVersion: '2025-06-18',
|
|
80
|
+
capabilities: {},
|
|
81
|
+
clientInfo: { name: 'livestore-mcp-stdio-test', version: '0.0.0' },
|
|
82
|
+
},
|
|
83
|
+
})}\n`,
|
|
84
|
+
)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
interface InitializeResponse {
|
|
88
|
+
readonly jsonrpc?: unknown
|
|
89
|
+
readonly id?: unknown
|
|
90
|
+
readonly result?: {
|
|
91
|
+
readonly protocolVersion?: string
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type Toolkit } from 'effect/ai'
|
|
2
|
+
|
|
3
|
+
import { Effect, FetchHttpClient, Layer } from '@livestore/utils/effect'
|
|
2
4
|
import { PlatformNode } from '@livestore/utils/node'
|
|
3
5
|
|
|
4
6
|
import { blogSchemaContent } from '../mcp-content/schemas/blog.ts'
|
|
@@ -59,13 +61,14 @@ export const schema = Schema.create({
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// Generate a basic custom schema based on description
|
|
62
|
-
const tableName =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
const tableName =
|
|
65
|
+
customDescription.toLowerCase().includes('user') === true
|
|
66
|
+
? 'users'
|
|
67
|
+
: customDescription.toLowerCase().includes('product') === true
|
|
68
|
+
? 'products'
|
|
69
|
+
: customDescription.toLowerCase().includes('post') === true
|
|
70
|
+
? 'posts'
|
|
71
|
+
: 'items'
|
|
69
72
|
|
|
70
73
|
schemaCode = `// Custom schema based on: ${customDescription}
|
|
71
74
|
import { Schema } from '@livestore/livestore'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Tool } from 'effect/ai'
|
|
2
|
+
|
|
3
|
+
import { Vitest } from '@livestore/utils-dev/node-vitest'
|
|
4
|
+
|
|
5
|
+
import { livestoreToolkit } from './mcp-tools-defs.ts'
|
|
6
|
+
|
|
7
|
+
Vitest.describe('livestoreToolkit', () => {
|
|
8
|
+
Vitest.it('exposes MCP-compatible object parameter schemas', () => {
|
|
9
|
+
for (const tool of Object.values(livestoreToolkit.tools)) {
|
|
10
|
+
Vitest.expect(Tool.getJsonSchema(tool), tool.name).toMatchObject({ type: 'object' })
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
})
|