Improve client.js and curl-examples.sh usability
Fix PKCE implementation, SSE parsing, error handling, and usage clarity. Add --help flag and improve output messaging for both examples.
This commit is contained in:
parent
1140815c7b
commit
7b7aa513f5
3 changed files with 328 additions and 272 deletions
|
|
@ -24,21 +24,28 @@ Shell script demonstrating API interactions using curl.
|
|||
- Resource listing and reading
|
||||
- Prompt operations
|
||||
|
||||
**Usage:**
|
||||
**Three-step workflow:**
|
||||
```bash
|
||||
# Make executable
|
||||
chmod +x curl-examples.sh
|
||||
|
||||
# Register client (step 1)
|
||||
# Step 1: Setup - Register OAuth client and get instructions
|
||||
./curl-examples.sh
|
||||
|
||||
# After getting token (via MCP Inspector)
|
||||
# Step 2: Create session - Get an access token (via Inspector or client.js), then:
|
||||
./curl-examples.sh YOUR_ACCESS_TOKEN
|
||||
# → Initializes MCP session and displays session ID
|
||||
|
||||
# With session ID
|
||||
# Step 3: Run examples - Use both access token and session ID:
|
||||
./curl-examples.sh YOUR_ACCESS_TOKEN YOUR_SESSION_ID
|
||||
# → Demonstrates all MCP features (tools, resources, prompts)
|
||||
```
|
||||
|
||||
**Quick reference:**
|
||||
- Run `./curl-examples.sh --help` for detailed usage
|
||||
- Get access token from MCP Inspector or `node client.js`
|
||||
- Each step explains what to do next
|
||||
|
||||
### client.js
|
||||
|
||||
Node.js client showing programmatic interaction with the MCP server.
|
||||
|
|
@ -59,6 +66,20 @@ chmod +x client.js
|
|||
./client.js
|
||||
```
|
||||
|
||||
**Note:** When you complete the OAuth flow in your browser, you'll be redirected to `http://localhost:8080/callback` which will show "site can't be reached". This is expected! Simply copy the authorization code from the URL in your browser's address bar (the long string after `code=`). The script will exchange this for an access token and display it for use with other tools.
|
||||
|
||||
## Understanding OAuth Tokens
|
||||
|
||||
**Authorization Code** vs **Access Token**:
|
||||
- **Authorization Code**: The temporary code you get from the browser redirect (e.g., `302a80e8...`)
|
||||
- One-time use only
|
||||
- Must be exchanged for an access token
|
||||
- Expires quickly (usually within minutes)
|
||||
- **Access Token**: The actual bearer token for API authentication (e.g., `mcp_at_...`)
|
||||
- Used in the `Authorization: Bearer` header
|
||||
- Valid for 7 days
|
||||
- What you need for `curl-examples.sh`
|
||||
|
||||
## Getting an Access Token
|
||||
|
||||
### Option 1: MCP Inspector (Easiest)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Simple Node.js MCP Client Example
|
||||
* Simple Node.js MCP Client Example - Manual Implementation
|
||||
*
|
||||
* This demonstrates how to interact with the MCP server programmatically.
|
||||
* It handles the OAuth flow and makes MCP requests.
|
||||
* This demonstrates how to interact with the MCP server programmatically
|
||||
* WITHOUT using the MCP SDK client. This is for educational purposes to
|
||||
* show the underlying protocol mechanics.
|
||||
*
|
||||
* In production, you would use the MCP SDK client which handles:
|
||||
* - SSE (Server-Sent Events) parsing
|
||||
* - Session management and reconnection logic
|
||||
* - Request/response correlation
|
||||
* - Error handling and retries
|
||||
*
|
||||
* For SDK usage, see:
|
||||
* @modelcontextprotocol/sdk/client/streamableHttp.js
|
||||
*
|
||||
* Prerequisites:
|
||||
* - Both servers running: npm run dev
|
||||
|
|
@ -18,6 +28,7 @@ const http = require('http');
|
|||
const https = require('https');
|
||||
const { URL } = require('url');
|
||||
const readline = require('readline');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// Configuration
|
||||
const AUTH_SERVER = 'http://localhost:3001';
|
||||
|
|
@ -64,11 +75,31 @@ function makeRequest(url, options = {}) {
|
|||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
resolve({ status: res.statusCode, headers: res.headers, data: json });
|
||||
} catch {
|
||||
// Check if this is an SSE response
|
||||
if (data.startsWith('event:') || data.includes('\ndata: ')) {
|
||||
// Parse SSE format
|
||||
const lines = data.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const json = JSON.parse(line.substring(6));
|
||||
resolve({ status: res.statusCode, headers: res.headers, data: json });
|
||||
return;
|
||||
} catch {
|
||||
// Continue to next line
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no JSON found in SSE, return raw data
|
||||
resolve({ status: res.statusCode, headers: res.headers, data });
|
||||
} else {
|
||||
// Try to parse as regular JSON
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
resolve({ status: res.statusCode, headers: res.headers, data: json });
|
||||
} catch {
|
||||
resolve({ status: res.statusCode, headers: res.headers, data });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -89,23 +120,30 @@ function makeRequest(url, options = {}) {
|
|||
async function registerClient() {
|
||||
log.section('Registering OAuth Client');
|
||||
|
||||
const response = await makeRequest(`${AUTH_SERVER}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
client_name: 'node-example-client',
|
||||
redirect_uris: [REDIRECT_URI]
|
||||
})
|
||||
});
|
||||
try {
|
||||
const response = await makeRequest(`${AUTH_SERVER}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
client_name: 'node-example-client',
|
||||
redirect_uris: [REDIRECT_URI]
|
||||
})
|
||||
});
|
||||
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
log.success('Client registered successfully');
|
||||
return {
|
||||
clientId: response.data.client_id,
|
||||
clientSecret: response.data.client_secret
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Registration failed: ${JSON.stringify(response.data)}`);
|
||||
if (response.status === 200 || response.status === 201) {
|
||||
log.success('Client registered successfully');
|
||||
return {
|
||||
clientId: response.data.client_id,
|
||||
clientSecret: response.data.client_secret
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Registration failed (HTTP ${response.status}): ${JSON.stringify(response.data)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
throw new Error(`Cannot connect to auth server at ${AUTH_SERVER}. Make sure it's running (npm run dev).`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,16 +157,19 @@ async function performOAuthFlow(clientId, clientSecret) {
|
|||
log.info('For this demo, we need you to manually complete the OAuth flow.');
|
||||
log.info('In a production app, this would be automated.\n');
|
||||
|
||||
// Generate PKCE challenge (simplified for demo)
|
||||
const codeVerifier = Buffer.from(Math.random().toString()).toString('base64url');
|
||||
const codeChallenge = Buffer.from(codeVerifier).toString('base64url');
|
||||
// Generate PKCE challenge (proper S256 implementation)
|
||||
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
||||
const codeChallenge = crypto
|
||||
.createHash('sha256')
|
||||
.update(codeVerifier)
|
||||
.digest('base64url');
|
||||
|
||||
const authUrl = `${AUTH_SERVER}/authorize?` +
|
||||
`client_id=${clientId}&` +
|
||||
`redirect_uri=${encodeURIComponent(REDIRECT_URI)}&` +
|
||||
`response_type=code&` +
|
||||
`code_challenge=${codeChallenge}&` +
|
||||
`code_challenge_method=plain&` +
|
||||
`code_challenge_method=S256&` +
|
||||
`state=demo-state`;
|
||||
|
||||
console.log('1. Open this URL in your browser:');
|
||||
|
|
@ -136,7 +177,11 @@ async function performOAuthFlow(clientId, clientSecret) {
|
|||
|
||||
console.log('2. Complete the authentication flow');
|
||||
console.log('3. You\'ll be redirected to a URL like:');
|
||||
console.log(` ${REDIRECT_URI}?code=AUTHORIZATION_CODE&state=demo-state\n`);
|
||||
console.log(` ${REDIRECT_URI}?code=AUTHORIZATION_CODE&state=demo-state`);
|
||||
console.log(` ${colors.yellow}(You'll see "site can't be reached" - this is expected!)${colors.reset}\n`);
|
||||
|
||||
console.log('4. Copy the authorization code from the URL in your browser\'s address bar');
|
||||
console.log(' The code is the long string after "code=" and before "&state="\n');
|
||||
|
||||
// Get authorization code from user
|
||||
const rl = readline.createInterface({
|
||||
|
|
@ -145,7 +190,7 @@ async function performOAuthFlow(clientId, clientSecret) {
|
|||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.question('4. Paste the AUTHORIZATION_CODE here: ', async (code) => {
|
||||
rl.question('5. Paste the AUTHORIZATION_CODE here: ', async (code) => {
|
||||
rl.close();
|
||||
|
||||
// Exchange code for token
|
||||
|
|
@ -182,7 +227,8 @@ async function initializeMCPSession(accessToken) {
|
|||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
|
|
@ -201,8 +247,13 @@ async function initializeMCPSession(accessToken) {
|
|||
|
||||
if (response.data.result) {
|
||||
log.success('MCP session initialized');
|
||||
// In a real implementation, extract session ID from headers
|
||||
return response.headers['mcp-session-id'] || 'demo-session-id';
|
||||
const sessionId = response.headers['mcp-session-id'];
|
||||
if (!sessionId) {
|
||||
throw new Error('No session ID received in headers');
|
||||
}
|
||||
return sessionId;
|
||||
} else if (response.data.error) {
|
||||
throw new Error(`Initialization failed: ${response.data.error.message}`);
|
||||
} else {
|
||||
throw new Error(`Initialization failed: ${JSON.stringify(response.data)}`);
|
||||
}
|
||||
|
|
@ -221,7 +272,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Mcp-Session-Id': sessionId,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
|
|
@ -235,6 +287,9 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
toolsResponse.data.result.tools.forEach(tool => {
|
||||
console.log(` - ${colors.cyan}${tool.name}${colors.reset}: ${tool.description}`);
|
||||
});
|
||||
} else if (toolsResponse.data.error) {
|
||||
log.error(`Failed to list tools: ${toolsResponse.data.error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the echo tool
|
||||
|
|
@ -244,7 +299,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Mcp-Session-Id': sessionId,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
|
|
@ -261,6 +317,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
|
||||
if (echoResponse.data.result) {
|
||||
console.log('Echo response:', echoResponse.data.result.content);
|
||||
} else if (echoResponse.data.error) {
|
||||
log.error(`Echo tool failed: ${echoResponse.data.error.message}`);
|
||||
}
|
||||
|
||||
// Call the add tool
|
||||
|
|
@ -270,7 +328,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Mcp-Session-Id': sessionId,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
|
|
@ -285,6 +344,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
|
||||
if (addResponse.data.result) {
|
||||
console.log('Add result:', addResponse.data.result.content);
|
||||
} else if (addResponse.data.error) {
|
||||
log.error(`Add tool failed: ${addResponse.data.error.message}`);
|
||||
}
|
||||
|
||||
// List resources
|
||||
|
|
@ -294,7 +355,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Mcp-Session-Id': sessionId,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
|
|
@ -308,6 +370,8 @@ async function demonstrateMCPFeatures(accessToken, sessionId) {
|
|||
resourcesResponse.data.result.resources.slice(0, 5).forEach(resource => {
|
||||
console.log(` - ${colors.cyan}${resource.uri}${colors.reset}: ${resource.name}`);
|
||||
});
|
||||
} else if (resourcesResponse.data.error) {
|
||||
log.error(`Failed to list resources: ${resourcesResponse.data.error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,8 +399,28 @@ async function main() {
|
|||
|
||||
log.success('\nExample completed successfully!');
|
||||
|
||||
// Show how to use curl-examples.sh as an alternative
|
||||
console.log(`\n${colors.bright}${colors.cyan}Try curl-examples.sh (alternative to this script):${colors.reset}`);
|
||||
console.log('─'.repeat(50));
|
||||
console.log(`\n${colors.green}Your credentials:${colors.reset}`);
|
||||
console.log(` Access Token: ${colors.yellow}${accessToken}${colors.reset}`);
|
||||
console.log(` Session ID: ${colors.yellow}${sessionId}${colors.reset}`);
|
||||
console.log(`\n${colors.cyan}Create a new session with curl:${colors.reset}`);
|
||||
console.log(` ./examples/curl-examples.sh ${accessToken}`);
|
||||
console.log(`\n${colors.cyan}Reuse this session with curl:${colors.reset}`);
|
||||
console.log(` ./examples/curl-examples.sh ${accessToken} ${sessionId}\n`);
|
||||
|
||||
} catch (error) {
|
||||
log.error(`Error: ${error.message}`);
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
log.error('Connection refused - Is the auth server running?');
|
||||
log.error(`Could not connect to ${error.address || 'server'}:${error.port || 'unknown'}`);
|
||||
console.log('\nMake sure to start the servers first:');
|
||||
console.log(' npm run dev');
|
||||
} else if (error.message) {
|
||||
log.error(`Error: ${error.message}`);
|
||||
} else {
|
||||
log.error(`Error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,27 @@
|
|||
#!/bin/bash
|
||||
|
||||
# =============================================================================
|
||||
# MCP Server API Examples using curl
|
||||
# MCP Server API Examples using curl - Manual Implementation
|
||||
# =============================================================================
|
||||
#
|
||||
# This script demonstrates how to interact with the MCP server using curl.
|
||||
# This script demonstrates how to interact with the MCP server using curl
|
||||
# WITHOUT using the MCP SDK. This is for educational purposes to show the
|
||||
# underlying protocol mechanics.
|
||||
#
|
||||
# In production, you would use the MCP SDK client which handles:
|
||||
# - SSE (Server-Sent Events) parsing
|
||||
# - Session management and reconnection logic
|
||||
# - Request/response correlation
|
||||
# - Error handling and retries
|
||||
#
|
||||
# Before running, ensure both servers are running: npm run dev
|
||||
#
|
||||
# Usage: ./curl-examples.sh [access_token]
|
||||
# Three-step workflow:
|
||||
# 1. ./curl-examples.sh → Register OAuth client, get setup instructions
|
||||
# 2. ./curl-examples.sh <access_token> → Initialize MCP session, get session ID
|
||||
# 3. ./curl-examples.sh <token> <session> → Run all MCP examples (tools, resources, prompts)
|
||||
#
|
||||
# Run with --help for detailed usage information
|
||||
# =============================================================================
|
||||
|
||||
# Configuration
|
||||
|
|
@ -43,6 +57,19 @@ print_info() {
|
|||
echo -e "${YELLOW}ℹ $1${NC}"
|
||||
}
|
||||
|
||||
# Parse SSE response and extract JSON data
|
||||
parse_sse_response() {
|
||||
local RESPONSE="$1"
|
||||
# Check if response is in SSE format
|
||||
if echo "$RESPONSE" | grep -q "^event:"; then
|
||||
# Extract JSON from SSE format (data: line)
|
||||
echo "$RESPONSE" | grep "^data: " | sed 's/^data: //'
|
||||
else
|
||||
# Return as-is if not SSE format
|
||||
echo "$RESPONSE"
|
||||
fi
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# OAuth Client Registration
|
||||
# =============================================================================
|
||||
|
|
@ -64,8 +91,6 @@ register_client() {
|
|||
print_success "Client registered successfully"
|
||||
echo "Client ID: $CLIENT_ID"
|
||||
echo "Client Secret: $CLIENT_SECRET"
|
||||
echo
|
||||
echo "Save these credentials - you'll need them for the OAuth flow"
|
||||
else
|
||||
print_error "Failed to register client"
|
||||
echo "Response: $RESPONSE"
|
||||
|
|
@ -80,17 +105,16 @@ register_client() {
|
|||
initialize_session() {
|
||||
local ACCESS_TOKEN=$1
|
||||
|
||||
print_section "Initializing MCP Session"
|
||||
|
||||
if [ -z "$ACCESS_TOKEN" ]; then
|
||||
print_error "Access token required"
|
||||
print_info "Get a token using MCP Inspector or implement OAuth flow"
|
||||
return 1
|
||||
fi
|
||||
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -i -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "init",
|
||||
|
|
@ -105,18 +129,32 @@ initialize_session() {
|
|||
}
|
||||
}')
|
||||
|
||||
# Extract session ID from response headers (would need -i flag)
|
||||
# For demonstration, we'll parse from response
|
||||
echo "Response: $RESPONSE"
|
||||
# Extract session ID from response headers
|
||||
SESSION_ID=$(echo "$RESPONSE" | grep -i "^mcp-session-id:" | cut -d' ' -f2 | tr -d '\r\n')
|
||||
|
||||
# Extract body from response (SSE format)
|
||||
BODY=$(echo "$RESPONSE" | sed -n '/^$/,$p' | tail -n +2)
|
||||
|
||||
# Parse SSE response if present
|
||||
if echo "$BODY" | grep -q "^event:"; then
|
||||
# Extract JSON from SSE format (data: line)
|
||||
JSON_DATA=$(echo "$BODY" | grep "^data: " | sed 's/^data: //')
|
||||
else
|
||||
JSON_DATA="$BODY"
|
||||
fi
|
||||
|
||||
# Check for error
|
||||
if echo "$RESPONSE" | grep -q "error"; then
|
||||
if echo "$JSON_DATA" | grep -q "error"; then
|
||||
print_error "Failed to initialize session"
|
||||
echo "$JSON_DATA" >&2
|
||||
return 1
|
||||
else
|
||||
print_success "Session initialized"
|
||||
print_info "Save the Mcp-Session-Id header for subsequent requests"
|
||||
elif [ -n "$SESSION_ID" ]; then
|
||||
# Return session ID on stdout (for capture by caller)
|
||||
echo "$SESSION_ID"
|
||||
return 0
|
||||
else
|
||||
print_error "No session ID received"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -130,15 +168,19 @@ list_tools() {
|
|||
|
||||
print_section "Listing Available Tools"
|
||||
|
||||
curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Mcp-Session-Id: $SESSION_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "list-tools",
|
||||
"method": "tools/list"
|
||||
}' | python3 -m json.tool
|
||||
}')
|
||||
|
||||
# Parse SSE response if needed and pretty print
|
||||
parse_sse_response "$RESPONSE" | python3 -m json.tool
|
||||
}
|
||||
|
||||
call_echo_tool() {
|
||||
|
|
@ -148,10 +190,11 @@ call_echo_tool() {
|
|||
|
||||
print_section "Calling Echo Tool"
|
||||
|
||||
curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Mcp-Session-Id: $SESSION_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d "{
|
||||
\"jsonrpc\": \"2.0\",
|
||||
\"id\": \"echo-1\",
|
||||
|
|
@ -162,7 +205,10 @@ call_echo_tool() {
|
|||
\"message\": \"$MESSAGE\"
|
||||
}
|
||||
}
|
||||
}" | python3 -m json.tool
|
||||
}")
|
||||
|
||||
# Parse SSE response if needed and pretty print
|
||||
parse_sse_response "$RESPONSE" | python3 -m json.tool
|
||||
}
|
||||
|
||||
call_add_tool() {
|
||||
|
|
@ -173,10 +219,11 @@ call_add_tool() {
|
|||
|
||||
print_section "Calling Add Tool"
|
||||
|
||||
curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Mcp-Session-Id: $SESSION_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d "{
|
||||
\"jsonrpc\": \"2.0\",
|
||||
\"id\": \"add-1\",
|
||||
|
|
@ -188,7 +235,10 @@ call_add_tool() {
|
|||
\"b\": $B
|
||||
}
|
||||
}
|
||||
}" | python3 -m json.tool
|
||||
}")
|
||||
|
||||
# Parse SSE response if needed and pretty print
|
||||
parse_sse_response "$RESPONSE" | python3 -m json.tool
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -201,15 +251,19 @@ list_resources() {
|
|||
|
||||
print_section "Listing Resources (First Page)"
|
||||
|
||||
curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Mcp-Session-Id: $SESSION_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "list-resources",
|
||||
"method": "resources/list"
|
||||
}' | python3 -m json.tool
|
||||
}')
|
||||
|
||||
# Parse SSE response if needed and pretty print
|
||||
parse_sse_response "$RESPONSE" | python3 -m json.tool
|
||||
}
|
||||
|
||||
read_resource() {
|
||||
|
|
@ -219,10 +273,11 @@ read_resource() {
|
|||
|
||||
print_section "Reading Resource: $RESOURCE_URI"
|
||||
|
||||
curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Mcp-Session-Id: $SESSION_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d "{
|
||||
\"jsonrpc\": \"2.0\",
|
||||
\"id\": \"read-resource\",
|
||||
|
|
@ -230,7 +285,10 @@ read_resource() {
|
|||
\"params\": {
|
||||
\"uri\": \"$RESOURCE_URI\"
|
||||
}
|
||||
}" | python3 -m json.tool
|
||||
}")
|
||||
|
||||
# Parse SSE response if needed and pretty print
|
||||
parse_sse_response "$RESPONSE" | python3 -m json.tool
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -243,15 +301,19 @@ list_prompts() {
|
|||
|
||||
print_section "Listing Available Prompts"
|
||||
|
||||
curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Mcp-Session-Id: $SESSION_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{
|
||||
"jsonrpc": "2.0",
|
||||
"id": "list-prompts",
|
||||
"method": "prompts/list"
|
||||
}' | python3 -m json.tool
|
||||
}')
|
||||
|
||||
# Parse SSE response if needed and pretty print
|
||||
parse_sse_response "$RESPONSE" | python3 -m json.tool
|
||||
}
|
||||
|
||||
get_prompt() {
|
||||
|
|
@ -261,10 +323,11 @@ get_prompt() {
|
|||
|
||||
print_section "Getting Prompt: $PROMPT_NAME"
|
||||
|
||||
curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
RESPONSE=$(curl -s -X POST "$MCP_SERVER/mcp" \
|
||||
-H "Authorization: Bearer $ACCESS_TOKEN" \
|
||||
-H "Mcp-Session-Id: $SESSION_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d "{
|
||||
\"jsonrpc\": \"2.0\",
|
||||
\"id\": \"get-prompt\",
|
||||
|
|
@ -272,14 +335,46 @@ get_prompt() {
|
|||
\"params\": {
|
||||
\"name\": \"$PROMPT_NAME\"
|
||||
}
|
||||
}" | python3 -m json.tool
|
||||
}")
|
||||
|
||||
# Parse SSE response if needed and pretty print
|
||||
parse_sse_response "$RESPONSE" | python3 -m json.tool
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Main Script
|
||||
# =============================================================================
|
||||
|
||||
show_usage() {
|
||||
echo -e "${BLUE}MCP Server API Examples - Usage${NC}"
|
||||
echo "================================="
|
||||
echo
|
||||
echo "This script demonstrates MCP API interactions in three steps:"
|
||||
echo
|
||||
echo -e " ${GREEN}Step 1: Register OAuth client${NC}"
|
||||
echo " ./curl-examples.sh"
|
||||
echo " → Registers a client and shows how to get an access token"
|
||||
echo
|
||||
echo -e " ${GREEN}Step 2: Initialize MCP session${NC}"
|
||||
echo " ./curl-examples.sh <access_token>"
|
||||
echo " → Creates an MCP session and returns a session ID"
|
||||
echo
|
||||
echo -e " ${GREEN}Step 3: Run MCP examples${NC}"
|
||||
echo " ./curl-examples.sh <access_token> <session_id>"
|
||||
echo " → Demonstrates tools, resources, and prompts"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " --help, -h Show this help message"
|
||||
echo
|
||||
}
|
||||
|
||||
main() {
|
||||
# Check for help flag
|
||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
|
||||
show_usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}MCP Server API Examples${NC}"
|
||||
echo "================================="
|
||||
|
||||
|
|
@ -288,26 +383,46 @@ main() {
|
|||
SESSION_ID=$2
|
||||
|
||||
if [ -z "$ACCESS_TOKEN" ]; then
|
||||
print_info "No access token provided"
|
||||
print_info "Usage: $0 <access_token> [session_id]"
|
||||
print_section "Getting Started"
|
||||
print_info "This script requires an OAuth access token to demonstrate MCP operations."
|
||||
print_info "Since you don't have one yet, let's help you get started!"
|
||||
echo
|
||||
print_info "First, we'll register an OAuth client for you..."
|
||||
echo
|
||||
print_section "Step 1: Register OAuth Client"
|
||||
register_client
|
||||
echo
|
||||
print_info "To continue, you need to:"
|
||||
print_info "1. Complete OAuth flow to get an access token"
|
||||
print_info "2. Run: $0 <your_access_token>"
|
||||
print_info ""
|
||||
print_info "Use MCP Inspector for easy OAuth flow:"
|
||||
print_info "npx -y @modelcontextprotocol/inspector"
|
||||
print_info "Connect to: http://localhost:3232/mcp"
|
||||
print_section "Next Steps"
|
||||
print_info "Now you need to get an access token. You have two options:"
|
||||
echo
|
||||
print_info "Option 1: Use MCP Inspector (recommended):"
|
||||
print_info " npx -y @modelcontextprotocol/inspector"
|
||||
print_info " Connect to: http://localhost:3232/mcp"
|
||||
print_info " Complete the OAuth flow in the Auth tab"
|
||||
echo
|
||||
print_info "Option 2: Use the client.js example:"
|
||||
print_info " node examples/client.js"
|
||||
echo
|
||||
print_info "Once you have an access token, run:"
|
||||
print_info " $0 <your_access_token>"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If we have a token, demonstrate API calls
|
||||
# If we have a token but no session, initialize session
|
||||
if [ -z "$SESSION_ID" ]; then
|
||||
initialize_session "$ACCESS_TOKEN"
|
||||
print_info "Rerun with: $0 $ACCESS_TOKEN <session_id>"
|
||||
print_section "Initializing MCP Session"
|
||||
RETURNED_SESSION_ID=$(initialize_session "$ACCESS_TOKEN")
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$RETURNED_SESSION_ID" ]; then
|
||||
echo
|
||||
print_success "MCP session initialized successfully"
|
||||
print_info "Session ID: $RETURNED_SESSION_ID"
|
||||
echo
|
||||
print_info "To run all MCP examples (tools, resources, prompts), use:"
|
||||
echo -e " ${CYAN}$0 $ACCESS_TOKEN $RETURNED_SESSION_ID${NC}"
|
||||
echo
|
||||
print_info "For help:"
|
||||
echo -e " ${CYAN}$0 --help${NC}"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue