Skip to content

Commit c78a6f1

Browse files
authored
Merge pull request #10 from damassi/v2
feat: Agent Chat CLI v2
2 parents 56a5139 + bcef9f5 commit c78a6f1

Some content is hidden

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

47 files changed

+1950
-608
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules/
2+
dist/
3+
*.env
4+
*.env.example
5+
.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: 137 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/f9a82631-ee26-4a7b-9d89-a732d2605513
1620

1721
### Why?
1822

@@ -49,6 +53,18 @@ 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+
- **Chrome DevTools MCP**: https://developer.chrome.com/blog/chrome-devtools-mcp
61+
- **Github MCP**: https://github.com/github/github-mcp-server
62+
- [Generate a Github PAT token](https://github.com/settings/personal-access-tokens)
63+
- **Notion MCP**: https://developers.notion.com/docs/mcp
64+
- Authenticate via OAuth, which will launch a browser when attempting to connect
65+
66+
**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.
67+
5268
### Usage
5369

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

8298
```bash
83-
bun server
84-
bun server:http
99+
bun server:http # streaming HTTP (use this for deployments)
100+
bun server # stdio
85101
```
86102

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.
103+
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.
88104

89105
### Configuration
90106

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

97113
```ts
98114
const config = {
99-
systemPrompt: "You are a helpful agent."
115+
systemPrompt: getPrompt("system.md"),
100116
mcpServers: {
101117
someMcpServer: {
102-
command: "npx",
118+
command: "bunx",
103119
args: ["..."],
104120
prompt: getPrompt("someMcpServer.md"),
105121
},
106122
},
107123
}
108124
```
109125

126+
#### Remote Prompts
127+
128+
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.
129+
130+
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.
131+
132+
```ts
133+
import { getRemotePrompt } from "./src/utils/getRemotePrompt"
134+
135+
const config = {
136+
systemPrompt: getRemotePrompt(),
137+
mcpServers: {
138+
someMcpServer: {
139+
command: "bunx",
140+
args: ["..."],
141+
prompt: getRemotePrompt({
142+
fetchPrompt: async () => {
143+
const response = await fetch("https://some-prompt/name")
144+
145+
if (!response.ok) {
146+
throw new Error(
147+
`[agent] [getRemotePrompt] [ERROR HTTP] status: ${response.status}`
148+
)
149+
}
150+
151+
const text = await response.text()
152+
return text
153+
},
154+
}),
155+
},
156+
},
157+
}
158+
```
159+
160+
You can also provide a fallback to a local file if the remote fetch fails:
161+
162+
```ts
163+
const config = {
164+
mcpServers: {
165+
github: {
166+
prompt: getRemotePrompt({
167+
fallback: "github.md"
168+
fetchPrompt: ...
169+
}),
170+
},
171+
},
172+
}
173+
```
174+
110175
#### Denying Tools
111176

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

114187
```ts
115188
const config = {
116189
mcpServers: {
117190
github: {
118-
command: "npx",
191+
command: "bunx",
119192
args: ["..."],
120-
denyTools: ["delete_repository", "update_secrets"],
193+
disallowedTools: ["delete_repository", "update_secrets"],
121194
},
122195
},
123196
}
124197
```
125198

126199
Denied tools are filtered at the SDK level and won't be available to the agent.
200+
201+
In CLI mode, if `permissionMode` is set to "ask" then a prompt will appear to confirm when tools need to be invoked.
202+
203+
### Specialized Subagents
204+
205+
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.
206+
207+
#### Example
208+
209+
```ts
210+
import { createAgent } from "./src/utils/createAgent"
211+
import { getPrompt } from "./src/utils/getPrompt"
212+
213+
const config = {
214+
agents: {
215+
"sales-partner-sentiment-agent": createAgent({
216+
description:
217+
"An expert SalesForce partner sentiment agent, designed to produce insights for renewal and churn conversations",
218+
prompt: getPrompt("agents/sales-partner-sentiment-agent.md"),
219+
mcpServers: ["salesforce"],
220+
}),
221+
},
222+
mcpServers: {
223+
salesforce: {
224+
description: "Salesforce CRM: leads, opportunities, accounts...",
225+
command: "bunx",
226+
args: ["-y", "@tsmztech/[email protected]"],
227+
enabled: true,
228+
},
229+
},
230+
}
231+
```
232+
233+
When a user asks something like "Analyze partner churn", the routing agent will:
234+
235+
1. Match the query to the `sales-partner-sentiment-agent` based on its description
236+
2. Automatically connect to the required `salesforce` MCP server
237+
3. Invoke the subagent with its specialized prompt and tools
238+
239+
The `description` field is **critical**; it's used by the routing agent to determine when to invoke the subagent.
240+
241+
**Note:** Subagents also support remote prompts via `getRemotePrompt`, allowing you to manage agent prompts dynamically from an API or database.
242+
243+
### Note on Lazy MCP Server Initialization
244+
245+
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!
246+
247+
#### The Flow
248+
249+
- User sends a message, something like "In Salesforce, tell me about some recent leads"
250+
- Sub-agent forwards message onto Anthropic's light-weight Haiku model and asks which MCP servers seem to be necessary
251+
- Returns result as JSON, and based on the result, mcpServers are passed to the main agent query
252+
- 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: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,80 @@
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: {
19+
chrome: {
20+
description:
21+
"The Chrome DevTools MCP server adds web browser automation and debugging capabilities to your AI agent",
22+
command: "bunx",
23+
args: ["chrome-devtools-mcp@latest"],
24+
},
725
github: {
26+
description:
27+
"GitHub MCP tools to search code, PRs, issues; discover documentation in repo docs/; find deployment guides and code examples.",
828
prompt: getPrompt("github.md"),
9-
command: "npx",
29+
command: "bunx",
1030
args: [
1131
1232
"https://api.githubcopilot.com/mcp/readonly",
1333
"--header",
1434
`Authorization: Bearer ${process.env.GITHUB_ACCESS_TOKEN}`,
1535
],
16-
env: {
17-
GITHUB_ACCESS_TOKEN: process.env.GITHUB_ACCESS_TOKEN!,
18-
},
19-
denyTools: [],
36+
disallowedTools: [],
37+
enabled: true,
2038
},
39+
2140
notion: {
41+
description:
42+
"Notion workspace for documentation, wikis, OKRs, department pages, onboarding guides. Navigate hierarchies, search pages, retrieve structured content.",
2243
prompt: getPrompt("notion.md"),
23-
command: "npx",
44+
command: "bunx",
2445
args: ["[email protected]", "https://mcp.notion.com/mcp"],
46+
enabled: true,
47+
},
48+
49+
/**
50+
// Example of how to use getRemotePrompt
51+
52+
someOtherServer: {
53+
description: "Some description",
54+
command: "bunx",
55+
args: ["[email protected]", "https://mcp.some-server.com/mcp"],
56+
prompt: getRemotePrompt({
57+
fetchPrompt: async () => {
58+
const response = await fetch("https://some-prompt/name")
59+
60+
if (!response.ok) {
61+
throw new Error(
62+
`[agent] [getRemotePrompt] [ERROR HTTP] status: ${response.status}`
63+
)
64+
}
65+
66+
const text = await response.text()
67+
return text
68+
},
69+
}),
2570
},
71+
*/
2672
},
73+
74+
disallowedTools: ["Bash"],
75+
76+
// Ungate MCP tools, which have their own disallowedTools array.
2777
permissionMode: "bypassPermissions",
28-
stream: false,
2978
}
3079

3180
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)