# Authorization Fix Summary ## Problem The MCP client was receiving "Unauthorized: insufficient permissions" errors when calling tools, and Example.run_demo was crashing with a MatchError. ## Root Causes ### 1. Permission Key Mismatch **Authorization Module** (`lib/auth/authorization.ex`): ```elixir @permissions %{ "index:view" => 123456, # Expects 6-digit key "admin:manage" => 11111 } ``` **Router** (`lib/my_mcp_server_router.ex`): ```elixir users = %{@james => %{12345 => true, 11111 => true}} # Only 5 digits! ``` **Result:** User had key `12345`, but authorization checked for key `123456` → Access denied! ### 2. Example Pattern Matching **Example.run_demo** (`lib/example.ex`): ```elixir {:ok, result} = MyMCPClient.call_tool("add_numbers", %{...}) # ❌ Crashes when result is {:error, ...} ``` When authorization failed, the tool returned `{:error, %{...}}` which didn't match the `{:ok, result}` pattern, causing a MatchError. ## Fixes Applied ### Fix 1: Corrected Permission Key **File:** `lib/my_mcp_server_router.ex:310` **Before:** ```elixir users = %{@james => %{12345 => true, 11111 => true}} ``` **After:** ```elixir # These keys map to named permissions in Auth.Authorization: # 123456 => "index:view" # 11111 => "admin:manage" users = %{@james => %{123456 => true, 11111 => true}} ``` ### Fix 2: Handle Both Success and Error **File:** `lib/example.ex` **Before:** ```elixir {:ok, result} = MyMCPClient.call_tool("add_numbers", %{"a" => 42, "b" => 8}) IO.inspect(result, label: "Result") ``` **After:** ```elixir case MyMCPClient.call_tool("add_numbers", %{"a" => 42, "b" => 8}) do {:ok, result} -> IO.inspect(result, label: "Result") {:error, error} -> IO.inspect(error, label: "Error") end ``` Applied to all 7 operations in run_demo. ## Testing ```bash # Recompile mix compile # Restart application sudo mix run --no-halt # In another terminal or IEx iex> Example.run_demo ``` **Expected Output:** ``` === MCP Demo === 1. Initializing... Server: %{capabilities: %{...}, ...} 2. Listing tools... Tools: %{tools: [%{name: "add_numbers", ...}, ...]} 3. Calling add_numbers(42, 8)... Result: %{content: [%{text: "Result: 50", type: "text"}]} 4. Calling get_greeting... Greeting: %{content: [%{text: "Hello, Alice! Welcome to MCP.", type: "text"}]} 5. Calling increment_counter... Counter: %{content: [%{text: "Counter is now: 1", type: "text"}]} 6. Calling increment_counter again... Counter: %{content: [%{text: "Counter is now: 2", type: "text"}]} 7. Listing resources... Resources: %{resources: [%{uri: "file:///config/settings.json", ...}]} === Demo Complete === ``` ## Authorization Flow (Fixed) ``` 1. Client authenticates → gets cookie with user ID (james) 2. Router decrypts cookie → loads user permissions: %{id: 1769578105155195386, keys: %{123456 => true, 11111 => true}} 3. Manager adds user to state: state.current_user 4. MyMCPServer checks authorization: Auth.Authorization.authorized?(user_keys, "index:view") → Looks up "index:view" → finds key 123456 → Checks if user has key 123456 → YES! ✓ 5. Tool executes successfully ``` ## Key Learnings 1. **Permission keys must match exactly** between: - `Auth.Authorization @permissions` map - Router's user permission assignment 2. **Always handle both success and error cases** when calling MCP tools: ```elixir case MyMCPClient.call_tool(...) do {:ok, result} -> # handle success {:error, error} -> # handle error end ``` 3. **Use Logger.info(inspect(state))** to debug permission issues: - See what user info is in state - Verify permission keys are correct - Check authorization decisions ## Related Files - Permission definitions: `lib/auth/authorization.ex:10-14` - User permission assignment: `lib/my_mcp_server_router.ex:310` - Authorization checks: `lib/my_mcp_server.ex` (all handle_* functions) - Example usage: `lib/example.ex` ## Prevention To avoid this in the future: 1. **Centralize permission key definitions:** ```elixir # In a shared module defmodule Permissions do def keys do %{ index_view: 123456, admin_manage: 11111 } end end # In Authorization @permissions %{ "index:view" => Permissions.keys().index_view, "admin:manage" => Permissions.keys().admin_manage } # In Router users = %{@james => %{ Permissions.keys().index_view => true, Permissions.keys().admin_manage => true }} ``` 2. **Add tests for authorization:** ```elixir test "user with index:view permission can call add_numbers" do user = %{id: 1, keys: %{123456 => true}} {:ok, result} = Manager.call_tool("add_numbers", %{"a" => 1, "b" => 2}, user) assert result.content end ``` 3. **Log permission key mismatches:** ```elixir # In Auth.Authorization case Map.get(@permissions, permission_name) do nil -> Logger.error("Permission '#{permission_name}' not defined") required_key -> if is_map_key(user_keys, required_key) do # authorized else Logger.warning("User lacks key #{required_key} for #{permission_name}") Logger.debug("User has keys: #{inspect(Map.keys(user_keys))}") end end ```