Skip to content

PoC: InferenceClient is also a McpClient #1351

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 37 commits into from
Apr 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4abd81f
Add dependency (hard dep for now)
julien-c Apr 11, 2025
9ffab69
First draft
julien-c Apr 11, 2025
44e4ff8
implement tool calling
julien-c Apr 11, 2025
07bd67c
scaffolding done by Gemini 2.5 Pro ✨
julien-c Apr 18, 2025
a50dd62
Update tsconfig.json
julien-c Apr 18, 2025
de8477b
move file around
julien-c Apr 18, 2025
47d6b3c
fixes
julien-c Apr 18, 2025
7c5f6c6
revert those changes
julien-c Apr 18, 2025
e1c4e65
Fix + improve types following #1367
julien-c Apr 18, 2025
63d8db0
Update .github/workflows/mcp-client-publish.yml
julien-c Apr 18, 2025
ad4d1a0
move types/node dep to root cc @coyotte508
julien-c Apr 18, 2025
0d8d6aa
Get version number from package.json
julien-c Apr 19, 2025
01d2d54
Better API + `addMcpServers`
julien-c Apr 19, 2025
b56258a
Add smol Agent
julien-c Apr 19, 2025
21649b2
better
julien-c Apr 19, 2025
d936c51
Add Smol agent implem
julien-c Apr 19, 2025
aa49d3d
improvements
julien-c Apr 19, 2025
daccdb4
improvements
julien-c Apr 19, 2025
46c7e62
very basic test
julien-c Apr 20, 2025
df2fb7d
Add playwright-mcp as default tool
julien-c Apr 20, 2025
8cea55e
one-shot a README using Gemini 2.5 Pro
julien-c Apr 20, 2025
f18d904
doc
julien-c Apr 20, 2025
e3d39ee
add tiny comment
julien-c Apr 24, 2025
a3af347
Switch to streaming mode
julien-c Apr 24, 2025
07fb0e3
Better default provider for this model + better formatting
julien-c Apr 24, 2025
a7ef69b
easier debugging
julien-c Apr 24, 2025
a710db0
[cli] better formatting
julien-c Apr 24, 2025
8f4858a
Fix for some providers
julien-c Apr 24, 2025
ebea1fb
clearer var name
julien-c Apr 24, 2025
765a5b8
easier to debug
julien-c Apr 24, 2025
f057aca
better var names...
julien-c Apr 24, 2025
4bb2ff0
Simpler loop
julien-c Apr 24, 2025
ee831d8
handle an `AbortSignal`
julien-c Apr 24, 2025
fdd548b
[cli] better formatting
julien-c Apr 24, 2025
7124e26
add prettier ignore
SBrandeis Apr 25, 2025
b38c0ac
add eslintignore
SBrandeis Apr 25, 2025
9734a91
Merge branch 'main' into mcp-client
SBrandeis Apr 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/mcp-client-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: MCP Client - Version and Release

on:
workflow_dispatch:
inputs:
newversion:
type: choice
description: "Semantic Version Bump Type"
default: patch
options:
- patch
- minor
- major

concurrency:
group: "push-to-main"

defaults:
run:
working-directory: packages/mcp-client

jobs:
version_and_release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.BOT_ACCESS_TOKEN }}
- run: npm install -g corepack@latest && corepack enable
- uses: actions/setup-node@v3
with:
node-version: "20"
cache: "pnpm"
cache-dependency-path: |
packages/mcp-client/pnpm-lock.yaml
packages/doc-internal/pnpm-lock.yaml
registry-url: "https://registry.npmjs.org"
- run: pnpm install
- run: git config --global user.name machineuser
- run: git config --global user.email [email protected]
- run: |
PACKAGE_VERSION=$(node -p "require('./package.json').version")
BUMPED_VERSION=$(node -p "require('semver').inc('$PACKAGE_VERSION', '${{ github.event.inputs.newversion }}')")
# Update package.json with the new version
node -e "const fs = require('fs'); const package = JSON.parse(fs.readFileSync('./package.json')); package.version = '$BUMPED_VERSION'; fs.writeFileSync('./package.json', JSON.stringify(package, null, '\t') + '\n');"
pnpm --filter doc-internal run fix-cdn-versions
git add ../..
git commit -m "🔖 @huggingface/mcp-client $BUMPED_VERSION"
git tag "mcp-client-v$BUMPED_VERSION"

# Add checks for dependencies if needed, similar to hub-publish.yml
- name: "Check Deps are published before publishing this package"
run: pnpm -w check-deps inference && pnpm -w check-deps tasks

- run: pnpm publish --no-git-checks .
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: git pull --rebase && git push --follow-tags
# hack - reuse actions/setup-node@v3 just to set a new registry
- uses: actions/setup-node@v3
with:
node-version: "20"
registry-url: "https://npm.pkg.github.com"
# Disable for now, until github supports PATs for writing github packages (https://github.com/github/roadmap/issues/558)
# - run: pnpm publish --no-git-checks .
# env:
# NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Update Doc"
uses: peter-evans/repository-dispatch@v2
with:
event-type: doc-build
token: ${{ secrets.BOT_ACCESS_TOKEN }}
5 changes: 5 additions & 0 deletions docs/_toctree.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
title: Interact with the Hub
- local: hub/modules
title: API Reference
- title: "@huggingface/mcp-client"
isExpanded: true
sections:
- local: mcp-client/README
title: Simple MCP Client and smol Agent built on top of Inference Client
- title: "@huggingface/agent"
isExpanded: true
sections:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"check-deps": "tsx scripts/check-deps.ts"
},
"devDependencies": {
"@types/node": "^22.14.1",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@types/node": "^18.16.1",
"@vitest/browser": "^0.34.6",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.0.0",
Expand Down
3 changes: 2 additions & 1 deletion packages/doc-internal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Package to generate doc for other @huggingface packages",
"private": true,
"scripts": {
"start": "pnpm run fix-cdn-versions && pnpm run doc-hub && pnpm run doc-inference && pnpm run doc-agents && pnpm run doc-space-header && pnpm run doc-gguf && cp ../../README.md ../../docs/index.md && pnpm run update-toc && pnpm run fix-md-links && pnpm run fix-md-headinghashlinks",
"start": "pnpm run fix-cdn-versions && pnpm run doc-hub && pnpm run doc-inference && pnpm run doc-agents && pnpm run doc-space-header && pnpm run doc-gguf && pnpm run doc-mcp-client && cp ../../README.md ../../docs/index.md && pnpm run update-toc && pnpm run fix-md-links && pnpm run fix-md-headinghashlinks",
"lint": "eslint --quiet --fix --ext .cjs,.ts .",
"lint:check": "eslint --ext .cjs,.ts .",
"format": "prettier --write .",
Expand All @@ -14,6 +14,7 @@
"doc-inference": "typedoc --tsconfig ../inference/tsconfig.json --githubPages false --plugin typedoc-plugin-markdown --out ../../docs/inference --hideBreadcrumbs --hideInPageTOC --sourceLinkTemplate https://github.com/huggingface/huggingface.js/blob/main/{path}#L{line} ../inference/src/index.ts",
"doc-agents": "typedoc --tsconfig ../agents/tsconfig.json --githubPages false --plugin typedoc-plugin-markdown --out ../../docs/agents --hideBreadcrumbs --hideInPageTOC --sourceLinkTemplate https://github.com/huggingface/huggingface.js/blob/main/{path}#L{line} ../agents/src/index.ts",
"doc-gguf": "mkdir -p ../../docs/gguf && cp ../../packages/gguf/README.md ../../docs/gguf/README.md",
"doc-mcp-client": "mkdir -p ../../docs/mcp-client && cp ../../packages/mcp-client/README.md ../../docs/mcp-client/README.md",
"doc-space-header": "mkdir -p ../../docs/space-header && cp ../../packages/space-header/README.md ../../docs/space-header/README.md",
"update-toc": "tsx update-toc.ts",
"fix-cdn-versions": "tsx fix-cdn-versions.ts",
Expand Down
1 change: 1 addition & 0 deletions packages/mcp-client/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
4 changes: 4 additions & 0 deletions packages/mcp-client/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pnpm-lock.yaml
# In order to avoid code samples to have tabs, they don't display well on npm
README.md
dist
72 changes: 72 additions & 0 deletions packages/mcp-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# @huggingface/mcp-client

Client for the Model Context Protocol (MCP).

This package provides a client implementation for interacting with MCP servers, built on top of our InferenceClient, `@huggingface/inference`.

It includes an example CLI smol Agent that can leverage MCP tools.

## Installation

This package is part of the Hugging Face JS monorepo. To install dependencies for all packages, run from the root of the repository:

```bash
pnpm install
```

## Usage (CLI Agent)

The package includes a command-line interface (CLI) agent that demonstrates how to use the MCP client.

### Prerequisites

* **Hugging Face API Token:** You need a Hugging Face API token with appropriate permissions. Set it as an environment variable:
```bash
export HF_TOKEN="hf_..."
```

### Running the Agent

Navigate to the package directory and run the agent script:

```bash
cd packages/mcp-client
pnpm agent
```

Alternatively, run from the root of the monorepo:

```bash
pnpm --filter @huggingface/mcp-client agent
```

The agent will load available MCP tools (by default, connecting to a filesystem server for your Desktop and a Playwright server) and prompt you for input (`>`).

### Configuration (Environment Variables)

* `HF_TOKEN` (Required): Your Hugging Face API token.
* `MODEL_ID` (Optional): The model ID to use for the agent's inference. Defaults to `Qwen/Qwen2.5-72B-Instruct`.
* `PROVIDER` (Optional): The inference provider. Defaults to `together`. See `@huggingface/inference` for available providers.
* `EXPERIMENTAL_HF_MCP_SERVER` (Optional): Set to `true` to enable connection to an experimental Hugging Face MCP server (requires separate setup).

Example with custom model:

```bash
export HF_TOKEN="hf_..."
export MODEL_ID="mistralai/Mixtral-8x7B-Instruct-v0.1"
pnpm agent
```

## Development

Common development tasks can be run using pnpm scripts:

* `pnpm build`: Build the package.
* `pnpm lint`: Lint and fix code style.
* `pnpm format`: Format code using Prettier.
* `pnpm test`: Run tests using Vitest.
* `pnpm check`: Type-check the code using TypeScript.

## License

MIT
128 changes: 128 additions & 0 deletions packages/mcp-client/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as readline from "node:readline/promises";
import { stdin, stdout } from "node:process";
import { join } from "node:path";
import { homedir } from "node:os";
import { Agent } from "./src";
import type { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
import { ANSI } from "./src/utils";
import type { InferenceProvider } from "@huggingface/inference";

const MODEL_ID = process.env.MODEL_ID ?? "Qwen/Qwen2.5-72B-Instruct";
const PROVIDER = (process.env.PROVIDER as InferenceProvider) ?? "nebius";

const SERVERS: StdioServerParameters[] = [
{
// Filesystem "official" mcp-server with access to your Desktop
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", join(homedir(), "Desktop")],
},
{
// Playwright MCP
command: "npx",
args: ["@playwright/mcp@latest"],
},
];

if (process.env.EXPERIMENTAL_HF_MCP_SERVER) {
SERVERS.push({
// Early version of a HF-MCP server
// you can download it from gist.github.com/julien-c/0500ba922e1b38f2dc30447fb81f7dc6
command: "node",
args: ["--disable-warning=ExperimentalWarning", join(homedir(), "Desktop/hf-mcp/index.ts")],
env: {
HF_TOKEN: process.env.HF_TOKEN ?? "",
},
});
}

async function main() {
if (!process.env.HF_TOKEN) {
console.error(`a valid HF_TOKEN must be present in the env`);
process.exit(1);
}

const agent = new Agent({
provider: PROVIDER,
model: MODEL_ID,
apiKey: process.env.HF_TOKEN,
servers: SERVERS,
});

const rl = readline.createInterface({ input: stdin, output: stdout });
let abortController = new AbortController();
let waitingForInput = false;
async function waitForInput() {
waitingForInput = true;
const input = await rl.question("> ");
waitingForInput = false;
return input;
}
rl.on("SIGINT", async () => {
if (waitingForInput) {
// close the whole process
await agent.cleanup();
stdout.write("\n");
rl.close();
} else {
// otherwise, it means a request is underway
abortController.abort();
abortController = new AbortController();
stdout.write("\n");
stdout.write(ANSI.GRAY);
stdout.write("Ctrl+C a second time to exit");
stdout.write(ANSI.RESET);
stdout.write("\n");
}
});
process.on("uncaughtException", (err) => {
stdout.write("\n");
rl.close();
throw err;
});

await agent.loadTools();

stdout.write(ANSI.BLUE);
stdout.write(`Agent loaded with ${agent.availableTools.length} tools:\n`);
stdout.write(agent.availableTools.map((t) => `- ${t.function.name}`).join("\n"));
stdout.write(ANSI.RESET);
stdout.write("\n");

while (true) {
const input = await waitForInput();
for await (const chunk of agent.run(input, { abortSignal: abortController.signal })) {
if ("choices" in chunk) {
const delta = chunk.choices[0]?.delta;
if (delta.content) {
stdout.write(delta.content);
}
if (delta.tool_calls) {
stdout.write(ANSI.GRAY);
for (const deltaToolCall of delta.tool_calls) {
if (deltaToolCall.id) {
stdout.write(`<Tool ${deltaToolCall.id}>\n`);
}
if (deltaToolCall.function.name) {
stdout.write(deltaToolCall.function.name + " ");
}
if (deltaToolCall.function.arguments) {
stdout.write(deltaToolCall.function.arguments);
}
}
stdout.write(ANSI.RESET);
}
} else {
/// Tool call info
stdout.write("\n\n");
stdout.write(ANSI.GREEN);
stdout.write(`Tool[${chunk.name}] ${chunk.tool_call_id}\n`);
stdout.write(chunk.content);
stdout.write(ANSI.RESET);
stdout.write("\n\n");
}
}
stdout.write("\n");
}
}

main();
57 changes: 57 additions & 0 deletions packages/mcp-client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@huggingface/mcp-client",
"packageManager": "[email protected]",
"version": "0.0.1",
"description": "Client for the Model Context Protocol",
"repository": "https://github.com/huggingface/huggingface.js.git",
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.mjs"
}
},
"engines": {
"node": ">=18"
},
"source": "index.ts",
"scripts": {
"lint": "eslint --quiet --fix --ext .cjs,.ts .",
"lint:check": "eslint --ext .cjs,.ts .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepublishOnly": "pnpm run build",
"build": "tsup src/index.ts --format cjs,esm --clean && tsc --emitDeclarationOnly --declaration",
"prepare": "pnpm run build",
"test": "vitest run",
"check": "tsc",
"agent": "tsx cli.ts"
},
"files": [
"src",
"dist",
"index.ts",
"tsconfig.json"
],
"keywords": [
"huggingface",
"model context protocol",
"mcp",
"client",
"hugging",
"face"
],
"author": "Hugging Face",
"license": "MIT",
"dependencies": {
"@huggingface/inference": "workspace:^",
"@huggingface/tasks": "workspace:^",
"@modelcontextprotocol/sdk": "^1.9.0"
}
}
Loading