Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80d0352d1e | ||
|
|
e4e6d083e5 | ||
|
|
ceaaec2068 | ||
|
|
560d2d102f | ||
|
|
81c7f99174 | ||
|
|
d7c952cf4d | ||
|
|
810820658a | ||
|
|
42206f8328 | ||
|
|
a525c2a1d0 | ||
|
|
bd75822e2d | ||
|
|
23da3cf87f | ||
|
|
55fdf722e4 | ||
|
|
67c7e829f3 | ||
|
|
66e3a17aa1 | ||
|
|
058a009f65 | ||
|
|
bc7e34472e | ||
|
|
30717f8021 | ||
|
|
f23109857b | ||
|
|
471a7b4fbf | ||
|
|
eab27798b2 | ||
|
|
b732981049 | ||
|
|
32dd8e8d2f | ||
|
|
58eb312a77 |
No files matched your search
@@ -9,10 +9,10 @@ This repository provides Nix derivations for MCP (Model Context Protocol) server
|
||||
| Package | Description | Language |
|
||||
| -------------------------------- | -------------------------------------------------------------------- | ---------- |
|
||||
| academic-search-mcp-server | Search academic papers via Semantic Scholar and Crossref | Python |
|
||||
| annas-mcp | Anna's Archive MCP server | Python |
|
||||
| applemusic-mcp | Apple Music — manage playlists, search catalog, browse library | Python |
|
||||
| duckduckgo-mcp-server | Web search and content fetching via DuckDuckGo | Python |
|
||||
| jellyfin-mcp | MCP server for Jellyfin media server | Python |
|
||||
| libgen-mcp | Library Genesis + open-access search/download/read (no account needed) | Go |
|
||||
| kindly-web-search-mcp-server | Web search + content retrieval via SearxNG, Tavily, or SearXNG | Python |
|
||||
| manim-mcp-server | Execute Manim animation code and return videos | Python |
|
||||
| mcp-arr-server | MCP server for *arr suite — Sonarr, Radarr, Lidarr, Prowlarr | TypeScript |
|
||||
@@ -22,11 +22,12 @@ This repository provides Nix derivations for MCP (Model Context Protocol) server
|
||||
| pdf-reader-mcp | Production‑ready PDF processing with parallel processing | TypeScript |
|
||||
| rust-research-mcp | Accumulate and organize academic knowledge | Rust |
|
||||
| simple-web-search-mcp | Zero-config web search and page reading for LLMs | TypeScript |
|
||||
| mcp-searxng | Privacy-respecting web search and URL reading via SearXNG | TypeScript |
|
||||
| transmission-mcp | MCP server for the Transmission BitTorrent client | Python |
|
||||
| sabnzbd-mcp | MCP server for SABnzbd download management | Python |
|
||||
| jellyfin-mcp | MCP server for Jellyfin — search, playback, metadata, admin | Go |
|
||||
| zotero-mcp | Connect Zotero research library with AI assistants | Python |
|
||||
| annas-mcp | Search/download documents from Anna's Archive | Go |
|
||||
| annas-mcp | Anna's Archive search/download (SokolskyNikita fork); needs cookie/API key | Go |
|
||||
| bitwarden-mcp | Official Bitwarden vault, login, Send, org management | TypeScript |
|
||||
| mcp-email-server | Send, receive, search email over IMAP/SMTP | Python |
|
||||
| namecheap-mcp | Namecheap DNS, domains, contacts, registrar lock | TypeScript |
|
||||
|
||||
Generated
+1
-18
@@ -18,24 +18,7 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-nixpkgs": "rust-nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1786599213,
|
||||
"narHash": "sha256-yNJd40f11EzXBjSByCB7IPpeFFAdeoSKKM67dGkfFoU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0e251e24a4f24e036a084b6b4b2d2491af4167f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0e251e24a4f24e036a084b6b4b2d2491af4167f4",
|
||||
"type": "github"
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
packages = builtins.attrValues mcpPackages ++ [ pkgs.mcp-nixos pkgs.git ];
|
||||
shellHook = ''
|
||||
export SEARXNG_BASE_URL="https://search.bueso.eu"
|
||||
export SEARXNG_URL="https://search.bueso.eu"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@
|
||||
"command": ["simple-web-search-mcp"],
|
||||
"enabled": true
|
||||
},
|
||||
"mcp-searxng": {
|
||||
"type": "local",
|
||||
"command": ["mcp-searxng"],
|
||||
"enabled": true,
|
||||
"environment": {
|
||||
"SEARXNG_URL": "https://search.bueso.eu"
|
||||
}
|
||||
},
|
||||
"rust-research": {
|
||||
"type": "local",
|
||||
"command": ["rust-research-mcp"],
|
||||
@@ -96,6 +104,11 @@
|
||||
"type": "local",
|
||||
"command": ["mcp-score", "serve"],
|
||||
"enabled": true
|
||||
},
|
||||
"docx": {
|
||||
"type": "local",
|
||||
"command": ["docx-mcp"],
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,16 @@
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "annas-mcp";
|
||||
version = "0.1.0-unstable-2026-06-26";
|
||||
version = "0.0.14";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "iosifache";
|
||||
owner = "SokolskyNikita";
|
||||
repo = "annas-mcp";
|
||||
rev = "c095963ad5d10d33da3c614495246b6ec7f680d4";
|
||||
hash = "sha256-4gH5175yA9YYPVifXX3n9WCTRug0gOOfpQBLaGbvBcU=";
|
||||
rev = "9e720ad93df0fdfb25d4de6afcba11a920b34af4";
|
||||
hash = "sha256-LcnWYpvSpSHYo1zMcLGTZgxZgo7JOXF4dESD4l+qVhI=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-2NdG5p2XfrhVgi388dRDBUSGwg6ybnzfn9495TWNGsA=";
|
||||
vendorHash = "sha256-SCTznvZ8DvDN+iPDcTxiqpNYJwRLS8zIomz7f8S1eD0=";
|
||||
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
|
||||
@@ -28,9 +28,8 @@ buildGoModule rec {
|
||||
|
||||
meta = with lib; {
|
||||
description = "MCP server and CLI tool for searching and downloading documents from Anna's Archive";
|
||||
homepage = "https://github.com/iosifache/annas-mcp";
|
||||
license = licenses.mit;
|
||||
homepage = "https://github.com/SokolskyNikita/annas-mcp";
|
||||
mainProgram = "annas-mcp";
|
||||
platforms = platforms.linux;
|
||||
platforms = platforms.all;
|
||||
};
|
||||
}
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "applemusic-mcp";
|
||||
version = "0.18.3";
|
||||
version = "0.20.1";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "epheterson";
|
||||
repo = "applemusic-mcp";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-VHW7ct824hW+Gv3x6tMDGnvx3Hj/0ZDqGjc7yQOaf7U=";
|
||||
hash = "sha256-T956sblq/4TrAXkY4cwIFDPW6a2bjzyeyRyejYB4ARc=";
|
||||
};
|
||||
|
||||
build-system = with python3Packages; [
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
pinchflat-mcp = pkgs.callPackage ./pinchflat-mcp/package.nix { };
|
||||
audiobookshelf-mcp = pkgs.callPackage ./audiobookshelf-mcp/package.nix { };
|
||||
annas-mcp = pkgs.callPackage ./annas-mcp/package.nix { };
|
||||
libgen-mcp = pkgs.callPackage ./libgen-mcp/package.nix { };
|
||||
bitwarden-mcp = pkgs.callPackage ./bitwarden-mcp/package.nix { };
|
||||
mcp-email-server = pkgs.callPackage ./mcp-email-server/package.nix { };
|
||||
namecheap-mcp = pkgs.callPackage ./namecheap-mcp/package.nix { };
|
||||
@@ -29,5 +30,8 @@
|
||||
ovhcloud-mcp = pkgs.callPackage ./ovhcloud-mcp/package.nix { };
|
||||
slskd-mcp = pkgs.callPackage ./slskd-mcp/package.nix { };
|
||||
yarr = pkgs.callPackage ./yarr/package.nix { };
|
||||
pdf-mcp = pkgs.callPackage ./pdf-mcp/package.nix { };
|
||||
mcp-searxng = pkgs.callPackage ./mcp-searxng/package.nix { };
|
||||
python-ssh-mcp = pkgs.callPackage ./python-ssh-mcp/package.nix { };
|
||||
# example-mcp-server = pkgs.callPackage ./example-mcp-server/package.nix { };
|
||||
}
|
||||
@@ -2,18 +2,28 @@
|
||||
lib,
|
||||
python3Packages,
|
||||
fetchFromGitHub,
|
||||
fetchurl,
|
||||
}:
|
||||
|
||||
let
|
||||
# Upstream 0.7.0 was ported to the MCP SDK 2.x API
|
||||
# (`mcp.server.mcpserver.MCPServer`). nixpkgs pins the SDK at 1.x, so use the
|
||||
# per-package 2.x stack from packages/lib/mcp2.nix.
|
||||
mcp2 = import ../lib/mcp2.nix {
|
||||
inherit lib fetchurl;
|
||||
pythonPackages = python3Packages;
|
||||
};
|
||||
in
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "duckduckgo-mcp-server";
|
||||
version = "0.4.0";
|
||||
version = "0.7.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "nickclyde";
|
||||
repo = "duckduckgo-mcp-server";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-g1mpBrhuCt3g/XnUfHoY8lJDMizINypikj8Lqk2LVuE=";
|
||||
hash = "sha256-wI6Q4lBZYzU4VORLwGZowv8D5UfhKaMeqBIQ7u6eRxc=";
|
||||
};
|
||||
|
||||
build-system = [ python3Packages.hatchling ];
|
||||
@@ -21,10 +31,12 @@ python3Packages.buildPythonApplication rec {
|
||||
dependencies = with python3Packages; [
|
||||
beautifulsoup4
|
||||
httpx
|
||||
mcp
|
||||
httpcore
|
||||
mcp2.mcp
|
||||
];
|
||||
|
||||
doCheck = false;
|
||||
dontCheckRuntimeDeps = true;
|
||||
|
||||
pythonImportsCheck = [ "duckduckgo_mcp_server" ];
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "jellyfin-mcp";
|
||||
version = "2026.604.2";
|
||||
version = "2026.1015.1";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "jaredtrent";
|
||||
repo = "jellyfin-mcp";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-Ks54txfOAkNEWfhl5ZXEIaFzAbmCnwhHtN1+FIa7dgo=";
|
||||
hash = "sha256-5wSYB/peTbvNYDlDBd2M0nA7JreP93nQe1G9rW3hv2I=";
|
||||
};
|
||||
|
||||
vendorHash = "sha256-cDy+PpZM6xL7ur/xq81y4vlyaDnSOpGkyVV2hh8qI7o=";
|
||||
vendorHash = "sha256-L39HdshrVfVXD+rV3prsKQRosvidynwIlA9J6dtfqmU=";
|
||||
|
||||
meta = with lib; {
|
||||
description = "MCP server for Jellyfin media server — search, playback, metadata, admin";
|
||||
|
||||
@@ -71,14 +71,14 @@ let
|
||||
in
|
||||
python313Packages.buildPythonApplication rec {
|
||||
pname = "kindly-web-search-mcp-server";
|
||||
version = "0.1.8-unstable";
|
||||
version = "0.1.9-unstable";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Shelpuk-AI-Technology-Consulting";
|
||||
repo = "kindly-web-search-mcp-server";
|
||||
rev = "2b76afb37bf311facbde6750d0a0c0cbdfb8fec2";
|
||||
hash = "sha256-90QHr2FPiMSwKXmjlZ2W9NIQvj3LDXzYIq6m/Z+sJMw=";
|
||||
rev = "52a4e480d8041f8d4f388cee1ddecbd8e1ab01c7";
|
||||
hash = "sha256-Mf3PMUic3XiNkiABRy6DrEhoGwcqq/xThU4ROO4S8dY=";
|
||||
};
|
||||
|
||||
build-system = with python313Packages; [
|
||||
@@ -95,10 +95,18 @@ python313Packages.buildPythonApplication rec {
|
||||
pymupdf
|
||||
fastmcp
|
||||
trafilatura
|
||||
starlette
|
||||
uvicorn
|
||||
];
|
||||
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
|
||||
# Upstream pins floors newer than nixpkgs currently ships (starlette 1.6,
|
||||
# pydantic 2.13, beautifulsoup4 4.15, trafilatura 2.2, ...). The APIs this
|
||||
# server actually uses are present in the nixpkgs versions, so the strict
|
||||
# runtime-deps floor check is skipped here.
|
||||
dontCheckRuntimeDeps = true;
|
||||
|
||||
# Wrap with Chromium path for headless browser content extraction
|
||||
postInstall = ''
|
||||
wrapProgram $out/bin/kindly-web-search-mcp-server \
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# mcp 2.x Python SDK stack
|
||||
#
|
||||
# nixpkgs pins the MCP Python SDK at 1.x, whose server class is
|
||||
# `mcp.server.fastmcp.FastMCP`. SDK 2.x renamed it to
|
||||
# `mcp.server.mcpserver.MCPServer` and dropped the old module, so packages that
|
||||
# upstream has ported to the 2.x API need their own copy of the SDK rather than
|
||||
# the nixpkgs attribute. Derivations are isolated, so this stack coexists with
|
||||
# the 1.x packages in the same repository without affecting them.
|
||||
#
|
||||
# The three pure-Python wheels below (`mcp-types`, `httpx2`, `httpcore2`) are
|
||||
# not in nixpkgs; everything else resolves from the pinned nixpkgs.
|
||||
{
|
||||
lib,
|
||||
pythonPackages,
|
||||
fetchurl,
|
||||
}:
|
||||
|
||||
let
|
||||
ps = pythonPackages;
|
||||
|
||||
mk = { pname, version, url, hash, deps ? [ ] }:
|
||||
ps.buildPythonPackage {
|
||||
inherit pname version;
|
||||
format = "wheel";
|
||||
src = fetchurl { inherit url hash; };
|
||||
propagatedBuildInputs = deps;
|
||||
doCheck = false;
|
||||
# Upstream floors track the latest releases, which routinely sit ahead of
|
||||
# nixpkgs. The build is pure-Python and import-checked below, so the
|
||||
# strict floor check is not useful here.
|
||||
dontCheckRuntimeDeps = true;
|
||||
pythonImportsCheck = [ (builtins.replaceStrings [ "-" ] [ "_" ] pname) ];
|
||||
meta = with lib; {
|
||||
description = "MCP Python SDK ${version} dependency: ${pname}";
|
||||
homepage = "https://github.com/modelcontextprotocol/python-sdk";
|
||||
license = licenses.mit;
|
||||
};
|
||||
};
|
||||
|
||||
mcp-types = mk {
|
||||
pname = "mcp-types";
|
||||
version = "2.1.1";
|
||||
url = "https://files.pythonhosted.org/packages/71/d0/242e63c510f4a17381f55b1549a3f94f5687a0595984febd2b6f87a687a0/mcp_types-2.1.1-py3-none-any.whl";
|
||||
hash = "sha256-Jvn38D8qVzBxeluY4qt+tkCsNS0FoAzcclwxGGR3gpU=";
|
||||
deps = with ps; [ pydantic typing-extensions ];
|
||||
};
|
||||
|
||||
httpcore2 = mk {
|
||||
pname = "httpcore2";
|
||||
version = "2.5.0";
|
||||
url = "https://files.pythonhosted.org/packages/c9/a1/7564199d1a8728fe737b0a72e5b3f8d92dfe085a74ddf7cdd83bce5f206d/httpcore2-2.5.0-py3-none-any.whl";
|
||||
hash = "sha256-XONRiN5GHTHo0AC/uO+L8ixsFlh6IR5Vcd6qXpvfhCo=";
|
||||
deps = with ps; [ h11 truststore ];
|
||||
};
|
||||
|
||||
httpx2 = mk {
|
||||
pname = "httpx2";
|
||||
version = "2.5.0";
|
||||
url = "https://files.pythonhosted.org/packages/31/22/859d8252dad9bc9adee34b52e62cde621ece07b042ccb2ab4da1be46695f/httpx2-2.5.0-py3-none-any.whl";
|
||||
hash = "sha256-PS1NnPS2HxofRqlZR8/bR+gMtWovkcYlasj1jkiR30E=";
|
||||
deps = with ps; [
|
||||
anyio
|
||||
httpcore2
|
||||
idna
|
||||
truststore
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit mcp-types httpcore2 httpx2;
|
||||
|
||||
mcp = mk {
|
||||
pname = "mcp";
|
||||
version = "2.1.1";
|
||||
url = "https://files.pythonhosted.org/packages/50/af/8644cc5fa26a59afd2df2e98eeb19e72926887fa4b7441aba4ff661140db/mcp-2.1.1-py3-none-any.whl";
|
||||
hash = "sha256-HGwxxdZHHFjbdq86+K9n9G0R0B8KWQd9CjCMvbPT6RU=";
|
||||
deps = with ps; [
|
||||
anyio
|
||||
httpx2
|
||||
jsonschema
|
||||
mcp-types
|
||||
opentelemetry-api
|
||||
pydantic
|
||||
pyjwt
|
||||
python-multipart
|
||||
sse-starlette
|
||||
starlette
|
||||
typing-extensions
|
||||
typing-inspection
|
||||
uvicorn
|
||||
] ++ (with ps; [
|
||||
# mcp[cli] console entry point
|
||||
python-dotenv
|
||||
rich
|
||||
typer
|
||||
]);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
lib,
|
||||
stdenv,
|
||||
fetchurl,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "libgen-mcp";
|
||||
version = "2.0.1";
|
||||
|
||||
src = fetchurl (
|
||||
{
|
||||
x86_64-linux = {
|
||||
url = "https://github.com/jmrplens/libgen-mcp/releases/download/v${version}/libgen-mcp-linux-amd64";
|
||||
hash = "sha256-MN6DQ76AmGfaz/Xsyuk6EgFbHxf4TEcg5HXyVomn8G8=";
|
||||
};
|
||||
aarch64-linux = {
|
||||
url = "https://github.com/jmrplens/libgen-mcp/releases/download/v${version}/libgen-mcp-linux-arm64";
|
||||
hash = "sha256-qIkFGqEaohEnL99vbjM86JjFaHbV+A7vKNVSZg0G5Cg=";
|
||||
};
|
||||
x86_64-darwin = {
|
||||
url = "https://github.com/jmrplens/libgen-mcp/releases/download/v${version}/libgen-mcp-darwin-amd64";
|
||||
hash = "sha256-eeLfugTMCbvuyT3NzVQmBovarz0tOtOMsbNzAVKUTco=";
|
||||
};
|
||||
aarch64-darwin = {
|
||||
url = "https://github.com/jmrplens/libgen-mcp/releases/download/v${version}/libgen-mcp-darwin-arm64";
|
||||
hash = "sha256-xmKMeMJtOrqZwgTv7CvXiLEMib/XLvkQ3rCFTOyGU/Y=";
|
||||
};
|
||||
}
|
||||
.${stdenv.hostPlatform.system} or (throw "libgen-mcp: unsupported system ${stdenv.hostPlatform.system}")
|
||||
);
|
||||
|
||||
dontUnpack = true;
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/bin
|
||||
install -m755 $src $out/bin/${pname}
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "MCP server for federated search, download and reading of books, papers, comics and standards across Library Genesis and open-access sources";
|
||||
homepage = "https://github.com/jmrplens/libgen-mcp";
|
||||
license = licenses.mit;
|
||||
mainProgram = pname;
|
||||
platforms = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "mcp-email-server";
|
||||
version = "1.4.1";
|
||||
version = "1.9.1";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Wh1isper";
|
||||
repo = "mcp-email-server";
|
||||
rev = "a1668a912db08373cc109b7482363129c75fd646";
|
||||
hash = "sha256-Zt+198bQMthp6Mxmmsd7eTh9iybqOXWuVqLylzfUiIg=";
|
||||
rev = "1.9.1";
|
||||
hash = "sha256-FGjpRkVOFLkqY45Wl1lBuSWZvUnkyESZPeAB+d+1KYw=";
|
||||
};
|
||||
|
||||
build-system = with python3Packages; [
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
}:
|
||||
|
||||
let
|
||||
# music21 is not in nixpkgs; vendor it (upstream mcp-score pins >=9.0,<10)
|
||||
# music21 is not in nixpkgs; vendor it (upstream mcp-score pins >=10.5,<11)
|
||||
music21 = python313Packages.buildPythonPackage rec {
|
||||
pname = "music21";
|
||||
version = "9.9.2";
|
||||
version = "10.5.0";
|
||||
format = "wheel";
|
||||
|
||||
src = fetchPypi {
|
||||
@@ -17,7 +17,7 @@ let
|
||||
format = "wheel";
|
||||
dist = "py3";
|
||||
python = "py3";
|
||||
hash = "sha256-ZIAQq40TKNCxB0bO8UV+hLEidM0xYkgifpbSHielGMo=";
|
||||
hash = "sha256-mSTv9fv1hJDmfL87eKWLpTBA53woGTDTVK8yp0fpRgY=";
|
||||
};
|
||||
|
||||
propagatedBuildInputs = with python313Packages; [
|
||||
@@ -50,8 +50,8 @@ python313Packages.buildPythonApplication rec {
|
||||
src = fetchFromGitHub {
|
||||
owner = "tskovlund";
|
||||
repo = "mcp-score";
|
||||
rev = "3ff9d4b6504fcee5032c44fdcf7dc1d7a21a4b5d";
|
||||
hash = "sha256-zrpvzxi8AbF5/etuvMZLni4HSFBYGRe55rpKJJEAg60=";
|
||||
rev = "b2155a59119103c39a37f14166d0d6920e564c76";
|
||||
hash = "sha256-XYZjGa7pWaVmZ4dF/vOLkrYjSjNXP/148DQjujMdvIU=";
|
||||
};
|
||||
|
||||
build-system = with python313Packages; [
|
||||
|
||||
Generated
+4606
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,32 @@
|
||||
{
|
||||
lib,
|
||||
buildNpmPackage,
|
||||
fetchzip,
|
||||
}:
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "mcp-searxng";
|
||||
version = "2.4.0";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://registry.npmjs.org/mcp-searxng/-/mcp-searxng-${version}.tgz";
|
||||
hash = "sha256-WB1p3+IDPpRzXh1WSFbWA3ezX8Ih2FsZlV3D3od/v1A=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-srchzmiehwh4wOzhXfDbHiT7t4i8FPRSlakdUGIF/Ks=";
|
||||
|
||||
postPatch = ''
|
||||
cp ${./package-lock.json} package-lock.json
|
||||
'';
|
||||
|
||||
# dist/ is already built in the npm tarball
|
||||
dontNpmBuild = true;
|
||||
|
||||
meta = with lib; {
|
||||
description = "MCP server integrating SearXNG for privacy-respecting web search and URL reading";
|
||||
homepage = "https://github.com/ihor-sokoliuk/mcp-searxng";
|
||||
license = licenses.mit;
|
||||
mainProgram = pname;
|
||||
platforms = platforms.all;
|
||||
};
|
||||
}
|
||||
Generated
+24788
-230
File diff suppressed because it is too large.
Load diff
@@ -8,14 +8,14 @@
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "n8n-mcp";
|
||||
version = "2.35.1";
|
||||
version = "2.87.0";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://registry.npmjs.org/${pname}/-/${pname}-${version}.tgz";
|
||||
hash = "sha256-CWHH1ACcMCWkLahV5fTO0/k5OtfVUKNldHseDP8EfEk=";
|
||||
hash = "sha256-YEINthnVgy5nc0KPPHWhXBiiA5vR/ThKqie9lJ6d3co=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-x9ziuYxZPQMqkB2HA4HmGkpge7NGp9t4t4aNTi0XXn4=";
|
||||
npmDepsHash = "sha256-oy3aR16bTuyztbY3rgTo2z66/4kCk2BNE4BLZjhtki8=";
|
||||
|
||||
postPatch = ''
|
||||
cp ${./package-lock.json} package-lock.json
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "navidrome-mcp";
|
||||
version = "2.2.0";
|
||||
version = "2.3.0";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://registry.npmjs.org/${pname}/-/${pname}-${version}.tgz";
|
||||
hash = "sha256-1wZyM0GJUrQfzBK1tWqqIcVk90KUv/9wt2ZpRobQp/A=";
|
||||
hash = "sha256-npUgzJpB0DaFuzU/1SuOIOVvd2gqZtb1Kos7NC8/Pd4=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-58OZ4qVfeN+0LVzcSJQHQqiY66gPSGWI6ht/4fMp0oQ=";
|
||||
|
||||
@@ -3,9 +3,18 @@
|
||||
python3Packages,
|
||||
fetchFromGitHub,
|
||||
fetchPypi,
|
||||
fetchurl,
|
||||
}:
|
||||
|
||||
let
|
||||
# Upstream >=0.185.0 was ported to the MCP SDK 2.x API
|
||||
# (`mcp.server.mcpserver.MCPServer`). nixpkgs pins the SDK at 1.x, so use the
|
||||
# per-package 2.x stack from packages/lib/mcp2.nix.
|
||||
mcp2 = import ../lib/mcp2.nix {
|
||||
inherit lib fetchurl;
|
||||
pythonPackages = python3Packages;
|
||||
};
|
||||
|
||||
# Helper: buildPythonPackage for a wheel fetched from PyPI
|
||||
# CRITICAL: pname MUST use underscores, not hyphens, for PyPI wheel filenames
|
||||
fetchWheel = { pname, version, hash, propagatedBuildInputs ? [ ], meta ? { } }:
|
||||
@@ -132,23 +141,24 @@ let
|
||||
in
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "nextcloud-mcp-server";
|
||||
version = "0.173.0";
|
||||
version = "0.195.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "cbcoutinho";
|
||||
repo = "nextcloud-mcp-server";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-wN0LUoKD4qR00H1eTeIHEoAmSEC7YY+sQ2HtY6oerjE=";
|
||||
hash = "sha256-k8WeVe1ScWN7Lh+trowzn+UVuQBgfgI1Klfz4w5F8do=";
|
||||
};
|
||||
|
||||
build-system = [ python3Packages.uv-build ];
|
||||
|
||||
dependencies = with python3Packages; [
|
||||
mcp httpx pillow icalendar pydantic click pyjwt
|
||||
mcp2.mcp httpx pillow icalendar pydantic click pyjwt
|
||||
aiosqlite alembic authlib fastembed anthropic boto3 starlette
|
||||
jinja2 markdownify pymupdf pymupdf4llm openai dynaconf sqlalchemy
|
||||
anyio uvicorn cryptography lxml botocore numpy packaging pypdfium2
|
||||
olefile openpyxl python-docx python-pptx
|
||||
|
||||
pythonvcard4 prometheus-client python-json-logger importlib-metadata
|
||||
googleapis-common-protos opentelemetry-api opentelemetry-proto
|
||||
@@ -163,13 +173,10 @@ python3Packages.buildPythonApplication rec {
|
||||
postPatch = ''
|
||||
substituteInPlace pyproject.toml \
|
||||
--replace-quiet '"uv_build>=0.12.1,<0.13.0"' '"uv_build"' \
|
||||
--replace-quiet '" (>=1.29,<1.30)"' '""' \
|
||||
--replace-quiet '" (>=2.1,<3)"' '""' \
|
||||
--replace-quiet '" (>=0.28.1,<0.29.0)"' '""' \
|
||||
--replace-quiet '" (>=10.3.0,<12.0.0)"' '""' \
|
||||
--replace-quiet '" (>=7.2.0,<7.3.0)"' '""' \
|
||||
--replace-quiet '" (>=3.0.1,<4.0)"' '""' \
|
||||
--replace-quiet '" (>=1.28.2,<2.0.0)"' '""' \
|
||||
--replace-quiet '" (>=2.0.0,<3.0.0)"' '""' \
|
||||
--replace-quiet '" (>=7.3.0,<7.4.0)"' '""' \
|
||||
--replace-quiet '">=3.0.1,<4.0"' '""' \
|
||||
--replace-quiet '">=3.8.0,<4.0"' '""' \
|
||||
--replace-quiet '">=3.2.13,<4.0"' '""' \
|
||||
@@ -189,6 +196,12 @@ python3Packages.buildPythonApplication rec {
|
||||
dontCheckRuntimeDeps = true;
|
||||
catchConflicts = false;
|
||||
|
||||
pythonImportsCheck = [
|
||||
"nextcloud_mcp_server"
|
||||
"mcp"
|
||||
"pymupdf"
|
||||
];
|
||||
|
||||
meta = with lib; {
|
||||
description = "MCP server for Nextcloud — files, calendar, contacts, notes, deck, talk, sharing";
|
||||
homepage = "https://github.com/cbcoutinho/nextcloud-mcp-server";
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
lib,
|
||||
python3Packages,
|
||||
fetchFromGitea,
|
||||
}:
|
||||
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "pdf-mcp";
|
||||
version = "0.1.1-unstable-2026-07-30";
|
||||
|
||||
src = fetchFromGitea {
|
||||
domain = "gitea.bueso.eu";
|
||||
owner = "hermes-agent";
|
||||
repo = "pdf-mcp";
|
||||
rev = "356e476d1dd0d2d9eb866df7c6f82a21018351c8";
|
||||
hash = "sha256-whfXjMQIUt94VEeo6Fdvdr96WV3HNNNVuL1gtkt4g7M=";
|
||||
};
|
||||
|
||||
pyproject = true;
|
||||
|
||||
build-system = with python3Packages; [ hatchling ];
|
||||
|
||||
dependencies = with python3Packages; [
|
||||
mcp
|
||||
pymupdf
|
||||
pymupdf4llm
|
||||
];
|
||||
|
||||
doCheck = false;
|
||||
pythonImportsCheck = [ "pdf_mcp" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "MCP server for reading, rendering, and searching PDF files (PyMuPDF)";
|
||||
homepage = "https://gitea.bueso.eu/hermes-agent/pdf-mcp";
|
||||
license = licenses.mit;
|
||||
mainProgram = "pdf-mcp";
|
||||
platforms = platforms.all;
|
||||
maintainers = [ ];
|
||||
};
|
||||
}
|
||||
+3740
-1379
File diff suppressed because it is too large.
Load diff
@@ -2,24 +2,33 @@
|
||||
lib,
|
||||
buildNpmPackage,
|
||||
fetchzip,
|
||||
autoPatchelfHook,
|
||||
stdenv,
|
||||
}:
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "pdf-reader-mcp";
|
||||
version = "2.3.0";
|
||||
version = "4.1.3";
|
||||
|
||||
src = fetchzip {
|
||||
url = "https://registry.npmjs.org/@sylphx/pdf-reader-mcp/-/pdf-reader-mcp-${version}.tgz";
|
||||
hash = "sha256-nLtTYE5pSLigAQqsWx3TvntOign30vUJ4jaCDScm9oY=";
|
||||
hash = "sha256-oxEL7lwEJdhPoA4p/HiQeMp9TP7al1z2KpiPTUPqn6c=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-Z+C1pGJb1gCUR4qlM9ZSjhevqWPdCOVhL631GFaehbc=";
|
||||
npmDepsHash = "sha256-3XohzskNUMGkIgZvQie9DA8DfHxJZGD86n+uVZPxMdg=";
|
||||
|
||||
# The runtime launcher spawns a prebuilt Rust binary shipped in the matching
|
||||
# optional platform package. On Linux it is dynamically linked against glibc,
|
||||
# so its interpreter and RPATH must be patched for NixOS.
|
||||
nativeBuildInputs = lib.optional stdenv.isLinux autoPatchelfHook;
|
||||
|
||||
buildInputs = lib.optional stdenv.isLinux stdenv.cc.cc.lib;
|
||||
|
||||
postPatch = ''
|
||||
cp ${./package-lock.json} package-lock.json
|
||||
'';
|
||||
|
||||
# dist/ is already built in the npm tarball
|
||||
# dist/ and the native Rust binary ship prebuilt; there is no build step.
|
||||
dontNpmBuild = true;
|
||||
|
||||
meta = with lib; {
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pinchflat MCP server — exposes the Pinchflat REST API as MCP tools."""
|
||||
"""Pinchflat MCP server — exposes the Pinchflat REST API as MCP tools.
|
||||
|
||||
Tool tiers
|
||||
----------
|
||||
Two tiers of tools exist and only one is exposed by default:
|
||||
|
||||
* ``subscriptions`` (default) — sources, media items, media profiles, tasks.
|
||||
This is what an assistant needs to manage subscriptions and their files.
|
||||
* ``admin`` — global application state: ``get_settings``, ``get_app_info``.
|
||||
These describe the installation rather than a subscription and are NOT
|
||||
exposed unless explicitly enabled.
|
||||
|
||||
Select the tier with ``PINCHFLAT_MCP_TOOLSET``:
|
||||
|
||||
PINCHFLAT_MCP_TOOLSET=subscriptions # default
|
||||
PINCHFLAT_MCP_TOOLSET=admin
|
||||
PINCHFLAT_MCP_TOOLSET=all
|
||||
|
||||
Responses are scrubbed of secrets on the way out (see ``_scrub``), because the
|
||||
Pinchflat settings record embeds the application's own ``api_token``. Exposing
|
||||
it to a tool caller hands out a credential that outlives the tool session, so
|
||||
secret-bearing keys are redacted at the transport boundary rather than left to
|
||||
individual tools to remember.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -20,13 +43,76 @@ HEADERS = {"Authorization": f"Bearer {PINCHFLAT_TOKEN}", "Content-Type": "applic
|
||||
|
||||
mcp = FastMCP("pinchflat")
|
||||
|
||||
# --- Tool tiers -------------------------------------------------------------
|
||||
|
||||
_TIERS = {
|
||||
"subscriptions": {"subscriptions"},
|
||||
"admin": {"subscriptions", "admin"},
|
||||
"all": {"subscriptions", "admin"},
|
||||
}
|
||||
_TOOLSET = os.environ.get("PINCHFLAT_MCP_TOOLSET", "subscriptions").strip().lower()
|
||||
ENABLED_TIERS = _TIERS.get(_TOOLSET, _TIERS["subscriptions"])
|
||||
|
||||
|
||||
def tool(tier: str = "subscriptions"):
|
||||
"""Register a tool only when its tier is enabled."""
|
||||
|
||||
def decorator(fn):
|
||||
if tier in ENABLED_TIERS:
|
||||
return mcp.tool()(fn)
|
||||
return fn
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# --- Response scrubbing -----------------------------------------------------
|
||||
|
||||
# Keys whose values are credentials and must never cross the tool boundary.
|
||||
SECRET_KEYS = frozenset(
|
||||
{
|
||||
"api_token",
|
||||
"api_key",
|
||||
"token",
|
||||
"secret",
|
||||
"password",
|
||||
"password_hash",
|
||||
"youtube_api_key",
|
||||
"apprise_server",
|
||||
}
|
||||
)
|
||||
REDACTED = "[redacted]"
|
||||
|
||||
|
||||
def _scrub(obj: Any) -> Any:
|
||||
"""Recursively redact credential-bearing values from an API response."""
|
||||
if isinstance(obj, dict):
|
||||
out = {}
|
||||
for k, v in obj.items():
|
||||
if isinstance(k, str) and k.strip().lower() in SECRET_KEYS and v:
|
||||
out[k] = REDACTED
|
||||
else:
|
||||
out[k] = _scrub(v)
|
||||
return out
|
||||
if isinstance(obj, list):
|
||||
return [_scrub(v) for v in obj]
|
||||
return obj
|
||||
|
||||
|
||||
# --- HTTP helpers -----------------------------------------------------------
|
||||
|
||||
|
||||
def _unwrap(r: httpx.Response) -> Any:
|
||||
"""Decode a response and scrub secrets before it reaches the caller."""
|
||||
body = r.json()
|
||||
return _scrub(body)
|
||||
|
||||
|
||||
def _get(path: str, params: Optional[dict] = None) -> Any:
|
||||
"""Make a GET request to the Pinchflat API."""
|
||||
with httpx.Client(timeout=30) as client:
|
||||
r = client.get(f"{BASE}{path}", headers=HEADERS, params=params)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
return _unwrap(r)
|
||||
|
||||
|
||||
def _post(path: str, json: Optional[dict] = None) -> Any:
|
||||
@@ -34,7 +120,7 @@ def _post(path: str, json: Optional[dict] = None) -> Any:
|
||||
with httpx.Client(timeout=30) as client:
|
||||
r = client.post(f"{BASE}{path}", headers=HEADERS, json=json)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
return _unwrap(r)
|
||||
|
||||
|
||||
def _put(path: str, json: dict) -> Any:
|
||||
@@ -42,38 +128,40 @@ def _put(path: str, json: dict) -> Any:
|
||||
with httpx.Client(timeout=30) as client:
|
||||
r = client.put(f"{BASE}{path}", headers=HEADERS, json=json)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
return _unwrap(r)
|
||||
|
||||
|
||||
def _delete(path: str) -> Any:
|
||||
def _delete(path: str, params: Optional[dict] = None, timeout: int = 30) -> Any:
|
||||
"""Make a DELETE request to the Pinchflat API."""
|
||||
with httpx.Client(timeout=30) as client:
|
||||
r = client.delete(f"{BASE}{path}", headers=HEADERS)
|
||||
with httpx.Client(timeout=timeout) as client:
|
||||
r = client.delete(f"{BASE}{path}", headers=HEADERS, params=params)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
return _unwrap(r)
|
||||
|
||||
|
||||
# --- Sources ---
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@tool()
|
||||
def list_sources() -> dict:
|
||||
"""List all configured media sources (YouTube channels, playlists, etc.)."""
|
||||
return _get("/sources")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def get_source(source_id: int) -> dict:
|
||||
"""Get details of a specific source by ID, including its pending tasks."""
|
||||
return _get(f"/sources/{source_id}")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def create_source(
|
||||
original_url: str,
|
||||
media_profile_id: int,
|
||||
collection_type: str = "channel",
|
||||
custom_name: Optional[str] = None,
|
||||
download_cutoff_date: Optional[str] = None,
|
||||
retention_period_days: Optional[int] = None,
|
||||
) -> dict:
|
||||
"""Create a new media source (e.g. add a YouTube channel to monitor).
|
||||
|
||||
@@ -83,6 +171,8 @@ def create_source(
|
||||
collection_type: 'channel', 'playlist', or 'video'
|
||||
custom_name: Optional custom name for the source
|
||||
download_cutoff_date: Only download media after this date (YYYY-MM-DD)
|
||||
retention_period_days: Delete downloaded media this many days after
|
||||
publication; omit to keep everything
|
||||
"""
|
||||
source = {
|
||||
"media_profile_id": media_profile_id,
|
||||
@@ -93,18 +183,30 @@ def create_source(
|
||||
source["custom_name"] = custom_name
|
||||
if download_cutoff_date:
|
||||
source["download_cutoff_date"] = download_cutoff_date
|
||||
if retention_period_days is not None:
|
||||
source["retention_period_days"] = retention_period_days
|
||||
return _post("/sources", json={"source": source})
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def update_source(
|
||||
source_id: int,
|
||||
custom_name: Optional[str] = None,
|
||||
download_cutoff_date: Optional[str] = None,
|
||||
enabled: Optional[bool] = None,
|
||||
index_frequency_minutes: Optional[int] = None,
|
||||
retention_period_days: Optional[int] = None,
|
||||
prevent_culling: Optional[bool] = None,
|
||||
download_media: Optional[bool] = None,
|
||||
) -> dict:
|
||||
"""Update an existing source. Only provided fields will be changed."""
|
||||
"""Update an existing source. Only provided fields will be changed.
|
||||
|
||||
Args:
|
||||
retention_period_days: Delete downloaded media this many days after
|
||||
publication (0 keeps everything)
|
||||
prevent_culling: If True, never delete this source's downloads
|
||||
download_media: If False, index the source but download nothing
|
||||
"""
|
||||
updates = {}
|
||||
if custom_name is not None:
|
||||
updates["custom_name"] = custom_name
|
||||
@@ -114,34 +216,46 @@ def update_source(
|
||||
updates["enabled"] = enabled
|
||||
if index_frequency_minutes is not None:
|
||||
updates["index_frequency_minutes"] = index_frequency_minutes
|
||||
if retention_period_days is not None:
|
||||
updates["retention_period_days"] = retention_period_days
|
||||
if prevent_culling is not None:
|
||||
updates["prevent_culling"] = prevent_culling
|
||||
if download_media is not None:
|
||||
updates["download_media"] = download_media
|
||||
if not updates:
|
||||
return {"data": {"message": "No fields to update."}}
|
||||
return _put(f"/sources/{source_id}", json={"source": updates})
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def delete_source(source_id: int) -> dict:
|
||||
"""Delete a source (marks it for deletion)."""
|
||||
"""Delete a source (marks it for deletion).
|
||||
|
||||
Note: this removes the source and its media records, but does NOT delete
|
||||
the files from disk. Delete the media items first if space is the goal.
|
||||
"""
|
||||
return _delete(f"/sources/{source_id}")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def force_download_pending(source_id: int) -> dict:
|
||||
"""Force download of all pending media items for a source."""
|
||||
return _post(f"/sources/{source_id}/force_download_pending")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def force_redownload(source_id: int) -> dict:
|
||||
"""Force re-download of all existing media for a source."""
|
||||
return _post(f"/sources/{source_id}/force_redownload")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def force_index(source_id: int) -> dict:
|
||||
"""Force re-index of a source."""
|
||||
return _post(f"/sources/{source_id}/force_index")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def force_metadata_refresh(source_id: int) -> dict:
|
||||
"""Force metadata refresh for a source."""
|
||||
return _post(f"/sources/{source_id}/force_metadata_refresh")
|
||||
@@ -149,7 +263,8 @@ def force_metadata_refresh(source_id: int) -> dict:
|
||||
|
||||
# --- Media Items ---
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@tool()
|
||||
def list_media(
|
||||
source_id: Optional[int] = None,
|
||||
limit: int = 50,
|
||||
@@ -172,25 +287,25 @@ def list_media(
|
||||
return _get("/media", params=params)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def search_media(query: str) -> dict:
|
||||
"""Search media items by title or description."""
|
||||
return _get("/media/search", params={"q": query})
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def get_media(media_id: int) -> dict:
|
||||
"""Get details of a specific media item."""
|
||||
return _get(f"/media/{media_id}")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def force_download_media(media_id: int) -> dict:
|
||||
"""Force download of a specific media item."""
|
||||
return _post(f"/media/{media_id}/force_download")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def delete_media(media_id: int, prevent_download: bool = False) -> dict:
|
||||
"""Delete a media item's downloaded files from disk.
|
||||
|
||||
@@ -201,21 +316,19 @@ def delete_media(media_id: int, prevent_download: bool = False) -> dict:
|
||||
params = {}
|
||||
if prevent_download:
|
||||
params["prevent_download"] = "true"
|
||||
with httpx.Client(timeout=120) as client:
|
||||
r = client.delete(f"{BASE}/media/{media_id}", headers=HEADERS, params=params)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
return _delete(f"/media/{media_id}", params=params, timeout=120)
|
||||
|
||||
|
||||
# --- Media Profiles ---
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@tool()
|
||||
def list_media_profiles() -> dict:
|
||||
"""List all media profiles (download quality/container settings)."""
|
||||
return _get("/media_profiles")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def get_media_profile(profile_id: int) -> dict:
|
||||
"""Get details of a specific media profile."""
|
||||
return _get(f"/media_profiles/{profile_id}")
|
||||
@@ -223,7 +336,8 @@ def get_media_profile(profile_id: int) -> dict:
|
||||
|
||||
# --- Tasks ---
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@tool()
|
||||
def list_tasks(source_id: Optional[int] = None) -> dict:
|
||||
"""List all tasks. Optionally filter by source_id."""
|
||||
params = {}
|
||||
@@ -232,21 +346,22 @@ def list_tasks(source_id: Optional[int] = None) -> dict:
|
||||
return _get("/tasks", params=params)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool()
|
||||
def get_task(task_id: int) -> dict:
|
||||
"""Get details of a specific task."""
|
||||
return _get(f"/tasks/{task_id}")
|
||||
|
||||
|
||||
# --- Settings & Info ---
|
||||
# --- Settings & Info (admin tier — not exposed by default) ---
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@tool("admin")
|
||||
def get_settings() -> dict:
|
||||
"""Get current Pinchflat settings."""
|
||||
"""Get current Pinchflat settings (credential values are redacted)."""
|
||||
return _get("/settings")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@tool("admin")
|
||||
def get_app_info() -> dict:
|
||||
"""Get Pinchflat app info (version, environment, etc.)."""
|
||||
return _get("/app_info")
|
||||
@@ -254,4 +369,4 @@ def get_app_info() -> dict:
|
||||
|
||||
if __name__ == "__main__":
|
||||
transport = os.environ.get("PINCHFLAT_MCP_TRANSPORT", "stdio")
|
||||
mcp.run(transport=transport if transport in ("stdio", "http") else "stdio")
|
||||
mcp.run(transport=transport if transport in ("stdio", "http") else "stdio")
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
lib,
|
||||
python313Packages,
|
||||
fetchFromGitHub,
|
||||
fetchPypi,
|
||||
}:
|
||||
|
||||
# Upstream pins `requires-python = ">=3.11,<3.14"`, so this must NOT be built
|
||||
# with the default `python3Packages` (which is 3.14.x on nixos-unstable and
|
||||
# would be rejected by the build backend's requires-python check). Pin the
|
||||
# 3.13 package set explicitly.
|
||||
#
|
||||
# `fastmcp[tasks]` is a HARD runtime dependency: `src/ssh_mcp/tools/exec_tools.py`
|
||||
# does `from fastmcp.server.tasks import TaskConfig` at import time, and the
|
||||
# decorator validates at import. nixpkgs' `fastmcp` installs only the
|
||||
# client+server extras (optional-dependencies are passthru-only in nix and
|
||||
# install nothing), so the `tasks` extra's backend -- pydocket -- has to be
|
||||
# listed explicitly or the server crashes at startup.
|
||||
let
|
||||
# Internal dependency: upstream pins `unidiff>=0.7.5,<0.8.0`, but nixpkgs only
|
||||
# carries 1.0.0. The 0.7.x API surface used by the ssh_edit tool
|
||||
# (`PatchSet.from_string` and hunk iteration) is unchanged in 1.0.0, but
|
||||
# buildPythonApplication's runtime-dep check enforces the specifier, so the
|
||||
# pinned version still has to be provided. 0.7.5 is sdist-only on PyPI
|
||||
# (no wheel), so this is a source build.
|
||||
unidiff = python313Packages.buildPythonPackage rec {
|
||||
pname = "unidiff";
|
||||
version = "0.7.5";
|
||||
|
||||
pyproject = true;
|
||||
build-system = [ python313Packages.setuptools ];
|
||||
|
||||
src = fetchPypi {
|
||||
inherit pname version;
|
||||
hash = "sha256-Ll8BYgUiSJRrnwlwpA6eEkI2v4bIK3CCEUOm/B3qJXQ=";
|
||||
};
|
||||
|
||||
doCheck = false;
|
||||
pythonImportsCheck = [ "unidiff" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Unified diff parsing and application";
|
||||
homepage = "https://github.com/matiasb/python-unidiff";
|
||||
license = licenses.mit;
|
||||
};
|
||||
};
|
||||
in
|
||||
python313Packages.buildPythonApplication rec {
|
||||
pname = "python-ssh-mcp";
|
||||
version = "1.5.2";
|
||||
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "Nightreaver";
|
||||
repo = "python-ssh-mcp";
|
||||
rev = "73c00c9b0d26add59798bc7b39dff88ac547b15a"; # v1.5.2
|
||||
hash = "sha256-OfwOiBan5sqGEs/zzHHC4vJdcALOLWaIr8uENCbQ4FU=";
|
||||
};
|
||||
|
||||
build-system = [ python313Packages.hatchling ];
|
||||
|
||||
dependencies = with python313Packages; [
|
||||
fastmcp
|
||||
pydocket # the fastmcp[tasks] extra
|
||||
asyncssh
|
||||
pydantic
|
||||
pydantic-settings
|
||||
unidiff
|
||||
];
|
||||
|
||||
# Upstream ships no importable test suite in the sdist workflow used here
|
||||
# (its 1649 tests need the dockerized sshd fixtures); skip and rely on the
|
||||
# import check + the smoke test below.
|
||||
doCheck = false;
|
||||
pythonImportsCheck = [ "ssh_mcp" ];
|
||||
|
||||
meta = with lib; {
|
||||
description = "SSH MCP server for multi-host systemd/docker/apt administration (read-only by default, tiered)";
|
||||
homepage = "https://github.com/Nightreaver/python-ssh-mcp";
|
||||
license = licenses.gpl3Only;
|
||||
mainProgram = "ssh-mcp";
|
||||
platforms = platforms.all;
|
||||
maintainers = [ ];
|
||||
};
|
||||
}
|
||||
@@ -6,13 +6,13 @@
|
||||
|
||||
let
|
||||
wheel = fetchurl {
|
||||
url = "https://files.pythonhosted.org/packages/6b/9c/dc73804f0f62208d59794080d009fb86b7ac6e9d4b5ad452791355dcfb51/transmission_mcp-1.0.1-py3-none-any.whl";
|
||||
hash = "sha256-1TKb75iNKgMqw0YVZ+rDEac4IyCuj94y1OMzqQROCrI=";
|
||||
url = "https://files.pythonhosted.org/packages/7e/81/fb2a2f2073d22b0a18c4c96b9c9ff386c3d0b49b6263097bf646d8b72800/transmission_mcp-1.1.0-py3-none-any.whl";
|
||||
hash = "sha256-J22L3lY+XVdLdZeu8KuFdly0BkFUoAbX4GE/9a5kKSU=";
|
||||
};
|
||||
in
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "transmission-mcp";
|
||||
version = "1.0.1";
|
||||
version = "1.1.0";
|
||||
format = "wheel";
|
||||
|
||||
src = wheel;
|
||||
|
||||
@@ -10,7 +10,7 @@ let
|
||||
# Internal dependency: pyzotero (not in nixpkgs)
|
||||
pyzotero = python3Packages.buildPythonPackage rec {
|
||||
pname = "pyzotero";
|
||||
version = "1.13.5";
|
||||
version = "1.14.0";
|
||||
format = "wheel";
|
||||
|
||||
src = fetchPypi {
|
||||
@@ -18,7 +18,7 @@ let
|
||||
format = "wheel";
|
||||
dist = "py3";
|
||||
python = "py3";
|
||||
hash = "sha256-KoHXv+ix6kRzvLUPOPBeAeAJaO/3bBxHi1PwUa5rDcg=";
|
||||
hash = "sha256-OUoU8HTEFwVfzqopcLWUzpJaYJTJ09zIpzTgdCbs9BU=";
|
||||
};
|
||||
|
||||
propagatedBuildInputs = with python3Packages; [
|
||||
@@ -62,21 +62,21 @@ let
|
||||
in
|
||||
python3Packages.buildPythonApplication rec {
|
||||
pname = "zotero-mcp";
|
||||
version = "0.9.1";
|
||||
version = "0.13.0";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "54yyyu";
|
||||
repo = "zotero-mcp";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-1A4aKGLFUmY+abLKhxmnSd3KaJtC8onda4/MkjrybIg=";
|
||||
hash = "sha256-xL54SbaAczkwk4tkan+wVVTv9ACKIC6hcAADI2GTnxk=";
|
||||
};
|
||||
|
||||
build-system = with python3Packages; [
|
||||
hatchling
|
||||
];
|
||||
|
||||
# Core dependencies (from pyproject.toml v0.9.1)
|
||||
# Core dependencies (from pyproject.toml v0.13.0)
|
||||
dependencies = with python3Packages; [
|
||||
pyzotero
|
||||
python-dotenv
|
||||
@@ -88,25 +88,36 @@ python3Packages.buildPythonApplication rec {
|
||||
httpx
|
||||
unidecode
|
||||
bibtexparser
|
||||
# Runtime deps for the annotation/layout/read tools (upstream "pdf" extra).
|
||||
# nix's optional-dependencies is passthru-only and installs nothing, so
|
||||
# anything needed at runtime must live in this list.
|
||||
pymupdf
|
||||
ebooklib
|
||||
# Runtime deps for the semantic-search tools (upstream "semantic" extra).
|
||||
# zotero_mcp.semantic_search imports chromadb unconditionally at module
|
||||
# scope (embeddings/providers/__init__.py, chroma_client.py), so these are
|
||||
# hard requirements, not truly optional at runtime.
|
||||
chromadb
|
||||
sentence-transformers
|
||||
openai
|
||||
google-genai
|
||||
tiktoken
|
||||
];
|
||||
|
||||
# Optional extras (semantic search, PDF, scite)
|
||||
optional-dependencies = with python3Packages; {
|
||||
semantic = [
|
||||
chromadb
|
||||
sentence-transformers
|
||||
openai
|
||||
google-genai
|
||||
tiktoken
|
||||
];
|
||||
pdf = [
|
||||
pymupdf
|
||||
ebooklib
|
||||
];
|
||||
scite = [
|
||||
requests
|
||||
];
|
||||
};
|
||||
# `zotero_get_pdf_outline` reads a PDF's table of contents in a throwaway
|
||||
# `sys.executable -c ...` child (tools/write.py, _extract_pdf_toc). Under the
|
||||
# Nix wrapper this package's dependencies are injected into the *parent*
|
||||
# interpreter via site.addsitedir, never exported as PYTHONPATH, so the child
|
||||
# gets a bare sys.path and its `import pymupdf` fails. The tool then reports
|
||||
# "PyMuPDF (fitz) is required..." even though PyMuPDF is a build input.
|
||||
# Forward the parent's sys.path so the child resolves the same modules.
|
||||
# Mirrors nixpkgs' python-modules/vllm/0003-propagate-pythonpath.patch.
|
||||
postPatch = ''
|
||||
substituteInPlace src/zotero_mcp/tools/write.py \
|
||||
--replace-fail \
|
||||
'child_env.setdefault("PYTHONUTF8", "1")' \
|
||||
'child_env.setdefault("PYTHONUTF8", "1"); child_env["PYTHONPATH"] = os.pathsep.join(p for p in sys.path if p)'
|
||||
'';
|
||||
|
||||
pythonImportsCheck = [ "zotero_mcp" ];
|
||||
|
||||
|
||||
Reference in new issue
Block a user