dotdo 0.0.2 → 0.1.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/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/template-literals.js +2 -2
- package/dist/ai/template-literals.js.map +1 -1
- package/dist/api/middleware/auth.js +3 -2
- package/dist/api/middleware/auth.js.map +1 -1
- package/dist/db/iceberg/inverted-index.js +1 -1
- package/dist/db/iceberg/inverted-index.js.map +1 -1
- package/dist/db/iceberg/puffin.js.map +1 -1
- package/dist/db/json-indexes.js.map +1 -1
- package/dist/db/objects.js.map +1 -1
- package/dist/db/primitives/dag-scheduler/index.js +1 -1
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -1
- package/dist/db/primitives/observability.js.map +1 -1
- package/dist/db/primitives/schema-evolution.js.map +1 -1
- package/dist/db/primitives/temporal-store.js.map +1 -1
- package/dist/db/primitives/typed-column-store.js.map +1 -1
- package/dist/db/primitives/utils/duration.js.map +1 -1
- package/dist/db/primitives/utils/murmur3.js +12 -14
- package/dist/db/primitives/utils/murmur3.js.map +1 -1
- package/dist/db/primitives/window-manager.js.map +1 -1
- package/dist/db/stores.js.map +1 -1
- package/dist/db/things.js.map +1 -1
- package/dist/lib/DODispatcher.js +2 -2
- package/dist/lib/DODispatcher.js.map +1 -1
- package/dist/lib/auto-wiring.js.map +1 -1
- package/dist/lib/channels/email.js +1 -1
- package/dist/lib/channels/email.js.map +1 -1
- package/dist/lib/channels/slack-blockkit.js.map +1 -1
- package/dist/lib/cloudflare/ai.js +1 -1
- package/dist/lib/cloudflare/ai.js.map +1 -1
- package/dist/lib/cloudflare/kv.js +1 -1
- package/dist/lib/cloudflare/kv.js.map +1 -1
- package/dist/lib/cloudflare/r2.js +3 -3
- package/dist/lib/cloudflare/r2.js.map +1 -1
- package/dist/lib/cloudflare/vectorize.js.map +1 -1
- package/dist/lib/cloudflare/workflows.js.map +1 -1
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -1
- package/dist/lib/experiments.js.map +1 -1
- package/dist/lib/flags/store.js.map +1 -1
- package/dist/lib/functions/FunctionComposition.js.map +1 -1
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -1
- package/dist/lib/functions/FunctionRegistry.js.map +1 -1
- package/dist/lib/humans/templates.js.map +1 -1
- package/dist/lib/identity.js +2 -2
- package/dist/lib/identity.js.map +1 -1
- package/dist/lib/logging/index.js.map +1 -1
- package/dist/lib/mixins/bash.js +1 -73
- package/dist/lib/mixins/bash.js.map +1 -1
- package/dist/lib/mixins/git.js +0 -5
- package/dist/lib/mixins/git.js.map +1 -1
- package/dist/lib/mixins/npm.js.map +1 -1
- package/dist/lib/noun-id.js.map +1 -1
- package/dist/lib/rate-limit/sliding-window.js.map +1 -1
- package/dist/lib/rpc/bindings.js.map +1 -1
- package/dist/lib/safe-stringify.js.map +1 -1
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -1
- package/dist/lib/sqids.js.map +1 -1
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -1
- package/dist/lib/sql/adapters/pgsql-parser.js +19 -18
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -1
- package/dist/metrics/hunch.js.map +1 -1
- package/dist/objects/API.js +1 -1
- package/dist/objects/API.js.map +1 -1
- package/dist/objects/Agent.js.map +1 -1
- package/dist/objects/Browser.js.map +1 -1
- package/dist/objects/CLI.js.map +1 -1
- package/dist/objects/DOBase.js.map +1 -1
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js.map +1 -1
- package/dist/objects/Entity.js.map +1 -1
- package/dist/objects/Human.js.map +1 -1
- package/dist/objects/IcebergMetadataDO.js.map +1 -1
- package/dist/objects/IntegrationsDO.js.map +1 -1
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -1
- package/dist/objects/Package.js.map +1 -1
- package/dist/objects/Product.js +1 -1
- package/dist/objects/Product.js.map +1 -1
- package/dist/objects/SaaS.js.map +1 -1
- package/dist/objects/SandboxDO.js.map +1 -1
- package/dist/objects/Service.js.map +1 -1
- package/dist/objects/VectorShardDO.js +9 -7
- package/dist/objects/VectorShardDO.js.map +1 -1
- package/dist/objects/Workflow.js.map +1 -1
- package/dist/objects/WorkflowFactory.js.map +1 -1
- package/dist/objects/WorkflowRuntime.js.map +1 -1
- package/dist/objects/lifecycle/Branch.js.map +1 -1
- package/dist/objects/lifecycle/Clone.js +1 -1
- package/dist/objects/lifecycle/Clone.js.map +1 -1
- package/dist/objects/lifecycle/Compact.js.map +1 -1
- package/dist/objects/lifecycle/Shard.js.map +1 -1
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -1
- package/dist/objects/persistence/migration-runner.js.map +1 -1
- package/dist/objects/persistence/replication-manager.js +2 -2
- package/dist/objects/persistence/replication-manager.js.map +1 -1
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -1
- package/dist/objects/persistence/wal-manager.js.map +1 -1
- package/dist/objects/transport/auth-layer.js.map +1 -1
- package/dist/objects/transport/chain.js.map +1 -1
- package/dist/objects/transport/mcp-server.js +7 -6
- package/dist/objects/transport/mcp-server.js.map +1 -1
- package/dist/objects/transport/rest-autowire.js +3 -2
- package/dist/objects/transport/rest-autowire.js.map +1 -1
- package/dist/objects/transport/rest-router.js.map +1 -1
- package/dist/objects/transport/rpc-server.js +18 -15
- package/dist/objects/transport/rpc-server.js.map +1 -1
- package/dist/objects/transport/shared.js +2 -1
- package/dist/objects/transport/shared.js.map +1 -1
- package/dist/snippets/artifacts-ingest.js.map +1 -1
- package/dist/snippets/artifacts-serve.js.map +1 -1
- package/dist/snippets/search.js.map +1 -1
- package/dist/workflows/ScheduleManager.js.map +1 -1
- package/dist/workflows/StepResultStorage.js.map +1 -1
- package/dist/workflows/WaitForEventManager.js.map +1 -1
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -1
- package/dist/workflows/compat/inngest/index.js.map +1 -1
- package/dist/workflows/compat/qstash/index.js.map +1 -1
- package/dist/workflows/compat/temporal/client.js.map +1 -1
- package/dist/workflows/compat/temporal/index.js.map +1 -1
- package/dist/workflows/compat/trigger/index.js.map +1 -1
- package/dist/workflows/compat/utils/index.js.map +1 -1
- package/dist/workflows/context/correlation.js +2 -2
- package/dist/workflows/context/correlation.js.map +1 -1
- package/dist/workflows/context/experiment.js +1 -1
- package/dist/workflows/context/experiment.js.map +1 -1
- package/dist/workflows/context/flag.js +1 -1
- package/dist/workflows/context/flag.js.map +1 -1
- package/dist/workflows/context/measure.js +1 -1
- package/dist/workflows/context/measure.js.map +1 -1
- package/dist/workflows/context/rate-limit.js.map +1 -1
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -1
- package/dist/workflows/data/experiment/index.js.map +1 -1
- package/dist/workflows/data/goal/context.js +1 -1
- package/dist/workflows/data/goal/context.js.map +1 -1
- package/dist/workflows/data/measure/index.js +1 -1
- package/dist/workflows/data/measure/index.js.map +1 -1
- package/dist/workflows/data/stream/index.js +10 -76
- package/dist/workflows/data/stream/index.js.map +1 -1
- package/dist/workflows/data/track/context.js.map +1 -1
- package/dist/workflows/data/view/context.js.map +1 -1
- package/dist/workflows/domain.js.map +1 -1
- package/dist/workflows/flags.js +1 -1
- package/dist/workflows/flags.js.map +1 -1
- package/dist/workflows/hash.js.map +1 -1
- package/dist/workflows/on.js +1 -1
- package/dist/workflows/on.js.map +1 -1
- package/dist/workflows/schedule-builder.js.map +1 -1
- package/dist/workflows/visibility/index.js +0 -2
- package/dist/workflows/visibility/index.js.map +1 -1
- package/dist/workflows/visibility/query-parser.js.map +1 -1
- package/package.json +18 -3
- package/dist/api/analytics/router.js +0 -601
- package/dist/api/analytics/router.js.map +0 -1
- package/dist/api/index.js +0 -158
- package/dist/api/index.js.map +0 -1
- package/dist/api/middleware/error-handling.js +0 -176
- package/dist/api/middleware/error-handling.js.map +0 -1
- package/dist/api/middleware/request-id.js +0 -21
- package/dist/api/middleware/request-id.js.map +0 -1
- package/dist/api/pages.js +0 -1180
- package/dist/api/pages.js.map +0 -1
- package/dist/api/routes/api.js +0 -612
- package/dist/api/routes/api.js.map +0 -1
- package/dist/api/routes/browsers.js +0 -471
- package/dist/api/routes/browsers.js.map +0 -1
- package/dist/api/routes/do.js +0 -188
- package/dist/api/routes/do.js.map +0 -1
- package/dist/api/routes/mcp.js +0 -459
- package/dist/api/routes/mcp.js.map +0 -1
- package/dist/api/routes/obs.js +0 -445
- package/dist/api/routes/obs.js.map +0 -1
- package/dist/api/routes/openapi.js +0 -794
- package/dist/api/routes/openapi.js.map +0 -1
- package/dist/api/routes/rpc.js +0 -1103
- package/dist/api/routes/rpc.js.map +0 -1
- package/dist/api/routes/sandboxes.js +0 -389
- package/dist/api/routes/sandboxes.js.map +0 -1
- package/dist/api/test-do.js +0 -38
- package/dist/api/test-do.js.map +0 -1
- package/dist/api/types.js +0 -11
- package/dist/api/types.js.map +0 -1
- package/dist/cli/bin.js +0 -2
- package/dist/cli/main.js +0 -52342
- package/dist/do/bash.js +0 -35
- package/dist/do/bash.js.map +0 -1
- package/dist/do/fs.js +0 -25
- package/dist/do/fs.js.map +0 -1
- package/dist/do/full.js +0 -61
- package/dist/do/full.js.map +0 -1
- package/dist/do/git.js +0 -28
- package/dist/do/git.js.map +0 -1
- package/dist/do/index.js +0 -52
- package/dist/do/index.js.map +0 -1
- package/dist/lib/agent/tools/bash.js +0 -336
- package/dist/lib/agent/tools/bash.js.map +0 -1
- package/dist/lib/agent/tools/edit.js +0 -157
- package/dist/lib/agent/tools/edit.js.map +0 -1
- package/dist/lib/agent/tools/glob.js +0 -137
- package/dist/lib/agent/tools/glob.js.map +0 -1
- package/dist/lib/agent/tools/grep.js +0 -315
- package/dist/lib/agent/tools/grep.js.map +0 -1
- package/dist/lib/agent/tools/index.js +0 -71
- package/dist/lib/agent/tools/index.js.map +0 -1
- package/dist/lib/agent/tools/read.js +0 -212
- package/dist/lib/agent/tools/read.js.map +0 -1
- package/dist/lib/agent/tools/types.js +0 -197
- package/dist/lib/agent/tools/types.js.map +0 -1
- package/dist/lib/agent/tools/write.js +0 -159
- package/dist/lib/agent/tools/write.js.map +0 -1
- package/dist/lib/mixins/index.js +0 -29
- package/dist/lib/mixins/index.js.map +0 -1
- package/dist/primitives/bashx/src/ast/analyze.js +0 -1472
- package/dist/primitives/bashx/src/ast/analyze.js.map +0 -1
- package/dist/primitives/bashx/src/ast/parser.js +0 -1488
- package/dist/primitives/bashx/src/ast/parser.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/crypto.js +0 -1954
- package/dist/primitives/bashx/src/do/commands/crypto.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/data-processing.js +0 -1812
- package/dist/primitives/bashx/src/do/commands/data-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/extended-utils.js +0 -804
- package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/math-control.js +0 -1122
- package/dist/primitives/bashx/src/do/commands/math-control.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/posix-utils.js +0 -1015
- package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/system-utils.js +0 -687
- package/dist/primitives/bashx/src/do/commands/system-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/test-command.js +0 -523
- package/dist/primitives/bashx/src/do/commands/test-command.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/text-processing.js +0 -1550
- package/dist/primitives/bashx/src/do/commands/text-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/container-executor.js +0 -429
- package/dist/primitives/bashx/src/do/container-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/index.js +0 -668
- package/dist/primitives/bashx/src/do/index.js.map +0 -1
- package/dist/primitives/bashx/src/do/tiered-executor.js +0 -2647
- package/dist/primitives/bashx/src/do/tiered-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/worker.js +0 -352
- package/dist/primitives/bashx/src/do/worker.js.map +0 -1
- package/dist/primitives/bashx/src/types.js +0 -10
- package/dist/primitives/bashx/src/types.js.map +0 -1
- package/dist/primitives/fsx/core/backend.js +0 -480
- package/dist/primitives/fsx/core/backend.js.map +0 -1
- package/dist/primitives/fsx/core/constants.js +0 -140
- package/dist/primitives/fsx/core/constants.js.map +0 -1
- package/dist/primitives/fsx/core/fsx.js +0 -1184
- package/dist/primitives/fsx/core/fsx.js.map +0 -1
- package/dist/primitives/fsx/core/glob/glob.js +0 -438
- package/dist/primitives/fsx/core/glob/glob.js.map +0 -1
- package/dist/primitives/fsx/core/glob/index.js +0 -8
- package/dist/primitives/fsx/core/glob/index.js.map +0 -1
- package/dist/primitives/fsx/core/glob/match.js +0 -392
- package/dist/primitives/fsx/core/glob/match.js.map +0 -1
- package/dist/primitives/fsx/core/types.js +0 -307
- package/dist/primitives/fsx/core/types.js.map +0 -1
- package/dist/sdk/capnweb-compat.js +0 -42
- package/dist/sdk/capnweb-compat.js.map +0 -1
- package/dist/sdk/client.js +0 -20
- package/dist/sdk/client.js.map +0 -1
- package/dist/sdk/index.js +0 -17
- package/dist/sdk/index.js.map +0 -1
package/cli/mcp-stdio.ts
ADDED
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP stdio Transport Module
|
|
3
|
+
*
|
|
4
|
+
* Implements MCP (Model Context Protocol) communication over stdin/stdout
|
|
5
|
+
* using JSON-RPC 2.0 with newline-delimited message framing.
|
|
6
|
+
*
|
|
7
|
+
* @see https://modelcontextprotocol.io/docs/concepts/transports
|
|
8
|
+
* @see https://www.jsonrpc.org/specification
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { EventEmitter } from 'events'
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Type Exports
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export interface JsonRpcMessage {
|
|
18
|
+
jsonrpc: '2.0'
|
|
19
|
+
id?: string | number | null
|
|
20
|
+
method?: string
|
|
21
|
+
params?: Record<string, unknown>
|
|
22
|
+
result?: unknown
|
|
23
|
+
error?: {
|
|
24
|
+
code: number
|
|
25
|
+
message: string
|
|
26
|
+
data?: unknown
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface JsonRpcRequest extends JsonRpcMessage {
|
|
31
|
+
id: string | number
|
|
32
|
+
method: string
|
|
33
|
+
params?: Record<string, unknown>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface JsonRpcResponse extends JsonRpcMessage {
|
|
37
|
+
id: string | number | null
|
|
38
|
+
result?: unknown
|
|
39
|
+
error?: {
|
|
40
|
+
code: number
|
|
41
|
+
message: string
|
|
42
|
+
data?: unknown
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface JsonRpcNotification extends JsonRpcMessage {
|
|
47
|
+
method: string
|
|
48
|
+
params?: Record<string, unknown>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface TransportOptions {
|
|
52
|
+
stdin: NodeJS.ReadableStream | { on: Function; pipe: Function; destroy: Function }
|
|
53
|
+
stdout: NodeJS.WritableStream | { write: Function; end: Function; destroy: Function }
|
|
54
|
+
stderr?: NodeJS.WritableStream | { write: Function; end: Function; destroy: Function }
|
|
55
|
+
delimiter?: string
|
|
56
|
+
encoding?: BufferEncoding
|
|
57
|
+
maxMessageSize?: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MessageHandler {
|
|
61
|
+
(message: JsonRpcMessage, send: (response: JsonRpcMessage) => Promise<void>): Promise<void> | void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface StdioSession {
|
|
65
|
+
id: string
|
|
66
|
+
createdAt: number
|
|
67
|
+
lastActivity: number
|
|
68
|
+
state: 'created' | 'initialized' | 'running' | 'closed'
|
|
69
|
+
clientInfo?: { name: string; version: string }
|
|
70
|
+
transport?: McpStdioTransport
|
|
71
|
+
inactivityTimeout?: number
|
|
72
|
+
|
|
73
|
+
initialize(params: {
|
|
74
|
+
protocolVersion: string
|
|
75
|
+
clientInfo: { name: string; version: string }
|
|
76
|
+
capabilities: Record<string, unknown>
|
|
77
|
+
}): void
|
|
78
|
+
start(): void
|
|
79
|
+
close(): void
|
|
80
|
+
touch(): void
|
|
81
|
+
attachTransport(transport: McpStdioTransport): void
|
|
82
|
+
respond(id: string | number, result: unknown): Promise<void>
|
|
83
|
+
notify(method: string, params?: Record<string, unknown>): Promise<void>
|
|
84
|
+
registerTool(tool: McpTool): void
|
|
85
|
+
unregisterTool(name: string): void
|
|
86
|
+
getTools(): McpTool[]
|
|
87
|
+
on(event: string, callback: (...args: unknown[]) => void): void
|
|
88
|
+
startTimeoutTimer(): void
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface McpTool {
|
|
92
|
+
name: string
|
|
93
|
+
description: string
|
|
94
|
+
inputSchema: Record<string, unknown>
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface MessageFramer {
|
|
98
|
+
push(data: string): void
|
|
99
|
+
getMessages(): string[]
|
|
100
|
+
reset(): void
|
|
101
|
+
bufferSize(): number
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface MessageFramerOptions {
|
|
105
|
+
delimiter?: string
|
|
106
|
+
maxBufferSize?: number
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// MCP Bridge Types (for HTTP proxy)
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
export interface McpBridgeOptions {
|
|
114
|
+
targetUrl?: string
|
|
115
|
+
fetch?: typeof fetch
|
|
116
|
+
retries?: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface McpBridge {
|
|
120
|
+
targetUrl: string
|
|
121
|
+
proxy(message: JsonRpcMessage): Promise<JsonRpcResponse>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface McpServerOptions {
|
|
125
|
+
stdin: NodeJS.ReadableStream | { on: Function; pipe: Function; destroy: Function }
|
|
126
|
+
stdout: NodeJS.WritableStream | { write: Function; end: Function; destroy: Function }
|
|
127
|
+
fetch?: typeof fetch
|
|
128
|
+
targetUrl?: string
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface McpServer {
|
|
132
|
+
isRunning(): boolean
|
|
133
|
+
stop(): Promise<void>
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// JSON-RPC Message Parsing
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse a JSON-RPC message from string
|
|
142
|
+
*/
|
|
143
|
+
export function parseJsonRpcMessage(input: string): JsonRpcMessage {
|
|
144
|
+
let parsed: Record<string, unknown>
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(input)
|
|
147
|
+
} catch {
|
|
148
|
+
throw new Error('Failed to parse JSON')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Validate jsonrpc version
|
|
152
|
+
if (parsed.jsonrpc !== '2.0') {
|
|
153
|
+
throw new Error('Invalid or missing jsonrpc version')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Validate id type if present
|
|
157
|
+
if (parsed.id !== undefined && parsed.id !== null) {
|
|
158
|
+
if (typeof parsed.id !== 'string' && typeof parsed.id !== 'number') {
|
|
159
|
+
throw new Error('Invalid id type: must be string, number, or null')
|
|
160
|
+
}
|
|
161
|
+
if (typeof parsed.id === 'object') {
|
|
162
|
+
throw new Error('Invalid id type: must be string, number, or null')
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Validate params if present (MCP requires object, not array)
|
|
167
|
+
if (parsed.params !== undefined) {
|
|
168
|
+
if (Array.isArray(parsed.params)) {
|
|
169
|
+
throw new Error('MCP requires params to be an object, not an array')
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return parsed as JsonRpcMessage
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Serialize a JSON-RPC message to string
|
|
178
|
+
*/
|
|
179
|
+
export function serializeJsonRpcMessage(message: JsonRpcMessage): string {
|
|
180
|
+
return JSON.stringify(message)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Message Framing
|
|
185
|
+
// ============================================================================
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Create a message framer for handling newline-delimited JSON
|
|
189
|
+
*/
|
|
190
|
+
export function createMessageFramer(options?: MessageFramerOptions): MessageFramer {
|
|
191
|
+
const delimiter = options?.delimiter ?? '\n'
|
|
192
|
+
const maxBufferSize = options?.maxBufferSize ?? Infinity
|
|
193
|
+
let buffer = ''
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
push(data: string): void {
|
|
197
|
+
if (buffer.length + data.length > maxBufferSize) {
|
|
198
|
+
throw new Error('Buffer size limit exceeded')
|
|
199
|
+
}
|
|
200
|
+
buffer += data
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
getMessages(): string[] {
|
|
204
|
+
const messages: string[] = []
|
|
205
|
+
let delimiterIndex: number
|
|
206
|
+
|
|
207
|
+
while ((delimiterIndex = buffer.indexOf(delimiter)) !== -1) {
|
|
208
|
+
const message = buffer.slice(0, delimiterIndex)
|
|
209
|
+
buffer = buffer.slice(delimiterIndex + delimiter.length)
|
|
210
|
+
if (message.length > 0) {
|
|
211
|
+
messages.push(message)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return messages
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
reset(): void {
|
|
219
|
+
buffer = ''
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
bufferSize(): number {
|
|
223
|
+
return buffer.length
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ============================================================================
|
|
229
|
+
// McpStdioTransport Class
|
|
230
|
+
// ============================================================================
|
|
231
|
+
|
|
232
|
+
export class McpStdioTransport extends EventEmitter {
|
|
233
|
+
private options: TransportOptions
|
|
234
|
+
private connected: boolean = false
|
|
235
|
+
private messageHandler?: MessageHandler
|
|
236
|
+
private framer: MessageFramer
|
|
237
|
+
private writeQueue: Promise<void> = Promise.resolve()
|
|
238
|
+
|
|
239
|
+
constructor(options: TransportOptions) {
|
|
240
|
+
super()
|
|
241
|
+
this.options = options
|
|
242
|
+
this.framer = createMessageFramer({
|
|
243
|
+
delimiter: options.delimiter ?? '\n',
|
|
244
|
+
maxBufferSize: options.maxMessageSize,
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
isConnected(): boolean {
|
|
249
|
+
return this.connected
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async start(): Promise<void> {
|
|
253
|
+
if (this.connected) {
|
|
254
|
+
throw new Error('Transport is already connected')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.connected = true
|
|
258
|
+
this.emit('connected')
|
|
259
|
+
|
|
260
|
+
// Listen for data from stdin
|
|
261
|
+
this.options.stdin.on('data', (data: Buffer | string) => {
|
|
262
|
+
this.handleIncomingData(data)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
// Handle stdin close
|
|
266
|
+
this.options.stdin.on('close', () => {
|
|
267
|
+
this.handleDisconnect()
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
// Handle stdin error
|
|
271
|
+
this.options.stdin.on('error', (error: Error) => {
|
|
272
|
+
this.emit('error', error)
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private handleIncomingData(data: Buffer | string): void {
|
|
277
|
+
const str = typeof data === 'string' ? data : data.toString(this.options.encoding ?? 'utf-8')
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
this.framer.push(str)
|
|
281
|
+
} catch {
|
|
282
|
+
this.emit('error', new Error('Message size limit exceeded'))
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const messages = this.framer.getMessages()
|
|
287
|
+
for (const raw of messages) {
|
|
288
|
+
this.processMessage(raw)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private processMessage(raw: string): void {
|
|
293
|
+
// Handle empty messages gracefully
|
|
294
|
+
if (!raw.trim()) {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
let message: JsonRpcMessage
|
|
299
|
+
try {
|
|
300
|
+
message = parseJsonRpcMessage(raw)
|
|
301
|
+
} catch (error) {
|
|
302
|
+
const parseError = error as Error
|
|
303
|
+
// Only emit error if there are listeners (avoid unhandled error events)
|
|
304
|
+
if (this.listenerCount('error') > 0) {
|
|
305
|
+
this.emit('error', new Error(`JSON parse error: ${parseError.message}`))
|
|
306
|
+
}
|
|
307
|
+
// Send parse error response
|
|
308
|
+
this.sendErrorResponse(null, -32700, 'Parse error')
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Validate that it's a request (has method) or response (has result/error)
|
|
313
|
+
const isRequest = 'method' in message
|
|
314
|
+
const isResponse = 'result' in message || 'error' in message
|
|
315
|
+
|
|
316
|
+
if (!isRequest && !isResponse && message.id !== undefined) {
|
|
317
|
+
// Has id but no method and no result/error - invalid request
|
|
318
|
+
// Only emit error if there are listeners (avoid unhandled error events)
|
|
319
|
+
if (this.listenerCount('error') > 0) {
|
|
320
|
+
this.emit('error', new Error('Invalid request: missing method'))
|
|
321
|
+
}
|
|
322
|
+
this.sendErrorResponse(message.id as string | number | null, -32600, 'Invalid Request')
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Emit message event
|
|
327
|
+
this.emit('message', message)
|
|
328
|
+
|
|
329
|
+
// Call message handler if registered
|
|
330
|
+
if (this.messageHandler) {
|
|
331
|
+
const sendFn = (response: JsonRpcMessage) => this.send(response)
|
|
332
|
+
Promise.resolve(this.messageHandler(message, sendFn)).catch((err) => {
|
|
333
|
+
if (this.listenerCount('error') > 0) {
|
|
334
|
+
this.emit('error', err)
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async sendErrorResponse(id: string | number | null, code: number, message: string): Promise<void> {
|
|
341
|
+
try {
|
|
342
|
+
await this.send({
|
|
343
|
+
jsonrpc: '2.0',
|
|
344
|
+
id,
|
|
345
|
+
error: { code, message },
|
|
346
|
+
})
|
|
347
|
+
} catch {
|
|
348
|
+
// Ignore errors when sending error response
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private handleDisconnect(): void {
|
|
353
|
+
if (this.connected) {
|
|
354
|
+
this.connected = false
|
|
355
|
+
this.emit('disconnected')
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async close(): Promise<void> {
|
|
360
|
+
if (this.connected) {
|
|
361
|
+
this.connected = false
|
|
362
|
+
this.emit('disconnected')
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async send(message: JsonRpcMessage): Promise<void> {
|
|
367
|
+
if (!this.connected) {
|
|
368
|
+
throw new Error('Transport is closed')
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const serialized = serializeJsonRpcMessage(message) + (this.options.delimiter ?? '\n')
|
|
372
|
+
|
|
373
|
+
// Queue writes to preserve order
|
|
374
|
+
this.writeQueue = this.writeQueue.then(() => {
|
|
375
|
+
return new Promise<void>((resolve) => {
|
|
376
|
+
try {
|
|
377
|
+
const stdout = this.options.stdout as { write: Function }
|
|
378
|
+
const result = stdout.write(serialized, () => {
|
|
379
|
+
resolve()
|
|
380
|
+
})
|
|
381
|
+
// If write returns false (backpressure), resolve immediately anyway
|
|
382
|
+
// The callback will still fire later, but we don't wait for it
|
|
383
|
+
if (result === false) {
|
|
384
|
+
resolve()
|
|
385
|
+
}
|
|
386
|
+
} catch (error) {
|
|
387
|
+
this.emit('error', error)
|
|
388
|
+
resolve()
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
await this.writeQueue
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
setMessageHandler(handler: MessageHandler): void {
|
|
397
|
+
this.messageHandler = handler
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ============================================================================
|
|
402
|
+
// Factory Functions
|
|
403
|
+
// ============================================================================
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Create a new MCP stdio transport
|
|
407
|
+
*/
|
|
408
|
+
export function createStdioTransport(options: TransportOptions): McpStdioTransport {
|
|
409
|
+
return new McpStdioTransport(options)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ============================================================================
|
|
413
|
+
// Session Management
|
|
414
|
+
// ============================================================================
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Create a new MCP stdio session
|
|
418
|
+
*/
|
|
419
|
+
export function createStdioSession(options?: { inactivityTimeout?: number }): StdioSession {
|
|
420
|
+
const emitter = new EventEmitter()
|
|
421
|
+
let state: 'created' | 'initialized' | 'running' | 'closed' = 'created'
|
|
422
|
+
let clientInfo: { name: string; version: string } | undefined
|
|
423
|
+
let transport: McpStdioTransport | undefined
|
|
424
|
+
let lastActivity = Date.now()
|
|
425
|
+
let timeoutTimer: ReturnType<typeof setTimeout> | undefined
|
|
426
|
+
const tools: Map<string, McpTool> = new Map()
|
|
427
|
+
|
|
428
|
+
const session: StdioSession = {
|
|
429
|
+
id: generateSessionId(),
|
|
430
|
+
createdAt: Date.now(),
|
|
431
|
+
get lastActivity() {
|
|
432
|
+
return lastActivity
|
|
433
|
+
},
|
|
434
|
+
get state() {
|
|
435
|
+
return state
|
|
436
|
+
},
|
|
437
|
+
get clientInfo() {
|
|
438
|
+
return clientInfo
|
|
439
|
+
},
|
|
440
|
+
get transport() {
|
|
441
|
+
return transport
|
|
442
|
+
},
|
|
443
|
+
get inactivityTimeout() {
|
|
444
|
+
return options?.inactivityTimeout
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
initialize(params: {
|
|
448
|
+
protocolVersion: string
|
|
449
|
+
clientInfo: { name: string; version: string }
|
|
450
|
+
capabilities: Record<string, unknown>
|
|
451
|
+
}): void {
|
|
452
|
+
if (state === 'closed') {
|
|
453
|
+
throw new Error('Cannot initialize closed session')
|
|
454
|
+
}
|
|
455
|
+
clientInfo = params.clientInfo
|
|
456
|
+
state = 'initialized'
|
|
457
|
+
this.touch()
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
start(): void {
|
|
461
|
+
if (state === 'initialized') {
|
|
462
|
+
state = 'running'
|
|
463
|
+
this.touch()
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
close(): void {
|
|
468
|
+
if (timeoutTimer) {
|
|
469
|
+
clearTimeout(timeoutTimer)
|
|
470
|
+
}
|
|
471
|
+
state = 'closed'
|
|
472
|
+
if (transport) {
|
|
473
|
+
transport.close()
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
touch(): void {
|
|
478
|
+
lastActivity = Date.now()
|
|
479
|
+
if (timeoutTimer && options?.inactivityTimeout) {
|
|
480
|
+
clearTimeout(timeoutTimer)
|
|
481
|
+
timeoutTimer = setTimeout(() => {
|
|
482
|
+
emitter.emit('timeout')
|
|
483
|
+
}, options.inactivityTimeout)
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
attachTransport(t: McpStdioTransport): void {
|
|
488
|
+
transport = t
|
|
489
|
+
|
|
490
|
+
// Listen for messages and handle MCP protocol
|
|
491
|
+
transport.on('message', async (msg: JsonRpcMessage) => {
|
|
492
|
+
this.touch()
|
|
493
|
+
|
|
494
|
+
// Handle initialize request
|
|
495
|
+
if (msg.method === 'initialize' && msg.params && msg.id !== undefined) {
|
|
496
|
+
this.initialize(msg.params as {
|
|
497
|
+
protocolVersion: string
|
|
498
|
+
clientInfo: { name: string; version: string }
|
|
499
|
+
capabilities: Record<string, unknown>
|
|
500
|
+
})
|
|
501
|
+
// Send initialize response
|
|
502
|
+
await this.respond(msg.id as string | number, {
|
|
503
|
+
protocolVersion: '2024-11-05',
|
|
504
|
+
serverInfo: { name: 'dotdo', version: '1.0.0' },
|
|
505
|
+
capabilities: { tools: {}, resources: {} },
|
|
506
|
+
})
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Handle tools/list request
|
|
511
|
+
if (msg.method === 'tools/list' && msg.id !== undefined) {
|
|
512
|
+
await this.respond(msg.id as string | number, {
|
|
513
|
+
tools: this.getTools(),
|
|
514
|
+
})
|
|
515
|
+
return
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Handle tools/call request - echo the tool name for now
|
|
519
|
+
if (msg.method === 'tools/call' && msg.id !== undefined && msg.params) {
|
|
520
|
+
const toolName = (msg.params as { name: string }).name
|
|
521
|
+
const args = (msg.params as { arguments?: Record<string, unknown> }).arguments ?? {}
|
|
522
|
+
const tool = tools.get(toolName)
|
|
523
|
+
if (tool) {
|
|
524
|
+
// Simple echo implementation for testing
|
|
525
|
+
await this.respond(msg.id as string | number, {
|
|
526
|
+
content: [{ type: 'text', text: `Tool ${toolName} called with: ${JSON.stringify(args)}` }],
|
|
527
|
+
})
|
|
528
|
+
} else {
|
|
529
|
+
await transport.send({
|
|
530
|
+
jsonrpc: '2.0',
|
|
531
|
+
id: msg.id,
|
|
532
|
+
error: { code: -32601, message: `Tool not found: ${toolName}` },
|
|
533
|
+
})
|
|
534
|
+
}
|
|
535
|
+
return
|
|
536
|
+
}
|
|
537
|
+
})
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
async respond(id: string | number, result: unknown): Promise<void> {
|
|
541
|
+
if (transport) {
|
|
542
|
+
await transport.send({
|
|
543
|
+
jsonrpc: '2.0',
|
|
544
|
+
id,
|
|
545
|
+
result,
|
|
546
|
+
})
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
async notify(method: string, params?: Record<string, unknown>): Promise<void> {
|
|
551
|
+
if (transport) {
|
|
552
|
+
await transport.send({
|
|
553
|
+
jsonrpc: '2.0',
|
|
554
|
+
method,
|
|
555
|
+
params,
|
|
556
|
+
})
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
registerTool(tool: McpTool): void {
|
|
561
|
+
tools.set(tool.name, tool)
|
|
562
|
+
// Send notification if initialized
|
|
563
|
+
if (state === 'initialized' || state === 'running') {
|
|
564
|
+
this.notify('notifications/tools/list_changed', {})
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
|
|
568
|
+
unregisterTool(name: string): void {
|
|
569
|
+
tools.delete(name)
|
|
570
|
+
// Send notification if initialized
|
|
571
|
+
if (state === 'initialized' || state === 'running') {
|
|
572
|
+
this.notify('notifications/tools/list_changed', {})
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
|
|
576
|
+
getTools(): McpTool[] {
|
|
577
|
+
return Array.from(tools.values())
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
on(event: string, callback: (...args: unknown[]) => void): void {
|
|
581
|
+
emitter.on(event, callback)
|
|
582
|
+
},
|
|
583
|
+
|
|
584
|
+
startTimeoutTimer(): void {
|
|
585
|
+
if (options?.inactivityTimeout) {
|
|
586
|
+
timeoutTimer = setTimeout(() => {
|
|
587
|
+
emitter.emit('timeout')
|
|
588
|
+
}, options.inactivityTimeout)
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return session
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function generateSessionId(): string {
|
|
597
|
+
return `session-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ============================================================================
|
|
601
|
+
// Helper Functions
|
|
602
|
+
// ============================================================================
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Normalize a URL by removing trailing slashes and /mcp suffix
|
|
606
|
+
*/
|
|
607
|
+
function normalizeTargetUrl(url: string): string {
|
|
608
|
+
let normalized = url
|
|
609
|
+
// Remove trailing slash
|
|
610
|
+
if (normalized.endsWith('/')) {
|
|
611
|
+
normalized = normalized.slice(0, -1)
|
|
612
|
+
}
|
|
613
|
+
// Remove /mcp suffix if present
|
|
614
|
+
if (normalized.endsWith('/mcp')) {
|
|
615
|
+
normalized = normalized.slice(0, -4)
|
|
616
|
+
}
|
|
617
|
+
return normalized
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Validate URL format
|
|
622
|
+
*/
|
|
623
|
+
function isValidUrl(url: string): boolean {
|
|
624
|
+
try {
|
|
625
|
+
new URL(url)
|
|
626
|
+
return true
|
|
627
|
+
} catch {
|
|
628
|
+
return false
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ============================================================================
|
|
633
|
+
// MCP HTTP Bridge Functions
|
|
634
|
+
// ============================================================================
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Create a new MCP HTTP bridge that proxies stdio to DO's /mcp endpoint
|
|
638
|
+
*/
|
|
639
|
+
export function createMcpBridge(options?: McpBridgeOptions): McpBridge {
|
|
640
|
+
const rawUrl = options?.targetUrl ?? process.env.DO_URL
|
|
641
|
+
|
|
642
|
+
// Throw if no URL configured
|
|
643
|
+
if (!rawUrl) {
|
|
644
|
+
throw new Error('DO_URL not configured: provide targetUrl option or set DO_URL environment variable')
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Validate URL format
|
|
648
|
+
if (!isValidUrl(rawUrl)) {
|
|
649
|
+
throw new Error('Invalid URL format')
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const targetUrl = normalizeTargetUrl(rawUrl)
|
|
653
|
+
const fetchFn = options?.fetch ?? fetch
|
|
654
|
+
const maxRetries = options?.retries ?? 0
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
targetUrl,
|
|
658
|
+
async proxy(message: JsonRpcMessage): Promise<JsonRpcResponse> {
|
|
659
|
+
let lastError: Error | undefined
|
|
660
|
+
|
|
661
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
662
|
+
try {
|
|
663
|
+
const res = await fetchFn(`${targetUrl}/mcp`, {
|
|
664
|
+
method: 'POST',
|
|
665
|
+
headers: { 'Content-Type': 'application/json' },
|
|
666
|
+
body: JSON.stringify(message),
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
// Handle HTTP errors
|
|
670
|
+
if (!res.ok) {
|
|
671
|
+
const status = res.status
|
|
672
|
+
const statusText = res.statusText || ''
|
|
673
|
+
let errorMessage: string
|
|
674
|
+
|
|
675
|
+
if (status === 401) {
|
|
676
|
+
errorMessage = `401 Unauthorized`
|
|
677
|
+
} else if (status === 404) {
|
|
678
|
+
errorMessage = `404 Not Found`
|
|
679
|
+
} else if (status >= 500) {
|
|
680
|
+
errorMessage = `${status} Internal Server Error`
|
|
681
|
+
} else {
|
|
682
|
+
errorMessage = `HTTP ${status}: ${statusText}`
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
return {
|
|
686
|
+
jsonrpc: '2.0',
|
|
687
|
+
id: message.id ?? null,
|
|
688
|
+
error: {
|
|
689
|
+
code: -32603,
|
|
690
|
+
message: errorMessage,
|
|
691
|
+
},
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
return (await res.json()) as JsonRpcResponse
|
|
697
|
+
} catch {
|
|
698
|
+
// JSON parse error
|
|
699
|
+
return {
|
|
700
|
+
jsonrpc: '2.0',
|
|
701
|
+
id: message.id ?? null,
|
|
702
|
+
error: {
|
|
703
|
+
code: -32700,
|
|
704
|
+
message: 'Parse error: Invalid JSON response from server',
|
|
705
|
+
},
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
} catch (error) {
|
|
709
|
+
lastError = error as Error
|
|
710
|
+
// If we have more retries, continue
|
|
711
|
+
if (attempt < maxRetries) {
|
|
712
|
+
continue
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Return error response for network/connection errors
|
|
718
|
+
return {
|
|
719
|
+
jsonrpc: '2.0',
|
|
720
|
+
id: message.id ?? null,
|
|
721
|
+
error: {
|
|
722
|
+
code: -32603,
|
|
723
|
+
message: `Connection failed: ${lastError?.message ?? 'Unknown error'}`,
|
|
724
|
+
},
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Start MCP stdio server that proxies to DO
|
|
732
|
+
*/
|
|
733
|
+
export async function startMcpServer(options: McpServerOptions): Promise<McpServer> {
|
|
734
|
+
// Validate DO_URL is configured
|
|
735
|
+
const targetUrl = options.targetUrl ?? process.env.DO_URL
|
|
736
|
+
if (!targetUrl) {
|
|
737
|
+
throw new Error('DO_URL not configured: provide targetUrl option or set DO_URL environment variable')
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const transport = createStdioTransport({
|
|
741
|
+
stdin: options.stdin,
|
|
742
|
+
stdout: options.stdout,
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
const bridge = createMcpBridge({
|
|
746
|
+
targetUrl,
|
|
747
|
+
fetch: options.fetch,
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
let running = true
|
|
751
|
+
|
|
752
|
+
transport.setMessageHandler(async (message, send) => {
|
|
753
|
+
const response = await bridge.proxy(message)
|
|
754
|
+
await send(response)
|
|
755
|
+
})
|
|
756
|
+
|
|
757
|
+
await transport.start()
|
|
758
|
+
|
|
759
|
+
return {
|
|
760
|
+
isRunning(): boolean {
|
|
761
|
+
return running
|
|
762
|
+
},
|
|
763
|
+
async stop(): Promise<void> {
|
|
764
|
+
running = false
|
|
765
|
+
await transport.close()
|
|
766
|
+
},
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* MCP command handler for CLI
|
|
772
|
+
*/
|
|
773
|
+
export async function mcpCommand(args?: { url?: string }): Promise<void> {
|
|
774
|
+
const server = await startMcpServer({
|
|
775
|
+
stdin: process.stdin,
|
|
776
|
+
stdout: process.stdout,
|
|
777
|
+
targetUrl: args?.url ?? process.env.DO_URL,
|
|
778
|
+
})
|
|
779
|
+
|
|
780
|
+
// Handle process signals
|
|
781
|
+
process.on('SIGINT', async () => {
|
|
782
|
+
await server.stop()
|
|
783
|
+
process.exit(0)
|
|
784
|
+
})
|
|
785
|
+
|
|
786
|
+
process.on('SIGTERM', async () => {
|
|
787
|
+
await server.stop()
|
|
788
|
+
process.exit(0)
|
|
789
|
+
})
|
|
790
|
+
}
|