ethagent 3.3.3 → 4.0.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/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +35 -0
- package/LICENSE +1 -1
- package/README.md +64 -104
- package/commands/ethagent.md +40 -0
- package/package.json +16 -16
- package/src/app/keybindings/KeybindingProvider.tsx +1 -6
- package/src/app/keybindings/types.ts +1 -6
- package/src/cli/ResetConfirmView.tsx +54 -53
- package/src/cli/demo.ts +86 -0
- package/src/cli/hookIo.ts +45 -0
- package/src/cli/main.tsx +94 -123
- package/src/cli/memoryGuard.ts +49 -0
- package/src/cli/reset.ts +28 -70
- package/src/cli/sessionStart.ts +33 -0
- package/src/cli/status.ts +46 -0
- package/src/cli/sync.ts +167 -0
- package/src/cli/syncAdapters/claude-code.ts +86 -0
- package/src/cli/syncAdapters/codex.ts +66 -0
- package/src/cli/syncAdapters/index.ts +45 -0
- package/src/cli/syncAdapters/managedBlock.ts +175 -0
- package/src/cli/syncAdapters/shared.ts +63 -0
- package/src/identity/continuity/envelopeParse.ts +20 -1
- package/src/identity/continuity/publicSkills.ts +3 -1
- package/src/identity/continuity/skills/publicSkillsSync.ts +2 -1
- package/src/identity/continuity/skills/scaffold.ts +5 -2
- package/src/identity/continuity/snapshots.ts +12 -5
- package/src/identity/continuity/storage/defaults.ts +20 -19
- package/src/identity/continuity/storage/status.ts +1 -1
- package/src/identity/ens/ensLookup/constants.ts +1 -1
- package/src/identity/manager/IdentityManager.tsx +33 -0
- package/src/identity/{hub → manager}/OperationalRoutes.tsx +37 -18
- package/src/identity/{hub → manager}/Routes.tsx +48 -34
- package/src/identity/{hub → manager}/continuity/ContinuityDashboardScreen.tsx +9 -19
- package/src/identity/{hub → manager}/continuity/RebackupStorageScreen.tsx +3 -3
- package/src/identity/manager/continuity/RecoveryConfirmScreen.tsx +102 -0
- package/src/identity/{hub → manager}/continuity/SavePromptScreen.tsx +2 -3
- package/src/identity/{hub → manager}/continuity/completion.ts +1 -1
- package/src/identity/{hub → manager}/continuity/effects.ts +1 -1
- package/src/identity/{hub → manager}/continuity/skills/DeleteSkillConfirmScreen.tsx +2 -2
- package/src/identity/{hub → manager}/continuity/skills/NewSkillScreen.tsx +0 -5
- package/src/identity/{hub → manager}/continuity/skills/NewSkillVisibilityScreen.tsx +4 -4
- package/src/identity/{hub → manager}/continuity/skills/SkillActionsScreen.tsx +6 -22
- package/src/identity/{hub → manager}/continuity/skills/SkillsTreeScreen.tsx +5 -17
- package/src/identity/{hub → manager}/continuity/snapshot.ts +1 -1
- package/src/identity/{hub → manager}/continuity/vault.ts +1 -1
- package/src/identity/{hub → manager}/create/CreateFlow.tsx +59 -32
- package/src/identity/{hub → manager}/create/effects.ts +19 -10
- package/src/identity/manager/create/importScan.ts +122 -0
- package/src/identity/{hub → manager}/custody/CustodyEditFlow.tsx +17 -61
- package/src/identity/{hub → manager}/custody/actions.ts +1 -15
- package/src/identity/{hub → manager}/custody/routes.tsx +20 -40
- package/src/identity/{hub → manager}/custody/transactions.ts +1 -0
- package/src/identity/{hub → manager}/custody/types.ts +1 -2
- package/src/identity/{hub → manager}/custody/useCustodyEffects.ts +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditAdvancedScreens.tsx +2 -2
- package/src/identity/{hub → manager}/ens/EnsEditMaintenanceScreens.tsx +12 -23
- package/src/identity/{hub → manager}/ens/EnsEditReviewScreens.tsx +18 -42
- package/src/identity/{hub → manager}/ens/EnsEditRunners.tsx +1 -1
- package/src/identity/{hub → manager}/ens/EnsEditShared.tsx +0 -2
- package/src/identity/{hub → manager}/ens/EnsEditSimpleScreens.tsx +10 -19
- package/src/identity/{hub → manager}/ens/EnsFlow.tsx +133 -41
- package/src/identity/{hub → manager}/ens/EnsOperatorWalletsScreen.tsx +14 -19
- package/src/identity/{hub → manager}/ens/editCopy.ts +1 -14
- package/src/identity/{hub → manager}/profile/EditProfileFlow.tsx +99 -66
- package/src/identity/{hub → manager}/profile/effects.ts +1 -3
- package/src/identity/{hub → manager}/profile/operatorSave.ts +1 -1
- package/src/identity/{hub → manager}/profile/state.ts +1 -1
- package/src/identity/{hub/identityHubReducer.ts → manager/reducer.ts} +25 -26
- package/src/identity/{hub → manager}/restore/RestoreFlow.tsx +16 -24
- package/src/identity/{hub → manager}/restore/apply.ts +1 -1
- package/src/identity/{hub → manager}/restore/auth.ts +1 -1
- package/src/identity/{hub → manager}/restore/discover.ts +1 -1
- package/src/identity/{hub → manager}/restore/fetch.ts +1 -1
- package/src/identity/{hub → manager}/restore/restoreAdmin.ts +1 -1
- package/src/identity/{hub → manager}/restore/useRestoreEffects.ts +2 -9
- package/src/identity/{hub → manager}/settings/StorageCredentialScreen.tsx +10 -25
- package/src/identity/{hub → manager}/shared/components/DetailsScreen.tsx +5 -7
- package/src/identity/{hub → manager}/shared/components/ErrorScreen.tsx +6 -10
- package/src/identity/{hub → manager}/shared/components/FlowTimeline.tsx +4 -3
- package/src/identity/{hub → manager}/shared/components/IdentitySummary.tsx +19 -59
- package/src/identity/manager/shared/components/LazyMenu.tsx +147 -0
- package/src/identity/manager/shared/components/MenuScreen.tsx +220 -0
- package/src/identity/manager/shared/components/OperationCompleteScreen.tsx +28 -0
- package/src/identity/{hub → manager}/shared/components/UnlinkedIdentityScreen.tsx +9 -10
- package/src/identity/{hub → manager}/shared/components/WalletApprovalScreen.tsx +1 -2
- package/src/identity/manager/shared/components/Wordmark.tsx +54 -0
- package/src/identity/{hub → manager}/shared/components/menuFlagsFromReconciliation.ts +39 -15
- package/src/identity/{hub → manager}/shared/effects/profilePrep.ts +1 -1
- package/src/identity/manager/shared/effects/types.ts +30 -0
- package/src/identity/{hub → manager}/shared/model/copy.ts +0 -4
- package/src/identity/{hub → manager}/shared/model/errors.ts +32 -3
- package/src/identity/{hub → manager}/shared/model/network.ts +2 -2
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/hook.ts +5 -0
- package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/run.ts +1 -1
- package/src/identity/{hub/shared/reconciliation/useAgentReconciliation.ts → manager/shared/reconciliation/index.ts} +6 -0
- package/src/identity/{hub → manager}/shared/utils.ts +6 -10
- package/src/identity/{hub → manager}/transfer/TokenTransferFlow.tsx +3 -3
- package/src/identity/{hub → manager}/transfer/TokenTransferScreens.tsx +4 -10
- package/src/identity/{hub → manager}/transfer/effects.ts +1 -1
- package/src/identity/{hub → manager}/types.ts +5 -6
- package/src/identity/{hub/useIdentityHubContinuity.ts → manager/useContinuity.ts} +59 -27
- package/src/identity/{hub/useIdentityHubController.ts → manager/useController.ts} +38 -35
- package/src/identity/{hub/useIdentityHubSideEffects.ts → manager/useSideEffects.ts} +40 -4
- package/src/identity/registry/erc8004/discovery.ts +3 -17
- package/src/identity/registry/erc8004/utils.ts +1 -1
- package/src/identity/storage/ipfs.ts +21 -1
- package/src/identity/wallet/browserWallet/html.ts +10 -2
- package/src/identity/wallet/browserWallet/http.ts +18 -0
- package/src/identity/wallet/browserWallet/requestServer.ts +5 -1
- package/src/identity/wallet/browserWallet/requests.ts +10 -28
- package/src/identity/wallet/browserWallet/session.ts +26 -33
- package/src/identity/wallet/browserWallet/validation.ts +14 -0
- package/src/identity/wallet/browserWallet/walletPageSource.ts +22 -40
- package/src/identity/wallet/page/boot.ts +43 -0
- package/src/identity/wallet/page/config.ts +59 -0
- package/src/identity/wallet/page/constants.ts +12 -0
- package/src/identity/wallet/page/copy.ts +47 -68
- package/src/identity/wallet/page/css.ts +638 -0
- package/src/identity/wallet/page/{errorView.ts → errors.ts} +5 -14
- package/src/identity/wallet/page/{controller.ts → flow.ts} +4 -71
- package/src/identity/wallet/page/markup.ts +44 -34
- package/src/identity/wallet/page/{walletProvider.ts → provider.ts} +0 -3
- package/src/identity/wallet/page/resize.ts +95 -0
- package/src/identity/wallet/page/state.ts +135 -8
- package/src/identity/wallet/page/timeline.ts +161 -0
- package/src/identity/wallet/page/view.ts +22 -302
- package/src/storage/config.ts +30 -80
- package/src/storage/reset.ts +31 -0
- package/src/storage/secrets.ts +1 -16
- package/src/ui/Select.tsx +27 -5
- package/src/ui/Spinner.tsx +16 -15
- package/src/ui/Surface.tsx +21 -17
- package/src/ui/TextArea.tsx +173 -0
- package/src/ui/TextInput.tsx +31 -133
- package/src/ui/theme.ts +22 -13
- package/src/utils/clipboard.ts +0 -140
- package/src/app/FirstRun.tsx +0 -577
- package/src/app/FirstRunTimeline.tsx +0 -51
- package/src/app/firstRunConfig.ts +0 -26
- package/src/app/hooks/useCancelRequest.ts +0 -22
- package/src/app/hooks/useDoublePress.ts +0 -46
- package/src/app/hooks/useExitOnCtrlC.ts +0 -36
- package/src/auth/openaiOAuth/credentials.ts +0 -47
- package/src/auth/openaiOAuth/crypto.ts +0 -23
- package/src/auth/openaiOAuth/index.ts +0 -238
- package/src/auth/openaiOAuth/landingPage.ts +0 -116
- package/src/auth/openaiOAuth/listener.ts +0 -151
- package/src/auth/openaiOAuth/refresh.ts +0 -70
- package/src/auth/openaiOAuth/shared.ts +0 -115
- package/src/chat/ChatBottomPane.tsx +0 -296
- package/src/chat/ChatScreen.tsx +0 -1685
- package/src/chat/ConversationStack.tsx +0 -56
- package/src/chat/MessageList.tsx +0 -638
- package/src/chat/SessionStatus.tsx +0 -53
- package/src/chat/chatEnvironment.ts +0 -16
- package/src/chat/chatScreenUtils.ts +0 -194
- package/src/chat/chatSessionState.ts +0 -146
- package/src/chat/chatTurnContext.ts +0 -50
- package/src/chat/chatTurnOrchestrator.ts +0 -603
- package/src/chat/chatTurnRows.ts +0 -64
- package/src/chat/commands.ts +0 -494
- package/src/chat/continuityEditReview.ts +0 -42
- package/src/chat/display/DiffView.tsx +0 -193
- package/src/chat/display/SyntaxText.tsx +0 -192
- package/src/chat/display/toolCallDisplay.ts +0 -103
- package/src/chat/display/toolResultDisplay.ts +0 -19
- package/src/chat/input/ChatInput.tsx +0 -625
- package/src/chat/input/chatInputHelpers.ts +0 -62
- package/src/chat/input/chatInputState.ts +0 -247
- package/src/chat/input/chatPaste.ts +0 -49
- package/src/chat/input/imageRefs.ts +0 -30
- package/src/chat/input/inputRendering.tsx +0 -93
- package/src/chat/input/textCursor.ts +0 -212
- package/src/chat/messageMarkdown.ts +0 -220
- package/src/chat/messageRows.ts +0 -43
- package/src/chat/planImplementation.ts +0 -62
- package/src/chat/slashCommandHandlers.ts +0 -122
- package/src/chat/slashCommandViews.ts +0 -120
- package/src/chat/transcript/TranscriptView.tsx +0 -184
- package/src/chat/transcript/transcriptViewport.ts +0 -295
- package/src/chat/views/ContextLimitView.tsx +0 -95
- package/src/chat/views/ContinuityEditReviewView.tsx +0 -50
- package/src/chat/views/CopyPicker.tsx +0 -50
- package/src/chat/views/PermissionPrompt.tsx +0 -156
- package/src/chat/views/PermissionsView.tsx +0 -165
- package/src/chat/views/PlanApprovalView.tsx +0 -91
- package/src/chat/views/ResumeView.tsx +0 -273
- package/src/chat/views/RewindView.tsx +0 -412
- package/src/cli/preview.tsx +0 -14
- package/src/cli/updateNotice.ts +0 -54
- package/src/identity/continuity/privateEdit/apply.ts +0 -170
- package/src/identity/continuity/privateEdit/diff.ts +0 -6
- package/src/identity/continuity/privateEdit/files.ts +0 -23
- package/src/identity/continuity/privateEdit/types.ts +0 -28
- package/src/identity/continuity/privateEdit.ts +0 -46
- package/src/identity/hub/IdentityHub.tsx +0 -14
- package/src/identity/hub/continuity/RecoveryConfirmScreen.tsx +0 -104
- package/src/identity/hub/ens/effects.ts +0 -218
- package/src/identity/hub/shared/components/MenuScreen.tsx +0 -241
- package/src/identity/hub/shared/effects/types.ts +0 -53
- package/src/identity/hub/shared/reconciliation/index.ts +0 -14
- package/src/identity/wallet/page/grainient.ts +0 -278
- package/src/identity/wallet/page/html.ts +0 -28
- package/src/identity/wallet/page/styles/base.ts +0 -259
- package/src/identity/wallet/page/styles/components.ts +0 -262
- package/src/identity/wallet/page/styles/index.ts +0 -5
- package/src/identity/wallet/page/styles/responsive.ts +0 -247
- package/src/identity/wallet/page.tsx +0 -38
- package/src/mcp/approvals.ts +0 -113
- package/src/mcp/config.ts +0 -235
- package/src/mcp/manager.ts +0 -482
- package/src/mcp/managerHelpers.ts +0 -70
- package/src/mcp/names.ts +0 -19
- package/src/mcp/output.ts +0 -96
- package/src/models/ModelPicker.tsx +0 -1009
- package/src/models/catalog.ts +0 -327
- package/src/models/huggingface.ts +0 -712
- package/src/models/huggingfaceStorage.ts +0 -136
- package/src/models/llamacpp.ts +0 -848
- package/src/models/llamacppCommands.ts +0 -44
- package/src/models/llamacppConfig.ts +0 -34
- package/src/models/llamacppDiscovery.ts +0 -176
- package/src/models/llamacppOutput.ts +0 -65
- package/src/models/llamacppPreflight.ts +0 -158
- package/src/models/modelDisplay.ts +0 -180
- package/src/models/modelPickerCatalogFlow.ts +0 -56
- package/src/models/modelPickerCredentials.ts +0 -166
- package/src/models/modelPickerData.ts +0 -41
- package/src/models/modelPickerDisplay.tsx +0 -132
- package/src/models/modelPickerHfFlow.ts +0 -192
- package/src/models/modelPickerLocalRunnerFlow.ts +0 -115
- package/src/models/modelPickerOptions.ts +0 -457
- package/src/models/modelPickerTypes.ts +0 -69
- package/src/models/modelPickerUninstallFlow.ts +0 -48
- package/src/models/modelPickerViewHelpers.ts +0 -174
- package/src/models/modelRecommendation.ts +0 -139
- package/src/models/providerDisplay.ts +0 -16
- package/src/models/runtimeDetection.ts +0 -81
- package/src/models/uncensoredCatalog.ts +0 -86
- package/src/providers/anthropic.ts +0 -290
- package/src/providers/contracts.ts +0 -71
- package/src/providers/errors.ts +0 -80
- package/src/providers/gemini.ts +0 -391
- package/src/providers/openai-chat.ts +0 -474
- package/src/providers/openai-responses-format.ts +0 -177
- package/src/providers/openai-responses.ts +0 -306
- package/src/providers/openaiChatWire.ts +0 -124
- package/src/providers/registry.ts +0 -120
- package/src/providers/retry.ts +0 -58
- package/src/providers/sse.ts +0 -93
- package/src/runtime/compaction.ts +0 -395
- package/src/runtime/cwd.ts +0 -43
- package/src/runtime/providerTurn.ts +0 -38
- package/src/runtime/sessionMode.ts +0 -55
- package/src/runtime/systemPrompt.ts +0 -213
- package/src/runtime/textToolParser.ts +0 -161
- package/src/runtime/toolClaimGuards.ts +0 -143
- package/src/runtime/toolExecution.ts +0 -304
- package/src/runtime/toolIntent.ts +0 -143
- package/src/runtime/turn.ts +0 -369
- package/src/runtime/turnNudges.ts +0 -223
- package/src/runtime/turnTypes.ts +0 -86
- package/src/storage/factoryReset.ts +0 -127
- package/src/storage/history.ts +0 -58
- package/src/storage/permissions.ts +0 -76
- package/src/storage/rewind.ts +0 -266
- package/src/storage/sessionExport.ts +0 -49
- package/src/storage/sessions.ts +0 -495
- package/src/tools/bashSafety.ts +0 -186
- package/src/tools/bashTool.ts +0 -140
- package/src/tools/changeDirectoryTool.ts +0 -213
- package/src/tools/contracts.ts +0 -192
- package/src/tools/deleteFileTool.ts +0 -116
- package/src/tools/editTool.ts +0 -165
- package/src/tools/editUtils.ts +0 -170
- package/src/tools/fileDiff.ts +0 -261
- package/src/tools/listDirectoryTool.ts +0 -55
- package/src/tools/listSkillFilesTool.ts +0 -77
- package/src/tools/listSkillsTool.ts +0 -68
- package/src/tools/mcpResourceTools.ts +0 -95
- package/src/tools/permissionRules.ts +0 -85
- package/src/tools/privateContinuityEditTool.ts +0 -187
- package/src/tools/privateContinuityReadTool.ts +0 -106
- package/src/tools/readSkillTool.ts +0 -107
- package/src/tools/readTool.ts +0 -85
- package/src/tools/registry.ts +0 -103
- package/src/tools/writeFileTool.ts +0 -167
- package/src/ui/BrandSplash.tsx +0 -133
- package/src/ui/terminalTitle.ts +0 -30
- package/src/utils/images.ts +0 -140
- package/src/utils/markdownSegments.ts +0 -51
- package/src/utils/messages.ts +0 -37
- package/src/utils/withRetry.ts +0 -324
- /package/src/identity/{hub → manager}/continuity/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/custody/preflight.ts +0 -0
- /package/src/identity/{hub → manager}/custody/state.ts +0 -0
- /package/src/identity/{hub → manager}/custody/useCustodyFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/EnsEditFlow.tsx +0 -0
- /package/src/identity/{hub → manager}/ens/advancedEnsValidation.ts +0 -0
- /package/src/identity/{hub → manager}/ens/state.ts +0 -0
- /package/src/identity/{hub → manager}/ens/transactions.ts +0 -0
- /package/src/identity/{hub → manager}/ens/types.ts +0 -0
- /package/src/identity/{hub → manager}/profile/identity.ts +0 -0
- /package/src/identity/{hub → manager}/restore/envelopes.ts +0 -0
- /package/src/identity/{hub → manager}/restore/helpers.ts +0 -0
- /package/src/identity/{hub → manager}/restore/recovery.ts +0 -0
- /package/src/identity/{hub → manager}/restore/resolve.ts +0 -0
- /package/src/identity/{hub → manager}/shared/components/BusyScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/NetworkScreen.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/components/PinataJwtInput.tsx +0 -0
- /package/src/identity/{hub → manager}/shared/effects/receipts.ts +0 -0
- /package/src/identity/{hub → manager}/shared/effects/sync.ts +0 -0
- /package/src/identity/{hub → manager}/shared/model/format.ts +0 -0
- /package/src/identity/{hub → manager}/shared/operatorWallets.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/ownership.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/agentReconciliation/types.ts +0 -0
- /package/src/identity/{hub → manager}/shared/reconciliation/walletSetup.ts +0 -0
- /package/src/identity/{hub → manager}/shared/txGuard.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/progress.ts +0 -0
- /package/src/identity/{hub → manager}/transfer/state.ts +0 -0
package/src/mcp/manager.ts
DELETED
|
@@ -1,482 +0,0 @@
|
|
|
1
|
-
import { Ajv } from 'ajv'
|
|
2
|
-
import { z } from 'zod'
|
|
3
|
-
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
|
4
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
5
|
-
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js'
|
|
6
|
-
import type { Tool, ToolResult } from '../tools/contracts.js'
|
|
7
|
-
import {
|
|
8
|
-
addMcpServerConfig,
|
|
9
|
-
loadMcpConfigs,
|
|
10
|
-
mcpServerTransport,
|
|
11
|
-
parseMcpServerConfigJson,
|
|
12
|
-
type McpConfigIssue,
|
|
13
|
-
type McpServerConfig,
|
|
14
|
-
type ScopedMcpServerConfig,
|
|
15
|
-
} from './config.js'
|
|
16
|
-
import {
|
|
17
|
-
getProjectMcpDecision,
|
|
18
|
-
isMcpServerDisabled,
|
|
19
|
-
setMcpServerEnabled,
|
|
20
|
-
setProjectMcpDecision,
|
|
21
|
-
} from './approvals.js'
|
|
22
|
-
import { buildMcpToolName, normalizeNameForMcp, parseMcpToolName } from './names.js'
|
|
23
|
-
import {
|
|
24
|
-
formatMcpCallResult,
|
|
25
|
-
formatMcpResourceResult,
|
|
26
|
-
promptMessagesToText,
|
|
27
|
-
} from './output.js'
|
|
28
|
-
import {
|
|
29
|
-
createTransport,
|
|
30
|
-
findScopedServer,
|
|
31
|
-
findServerSnapshot,
|
|
32
|
-
normalizeInputSchemaJson,
|
|
33
|
-
parsePromptArgs,
|
|
34
|
-
} from './managerHelpers.js'
|
|
35
|
-
|
|
36
|
-
const MCP_CONNECT_TIMEOUT_MS = 10_000
|
|
37
|
-
const MCP_LIST_TIMEOUT_MS = 10_000
|
|
38
|
-
const MCP_TOOL_TIMEOUT_MS = 120_000
|
|
39
|
-
|
|
40
|
-
export type ListedMcpTool = {
|
|
41
|
-
name: string
|
|
42
|
-
description?: string
|
|
43
|
-
inputSchema: {
|
|
44
|
-
type: 'object'
|
|
45
|
-
properties?: Record<string, unknown>
|
|
46
|
-
required?: string[]
|
|
47
|
-
[key: string]: unknown
|
|
48
|
-
}
|
|
49
|
-
annotations?: {
|
|
50
|
-
readOnlyHint?: boolean
|
|
51
|
-
destructiveHint?: boolean
|
|
52
|
-
openWorldHint?: boolean
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type McpResourceInfo = {
|
|
57
|
-
server: string
|
|
58
|
-
uri: string
|
|
59
|
-
name: string
|
|
60
|
-
description?: string
|
|
61
|
-
mimeType?: string
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type McpPromptInfo = {
|
|
65
|
-
server: string
|
|
66
|
-
promptName: string
|
|
67
|
-
slashName: string
|
|
68
|
-
description?: string
|
|
69
|
-
arguments?: Array<{ name: string; required?: boolean; description?: string }>
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export type McpServerSnapshot = {
|
|
73
|
-
name: string
|
|
74
|
-
normalizedName: string
|
|
75
|
-
scope: 'user' | 'project'
|
|
76
|
-
transport: 'stdio' | 'http' | 'sse'
|
|
77
|
-
status: 'pending' | 'connected' | 'failed' | 'disabled' | 'rejected'
|
|
78
|
-
tools: number
|
|
79
|
-
resources: number
|
|
80
|
-
prompts: number
|
|
81
|
-
message?: string
|
|
82
|
-
configHash: string
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export type McpSnapshot = {
|
|
86
|
-
servers: McpServerSnapshot[]
|
|
87
|
-
issues: McpConfigIssue[]
|
|
88
|
-
prompts: McpPromptInfo[]
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const EMPTY_MCP_SNAPSHOT: McpSnapshot = { servers: [], issues: [], prompts: [] }
|
|
92
|
-
|
|
93
|
-
export type McpRuntime = {
|
|
94
|
-
callTool(name: string, input: Record<string, unknown>, signal?: AbortSignal): Promise<ToolResult>
|
|
95
|
-
listResources(serverName?: string): Promise<string>
|
|
96
|
-
readResource(serverName: string, uri: string, signal?: AbortSignal): Promise<string>
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
type ConnectedMcpServer = {
|
|
100
|
-
name: string
|
|
101
|
-
normalizedName: string
|
|
102
|
-
config: ScopedMcpServerConfig
|
|
103
|
-
client: Client
|
|
104
|
-
transport: Transport
|
|
105
|
-
tools: ListedMcpTool[]
|
|
106
|
-
resources: McpResourceInfo[]
|
|
107
|
-
prompts: McpPromptInfo[]
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
type ToolIndexEntry = {
|
|
111
|
-
connection: ConnectedMcpServer
|
|
112
|
-
tool: ListedMcpTool
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const mcpInputSchema = z.object({}).passthrough()
|
|
116
|
-
const ajv = new Ajv({ strict: false })
|
|
117
|
-
|
|
118
|
-
export class McpManager implements McpRuntime {
|
|
119
|
-
private cwd: string
|
|
120
|
-
private closed = false
|
|
121
|
-
private snapshot: McpSnapshot = EMPTY_MCP_SNAPSHOT
|
|
122
|
-
private tools: Tool[] = []
|
|
123
|
-
private connections = new Map<string, ConnectedMcpServer>()
|
|
124
|
-
private toolIndex = new Map<string, ToolIndexEntry>()
|
|
125
|
-
|
|
126
|
-
constructor(
|
|
127
|
-
cwd: string,
|
|
128
|
-
private readonly onChange: (snapshot: McpSnapshot) => void,
|
|
129
|
-
) {
|
|
130
|
-
this.cwd = cwd
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
currentSnapshot(): McpSnapshot {
|
|
134
|
-
return this.snapshot
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
getTools(): Tool[] {
|
|
138
|
-
return this.tools
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
getPromptSuggestions(): Array<{ name: string; summary: string; completion: string; executeOnEnter: boolean }> {
|
|
142
|
-
return this.snapshot.prompts.map(prompt => ({
|
|
143
|
-
name: prompt.slashName.slice(1),
|
|
144
|
-
summary: prompt.description ?? `MCP prompt from ${prompt.server}`,
|
|
145
|
-
completion: `${prompt.slashName} `,
|
|
146
|
-
executeOnEnter: false,
|
|
147
|
-
}))
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async refresh(cwd = this.cwd): Promise<void> {
|
|
151
|
-
if (this.closed) return
|
|
152
|
-
this.cwd = cwd
|
|
153
|
-
await this.closeConnections()
|
|
154
|
-
if (this.closed) return
|
|
155
|
-
|
|
156
|
-
const loaded = await loadMcpConfigs(this.cwd)
|
|
157
|
-
const statuses: McpServerSnapshot[] = []
|
|
158
|
-
const promptInfos: McpPromptInfo[] = []
|
|
159
|
-
const nextTools: Tool[] = []
|
|
160
|
-
const seenNormalized = new Set<string>()
|
|
161
|
-
|
|
162
|
-
for (const server of loaded.servers) {
|
|
163
|
-
const normalizedName = normalizeNameForMcp(server.name)
|
|
164
|
-
const base: Omit<McpServerSnapshot, 'status' | 'tools' | 'resources' | 'prompts'> = {
|
|
165
|
-
name: server.name,
|
|
166
|
-
normalizedName,
|
|
167
|
-
scope: server.scope,
|
|
168
|
-
transport: mcpServerTransport(server.config),
|
|
169
|
-
configHash: server.configHash,
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (seenNormalized.has(normalizedName)) {
|
|
173
|
-
statuses.push({ ...base, status: 'failed', tools: 0, resources: 0, prompts: 0, message: 'normalized server name collides with another MCP server' })
|
|
174
|
-
continue
|
|
175
|
-
}
|
|
176
|
-
seenNormalized.add(normalizedName)
|
|
177
|
-
|
|
178
|
-
if (await isMcpServerDisabled(this.cwd, server.name)) {
|
|
179
|
-
statuses.push({ ...base, status: 'disabled', tools: 0, resources: 0, prompts: 0 })
|
|
180
|
-
continue
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (server.scope === 'project') {
|
|
184
|
-
const decision = await getProjectMcpDecision({
|
|
185
|
-
workspaceRoot: this.cwd,
|
|
186
|
-
serverName: server.name,
|
|
187
|
-
configHash: server.configHash,
|
|
188
|
-
})
|
|
189
|
-
if (decision === 'rejected') {
|
|
190
|
-
statuses.push({ ...base, status: 'rejected', tools: 0, resources: 0, prompts: 0, message: 'project server rejected' })
|
|
191
|
-
continue
|
|
192
|
-
}
|
|
193
|
-
if (decision !== 'approved') {
|
|
194
|
-
statuses.push({ ...base, status: 'pending', tools: 0, resources: 0, prompts: 0, message: 'project server needs approval' })
|
|
195
|
-
continue
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const connected = await this.connectServer(server, normalizedName)
|
|
200
|
-
if (!connected.ok) {
|
|
201
|
-
statuses.push({ ...base, status: 'failed', tools: 0, resources: 0, prompts: 0, message: connected.error })
|
|
202
|
-
continue
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
this.connections.set(normalizedName, connected.server)
|
|
206
|
-
for (const remoteTool of connected.server.tools) {
|
|
207
|
-
const wrappedTool = this.wrapTool(connected.server, remoteTool)
|
|
208
|
-
this.toolIndex.set(wrappedTool.name, { connection: connected.server, tool: remoteTool })
|
|
209
|
-
nextTools.push(wrappedTool)
|
|
210
|
-
}
|
|
211
|
-
promptInfos.push(...connected.server.prompts)
|
|
212
|
-
statuses.push({
|
|
213
|
-
...base,
|
|
214
|
-
status: 'connected',
|
|
215
|
-
tools: connected.server.tools.length,
|
|
216
|
-
resources: connected.server.resources.length,
|
|
217
|
-
prompts: connected.server.prompts.length,
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
this.tools = nextTools
|
|
222
|
-
this.snapshot = { servers: statuses, issues: loaded.issues, prompts: promptInfos }
|
|
223
|
-
if (!this.closed) this.onChange(this.snapshot)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async approveServer(serverName: string): Promise<string> {
|
|
227
|
-
const loaded = await loadMcpConfigs(this.cwd)
|
|
228
|
-
const server = findScopedServer(loaded.servers, serverName)
|
|
229
|
-
if (!server) return `MCP server "${serverName}" was not found.`
|
|
230
|
-
if (server.scope !== 'project') return `MCP server "${server.name}" is user-scoped and does not need project approval.`
|
|
231
|
-
await setProjectMcpDecision({
|
|
232
|
-
workspaceRoot: this.cwd,
|
|
233
|
-
serverName: server.name,
|
|
234
|
-
configHash: server.configHash,
|
|
235
|
-
decision: 'approved',
|
|
236
|
-
})
|
|
237
|
-
await this.refresh()
|
|
238
|
-
return `approved MCP project server "${server.name}".`
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async rejectServer(serverName: string): Promise<string> {
|
|
242
|
-
const loaded = await loadMcpConfigs(this.cwd)
|
|
243
|
-
const server = findScopedServer(loaded.servers, serverName)
|
|
244
|
-
if (!server) return `MCP server "${serverName}" was not found.`
|
|
245
|
-
if (server.scope !== 'project') return `MCP server "${server.name}" is user-scoped; disable it instead.`
|
|
246
|
-
await setProjectMcpDecision({
|
|
247
|
-
workspaceRoot: this.cwd,
|
|
248
|
-
serverName: server.name,
|
|
249
|
-
configHash: server.configHash,
|
|
250
|
-
decision: 'rejected',
|
|
251
|
-
})
|
|
252
|
-
await this.refresh()
|
|
253
|
-
return `rejected MCP project server "${server.name}".`
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async setEnabled(serverName: string, enabled: boolean): Promise<string> {
|
|
257
|
-
const loaded = await loadMcpConfigs(this.cwd)
|
|
258
|
-
const server = findScopedServer(loaded.servers, serverName)
|
|
259
|
-
if (!server) return `MCP server "${serverName}" was not found.`
|
|
260
|
-
await setMcpServerEnabled({ workspaceRoot: this.cwd, serverName: server.name, enabled })
|
|
261
|
-
await this.refresh()
|
|
262
|
-
return `${enabled ? 'enabled' : 'disabled'} MCP server "${server.name}".`
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async reconnect(serverName?: string): Promise<string> {
|
|
266
|
-
await this.refresh()
|
|
267
|
-
if (!serverName || serverName === 'all') return 'reconnected MCP servers.'
|
|
268
|
-
const server = findServerSnapshot(this.snapshot.servers, serverName)
|
|
269
|
-
return server ? `reconnected MCP server "${server.name}".` : `MCP server "${serverName}" was not found.`
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async addJson(name: string, json: string, scope: 'user' | 'project'): Promise<string> {
|
|
273
|
-
const config = parseMcpServerConfigJson(json)
|
|
274
|
-
const filePath = await addMcpServerConfig({ cwd: this.cwd, scope, name, config })
|
|
275
|
-
await this.refresh()
|
|
276
|
-
return `added MCP server "${name}" to ${scope} config: ${filePath}`
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
renderStatus(): string {
|
|
280
|
-
const lines: string[] = ['mcp servers:']
|
|
281
|
-
if (this.snapshot.servers.length === 0) {
|
|
282
|
-
lines.push(' none configured. use /mcp add-json <name> <json>')
|
|
283
|
-
} else {
|
|
284
|
-
for (const server of this.snapshot.servers) {
|
|
285
|
-
const counts = server.status === 'connected'
|
|
286
|
-
? ` · ${server.tools} tools, ${server.resources} resources, ${server.prompts} prompts`
|
|
287
|
-
: server.message ? ` · ${server.message}` : ''
|
|
288
|
-
lines.push(` ${server.name} ${server.status} ${server.scope}/${server.transport}${counts}`)
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
if (this.snapshot.issues.length > 0) {
|
|
292
|
-
lines.push('', 'mcp config notes:')
|
|
293
|
-
for (const issue of this.snapshot.issues) {
|
|
294
|
-
const server = issue.serverName ? ` ${issue.serverName}` : ''
|
|
295
|
-
lines.push(` ${issue.severity}${server}: ${issue.message}`)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return lines.join('\n')
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async runPromptSlash(name: string, argsText: string, signal?: AbortSignal): Promise<string | null> {
|
|
302
|
-
const parsed = parseMcpToolName(name)
|
|
303
|
-
if (!parsed) return null
|
|
304
|
-
const connection = this.connections.get(parsed.serverName)
|
|
305
|
-
if (!connection) return null
|
|
306
|
-
const prompt = connection.prompts.find(entry => normalizeNameForMcp(entry.promptName) === parsed.toolName)
|
|
307
|
-
if (!prompt) return null
|
|
308
|
-
const args = parsePromptArgs(argsText)
|
|
309
|
-
const result = await connection.client.getPrompt(
|
|
310
|
-
{ name: prompt.promptName, arguments: Object.keys(args).length > 0 ? args : undefined },
|
|
311
|
-
{ signal, timeout: MCP_TOOL_TIMEOUT_MS },
|
|
312
|
-
)
|
|
313
|
-
return promptMessagesToText(result)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async callTool(name: string, input: Record<string, unknown>, signal?: AbortSignal): Promise<ToolResult> {
|
|
317
|
-
const entry = this.toolIndex.get(name)
|
|
318
|
-
if (!entry) {
|
|
319
|
-
return { ok: false, summary: `${name} not connected`, content: `MCP tool "${name}" is not connected.` }
|
|
320
|
-
}
|
|
321
|
-
try {
|
|
322
|
-
const result = await entry.connection.client.callTool(
|
|
323
|
-
{ name: entry.tool.name, arguments: input },
|
|
324
|
-
CallToolResultSchema,
|
|
325
|
-
{ signal, timeout: MCP_TOOL_TIMEOUT_MS },
|
|
326
|
-
)
|
|
327
|
-
const formatted = formatMcpCallResult(result)
|
|
328
|
-
return {
|
|
329
|
-
ok: formatted.ok,
|
|
330
|
-
summary: `${entry.connection.name}/${entry.tool.name}`,
|
|
331
|
-
content: formatted.content,
|
|
332
|
-
}
|
|
333
|
-
} catch (err: unknown) {
|
|
334
|
-
return {
|
|
335
|
-
ok: false,
|
|
336
|
-
summary: `${entry.connection.name}/${entry.tool.name} failed`,
|
|
337
|
-
content: (err as Error).message || 'MCP tool failed',
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
async listResources(serverName?: string): Promise<string> {
|
|
343
|
-
const connections = serverName
|
|
344
|
-
? [this.getConnection(serverName)].filter((conn): conn is ConnectedMcpServer => Boolean(conn))
|
|
345
|
-
: [...this.connections.values()]
|
|
346
|
-
if (connections.length === 0) return serverName ? `MCP server "${serverName}" is not connected.` : 'No connected MCP servers.'
|
|
347
|
-
const lines: string[] = []
|
|
348
|
-
for (const connection of connections) {
|
|
349
|
-
lines.push(`${connection.name}:`)
|
|
350
|
-
if (connection.resources.length === 0) {
|
|
351
|
-
lines.push(' no resources')
|
|
352
|
-
} else {
|
|
353
|
-
for (const resource of connection.resources) {
|
|
354
|
-
const mime = resource.mimeType ? ` ${resource.mimeType}` : ''
|
|
355
|
-
const desc = resource.description ? ` · ${resource.description}` : ''
|
|
356
|
-
lines.push(` ${resource.uri}${mime}${desc}`)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return lines.join('\n')
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
async readResource(serverName: string, uri: string, signal?: AbortSignal): Promise<string> {
|
|
364
|
-
const connection = this.getConnection(serverName)
|
|
365
|
-
if (!connection) return `MCP server "${serverName}" is not connected.`
|
|
366
|
-
const result = await connection.client.readResource({ uri }, { signal, timeout: MCP_TOOL_TIMEOUT_MS })
|
|
367
|
-
return formatMcpResourceResult(result)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async close(): Promise<void> {
|
|
371
|
-
this.closed = true
|
|
372
|
-
await this.closeConnections()
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
private async connectServer(
|
|
376
|
-
config: ScopedMcpServerConfig,
|
|
377
|
-
normalizedName: string,
|
|
378
|
-
): Promise<{ ok: true; server: ConnectedMcpServer } | { ok: false; error: string }> {
|
|
379
|
-
const client = new Client(
|
|
380
|
-
{ name: 'ethagent', version: '1.0.0' },
|
|
381
|
-
{ capabilities: {} },
|
|
382
|
-
)
|
|
383
|
-
const transport = createTransport(config.config, this.cwd)
|
|
384
|
-
try {
|
|
385
|
-
await client.connect(transport, { timeout: MCP_CONNECT_TIMEOUT_MS })
|
|
386
|
-
const capabilities = client.getServerCapabilities()
|
|
387
|
-
const tools = capabilities?.tools
|
|
388
|
-
? (await client.listTools(undefined, { timeout: MCP_LIST_TIMEOUT_MS })).tools as ListedMcpTool[]
|
|
389
|
-
: []
|
|
390
|
-
const resources = capabilities?.resources
|
|
391
|
-
? (await client.listResources(undefined, { timeout: MCP_LIST_TIMEOUT_MS })).resources.map(resource => ({
|
|
392
|
-
server: config.name,
|
|
393
|
-
uri: resource.uri,
|
|
394
|
-
name: resource.name,
|
|
395
|
-
description: resource.description,
|
|
396
|
-
mimeType: resource.mimeType,
|
|
397
|
-
}))
|
|
398
|
-
: []
|
|
399
|
-
const prompts = capabilities?.prompts
|
|
400
|
-
? (await client.listPrompts(undefined, { timeout: MCP_LIST_TIMEOUT_MS })).prompts.map(prompt => ({
|
|
401
|
-
server: config.name,
|
|
402
|
-
promptName: prompt.name,
|
|
403
|
-
slashName: `/${buildMcpToolName(config.name, prompt.name)}`,
|
|
404
|
-
description: prompt.description,
|
|
405
|
-
arguments: prompt.arguments,
|
|
406
|
-
}))
|
|
407
|
-
: []
|
|
408
|
-
return {
|
|
409
|
-
ok: true,
|
|
410
|
-
server: {
|
|
411
|
-
name: config.name,
|
|
412
|
-
normalizedName,
|
|
413
|
-
config,
|
|
414
|
-
client,
|
|
415
|
-
transport,
|
|
416
|
-
tools,
|
|
417
|
-
resources,
|
|
418
|
-
prompts,
|
|
419
|
-
},
|
|
420
|
-
}
|
|
421
|
-
} catch (err: unknown) {
|
|
422
|
-
await transport.close().catch(() => {})
|
|
423
|
-
return { ok: false, error: (err as Error).message || 'MCP connection failed' }
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
private wrapTool(connection: ConnectedMcpServer, tool: ListedMcpTool): Tool<typeof mcpInputSchema> {
|
|
428
|
-
const toolName = buildMcpToolName(connection.name, tool.name)
|
|
429
|
-
const validate = ajv.compile(tool.inputSchema)
|
|
430
|
-
const readOnly = tool.annotations?.readOnlyHint === true
|
|
431
|
-
return {
|
|
432
|
-
name: toolName,
|
|
433
|
-
kind: 'mcp',
|
|
434
|
-
readOnly,
|
|
435
|
-
description: tool.description ?? `MCP tool ${tool.name} from ${connection.name}`,
|
|
436
|
-
inputSchema: mcpInputSchema,
|
|
437
|
-
inputSchemaJson: normalizeInputSchemaJson(tool.inputSchema),
|
|
438
|
-
parse(input) {
|
|
439
|
-
const parsed = mcpInputSchema.parse(input)
|
|
440
|
-
if (!validate(parsed)) {
|
|
441
|
-
throw new Error(`MCP tool input failed schema validation: ${ajv.errorsText(validate.errors)}`)
|
|
442
|
-
}
|
|
443
|
-
return parsed
|
|
444
|
-
},
|
|
445
|
-
async buildPermissionRequest() {
|
|
446
|
-
return {
|
|
447
|
-
kind: 'mcp',
|
|
448
|
-
title: 'Allow MCP tool?',
|
|
449
|
-
subtitle: `${connection.name} / ${tool.name}`,
|
|
450
|
-
serverName: connection.name,
|
|
451
|
-
normalizedServerName: connection.normalizedName,
|
|
452
|
-
toolName: tool.name,
|
|
453
|
-
toolKey: toolName,
|
|
454
|
-
readOnly,
|
|
455
|
-
destructive: tool.annotations?.destructiveHint === true,
|
|
456
|
-
openWorld: tool.annotations?.openWorldHint === true,
|
|
457
|
-
canPersistServer: true,
|
|
458
|
-
}
|
|
459
|
-
},
|
|
460
|
-
async execute(input, context) {
|
|
461
|
-
if (!context.mcp) {
|
|
462
|
-
return { ok: false, summary: `${toolName} unavailable`, content: 'MCP runtime is not available.' }
|
|
463
|
-
}
|
|
464
|
-
return context.mcp.callTool(toolName, input, context.abortSignal)
|
|
465
|
-
},
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
private getConnection(serverName: string): ConnectedMcpServer | undefined {
|
|
470
|
-
const normalized = normalizeNameForMcp(serverName)
|
|
471
|
-
return this.connections.get(normalized) ?? [...this.connections.values()].find(conn => conn.name === serverName)
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
private async closeConnections(): Promise<void> {
|
|
475
|
-
for (const connection of this.connections.values()) {
|
|
476
|
-
await connection.transport.close().catch(() => {})
|
|
477
|
-
}
|
|
478
|
-
this.connections.clear()
|
|
479
|
-
this.toolIndex.clear()
|
|
480
|
-
this.tools = []
|
|
481
|
-
}
|
|
482
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
|
|
2
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
3
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
|
4
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
|
|
5
|
-
import type { Tool } from '../tools/contracts.js'
|
|
6
|
-
import type { McpServerConfig, ScopedMcpServerConfig } from './config.js'
|
|
7
|
-
import { normalizeNameForMcp } from './names.js'
|
|
8
|
-
import type { ListedMcpTool, McpServerSnapshot } from './manager.js'
|
|
9
|
-
|
|
10
|
-
export function createTransport(config: McpServerConfig, cwd: string): Transport {
|
|
11
|
-
if (config.type === 'http') {
|
|
12
|
-
return new StreamableHTTPClientTransport(new URL(config.url), {
|
|
13
|
-
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
14
|
-
})
|
|
15
|
-
}
|
|
16
|
-
if (config.type === 'sse') {
|
|
17
|
-
return new SSEClientTransport(new URL(config.url), {
|
|
18
|
-
requestInit: config.headers ? { headers: config.headers } : undefined,
|
|
19
|
-
eventSourceInit: config.headers ? { fetch: (url, init) => fetch(url, { ...init, headers: config.headers }) } : undefined,
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
return new StdioClientTransport({
|
|
23
|
-
command: config.command,
|
|
24
|
-
args: config.args ?? [],
|
|
25
|
-
env: config.env ? mergeProcessEnv(config.env) : undefined,
|
|
26
|
-
cwd: config.cwd ?? cwd,
|
|
27
|
-
stderr: 'pipe',
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function normalizeInputSchemaJson(schema: ListedMcpTool['inputSchema']): Tool['inputSchemaJson'] {
|
|
32
|
-
return {
|
|
33
|
-
type: 'object',
|
|
34
|
-
properties: schema.properties,
|
|
35
|
-
required: schema.required,
|
|
36
|
-
oneOf: Array.isArray(schema.oneOf) ? schema.oneOf as Array<Record<string, unknown>> : undefined,
|
|
37
|
-
anyOf: Array.isArray(schema.anyOf) ? schema.anyOf as Array<Record<string, unknown>> : undefined,
|
|
38
|
-
additionalProperties: schema.additionalProperties as boolean | undefined,
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function findScopedServer(servers: ScopedMcpServerConfig[], name: string): ScopedMcpServerConfig | undefined {
|
|
43
|
-
const normalized = normalizeNameForMcp(name)
|
|
44
|
-
return servers.find(server => server.name === name || normalizeNameForMcp(server.name) === normalized)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function findServerSnapshot(servers: McpServerSnapshot[], name: string): McpServerSnapshot | undefined {
|
|
48
|
-
const normalized = normalizeNameForMcp(name)
|
|
49
|
-
return servers.find(server => server.name === name || server.normalizedName === normalized)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function parsePromptArgs(value: string): Record<string, string> {
|
|
53
|
-
const args: Record<string, string> = {}
|
|
54
|
-
for (const token of value.trim().split(/\s+/).filter(Boolean)) {
|
|
55
|
-
const idx = token.indexOf('=')
|
|
56
|
-
if (idx === -1) continue
|
|
57
|
-
const key = token.slice(0, idx)
|
|
58
|
-
if (!key) continue
|
|
59
|
-
args[key] = token.slice(idx + 1)
|
|
60
|
-
}
|
|
61
|
-
return args
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function mergeProcessEnv(extra: Record<string, string>): Record<string, string> {
|
|
65
|
-
const env: Record<string, string> = {}
|
|
66
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
67
|
-
if (value !== undefined) env[key] = value
|
|
68
|
-
}
|
|
69
|
-
return { ...env, ...extra }
|
|
70
|
-
}
|
package/src/mcp/names.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export function normalizeNameForMcp(name: string): string {
|
|
2
|
-
const normalized = name.trim().replace(/[^a-zA-Z0-9_-]+/g, '_').replace(/^_+|_+$/g, '')
|
|
3
|
-
return normalized || 'unnamed'
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function mcpToolPrefix(serverName: string): string {
|
|
7
|
-
return `mcp__${normalizeNameForMcp(serverName)}__`
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function buildMcpToolName(serverName: string, toolName: string): string {
|
|
11
|
-
return `${mcpToolPrefix(serverName)}${normalizeNameForMcp(toolName)}`
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function parseMcpToolName(value: string): { serverName: string; toolName: string } | null {
|
|
15
|
-
const parts = value.split('__')
|
|
16
|
-
const [prefix, serverName, ...toolNameParts] = parts
|
|
17
|
-
if (prefix !== 'mcp' || !serverName || toolNameParts.length === 0) return null
|
|
18
|
-
return { serverName, toolName: toolNameParts.join('__') }
|
|
19
|
-
}
|
package/src/mcp/output.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
export const MAX_MCP_OUTPUT_CHARS = 100_000
|
|
2
|
-
|
|
3
|
-
export function formatMcpCallResult(result: unknown): { ok: boolean; content: string } {
|
|
4
|
-
if (isRecord(result) && 'toolResult' in result) {
|
|
5
|
-
return { ok: true, content: truncateMcpOutput(formatUnknown(result.toolResult)) }
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const isError = isRecord(result) && result.isError === true
|
|
9
|
-
const parts: string[] = []
|
|
10
|
-
if (isRecord(result) && Array.isArray(result.content)) {
|
|
11
|
-
for (const block of result.content) parts.push(formatContentBlock(block))
|
|
12
|
-
}
|
|
13
|
-
if (isRecord(result) && isRecord(result.structuredContent)) {
|
|
14
|
-
parts.push(`structuredContent:\n${JSON.stringify(result.structuredContent, null, 2)}`)
|
|
15
|
-
}
|
|
16
|
-
if (parts.length === 0) parts.push(formatUnknown(result))
|
|
17
|
-
const content = parts.filter(Boolean).join('\n\n')
|
|
18
|
-
return { ok: !isError, content: truncateMcpOutput(isError ? annotateMcpError(content) : content) }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function formatMcpResourceResult(result: unknown): string {
|
|
22
|
-
if (!isRecord(result) || !Array.isArray(result.contents)) return truncateMcpOutput(formatUnknown(result))
|
|
23
|
-
return truncateMcpOutput(result.contents.map(content => {
|
|
24
|
-
if (!isRecord(content)) return formatUnknown(content)
|
|
25
|
-
const uri = typeof content.uri === 'string' ? content.uri : 'resource'
|
|
26
|
-
const mime = typeof content.mimeType === 'string' ? ` (${content.mimeType})` : ''
|
|
27
|
-
if (typeof content.text === 'string') return `${uri}${mime}\n${content.text}`
|
|
28
|
-
if (typeof content.blob === 'string') return `${uri}${mime}\n[binary blob ${content.blob.length} base64 chars]`
|
|
29
|
-
return `${uri}${mime}\n${formatUnknown(content)}`
|
|
30
|
-
}).join('\n\n'))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function truncateMcpOutput(value: string): string {
|
|
34
|
-
if (value.length <= MAX_MCP_OUTPUT_CHARS) return value
|
|
35
|
-
return `${value.slice(0, MAX_MCP_OUTPUT_CHARS)}\n\n[OUTPUT TRUNCATED · exceeded ${MAX_MCP_OUTPUT_CHARS.toLocaleString()} characters. If this MCP server supports pagination or filters, call it again for a narrower result.]`
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function promptMessagesToText(result: unknown): string {
|
|
39
|
-
if (!isRecord(result) || !Array.isArray(result.messages)) return formatUnknown(result)
|
|
40
|
-
return result.messages.map(message => {
|
|
41
|
-
if (!isRecord(message)) return formatUnknown(message)
|
|
42
|
-
const role = typeof message.role === 'string' ? message.role : 'user'
|
|
43
|
-
const content = formatContentBlock(message.content)
|
|
44
|
-
return `${role}:\n${content}`
|
|
45
|
-
}).join('\n\n')
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function formatContentBlock(block: unknown): string {
|
|
49
|
-
if (!isRecord(block)) return formatUnknown(block)
|
|
50
|
-
if (block.type === 'text' && typeof block.text === 'string') return block.text
|
|
51
|
-
if (block.type === 'image') {
|
|
52
|
-
const mime = typeof block.mimeType === 'string' ? block.mimeType : 'image'
|
|
53
|
-
const data = typeof block.data === 'string' ? ` ${block.data.length} base64 chars` : ''
|
|
54
|
-
return `[image ${mime}${data}]`
|
|
55
|
-
}
|
|
56
|
-
if (block.type === 'audio') {
|
|
57
|
-
const mime = typeof block.mimeType === 'string' ? block.mimeType : 'audio'
|
|
58
|
-
const data = typeof block.data === 'string' ? ` ${block.data.length} base64 chars` : ''
|
|
59
|
-
return `[audio ${mime}${data}]`
|
|
60
|
-
}
|
|
61
|
-
if (block.type === 'resource' && isRecord(block.resource)) {
|
|
62
|
-
const resource = block.resource
|
|
63
|
-
const uri = typeof resource.uri === 'string' ? resource.uri : 'resource'
|
|
64
|
-
if (typeof resource.text === 'string') return `${uri}\n${resource.text}`
|
|
65
|
-
if (typeof resource.blob === 'string') return `${uri}\n[binary blob ${resource.blob.length} base64 chars]`
|
|
66
|
-
}
|
|
67
|
-
if (block.type === 'resource_link') {
|
|
68
|
-
const name = typeof block.name === 'string' ? block.name : 'resource'
|
|
69
|
-
const uri = typeof block.uri === 'string' ? block.uri : ''
|
|
70
|
-
return `[resource link ${name}${uri ? ` ${uri}` : ''}]`
|
|
71
|
-
}
|
|
72
|
-
return formatUnknown(block)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function formatUnknown(value: unknown): string {
|
|
76
|
-
if (typeof value === 'string') return value
|
|
77
|
-
return JSON.stringify(value, null, 2) ?? String(value)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function annotateMcpError(content: string): string {
|
|
81
|
-
const lower = content.toLowerCase()
|
|
82
|
-
const looksRateLimited = lower.includes('rate limit') ||
|
|
83
|
-
lower.includes('too quickly') ||
|
|
84
|
-
lower.includes('429') ||
|
|
85
|
-
lower.includes('ddg detected an anomaly')
|
|
86
|
-
|
|
87
|
-
if (!looksRateLimited) return content
|
|
88
|
-
return [
|
|
89
|
-
content,
|
|
90
|
-
'[MCP server returned an upstream rate-limit or anti-abuse error; the MCP transport is still connected. Wait before retrying or use an API-key-backed search server for frequent searches.]',
|
|
91
|
-
].join('\n\n')
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
95
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
96
|
-
}
|