keycardai-fastmcp 0.2.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.
- keycardai_fastmcp-0.2.0/.gitignore +193 -0
- keycardai_fastmcp-0.2.0/CHANGELOG.md +65 -0
- keycardai_fastmcp-0.2.0/PKG-INFO +78 -0
- keycardai_fastmcp-0.2.0/README.md +42 -0
- keycardai_fastmcp-0.2.0/examples/delegated_access/.python-version +1 -0
- keycardai_fastmcp-0.2.0/examples/delegated_access/README.md +187 -0
- keycardai_fastmcp-0.2.0/examples/delegated_access/main.py +166 -0
- keycardai_fastmcp-0.2.0/examples/delegated_access/pyproject.toml +17 -0
- keycardai_fastmcp-0.2.0/examples/hello_world_server/.python-version +1 -0
- keycardai_fastmcp-0.2.0/examples/hello_world_server/README.md +72 -0
- keycardai_fastmcp-0.2.0/examples/hello_world_server/main.py +46 -0
- keycardai_fastmcp-0.2.0/examples/hello_world_server/pyproject.toml +16 -0
- keycardai_fastmcp-0.2.0/pyproject.toml +125 -0
- keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/__init__.py +151 -0
- keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/provider.py +778 -0
- keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/testing/__init__.py +36 -0
- keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/testing/test_utils.py +98 -0
- keycardai_fastmcp-0.2.0/tests/conftest.py +3 -0
- keycardai_fastmcp-0.2.0/tests/fixtures/__init__.py +1 -0
- keycardai_fastmcp-0.2.0/tests/fixtures/auth_provider.py +138 -0
- keycardai_fastmcp-0.2.0/tests/integration/__init__.py +6 -0
- keycardai_fastmcp-0.2.0/tests/integration/test_auth_provider.py +182 -0
- keycardai_fastmcp-0.2.0/tests/integration/test_grant_decorator.py +374 -0
- keycardai_fastmcp-0.2.0/tests/keycardai/mcp/integrations/fastmcp/test_provider.py +516 -0
- keycardai_fastmcp-0.2.0/tests/test_examples.py +80 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
env/
|
|
126
|
+
venv/
|
|
127
|
+
ENV/
|
|
128
|
+
env.bak/
|
|
129
|
+
venv.bak/
|
|
130
|
+
|
|
131
|
+
# Spyder project settings
|
|
132
|
+
.spyderproject
|
|
133
|
+
.spyproject
|
|
134
|
+
|
|
135
|
+
# Rope project settings
|
|
136
|
+
.ropeproject
|
|
137
|
+
|
|
138
|
+
# mkdocs documentation
|
|
139
|
+
/site
|
|
140
|
+
|
|
141
|
+
# mypy
|
|
142
|
+
.mypy_cache/
|
|
143
|
+
.dmypy.json
|
|
144
|
+
dmypy.json
|
|
145
|
+
|
|
146
|
+
# Pyre type checker
|
|
147
|
+
.pyre/
|
|
148
|
+
|
|
149
|
+
# pytype static type analyzer
|
|
150
|
+
.pytype/
|
|
151
|
+
|
|
152
|
+
# Cython debug symbols
|
|
153
|
+
cython_debug/
|
|
154
|
+
|
|
155
|
+
# PyCharm
|
|
156
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
157
|
+
# be added to the global gitignore or merged into this project gitignore. For a PyCharm
|
|
158
|
+
# project, uncomment below!
|
|
159
|
+
#.idea/
|
|
160
|
+
|
|
161
|
+
# VS Code
|
|
162
|
+
.vscode/
|
|
163
|
+
|
|
164
|
+
# OS generated files
|
|
165
|
+
.DS_Store
|
|
166
|
+
.DS_Store?
|
|
167
|
+
._*
|
|
168
|
+
.Spotlight-V100
|
|
169
|
+
.Trashes
|
|
170
|
+
ehthumbs.db
|
|
171
|
+
Thumbs.db
|
|
172
|
+
|
|
173
|
+
# Backup files
|
|
174
|
+
*~
|
|
175
|
+
*.bak
|
|
176
|
+
*.backup
|
|
177
|
+
*.old
|
|
178
|
+
*.orig
|
|
179
|
+
|
|
180
|
+
# Temporary files
|
|
181
|
+
*.tmp
|
|
182
|
+
*.temp
|
|
183
|
+
|
|
184
|
+
# IDE files
|
|
185
|
+
*.swp
|
|
186
|
+
*.swo
|
|
187
|
+
*~
|
|
188
|
+
|
|
189
|
+
# Local development
|
|
190
|
+
.local/
|
|
191
|
+
|
|
192
|
+
# Claude Code
|
|
193
|
+
CLAUDE.md
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
## 0.2.0-keycardai-fastmcp (2026-04-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
- feat(keycardai-fastmcp): rename keycardai-mcp-fastmcp via bridge package (ACC-233) (#102)
|
|
5
|
+
- * feat(keycardai-fastmcp): rename keycardai-mcp-fastmcp via bridge package (ACC-233)
|
|
6
|
+
- The current name carries a redundant -mcp suffix (FastMCP only speaks MCP,
|
|
7
|
+
so the protocol tag adds no information). Renames to keycardai-fastmcp per
|
|
8
|
+
the revised KEP, with keycardai-mcp-fastmcp preserved as a deprecation
|
|
9
|
+
bridge so the customer in production on the old name keeps working
|
|
10
|
+
indefinitely.
|
|
11
|
+
- What ships:
|
|
12
|
+
- * New keycardai-fastmcp package at packages/fastmcp/, full implementation
|
|
13
|
+
under the keycardai.fastmcp namespace. Tests, examples, README move with
|
|
14
|
+
the source. Wired into the workspace and the release.yml tag filter.
|
|
15
|
+
* Deprecated keycardai-mcp-fastmcp now depends only on keycardai-fastmcp
|
|
16
|
+
and re-exports every public symbol at the original
|
|
17
|
+
keycardai.mcp.integrations.fastmcp.* paths. Importing the top-level
|
|
18
|
+
module emits a DeprecationWarning pointing at the canonical name.
|
|
19
|
+
* Bridge contract test (test_bridge.py, 4 tests) asserts the
|
|
20
|
+
DeprecationWarning fires and that bridge symbols are identity-equal to
|
|
21
|
+
the canonical ones. The full behavioral suite lives in keycardai-fastmcp
|
|
22
|
+
going forward.
|
|
23
|
+
- Customer impact: pip install keycardai-mcp-fastmcp keeps working; the
|
|
24
|
+
package transitively pulls keycardai-fastmcp. No forced removal timeline,
|
|
25
|
+
the bridge ships until every known caller migrates.
|
|
26
|
+
- Verified: ruff clean. fastmcp 51/51, mcp-fastmcp bridge 4/4, mcp 560/560,
|
|
27
|
+
oauth 208/208, starlette 49/49.
|
|
28
|
+
- Supersedes the canceled ACC-195 (which used the now-rejected
|
|
29
|
+
keycardai-fastmcp-mcp name).
|
|
30
|
+
- * fix(keycardai-fastmcp): bridge re-exports the full canonical surface
|
|
31
|
+
- Review caught that the bridge provider.py only re-exported a hand-enumerated
|
|
32
|
+
subset of the canonical surface, dropping documented public symbols
|
|
33
|
+
(get_token_debug_info, introspect, INTROSPECT, AuthProviderConfigurationError,
|
|
34
|
+
AuthProviderInternalError, AuthProviderRemoteError). Importing any of those
|
|
35
|
+
from keycardai.mcp.integrations.fastmcp.provider raised ImportError, breaking
|
|
36
|
+
the bridge contract for downstream callers using less common symbols.
|
|
37
|
+
- Fixes:
|
|
38
|
+
- - Add __all__ to keycardai.fastmcp.provider listing the 28-name public
|
|
39
|
+
surface. Stdlib/typing helpers (logging, os, urlparse, wraps, Any,
|
|
40
|
+
Callable, etc.) are deliberately excluded.
|
|
41
|
+
- Replace the bridge provider.py hand-enumeration with
|
|
42
|
+
``from keycardai.fastmcp.provider import *``, plus a re-export of __all__
|
|
43
|
+
so future symbol additions to the canonical module flow through
|
|
44
|
+
automatically.
|
|
45
|
+
- Add test_bridge_provider_exposes_full_public_surface: iterates the
|
|
46
|
+
canonical __all__, asserts every symbol is present at the bridge path
|
|
47
|
+
and identity-equal to the canonical reference. Regression test for the
|
|
48
|
+
symbol-drop class of bug.
|
|
49
|
+
- Scrub em dashes from the renamed example READMEs (pre-existing prose,
|
|
50
|
+
but new file paths shipping under our review).
|
|
51
|
+
- Verified: fastmcp 51/51, mcp-fastmcp bridge 5/5 (was 4 + 1 new). Smoke:
|
|
52
|
+
the six previously-missing symbols now import cleanly from the old path.
|
|
53
|
+
- * ci: pin extractions/setup-just version
|
|
54
|
+
- The action stopped resolving "latest" sometime today and started failing
|
|
55
|
+
with `no release for just matching version specifier`. Pinning unblocks
|
|
56
|
+
PR validation and the post-merge bump-and-publish pipeline.
|
|
57
|
+
- 1.50.0 is the current stable just release (April 2026).
|
|
58
|
+
- * ci: replace extractions/setup-just with the upstream install script
|
|
59
|
+
- extractions/setup-just@v2 is currently broken for both unpinned and
|
|
60
|
+
explicit-version requests ("no release for just matching version
|
|
61
|
+
specifier"). Pinning to 1.50.0 did not help because the action regression
|
|
62
|
+
is in its release lookup, not its version resolution.
|
|
63
|
+
- Switch to the just.systems install script (the project owners ship and
|
|
64
|
+
maintain it). Runs as a plain bash step with no third-party action
|
|
65
|
+
dependency and is unaffected by setup-just regressions.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keycardai-fastmcp
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: FastMCP integration for Keycard OAuth client with automated token exchange and authentication
|
|
5
|
+
Project-URL: Homepage, https://github.com/keycardai/python-sdk
|
|
6
|
+
Project-URL: Repository, https://github.com/keycardai/python-sdk
|
|
7
|
+
Project-URL: Documentation, https://docs.keycardai.com
|
|
8
|
+
Project-URL: Issues, https://github.com/keycardai/python-sdk/issues
|
|
9
|
+
Author-email: Keycard <support@keycard.ai>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: authentication,fastmcp,keycard,mcp,model-context-protocol,oauth,token-exchange
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Session
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: fastmcp>=3.0.0
|
|
27
|
+
Requires-Dist: httpx>=0.27.2
|
|
28
|
+
Requires-Dist: keycardai-mcp>=0.15.0
|
|
29
|
+
Requires-Dist: keycardai-oauth>=0.7.0
|
|
30
|
+
Requires-Dist: pydantic-settings>=2.7.1
|
|
31
|
+
Requires-Dist: pydantic>=2.11.7
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: pytest-asyncio>=1.1.0; extra == 'test'
|
|
34
|
+
Requires-Dist: pytest>=8.4.1; extra == 'test'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# keycardai-fastmcp
|
|
38
|
+
|
|
39
|
+
FastMCP integration for Keycard OAuth: protect FastMCP servers with Keycard
|
|
40
|
+
authentication and run delegated OAuth 2.0 token exchange (RFC 8693) for
|
|
41
|
+
downstream APIs.
|
|
42
|
+
|
|
43
|
+
This is the canonical home for the integration; `keycardai-mcp-fastmcp` is
|
|
44
|
+
preserved as a deprecation bridge for callers still on the old name.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install keycardai-fastmcp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from fastmcp import FastMCP, Context
|
|
56
|
+
from keycardai.fastmcp import AuthProvider
|
|
57
|
+
|
|
58
|
+
auth_provider = AuthProvider(
|
|
59
|
+
zone_id="abc1234",
|
|
60
|
+
mcp_server_name="My Server",
|
|
61
|
+
mcp_base_url="http://localhost:8000",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
mcp = FastMCP("My Server", auth=auth_provider.get_remote_auth_provider())
|
|
65
|
+
|
|
66
|
+
@mcp.tool()
|
|
67
|
+
@auth_provider.grant("https://api.example.com")
|
|
68
|
+
async def call_external_api(ctx: Context, query: str):
|
|
69
|
+
keycardai = await ctx.get_state("keycardai")
|
|
70
|
+
token = keycardai.access("https://api.example.com").access_token
|
|
71
|
+
return f"Results for {query} (token starts with {token[:8]})"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Migration from `keycardai-mcp-fastmcp`
|
|
75
|
+
|
|
76
|
+
The old package keeps working: `from keycardai.mcp.integrations.fastmcp import AuthProvider`
|
|
77
|
+
emits a `DeprecationWarning` pointing here and returns the same class. Migrate
|
|
78
|
+
when convenient.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# keycardai-fastmcp
|
|
2
|
+
|
|
3
|
+
FastMCP integration for Keycard OAuth: protect FastMCP servers with Keycard
|
|
4
|
+
authentication and run delegated OAuth 2.0 token exchange (RFC 8693) for
|
|
5
|
+
downstream APIs.
|
|
6
|
+
|
|
7
|
+
This is the canonical home for the integration; `keycardai-mcp-fastmcp` is
|
|
8
|
+
preserved as a deprecation bridge for callers still on the old name.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pip install keycardai-fastmcp
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from fastmcp import FastMCP, Context
|
|
20
|
+
from keycardai.fastmcp import AuthProvider
|
|
21
|
+
|
|
22
|
+
auth_provider = AuthProvider(
|
|
23
|
+
zone_id="abc1234",
|
|
24
|
+
mcp_server_name="My Server",
|
|
25
|
+
mcp_base_url="http://localhost:8000",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
mcp = FastMCP("My Server", auth=auth_provider.get_remote_auth_provider())
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
@auth_provider.grant("https://api.example.com")
|
|
32
|
+
async def call_external_api(ctx: Context, query: str):
|
|
33
|
+
keycardai = await ctx.get_state("keycardai")
|
|
34
|
+
token = keycardai.access("https://api.example.com").access_token
|
|
35
|
+
return f"Results for {query} (token starts with {token[:8]})"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Migration from `keycardai-mcp-fastmcp`
|
|
39
|
+
|
|
40
|
+
The old package keeps working: `from keycardai.mcp.integrations.fastmcp import AuthProvider`
|
|
41
|
+
emits a `DeprecationWarning` pointing here and returns the same class. Migrate
|
|
42
|
+
when convenient.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# GitHub API Integration with Keycard Delegated Access
|
|
2
|
+
|
|
3
|
+
A complete example demonstrating how to use the `@grant` decorator for token exchange, enabling your MCP server to access external APIs (GitHub) on behalf of authenticated users.
|
|
4
|
+
|
|
5
|
+
## Why Keycard?
|
|
6
|
+
|
|
7
|
+
Keycard lets you securely connect your AI IDE or agent to external resources. With delegated access, your MCP server can:
|
|
8
|
+
|
|
9
|
+
- **Exchange user tokens** for API-specific access tokens via OAuth 2.0 Token Exchange
|
|
10
|
+
- **Access external APIs** on behalf of authenticated users with proper scopes
|
|
11
|
+
- **Maintain audit trails** of all delegated operations
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
Before running this example, set up Keycard for delegated access:
|
|
16
|
+
|
|
17
|
+
### 1. Sign up at [keycard.ai](https://keycard.ai)
|
|
18
|
+
|
|
19
|
+
### 2. Create a Zone
|
|
20
|
+
|
|
21
|
+
A zone is your authentication boundary. Create one in the Keycard console.
|
|
22
|
+
|
|
23
|
+
### 3. Configure an Identity Provider
|
|
24
|
+
|
|
25
|
+
Set up an identity provider (Google, Microsoft, etc.) for user authentication.
|
|
26
|
+
|
|
27
|
+
### 4. Create a GitHub App
|
|
28
|
+
|
|
29
|
+
Create a GitHub App in your organization (or personal account) to enable delegated access:
|
|
30
|
+
|
|
31
|
+
1. Go to your GitHub organization → **Settings** → **Developer settings** → **GitHub Apps**
|
|
32
|
+
2. Click **New GitHub App**
|
|
33
|
+
3. Configure the app with the permissions your MCP server needs (e.g., **Repository** access, **User** profile read)
|
|
34
|
+
4. Generate a **Client Secret**
|
|
35
|
+
5. Note down the **Client ID** and **Client Secret**; you'll use these to configure the credential provider in Keycard
|
|
36
|
+
|
|
37
|
+
### 5. Configure a Credential Provider in Keycard
|
|
38
|
+
|
|
39
|
+
Set up the GitHub credential provider so Keycard can obtain tokens on behalf of users:
|
|
40
|
+
|
|
41
|
+
1. In your Keycard zone, go to **Providers**
|
|
42
|
+
2. Add a new GitHub credential provider using the **Client ID** and **Client Secret** from the GitHub App you created in step 4
|
|
43
|
+
3. This credential provider is what Keycard uses to issue GitHub API tokens on behalf of authenticated users
|
|
44
|
+
|
|
45
|
+
### 6. Configure GitHub as an API Resource
|
|
46
|
+
|
|
47
|
+
Add GitHub as an API resource in your zone:
|
|
48
|
+
|
|
49
|
+
1. In your Keycard zone, go to **Resources**
|
|
50
|
+
2. Add a new API resource for GitHub:
|
|
51
|
+
- **Resource URL**: `https://api.github.com`
|
|
52
|
+
- **Credential Provider**: Select the GitHub credential provider you created in step 5
|
|
53
|
+
- **Scopes**: `read:user`, `repo` (adjust based on your needs)
|
|
54
|
+
|
|
55
|
+
### 7. Create an Application
|
|
56
|
+
|
|
57
|
+
Create an application that will represent your MCP server:
|
|
58
|
+
|
|
59
|
+
1. Go to **Applications** in your zone
|
|
60
|
+
2. Create a new application
|
|
61
|
+
- **Identifier**: Set this to match your `MCP_SERVER_URL` (e.g., `http://localhost:8000/`)
|
|
62
|
+
3. Add the **GitHub API** resource as a dependency of this application
|
|
63
|
+
4. Generate **Application Credentials** (Client ID and Client Secret)
|
|
64
|
+
- These are what you'll use for `KEYCARD_CLIENT_ID` and `KEYCARD_CLIENT_SECRET`
|
|
65
|
+
|
|
66
|
+
### 8. Create an MCP Server Resource
|
|
67
|
+
|
|
68
|
+
Register your MCP server with Keycard:
|
|
69
|
+
|
|
70
|
+
1. Go to **Resources** and add a new MCP Server resource
|
|
71
|
+
2. Set the URL to your server's MCP endpoint: `http://localhost:8000/mcp`
|
|
72
|
+
3. Configure the resource:
|
|
73
|
+
- **Provided by**: Select the application you created in step 7
|
|
74
|
+
- **Credential Provider**: Keycard STS Zone Provider
|
|
75
|
+
|
|
76
|
+
> **Note:** Delegated token exchange requires Keycard to reach your MCP server. For local development, use a tunneling service (e.g., ngrok, Cloudflare Tunnel) or host the server on a publicly accessible URL.
|
|
77
|
+
|
|
78
|
+
See [Delegated Access Setup](https://docs.keycard.ai/build-with-keycard/delegated-access) for detailed instructions.
|
|
79
|
+
|
|
80
|
+
## Quick Start
|
|
81
|
+
|
|
82
|
+
### 1. Set Up Tunneling (for local development)
|
|
83
|
+
|
|
84
|
+
Delegated access requires Keycard to reach your server. For local development, set up a tunnel:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Using ngrok
|
|
88
|
+
ngrok http 8000
|
|
89
|
+
|
|
90
|
+
# Or using Cloudflare Tunnel
|
|
91
|
+
cloudflared tunnel --url http://localhost:8000
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use the public URL from your tunnel as `MCP_SERVER_URL`.
|
|
95
|
+
|
|
96
|
+
### 2. Set Environment Variables
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
export KEYCARD_ZONE_ID="your-zone-id" # or use KEYCARD_ZONE_URL
|
|
100
|
+
export KEYCARD_CLIENT_ID="your-client-id"
|
|
101
|
+
export KEYCARD_CLIENT_SECRET="your-client-secret"
|
|
102
|
+
export MCP_SERVER_URL="https://your-tunnel-url.ngrok.io/" # Must be publicly reachable
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. Install Dependencies and Run the Server
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd packages/mcp-fastmcp/examples/delegated_access
|
|
109
|
+
uv sync
|
|
110
|
+
uv run python main.py
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The server will start on `http://localhost:8000`.
|
|
114
|
+
|
|
115
|
+
### 5. Verify the Server
|
|
116
|
+
|
|
117
|
+
Check that OAuth metadata is being served:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
curl http://localhost:8000/.well-known/oauth-authorization-server
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
You should see JSON with `issuer`, `authorization_endpoint`, and other OAuth metadata.
|
|
124
|
+
|
|
125
|
+
## Testing with MCP Client
|
|
126
|
+
|
|
127
|
+
1. Connect to your server using an MCP-compatible client (e.g., Cursor, Claude Desktop)
|
|
128
|
+
2. Authenticate through your configured identity provider
|
|
129
|
+
3. When prompted by Keycard, authorize GitHub access
|
|
130
|
+
4. Call the `get_github_user` or `list_github_repos` tools
|
|
131
|
+
5. Verify GitHub user data is returned
|
|
132
|
+
|
|
133
|
+
## How It Works
|
|
134
|
+
|
|
135
|
+
### Token Exchange Flow
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
User MCP Server Keycard GitHub
|
|
139
|
+
│ │ │ │
|
|
140
|
+
│──── Authenticate ──────►│ │ │
|
|
141
|
+
│ │◄── User Token ───────│ │
|
|
142
|
+
│ │ │ │
|
|
143
|
+
│──── Call Tool ─────────►│ │ │
|
|
144
|
+
│ │── Exchange Token ───►│ │
|
|
145
|
+
│ │◄─ GitHub Token ──────│ │
|
|
146
|
+
│ │ │ │
|
|
147
|
+
│ │──────────────────────┼── API Request ───────►│
|
|
148
|
+
│ │◄─────────────────────┼── API Response ───────│
|
|
149
|
+
│◄─── Tool Result ────────│ │ │
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
1. User authenticates to your MCP server via Keycard
|
|
153
|
+
2. When a tool with `@grant` is called, Keycard exchanges the user's token
|
|
154
|
+
3. The exchanged token has the scopes configured for the external resource
|
|
155
|
+
4. Your server uses this token to call GitHub API on behalf of the user
|
|
156
|
+
|
|
157
|
+
## Error Handling
|
|
158
|
+
|
|
159
|
+
The example demonstrates comprehensive error handling patterns:
|
|
160
|
+
|
|
161
|
+
| Method | Description |
|
|
162
|
+
|--------|-------------|
|
|
163
|
+
| `has_errors()` | Check for any errors (global or resource-specific) |
|
|
164
|
+
| `get_errors()` | Get all error details as a dictionary |
|
|
165
|
+
| `has_resource_error(url)` | Check for errors on a specific resource |
|
|
166
|
+
| `get_resource_errors(url)` | Get errors for a specific resource |
|
|
167
|
+
| `has_error()` | Check for global errors only |
|
|
168
|
+
| `get_error()` | Get global error details |
|
|
169
|
+
|
|
170
|
+
## Environment Variables Reference
|
|
171
|
+
|
|
172
|
+
| Variable | Required | Description |
|
|
173
|
+
|----------|----------|-------------|
|
|
174
|
+
| `KEYCARD_ZONE_ID` | Yes* | Your Keycard zone ID |
|
|
175
|
+
| `KEYCARD_ZONE_URL` | Yes* | Your full Keycard zone URL (alternative to `KEYCARD_ZONE_ID`) |
|
|
176
|
+
| `KEYCARD_CLIENT_ID` | Yes | Client ID from application credentials |
|
|
177
|
+
| `KEYCARD_CLIENT_SECRET` | Yes | Client secret from application credentials |
|
|
178
|
+
| `MCP_SERVER_URL` | Yes | Server URL (must be publicly reachable for delegated access) |
|
|
179
|
+
|
|
180
|
+
\* Provide either `KEYCARD_ZONE_ID` or `KEYCARD_ZONE_URL`, not both.
|
|
181
|
+
|
|
182
|
+
## Learn More
|
|
183
|
+
|
|
184
|
+
- [Keycard Documentation](https://docs.keycard.ai)
|
|
185
|
+
- [Delegated Access Guide](https://docs.keycard.ai/build-with-keycard/delegated-access)
|
|
186
|
+
- [FastMCP Documentation](https://docs.fastmcp.com)
|
|
187
|
+
- [GitHub API Documentation](https://docs.github.com/rest)
|