Skip to content

Commit edb5924

Browse files
guyernestclaude
andcommitted
feat: Simplify MCP server authentication to use AppSync API keys
- Remove separate DynamoDB API key table and custom authorizer - Pass API keys from MCP requests directly to GraphQL calls - Let AppSync handle API key validation instead of custom auth - MCP endpoints are publicly accessible, GraphQL ops require valid keys - Add Makefile targets for testing MCP server with API keys This simplifies the architecture by reusing existing AppSync API keys for n8n integration instead of managing a separate authentication system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a9cdee3 commit edb5924

7 files changed

Lines changed: 85 additions & 351 deletions

File tree

ui_amplify/Makefile

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ help:
2323
@echo " make format - Format code"
2424
@echo " make clean - Clean build artifacts"
2525
@echo ""
26+
@echo "Testing commands:"
27+
@echo " make test-mcp-server MCP_URL=<url> - Test MCP server basic endpoints"
28+
@echo " make test-mcp-graphql MCP_URL=<url> API_KEY=<key> - Test MCP with GraphQL auth"
29+
@echo ""
2630

2731
# Install dependencies
2832
install:
@@ -98,6 +102,41 @@ clean:
98102
@cd $(MCP_SERVER_DIR) && make clean
99103
@echo "Clean complete!"
100104

105+
# Test MCP server endpoints
106+
test-mcp-server:
107+
@echo "Testing MCP server endpoints..."
108+
@if [ -z "$(MCP_URL)" ]; then \
109+
echo "❌ Please set MCP_URL environment variable"; \
110+
echo "Example: make test-mcp-server MCP_URL=https://your-api.execute-api.region.amazonaws.com/mcp"; \
111+
exit 1; \
112+
fi
113+
@echo "Testing initialize..."
114+
@curl -X POST $(MCP_URL) \
115+
-H "Content-Type: application/json" \
116+
-d '{"jsonrpc":"2.0","method":"initialize","params":{},"id":1}' | jq '.'
117+
@echo "\nTesting tools/list..."
118+
@curl -X POST $(MCP_URL) \
119+
-H "Content-Type: application/json" \
120+
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}' | jq '.'
121+
122+
# Test MCP server with GraphQL API key
123+
test-mcp-graphql:
124+
@echo "Testing MCP server with GraphQL authentication..."
125+
@if [ -z "$(MCP_URL)" ]; then \
126+
echo "❌ Please set MCP_URL environment variable"; \
127+
exit 1; \
128+
fi
129+
@if [ -z "$(API_KEY)" ]; then \
130+
echo "❌ Please set API_KEY environment variable (your AppSync API key)"; \
131+
echo "You can find this in the AWS AppSync console"; \
132+
exit 1; \
133+
fi
134+
@echo "Testing list_available_agents with API key..."
135+
@curl -X POST $(MCP_URL) \
136+
-H "Content-Type: application/json" \
137+
-H "x-api-key: $(API_KEY)" \
138+
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"list_available_agents","arguments":{}},"id":3}' | jq '.'
139+
101140
# Local development helpers
102141
dev-mcp:
103142
@echo "Running MCP server locally..."

ui_amplify/amplify/mcp-server/constructs/SimpleApiKeyMcpConstruct.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,18 @@ export class SimpleApiKeyMcpConstruct extends Construct {
3737
environment: {
3838
ENVIRONMENT: props.environment,
3939
APPLICATION_NAME: props.applicationName,
40-
API_KEY_TABLE_NAME: props.apiKeyTable.tableName,
4140
AWS_ACCOUNT_ID: Stack.of(this).account,
4241
// GraphQL endpoint will be set after creation
4342
GRAPHQL_ENDPOINT: props.graphqlApi.attrGraphQlUrl || "", // Note: capital Q in GraphQl
44-
// Use the API key from the created CfnApiKey
45-
GRAPHQL_API_KEY: props.graphqlApiKey?.attrApiKey || "",
43+
// API key will be passed from request headers
4644
RUST_LOG: "info",
4745
RUST_BACKTRACE: "1",
4846
},
4947
description: "Rust MCP server with API key authentication for Step Functions agents",
5048
}
5149
);
5250

51+
5352
// Create HTTP API integration
5453
const mcpIntegration = new HttpLambdaIntegration(
5554
`${props.projectName}-mcp-integration`,
@@ -58,6 +57,7 @@ export class SimpleApiKeyMcpConstruct extends Construct {
5857

5958
// Add MCP routes to HTTP API Gateway
6059
// Main MCP endpoint (handles all MCP protocol requests)
60+
// Authentication is handled by passing API key to GraphQL
6161
props.httpApi.addRoutes({
6262
path: "/mcp",
6363
methods: [
@@ -67,14 +67,15 @@ export class SimpleApiKeyMcpConstruct extends Construct {
6767
integration: mcpIntegration,
6868
});
6969

70-
// Health check endpoint
70+
// Health check endpoint - NOT PROTECTED (useful for monitoring)
7171
props.httpApi.addRoutes({
7272
path: "/health",
7373
methods: [
7474
apigateway.HttpMethod.GET,
7575
apigateway.HttpMethod.OPTIONS,
7676
],
7777
integration: mcpIntegration,
78+
// No authorizer - health check is public
7879
});
7980

8081
// MCP-specific endpoints following the protocol
@@ -88,20 +89,11 @@ export class SimpleApiKeyMcpConstruct extends Construct {
8889
integration: mcpIntegration,
8990
});
9091

91-
// Ensure GraphQL endpoint is set (similar to reference project)
92+
// Ensure GraphQL endpoint is set
9293
// The attribute name uses capital Q: attrGraphQlUrl
9394
const graphqlUrl = props.graphqlApi.attrGraphQlUrl;
9495
if (graphqlUrl) {
9596
this.mcpServerFunction.addEnvironment("GRAPHQL_ENDPOINT", graphqlUrl);
9697
}
97-
98-
// Also ensure API key is properly set from the created key
99-
if (props.graphqlApiKey) {
100-
const apiKeyValue = props.graphqlApiKey.attrApiKey;
101-
console.log("Using created GraphQL API Key");
102-
this.mcpServerFunction.addEnvironment("GRAPHQL_API_KEY", apiKeyValue);
103-
} else {
104-
console.log("Warning: No API key provided to construct");
105-
}
10698
}
10799
}

ui_amplify/amplify/mcp-server/constructs/common-interfaces.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as apigateway from "aws-cdk-lib/aws-apigatewayv2";
2-
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
32
import * as iam from "aws-cdk-lib/aws-iam";
43
import * as cognito from "aws-cdk-lib/aws-cognito";
54
import * as logs from "aws-cdk-lib/aws-logs";
@@ -14,30 +13,11 @@ export interface McpConstructProps {
1413
userPool: cognito.UserPool;
1514
userPoolClient: cognito.UserPoolClient;
1615
graphqlApi: any; // AppSync GraphQL API reference
17-
graphqlApiKey?: any; // AppSync API Key reference
1816
mcpServerLogGroup: logs.LogGroup;
19-
apiKeyTable: dynamodb.Table;
2017
lambdaRole: iam.Role;
2118
httpApi: apigateway.HttpApi;
2219
}
2320

24-
/**
25-
* API Key structure for DynamoDB storage
26-
*/
27-
export interface ApiKeyRecord {
28-
api_key_hash: string; // PK - SHA256 hash of the API key
29-
client_id: string; // GSI - Client identifier for lookup
30-
client_name: string; // Human readable name
31-
created_at: string; // ISO timestamp
32-
expires_at: string; // ISO timestamp
33-
last_used?: string; // ISO timestamp
34-
is_active: boolean;
35-
permissions: string[]; // Array of allowed operations
36-
usage_count: number;
37-
created_by: string; // User who created the key
38-
metadata?: Record<string, any>; // Additional metadata
39-
}
40-
4121
/**
4222
* MCP Tool definition for the server
4323
*/

ui_amplify/amplify/mcp-server/resource.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,7 @@ export function createMcpServerResources(backend: Backend<{ auth: any; data: any
178178
userPool,
179179
userPoolClient,
180180
graphqlApi,
181-
graphqlApiKey: apiKey,
182181
mcpServerLogGroup,
183-
apiKeyTable,
184182
lambdaRole,
185183
httpApi,
186184
};
@@ -212,9 +210,6 @@ export function createMcpServerResources(backend: Backend<{ auth: any; data: any
212210
mcpServer: `${PROJECT_NAME}-mcp-server`,
213211
},
214212

215-
dynamoDbTables: {
216-
apiKeys: apiKeyTable.tableName,
217-
},
218213

219214
logGroups: {
220215
mcpServer: mcpServerLogGroup.logGroupName,
@@ -229,7 +224,6 @@ export function createMcpServerResources(backend: Backend<{ auth: any; data: any
229224
return {
230225
httpApi,
231226
mcpConstruct,
232-
apiKeyTable,
233227
mcpServerLogGroup,
234228
};
235229
}

ui_amplify/amplify/mcp-server/rust-mcp-server/src/agents.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use anyhow::{Result, anyhow};
66
use serde::{Deserialize, Serialize};
77
use chrono::{DateTime, Utc};
88
use std::collections::HashMap;
9-
use tracing::info;
9+
use tracing::{info, warn};
1010
use uuid::Uuid;
1111
use serde_json::{json, Value};
1212

@@ -16,7 +16,7 @@ pub struct StepFunctionsAgentsClient {
1616
dynamodb: DynamoDbClient,
1717
environment: String,
1818
graphql_endpoint: Option<String>,
19-
api_key: Option<String>,
19+
// API key will be passed per request, not stored
2020
}
2121

2222
/// Agent definition from registry
@@ -75,26 +75,19 @@ impl StepFunctionsAgentsClient {
7575
let dynamodb = DynamoDbClient::new(aws_config);
7676
let environment = std::env::var("ENVIRONMENT").unwrap_or_else(|_| "prod".to_string());
7777
let graphql_endpoint = std::env::var("GRAPHQL_ENDPOINT").ok();
78-
let api_key = std::env::var("GRAPHQL_API_KEY").ok();
7978

8079
info!("Initialized StepFunctionsAgentsClient for environment: {}", environment);
8180
if let Some(ref endpoint) = graphql_endpoint {
8281
info!("GraphQL endpoint configured: {}", endpoint);
8382
} else {
8483
info!("GraphQL endpoint not found in environment variables");
8584
}
86-
if api_key.is_some() {
87-
info!("GraphQL API key configured");
88-
} else {
89-
info!("GraphQL API key not found in environment variables");
90-
}
9185

9286
Self {
9387
sfn,
9488
dynamodb,
9589
environment,
9690
graphql_endpoint,
97-
api_key,
9891
}
9992
}
10093

@@ -217,15 +210,19 @@ impl StepFunctionsAgentsClient {
217210
}
218211

219212
/// List available agents from registry
220-
pub async fn list_available_agents(&self) -> Result<Vec<Agent>> {
213+
pub async fn list_available_agents(&self, api_key: Option<String>) -> Result<Vec<Agent>> {
221214
info!("Listing available agents from registry");
222215

223216
// If GraphQL endpoint is configured, use it
224-
if let (Some(endpoint), Some(api_key)) = (&self.graphql_endpoint, &self.api_key) {
217+
if let Some(endpoint) = &self.graphql_endpoint {
225218
info!("Using GraphQL API to list agents");
226219
info!("GraphQL endpoint: {}", endpoint);
227-
// Log API key length for security (not the actual key)
228-
info!("API key length: {}", api_key.len());
220+
if let Some(ref key) = api_key {
221+
// Log API key length for security (not the actual key)
222+
info!("API key provided, length: {}", key.len());
223+
} else {
224+
warn!("No API key provided for GraphQL request");
225+
}
229226

230227
let query = r#"
231228
query ListAgentsFromRegistry {
@@ -254,11 +251,15 @@ impl StepFunctionsAgentsClient {
254251
.post(endpoint)
255252
.header("Content-Type", "application/json");
256253

257-
// Only add API key header if it's not empty
258-
if !api_key.is_empty() {
259-
request = request.header("x-api-key", api_key);
254+
// Add API key header if provided
255+
if let Some(ref key) = api_key {
256+
if !key.is_empty() {
257+
request = request.header("x-api-key", key);
258+
} else {
259+
info!("Warning: API key is empty, request may fail");
260+
}
260261
} else {
261-
info!("Warning: API key is empty, request may fail");
262+
warn!("No API key provided, GraphQL request may fail");
262263
}
263264

264265
let response = request

0 commit comments

Comments
 (0)