Skip to content

Commit b56258a

Browse files
committed
Add smol Agent
1 parent 01d2d54 commit b56258a

File tree

6 files changed

+134
-39
lines changed

6 files changed

+134
-39
lines changed

packages/mcp-client/cli.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as readline from "node:readline/promises";
2+
import { stdin, stdout } from "node:process";
3+
import { join } from "node:path";
4+
import { homedir } from "node:os";
5+
import { Agent } from "./src";
6+
import type { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
7+
8+
async function* boboGenerator(message: string) {
9+
for (let i = 0; i < 6; i++) {
10+
yield "bobo ";
11+
await new Promise((resolve) => setTimeout(resolve, 400));
12+
}
13+
}
14+
15+
const ANSI = {
16+
BLUE: "\x1b[34m",
17+
RED: "\x1b[31m",
18+
RESET: "\x1b[0m",
19+
};
20+
21+
const SERVERS: StdioServerParameters[] = [
22+
{
23+
// Filesystem "official" mcp-server with access to your Desktop
24+
command: "npx",
25+
args: ["-y", "@modelcontextprotocol/server-filesystem", join(homedir(), "Desktop")],
26+
},
27+
{
28+
// Early version of a HF-MCP server
29+
command: "node",
30+
args: ["--disable-warning=ExperimentalWarning", join(homedir(), "Desktop/hf-mcp/index.ts")],
31+
env: {
32+
HF_TOKEN: process.env.HF_TOKEN ?? "",
33+
},
34+
},
35+
];
36+
37+
async function main() {
38+
if (!process.env.HF_TOKEN) {
39+
console.error(`a valid HF_TOKEN must be present in the env`);
40+
process.exit(1);
41+
}
42+
43+
const agent = new Agent({
44+
provider: "together",
45+
model: "Qwen/Qwen2.5-72B-Instruct",
46+
apiKey: process.env.HF_TOKEN,
47+
servers: SERVERS,
48+
});
49+
50+
const rl = readline.createInterface({ input: stdin, output: stdout });
51+
rl.on("SIGINT", async () => {
52+
await agent.client.cleanup();
53+
stdout.write("\n");
54+
rl.close();
55+
});
56+
57+
await agent.loadTools();
58+
59+
stdout.write(ANSI.BLUE);
60+
stdout.write(`Agent loaded with ${agent.client.availableTools.length} tools:\n`);
61+
stdout.write(agent.client.availableTools.map((t) => `- ${t.function.name}`).join("\n"));
62+
stdout.write(ANSI.RESET);
63+
stdout.write("\n");
64+
65+
while (true) {
66+
const message = await rl.question("> ");
67+
for await (const bobo of boboGenerator(message)) {
68+
console.log(bobo);
69+
}
70+
}
71+
}
72+
73+
main();

packages/mcp-client/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"build": "tsup src/index.ts --format cjs,esm --clean && tsc --emitDeclarationOnly --declaration",
3131
"prepare": "pnpm run build",
3232
"test": "vitest run",
33-
"check": "tsc"
33+
"check": "tsc",
34+
"agent": "tsx cli.ts"
3435
},
3536
"files": [
3637
"src",

packages/mcp-client/src/Agent.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { InferenceProvider } from "@huggingface/inference";
2+
import { McpClient } from "./McpClient";
3+
import type { ChatCompletionInputMessage } from "@huggingface/tasks";
4+
import type { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio";
5+
6+
const DEFAULT_SYSTEM_PROMPT = `
7+
You are an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved, or if you need more info from the user to solve the problem.
8+
9+
If you are not sure about anything pertaining to the user’s request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.
10+
11+
You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.
12+
`.trim();
13+
14+
export class Agent {
15+
readonly client: McpClient;
16+
private readonly servers: StdioServerParameters[];
17+
protected messages: ChatCompletionInputMessage[];
18+
19+
constructor({
20+
provider,
21+
model,
22+
apiKey,
23+
servers,
24+
}: {
25+
provider: InferenceProvider;
26+
model: string;
27+
apiKey: string;
28+
servers: StdioServerParameters[];
29+
}) {
30+
this.client = new McpClient({ provider, model, apiKey });
31+
this.servers = servers;
32+
this.messages = [
33+
{
34+
role: "system",
35+
content: DEFAULT_SYSTEM_PROMPT,
36+
},
37+
];
38+
}
39+
40+
async loadTools(): Promise<void> {
41+
return this.client.addMcpServers(this.servers);
42+
}
43+
44+
async processUserMessage(query: string) {
45+
this.messages.push({
46+
role: "user",
47+
content: query,
48+
});
49+
}
50+
}

packages/mcp-client/src/McpClient.ts

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
22
import type { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
33
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4-
import { homedir } from "os";
5-
import { join } from "path";
64
import { InferenceClient } from "@huggingface/inference";
75
import type { InferenceProvider } from "@huggingface/inference";
86
import type {
@@ -11,6 +9,7 @@ import type {
119
ChatCompletionOutput,
1210
} from "@huggingface/tasks/src/tasks/chat-completion/inference";
1311
import { version as packageVersion } from "../package.json";
12+
import { debug } from "./utils";
1413

1514
type ToolName = string;
1615

@@ -19,7 +18,7 @@ export class McpClient {
1918
private provider: string;
2019
private model: string;
2120
private clients: Map<ToolName, Client> = new Map();
22-
private availableTools: ChatCompletionInputTool[] = [];
21+
public readonly availableTools: ChatCompletionInputTool[] = [];
2322

2423
constructor({ provider, model, apiKey }: { provider: InferenceProvider; model: string; apiKey: string }) {
2524
this.client = new InferenceClient(apiKey);
@@ -40,7 +39,7 @@ export class McpClient {
4039
await mcp.connect(transport);
4140

4241
const toolsResult = await mcp.listTools();
43-
console.log(
42+
debug(
4443
"Connected to server with tools:",
4544
toolsResult.tools.map(({ name }) => name)
4645
);
@@ -121,37 +120,3 @@ export class McpClient {
121120
await Promise.all([...clients].map((client) => client.close()));
122121
}
123122
}
124-
125-
async function main() {
126-
if (!process.env.HF_TOKEN) {
127-
console.error(`a valid HF_TOKEN must be passed`);
128-
process.exit(1);
129-
}
130-
131-
const client = new McpClient({
132-
provider: "together",
133-
model: "Qwen/Qwen2.5-72B-Instruct",
134-
apiKey: process.env.HF_TOKEN,
135-
});
136-
137-
try {
138-
await client.addMcpServer({
139-
command: "node",
140-
args: ["--disable-warning=ExperimentalWarning", join(homedir(), "Desktop/hf-mcp/index.ts")],
141-
env: {
142-
HF_TOKEN: process.env.HF_TOKEN,
143-
},
144-
});
145-
146-
const response = await client.processQuery(`
147-
find an app that generates 3D models from text,
148-
and also get the best paper about transformers
149-
`);
150-
151-
console.log("\n" + response.choices[0].message.content);
152-
} finally {
153-
await client.cleanup();
154-
}
155-
}
156-
157-
main();

packages/mcp-client/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./McpClient";
2+
export * from "./Agent";

packages/mcp-client/src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function debug(...args: unknown[]): void {
2+
if (process.env.DEBUG) {
3+
console.log(args);
4+
}
5+
}

0 commit comments

Comments
 (0)