Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b55c7af097 | ||
|
|
e9dc6f91b7 | ||
|
|
e797c592cd | ||
|
|
e9946694e6 | ||
|
|
4709b47c1e | ||
|
|
80d0352d1e | ||
|
|
e4e6d083e5 | ||
|
|
ceaaec2068 | ||
|
|
560d2d102f | ||
|
|
81c7f99174 | ||
|
|
d7c952cf4d | ||
|
|
810820658a | ||
|
|
42206f8328 | ||
|
|
a525c2a1d0 | ||
|
|
bd75822e2d | ||
|
|
23da3cf87f | ||
|
|
55fdf722e4 | ||
|
|
67c7e829f3 | ||
|
|
66e3a17aa1 | ||
|
|
058a009f65 | ||
|
|
bc7e34472e | ||
|
|
30717f8021 | ||
|
|
f23109857b | ||
|
|
471a7b4fbf | ||
|
|
eab27798b2 | ||
|
|
b732981049 | ||
|
|
32dd8e8d2f | ||
|
|
58eb312a77 | ||
|
|
9e26156949 | ||
|
|
279b4e5caf | ||
|
|
0d0ca4aa8e | ||
|
|
de417511c7 | ||
|
|
1a06666b77 | ||
|
|
e9c4ae7273 | ||
|
|
9cf77387fd | ||
|
|
e2b5eac582 | ||
|
|
98ba683632 | ||
|
|
e5db294f18 | ||
|
|
9bcb95b262 | ||
|
|
017683f9e2 | ||
|
|
377d7534ff | ||
|
|
fcd3f6d0f0 | ||
|
|
b4b26c6ef2 | ||
|
|
80035de8f3 | ||
|
|
fd19b3519f | ||
|
|
633dc74563 | ||
|
|
483e87b0a8 | ||
|
|
9383fe1d8d | ||
|
|
2d7dedd1a2 | ||
|
|
7af3d89e5d | ||
|
|
84bb4f4425 | ||
|
|
9f9001299c | ||
|
|
8dc25624ff | ||
|
|
d276fe1ef0 | ||
|
|
676d06f8ca | ||
|
|
4856b98429 | ||
|
|
d009717854 | ||
|
|
617bb9cab5 | ||
|
|
15906893c0 |
No files matched your search
@@ -27,3 +27,4 @@ result-*
|
|||||||
# Local environment overrides
|
# Local environment overrides
|
||||||
.envrc.local
|
.envrc.local
|
||||||
/.dir-locals.el
|
/.dir-locals.el
|
||||||
|
__pycache__/
|
||||||
@@ -43,6 +43,14 @@ packages/
|
|||||||
overlays/
|
overlays/
|
||||||
└── default.nix # Nix overlay that exports all MCP packages
|
└── default.nix # Nix overlay that exports all MCP packages
|
||||||
|
|
||||||
|
modules/
|
||||||
|
├── mcp-gateway.nix # Home Manager module: services.mcp-gateway
|
||||||
|
└── ssh-mcp.nix # Home Manager module: services.ssh-mcp
|
||||||
|
|
||||||
|
docs/
|
||||||
|
├── mcp-gateway.md # Gateway module usage
|
||||||
|
└── ssh-mcp.md # ssh-mcp module usage
|
||||||
|
|
||||||
flake.nix # Main flake definition
|
flake.nix # Main flake definition
|
||||||
flake.lock # Locked dependency versions
|
flake.lock # Locked dependency versions
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ This repository provides Nix derivations for MCP (Model Context Protocol) server
|
|||||||
| Package | Description | Language |
|
| Package | Description | Language |
|
||||||
| -------------------------------- | -------------------------------------------------------------------- | ---------- |
|
| -------------------------------- | -------------------------------------------------------------------- | ---------- |
|
||||||
| academic-search-mcp-server | Search academic papers via Semantic Scholar and Crossref | Python |
|
| 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 |
|
| applemusic-mcp | Apple Music — manage playlists, search catalog, browse library | Python |
|
||||||
| duckduckgo-mcp-server | Web search and content fetching via DuckDuckGo | Python |
|
| duckduckgo-mcp-server | Web search and content fetching via DuckDuckGo | Python |
|
||||||
| jellyfin-mcp | MCP server for Jellyfin media server | 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 |
|
| 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 |
|
| manim-mcp-server | Execute Manim animation code and return videos | Python |
|
||||||
| mcp-arr-server | MCP server for *arr suite — Sonarr, Radarr, Lidarr, Prowlarr | TypeScript |
|
| mcp-arr-server | MCP server for *arr suite — Sonarr, Radarr, Lidarr, Prowlarr | TypeScript |
|
||||||
@@ -22,12 +22,38 @@ This repository provides Nix derivations for MCP (Model Context Protocol) server
|
|||||||
| pdf-reader-mcp | Production‑ready PDF processing with parallel processing | TypeScript |
|
| pdf-reader-mcp | Production‑ready PDF processing with parallel processing | TypeScript |
|
||||||
| rust-research-mcp | Accumulate and organize academic knowledge | Rust |
|
| rust-research-mcp | Accumulate and organize academic knowledge | Rust |
|
||||||
| simple-web-search-mcp | Zero-config web search and page reading for LLMs | TypeScript |
|
| 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 |
|
| transmission-mcp | MCP server for the Transmission BitTorrent client | Python |
|
||||||
| sabnzbd-mcp | MCP server for SABnzbd download management | Python |
|
| sabnzbd-mcp | MCP server for SABnzbd download management | Python |
|
||||||
| jellyfin-mcp | MCP server for Jellyfin — search, playback, metadata, admin | Go |
|
| jellyfin-mcp | MCP server for Jellyfin — search, playback, metadata, admin | Go |
|
||||||
| zotero-mcp | Connect Zotero research library with AI assistants | Python |
|
| zotero-mcp | Connect Zotero research library with AI assistants | Python |
|
||||||
|
| 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 |
|
||||||
|
| navidrome-mcp | Navidrome music library search, playlists, radio, playback | TypeScript |
|
||||||
|
| ovhcloud-mcp | OVHcloud VPS, domains, DNS, billing, SSH, API explorer | TypeScript |
|
||||||
|
| slskd-mcp | slskd (Soulseek) search, download, peer browsing, private messages | Python |
|
||||||
| mcp-score | AI-driven music score generation and MuseScore live control | Python |
|
| mcp-score | AI-driven music score generation and MuseScore live control | Python |
|
||||||
|
|
||||||
|
## Home Manager Modules
|
||||||
|
|
||||||
|
The flake also exports reusable Home Manager modules:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
mcp-servers.homeManagerModules.mcp-gateway
|
||||||
|
mcp-servers.homeManagerModules.ssh-mcp
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
- `services.mcp-gateway` renders `gateway.yaml` from Nix options.
|
||||||
|
- `services.ssh-mcp.instances.<name>` renders per-instance `hosts.toml` and
|
||||||
|
`known_hosts` files and exposes a `gatewayBackend` helper.
|
||||||
|
|
||||||
|
See [docs/mcp-gateway.md](docs/mcp-gateway.md) and
|
||||||
|
[docs/ssh-mcp.md](docs/ssh-mcp.md).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Using the Overlay (Recommended)
|
### Using the Overlay (Recommended)
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# MCP Gateway Home Manager Module
|
||||||
|
|
||||||
|
`homeManagerModules.mcp-gateway` exposes `services.mcp-gateway` and renders
|
||||||
|
`~/.config/mcp-gateway/gateway.yaml` from Nix options.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
```nix
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
mcp-servers.homeManagerModules.mcp-gateway
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Minimal example
|
||||||
|
|
||||||
|
```nix
|
||||||
|
services.mcp-gateway = {
|
||||||
|
enable = true;
|
||||||
|
server = {
|
||||||
|
host = "100.64.0.19";
|
||||||
|
port = 39401;
|
||||||
|
};
|
||||||
|
|
||||||
|
environmentFile = "/run/secrets/rendered/mcp_gateway_env";
|
||||||
|
|
||||||
|
auth.apiKeys.dev = {
|
||||||
|
key = "env:DEV_GATEWAY_KEY";
|
||||||
|
backends = [ "nixos" "fetch" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
backends = {
|
||||||
|
nixos.command = "${pkgs.mcp-nixos}/bin/mcp-nixos";
|
||||||
|
fetch.command = "${pkgs.mcp-server-fetch}/bin/mcp-server-fetch";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`services.mcp-gateway.url` is read-only and resolves to
|
||||||
|
`http://<server.host>:<server.port>/mcp`, for use by MCP clients.
|
||||||
|
|
||||||
|
## Backends
|
||||||
|
|
||||||
|
Backends are keyed by name and support:
|
||||||
|
|
||||||
|
- `command` for stdio backends, or `httpUrl` for HTTP backends
|
||||||
|
- `cwd`, `timeout`, `env`, `headers`
|
||||||
|
- `passthrough`, `allowCleartextCredentials`
|
||||||
|
- `resolveEnv = true` to wrap commands that read literal `${env.VAR}`
|
||||||
|
placeholders
|
||||||
|
- `extraConfig` for fields not modelled by the module
|
||||||
|
|
||||||
|
## API keys
|
||||||
|
|
||||||
|
API keys are keyed by client name. The key may use the gateway's `env:VAR`,
|
||||||
|
`keychain:NAME`, or `oauth:PROVIDER` indirection. Avoid literal secrets in the
|
||||||
|
Nix store.
|
||||||
|
|
||||||
|
## Routing profiles
|
||||||
|
|
||||||
|
`routingProfiles.<name>` supports `allowBackends`, `denyBackends`,
|
||||||
|
`allowTools`, and `denyTools`. Set `defaultRoutingProfile` to the profile used
|
||||||
|
when a session has not selected one.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# ssh-mcp Home Manager Module
|
||||||
|
|
||||||
|
`homeManagerModules.ssh-mcp` exposes `services.ssh-mcp.instances.<name>`.
|
||||||
|
Each instance renders its own SSH host policy and known_hosts file under
|
||||||
|
`~/.config/ssh-mcp/<instance>/`.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
```nix
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
mcp-servers.homeManagerModules.ssh-mcp
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```nix
|
||||||
|
services.ssh-mcp.instances.admin = {
|
||||||
|
enable = true;
|
||||||
|
description = "Read-only systemd/journalctl over SSH";
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
user = "ai-agent";
|
||||||
|
port = 22;
|
||||||
|
platform = "linux";
|
||||||
|
persistentSession = false;
|
||||||
|
auth = {
|
||||||
|
method = "key";
|
||||||
|
key = "/run/secrets/ssh_ai_agent_key";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
hosts = {
|
||||||
|
perlis.hostname = "100.64.0.4";
|
||||||
|
ravel.hostname = "100.64.0.19";
|
||||||
|
};
|
||||||
|
|
||||||
|
knownHosts = {
|
||||||
|
"100.64.0.4" = "ssh-ed25519 AAAA...";
|
||||||
|
"100.64.0.19" = "ssh-ed25519 AAAA...";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
enabledGroups = [ "host" "systemctl" ];
|
||||||
|
allowLowAccessTools = false;
|
||||||
|
allowDangerousTools = false;
|
||||||
|
allowSudo = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gateway integration
|
||||||
|
|
||||||
|
Each instance exposes `gatewayBackend`, a plain attrset shaped like a
|
||||||
|
`services.mcp-gateway.backends.<name>` entry:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
services.mcp-gateway.backends = {
|
||||||
|
ssh-mcp = config.services.ssh-mcp.instances.admin.gatewayBackend;
|
||||||
|
ssh-mcp-media = config.services.ssh-mcp.instances.media.gatewayBackend;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Read-only paths are also available:
|
||||||
|
|
||||||
|
- `config.services.ssh-mcp.instances.<name>.hostsFile`
|
||||||
|
- `config.services.ssh-mcp.instances.<name>.knownHostsFile`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The default `package` is `pkgs.python-ssh-mcp`, so apply
|
||||||
|
`mcp-servers.overlays.default` on the host or set `package` explicitly.
|
||||||
|
- `SSH_ENABLED_GROUPS` is server-wide, not per-host. Run separate instances
|
||||||
|
when different catalogs are needed.
|
||||||
|
- Tier flags (`allowLowAccessTools`, `allowDangerousTools`, `allowSudo`) are
|
||||||
|
hard security gates. Group filters only change what is advertised.
|
||||||
|
- Empty path or command allowlists mean denied, not unrestricted.
|
||||||
@@ -24,9 +24,23 @@
|
|||||||
"aarch64-darwin"
|
"aarch64-darwin"
|
||||||
];
|
];
|
||||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
||||||
|
|
||||||
|
mcpGatewayModule = import ./modules/mcp-gateway.nix;
|
||||||
|
sshMcpModule = import ./modules/ssh-mcp.nix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
overlays.default = import ./overlays/default.nix;
|
overlays.default = import ./overlays/default.nix;
|
||||||
|
|
||||||
|
homeManagerModules = {
|
||||||
|
mcp-gateway = mcpGatewayModule;
|
||||||
|
ssh-mcp = sshMcpModule;
|
||||||
|
default = {
|
||||||
|
imports = [
|
||||||
|
mcpGatewayModule
|
||||||
|
sshMcpModule
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// {
|
// {
|
||||||
packages = forAllSystems (
|
packages = forAllSystems (
|
||||||
@@ -60,6 +74,7 @@
|
|||||||
packages = builtins.attrValues mcpPackages ++ [ pkgs.mcp-nixos pkgs.git ];
|
packages = builtins.attrValues mcpPackages ++ [ pkgs.mcp-nixos pkgs.git ];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export SEARXNG_BASE_URL="https://search.bueso.eu"
|
export SEARXNG_BASE_URL="https://search.bueso.eu"
|
||||||
|
export SEARXNG_URL="https://search.bueso.eu"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,606 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkEnableOption
|
||||||
|
mkOption
|
||||||
|
mkIf
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
cfg = config.services.mcp-gateway;
|
||||||
|
|
||||||
|
# Gateway configuration is YAML. `pkgs.formats.yaml` builds it via
|
||||||
|
# structured attrs, which also drops null-valued attributes, so unset
|
||||||
|
# optional fields simply disappear from the rendered file.
|
||||||
|
yaml = pkgs.formats.yaml { };
|
||||||
|
|
||||||
|
# The gateway passes `${env.VAR}` placeholders VERBATIM to every backend
|
||||||
|
# subprocess -- it does NOT resolve them. Some MCP servers resolve the
|
||||||
|
# placeholder themselves; others (yarr, slskd-mcp, nextcloud-mcp-server,
|
||||||
|
# gitea-mcp, ovhcloud-mcp, pinchflat-mcp) read the literal `${env.VAR}`
|
||||||
|
# string as the credential and fail auth. This wrapper scans every env var
|
||||||
|
# and resolves any literal `${env.X}` placeholder from the rendered gateway
|
||||||
|
# environment file before exec'ing the real binary.
|
||||||
|
resolveEnv = pkgs.writeShellScript "mcp-gateway-resolve-env" ''
|
||||||
|
env_file=${lib.escapeShellArg (if cfg.environmentFile == null then "" else cfg.environmentFile)}
|
||||||
|
for line in $(env | cut -d= -f1); do
|
||||||
|
val="$(printenv "$line")"
|
||||||
|
if [[ "$val" == \$\{env.* ]]; then
|
||||||
|
name="''${val#\$\{env.}"
|
||||||
|
name="''${name%\}}"
|
||||||
|
export "$line"="$(grep "^$name=" "$env_file" | head -1 | cut -d= -f2-)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exec "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
apiKeyType = types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
key = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
API key value. Prefer an indirection such as `env:VAR_NAME`,
|
||||||
|
`keychain:NAME`, or `oauth:PROVIDER` instead of a literal secret.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
description = "Human-readable client name. Defaults to the attribute name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
rateLimit = mkOption {
|
||||||
|
type = types.ints.unsigned;
|
||||||
|
default = 0;
|
||||||
|
description = "Requests per minute. 0 means unlimited.";
|
||||||
|
};
|
||||||
|
|
||||||
|
backends = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Backends this key may use. Empty means every backend.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowedTools = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "If set, only these tools (glob patterns allowed) are accessible.";
|
||||||
|
};
|
||||||
|
|
||||||
|
deniedTools = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "Tools (glob patterns allowed) blocked for this key.";
|
||||||
|
};
|
||||||
|
|
||||||
|
admin = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether this key may use admin-only management tools.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
routingProfileType = types.submodule {
|
||||||
|
options = {
|
||||||
|
description = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Human-readable profile description.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowBackends = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "If set, only these backends are reachable in this profile.";
|
||||||
|
};
|
||||||
|
|
||||||
|
denyBackends = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "Backends denied in this profile.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowTools = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "If set, only these tools (glob patterns allowed) are reachable.";
|
||||||
|
};
|
||||||
|
|
||||||
|
denyTools = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "Tools (glob patterns allowed) denied in this profile.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
surfacedToolType = types.submodule {
|
||||||
|
options = {
|
||||||
|
server = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Backend name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
tool = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Tool name to surface directly in tools/list.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
backendType = types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
description = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Human-readable backend description.";
|
||||||
|
};
|
||||||
|
|
||||||
|
enabled = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether the backend is enabled.";
|
||||||
|
};
|
||||||
|
|
||||||
|
command = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Shell command for a stdio backend. Mutually exclusive with
|
||||||
|
`httpUrl`. Set `resolveEnv = true` if the backend reads literal
|
||||||
|
`''${env.VAR}` placeholders.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
cwd = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Working directory for a stdio backend.";
|
||||||
|
};
|
||||||
|
|
||||||
|
httpUrl = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "URL for an HTTP backend. Mutually exclusive with `command`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
streamableHttp = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Use Streamable HTTP instead of the SSE handshake for `httpUrl`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
timeout = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "30s";
|
||||||
|
description = "Per-backend request timeout, in humantime syntax.";
|
||||||
|
};
|
||||||
|
|
||||||
|
env = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = { };
|
||||||
|
description = "Environment variables passed to the backend process.";
|
||||||
|
};
|
||||||
|
|
||||||
|
headers = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = { };
|
||||||
|
description = "HTTP headers for an HTTP backend.";
|
||||||
|
};
|
||||||
|
|
||||||
|
passthrough = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Skip input sanitization on this backend's tools/call requests.
|
||||||
|
Only use for fully trusted internal backends.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
allowCleartextCredentials = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Permit credentials over cleartext HTTP to a non-loopback host.
|
||||||
|
This is a security exception, not a convenience toggle.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
resolveEnv = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Prefix `command` with the module's env-placeholder resolver. This
|
||||||
|
makes backends that read literal `''${env.VAR}` strings resolve
|
||||||
|
them from `environmentFile` before starting.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
default = { };
|
||||||
|
description = "Escape hatch merged into this backend's YAML map.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Render an environment-like value the way gateway.yaml expects it:
|
||||||
|
# booleans as true/false, lists as comma-separated strings.
|
||||||
|
renderEnvValue =
|
||||||
|
value:
|
||||||
|
if builtins.isBool value then
|
||||||
|
(if value then "true" else "false")
|
||||||
|
else if builtins.isList value then
|
||||||
|
lib.concatStringsSep "," value
|
||||||
|
else
|
||||||
|
toString value;
|
||||||
|
|
||||||
|
apiSettings = {
|
||||||
|
enabled = cfg.auth.enable;
|
||||||
|
bearer_token = cfg.auth.bearerToken;
|
||||||
|
public_paths = cfg.auth.publicPaths;
|
||||||
|
single_user = cfg.auth.singleUser;
|
||||||
|
api_keys = lib.mapAttrsToList (_name: key: {
|
||||||
|
inherit (key) key name backends admin;
|
||||||
|
rate_limit = key.rateLimit;
|
||||||
|
allowed_tools = key.allowedTools;
|
||||||
|
denied_tools = key.deniedTools;
|
||||||
|
}) cfg.auth.apiKeys;
|
||||||
|
};
|
||||||
|
|
||||||
|
metaMcpSettings = {
|
||||||
|
enabled = cfg.metaMcp.enable;
|
||||||
|
cache_tools = cfg.metaMcp.cacheTools;
|
||||||
|
cache_ttl = cfg.metaMcp.cacheTtl;
|
||||||
|
prompts_resources_fetch_timeout = cfg.metaMcp.promptsResourcesFetchTimeout;
|
||||||
|
warm_start = cfg.metaMcp.warmStart;
|
||||||
|
surfaced_tools = cfg.metaMcp.surfacedTools;
|
||||||
|
projection_mode = cfg.metaMcp.projectionMode;
|
||||||
|
exposed_meta_tools = cfg.metaMcp.exposedMetaTools;
|
||||||
|
expose_stats_tool = cfg.metaMcp.exposeStatsTool;
|
||||||
|
};
|
||||||
|
|
||||||
|
routingSettings = lib.mapAttrs (_name: profile: {
|
||||||
|
inherit (profile) description;
|
||||||
|
allow_backends = profile.allowBackends;
|
||||||
|
deny_backends = profile.denyBackends;
|
||||||
|
allow_tools = profile.allowTools;
|
||||||
|
deny_tools = profile.denyTools;
|
||||||
|
}) cfg.routingProfiles;
|
||||||
|
|
||||||
|
backendSettings = lib.mapAttrs (
|
||||||
|
name: backend:
|
||||||
|
{
|
||||||
|
inherit (backend) description enabled timeout env headers;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (backend.command != null) {
|
||||||
|
command =
|
||||||
|
if backend.resolveEnv then
|
||||||
|
"${resolveEnv} ${backend.command}"
|
||||||
|
else
|
||||||
|
backend.command;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (backend.cwd != null) {
|
||||||
|
inherit (backend) cwd;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (backend.httpUrl != null) {
|
||||||
|
http_url = backend.httpUrl;
|
||||||
|
streamable_http = backend.streamableHttp;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs backend.passthrough {
|
||||||
|
passthrough = true;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs backend.allowCleartextCredentials {
|
||||||
|
allow_cleartext_credentials = true;
|
||||||
|
}
|
||||||
|
// backend.extraConfig
|
||||||
|
) cfg.backends;
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
server = {
|
||||||
|
inherit (cfg.server) host port;
|
||||||
|
modern_protocol = cfg.server.modernProtocol;
|
||||||
|
request_timeout = cfg.server.requestTimeout;
|
||||||
|
shutdown_timeout = cfg.server.shutdownTimeout;
|
||||||
|
max_body_size = cfg.server.maxBodySize;
|
||||||
|
public_url = cfg.server.publicUrl;
|
||||||
|
ws_port = cfg.server.wsPort;
|
||||||
|
allow_unauthenticated_network_bind = cfg.server.allowUnauthenticatedNetworkBind;
|
||||||
|
};
|
||||||
|
auth = apiSettings;
|
||||||
|
meta_mcp = metaMcpSettings;
|
||||||
|
default_routing_profile = cfg.defaultRoutingProfile;
|
||||||
|
routing_profiles = routingSettings;
|
||||||
|
backends = backendSettings;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (cfg.envFiles != [ ]) {
|
||||||
|
env_files = cfg.envFiles;
|
||||||
|
}
|
||||||
|
// cfg.extraConfig;
|
||||||
|
|
||||||
|
referencedBackends =
|
||||||
|
lib.concatMap (key: key.backends) (lib.attrValues cfg.auth.apiKeys)
|
||||||
|
++ lib.concatMap (
|
||||||
|
profile:
|
||||||
|
lib.optionals (profile.allowBackends != null) profile.allowBackends
|
||||||
|
++ lib.optionals (profile.denyBackends != null) profile.denyBackends
|
||||||
|
) (lib.attrValues cfg.routingProfiles)
|
||||||
|
++ cfg.metaMcp.warmStart
|
||||||
|
++ map (tool: tool.server) cfg.metaMcp.surfacedTools;
|
||||||
|
|
||||||
|
missingBackends = lib.filter (name: !(builtins.hasAttr name cfg.backends)) referencedBackends;
|
||||||
|
|
||||||
|
invalidBackends = lib.filter (
|
||||||
|
backend: (backend.command == null) == (backend.httpUrl == null)
|
||||||
|
) (lib.attrValues cfg.backends);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.mcp-gateway = {
|
||||||
|
enable = mkEnableOption "the local MCP gateway";
|
||||||
|
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.mcp-gateway;
|
||||||
|
description = "MCP gateway package.";
|
||||||
|
};
|
||||||
|
|
||||||
|
environmentFile = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Optional systemd EnvironmentFile containing backend credentials and
|
||||||
|
`env:` API keys. Also the file the `resolveEnv` wrapper consults.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
environment = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = { };
|
||||||
|
description = "Extra environment variables for the gateway process.";
|
||||||
|
};
|
||||||
|
|
||||||
|
envFiles = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Extra env files loaded by the gateway itself (gateway `env_files`).";
|
||||||
|
};
|
||||||
|
|
||||||
|
server = {
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = ''
|
||||||
|
Address to bind to. Use this host's Tailscale address where remote
|
||||||
|
clients must reach it. Never use 0.0.0.0 on a host with a public
|
||||||
|
interface.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 39401;
|
||||||
|
description = "TCP port to listen on.";
|
||||||
|
};
|
||||||
|
|
||||||
|
modernProtocol = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Serve the newest MCP protocol revision and negotiate down.";
|
||||||
|
};
|
||||||
|
|
||||||
|
requestTimeout = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "30s";
|
||||||
|
description = "Request timeout in humantime syntax.";
|
||||||
|
};
|
||||||
|
|
||||||
|
shutdownTimeout = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "30s";
|
||||||
|
description = "Graceful shutdown timeout in humantime syntax.";
|
||||||
|
};
|
||||||
|
|
||||||
|
maxBodySize = mkOption {
|
||||||
|
type = types.ints.positive;
|
||||||
|
default = 10485760;
|
||||||
|
description = "Maximum request body size in bytes.";
|
||||||
|
};
|
||||||
|
|
||||||
|
publicUrl = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Externally reachable base URL when running behind a proxy.";
|
||||||
|
};
|
||||||
|
|
||||||
|
wsPort = mkOption {
|
||||||
|
type = types.nullOr types.port;
|
||||||
|
default = null;
|
||||||
|
description = "Optional WebSocket listener port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowUnauthenticatedNetworkBind = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Permit an unauthenticated bind on a non-loopback address. Only set
|
||||||
|
when authentication terminates in front of the gateway.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
auth = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Enable API-key authentication.";
|
||||||
|
};
|
||||||
|
|
||||||
|
bearerToken = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Optional single bearer token in addition to API keys.";
|
||||||
|
};
|
||||||
|
|
||||||
|
publicPaths = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Paths allowed without authentication.";
|
||||||
|
};
|
||||||
|
|
||||||
|
singleUser = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Treat all callers as a single user.";
|
||||||
|
};
|
||||||
|
|
||||||
|
apiKeys = mkOption {
|
||||||
|
type = types.attrsOf apiKeyType;
|
||||||
|
default = { };
|
||||||
|
description = "API keys keyed by client name.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
metaMcp = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Expose the compact meta-MCP surface.";
|
||||||
|
};
|
||||||
|
|
||||||
|
cacheTools = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Cache backend tool lists.";
|
||||||
|
};
|
||||||
|
|
||||||
|
cacheTtl = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "5m";
|
||||||
|
description = "Tool cache TTL in humantime syntax.";
|
||||||
|
};
|
||||||
|
|
||||||
|
promptsResourcesFetchTimeout = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "10s";
|
||||||
|
description = "Per-backend bound for prompts/resources aggregation.";
|
||||||
|
};
|
||||||
|
|
||||||
|
warmStart = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Backends to warm-start on gateway startup.";
|
||||||
|
};
|
||||||
|
|
||||||
|
surfacedTools = mkOption {
|
||||||
|
type = types.listOf surfacedToolType;
|
||||||
|
default = [ ];
|
||||||
|
description = "Backend tools to expose directly alongside the meta-tools.";
|
||||||
|
};
|
||||||
|
|
||||||
|
projectionMode = mkOption {
|
||||||
|
type = types.enum [ "off" "on" "experimental" ];
|
||||||
|
default = "off";
|
||||||
|
description = "Canonical response-projection rollout mode.";
|
||||||
|
};
|
||||||
|
|
||||||
|
exposedMetaTools = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "If non-empty, only these meta-tools are exposed.";
|
||||||
|
};
|
||||||
|
|
||||||
|
exposeStatsTool = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "List `gateway_get_stats` in tools/list.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultRoutingProfile = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "default";
|
||||||
|
description = "Routing profile used when a session does not select one.";
|
||||||
|
};
|
||||||
|
|
||||||
|
routingProfiles = mkOption {
|
||||||
|
type = types.attrsOf routingProfileType;
|
||||||
|
default = { };
|
||||||
|
description = "Session-scoped tool/backend access profiles.";
|
||||||
|
};
|
||||||
|
|
||||||
|
backends = mkOption {
|
||||||
|
type = types.attrsOf backendType;
|
||||||
|
default = { };
|
||||||
|
description = "MCP backends keyed by backend name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
default = { };
|
||||||
|
description = "Escape hatch merged over the generated top-level YAML.";
|
||||||
|
};
|
||||||
|
|
||||||
|
url = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "http://${cfg.server.host}:${toString cfg.server.port}/mcp";
|
||||||
|
description = "MCP endpoint URL derived from `server.host` and `server.port`.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = missingBackends == [ ];
|
||||||
|
message = "services.mcp-gateway references unknown backends: ${lib.concatStringsSep ", " missingBackends}";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = invalidBackends == [ ];
|
||||||
|
message = "services.mcp-gateway backends must set exactly one of `command` or `httpUrl`";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion =
|
||||||
|
cfg.environmentFile != null
|
||||||
|
|| !(lib.any ( backend: backend.resolveEnv ) (lib.attrValues cfg.backends));
|
||||||
|
message = "services.mcp-gateway: `resolveEnv = true` requires `environmentFile`";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
xdg.configFile."mcp-gateway/gateway.yaml".source =
|
||||||
|
yaml.generate "gateway.yaml" settings;
|
||||||
|
|
||||||
|
systemd.user.services.mcp-gateway = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Local MCP gateway";
|
||||||
|
After = [ "network.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${cfg.package}/bin/mcp-gateway serve --config ${config.xdg.configHome}/mcp-gateway/gateway.yaml";
|
||||||
|
EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
|
||||||
|
Environment = [
|
||||||
|
"NIX_SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
|
||||||
|
"HOME=%h"
|
||||||
|
]
|
||||||
|
++ lib.mapAttrsToList (name: value: "${name}=${value}") cfg.environment;
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 5;
|
||||||
|
};
|
||||||
|
|
||||||
|
Install.WantedBy = [ "default.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,521 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkEnableOption
|
||||||
|
mkOption
|
||||||
|
mkIf
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
cfg = config.services.ssh-mcp;
|
||||||
|
hostConfig = config;
|
||||||
|
|
||||||
|
toml = pkgs.formats.toml { };
|
||||||
|
|
||||||
|
renderEnvValue =
|
||||||
|
value:
|
||||||
|
if builtins.isBool value then
|
||||||
|
(if value then "true" else "false")
|
||||||
|
else if builtins.isList value then
|
||||||
|
lib.concatStringsSep "," value
|
||||||
|
else
|
||||||
|
toString value;
|
||||||
|
|
||||||
|
authType = types.submodule {
|
||||||
|
options = {
|
||||||
|
method = mkOption {
|
||||||
|
type = types.enum [ "agent" "key" "password" ];
|
||||||
|
default = "agent";
|
||||||
|
description = "SSH authentication method.";
|
||||||
|
};
|
||||||
|
|
||||||
|
key = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Private key path for `method = \"key\"`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
identityFingerprint = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Pin one agent identity by SHA-256 fingerprint.";
|
||||||
|
};
|
||||||
|
|
||||||
|
identitiesOnly = mkOption {
|
||||||
|
type = types.nullOr types.bool;
|
||||||
|
default = null;
|
||||||
|
description = "Offer only the pinned identity to the server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
identityAgent = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "SSH agent socket to use instead of SSH_AUTH_SOCK.";
|
||||||
|
};
|
||||||
|
|
||||||
|
passphraseCmd = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Local command that prints the key passphrase.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
default = { };
|
||||||
|
description = "Escape hatch merged into this auth table.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultsType = types.submodule {
|
||||||
|
options = {
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "root";
|
||||||
|
description = "Default SSH user.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 22;
|
||||||
|
description = "Default SSH port.";
|
||||||
|
};
|
||||||
|
|
||||||
|
platform = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "linux";
|
||||||
|
description = "Default target platform.";
|
||||||
|
};
|
||||||
|
|
||||||
|
persistentSession = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Allow persistent shell sessions by default.";
|
||||||
|
};
|
||||||
|
|
||||||
|
pathAllowlist = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Default path roots. Empty means no paths are allowed.";
|
||||||
|
};
|
||||||
|
|
||||||
|
restrictedPaths = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Default paths carved out of the allowlist.";
|
||||||
|
};
|
||||||
|
|
||||||
|
commandAllowlist = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Default exec command allowlist.";
|
||||||
|
};
|
||||||
|
|
||||||
|
auth = mkOption {
|
||||||
|
type = authType;
|
||||||
|
default = { };
|
||||||
|
description = "Default SSH authentication policy.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
default = { };
|
||||||
|
description = "Escape hatch merged into the `[defaults]` table.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
hostType = types.submodule {
|
||||||
|
options = {
|
||||||
|
hostname = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Canonical hostname or address used on the wire.";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Per-host SSH user override.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.nullOr types.port;
|
||||||
|
default = null;
|
||||||
|
description = "Per-host SSH port override.";
|
||||||
|
};
|
||||||
|
|
||||||
|
platform = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Per-host platform override.";
|
||||||
|
};
|
||||||
|
|
||||||
|
persistentSession = mkOption {
|
||||||
|
type = types.nullOr types.bool;
|
||||||
|
default = null;
|
||||||
|
description = "Per-host persistent-session override.";
|
||||||
|
};
|
||||||
|
|
||||||
|
pathAllowlist = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "Per-host path roots.";
|
||||||
|
};
|
||||||
|
|
||||||
|
restrictedPaths = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "Per-host paths carved out of the allowlist.";
|
||||||
|
};
|
||||||
|
|
||||||
|
commandAllowlist = mkOption {
|
||||||
|
type = types.nullOr (types.listOf types.str);
|
||||||
|
default = null;
|
||||||
|
description = "Per-host exec command allowlist.";
|
||||||
|
};
|
||||||
|
|
||||||
|
auth = mkOption {
|
||||||
|
type = types.nullOr authType;
|
||||||
|
default = null;
|
||||||
|
description = "Per-host authentication policy override.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
default = { };
|
||||||
|
description = "Escape hatch merged into this host's table.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
settingsType = types.submodule {
|
||||||
|
options = {
|
||||||
|
enabledGroups = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Tool groups to expose. Empty exposes all groups.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hostsAllowlist = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Additional hostnames allowed by the server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hostsBlocklist = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Hostnames denied by the server. Deny wins.";
|
||||||
|
};
|
||||||
|
|
||||||
|
pathAllowlist = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Server-wide path allowlist.";
|
||||||
|
};
|
||||||
|
|
||||||
|
restrictedPaths = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Server-wide restricted paths.";
|
||||||
|
};
|
||||||
|
|
||||||
|
commandAllowlist = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Server-wide exec command allowlist.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowLowAccessTools = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable SFTP-mediated file mutation tools.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowDangerousTools = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable arbitrary command execution tools.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowSudo = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable sudo-tier tools. Also requires dangerous tools.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowAnyCommand = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Permit arbitrary commands when no command allowlist is set.";
|
||||||
|
};
|
||||||
|
|
||||||
|
allowPasswordAuth = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Permit password authentication.";
|
||||||
|
};
|
||||||
|
|
||||||
|
pingIncludesNotes = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Include operator notes in host ping results.";
|
||||||
|
};
|
||||||
|
|
||||||
|
pingIncludesAgentNotes = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Include agent notes in host ping results.";
|
||||||
|
};
|
||||||
|
|
||||||
|
otelEnabled = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable OpenTelemetry export.";
|
||||||
|
};
|
||||||
|
|
||||||
|
localTransferRoots = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Local paths the server may read for uploads and transfers.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dockerCmd = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Docker-compatible CLI to use, e.g. `podman`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
configFile = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Optional OpenSSH config file to fill in unset host fields.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraEnv = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = { };
|
||||||
|
description = "Extra environment variables for the ssh-mcp process.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
renderAuth =
|
||||||
|
auth:
|
||||||
|
{ inherit (auth) method; }
|
||||||
|
// lib.optionalAttrs (auth.key != null) { inherit (auth) key; }
|
||||||
|
// lib.optionalAttrs (auth.identityFingerprint != null) {
|
||||||
|
identity_fingerprint = auth.identityFingerprint;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (auth.identitiesOnly != null) {
|
||||||
|
identities_only = auth.identitiesOnly;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (auth.identityAgent != null) {
|
||||||
|
identity_agent = auth.identityAgent;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (auth.passphraseCmd != null) {
|
||||||
|
passphrase_cmd = auth.passphraseCmd;
|
||||||
|
}
|
||||||
|
// auth.extraConfig;
|
||||||
|
|
||||||
|
renderDefaults =
|
||||||
|
defaults:
|
||||||
|
{
|
||||||
|
inherit (defaults) user port platform;
|
||||||
|
persistent_session = defaults.persistentSession;
|
||||||
|
path_allowlist = defaults.pathAllowlist;
|
||||||
|
restricted_paths = defaults.restrictedPaths;
|
||||||
|
command_allowlist = defaults.commandAllowlist;
|
||||||
|
auth = renderAuth defaults.auth;
|
||||||
|
}
|
||||||
|
// defaults.extraConfig;
|
||||||
|
|
||||||
|
renderHost =
|
||||||
|
host:
|
||||||
|
{ inherit (host) hostname; }
|
||||||
|
// lib.optionalAttrs (host.user != null) { inherit (host) user; }
|
||||||
|
// lib.optionalAttrs (host.port != null) { inherit (host) port; }
|
||||||
|
// lib.optionalAttrs (host.platform != null) { inherit (host) platform; }
|
||||||
|
// lib.optionalAttrs (host.persistentSession != null) {
|
||||||
|
persistent_session = host.persistentSession;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (host.pathAllowlist != null) {
|
||||||
|
path_allowlist = host.pathAllowlist;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (host.restrictedPaths != null) {
|
||||||
|
restricted_paths = host.restrictedPaths;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (host.commandAllowlist != null) {
|
||||||
|
command_allowlist = host.commandAllowlist;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (host.auth != null) {
|
||||||
|
auth = renderAuth host.auth;
|
||||||
|
}
|
||||||
|
// host.extraConfig;
|
||||||
|
|
||||||
|
renderInstanceSettings =
|
||||||
|
instance:
|
||||||
|
{
|
||||||
|
ALLOW_LOW_ACCESS_TOOLS = renderEnvValue instance.settings.allowLowAccessTools;
|
||||||
|
ALLOW_DANGEROUS_TOOLS = renderEnvValue instance.settings.allowDangerousTools;
|
||||||
|
ALLOW_SUDO = renderEnvValue instance.settings.allowSudo;
|
||||||
|
ALLOW_ANY_COMMAND = renderEnvValue instance.settings.allowAnyCommand;
|
||||||
|
ALLOW_PASSWORD_AUTH = renderEnvValue instance.settings.allowPasswordAuth;
|
||||||
|
SSH_PING_INCLUDES_NOTES = renderEnvValue instance.settings.pingIncludesNotes;
|
||||||
|
SSH_PING_INCLUDES_AGENT_NOTES = renderEnvValue instance.settings.pingIncludesAgentNotes;
|
||||||
|
OTEL_ENABLED = renderEnvValue instance.settings.otelEnabled;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.enabledGroups != [ ]) {
|
||||||
|
SSH_ENABLED_GROUPS = renderEnvValue instance.settings.enabledGroups;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.hostsAllowlist != [ ]) {
|
||||||
|
SSH_HOSTS_ALLOWLIST = renderEnvValue instance.settings.hostsAllowlist;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.hostsBlocklist != [ ]) {
|
||||||
|
SSH_HOSTS_BLOCKLIST = renderEnvValue instance.settings.hostsBlocklist;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.pathAllowlist != [ ]) {
|
||||||
|
SSH_PATH_ALLOWLIST = renderEnvValue instance.settings.pathAllowlist;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.restrictedPaths != [ ]) {
|
||||||
|
SSH_RESTRICTED_PATHS = renderEnvValue instance.settings.restrictedPaths;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.commandAllowlist != [ ]) {
|
||||||
|
SSH_COMMAND_ALLOWLIST = renderEnvValue instance.settings.commandAllowlist;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.localTransferRoots != [ ]) {
|
||||||
|
SSH_LOCAL_TRANSFER_ROOTS = renderEnvValue instance.settings.localTransferRoots;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.dockerCmd != null) {
|
||||||
|
SSH_DOCKER_CMD = instance.settings.dockerCmd;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (instance.settings.configFile != null) {
|
||||||
|
SSH_CONFIG_FILE = instance.settings.configFile;
|
||||||
|
}
|
||||||
|
// instance.settings.extraEnv;
|
||||||
|
|
||||||
|
instanceType = types.submodule (
|
||||||
|
{ name, config, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
enable = mkEnableOption "the ssh-mcp instance ${name}";
|
||||||
|
|
||||||
|
package = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.python-ssh-mcp;
|
||||||
|
description = "ssh-mcp package for this instance.";
|
||||||
|
};
|
||||||
|
|
||||||
|
description = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "ssh-mcp instance ${name}";
|
||||||
|
description = "Backend description used by `gatewayBackend`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hostsFileName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "hosts.toml";
|
||||||
|
description = "Generated hosts policy filename under `~/.config/ssh-mcp/${name}`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
knownHostsFileName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "known_hosts";
|
||||||
|
description = "Generated known_hosts filename under `~/.config/ssh-mcp/${name}`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
defaults = mkOption {
|
||||||
|
type = defaultsType;
|
||||||
|
default = { };
|
||||||
|
description = "Default host policy for this instance.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hosts = mkOption {
|
||||||
|
type = types.attrsOf hostType;
|
||||||
|
default = { };
|
||||||
|
description = "Hosts reachable through this instance, keyed by alias.";
|
||||||
|
};
|
||||||
|
|
||||||
|
knownHosts = mkOption {
|
||||||
|
type = types.attrsOf types.str;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Pinned host keys keyed by canonical hostname. Each value is the
|
||||||
|
key type and public key, e.g. `ssh-ed25519 AAAA...`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
type = settingsType;
|
||||||
|
default = { };
|
||||||
|
description = "ssh-mcp server-wide environment settings.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hostsFile = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "${hostConfig.xdg.configHome}/ssh-mcp/${name}/${config.hostsFileName}";
|
||||||
|
description = "Runtime path of the generated hosts policy.";
|
||||||
|
};
|
||||||
|
|
||||||
|
knownHostsFile = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
readOnly = true;
|
||||||
|
default = "${hostConfig.xdg.configHome}/ssh-mcp/${name}/${config.knownHostsFileName}";
|
||||||
|
description = "Runtime path of the generated known_hosts file.";
|
||||||
|
};
|
||||||
|
|
||||||
|
gatewayBackend = mkOption {
|
||||||
|
type = types.attrsOf types.anything;
|
||||||
|
readOnly = true;
|
||||||
|
default = {
|
||||||
|
command = "${config.package}/bin/ssh-mcp";
|
||||||
|
inherit (config) description;
|
||||||
|
env = {
|
||||||
|
SSH_HOSTS_FILE = config.hostsFile;
|
||||||
|
SSH_KNOWN_HOSTS = config.knownHostsFile;
|
||||||
|
}
|
||||||
|
// renderInstanceSettings config;
|
||||||
|
};
|
||||||
|
description = "Backend attrset ready for `services.mcp-gateway.backends`.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.ssh-mcp = {
|
||||||
|
instances = mkOption {
|
||||||
|
type = types.attrsOf instanceType;
|
||||||
|
default = { };
|
||||||
|
description = "Independent ssh-mcp server instances.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
xdg.configFile = lib.mkMerge (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
name: instance:
|
||||||
|
mkIf instance.enable {
|
||||||
|
"ssh-mcp/${name}/${instance.hostsFileName}".source = toml.generate "ssh-mcp-${name}-hosts.toml" {
|
||||||
|
defaults = renderDefaults instance.defaults;
|
||||||
|
hosts = lib.mapAttrs (_host: host: renderHost host) instance.hosts;
|
||||||
|
};
|
||||||
|
|
||||||
|
"ssh-mcp/${name}/${instance.knownHostsFileName}".text =
|
||||||
|
lib.optionalString (instance.knownHosts != { }) (
|
||||||
|
lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (host: key: "${host} ${key}") instance.knownHosts
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
) cfg.instances
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -39,6 +39,14 @@
|
|||||||
"command": ["simple-web-search-mcp"],
|
"command": ["simple-web-search-mcp"],
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
|
"mcp-searxng": {
|
||||||
|
"type": "local",
|
||||||
|
"command": ["mcp-searxng"],
|
||||||
|
"enabled": true,
|
||||||
|
"environment": {
|
||||||
|
"SEARXNG_URL": "https://search.bueso.eu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"rust-research": {
|
"rust-research": {
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"command": ["rust-research-mcp"],
|
"command": ["rust-research-mcp"],
|
||||||
@@ -96,6 +104,11 @@
|
|||||||
"type": "local",
|
"type": "local",
|
||||||
"command": ["mcp-score", "serve"],
|
"command": ["mcp-score", "serve"],
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
},
|
||||||
|
"docx": {
|
||||||
|
"type": "local",
|
||||||
|
"command": ["docx-mcp"],
|
||||||
|
"enabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,9 +15,8 @@
|
|||||||
# # Now pkgs.manim-mcp-server is available
|
# # Now pkgs.manim-mcp-server is available
|
||||||
# };
|
# };
|
||||||
# }
|
# }
|
||||||
#
|
|
||||||
final: prev:
|
final: prev:
|
||||||
let
|
let
|
||||||
mcpPackages = import ../packages { pkgs = final; };
|
mcpPackages = import ../packages { pkgs = final; };
|
||||||
in
|
in
|
||||||
mcpPackages
|
mcpPackages
|
||||||
@@ -2,27 +2,33 @@
|
|||||||
lib,
|
lib,
|
||||||
buildGoModule,
|
buildGoModule,
|
||||||
fetchFromGitHub,
|
fetchFromGitHub,
|
||||||
|
makeWrapper,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "annas-mcp";
|
pname = "annas-mcp";
|
||||||
version = "0.0.5";
|
version = "0.0.14";
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "iosifache";
|
owner = "SokolskyNikita";
|
||||||
repo = "annas-mcp";
|
repo = "annas-mcp";
|
||||||
rev = "v${version}";
|
rev = "9e720ad93df0fdfb25d4de6afcba11a920b34af4";
|
||||||
hash = "sha256-XicM7tU5jD8B8n7JJDQ/84koBiLb8XF4+WBQ4LCUoRU=";
|
hash = "sha256-LcnWYpvSpSHYo1zMcLGTZgxZgo7JOXF4dESD4l+qVhI=";
|
||||||
};
|
};
|
||||||
|
|
||||||
vendorHash = "sha256-2NdG5p2XfrhVgi388dRDBUSGwg6ybnzfn9495TWNGsA=";
|
vendorHash = "sha256-SCTznvZ8DvDN+iPDcTxiqpNYJwRLS8zIomz7f8S1eD0=";
|
||||||
|
|
||||||
subPackages = [ "cmd/annas-mcp" ];
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
|
||||||
|
# The MCP server mode is invoked with "mcp" subcommand
|
||||||
|
postInstall = ''
|
||||||
|
wrapProgram $out/bin/annas-mcp \
|
||||||
|
--add-flags "mcp"
|
||||||
|
'';
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
description = "MCP server and CLI for searching and downloading documents from Anna's Archive";
|
description = "MCP server and CLI tool for searching and downloading documents from Anna's Archive";
|
||||||
homepage = "https://github.com/iosifache/annas-mcp";
|
homepage = "https://github.com/SokolskyNikita/annas-mcp";
|
||||||
license = licenses.mit;
|
|
||||||
mainProgram = "annas-mcp";
|
mainProgram = "annas-mcp";
|
||||||
platforms = platforms.all;
|
platforms = platforms.all;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
|
|
||||||
python3Packages.buildPythonApplication rec {
|
python3Packages.buildPythonApplication rec {
|
||||||
pname = "applemusic-mcp";
|
pname = "applemusic-mcp";
|
||||||
version = "0.15.1";
|
version = "0.20.1";
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "epheterson";
|
owner = "epheterson";
|
||||||
repo = "applemusic-mcp";
|
repo = "applemusic-mcp";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = "sha256-N97bZyT8TWOrt1WS7W31OJaICwoUxFfcDDuRs+TweTQ=";
|
hash = "sha256-T956sblq/4TrAXkY4cwIFDPW6a2bjzyeyRyejYB4ARc=";
|
||||||
};
|
};
|
||||||
|
|
||||||
build-system = with python3Packages; [
|
build-system = with python3Packages; [
|
||||||
@@ -26,6 +26,7 @@ python3Packages.buildPythonApplication rec {
|
|||||||
requests
|
requests
|
||||||
cryptography
|
cryptography
|
||||||
playwright
|
playwright
|
||||||
|
keyring
|
||||||
];
|
];
|
||||||
|
|
||||||
pythonImportsCheck = [ "applemusic_mcp" ];
|
pythonImportsCheck = [ "applemusic_mcp" ];
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildGoModule,
|
||||||
|
fetchFromGitHub,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildGoModule rec {
|
||||||
|
pname = "audiobookshelf-mcp";
|
||||||
|
version = "unstable-2026-06-27";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "michaeldvinci";
|
||||||
|
repo = "audiobookshelf-mcp";
|
||||||
|
rev = "e790d9c75085772180c95f2ec1376acc98e9282c";
|
||||||
|
hash = "sha256-vQR+Ej3Qtg8ehIUWnXkSayNYpqnOZPWV0TpTVPYh+io=";
|
||||||
|
};
|
||||||
|
|
||||||
|
vendorHash = "sha256-W8hlCGf4QdFbKc3QFc9pa4MWBhnp5A5GvWFNzg0BEhw=";
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "MCP server for Audiobookshelf — libraries, audiobooks, podcasts, authors, collections, playlists";
|
||||||
|
homepage = "https://github.com/michaeldvinci/audiobookshelf-mcp";
|
||||||
|
license = licenses.gpl3Only;
|
||||||
|
mainProgram = "abs-mcp";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+10011
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildNpmPackage,
|
||||||
|
fetchzip,
|
||||||
|
makeWrapper,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildNpmPackage rec {
|
||||||
|
pname = "bitwarden-mcp";
|
||||||
|
version = "2026.7.0";
|
||||||
|
|
||||||
|
src = fetchzip {
|
||||||
|
url = "https://registry.npmjs.org/@bitwarden/mcp-server/-/mcp-server-${version}.tgz";
|
||||||
|
hash = "sha256-uvytqAXW0G9HCshJ987j0U3a+c0p8xUG1m0BsU9Q7AQ=";
|
||||||
|
};
|
||||||
|
|
||||||
|
npmDepsHash = "sha256-X3mXupbs2Sx6E3mYRvtuWyT+FhfGmYyQZAK1iPAStM0=";
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
cp ${./package-lock.json} package-lock.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
# dist/ is already built in the npm tarball
|
||||||
|
dontNpmBuild = true;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Official Bitwarden MCP server - vault, login, folder, Send, and organization management";
|
||||||
|
homepage = "https://github.com/bitwarden/mcp-server";
|
||||||
|
license = licenses.bsd3;
|
||||||
|
mainProgram = "bitwarden-mcp";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
+16
-2
@@ -8,6 +8,7 @@
|
|||||||
zotero-mcp = pkgs.callPackage ./zotero-mcp/package.nix { };
|
zotero-mcp = pkgs.callPackage ./zotero-mcp/package.nix { };
|
||||||
pdf-reader-mcp = pkgs.callPackage ./pdf-reader-mcp/package.nix { };
|
pdf-reader-mcp = pkgs.callPackage ./pdf-reader-mcp/package.nix { };
|
||||||
kindly-web-search-mcp-server = pkgs.callPackage ./kindly-web-search-mcp-server/package.nix { };
|
kindly-web-search-mcp-server = pkgs.callPackage ./kindly-web-search-mcp-server/package.nix { };
|
||||||
|
docx-mcp = pkgs.callPackage ./docx-mcp/package.nix { };
|
||||||
duckduckgo-mcp-server = pkgs.callPackage ./duckduckgo-mcp-server/package.nix { };
|
duckduckgo-mcp-server = pkgs.callPackage ./duckduckgo-mcp-server/package.nix { };
|
||||||
n8n-mcp = pkgs.callPackage ./n8n-mcp/package.nix { };
|
n8n-mcp = pkgs.callPackage ./n8n-mcp/package.nix { };
|
||||||
simple-web-search-mcp = pkgs.callPackage ./simple-web-search-mcp/package.nix { };
|
simple-web-search-mcp = pkgs.callPackage ./simple-web-search-mcp/package.nix { };
|
||||||
@@ -16,9 +17,22 @@
|
|||||||
sabnzbd-mcp = pkgs.callPackage ./sabnzbd-mcp/package.nix { };
|
sabnzbd-mcp = pkgs.callPackage ./sabnzbd-mcp/package.nix { };
|
||||||
jellyfin-mcp = pkgs.callPackage ./jellyfin-mcp/package.nix { };
|
jellyfin-mcp = pkgs.callPackage ./jellyfin-mcp/package.nix { };
|
||||||
nextcloud-mcp-server = pkgs.callPackage ./nextcloud-mcp-server/package.nix { };
|
nextcloud-mcp-server = pkgs.callPackage ./nextcloud-mcp-server/package.nix { };
|
||||||
annas-mcp = pkgs.callPackage ./annas-mcp/package.nix { };
|
|
||||||
mcp-score = pkgs.callPackage ./mcp-score/package.nix { };
|
mcp-score = pkgs.callPackage ./mcp-score/package.nix { };
|
||||||
applemusic-mcp = pkgs.callPackage ./applemusic-mcp/package.nix { };
|
applemusic-mcp = pkgs.callPackage ./applemusic-mcp/package.nix { };
|
||||||
pinchflat-mcp = pkgs.callPackage ./pinchflat-mcp/package.nix { };
|
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 { };
|
||||||
|
navidrome-mcp = pkgs.callPackage ./navidrome-mcp/package.nix { };
|
||||||
|
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 { };
|
||||||
|
romm-mcp = pkgs.callPackage ./romm-mcp/package.nix { };
|
||||||
# example-mcp-server = pkgs.callPackage ./example-mcp-server/package.nix { };
|
# example-mcp-server = pkgs.callPackage ./example-mcp-server/package.nix { };
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3Packages,
|
||||||
|
fetchFromGitHub,
|
||||||
|
}:
|
||||||
|
|
||||||
|
python3Packages.buildPythonApplication rec {
|
||||||
|
pname = "docx-mcp";
|
||||||
|
version = "2.0.0-unstable-2026-07-13";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "Rookie0x80";
|
||||||
|
repo = "docx-mcp";
|
||||||
|
rev = "b6188eaa57d2694b8f70217e8f6a15980b22ec16";
|
||||||
|
hash = "sha256-JjcQjwTiJvO6f0f+iiLgm24Rjms8+xehXHsAD6L+TEY=";
|
||||||
|
};
|
||||||
|
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
build-system = with python3Packages; [ setuptools ];
|
||||||
|
|
||||||
|
dependencies = with python3Packages; [
|
||||||
|
python-docx
|
||||||
|
fastmcp
|
||||||
|
typing-extensions
|
||||||
|
];
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
pythonImportsCheck = [ "docx_mcp" ];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "MCP server for Microsoft Word .docx operations using python-docx (table-focused)";
|
||||||
|
homepage = "https://github.com/Rookie0x80/docx-mcp";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "docx-mcp";
|
||||||
|
platforms = platforms.all;
|
||||||
|
maintainers = [ ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,18 +2,28 @@
|
|||||||
lib,
|
lib,
|
||||||
python3Packages,
|
python3Packages,
|
||||||
fetchFromGitHub,
|
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 {
|
python3Packages.buildPythonApplication rec {
|
||||||
pname = "duckduckgo-mcp-server";
|
pname = "duckduckgo-mcp-server";
|
||||||
version = "0.4.0";
|
version = "0.7.0";
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "nickclyde";
|
owner = "nickclyde";
|
||||||
repo = "duckduckgo-mcp-server";
|
repo = "duckduckgo-mcp-server";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = "sha256-g1mpBrhuCt3g/XnUfHoY8lJDMizINypikj8Lqk2LVuE=";
|
hash = "sha256-wI6Q4lBZYzU4VORLwGZowv8D5UfhKaMeqBIQ7u6eRxc=";
|
||||||
};
|
};
|
||||||
|
|
||||||
build-system = [ python3Packages.hatchling ];
|
build-system = [ python3Packages.hatchling ];
|
||||||
@@ -21,10 +31,12 @@ python3Packages.buildPythonApplication rec {
|
|||||||
dependencies = with python3Packages; [
|
dependencies = with python3Packages; [
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
httpx
|
httpx
|
||||||
mcp
|
httpcore
|
||||||
|
mcp2.mcp
|
||||||
];
|
];
|
||||||
|
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
|
dontCheckRuntimeDeps = true;
|
||||||
|
|
||||||
pythonImportsCheck = [ "duckduckgo_mcp_server" ];
|
pythonImportsCheck = [ "duckduckgo_mcp_server" ];
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "jellyfin-mcp";
|
pname = "jellyfin-mcp";
|
||||||
version = "2026.604.2";
|
version = "2026.1015.1";
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "jaredtrent";
|
owner = "jaredtrent";
|
||||||
repo = "jellyfin-mcp";
|
repo = "jellyfin-mcp";
|
||||||
tag = "v${version}";
|
tag = "v${version}";
|
||||||
hash = "sha256-Ks54txfOAkNEWfhl5ZXEIaFzAbmCnwhHtN1+FIa7dgo=";
|
hash = "sha256-5wSYB/peTbvNYDlDBd2M0nA7JreP93nQe1G9rW3hv2I=";
|
||||||
};
|
};
|
||||||
|
|
||||||
vendorHash = "sha256-cDy+PpZM6xL7ur/xq81y4vlyaDnSOpGkyVV2hh8qI7o=";
|
vendorHash = "sha256-L39HdshrVfVXD+rV3prsKQRosvidynwIlA9J6dtfqmU=";
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
description = "MCP server for Jellyfin media server — search, playback, metadata, admin";
|
description = "MCP server for Jellyfin media server — search, playback, metadata, admin";
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ let
|
|||||||
in
|
in
|
||||||
python313Packages.buildPythonApplication rec {
|
python313Packages.buildPythonApplication rec {
|
||||||
pname = "kindly-web-search-mcp-server";
|
pname = "kindly-web-search-mcp-server";
|
||||||
version = "0.1.8-unstable";
|
version = "0.1.9-unstable";
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "Shelpuk-AI-Technology-Consulting";
|
owner = "Shelpuk-AI-Technology-Consulting";
|
||||||
repo = "kindly-web-search-mcp-server";
|
repo = "kindly-web-search-mcp-server";
|
||||||
rev = "2b76afb37bf311facbde6750d0a0c0cbdfb8fec2";
|
rev = "52a4e480d8041f8d4f388cee1ddecbd8e1ab01c7";
|
||||||
hash = "sha256-90QHr2FPiMSwKXmjlZ2W9NIQvj3LDXzYIq6m/Z+sJMw=";
|
hash = "sha256-Mf3PMUic3XiNkiABRy6DrEhoGwcqq/xThU4ROO4S8dY=";
|
||||||
};
|
};
|
||||||
|
|
||||||
build-system = with python313Packages; [
|
build-system = with python313Packages; [
|
||||||
@@ -95,10 +95,18 @@ python313Packages.buildPythonApplication rec {
|
|||||||
pymupdf
|
pymupdf
|
||||||
fastmcp
|
fastmcp
|
||||||
trafilatura
|
trafilatura
|
||||||
|
starlette
|
||||||
|
uvicorn
|
||||||
];
|
];
|
||||||
|
|
||||||
nativeBuildInputs = [ makeWrapper ];
|
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
|
# Wrap with Chromium path for headless browser content extraction
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
wrapProgram $out/bin/kindly-web-search-mcp-server \
|
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"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
+127
-87
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "mcp-arr-server",
|
"name": "mcp-arr-server",
|
||||||
"version": "1.5.4",
|
"version": "1.7.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mcp-arr-server",
|
"name": "mcp-arr-server",
|
||||||
"version": "1.5.4",
|
"version": "1.7.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.25.2"
|
"@modelcontextprotocol/sdk": "^1.30.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"mcp-arr": "dist/index.js"
|
"mcp-arr": "dist/index.js"
|
||||||
@@ -23,24 +23,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@hono/node-server": {
|
"node_modules/@hono/node-server": {
|
||||||
"version": "1.19.11",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
|
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.0.tgz",
|
||||||
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
|
"integrity": "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.14.1"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"hono": "^4"
|
"hono": "^4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.28.0",
|
"version": "1.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.28.0.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
|
||||||
"integrity": "sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==",
|
"integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/node-server": "^1.19.9",
|
"@hono/node-server": "^1.19.9 || ^2.0.5",
|
||||||
"ajv": "^8.17.1",
|
"ajv": "^8.17.1",
|
||||||
"ajv-formats": "^3.0.1",
|
"ajv-formats": "^3.0.1",
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
@@ -75,9 +75,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.19.37",
|
"version": "20.19.43",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||||
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
|
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -98,9 +98,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "8.18.0",
|
"version": "8.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||||
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
|
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.3",
|
"fast-deep-equal": "^3.1.3",
|
||||||
@@ -131,20 +131,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "2.2.2",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
|
||||||
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
|
"integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^2.0.0",
|
||||||
"debug": "^4.4.3",
|
"debug": "^4.4.3",
|
||||||
"http-errors": "^2.0.0",
|
"http-errors": "^2.0.1",
|
||||||
"iconv-lite": "^0.7.0",
|
"iconv-lite": "^0.7.2",
|
||||||
"on-finished": "^2.4.1",
|
"on-finished": "^2.4.1",
|
||||||
"qs": "^6.14.1",
|
"qs": "^6.15.2",
|
||||||
"raw-body": "^3.0.1",
|
"raw-body": "^3.0.2",
|
||||||
"type-is": "^2.0.1"
|
"type-is": "^2.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -154,6 +154,19 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/body-parser/node_modules/content-type": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -193,9 +206,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
|
||||||
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
|
"integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -337,9 +350,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-object-atoms": {
|
"node_modules/es-object-atoms": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@@ -376,9 +389,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eventsource-parser": {
|
"node_modules/eventsource-parser": {
|
||||||
"version": "3.0.6",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
|
||||||
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
|
"integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
@@ -428,12 +441,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-rate-limit": {
|
"node_modules/express-rate-limit": {
|
||||||
"version": "8.3.1",
|
"version": "8.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz",
|
||||||
"integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
|
"integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ip-address": "10.1.0"
|
"debug": "^4.4.3",
|
||||||
|
"ip-address": "^10.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
@@ -452,9 +466,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
||||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -577,9 +591,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -589,9 +603,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hono": {
|
"node_modules/hono": {
|
||||||
"version": "4.12.9",
|
"version": "4.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz",
|
||||||
"integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
|
"integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
@@ -618,9 +632,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.7.2",
|
"version": "0.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
|
||||||
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
|
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
@@ -640,9 +654,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "10.1.0",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz",
|
||||||
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
"integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
@@ -670,9 +684,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/jose": {
|
"node_modules/jose": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.8.tgz",
|
||||||
"integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==",
|
"integrity": "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/panva"
|
"url": "https://github.com/sponsors/panva"
|
||||||
@@ -700,12 +714,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
|
||||||
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
|
"integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
@@ -821,9 +839,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
|
||||||
"integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==",
|
"integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -853,12 +871,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.15.0",
|
"version": "6.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
|
||||||
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
|
"integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.1.0"
|
"es-define-property": "^1.0.1",
|
||||||
|
"side-channel": "^1.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
@@ -868,12 +887,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/range-parser": {
|
"node_modules/range-parser": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
|
||||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
"integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
@@ -995,14 +1018,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
|
||||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
"integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"object-inspect": "^1.13.3",
|
"object-inspect": "^1.13.4",
|
||||||
"side-channel-list": "^1.0.0",
|
"side-channel-list": "^1.0.1",
|
||||||
"side-channel-map": "^1.0.1",
|
"side-channel-map": "^1.0.1",
|
||||||
"side-channel-weakmap": "^1.0.2"
|
"side-channel-weakmap": "^1.0.2"
|
||||||
},
|
},
|
||||||
@@ -1014,13 +1037,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/side-channel-list": {
|
"node_modules/side-channel-list": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"object-inspect": "^1.13.3"
|
"object-inspect": "^1.13.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -1085,17 +1108,34 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/type-is": {
|
"node_modules/type-is": {
|
||||||
"version": "2.0.1",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
|
||||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
"integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^2.0.0",
|
||||||
"media-typer": "^1.1.0",
|
"media-typer": "^1.1.0",
|
||||||
"mime-types": "^3.0.0"
|
"mime-types": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-is/node_modules/content-type": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
@@ -1159,9 +1199,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "4.3.6",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
|
||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
|
|
||||||
buildNpmPackage rec {
|
buildNpmPackage rec {
|
||||||
pname = "mcp-arr-server";
|
pname = "mcp-arr-server";
|
||||||
version = "1.5.4";
|
version = "1.7.3";
|
||||||
|
|
||||||
src = fetchzip {
|
src = fetchzip {
|
||||||
url = "https://registry.npmjs.org/mcp-arr-server/-/mcp-arr-server-${version}.tgz";
|
url = "https://registry.npmjs.org/mcp-arr-server/-/mcp-arr-server-${version}.tgz";
|
||||||
hash = "sha256-4eSL1eQmPfm5XlQuxykRDotTHvq9oScJOdXOZrYs6sU=";
|
hash = "sha256-n2wbAvkssh1OuxAZDcixOYkc2fdmhRkKJWsb+zoEEmY=";
|
||||||
};
|
};
|
||||||
|
|
||||||
npmDepsHash = "sha256-s3/NbuHGqp+wAs7ysKcdBk6X0DNcvfMce+RbsB8Nt14=";
|
npmDepsHash = "sha256-fG9reQSzNb3QvqvrHKhtOjijTKNEKH7I27rpr0t2gio=";
|
||||||
|
|
||||||
postPatch = ''
|
postPatch = ''
|
||||||
cp ${./package-lock.json} package-lock.json
|
cp ${./package-lock.json} package-lock.json
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3Packages,
|
||||||
|
fetchFromGitHub,
|
||||||
|
}:
|
||||||
|
|
||||||
|
python3Packages.buildPythonApplication rec {
|
||||||
|
pname = "mcp-email-server";
|
||||||
|
version = "1.9.1";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "Wh1isper";
|
||||||
|
repo = "mcp-email-server";
|
||||||
|
rev = "1.9.1";
|
||||||
|
hash = "sha256-FGjpRkVOFLkqY45Wl1lBuSWZvUnkyESZPeAB+d+1KYw=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = with python3Packages; [
|
||||||
|
hatchling
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = with python3Packages; [
|
||||||
|
aioimaplib
|
||||||
|
aiosmtplib
|
||||||
|
anyio
|
||||||
|
beautifulsoup4
|
||||||
|
click
|
||||||
|
jinja2
|
||||||
|
keyring
|
||||||
|
loguru
|
||||||
|
mcp
|
||||||
|
pydantic
|
||||||
|
pydantic-settings
|
||||||
|
starlette
|
||||||
|
tomli-w
|
||||||
|
typer
|
||||||
|
email-validator
|
||||||
|
filelock
|
||||||
|
uvicorn
|
||||||
|
];
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
# mcp transitively requires filelock>=3.32.2 but nixpkgs ships 3.29.7;
|
||||||
|
# the runtime-deps check is too strict, so skip it (same as nextcloud-mcp-server).
|
||||||
|
dontCheckRuntimeDeps = true;
|
||||||
|
|
||||||
|
pythonImportsCheck = [ "mcp_email_server" ];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "MCP server for email - send, receive, search, and manage mail over IMAP/SMTP";
|
||||||
|
homepage = "https://github.com/Wh1isper/mcp-email-server";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "mcp-email-server";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
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 {
|
music21 = python313Packages.buildPythonPackage rec {
|
||||||
pname = "music21";
|
pname = "music21";
|
||||||
version = "9.9.2";
|
version = "10.5.0";
|
||||||
format = "wheel";
|
format = "wheel";
|
||||||
|
|
||||||
src = fetchPypi {
|
src = fetchPypi {
|
||||||
@@ -17,7 +17,7 @@ let
|
|||||||
format = "wheel";
|
format = "wheel";
|
||||||
dist = "py3";
|
dist = "py3";
|
||||||
python = "py3";
|
python = "py3";
|
||||||
hash = "sha256-ZIAQq40TKNCxB0bO8UV+hLEidM0xYkgifpbSHielGMo=";
|
hash = "sha256-mSTv9fv1hJDmfL87eKWLpTBA53woGTDTVK8yp0fpRgY=";
|
||||||
};
|
};
|
||||||
|
|
||||||
propagatedBuildInputs = with python313Packages; [
|
propagatedBuildInputs = with python313Packages; [
|
||||||
@@ -50,8 +50,8 @@ python313Packages.buildPythonApplication rec {
|
|||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "tskovlund";
|
owner = "tskovlund";
|
||||||
repo = "mcp-score";
|
repo = "mcp-score";
|
||||||
rev = "3ff9d4b6504fcee5032c44fdcf7dc1d7a21a4b5d";
|
rev = "b2155a59119103c39a37f14166d0d6920e564c76";
|
||||||
hash = "sha256-zrpvzxi8AbF5/etuvMZLni4HSFBYGRe55rpKJJEAg60=";
|
hash = "sha256-XYZjGa7pWaVmZ4dF/vOLkrYjSjNXP/148DQjujMdvIU=";
|
||||||
};
|
};
|
||||||
|
|
||||||
build-system = with python313Packages; [
|
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 {
|
buildNpmPackage rec {
|
||||||
pname = "n8n-mcp";
|
pname = "n8n-mcp";
|
||||||
version = "2.35.1";
|
version = "2.87.0";
|
||||||
|
|
||||||
src = fetchzip {
|
src = fetchzip {
|
||||||
url = "https://registry.npmjs.org/${pname}/-/${pname}-${version}.tgz";
|
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 = ''
|
postPatch = ''
|
||||||
cp ${./package-lock.json} package-lock.json
|
cp ${./package-lock.json} package-lock.json
|
||||||
|
|||||||
Generated
+1252
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildNpmPackage,
|
||||||
|
fetchzip,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildNpmPackage rec {
|
||||||
|
pname = "namecheap-mcp";
|
||||||
|
version = "1.0.1";
|
||||||
|
|
||||||
|
src = fetchzip {
|
||||||
|
# npm package is "namecheap-api-mcp"; pname is the Nix attr name.
|
||||||
|
url = "https://registry.npmjs.org/namecheap-api-mcp/-/namecheap-api-mcp-${version}.tgz";
|
||||||
|
hash = "sha256-+/MwPgrxJiWmObkLF30YqXWGwesbp98qzGiv/W86WYw=";
|
||||||
|
};
|
||||||
|
|
||||||
|
npmDepsHash = "sha256-l0i/zzy3I29ilF8vZMBrTlBQdvrISC9Vr/iQJXDVKmw=";
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
cp ${./package-lock.json} package-lock.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
# dist/ is already built in the npm tarball
|
||||||
|
dontNpmBuild = true;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Namecheap API MCP server - DNS, domains, contacts, and registrar lock management";
|
||||||
|
homepage = "https://github.com/lanternrow/namecheap-api-mcp";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "namecheap-api-mcp";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+5900
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildNpmPackage,
|
||||||
|
fetchzip,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildNpmPackage rec {
|
||||||
|
pname = "navidrome-mcp";
|
||||||
|
version = "2.3.0";
|
||||||
|
|
||||||
|
src = fetchzip {
|
||||||
|
url = "https://registry.npmjs.org/${pname}/-/${pname}-${version}.tgz";
|
||||||
|
hash = "sha256-npUgzJpB0DaFuzU/1SuOIOVvd2gqZtb1Kos7NC8/Pd4=";
|
||||||
|
};
|
||||||
|
|
||||||
|
npmDepsHash = "sha256-58OZ4qVfeN+0LVzcSJQHQqiY66gPSGWI6ht/4fMp0oQ=";
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
cp ${./package-lock.json} package-lock.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
# dist/ is already built in the npm tarball
|
||||||
|
dontNpmBuild = true;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "MCP server for Navidrome - search music library, playlists, radio stations, and playback control";
|
||||||
|
homepage = "https://github.com/Blakeem/Navidrome-MCP";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "navidrome-mcp";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,9 +3,18 @@
|
|||||||
python3Packages,
|
python3Packages,
|
||||||
fetchFromGitHub,
|
fetchFromGitHub,
|
||||||
fetchPypi,
|
fetchPypi,
|
||||||
|
fetchurl,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
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
|
# Helper: buildPythonPackage for a wheel fetched from PyPI
|
||||||
# CRITICAL: pname MUST use underscores, not hyphens, for PyPI wheel filenames
|
# CRITICAL: pname MUST use underscores, not hyphens, for PyPI wheel filenames
|
||||||
fetchWheel = { pname, version, hash, propagatedBuildInputs ? [ ], meta ? { } }:
|
fetchWheel = { pname, version, hash, propagatedBuildInputs ? [ ], meta ? { } }:
|
||||||
@@ -132,22 +141,24 @@ let
|
|||||||
in
|
in
|
||||||
python3Packages.buildPythonApplication rec {
|
python3Packages.buildPythonApplication rec {
|
||||||
pname = "nextcloud-mcp-server";
|
pname = "nextcloud-mcp-server";
|
||||||
version = "0.121.3";
|
version = "0.195.0";
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "cbcoutinho";
|
owner = "cbcoutinho";
|
||||||
repo = "nextcloud-mcp-server";
|
repo = "nextcloud-mcp-server";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = "sha256-U6BG5anTEllNnJzta4VV6QOfn86+N15q6v/SfLGM7mc=";
|
hash = "sha256-k8WeVe1ScWN7Lh+trowzn+UVuQBgfgI1Klfz4w5F8do=";
|
||||||
};
|
};
|
||||||
|
|
||||||
build-system = [ python3Packages.uv-build ];
|
build-system = [ python3Packages.uv-build ];
|
||||||
|
|
||||||
dependencies = with python3Packages; [
|
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
|
aiosqlite alembic authlib fastembed anthropic boto3 starlette
|
||||||
jinja2 markdownify pymupdf pymupdf4llm openai dynaconf sqlalchemy
|
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
|
pythonvcard4 prometheus-client python-json-logger importlib-metadata
|
||||||
googleapis-common-protos opentelemetry-api opentelemetry-proto
|
googleapis-common-protos opentelemetry-api opentelemetry-proto
|
||||||
@@ -161,14 +172,11 @@ python3Packages.buildPythonApplication rec {
|
|||||||
|
|
||||||
postPatch = ''
|
postPatch = ''
|
||||||
substituteInPlace pyproject.toml \
|
substituteInPlace pyproject.toml \
|
||||||
--replace-quiet '"uv_build>=0.11.5,<0.12.0"' '"uv_build"' \
|
--replace-quiet '"uv_build>=0.12.1,<0.13.0"' '"uv_build"' \
|
||||||
--replace-quiet '" (>=1.27,<1.28)"' '""' \
|
--replace-quiet '" (>=2.1,<3)"' '""' \
|
||||||
--replace-quiet '" (>=0.28.1,<0.29.0)"' '""' \
|
--replace-quiet '" (>=0.28.1,<0.29.0)"' '""' \
|
||||||
--replace-quiet '" (>=10.3.0,<12.0.0)"' '""' \
|
--replace-quiet '" (>=10.3.0,<12.0.0)"' '""' \
|
||||||
--replace-quiet '" (>=7.1.0,<7.2.0)"' '""' \
|
--replace-quiet '" (>=7.3.0,<7.4.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 '">=3.0.1,<4.0"' '""' \
|
--replace-quiet '">=3.0.1,<4.0"' '""' \
|
||||||
--replace-quiet '">=3.8.0,<4.0"' '""' \
|
--replace-quiet '">=3.8.0,<4.0"' '""' \
|
||||||
--replace-quiet '">=3.2.13,<4.0"' '""' \
|
--replace-quiet '">=3.2.13,<4.0"' '""' \
|
||||||
@@ -188,6 +196,12 @@ python3Packages.buildPythonApplication rec {
|
|||||||
dontCheckRuntimeDeps = true;
|
dontCheckRuntimeDeps = true;
|
||||||
catchConflicts = false;
|
catchConflicts = false;
|
||||||
|
|
||||||
|
pythonImportsCheck = [
|
||||||
|
"nextcloud_mcp_server"
|
||||||
|
"mcp"
|
||||||
|
"pymupdf"
|
||||||
|
];
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
description = "MCP server for Nextcloud — files, calendar, contacts, notes, deck, talk, sharing";
|
description = "MCP server for Nextcloud — files, calendar, contacts, notes, deck, talk, sharing";
|
||||||
homepage = "https://github.com/cbcoutinho/nextcloud-mcp-server";
|
homepage = "https://github.com/cbcoutinho/nextcloud-mcp-server";
|
||||||
|
|||||||
Generated
+5642
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildNpmPackage,
|
||||||
|
fetchFromGitHub,
|
||||||
|
makeWrapper,
|
||||||
|
nodejs,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildNpmPackage rec {
|
||||||
|
pname = "ovhcloud-mcp";
|
||||||
|
version = "2.0.1";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "hlebtkachenko";
|
||||||
|
repo = "ovhcloud-mcp";
|
||||||
|
rev = "ccadc85bd4aab35434a24732bc77ef2b6f100b4c";
|
||||||
|
hash = "sha256-FYJSKOU/lCutGCUJhaAtKWBLJOBUyWvdkSnMWbxwhI4=";
|
||||||
|
};
|
||||||
|
|
||||||
|
npmDepsHash = "sha256-myeqksb3mjJ1GJej/4D7iuyOOTsGJBdkka9zUpH+flY=";
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
cp ${./package-lock.json} package-lock.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
# dist/ is NOT committed to the repo; buildNpmPackage runs "npm run build"
|
||||||
|
# (tsc) by default, which emits dist/index.js.
|
||||||
|
#
|
||||||
|
# The upstream package.json has NO "bin" field, so buildNpmPackage produces
|
||||||
|
# no executable. Create a wrapper that runs node dist/index.js.
|
||||||
|
postInstall = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
makeWrapper ${nodejs}/bin/node $out/bin/ovhcloud-mcp \
|
||||||
|
--add-flags "$out/lib/node_modules/ovhcloud-mcp/dist/index.js"
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "OVHcloud MCP server - VPS, domains, DNS, billing, SSH, and API explorer";
|
||||||
|
homepage = "https://github.com/hlebtkachenko/ovhcloud-mcp";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "ovhcloud-mcp";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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,
|
lib,
|
||||||
buildNpmPackage,
|
buildNpmPackage,
|
||||||
fetchzip,
|
fetchzip,
|
||||||
|
autoPatchelfHook,
|
||||||
|
stdenv,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
buildNpmPackage rec {
|
buildNpmPackage rec {
|
||||||
pname = "pdf-reader-mcp";
|
pname = "pdf-reader-mcp";
|
||||||
version = "2.3.0";
|
version = "4.1.3";
|
||||||
|
|
||||||
src = fetchzip {
|
src = fetchzip {
|
||||||
url = "https://registry.npmjs.org/@sylphx/pdf-reader-mcp/-/pdf-reader-mcp-${version}.tgz";
|
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 = ''
|
postPatch = ''
|
||||||
cp ${./package-lock.json} package-lock.json
|
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;
|
dontNpmBuild = true;
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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 os
|
||||||
import sys
|
import sys
|
||||||
@@ -20,13 +43,76 @@ HEADERS = {"Authorization": f"Bearer {PINCHFLAT_TOKEN}", "Content-Type": "applic
|
|||||||
|
|
||||||
mcp = FastMCP("pinchflat")
|
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:
|
def _get(path: str, params: Optional[dict] = None) -> Any:
|
||||||
"""Make a GET request to the Pinchflat API."""
|
"""Make a GET request to the Pinchflat API."""
|
||||||
with httpx.Client(timeout=30) as client:
|
with httpx.Client(timeout=30) as client:
|
||||||
r = client.get(f"{BASE}{path}", headers=HEADERS, params=params)
|
r = client.get(f"{BASE}{path}", headers=HEADERS, params=params)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()
|
return _unwrap(r)
|
||||||
|
|
||||||
|
|
||||||
def _post(path: str, json: Optional[dict] = None) -> Any:
|
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:
|
with httpx.Client(timeout=30) as client:
|
||||||
r = client.post(f"{BASE}{path}", headers=HEADERS, json=json)
|
r = client.post(f"{BASE}{path}", headers=HEADERS, json=json)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()
|
return _unwrap(r)
|
||||||
|
|
||||||
|
|
||||||
def _put(path: str, json: dict) -> Any:
|
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:
|
with httpx.Client(timeout=30) as client:
|
||||||
r = client.put(f"{BASE}{path}", headers=HEADERS, json=json)
|
r = client.put(f"{BASE}{path}", headers=HEADERS, json=json)
|
||||||
r.raise_for_status()
|
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."""
|
"""Make a DELETE request to the Pinchflat API."""
|
||||||
with httpx.Client(timeout=30) as client:
|
with httpx.Client(timeout=timeout) as client:
|
||||||
r = client.delete(f"{BASE}{path}", headers=HEADERS)
|
r = client.delete(f"{BASE}{path}", headers=HEADERS, params=params)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
return r.json()
|
return _unwrap(r)
|
||||||
|
|
||||||
|
|
||||||
# --- Sources ---
|
# --- Sources ---
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
|
@tool()
|
||||||
def list_sources() -> dict:
|
def list_sources() -> dict:
|
||||||
"""List all configured media sources (YouTube channels, playlists, etc.)."""
|
"""List all configured media sources (YouTube channels, playlists, etc.)."""
|
||||||
return _get("/sources")
|
return _get("/sources")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def get_source(source_id: int) -> dict:
|
def get_source(source_id: int) -> dict:
|
||||||
"""Get details of a specific source by ID, including its pending tasks."""
|
"""Get details of a specific source by ID, including its pending tasks."""
|
||||||
return _get(f"/sources/{source_id}")
|
return _get(f"/sources/{source_id}")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def create_source(
|
def create_source(
|
||||||
original_url: str,
|
original_url: str,
|
||||||
media_profile_id: int,
|
media_profile_id: int,
|
||||||
collection_type: str = "channel",
|
collection_type: str = "channel",
|
||||||
custom_name: Optional[str] = None,
|
custom_name: Optional[str] = None,
|
||||||
download_cutoff_date: Optional[str] = None,
|
download_cutoff_date: Optional[str] = None,
|
||||||
|
retention_period_days: Optional[int] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Create a new media source (e.g. add a YouTube channel to monitor).
|
"""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'
|
collection_type: 'channel', 'playlist', or 'video'
|
||||||
custom_name: Optional custom name for the source
|
custom_name: Optional custom name for the source
|
||||||
download_cutoff_date: Only download media after this date (YYYY-MM-DD)
|
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 = {
|
source = {
|
||||||
"media_profile_id": media_profile_id,
|
"media_profile_id": media_profile_id,
|
||||||
@@ -93,18 +183,30 @@ def create_source(
|
|||||||
source["custom_name"] = custom_name
|
source["custom_name"] = custom_name
|
||||||
if download_cutoff_date:
|
if download_cutoff_date:
|
||||||
source["download_cutoff_date"] = 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})
|
return _post("/sources", json={"source": source})
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def update_source(
|
def update_source(
|
||||||
source_id: int,
|
source_id: int,
|
||||||
custom_name: Optional[str] = None,
|
custom_name: Optional[str] = None,
|
||||||
download_cutoff_date: Optional[str] = None,
|
download_cutoff_date: Optional[str] = None,
|
||||||
enabled: Optional[bool] = None,
|
enabled: Optional[bool] = None,
|
||||||
index_frequency_minutes: Optional[int] = None,
|
index_frequency_minutes: Optional[int] = None,
|
||||||
|
retention_period_days: Optional[int] = None,
|
||||||
|
prevent_culling: Optional[bool] = None,
|
||||||
|
download_media: Optional[bool] = None,
|
||||||
) -> dict:
|
) -> 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 = {}
|
updates = {}
|
||||||
if custom_name is not None:
|
if custom_name is not None:
|
||||||
updates["custom_name"] = custom_name
|
updates["custom_name"] = custom_name
|
||||||
@@ -114,34 +216,46 @@ def update_source(
|
|||||||
updates["enabled"] = enabled
|
updates["enabled"] = enabled
|
||||||
if index_frequency_minutes is not None:
|
if index_frequency_minutes is not None:
|
||||||
updates["index_frequency_minutes"] = index_frequency_minutes
|
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})
|
return _put(f"/sources/{source_id}", json={"source": updates})
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def delete_source(source_id: int) -> dict:
|
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}")
|
return _delete(f"/sources/{source_id}")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def force_download_pending(source_id: int) -> dict:
|
def force_download_pending(source_id: int) -> dict:
|
||||||
"""Force download of all pending media items for a source."""
|
"""Force download of all pending media items for a source."""
|
||||||
return _post(f"/sources/{source_id}/force_download_pending")
|
return _post(f"/sources/{source_id}/force_download_pending")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def force_redownload(source_id: int) -> dict:
|
def force_redownload(source_id: int) -> dict:
|
||||||
"""Force re-download of all existing media for a source."""
|
"""Force re-download of all existing media for a source."""
|
||||||
return _post(f"/sources/{source_id}/force_redownload")
|
return _post(f"/sources/{source_id}/force_redownload")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def force_index(source_id: int) -> dict:
|
def force_index(source_id: int) -> dict:
|
||||||
"""Force re-index of a source."""
|
"""Force re-index of a source."""
|
||||||
return _post(f"/sources/{source_id}/force_index")
|
return _post(f"/sources/{source_id}/force_index")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def force_metadata_refresh(source_id: int) -> dict:
|
def force_metadata_refresh(source_id: int) -> dict:
|
||||||
"""Force metadata refresh for a source."""
|
"""Force metadata refresh for a source."""
|
||||||
return _post(f"/sources/{source_id}/force_metadata_refresh")
|
return _post(f"/sources/{source_id}/force_metadata_refresh")
|
||||||
@@ -149,42 +263,72 @@ def force_metadata_refresh(source_id: int) -> dict:
|
|||||||
|
|
||||||
# --- Media Items ---
|
# --- Media Items ---
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
def list_media(source_id: Optional[int] = None, limit: int = 50) -> dict:
|
@tool()
|
||||||
"""List media items. Optionally filter by source_id."""
|
def list_media(
|
||||||
params = {}
|
source_id: Optional[int] = None,
|
||||||
|
limit: int = 50,
|
||||||
|
offset: int = 0,
|
||||||
|
downloaded: Optional[bool] = None,
|
||||||
|
) -> dict:
|
||||||
|
"""List media items with pagination. Optionally filter by source_id or download status.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_id: Filter to a specific source
|
||||||
|
limit: Max items to return (default 50)
|
||||||
|
offset: Skip first N items for pagination (default 0)
|
||||||
|
downloaded: If True, only return downloaded media; if False, only undownloaded
|
||||||
|
"""
|
||||||
|
params = {"limit": limit, "offset": offset}
|
||||||
if source_id:
|
if source_id:
|
||||||
params["source_id"] = source_id
|
params["source_id"] = source_id
|
||||||
|
if downloaded is not None:
|
||||||
|
params["downloaded"] = "true" if downloaded else "false"
|
||||||
return _get("/media", params=params)
|
return _get("/media", params=params)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def search_media(query: str) -> dict:
|
def search_media(query: str) -> dict:
|
||||||
"""Search media items by title or description."""
|
"""Search media items by title or description."""
|
||||||
return _get("/media/search", params={"q": query})
|
return _get("/media/search", params={"q": query})
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def get_media(media_id: int) -> dict:
|
def get_media(media_id: int) -> dict:
|
||||||
"""Get details of a specific media item."""
|
"""Get details of a specific media item."""
|
||||||
return _get(f"/media/{media_id}")
|
return _get(f"/media/{media_id}")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def force_download_media(media_id: int) -> dict:
|
def force_download_media(media_id: int) -> dict:
|
||||||
"""Force download of a specific media item."""
|
"""Force download of a specific media item."""
|
||||||
return _post(f"/media/{media_id}/force_download")
|
return _post(f"/media/{media_id}/force_download")
|
||||||
|
|
||||||
|
|
||||||
|
@tool()
|
||||||
|
def delete_media(media_id: int, prevent_download: bool = False) -> dict:
|
||||||
|
"""Delete a media item's downloaded files from disk.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
media_id: The media item ID to delete
|
||||||
|
prevent_download: If True, prevent the item from being re-downloaded in the future
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
if prevent_download:
|
||||||
|
params["prevent_download"] = "true"
|
||||||
|
return _delete(f"/media/{media_id}", params=params, timeout=120)
|
||||||
|
|
||||||
|
|
||||||
# --- Media Profiles ---
|
# --- Media Profiles ---
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
|
@tool()
|
||||||
def list_media_profiles() -> dict:
|
def list_media_profiles() -> dict:
|
||||||
"""List all media profiles (download quality/container settings)."""
|
"""List all media profiles (download quality/container settings)."""
|
||||||
return _get("/media_profiles")
|
return _get("/media_profiles")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def get_media_profile(profile_id: int) -> dict:
|
def get_media_profile(profile_id: int) -> dict:
|
||||||
"""Get details of a specific media profile."""
|
"""Get details of a specific media profile."""
|
||||||
return _get(f"/media_profiles/{profile_id}")
|
return _get(f"/media_profiles/{profile_id}")
|
||||||
@@ -192,7 +336,8 @@ def get_media_profile(profile_id: int) -> dict:
|
|||||||
|
|
||||||
# --- Tasks ---
|
# --- Tasks ---
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
|
@tool()
|
||||||
def list_tasks(source_id: Optional[int] = None) -> dict:
|
def list_tasks(source_id: Optional[int] = None) -> dict:
|
||||||
"""List all tasks. Optionally filter by source_id."""
|
"""List all tasks. Optionally filter by source_id."""
|
||||||
params = {}
|
params = {}
|
||||||
@@ -201,21 +346,22 @@ def list_tasks(source_id: Optional[int] = None) -> dict:
|
|||||||
return _get("/tasks", params=params)
|
return _get("/tasks", params=params)
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool()
|
||||||
def get_task(task_id: int) -> dict:
|
def get_task(task_id: int) -> dict:
|
||||||
"""Get details of a specific task."""
|
"""Get details of a specific task."""
|
||||||
return _get(f"/tasks/{task_id}")
|
return _get(f"/tasks/{task_id}")
|
||||||
|
|
||||||
|
|
||||||
# --- Settings & Info ---
|
# --- Settings & Info (admin tier — not exposed by default) ---
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
|
@tool("admin")
|
||||||
def get_settings() -> dict:
|
def get_settings() -> dict:
|
||||||
"""Get current Pinchflat settings."""
|
"""Get current Pinchflat settings (credential values are redacted)."""
|
||||||
return _get("/settings")
|
return _get("/settings")
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
@tool("admin")
|
||||||
def get_app_info() -> dict:
|
def get_app_info() -> dict:
|
||||||
"""Get Pinchflat app info (version, environment, etc.)."""
|
"""Get Pinchflat app info (version, environment, etc.)."""
|
||||||
return _get("/app_info")
|
return _get("/app_info")
|
||||||
@@ -223,4 +369,4 @@ def get_app_info() -> dict:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
transport = os.environ.get("PINCHFLAT_MCP_TRANSPORT", "stdio")
|
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 = [ ];
|
||||||
|
};
|
||||||
|
}
|
||||||
Generated
+1267
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildNpmPackage,
|
||||||
|
fetchzip,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildNpmPackage rec {
|
||||||
|
pname = "romm-mcp";
|
||||||
|
version = "1.0.5";
|
||||||
|
|
||||||
|
src = fetchzip {
|
||||||
|
url = "https://registry.npmjs.org/${pname}/-/${pname}-${version}.tgz";
|
||||||
|
hash = "sha256-SOYzG0CtNSvIrxOYJq3UDXRlXylqbDjo2f5qfpbiYmI=";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Upstream publishes no package-lock.json (only package.json), and
|
||||||
|
# buildNpmPackage requires one to compute npmDepsHash. This lock is generated
|
||||||
|
# from the published tarball itself -- `npm install --package-lock-only
|
||||||
|
# --ignore-scripts` inside the extracted 1.0.5 package -- so it locks exactly
|
||||||
|
# the dependency tree that shipped with this version.
|
||||||
|
npmDepsHash = "sha256-oSlECdIf2ZP8ZZhOUChkcJAmUTj6TFTdeTJLY5RwLGo=";
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
cp ${./package-lock.json} package-lock.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
# dist/*.js (tsc output) ships inside the npm tarball, so there is nothing to
|
||||||
|
# compile here -- same arrangement as navidrome-mcp and n8n-mcp.
|
||||||
|
dontNpmBuild = true;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "MCP server for RomM: browse, search and administrate a self-hosted ROM library";
|
||||||
|
homepage = "https://github.com/rodrigosiviero/romm-mcp";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = pname;
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,10 +21,14 @@ stdenv.mkDerivation {
|
|||||||
version = "0.1.0-unstable";
|
version = "0.1.0-unstable";
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "jmagar";
|
# Original upstream (jmagar/sabnzbd-mcp) was deleted from GitHub.
|
||||||
|
# Using jasonpaulso's fork, which preserves the same file layout
|
||||||
|
# (client.py + sabnzbd-mcp-server.py at repo root) and the
|
||||||
|
# /sabnzbd/api string that postPatch below rewrites.
|
||||||
|
owner = "jasonpaulso";
|
||||||
repo = "sabnzbd-mcp";
|
repo = "sabnzbd-mcp";
|
||||||
rev = "9d691ed70b72397fddec03ca4ec68c81b30c79c3";
|
rev = "8755e70e2634c02c24471f32962224833877dc81";
|
||||||
hash = "sha256-1sEnHH40cDVT5oiZD2Xywl7S9vJThii/ce+6r+XmpSw=";
|
hash = "sha256-y46jTXBsOXMoFnnwyv0n+8uq2Z/kw2HbnZC85knWpUw=";
|
||||||
};
|
};
|
||||||
|
|
||||||
nativeBuildInputs = [ makeWrapper ];
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
fetchFromGitHub,
|
||||||
|
makeWrapper,
|
||||||
|
python3,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
python = python3.withPackages (
|
||||||
|
ps: with ps; [
|
||||||
|
fastmcp
|
||||||
|
httpx
|
||||||
|
]
|
||||||
|
);
|
||||||
|
in
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
pname = "slskd-mcp";
|
||||||
|
version = "0.1.0-unstable-2026-07-28";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "abl030";
|
||||||
|
repo = "slskd-mcp";
|
||||||
|
rev = "08e4373f41fddcceff8ea3240323e0c6258a373e";
|
||||||
|
hash = "sha256-ELUoAOOWmNoxoTjPxxAY00exN56lngSPs4wtn/GadQU=";
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [ makeWrapper ];
|
||||||
|
|
||||||
|
# generated/server.py is tracked in git; no build step needed.
|
||||||
|
dontBuild = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p $out/lib/slskd-mcp $out/bin
|
||||||
|
cp -r $src/generated $out/lib/slskd-mcp/
|
||||||
|
makeWrapper ${python}/bin/fastmcp $out/bin/slskd-mcp \
|
||||||
|
--add-flags "run $out/lib/slskd-mcp/generated/server.py"
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "MCP server for slskd (Soulseek daemon) - search, download, browse peers, private messages";
|
||||||
|
homepage = "https://github.com/abl030/slskd-mcp";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "slskd-mcp";
|
||||||
|
platforms = platforms.all;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,13 +6,13 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
wheel = fetchurl {
|
wheel = fetchurl {
|
||||||
url = "https://files.pythonhosted.org/packages/6b/9c/dc73804f0f62208d59794080d009fb86b7ac6e9d4b5ad452791355dcfb51/transmission_mcp-1.0.1-py3-none-any.whl";
|
url = "https://files.pythonhosted.org/packages/7e/81/fb2a2f2073d22b0a18c4c96b9c9ff386c3d0b49b6263097bf646d8b72800/transmission_mcp-1.1.0-py3-none-any.whl";
|
||||||
hash = "sha256-1TKb75iNKgMqw0YVZ+rDEac4IyCuj94y1OMzqQROCrI=";
|
hash = "sha256-J22L3lY+XVdLdZeu8KuFdly0BkFUoAbX4GE/9a5kKSU=";
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
python3Packages.buildPythonApplication rec {
|
python3Packages.buildPythonApplication rec {
|
||||||
pname = "transmission-mcp";
|
pname = "transmission-mcp";
|
||||||
version = "1.0.1";
|
version = "1.1.0";
|
||||||
format = "wheel";
|
format = "wheel";
|
||||||
|
|
||||||
src = wheel;
|
src = wheel;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
fetchzip,
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation rec {
|
||||||
|
pname = "yarr";
|
||||||
|
version = "2.2.2";
|
||||||
|
|
||||||
|
src = fetchzip {
|
||||||
|
url = "https://github.com/dinglebear-ai/yarr/releases/download/v${version}/yarr-x86_64.tar.gz";
|
||||||
|
hash = "sha256-2L1slCN/zigS4eMIXK7vexAyLNQZs8Q4BMdA7GHIIiM=";
|
||||||
|
};
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp yarr $out/bin/
|
||||||
|
chmod +x $out/bin/yarr
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "Rust MCP server for media automation fleets - Sonarr, Radarr, Prowlarr, Jellyfin, SABnzbd, qBittorrent, Plex";
|
||||||
|
homepage = "https://github.com/dinglebear-ai/yarr";
|
||||||
|
license = licenses.agpl3Only;
|
||||||
|
mainProgram = "yarr";
|
||||||
|
platforms = [ "x86_64-linux" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -3,13 +3,14 @@
|
|||||||
python3Packages,
|
python3Packages,
|
||||||
fetchFromGitHub,
|
fetchFromGitHub,
|
||||||
fetchPypi,
|
fetchPypi,
|
||||||
|
fetchurl,
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
# Internal dependency: pyzotero (not in nixpkgs)
|
# Internal dependency: pyzotero (not in nixpkgs)
|
||||||
pyzotero = python3Packages.buildPythonPackage rec {
|
pyzotero = python3Packages.buildPythonPackage rec {
|
||||||
pname = "pyzotero";
|
pname = "pyzotero";
|
||||||
version = "1.11.0";
|
version = "1.14.0";
|
||||||
format = "wheel";
|
format = "wheel";
|
||||||
|
|
||||||
src = fetchPypi {
|
src = fetchPypi {
|
||||||
@@ -17,7 +18,7 @@ let
|
|||||||
format = "wheel";
|
format = "wheel";
|
||||||
dist = "py3";
|
dist = "py3";
|
||||||
python = "py3";
|
python = "py3";
|
||||||
hash = "sha256-VDSMczLHnNGs9THyVI/I2MbqMeiemMQgIy2XoZOtlbI=";
|
hash = "sha256-OUoU8HTEFwVfzqopcLWUzpJaYJTJ09zIpzTgdCbs9BU=";
|
||||||
};
|
};
|
||||||
|
|
||||||
propagatedBuildInputs = with python3Packages; [
|
propagatedBuildInputs = with python3Packages; [
|
||||||
@@ -36,38 +37,66 @@ let
|
|||||||
license = licenses.blueOak100;
|
license = licenses.blueOak100;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# pdf-inspector: Rust with prebuilt abi3 wheels, not in nixpkgs.
|
||||||
|
# Pinned exactly (pre-1.0); no Python dependencies of its own.
|
||||||
|
pdf-inspector = python3Packages.buildPythonPackage rec {
|
||||||
|
pname = "pdf-inspector";
|
||||||
|
version = "0.2.6";
|
||||||
|
format = "wheel";
|
||||||
|
|
||||||
|
src = fetchurl {
|
||||||
|
url = "https://files.pythonhosted.org/packages/59/af/be72ab2bd310b6532e4311cf06175eb722802ccea29dbd9281f66c645bd8/pdf_inspector-0.2.6-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl";
|
||||||
|
hash = "sha256-33bdEAUEtwXOku8sZo8VLwUnfZQqvZSn0/JSy1RI1W0=";
|
||||||
|
};
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
pythonImportsCheck = [ "pdf_inspector" ];
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "PDF text extraction (Rust, abi3 wheels)";
|
||||||
|
homepage = "https://github.com/54yyyu/pdf-inspector";
|
||||||
|
license = licenses.mit;
|
||||||
|
};
|
||||||
|
};
|
||||||
in
|
in
|
||||||
python3Packages.buildPythonApplication rec {
|
python3Packages.buildPythonApplication rec {
|
||||||
pname = "zotero-mcp";
|
pname = "zotero-mcp";
|
||||||
version = "0.5.0";
|
version = "0.13.0";
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "54yyyu";
|
owner = "54yyyu";
|
||||||
repo = "zotero-mcp";
|
repo = "zotero-mcp";
|
||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
hash = "sha256-ioy6bdP+jFioUGBKiiYtD4nbdFe6v2E3nCi6s9+dTT0=";
|
hash = "sha256-xL54SbaAczkwk4tkan+wVVTv9ACKIC6hcAADI2GTnxk=";
|
||||||
};
|
};
|
||||||
|
|
||||||
build-system = with python3Packages; [
|
build-system = with python3Packages; [
|
||||||
hatchling
|
hatchling
|
||||||
];
|
];
|
||||||
|
|
||||||
# Core dependencies
|
# Core dependencies (from pyproject.toml v0.13.0)
|
||||||
dependencies = with python3Packages; [
|
dependencies = with python3Packages; [
|
||||||
pyzotero
|
pyzotero
|
||||||
mcp
|
|
||||||
python-dotenv
|
python-dotenv
|
||||||
markitdown
|
pdf-inspector
|
||||||
|
markdownify
|
||||||
pydantic
|
pydantic
|
||||||
requests
|
requests
|
||||||
fastmcp
|
fastmcp
|
||||||
|
httpx
|
||||||
unidecode
|
unidecode
|
||||||
# PDF support
|
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
|
pymupdf
|
||||||
ebooklib
|
ebooklib
|
||||||
pdfplumber
|
# Runtime deps for the semantic-search tools (upstream "semantic" extra).
|
||||||
# Semantic search
|
# 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
|
chromadb
|
||||||
sentence-transformers
|
sentence-transformers
|
||||||
openai
|
openai
|
||||||
@@ -75,20 +104,20 @@ python3Packages.buildPythonApplication rec {
|
|||||||
tiktoken
|
tiktoken
|
||||||
];
|
];
|
||||||
|
|
||||||
# Optional: semantic search dependencies
|
# `zotero_get_pdf_outline` reads a PDF's table of contents in a throwaway
|
||||||
optional-dependencies = with python3Packages; {
|
# `sys.executable -c ...` child (tools/write.py, _extract_pdf_toc). Under the
|
||||||
semantic = [
|
# Nix wrapper this package's dependencies are injected into the *parent*
|
||||||
chromadb
|
# interpreter via site.addsitedir, never exported as PYTHONPATH, so the child
|
||||||
sentence-transformers
|
# gets a bare sys.path and its `import pymupdf` fails. The tool then reports
|
||||||
openai
|
# "PyMuPDF (fitz) is required..." even though PyMuPDF is a build input.
|
||||||
google-genai
|
# Forward the parent's sys.path so the child resolves the same modules.
|
||||||
tiktoken
|
# Mirrors nixpkgs' python-modules/vllm/0003-propagate-pythonpath.patch.
|
||||||
];
|
postPatch = ''
|
||||||
pdf = [
|
substituteInPlace src/zotero_mcp/tools/write.py \
|
||||||
pymupdf
|
--replace-fail \
|
||||||
ebooklib
|
'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" ];
|
pythonImportsCheck = [ "zotero_mcp" ];
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user