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.
Files changed (25) hide show
  1. keycardai_fastmcp-0.2.0/.gitignore +193 -0
  2. keycardai_fastmcp-0.2.0/CHANGELOG.md +65 -0
  3. keycardai_fastmcp-0.2.0/PKG-INFO +78 -0
  4. keycardai_fastmcp-0.2.0/README.md +42 -0
  5. keycardai_fastmcp-0.2.0/examples/delegated_access/.python-version +1 -0
  6. keycardai_fastmcp-0.2.0/examples/delegated_access/README.md +187 -0
  7. keycardai_fastmcp-0.2.0/examples/delegated_access/main.py +166 -0
  8. keycardai_fastmcp-0.2.0/examples/delegated_access/pyproject.toml +17 -0
  9. keycardai_fastmcp-0.2.0/examples/hello_world_server/.python-version +1 -0
  10. keycardai_fastmcp-0.2.0/examples/hello_world_server/README.md +72 -0
  11. keycardai_fastmcp-0.2.0/examples/hello_world_server/main.py +46 -0
  12. keycardai_fastmcp-0.2.0/examples/hello_world_server/pyproject.toml +16 -0
  13. keycardai_fastmcp-0.2.0/pyproject.toml +125 -0
  14. keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/__init__.py +151 -0
  15. keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/provider.py +778 -0
  16. keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/testing/__init__.py +36 -0
  17. keycardai_fastmcp-0.2.0/src/keycardai/fastmcp/testing/test_utils.py +98 -0
  18. keycardai_fastmcp-0.2.0/tests/conftest.py +3 -0
  19. keycardai_fastmcp-0.2.0/tests/fixtures/__init__.py +1 -0
  20. keycardai_fastmcp-0.2.0/tests/fixtures/auth_provider.py +138 -0
  21. keycardai_fastmcp-0.2.0/tests/integration/__init__.py +6 -0
  22. keycardai_fastmcp-0.2.0/tests/integration/test_auth_provider.py +182 -0
  23. keycardai_fastmcp-0.2.0/tests/integration/test_grant_decorator.py +374 -0
  24. keycardai_fastmcp-0.2.0/tests/keycardai/mcp/integrations/fastmcp/test_provider.py +516 -0
  25. 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,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)