forked from modelcontextprotocol/typescript-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresourceServerOnly.ts
More file actions
87 lines (77 loc) · 3.4 KB
/
resourceServerOnly.ts
File metadata and controls
87 lines (77 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
* Minimal Resource-Server-only auth using the SDK's RS helpers
* (`mcpAuthMetadataRouter`, `requireBearerAuth`, `OAuthTokenVerifier`).
*
* No better-auth. The Authorization Server is external; this example points
* its metadata at a placeholder issuer. For a full AS+RS setup with a real
* demo Authorization Server, see {@link ./simpleStreamableHttp.ts}.
*
* Run: pnpm tsx src/resourceServerOnly.ts
* Probe: curl http://localhost:3000/.well-known/oauth-protected-resource/mcp
* curl -H 'Authorization: Bearer demo-token' -X POST http://localhost:3000/mcp ...
*/
import type { OAuthTokenVerifier } from '@modelcontextprotocol/express';
import {
createMcpExpressApp,
getOAuthProtectedResourceMetadataUrl,
mcpAuthMetadataRouter,
requireBearerAuth
} from '@modelcontextprotocol/express';
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { AuthInfo, CallToolResult, OAuthMetadata } from '@modelcontextprotocol/server';
import { McpServer, OAuthError, OAuthErrorCode } from '@modelcontextprotocol/server';
import type { Request, Response } from 'express';
import * as z from 'zod/v4';
const PORT = 3000;
const mcpServerUrl = new URL(`http://localhost:${PORT}/mcp`);
// In a real deployment this is your external Authorization Server's metadata
// (RFC 8414). The SDK router serves it verbatim at
// /.well-known/oauth-authorization-server so clients probing the RS origin
// can still discover the AS.
const oauthMetadata: OAuthMetadata = {
issuer: 'https://auth.example.com',
authorization_endpoint: 'https://auth.example.com/authorize',
token_endpoint: 'https://auth.example.com/token',
response_types_supported: ['code']
};
// Replace with JWT verification, RFC 7662 introspection, etc.
const staticTokenVerifier: OAuthTokenVerifier = {
async verifyAccessToken(token): Promise<AuthInfo> {
if (token !== 'demo-token') {
throw new OAuthError(OAuthErrorCode.InvalidToken, 'unknown token');
}
return { token, clientId: 'demo-client', scopes: ['mcp'], expiresAt: Math.floor(Date.now() / 1000) + 3600 };
}
};
const server = new McpServer({ name: 'rs-only', version: '1.0.0' }, { capabilities: {} });
server.registerTool(
'whoami',
{ description: 'Returns the authenticated subject.', inputSchema: z.object({}) },
async (_args, ctx): Promise<CallToolResult> => ({
content: [{ type: 'text', text: `client=${ctx.http?.authInfo?.clientId ?? 'anon'}` }]
})
);
const app = createMcpExpressApp();
app.use(
mcpAuthMetadataRouter({
oauthMetadata,
resourceServerUrl: mcpServerUrl,
resourceName: 'RS-only example'
})
);
const auth = requireBearerAuth({
verifier: staticTokenVerifier,
requiredScopes: ['mcp'],
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl)
});
app.post('/mcp', auth, async (req: Request, res: Response) => {
const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on('close', () => void transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
app.listen(PORT, () => {
console.log(`RS-only MCP server on http://localhost:${PORT}/mcp`);
console.log(` PRM: ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`);
console.log(` AS metadata mirror: http://localhost:${PORT}/.well-known/oauth-authorization-server`);
});