fastmcp-creds 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp_creds-0.1.0/.gitattributes +10 -0
- fastmcp_creds-0.1.0/.github/workflows/publish-test.yml +40 -0
- fastmcp_creds-0.1.0/.github/workflows/publish.yml +42 -0
- fastmcp_creds-0.1.0/.github/workflows/run-static.yml +58 -0
- fastmcp_creds-0.1.0/.github/workflows/run-tests.yml +51 -0
- fastmcp_creds-0.1.0/.gitignore +24 -0
- fastmcp_creds-0.1.0/.pre-commit-config.yaml +41 -0
- fastmcp_creds-0.1.0/CONTRIBUTING.md +124 -0
- fastmcp_creds-0.1.0/LICENSE +21 -0
- fastmcp_creds-0.1.0/PKG-INFO +265 -0
- fastmcp_creds-0.1.0/README.md +249 -0
- fastmcp_creds-0.1.0/fastmcp_creds/__init__.py +48 -0
- fastmcp_creds-0.1.0/fastmcp_creds/basic_auth.py +154 -0
- fastmcp_creds-0.1.0/fastmcp_creds/chain.py +5 -0
- fastmcp_creds-0.1.0/fastmcp_creds/env.py +5 -0
- fastmcp_creds-0.1.0/fastmcp_creds/header.py +86 -0
- fastmcp_creds-0.1.0/fastmcp_creds/keyring.py +11 -0
- fastmcp_creds-0.1.0/fastmcp_creds/protocol.py +5 -0
- fastmcp_creds-0.1.0/pyproject.toml +99 -0
- fastmcp_creds-0.1.0/tests/__init__.py +0 -0
- fastmcp_creds-0.1.0/tests/test_basic_auth.py +91 -0
- fastmcp_creds-0.1.0/tests/test_header.py +53 -0
- fastmcp_creds-0.1.0/tests/test_reexports.py +33 -0
- fastmcp_creds-0.1.0/uv.lock +2381 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Store and check out text files as LF on every platform, overriding a local
|
|
2
|
+
# core.autocrlf=true that would otherwise produce phantom EOL-only diffs.
|
|
3
|
+
# text=auto leaves detected binaries untouched; eol=lf pins the *working tree*.
|
|
4
|
+
* text=auto eol=lf
|
|
5
|
+
|
|
6
|
+
# cmd.exe reads batch files by byte offset, so LF-only endings break labels
|
|
7
|
+
# and goto. Do NOT add *.ps1 here: PowerShell is cross-platform, and CRLF
|
|
8
|
+
# breaks the shebang of a .ps1 meant to run on Linux/macOS.
|
|
9
|
+
*.bat text eol=crlf
|
|
10
|
+
*.cmd text eol=crlf
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Publish to TestPyPI
|
|
2
|
+
on:
|
|
3
|
+
workflow_dispatch:
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
test-pypi-publish:
|
|
7
|
+
name: Upload to TestPyPI
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
permissions:
|
|
10
|
+
id-token: write # For PyPI's trusted publishing
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout
|
|
13
|
+
uses: actions/checkout@v5
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
fetch-tags: true
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v6
|
|
20
|
+
|
|
21
|
+
- name: Build
|
|
22
|
+
run: uv build
|
|
23
|
+
|
|
24
|
+
- name: Check version format
|
|
25
|
+
run: |
|
|
26
|
+
# Extract version from the built package
|
|
27
|
+
VERSION=$(ls dist/*.whl | head -n1 | sed -E 's/.*-([0-9].*)-.*/\1/')
|
|
28
|
+
echo "Package version: $VERSION"
|
|
29
|
+
|
|
30
|
+
# Check if version contains dev/post markers (development version)
|
|
31
|
+
if [[ "$VERSION" =~ (dev|post.*dev) ]]; then
|
|
32
|
+
echo "❌ ERROR: Cannot publish development version: $VERSION"
|
|
33
|
+
echo "Please create a proper Git tag first (e.g., 'git tag v0.1.0')"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
echo "✅ Version $VERSION is valid for publishing"
|
|
38
|
+
|
|
39
|
+
- name: Publish to TestPyPI
|
|
40
|
+
run: uv publish --publish-url https://test.pypi.org/legacy/ -v dist/*
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
pypi-publish:
|
|
9
|
+
name: Upload to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # For PyPI's trusted publishing
|
|
13
|
+
steps:
|
|
14
|
+
- name: Checkout
|
|
15
|
+
uses: actions/checkout@v5
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
fetch-tags: true
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v6
|
|
22
|
+
|
|
23
|
+
- name: Build
|
|
24
|
+
run: uv build
|
|
25
|
+
|
|
26
|
+
- name: Check version format
|
|
27
|
+
run: |
|
|
28
|
+
# Extract version from the built package
|
|
29
|
+
VERSION=$(ls dist/*.whl | head -n1 | sed -E 's/.*-([0-9].*)-.*/\1/')
|
|
30
|
+
echo "Package version: $VERSION"
|
|
31
|
+
|
|
32
|
+
# Check if version contains dev/post markers (development version)
|
|
33
|
+
if [[ "$VERSION" =~ (dev|post.*dev) ]]; then
|
|
34
|
+
echo "❌ ERROR: Cannot publish development version: $VERSION"
|
|
35
|
+
echo "Please create a proper Git tag first (e.g., 'git tag v0.1.0')"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
echo "✅ Version $VERSION is valid for publishing"
|
|
40
|
+
|
|
41
|
+
- name: Publish to PyPI
|
|
42
|
+
run: uv publish -v dist/*
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Run static analysis
|
|
2
|
+
|
|
3
|
+
env:
|
|
4
|
+
# Enable colored output
|
|
5
|
+
PY_COLORS: 1
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: ["main"]
|
|
10
|
+
paths:
|
|
11
|
+
- "src/**"
|
|
12
|
+
- "tests/**"
|
|
13
|
+
- "uv.lock"
|
|
14
|
+
- "pyproject.toml"
|
|
15
|
+
- ".github/workflows/**"
|
|
16
|
+
|
|
17
|
+
# Run on all pull requests because these checks are required and will block merges otherwise
|
|
18
|
+
pull_request:
|
|
19
|
+
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
static_analysis:
|
|
27
|
+
timeout-minutes: 2
|
|
28
|
+
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v5
|
|
33
|
+
- name: Install uv
|
|
34
|
+
uses: astral-sh/setup-uv@v6
|
|
35
|
+
with:
|
|
36
|
+
enable-cache: true
|
|
37
|
+
cache-dependency-glob: "uv.lock"
|
|
38
|
+
|
|
39
|
+
- name: Install dependencies
|
|
40
|
+
run: uv sync
|
|
41
|
+
|
|
42
|
+
- name: Check lockfile is up to date
|
|
43
|
+
run: |
|
|
44
|
+
if ! uv lock --check; then
|
|
45
|
+
echo "❌ Lockfile is out of date!"
|
|
46
|
+
echo "To update the lockfile, run 'uv lock'."
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
echo "✅ Lockfile is up to date"
|
|
50
|
+
|
|
51
|
+
- name: Lint
|
|
52
|
+
run: uv run ruff check src tests
|
|
53
|
+
|
|
54
|
+
- name: Check formatting
|
|
55
|
+
run: uv run ruff format --check src tests
|
|
56
|
+
|
|
57
|
+
- name: Type checking
|
|
58
|
+
run: uv run ty check src
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Run tests
|
|
2
|
+
|
|
3
|
+
env:
|
|
4
|
+
# Enable colored output
|
|
5
|
+
PY_COLORS: 1
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: ["main"]
|
|
10
|
+
paths:
|
|
11
|
+
- "src/**"
|
|
12
|
+
- "tests/**"
|
|
13
|
+
- "uv.lock"
|
|
14
|
+
- "pyproject.toml"
|
|
15
|
+
- ".github/workflows/**"
|
|
16
|
+
|
|
17
|
+
# Run on all pull requests because these checks are required and will block merges otherwise
|
|
18
|
+
pull_request:
|
|
19
|
+
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
run_tests:
|
|
27
|
+
name: "Run tests: Python ${{ matrix.python-version }} on ${{ matrix.os }}"
|
|
28
|
+
runs-on: ${{ matrix.os }}
|
|
29
|
+
strategy:
|
|
30
|
+
matrix:
|
|
31
|
+
os: [ubuntu-latest, windows-latest]
|
|
32
|
+
python-version: ["3.10"]
|
|
33
|
+
fail-fast: false
|
|
34
|
+
timeout-minutes: 5
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v5
|
|
38
|
+
|
|
39
|
+
- name: Install uv
|
|
40
|
+
uses: astral-sh/setup-uv@v6
|
|
41
|
+
with:
|
|
42
|
+
enable-cache: true
|
|
43
|
+
cache-dependency-glob: "uv.lock"
|
|
44
|
+
python-version: ${{ matrix.python-version }}
|
|
45
|
+
|
|
46
|
+
- name: Install dependencies
|
|
47
|
+
# Run with frozen to use the current lockfile; static checks will determine if it needs updating
|
|
48
|
+
run: uv sync --frozen
|
|
49
|
+
|
|
50
|
+
- name: Run tests
|
|
51
|
+
run: uv run pytest
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
|
|
12
|
+
# Log files
|
|
13
|
+
*.log
|
|
14
|
+
|
|
15
|
+
# Coverage reports
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
|
|
20
|
+
# VSCode settings
|
|
21
|
+
.vscode/PythonImportHelper-v2-Completion.json
|
|
22
|
+
|
|
23
|
+
# Claude Code local settings (tool permissions, personal overrides — not shared)
|
|
24
|
+
.claude/settings.local.json
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
fail_fast: false
|
|
2
|
+
|
|
3
|
+
repos:
|
|
4
|
+
- repo: https://github.com/abravalheri/validate-pyproject
|
|
5
|
+
rev: v0.24.1
|
|
6
|
+
hooks:
|
|
7
|
+
- id: validate-pyproject
|
|
8
|
+
|
|
9
|
+
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
10
|
+
rev: v3.1.0
|
|
11
|
+
hooks:
|
|
12
|
+
- id: prettier
|
|
13
|
+
types_or: [yaml, json]
|
|
14
|
+
|
|
15
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
16
|
+
# Ruff version.
|
|
17
|
+
rev: v0.15.12
|
|
18
|
+
hooks:
|
|
19
|
+
# Run the linter.
|
|
20
|
+
- id: ruff-check
|
|
21
|
+
args: [--fix, --exit-non-zero-on-fix]
|
|
22
|
+
# Run the formatter.
|
|
23
|
+
- id: ruff-format
|
|
24
|
+
|
|
25
|
+
- repo: local
|
|
26
|
+
hooks:
|
|
27
|
+
- id: ty
|
|
28
|
+
name: ty check
|
|
29
|
+
entry: uv run ty check
|
|
30
|
+
language: system
|
|
31
|
+
types: [python]
|
|
32
|
+
files: ^src/|^tests/
|
|
33
|
+
pass_filenames: false
|
|
34
|
+
require_serial: true
|
|
35
|
+
|
|
36
|
+
- repo: https://github.com/codespell-project/codespell
|
|
37
|
+
rev: v2.4.1
|
|
38
|
+
hooks:
|
|
39
|
+
- id: codespell # See pyproject.toml for args
|
|
40
|
+
additional_dependencies:
|
|
41
|
+
- tomli
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Contributing <!-- omit in toc -->
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to `fastmcp-creds`! This document
|
|
4
|
+
provides guidelines for setting up a development environment, making changes,
|
|
5
|
+
and testing them locally before pushing to GitHub.
|
|
6
|
+
|
|
7
|
+
- [Development Setup](#development-setup)
|
|
8
|
+
- [Prerequisites](#prerequisites)
|
|
9
|
+
- [Clone and install](#clone-and-install)
|
|
10
|
+
- [Code Quality](#code-quality)
|
|
11
|
+
- [Enable automatic execution on git commit](#enable-automatic-execution-on-git-commit)
|
|
12
|
+
- [Manual execution](#manual-execution)
|
|
13
|
+
- [Testing](#testing)
|
|
14
|
+
- [Release Process](#release-process)
|
|
15
|
+
- [Testing a release first](#testing-a-release-first)
|
|
16
|
+
- [Building Packages Locally](#building-packages-locally)
|
|
17
|
+
- [Dependency Management](#dependency-management)
|
|
18
|
+
|
|
19
|
+
## Development Setup
|
|
20
|
+
|
|
21
|
+
### Prerequisites
|
|
22
|
+
|
|
23
|
+
- [Python 3.10](https://www.python.org/downloads) or later
|
|
24
|
+
- [uv](https://docs.astral.sh/uv/) — fast Python package manager
|
|
25
|
+
|
|
26
|
+
### Clone and install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/stephaneberle9/fastmcp-creds.git
|
|
30
|
+
cd fastmcp-creds
|
|
31
|
+
uv sync
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Code Quality
|
|
35
|
+
|
|
36
|
+
This project uses `pre-commit` hooks for static checks to maintain high code
|
|
37
|
+
quality standards:
|
|
38
|
+
|
|
39
|
+
| Hook | Purpose |
|
|
40
|
+
| ---- | ------- |
|
|
41
|
+
| `ruff-check` | Python linting (with auto-fix) |
|
|
42
|
+
| `ruff-format` | Python code formatting |
|
|
43
|
+
|
|
44
|
+
### Enable automatic execution on git commit
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv run pre-commit install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Manual execution
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Run all checks on all files
|
|
54
|
+
uv run pre-commit run --all-files
|
|
55
|
+
|
|
56
|
+
# Run individual tools
|
|
57
|
+
uv run ruff format src tests # Code formatting
|
|
58
|
+
uv run ruff check --fix src tests # Linting with auto-fix
|
|
59
|
+
uv run ty check src # Type checking
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Testing
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Run all tests with coverage
|
|
66
|
+
uv run pytest
|
|
67
|
+
|
|
68
|
+
# Run a specific test file
|
|
69
|
+
uv run pytest tests/test_chain.py
|
|
70
|
+
|
|
71
|
+
# Run with verbose output
|
|
72
|
+
uv run pytest -v
|
|
73
|
+
|
|
74
|
+
# Show only a coverage summary (skip HTML report)
|
|
75
|
+
uv run pytest --cov-report=term-missing --no-cov-on-fail
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Coverage is collected from `src/fastmcp_creds` and an HTML report is written
|
|
79
|
+
to `htmlcov/`.
|
|
80
|
+
|
|
81
|
+
## Release Process
|
|
82
|
+
|
|
83
|
+
1. Ensure all changes are committed and the `main` branch is up to date.
|
|
84
|
+
|
|
85
|
+
2. Create and push a version tag:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git tag v0.1.0
|
|
89
|
+
git push origin v0.1.0
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
3. Create a GitHub release from the tag and add release notes. The
|
|
93
|
+
`publish.yml` workflow triggers automatically and uploads the package to
|
|
94
|
+
PyPI.
|
|
95
|
+
|
|
96
|
+
The package version is derived automatically from the git tag by
|
|
97
|
+
[uv-dynamic-versioning](https://github.com/nicoddemus/uv-dynamic-versioning).
|
|
98
|
+
|
|
99
|
+
### Testing a release first
|
|
100
|
+
|
|
101
|
+
To publish to TestPyPI before a real release, trigger the `publish-test.yml`
|
|
102
|
+
workflow manually from the **Actions** tab on GitHub (select **Run workflow**).
|
|
103
|
+
|
|
104
|
+
## Building Packages Locally
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv build
|
|
108
|
+
ls dist/
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
This produces both a wheel (`.whl`) and a source distribution (`.tar.gz`).
|
|
112
|
+
|
|
113
|
+
## Dependency Management
|
|
114
|
+
|
|
115
|
+
Dependencies are pinned in `uv.lock` for reproducible installs. After adding or
|
|
116
|
+
changing dependencies in `pyproject.toml`:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
uv lock # update the lockfile
|
|
120
|
+
uv sync # sync your environment to the new lockfile
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> [!IMPORTANT]
|
|
124
|
+
> Always commit both `pyproject.toml` and `uv.lock` together.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stephan Eberle
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fastmcp-creds
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Credential provider chain for FastMCP servers: keyring, environment variables, and HTTP Basic Auth header providers with unresolved placeholder detection.
|
|
5
|
+
Project-URL: Homepage, https://github.com/stephaneberle9/fastmcp-creds
|
|
6
|
+
Project-URL: Repository, https://github.com/stephaneberle9/fastmcp-creds
|
|
7
|
+
Project-URL: Issues, https://github.com/stephaneberle9/fastmcp-creds/issues
|
|
8
|
+
Author-email: Stephan Eberle <stephaneberle9@gmail.com>
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: credchain>=0.1.0
|
|
12
|
+
Requires-Dist: fastmcp<5,>=4.0.0
|
|
13
|
+
Provides-Extra: keyring
|
|
14
|
+
Requires-Dist: credchain[keyring]; extra == 'keyring'
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# fastmcp-creds <!-- omit in toc -->
|
|
18
|
+
|
|
19
|
+
Pluggable credential management for FastMCP servers — supply API tokens or
|
|
20
|
+
username/password credentials for the backend services they connect to, from
|
|
21
|
+
environment variables, OS keychain, or HTTP headers, with automatic fallback
|
|
22
|
+
through a priority chain.
|
|
23
|
+
|
|
24
|
+
The framework-neutral parts — the chain and the environment and keychain
|
|
25
|
+
providers — live in [credchain](https://github.com/stephaneberle9/python-credchain)
|
|
26
|
+
and are re-exported here unchanged, so existing imports keep working. This
|
|
27
|
+
package adds the providers that read FastMCP's HTTP request headers.
|
|
28
|
+
|
|
29
|
+
- [Installation](#installation)
|
|
30
|
+
- [Usage](#usage)
|
|
31
|
+
- [Credential provider chain](#credential-provider-chain)
|
|
32
|
+
- [Environment variables](#environment-variables)
|
|
33
|
+
- [HTTP Basic Auth header](#http-basic-auth-header)
|
|
34
|
+
- [Custom HTTP headers](#custom-http-headers)
|
|
35
|
+
- [OS keychain](#os-keychain)
|
|
36
|
+
- [API Reference](#api-reference)
|
|
37
|
+
- [`CredentialsProvider`](#credentialsprovider)
|
|
38
|
+
- [`CredentialsProviderChain`](#credentialsproviderchain)
|
|
39
|
+
- [`EnvironmentCredentialsProvider`](#environmentcredentialsprovider)
|
|
40
|
+
- [`BasicAuthCredentialsProvider`](#basicauthcredentialsprovider)
|
|
41
|
+
- [`CustomHeaderCredentialsProvider`](#customheadercredentialsprovider)
|
|
42
|
+
- [`KeyringCredentialsProvider`](#keyringcredentialsprovider)
|
|
43
|
+
- [`BasicAuthError`](#basicautherror)
|
|
44
|
+
- [`is_unresolved_placeholder_value`](#is_unresolved_placeholder_value)
|
|
45
|
+
- [Contributing](#contributing)
|
|
46
|
+
- [License](#license)
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install fastmcp-creds
|
|
52
|
+
# or, with uv:
|
|
53
|
+
uv add fastmcp-creds
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The `KeyringCredentialsProvider` requires the optional `keyring` extra:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install fastmcp-creds[keyring]
|
|
60
|
+
# or, with uv:
|
|
61
|
+
uv add fastmcp-creds[keyring]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Requires Python 3.10+.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
### Credential provider chain
|
|
69
|
+
|
|
70
|
+
Compose providers in priority order. The chain tries each in sequence and
|
|
71
|
+
returns the first result where both username and password (or token) are
|
|
72
|
+
non-empty.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from fastmcp_creds import (
|
|
76
|
+
CredentialsProviderChain,
|
|
77
|
+
BasicAuthCredentialsProvider,
|
|
78
|
+
EnvironmentCredentialsProvider,
|
|
79
|
+
)
|
|
80
|
+
from fastmcp_creds.keyring import KeyringCredentialsProvider # optional dep
|
|
81
|
+
|
|
82
|
+
chain = CredentialsProviderChain([
|
|
83
|
+
KeyringCredentialsProvider("my-service"),
|
|
84
|
+
BasicAuthCredentialsProvider(),
|
|
85
|
+
EnvironmentCredentialsProvider("MY_USERNAME", "MY_PASSWORD"),
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
username, password = chain.get_credentials()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Environment variables
|
|
92
|
+
|
|
93
|
+
Read username and password from two environment variables:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from fastmcp_creds import EnvironmentCredentialsProvider
|
|
97
|
+
|
|
98
|
+
provider = EnvironmentCredentialsProvider("MY_USERNAME", "MY_PASSWORD")
|
|
99
|
+
username, password = provider.get_credentials()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
For a single token variable:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
provider = EnvironmentCredentialsProvider.for_token("MY_API_TOKEN")
|
|
106
|
+
token, _ = provider.get_credentials()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### HTTP Basic Auth header
|
|
110
|
+
|
|
111
|
+
Decode credentials from the standard `Authorization: Basic <base64>` header.
|
|
112
|
+
Also accepts the non-compliant `Bearer Basic <base64>` variant emitted by MCP
|
|
113
|
+
Inspector, and raw base64 without a scheme prefix.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from fastmcp_creds import BasicAuthCredentialsProvider
|
|
117
|
+
|
|
118
|
+
provider = BasicAuthCredentialsProvider()
|
|
119
|
+
username, password = provider.get_credentials()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Optionally check a custom header first before falling back to `Authorization`:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
provider = BasicAuthCredentialsProvider(custom_header_name="X-My-Auth")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Custom HTTP headers
|
|
129
|
+
|
|
130
|
+
Read credentials from two plain-text HTTP headers:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from fastmcp_creds import CustomHeaderCredentialsProvider
|
|
134
|
+
|
|
135
|
+
provider = CustomHeaderCredentialsProvider("X-Username", "X-Password")
|
|
136
|
+
username, password = provider.get_credentials()
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
For a single token header:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
provider = CustomHeaderCredentialsProvider.for_token("X-Api-Token")
|
|
143
|
+
token, _ = provider.get_credentials()
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### OS keychain
|
|
147
|
+
|
|
148
|
+
Read credentials stored in the OS keychain via
|
|
149
|
+
[`keyring`](https://pypi.org/project/keyring/). Requires `fastmcp-creds[keyring]`.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from fastmcp_creds.keyring import KeyringCredentialsProvider
|
|
153
|
+
|
|
154
|
+
# Two-key mode: reads "username" and "password" entries under the service name
|
|
155
|
+
provider = KeyringCredentialsProvider("my-service")
|
|
156
|
+
username, password = provider.get_credentials()
|
|
157
|
+
|
|
158
|
+
# Token mode: reads a single "token" entry
|
|
159
|
+
provider = KeyringCredentialsProvider.for_token("my-service")
|
|
160
|
+
token, _ = provider.get_credentials()
|
|
161
|
+
|
|
162
|
+
# Custom key names
|
|
163
|
+
provider = KeyringCredentialsProvider("my-service", username_key="user", password_key="secret")
|
|
164
|
+
provider = KeyringCredentialsProvider.for_token("my-service", token_key="jwt")
|
|
165
|
+
|
|
166
|
+
# Custom lookup timeout in seconds (default 5), or None to wait indefinitely
|
|
167
|
+
provider = KeyringCredentialsProvider("my-service", timeout=10)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The keychain lookup is bounded by a timeout, so a locked or unresponsive
|
|
171
|
+
keychain (e.g. one waiting for an unlock prompt in a headless session) cannot
|
|
172
|
+
stall server startup. On timeout, a warning is logged and `(None, None)` is
|
|
173
|
+
returned, so a `CredentialsProviderChain` falls through to its next provider.
|
|
174
|
+
|
|
175
|
+
## API Reference
|
|
176
|
+
|
|
177
|
+
### `CredentialsProvider`
|
|
178
|
+
|
|
179
|
+
`CredentialsProvider` is a `Protocol` — any object with a
|
|
180
|
+
`get_credentials() -> tuple[str | None, str | None]` method satisfies it.
|
|
181
|
+
|
|
182
|
+
### `CredentialsProviderChain`
|
|
183
|
+
|
|
184
|
+
`CredentialsProviderChain(providers)` — tries each provider in order and
|
|
185
|
+
returns the first result where both values are non-empty. Returns
|
|
186
|
+
`(None, None)` if no provider succeeds.
|
|
187
|
+
|
|
188
|
+
| Method | Description |
|
|
189
|
+
| --- | --- |
|
|
190
|
+
| `get_credentials()` | Returns `tuple[str \| None, str \| None]` — `(username, password)` or `(token, token)` |
|
|
191
|
+
|
|
192
|
+
### `EnvironmentCredentialsProvider`
|
|
193
|
+
|
|
194
|
+
`EnvironmentCredentialsProvider(username_env_var_name, password_env_var_name)`
|
|
195
|
+
— reads two environment variables.
|
|
196
|
+
|
|
197
|
+
`EnvironmentCredentialsProvider.for_token(token_env_var_name)` — reads a
|
|
198
|
+
single environment variable; returns it as both elements of the tuple.
|
|
199
|
+
|
|
200
|
+
| Method | Description |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `get_credentials()` | Returns `tuple[str \| None, str \| None]` |
|
|
203
|
+
|
|
204
|
+
### `BasicAuthCredentialsProvider`
|
|
205
|
+
|
|
206
|
+
`BasicAuthCredentialsProvider(custom_header_name=None)` — extracts credentials
|
|
207
|
+
from an HTTP Basic Auth header. Checks `custom_header_name` first if provided,
|
|
208
|
+
then falls back to the standard `Authorization` header. Silently ignores
|
|
209
|
+
unresolved MCP client placeholders (e.g. `${MY_VAR}`).
|
|
210
|
+
|
|
211
|
+
| Method | Description |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| `get_credentials()` | Returns `tuple[str \| None, str \| None]`; raises `BasicAuthError` on malformed headers |
|
|
214
|
+
|
|
215
|
+
### `CustomHeaderCredentialsProvider`
|
|
216
|
+
|
|
217
|
+
`CustomHeaderCredentialsProvider(username_header_name, password_header_name)`
|
|
218
|
+
— reads credentials from two plain-text HTTP headers. Ignores unresolved
|
|
219
|
+
placeholders.
|
|
220
|
+
|
|
221
|
+
`CustomHeaderCredentialsProvider.for_token(token_header_name)` — reads a
|
|
222
|
+
single header; returns it as both elements of the tuple.
|
|
223
|
+
|
|
224
|
+
| Method | Description |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `get_credentials()` | Returns `tuple[str \| None, str \| None]` |
|
|
227
|
+
|
|
228
|
+
### `KeyringCredentialsProvider`
|
|
229
|
+
|
|
230
|
+
`KeyringCredentialsProvider(service, username_key="username", password_key="password", timeout=5.0)`
|
|
231
|
+
— reads credentials stored in the OS keychain under the given service name.
|
|
232
|
+
Returns `(None, None)` if the keychain does not answer within `timeout` seconds
|
|
233
|
+
(`None` waits indefinitely). Requires `fastmcp-creds[keyring]`.
|
|
234
|
+
|
|
235
|
+
`KeyringCredentialsProvider.for_token(service, token_key="token", timeout=5.0)`
|
|
236
|
+
— reads a single keychain entry.
|
|
237
|
+
|
|
238
|
+
| Method | Description |
|
|
239
|
+
| --- | --- |
|
|
240
|
+
| `get_credentials()` | Returns `tuple[str \| None, str \| None]` |
|
|
241
|
+
|
|
242
|
+
### `BasicAuthError`
|
|
243
|
+
|
|
244
|
+
`BasicAuthError(message, header_name)` — raised by `BasicAuthCredentialsProvider`
|
|
245
|
+
when a header is present but malformed (bad base64, unsupported scheme, missing
|
|
246
|
+
colon separator, empty username or password).
|
|
247
|
+
|
|
248
|
+
| Field | Type | Description |
|
|
249
|
+
| --- | --- | --- |
|
|
250
|
+
| `header_name` | `str` | Name of the header that caused the error |
|
|
251
|
+
|
|
252
|
+
### `is_unresolved_placeholder_value`
|
|
253
|
+
|
|
254
|
+
`is_unresolved_placeholder_value(value: str) -> bool` — returns `True` when a
|
|
255
|
+
header value is an unresolved MCP client placeholder such as `${MY_VAR}` or
|
|
256
|
+
`Basic ${MY_VAR}`. Used internally by `BasicAuthCredentialsProvider` and
|
|
257
|
+
`CustomHeaderCredentialsProvider`; exposed for custom provider implementations.
|
|
258
|
+
|
|
259
|
+
## Contributing
|
|
260
|
+
|
|
261
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT — see [LICENSE](LICENSE).
|