branch: elpa/gptel commit 18a5eb413cc09f8c449462e4a39656954437ccfc Author: Karthik Chikmagalur <karthikchikmaga...@gmail.com> Commit: Karthik Chikmagalur <karthikchikmaga...@gmail.com>
gptel-integrations: Add utilities for MCP * gptel-integrations.el: Add a new file with utilities for integrating with mcp.el, the Emacs client for MCP. This file contains helper commands for integrating with the mcp.el package: (gptel-mcp-connect): Register gptel-tools from MCP servers. This command guides the user through the mcp.el setup process (somewhat) when called interactively. (gptel-mcp-disconnect): Unregister tools from MCP servers Neither of these commands is autoloaded, and to use this library the user must explicitly run (require 'gptel-integrations). This is to avoid cluttering the experience and Emacs session of users who have no interest in using tools/MCP. Once this feature is loaded, these commands will also be available from gptel's tools menu, via `M-x gptel-tools' or `gptel-menu'. (gptel--suffix-mcp-connect, gptel--suffix-mcp-disconnect): Transient menu versions of the above commands. In the future support for the llm-tools-collection package is planned. * README: Document gptel-integrations. --- README.org | 31 +++++++++- gptel-integrations.el | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 2 deletions(-) diff --git a/README.org b/README.org index 1bebbbf406..cc91244500 100644 --- a/README.org +++ b/README.org @@ -111,7 +111,7 @@ gptel uses Curl if available, but falls back to the built-in url-retrieve to wor - [[#openrouter][OpenRouter]] - [[#privategpt][PrivateGPT]] - [[#deepseek][DeepSeek]] - - [[#sambanova-deepseek][Sambanova (DeepSeek)]] + - [[#sambanova-deepseek][Sambanova (Deepseek)]] - [[#cerebras][Cerebras]] - [[#github-models][Github Models]] - [[#novita-ai][Novita AI]] @@ -128,6 +128,7 @@ gptel uses Curl if available, but falls back to the built-in url-retrieve to wor - [[#tool-use][Tool use]] - [[#defining-gptel-tools][Defining gptel tools]] - [[#selecting-tools][Selecting tools]] + - [[#tools-from-mcp-servers][Tools from MCP servers]] - [[#rewrite-refactor-or-fill-in-a-region][Rewrite, refactor or fill in a region]] - [[#extra-org-mode-conveniences][Extra Org mode conveniences]] - [[#faq][FAQ]] @@ -1179,14 +1180,40 @@ With some prompting, you can get an LLM to write these tools for you. Tools can also be asynchronous, use optional arguments and arguments with more structure (enums, arrays, objects etc). See =gptel-make-tool= for details. #+html: </details> +#+html: <details><summary> **** Selecting tools - +#+html: </summary> Once defined, tools can be selected (globally, buffer-locally or for the next request only) from gptel's transient menu: #+html: <img src="https://github.com/user-attachments/assets/fd878596-b313-4385-b675-3d6546909d8b" align="center" alt="Image showing gptel's tool selection menu."> From here you can also require confirmation for all tool calls, and decide if tool call results should be included in the LLM response. See [[#additional-configuration][Additional Configuration]] for doing these things via elisp. +#+html: </details> +#+html: <details><summary> +**** Tools from MCP servers +#+html: </summary> + +The [[https://modelcontextprotocol.io/introduction][Model Context Protocol]] (MCP) is a protocol for providing resources and tools to LLMs, and [[https://github.com/appcypher/awesome-mcp-servers][many MCP servers exist]] that provide LLM tools for file access, database connections, API integrations etc. The [[mcp.el]] package for Emacs can act as an MCP client and manage these tool calls for gptel. + +To use MCP servers with gptel, you thus need three pieces: + +1. The [[https://github.com/lizqwerscott/mcp.el][mcp.el]] package for Emacs +2. MCP servers configured for and running via mcp.el. (MCP servers are typically run locally.) +3. gptel and access to an LLM + +While mcp.el [[https://github.com/lizqwerscott/mcp.el?tab=readme-ov-file#use-with-gptel][provides instructions]] for accessing MCP-provided tools from gptel, gptel includes =gptel-integrations=, a small library to make this more convenient. This library is not automatically loaded by gptel, so if you would like to use it you have to require it: + +#+begin_src emacs-lisp +(require 'gptel-integrations) +#+end_src + +Once loaded, you can run the =gptel-mcp-connect= and =gptel-mcp-disconnect= commands to register and unregister MCP-provided tools in gptel. These will also show up in the tools menu in gptel, accessed via =M-x gptel-menu= or =M-x gptel-tools=: + +#+html: <img src="https://github.com/user-attachments/assets/2cbbf8a0-49c7-49a5-ba24-514ad7e08799" align="center" alt="Image showing MCP tool registration commands in gptel's tool selection menu."> + +MCP-provided tools can be used as normal with gptel. + *** Rewrite, refactor or fill in a region In any buffer: with a region selected, you can modify text, rewrite prose or refactor code with =gptel-rewrite=. Example with prose: diff --git a/gptel-integrations.el b/gptel-integrations.el new file mode 100644 index 0000000000..6f10eb13aa --- /dev/null +++ b/gptel-integrations.el @@ -0,0 +1,155 @@ +;;; gptel-transient.el --- Integrations for gptel -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Karthik Chikmagalur + +;; Author: Karthik Chikmagalur <karthikchikmaga...@gmail.com> +;; Keywords: convenience + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Integrations with related packages for gptel. To use these, run +;; +;; (require 'gptel-integrations) +;; +;; For MCP integration: +;; - Run M-x `gptel-mcp-connect' and M-x `gptel-mcp-disconnect', OR +;; - Use gptel's tools menu, M-x `gptel-tools', OR +;; - Access tools from `gptel-menu' + +;;; Code: +(require 'gptel) +(require 'cl-lib) +(eval-when-compile (require 'transient)) + +;;;; MCP integration - requires the mcp package +(declare-function mcp-hub-get-all-tool "mcp-hub") +(declare-function mcp-hub-get-servers "mcp-hub") +(declare-function mcp-hub "mcp-hub") +(defvar mcp-hub-servers) + +(defun gptel-mcp-connect (&optional interactive) + "Get gptel tools from MCP servers using the mcp package. + +If INTERACTIVE is non-nil, guide the user through setting up mcp and +query for servers to retrieve tools from." + (interactive (list t)) + (if (locate-library "mcp-hub") + (unless (require 'mcp-hub nil t) + (user-error "Could not load `mcp-hub'! Please install\ + or configure the mcp package")) + (user-error "Could not find mcp! Please install or configure the mcp package")) + (if (null mcp-hub-servers) + (user-error "No MCP servers available! Please configure `mcp-hub-servers'") + (let* ((servers (mcp-hub-get-servers)) + (active (cl-remove-if-not (lambda (el) (eq (plist-get el :status) 'connected)) + servers))) + (if (null active) + (when (and interactive + (y-or-n-p "No MCP servers are running. Open the MCP hub?")) + (message (substitute-command-keys + "Start some MCP servers for gptel to connect to!\ + (\\`s' to start, \\`k' to kill, \\[mcp-hub] to get here)")) + (mcp-hub)) + ;; Check which servers to connect to + (letrec ((tools (mcp-hub-get-all-tool :asyncp t :categoryp t)) + (connect-all-fn + (lambda () (mapc #'(lambda (tool) (apply #'gptel-make-tool tool)) + tools) + (message "Added %d tools from %d MCP server%s" + (length tools) (length active) + (if (= (length active) 1) "" "s"))))) + (if (not interactive) + (funcall connect-all-fn) ; Connect to all of them + (when-let* ((names (completing-read-multiple ; Ask for confirmation + "Get tools from servers (separate with \",\"): " + (cons "ALL" (mapcar (lambda (el) (plist-get el :name)) active)) + nil t))) + (if (member "ALL" names) + (funcall connect-all-fn) + (let ((idx 0)) + (dolist (name names) + (mapc (lambda (tool) + (when (equal (plist-get tool :category) (format "mcp-%s" name)) + (apply #'gptel-make-tool tool) + (cl-incf idx))) + tools)) + (message "Added %d tools from MCP servers: %S" + idx names)))))))))) + +(defun gptel-mcp-disconnect (&optional interactive) + "Unregister gptel tools provided by MCP servers using the mcp package. + +If INTERACTIVE is non-nil, query the user about which tools to remove." + (interactive (list t)) + (if-let* ((names-alist + (cl-loop + for (category . _tools) in gptel--known-tools + if (string-match "^mcp-\\(.*\\)" category) + collect (cons (match-string 1 category) category)))) + (let ((remove-fn (lambda (cat-names) + (mapc (lambda (category) (setf (alist-get category gptel--known-tools + nil t #'equal) + nil)) + cat-names)))) + (if interactive + (when-let* ((categories + (completing-read-multiple + "Remove MCP server tools for (separate with \",\"): " + (cons '("ALL" . nil) names-alist) + nil t))) + (if (member "ALL" categories) + (setq categories (map-values names-alist)) + (setq categories (mapcar (lambda (n) (cdr (assoc n names-alist))) categories))) + (funcall remove-fn categories) + (message "Removed MCP tools for: %S" (map-keys names-alist))) + (funcall remove-fn (map-values names-alist)))) + (message "No MCP tools found!"))) + +(with-eval-after-load 'gptel-transient + ;; FIXME: If `gptel-mcp-connect' opens mcp-hub, the transient stays open. I + ;; don't know how to fix this. + (transient-define-suffix gptel--suffix-mcp-connect () + "Register tools provided by MCP servers." + :key "M+" + :description "Add tools from MCP servers" + :transient t + (interactive) + (condition-case err + (call-interactively #'gptel-mcp-connect) + (user-error (message "%s" (cadr err)))) + (transient-setup)) + + (transient-define-suffix gptel--suffix-mcp-disconnect () + "Remove tools provided by MCP servers from gptel." + :key "M-" + :description "Remove tools from MCP servers" + :transient t + :inapt-if (lambda () (or (not (boundp 'mcp-hub-servers)) + (null mcp-hub-servers))) + (interactive) + (call-interactively #'gptel-mcp-disconnect) + (transient-setup)) + + (transient-remove-suffix 'gptel-tools '(0 2)) + (transient-append-suffix 'gptel-tools '(0 -1) + ["" + (gptel--suffix-mcp-connect) + (gptel--suffix-mcp-disconnect)])) + +(provide 'gptel-integrations) +;;; gptel-integrations.el ends here