Skip to content

Commit 5316abf

Browse files
committed
feat(v2): add v2 updates
1 parent 56a5139 commit 5316abf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1845
-598
lines changed

.env.example

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
# Anthropic API Key
1+
# Required
22
ANTHROPIC_API_KEY=
33

4-
# GitHub Access Token
4+
ENABLE_LOGGING=false
5+
6+
# Generate at https://github.com/settings/personal-access-tokens
57
GITHUB_ACCESS_TOKEN=
8+

.prettierignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
hokusai/
2+
node_modules/
3+
dist/
4+
*.env
5+
*.env.example
6+
.prettierignore

CLAUDE.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
---
2-
31
Default to using Bun instead of Node.js.
42

53
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
@@ -17,7 +15,12 @@ Default to using Bun instead of Node.js.
1715
- Prefer named exports vs default exports
1816
- Prefer try/catch over vanilla promises
1917
- If a function has two or more arguments, use an object. And use an interface, vs adding types inline.
20-
- Never use implicit returns in react components. They should ONLY be used in one-liners.-
18+
- Never use implicit returns in react components. They should ONLY be used in one-liners.
19+
20+
## Common Developer Commands
21+
22+
- `bun test`
23+
- `bun type-check`
2124

2225
## APIs
2326

README.md

Lines changed: 136 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
# Agent Chat CLI
1+
# Agent Chat Cli
22

3-
A bare-bones, terminal-based chat CLI built to explore the new [Claude Agent SDK](https://docs.claude.com/en/api/agent-sdk/overview). Terminal rendering is built on top of [React Ink](https://github.com/vadimdemedes/ink).
3+
A minimalist, terminal-based chat CLI built to explore the new [Claude Agent SDK](https://docs.claude.com/en/api/agent-sdk/overview) and based on [damassi/agent-chat-cli](https://github.com/damassi/agent-chat-cli). Terminal rendering is built on top of [React Ink](https://github.com/vadimdemedes/ink).
4+
5+
Additionally, via inference, Agent Chat CLI supports lazy, turn-based MCP connections to keep token costs down. The agent will only use those MCP servers you ask about, limiting the context that is sent up to the LLM. (After an MCP server is connected it remains connected, however.)
6+
7+
## Overview
48

59
The app has three modes:
610

@@ -12,7 +16,7 @@ The agent, including MCP server setup, is configured in [agent-chat-cli.config.t
1216

1317
The MCP _client_ is configured in [mcp-client.config.ts](mcp-client.config.ts).
1418

15-
https://github.com/user-attachments/assets/c2026c47-c798-4a1d-a68a-54e4abe73c63
19+
https://github.com/user-attachments/assets/20d77095-2ced-4bc2-b416-8545ace53a4f
1620

1721
### Why?
1822

@@ -49,6 +53,17 @@ OAuth support works out of the box via `mcp-remote`:
4953

5054
See the config above for an example.
5155

56+
### Example MCP Servers
57+
58+
For demonstration purposes, Agent is configured with the following MCP servers:
59+
60+
- **Github**: https://github.com/github/github-mcp-server
61+
- [Generate a Github PAT token](https://github.com/settings/personal-access-tokens)
62+
- **Notion**: https://developers.notion.com/docs/mcp
63+
- Authenticate via OAuth, which will launch a browser when attempting to connect
64+
65+
**Note**: OAuth-based MCP servers (Notion, JIRA, etc) require browser-based authentication and cannot be deployed remotely. These servers are only accessible in the CLI version of the agent.
66+
5267
### Usage
5368

5469
#### Interactive Agent Mode
@@ -80,11 +95,11 @@ Configure the MCP server connection in `mcp-client.config.ts`. HTTP is also supp
8095
Run as a stand-alone MCP server, using one of two modes:
8196

8297
```bash
83-
bun server
84-
bun server:http
98+
bun server:http # streaming HTTP (use this for deployments)
99+
bun server # stdio
85100
```
86101

87-
The server exposes an `ask_agent` tool that other MCP clients can use to interact with the agent. The agent has access to all configured MCP servers and can use their tools.
102+
The MCP server exposes an `ask_agent` and `ask_agent_slackbot` tools that other MCP clients can use to interact with the agent. The agent has access to all configured MCP servers and can use their tools.
88103

89104
### Configuration
90105

@@ -96,31 +111,141 @@ To add specific instructions for each MCP server, create a markdown file in `src
96111

97112
```ts
98113
const config = {
99-
systemPrompt: "You are a helpful agent."
114+
systemPrompt: getPrompt("system.md"),
100115
mcpServers: {
101116
someMcpServer: {
102-
command: "npx",
117+
command: "bunx",
103118
args: ["..."],
104119
prompt: getPrompt("someMcpServer.md"),
105120
},
106121
},
107122
}
108123
```
109124

125+
#### Remote Prompts
126+
127+
Prompts can be loaded from remote sources (e.g., APIs) using `getRemotePrompt`. This enables dynamic prompt management where prompts are stored in a database or CMS rather than in files.
128+
129+
Both `getPrompt` (for local files) and `getRemotePrompt` (for API calls) return lazy functions that are only evaluated when the agent needs them, ensuring prompts are fetched on-demand during each LLM turn, enabling iteration in real time.
130+
131+
```ts
132+
import { getRemotePrompt } from "./src/utils/getRemotePrompt"
133+
134+
const config = {
135+
systemPrompt: getRemotePrompt(),
136+
mcpServers: {
137+
someMcpServer: {
138+
command: "bunx",
139+
args: ["..."],
140+
prompt: getRemotePrompt({
141+
fetchPrompt: async () => {
142+
const response = await fetch("https://some-prompt/name")
143+
144+
if (!response.ok) {
145+
throw new Error(
146+
`[agent] [getRemotePrompt] [ERROR HTTP] status: ${response.status}`
147+
)
148+
}
149+
150+
const text = await response.text()
151+
return text
152+
},
153+
}),
154+
},
155+
},
156+
}
157+
```
158+
159+
You can also provide a fallback to a local file if the remote fetch fails:
160+
161+
```ts
162+
const config = {
163+
mcpServers: {
164+
github: {
165+
prompt: getRemotePrompt({
166+
fallback: "github.md"
167+
fetchPrompt: ...
168+
}),
169+
},
170+
},
171+
}
172+
```
173+
110174
#### Denying Tools
111175

112-
You can prevent specific MCP tools from being used by adding a `denyTools` array to your server configuration:
176+
You can limit what tools the claude-agent-sdk has access to by adding a `disallowedTools` config:
177+
178+
```ts
179+
const config = {
180+
disallowedTools: ["Bash"],
181+
}
182+
```
183+
184+
You can also prevent specific MCP tools from being used by adding a `disallowedTools` array to your server configuration:
113185

114186
```ts
115187
const config = {
116188
mcpServers: {
117189
github: {
118-
command: "npx",
190+
command: "bunx",
119191
args: ["..."],
120-
denyTools: ["delete_repository", "update_secrets"],
192+
disallowedTools: ["delete_repository", "update_secrets"],
121193
},
122194
},
123195
}
124196
```
125197

126198
Denied tools are filtered at the SDK level and won't be available to the agent.
199+
200+
In CLI mode, if `permissionMode` is set to "ask" then a prompt will appear to confirm when tools need to be invoked.
201+
202+
### Specialized Subagents
203+
204+
You can define specialized subagents in `agent-chat-cli.config.ts` to handle domain-specific tasks, leveraging the powerful [Claude Subagent SDK](https://docs.claude.com/en/docs/claude-code/sub-agents). Subagents are automatically invoked when user queries match their domain, and they have access to specific MCP servers.
205+
206+
#### Example
207+
208+
```ts
209+
import { createAgent } from "./src/utils/createAgent"
210+
import { getPrompt } from "./src/utils/getPrompt"
211+
212+
const config = {
213+
agents: {
214+
"sales-partner-sentiment-agent": createAgent({
215+
description:
216+
"An expert SalesForce partner sentiment agent, designed to produce insights for renewal and churn conversations",
217+
prompt: getPrompt("agents/sales-partner-sentiment-agent.md"),
218+
mcpServers: ["salesforce"],
219+
}),
220+
},
221+
mcpServers: {
222+
salesforce: {
223+
description: "Salesforce CRM: leads, opportunities, accounts...",
224+
command: "bunx",
225+
args: ["-y", "@tsmztech/[email protected]"],
226+
enabled: true,
227+
},
228+
},
229+
}
230+
```
231+
232+
When a user asks something like "Analyze partner churn", the routing agent will:
233+
234+
1. Match the query to the `sales-partner-sentiment-agent` based on its description
235+
2. Automatically connect to the required `salesforce` MCP server
236+
3. Invoke the subagent with its specialized prompt and tools
237+
238+
The `description` field is **critical**; it's used by the routing agent to determine when to invoke the subagent.
239+
240+
**Note:** Subagents also support remote prompts via `getRemotePrompt`, allowing you to manage agent prompts dynamically from an API or database.
241+
242+
### Note on Lazy MCP Server Initialization
243+
244+
In order to keep LLM costs low and response times quick, a specialized sub-agent sits in front of user queries to infer which MCP servers are needed; the result is then forwarded on to the main agent, lazily initializing required MCP servers. Without this, we would need to initialize _all_ MCP servers defined in the config upfront, and for every query that we send to Anthropic, we'd _also_ be sending along a huge system prompt, and this is very expensive!
245+
246+
#### The Flow
247+
248+
- User sends a message, something like "In Salesforce, tell me about some recent leads"
249+
- Sub-agent forwards message onto Anthropic's light-weight Haiku model and asks which MCP servers seem to be necessary
250+
- Returns result as JSON, and based on the result, mcpServers are passed to the main agent query
251+
- Agent now boots quickly and responds in a timely way, vs having to wait for every MCP server to initialize before being able to chat

agent-chat-cli.config.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,74 @@
11
import type { AgentChatConfig } from "./src/store"
2+
import { createAgent } from "./src/utils/createAgent"
23
import { getPrompt } from "./src/utils/getPrompt"
34

45
const config: AgentChatConfig = {
56
systemPrompt: getPrompt("system.md"),
7+
model: "sonnet",
8+
stream: true,
9+
10+
agents: {
11+
"demo-agent": createAgent({
12+
description: "A claude subagent designed to show off functionality",
13+
prompt: getPrompt("agents/demo-agent.md"),
14+
mcpServers: [],
15+
}),
16+
},
17+
618
mcpServers: {
719
github: {
20+
description:
21+
"Artsy GitHub organization: search code, PRs, issues; discover documentation in repo docs/; find deployment guides and code examples.",
822
prompt: getPrompt("github.md"),
9-
command: "npx",
23+
command: "bunx",
1024
args: [
1125
1226
"https://api.githubcopilot.com/mcp/readonly",
1327
"--header",
1428
`Authorization: Bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
1529
],
16-
env: {
17-
GITHUB_ACCESS_TOKEN: process.env.GITHUB_ACCESS_TOKEN!,
18-
},
19-
denyTools: [],
30+
disallowedTools: [],
31+
enabled: true,
2032
},
33+
2134
notion: {
35+
description:
36+
"Notion workspace for documentation, wikis, OKRs, department pages, onboarding guides. Navigate hierarchies, search pages, retrieve structured content.",
2237
prompt: getPrompt("notion.md"),
23-
command: "npx",
38+
command: "bunx",
2439
args: ["[email protected]", "https://mcp.notion.com/mcp"],
40+
enabled: true,
2541
},
42+
43+
/**
44+
// Example of how to use getRemotePrompt
45+
46+
someOtherServer: {
47+
description: "Some description",
48+
command: "bunx",
49+
args: ["[email protected]", "https://mcp.some-server.com/mcp"],
50+
prompt: getRemotePrompt({
51+
fetchPrompt: async () => {
52+
const response = await fetch("https://some-prompt/name")
53+
54+
if (!response.ok) {
55+
throw new Error(
56+
`[agent] [getRemotePrompt] [ERROR HTTP] status: ${response.status}`
57+
)
58+
}
59+
60+
const text = await response.text()
61+
return text
62+
},
63+
}),
64+
},
65+
*/
2666
},
67+
68+
disallowedTools: ["Bash"],
69+
70+
// Ungate MCP tools, which have their own disallowedTools array.
2771
permissionMode: "bypassPermissions",
28-
stream: false,
2972
}
3073

3174
export default config

bun.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"lockfileVersion": 1,
33
"workspaces": {
44
"": {
5-
"name": "artsy-agent-claude",
5+
"name": "agent-claude",
66
"dependencies": {
7-
"@anthropic-ai/claude-agent-sdk": "^0.1.1",
7+
"@anthropic-ai/claude-agent-sdk": "^0.1.30",
88
"@modelcontextprotocol/sdk": "^1.18.2",
99
"cors": "^2.8.5",
1010
"cosmiconfig": "^9.0.0",
@@ -35,7 +35,7 @@
3535
"packages": {
3636
"@alcalzone/ansi-tokenize": ["@alcalzone/[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-qI/5TaaaCZE4yeSZ83lu0+xi1r88JSxUjnH4OP/iZF7+KKZ75u3ee5isd0LxX+6N8U0npL61YrpbthILHB6BnA=="],
3737

38-
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/[email protected].1", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" } }, "sha512-+12GQktMFc5Uqz6oVjJbj7Q+GD5QDorKEKtInALKD7VleJwLlFbMYIlm4586owIV5veFvb6bAVofKn9CnYWtvw=="],
38+
"@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/[email protected].30", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^3.24.1" } }, "sha512-lo1tqxCr2vygagFp6kUMHKSN6AAWlULCskwGKtLB/JcIXy/8H8GsLSKX54anTsvc9mBbCR8wWASdFmiiL9NSKA=="],
3939

4040
"@babel/code-frame": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
4141

0 commit comments

Comments
 (0)