@clawpump/claw-agent 0.1.15 → 0.1.16

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.
@@ -2,7 +2,7 @@
2
2
  "name": "hermes",
3
3
  "productName": "Claw Agent",
4
4
  "private": true,
5
- "version": "0.15.5",
5
+ "version": "0.15.7",
6
6
  "description": "Claw Agent by ClawPump — native desktop app for Solana agents, built on Hermes Agent by Nous Research.",
7
7
  "author": "ClawPump (built on Hermes by Nous Research)",
8
8
  "type": "module",
@@ -95,7 +95,7 @@ import {
95
95
  sessionPinId
96
96
  } from '@/store/session'
97
97
 
98
- import { type AppView, ARTIFACTS_ROUTE, MESSAGING_ROUTE, SKILLS_ROUTE, WALLET_ROUTE, X402_ROUTE } from '../../routes'
98
+ import { type AppView, ARTIFACTS_ROUTE, MCP_ROUTE, MESSAGING_ROUTE, SKILLS_ROUTE, WALLET_ROUTE, X402_ROUTE } from '../../routes'
99
99
  import { SidebarPanelLabel } from '../../shell/sidebar-label'
100
100
  import type { SidebarNavItem } from '../../types'
101
101
 
@@ -133,7 +133,8 @@ const SIDEBAR_NAV: SidebarNavItem[] = [
133
133
  { id: 'messaging', label: '', icon: props => <Codicon name="comment" {...props} />, route: MESSAGING_ROUTE },
134
134
  { id: 'artifacts', label: '', icon: props => <Codicon name="files" {...props} />, route: ARTIFACTS_ROUTE },
135
135
  { id: 'wallet', label: 'Wallet', icon: props => <Codicon name="credit-card" {...props} />, route: WALLET_ROUTE },
136
- { id: 'x402', label: 'x402', icon: props => <Codicon name="zap" {...props} />, route: X402_ROUTE }
136
+ { id: 'x402', label: 'x402', icon: props => <Codicon name="zap" {...props} />, route: X402_ROUTE },
137
+ { id: 'mcp', label: 'MCP', icon: props => <Codicon name="plug" {...props} />, route: MCP_ROUTE }
137
138
  ]
138
139
 
139
140
  const WORKSPACE_PAGE = 5
@@ -139,6 +139,7 @@ const SettingsView = lazy(async () => ({ default: (await import('./settings')).S
139
139
  const SkillsView = lazy(async () => ({ default: (await import('./skills')).SkillsView }))
140
140
  const WalletView = lazy(async () => ({ default: (await import('./wallet')).WalletView }))
141
141
  const X402View = lazy(async () => ({ default: (await import('./x402')).X402View }))
142
+ const McpView = lazy(async () => ({ default: (await import('./mcp')).McpView }))
142
143
 
143
144
  // Latest cron-job sessions surfaced in the collapsed "Cron jobs" section. The
144
145
  // Cron sessions are written by a background scheduler tick (the desktop
@@ -1203,6 +1204,14 @@ export function DesktopController() {
1203
1204
  }
1204
1205
  path="x402"
1205
1206
  />
1207
+ <Route
1208
+ element={
1209
+ <Suspense fallback={null}>
1210
+ <McpView setStatusbarItemGroup={setStatusbarItemGroup} />
1211
+ </Suspense>
1212
+ }
1213
+ path="mcp"
1214
+ />
1206
1215
  <Route element={null} path="cron" />
1207
1216
  <Route element={null} path="profiles" />
1208
1217
  <Route element={null} path="settings" />
@@ -0,0 +1,123 @@
1
+ import { useQuery } from '@tanstack/react-query'
2
+
3
+ import { Badge } from '@/components/ui/badge'
4
+ import { Button } from '@/components/ui/button'
5
+ import { getMcpServers, type McpServer } from '@/hermes'
6
+ import { Check, ExternalLink, Loader2, Zap } from '@/lib/icons'
7
+
8
+ import type { SetStatusbarItemGroup } from '../shell/statusbar-controls'
9
+
10
+ // Where an unauthenticated user goes to connect the ClawPump MCP — the gateway
11
+ // (browser login / cpk_* key). Shown prominently when not connected.
12
+ const CLAWPUMP_GATEWAY_URL = 'https://agents.clawpump.tech/dashboard/api'
13
+ const CLAWPUMP_NAMES = new Set(['clawpump', 'clawpump-agents', 'clawpump-stdio'])
14
+
15
+ const isClawpump = (s: McpServer) => CLAWPUMP_NAMES.has(s.name)
16
+
17
+ interface McpViewProps extends React.ComponentProps<'section'> {
18
+ setStatusbarItemGroup?: SetStatusbarItemGroup
19
+ }
20
+
21
+ export function McpView({ setStatusbarItemGroup: _setStatusbarItemGroup, ...props }: McpViewProps) {
22
+ const query = useQuery({ queryKey: ['mcp-servers'], queryFn: getMcpServers, staleTime: 15_000 })
23
+ const servers = query.data?.servers ?? []
24
+ const clawpump = servers.find(isClawpump)
25
+ const others = servers.filter(s => !isClawpump(s))
26
+
27
+ // authenticated === true → OAuth tokens are on disk (the same session chat
28
+ // uses), so the MCP is genuinely connected. === false → needs sign-in.
29
+ const clawpumpConnected = clawpump?.authenticated === true
30
+ const clawpumpNeedsAuth = clawpump != null && clawpump.authenticated === false
31
+
32
+ const openGateway = () => void window.hermesDesktop?.openExternal?.(CLAWPUMP_GATEWAY_URL)
33
+
34
+ return (
35
+ <section {...props} className="flex h-full min-h-0 flex-col">
36
+ <div className="min-h-0 flex-1 overflow-y-auto">
37
+ <div className="mx-auto max-w-3xl space-y-4 px-5 py-4">
38
+ <header className="flex items-center gap-2">
39
+ <Zap className="size-5 text-primary" />
40
+ <h1 className="text-lg font-semibold">MCP Servers</h1>
41
+ </header>
42
+ <p className="text-sm text-muted-foreground">
43
+ Model Context Protocol servers wired into your agent. The ClawPump MCP brings 133 tools —
44
+ wallet, trading, marketplace, perps, token launch.
45
+ </p>
46
+
47
+ {query.isPending && (
48
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
49
+ <Loader2 className="size-4 animate-spin" /> Loading…
50
+ </div>
51
+ )}
52
+
53
+ {clawpump && (
54
+ <div className="rounded-lg border p-4">
55
+ <div className="flex items-center justify-between gap-2">
56
+ <div className="flex items-center gap-2">
57
+ <span className="font-medium">ClawPump MCP</span>
58
+ <span className="text-xs text-muted-foreground">{clawpump.name}</span>
59
+ </div>
60
+ {clawpumpConnected ? (
61
+ <Badge className="gap-1">
62
+ <Check className="size-3" /> Connected
63
+ </Badge>
64
+ ) : (
65
+ <Badge variant="outline">Not connected</Badge>
66
+ )}
67
+ </div>
68
+ {clawpumpNeedsAuth && (
69
+ <div className="mt-3 space-y-2">
70
+ <p className="text-sm text-muted-foreground">
71
+ Sign in at the ClawPump gateway to connect — then your 133 ClawPump tools come
72
+ online in chat and across the app.
73
+ </p>
74
+ <div className="flex flex-wrap items-center gap-2">
75
+ <Button onClick={openGateway} size="sm">
76
+ <ExternalLink className="size-4" /> Connect at the gateway
77
+ </Button>
78
+ <code className="rounded bg-muted px-2 py-1 text-xs">claw clawpump login</code>
79
+ </div>
80
+ <p className="break-all text-xs text-muted-foreground">{CLAWPUMP_GATEWAY_URL}</p>
81
+ </div>
82
+ )}
83
+ </div>
84
+ )}
85
+
86
+ {!query.isPending && !clawpump && (
87
+ <div className="space-y-2 rounded-lg border p-4">
88
+ <p className="text-sm text-muted-foreground">
89
+ The ClawPump MCP isn&apos;t installed yet. Connect it at the gateway to unlock the 133
90
+ tools.
91
+ </p>
92
+ <div className="flex flex-wrap items-center gap-2">
93
+ <Button onClick={openGateway} size="sm">
94
+ <ExternalLink className="size-4" /> Open the ClawPump gateway
95
+ </Button>
96
+ <code className="rounded bg-muted px-2 py-1 text-xs">claw clawpump setup</code>
97
+ </div>
98
+ <p className="break-all text-xs text-muted-foreground">{CLAWPUMP_GATEWAY_URL}</p>
99
+ </div>
100
+ )}
101
+
102
+ {others.length > 0 && (
103
+ <div className="space-y-2">
104
+ <h2 className="text-sm font-medium text-muted-foreground">Other servers</h2>
105
+ {others.map(s => (
106
+ <div
107
+ className="flex items-center justify-between rounded-md border px-3 py-2"
108
+ key={s.name}
109
+ >
110
+ <div className="flex items-center gap-2">
111
+ <span className="text-sm font-medium">{s.name}</span>
112
+ <span className="text-xs text-muted-foreground">{s.transport}</span>
113
+ </div>
114
+ {s.enabled ? <Badge>Enabled</Badge> : <Badge variant="outline">Disabled</Badge>}
115
+ </div>
116
+ ))}
117
+ </div>
118
+ )}
119
+ </div>
120
+ </div>
121
+ </section>
122
+ )
123
+ }
@@ -10,6 +10,7 @@ export const PROFILES_ROUTE = '/profiles'
10
10
  export const AGENTS_ROUTE = '/agents'
11
11
  export const WALLET_ROUTE = '/wallet'
12
12
  export const X402_ROUTE = '/x402'
13
+ export const MCP_ROUTE = '/mcp'
13
14
 
14
15
  export type AppView =
15
16
  | 'agents'
@@ -17,6 +18,7 @@ export type AppView =
17
18
  | 'chat'
18
19
  | 'command-center'
19
20
  | 'cron'
21
+ | 'mcp'
20
22
  | 'messaging'
21
23
  | 'profiles'
22
24
  | 'settings'
@@ -29,6 +31,7 @@ export type AppRouteId =
29
31
  | 'artifacts'
30
32
  | 'command-center'
31
33
  | 'cron'
34
+ | 'mcp'
32
35
  | 'messaging'
33
36
  | 'new'
34
37
  | 'profiles'
@@ -54,7 +57,8 @@ export const APP_ROUTES = [
54
57
  { id: 'profiles', path: PROFILES_ROUTE, view: 'profiles' },
55
58
  { id: 'agents', path: AGENTS_ROUTE, view: 'agents' },
56
59
  { id: 'wallet', path: WALLET_ROUTE, view: 'wallet' },
57
- { id: 'x402', path: X402_ROUTE, view: 'x402' }
60
+ { id: 'x402', path: X402_ROUTE, view: 'x402' },
61
+ { id: 'mcp', path: MCP_ROUTE, view: 'mcp' }
58
62
  ] as const satisfies readonly AppRoute[]
59
63
 
60
64
  const APP_VIEW_BY_PATH = new Map<string, AppView>(APP_ROUTES.map(route => [route.path, route.view]))
@@ -125,6 +125,7 @@ export type CommandDispatchResponse =
125
125
  export type SidebarNavId =
126
126
  | 'artifacts'
127
127
  | 'command-center'
128
+ | 'mcp'
128
129
  | 'messaging'
129
130
  | 'new-session'
130
131
  | 'settings'
@@ -771,6 +771,25 @@ export function getPodStatus(): Promise<{ connected: boolean; balance_usdc?: num
771
771
  })
772
772
  }
773
773
 
774
+ export interface McpServer {
775
+ name: string
776
+ transport: string
777
+ url?: string | null
778
+ command?: string | null
779
+ enabled: boolean
780
+ /** OAuth servers: true/false once tokens are checked; null when not applicable. */
781
+ authenticated?: boolean | null
782
+ tools?: string[] | null
783
+ }
784
+
785
+ /** Configured MCP servers + their connection state (for the sidebar MCP page). */
786
+ export function getMcpServers(): Promise<{ servers: McpServer[] }> {
787
+ return window.hermesDesktop.api<{ servers: McpServer[] }>({
788
+ ...profileScoped(),
789
+ path: '/api/mcp/servers'
790
+ })
791
+ }
792
+
774
793
  /** ClawPump agent wallets (id + name + USDC balance) for the Pod funding picker. */
775
794
  export function getPodWallets(): Promise<{ ok: boolean; wallets: PodWallet[]; error?: string }> {
776
795
  return window.hermesDesktop.api<{ ok: boolean; wallets: PodWallet[]; error?: string }>({
@@ -488,7 +488,15 @@ def _clawpump_mcp_config():
488
488
  from hermes_cli.mcp_config import _get_mcp_servers, _resolve_mcp_server_config
489
489
 
490
490
  servers = _get_mcp_servers()
491
- name = next((n for n in ("clawpump", "clawpump-stdio") if n in servers), None)
491
+ # Match the dashboard's _clawpump_mcp(): known names first, then any
492
+ # clawpump* entry (e.g. clawpump-agents), so every ClawPump MCP variant
493
+ # the agent can load is also resolvable here.
494
+ name = next(
495
+ (n for n in ("clawpump", "clawpump-stdio", "clawpump-agents") if n in servers),
496
+ None,
497
+ )
498
+ if not name:
499
+ name = next((n for n in servers if n.startswith("clawpump")), None)
492
500
  if not name:
493
501
  return (None, None)
494
502
  return (name, _resolve_mcp_server_config(servers[name]))
@@ -7983,6 +7983,24 @@ def _redact_mcp_env(env: Dict[str, Any]) -> Dict[str, str]:
7983
7983
  return out
7984
7984
 
7985
7985
 
7986
+ def _mcp_server_authenticated(name: str, cfg: Dict[str, Any]) -> Optional[bool]:
7987
+ """True/False if this server uses OAuth and we can tell whether tokens are
7988
+ on disk; None when auth state isn't applicable/known (stdio key servers).
7989
+
7990
+ Lets the GUI render a real "Connected" vs "Connect" state for the ClawPump
7991
+ MCP instead of guessing — the OAuth token is shared on disk, so the sidebar
7992
+ reflects the same authenticated session the chat agent uses.
7993
+ """
7994
+ is_oauth = cfg.get("auth") == "oauth" or (cfg.get("url") and not cfg.get("command"))
7995
+ if not is_oauth:
7996
+ return None
7997
+ try:
7998
+ from hermes_cli.mcp_config import _oauth_tokens_present
7999
+ return bool(_oauth_tokens_present(name))
8000
+ except Exception:
8001
+ return None
8002
+
8003
+
7986
8004
  def _mcp_server_summary(name: str, cfg: Dict[str, Any]) -> Dict[str, Any]:
7987
8005
  transport = "http" if cfg.get("url") else ("stdio" if cfg.get("command") else "unknown")
7988
8006
  return {
@@ -7994,6 +8012,7 @@ def _mcp_server_summary(name: str, cfg: Dict[str, Any]) -> Dict[str, Any]:
7994
8012
  "env": _redact_mcp_env(cfg.get("env") or {}),
7995
8013
  "auth": cfg.get("auth"),
7996
8014
  "enabled": cfg.get("enabled", True) is not False,
8015
+ "authenticated": _mcp_server_authenticated(name, cfg),
7997
8016
  # Tool selection: list of enabled tool names, or None = all.
7998
8017
  "tools": cfg.get("tools"),
7999
8018
  }
@@ -12048,11 +12067,25 @@ async def set_dashboard_theme(body: ThemeSetBody):
12048
12067
  # in the vetted catalog (the font's webfont URL is injected as a <link>,
12049
12068
  # so we never accept an arbitrary user-supplied id/URL here).
12050
12069
  def _clawpump_mcp():
12051
- """Return (server_name, config) for the configured ClawPump MCP, else (None, None)."""
12070
+ """Return (server_name, config) for the configured ClawPump MCP, else (None, None).
12071
+
12072
+ The chat agent loads EVERY configured MCP server, so the ClawPump tools work
12073
+ in chat under whatever entry name the user installed — ``clawpump`` (remote
12074
+ OAuth), ``clawpump-stdio``, or ``clawpump-agents`` (``npx @clawpump/agents``).
12075
+ The dashboard must recognise the SAME entry, otherwise chat works while every
12076
+ sidebar MCP page (wallet / x402 / pod / mail) reports "not configured". Try
12077
+ the known names in preference order, then fall back to any ``clawpump*``
12078
+ server so a future/renamed entry still resolves.
12079
+ """
12052
12080
  from hermes_cli.mcp_config import _get_mcp_servers
12053
12081
 
12054
12082
  servers = _get_mcp_servers()
12055
- name = next((n for n in ("clawpump", "clawpump-stdio") if n in servers), None)
12083
+ name = next(
12084
+ (n for n in ("clawpump", "clawpump-stdio", "clawpump-agents") if n in servers),
12085
+ None,
12086
+ )
12087
+ if name is None:
12088
+ name = next((n for n in servers if n.startswith("clawpump")), None)
12056
12089
  return (name, servers[name]) if name else (None, None)
12057
12090
 
12058
12091
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawpump/claw-agent",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },