From a58ff77911b9431201d29700eb11cb184c6e62f2 Mon Sep 17 00:00:00 2001 From: Larry-Osakwe Date: Mon, 6 Apr 2026 15:07:54 -0700 Subject: [PATCH 1/7] fix: add examples as workspace members so they resolve deps correctly Running `uv run python main.py` from an example directory failed because examples were not workspace members. Their editable path loaded the parent package pyproject which has `workspace = true` sources, but those only resolve within the workspace context. Adding `packages/*/examples/*` to workspace members fixes this for all 9 examples across mcp-fastmcp, mcp, oauth, and agents. --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e4b3a96..715f837 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,8 @@ Issues = "https://github.com/keycardai/python-sdk/issues" [tool.uv.workspace] members = [ ".", - "packages/*" + "packages/*", + "packages/*/examples/*" ] # Exclude any packages that shouldn't be part of the workspace exclude = [] From 3b71aff51884dc9dbfadc1c7b549836a9c56652e Mon Sep 17 00:00:00 2001 From: Larry-Osakwe Date: Mon, 6 Apr 2026 15:10:37 -0700 Subject: [PATCH 2/7] fix: disambiguate duplicate example project names Two delegated_access examples (mcp-fastmcp and mcp) were both named delegated-access-example, causing uv workspace member name collision. Renamed to delegated-access-fastmcp-example and delegated-access-lowlevel-example. --- packages/mcp-fastmcp/examples/delegated_access/pyproject.toml | 2 +- packages/mcp/examples/delegated_access/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mcp-fastmcp/examples/delegated_access/pyproject.toml b/packages/mcp-fastmcp/examples/delegated_access/pyproject.toml index 04ac81f..82c8df3 100644 --- a/packages/mcp-fastmcp/examples/delegated_access/pyproject.toml +++ b/packages/mcp-fastmcp/examples/delegated_access/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "delegated-access-example" +name = "delegated-access-fastmcp-example" version = "0.1.0" description = "GitHub API integration with Keycard delegated access using the @grant decorator" readme = "README.md" diff --git a/packages/mcp/examples/delegated_access/pyproject.toml b/packages/mcp/examples/delegated_access/pyproject.toml index 3fde4b2..787517e 100644 --- a/packages/mcp/examples/delegated_access/pyproject.toml +++ b/packages/mcp/examples/delegated_access/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "delegated-access-example" +name = "delegated-access-lowlevel-example" version = "0.1.0" description = "GitHub API integration with Keycard delegated access using the low-level MCP package" readme = "README.md" From 34b6725dffdcf4a29180d31205101d5df27c750b Mon Sep 17 00:00:00 2001 From: Larry-Osakwe Date: Mon, 6 Apr 2026 15:25:20 -0700 Subject: [PATCH 3/7] fix: remove redundant workspace sources from package pyproject files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Package-level [tool.uv.sources] with `workspace = true` caused failures when examples resolved deps via editable paths — uv loaded the package pyproject outside the workspace context and could not resolve the workspace references. The root pyproject.toml already declares all workspace sources, so package-level declarations were redundant. Removing them lets examples run directly from their directory with `uv run python main.py`. --- packages/mcp-fastmcp/pyproject.toml | 3 - packages/mcp/pyproject.toml | 4 - uv.lock | 136 ++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/packages/mcp-fastmcp/pyproject.toml b/packages/mcp-fastmcp/pyproject.toml index f360319..dcd5791 100644 --- a/packages/mcp-fastmcp/pyproject.toml +++ b/packages/mcp-fastmcp/pyproject.toml @@ -61,9 +61,6 @@ url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" explicit = true -[tool.uv.sources] -keycardai-oauth = { workspace = true } -keycardai-mcp = { workspace = true } [tool.hatch.build.targets.wheel] packages = ["src/keycardai"] diff --git a/packages/mcp/pyproject.toml b/packages/mcp/pyproject.toml index f12deb4..27d2475 100644 --- a/packages/mcp/pyproject.toml +++ b/packages/mcp/pyproject.toml @@ -79,10 +79,6 @@ url = "https://test.pypi.org/simple/" publish-url = "https://test.pypi.org/legacy/" explicit = true -[tool.uv.sources] -keycardai-oauth = { workspace = true } -keycardai-mcp-fastmcp = { workspace = true } - [tool.hatch.build.targets.wheel] packages = ["src/keycardai"] diff --git a/uv.lock b/uv.lock index 440f241..a8f1881 100644 --- a/uv.lock +++ b/uv.lock @@ -9,11 +9,35 @@ resolution-markers = [ [manifest] members = [ + "a2a-jsonrpc-usage", + "client-connection-example", + "delegated-access-fastmcp-example", + "delegated-access-lowlevel-example", + "discover-server-metadata", + "dynamic-client-registration", + "hello-world-server", + "hello-world-server-lowlevel", "keycardai", "keycardai-agents", "keycardai-mcp", "keycardai-mcp-fastmcp", "keycardai-oauth", + "oauth-client-usage", +] + +[[package]] +name = "a2a-jsonrpc-usage" +version = "0.1.0" +source = { virtual = "packages/agents/examples/a2a_jsonrpc_usage" } +dependencies = [ + { name = "httpx" }, + { name = "keycardai-agents" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.27.2" }, + { name = "keycardai-agents", editable = "packages/agents" }, ] [[package]] @@ -726,6 +750,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, ] +[[package]] +name = "client-connection-example" +version = "0.1.0" +source = { virtual = "packages/mcp/examples/client_connection" } +dependencies = [ + { name = "keycardai-mcp" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "keycardai-mcp", editable = "packages/mcp" }, + { name = "uvicorn", specifier = ">=0.30.0" }, +] + [[package]] name = "cohere" version = "5.20.1" @@ -987,6 +1026,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl", hash = "sha256:5152347c7bb8e3114ad65db719e5709b28d7f7f45bdb709f70167925e55640f3", size = 7989, upload-time = "2025-06-01T15:23:40.228Z" }, ] +[[package]] +name = "delegated-access-fastmcp-example" +version = "0.1.0" +source = { virtual = "packages/mcp-fastmcp/examples/delegated_access" } +dependencies = [ + { name = "fastmcp" }, + { name = "httpx" }, + { name = "keycardai-mcp-fastmcp" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = ">=3.0.0" }, + { name = "httpx", specifier = ">=0.27.0,<1.0.0" }, + { name = "keycardai-mcp-fastmcp", editable = "packages/mcp-fastmcp" }, +] + +[[package]] +name = "delegated-access-lowlevel-example" +version = "0.1.0" +source = { virtual = "packages/mcp/examples/delegated_access" } +dependencies = [ + { name = "httpx" }, + { name = "keycardai-mcp" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "httpx", specifier = ">=0.27.0,<1.0.0" }, + { name = "keycardai-mcp", editable = "packages/mcp" }, + { name = "uvicorn", specifier = ">=0.30.0" }, +] + +[[package]] +name = "discover-server-metadata" +version = "0.1.0" +source = { virtual = "packages/oauth/examples/discover_server_metadata" } +dependencies = [ + { name = "keycardai-oauth" }, +] + +[package.metadata] +requires-dist = [{ name = "keycardai-oauth", editable = "packages/oauth" }] + [[package]] name = "diskcache" version = "5.6.3" @@ -1050,6 +1134,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, ] +[[package]] +name = "dynamic-client-registration" +version = "0.1.0" +source = { virtual = "packages/oauth/examples/dynamic_client_registration" } +dependencies = [ + { name = "keycardai-oauth" }, +] + +[package.metadata] +requires-dist = [{ name = "keycardai-oauth", editable = "packages/oauth" }] + [[package]] name = "email-validator" version = "2.3.0" @@ -1523,6 +1618,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "hello-world-server" +version = "0.1.0" +source = { virtual = "packages/mcp-fastmcp/examples/hello_world_server" } +dependencies = [ + { name = "fastmcp" }, + { name = "keycardai-mcp-fastmcp" }, +] + +[package.metadata] +requires-dist = [ + { name = "fastmcp", specifier = ">=3.0.0" }, + { name = "keycardai-mcp-fastmcp", editable = "packages/mcp-fastmcp" }, +] + +[[package]] +name = "hello-world-server-lowlevel" +version = "0.1.0" +source = { virtual = "packages/mcp/examples/hello_world_server" } +dependencies = [ + { name = "keycardai-mcp" }, + { name = "uvicorn" }, +] + +[package.metadata] +requires-dist = [ + { name = "keycardai-mcp", editable = "packages/mcp" }, + { name = "uvicorn", specifier = ">=0.30.0" }, +] + [[package]] name = "hf-xet" version = "1.2.0" @@ -3079,6 +3204,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, ] +[[package]] +name = "oauth-client-usage" +version = "0.1.0" +source = { virtual = "packages/agents/examples/oauth_client_usage" } +dependencies = [ + { name = "keycardai-agents" }, +] + +[package.metadata] +requires-dist = [{ name = "keycardai-agents", editable = "packages/agents" }] + [[package]] name = "oauthlib" version = "3.3.1" From aed805c42488d28c0ff703230687e0ed095b0666 Mon Sep 17 00:00:00 2001 From: Larry-Osakwe Date: Mon, 6 Apr 2026 15:36:55 -0700 Subject: [PATCH 4/7] docs: improve delegated_access example with ZONE_URL support and better README Incorporates improvements from Sean's PR #94: - Support KEYCARD_ZONE_URL in addition to KEYCARD_ZONE_ID - Remove hardcoded fallback values for credentials - Streamline README install/run steps - Add note for local SDK development workflow --- .../examples/delegated_access/README.md | 22 ++++++++++++------- .../examples/delegated_access/main.py | 7 +++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/mcp-fastmcp/examples/delegated_access/README.md b/packages/mcp-fastmcp/examples/delegated_access/README.md index 8b516ef..1c502a4 100644 --- a/packages/mcp-fastmcp/examples/delegated_access/README.md +++ b/packages/mcp-fastmcp/examples/delegated_access/README.md @@ -96,27 +96,30 @@ Use the public URL from your tunnel as `MCP_SERVER_URL`. ### 2. Set Environment Variables ```bash -export KEYCARD_ZONE_ID="your-zone-id" +export KEYCARD_ZONE_ID="your-zone-id" # or use KEYCARD_ZONE_URL export KEYCARD_CLIENT_ID="your-client-id" export KEYCARD_CLIENT_SECRET="your-client-secret" export MCP_SERVER_URL="https://your-tunnel-url.ngrok.io/" # Must be publicly reachable ``` -### 3. Install Dependencies +### 3. Install Dependencies and Run the Server ```bash cd packages/mcp-fastmcp/examples/delegated_access uv sync -``` - -### 4. Run the Server - -```bash uv run python main.py ``` The server will start on `http://localhost:8000`. +> **Local SDK development:** If you're working on the Keycard SDK packages locally and want to run this example against your local changes, run from the repository root instead: +> +> ```bash +> uv run --package delegated-access-example python packages/mcp-fastmcp/examples/delegated_access/main.py +> ``` +> +> This uses the root workspace to resolve all SDK packages from source. + ### 5. Verify the Server Check that OAuth metadata is being served: @@ -176,11 +179,14 @@ The example demonstrates comprehensive error handling patterns: | Variable | Required | Description | |----------|----------|-------------| -| `KEYCARD_ZONE_ID` | Yes | Your Keycard zone ID | +| `KEYCARD_ZONE_ID` | Yes* | Your Keycard zone ID | +| `KEYCARD_ZONE_URL` | Yes* | Your full Keycard zone URL (alternative to `KEYCARD_ZONE_ID`) | | `KEYCARD_CLIENT_ID` | Yes | Client ID from application credentials | | `KEYCARD_CLIENT_SECRET` | Yes | Client secret from application credentials | | `MCP_SERVER_URL` | Yes | Server URL (must be publicly reachable for delegated access) | +\* Provide either `KEYCARD_ZONE_ID` or `KEYCARD_ZONE_URL`, not both. + ## Learn More - [Keycard Documentation](https://docs.keycard.ai) diff --git a/packages/mcp-fastmcp/examples/delegated_access/main.py b/packages/mcp-fastmcp/examples/delegated_access/main.py index f4abc9c..9564254 100644 --- a/packages/mcp-fastmcp/examples/delegated_access/main.py +++ b/packages/mcp-fastmcp/examples/delegated_access/main.py @@ -19,16 +19,15 @@ from keycardai.mcp.integrations.fastmcp import AccessContext, AuthProvider, ClientSecret # Configure Keycard authentication with client credentials for delegated access -# Get your zone_id and client credentials from console.keycard.ai +# Set KEYCARD_ZONE_ID (or KEYCARD_ZONE_URL) and client credentials from console.keycard.ai auth_provider = AuthProvider( - zone_id=os.getenv("KEYCARD_ZONE_ID", "your-zone-id"), mcp_server_name="GitHub API Server", mcp_base_url=os.getenv("MCP_SERVER_URL", "http://localhost:8000/"), # ClientSecret enables token exchange for delegated access application_credential=ClientSecret( ( - os.getenv("KEYCARD_CLIENT_ID", "your-client-id"), - os.getenv("KEYCARD_CLIENT_SECRET", "your-client-secret"), + os.getenv("KEYCARD_CLIENT_ID"), + os.getenv("KEYCARD_CLIENT_SECRET"), ) ), ) From 5e992651c7dd2f6486dea4239b34e33e9eb96b15 Mon Sep 17 00:00:00 2001 From: Larry-Osakwe Date: Mon, 6 Apr 2026 15:38:49 -0700 Subject: [PATCH 5/7] docs: remove unnecessary local SDK workaround note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace fix makes examples resolve against local source by default — no need for a separate root-level command. --- packages/mcp-fastmcp/examples/delegated_access/README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/mcp-fastmcp/examples/delegated_access/README.md b/packages/mcp-fastmcp/examples/delegated_access/README.md index 1c502a4..b4e6b34 100644 --- a/packages/mcp-fastmcp/examples/delegated_access/README.md +++ b/packages/mcp-fastmcp/examples/delegated_access/README.md @@ -112,14 +112,6 @@ uv run python main.py The server will start on `http://localhost:8000`. -> **Local SDK development:** If you're working on the Keycard SDK packages locally and want to run this example against your local changes, run from the repository root instead: -> -> ```bash -> uv run --package delegated-access-example python packages/mcp-fastmcp/examples/delegated_access/main.py -> ``` -> -> This uses the root workspace to resolve all SDK packages from source. - ### 5. Verify the Server Check that OAuth metadata is being served: From fa328a2eb4fe68fd80bdd15072420d4ab7ee52e0 Mon Sep 17 00:00:00 2001 From: Larry-Osakwe Date: Mon, 6 Apr 2026 15:43:48 -0700 Subject: [PATCH 6/7] fix: restore default fallbacks for client credentials in example The test suite imports the example without env vars set. Removing the fallback defaults caused ClientSecret to receive None, failing the client_id validation. --- packages/mcp-fastmcp/examples/delegated_access/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mcp-fastmcp/examples/delegated_access/main.py b/packages/mcp-fastmcp/examples/delegated_access/main.py index 9564254..90b7aea 100644 --- a/packages/mcp-fastmcp/examples/delegated_access/main.py +++ b/packages/mcp-fastmcp/examples/delegated_access/main.py @@ -26,8 +26,8 @@ # ClientSecret enables token exchange for delegated access application_credential=ClientSecret( ( - os.getenv("KEYCARD_CLIENT_ID"), - os.getenv("KEYCARD_CLIENT_SECRET"), + os.getenv("KEYCARD_CLIENT_ID", "your-client-id"), + os.getenv("KEYCARD_CLIENT_SECRET", "your-client-secret"), ) ), ) From 81ff861a10f33038687915c2491d6d00692db0f5 Mon Sep 17 00:00:00 2001 From: Larry-Osakwe Date: Mon, 6 Apr 2026 15:48:02 -0700 Subject: [PATCH 7/7] fix: restore zone_id fallback default in delegated_access example Sean's improvement removed the explicit zone_id parameter, relying on AuthProvider's internal env var discovery. But the test suite imports the example without env vars, so AuthProvider raises AuthProviderConfigurationError. Restore the fallback default. --- packages/mcp-fastmcp/examples/delegated_access/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mcp-fastmcp/examples/delegated_access/main.py b/packages/mcp-fastmcp/examples/delegated_access/main.py index 90b7aea..6cf1974 100644 --- a/packages/mcp-fastmcp/examples/delegated_access/main.py +++ b/packages/mcp-fastmcp/examples/delegated_access/main.py @@ -21,6 +21,7 @@ # Configure Keycard authentication with client credentials for delegated access # Set KEYCARD_ZONE_ID (or KEYCARD_ZONE_URL) and client credentials from console.keycard.ai auth_provider = AuthProvider( + zone_id=os.getenv("KEYCARD_ZONE_ID", "your-zone-id"), mcp_server_name="GitHub API Server", mcp_base_url=os.getenv("MCP_SERVER_URL", "http://localhost:8000/"), # ClientSecret enables token exchange for delegated access