@atxp/server 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -0
- package/dist/atxpContext.d.ts +6 -0
- package/dist/atxpContext.d.ts.map +1 -0
- package/dist/atxpContext.js +41 -0
- package/dist/atxpContext.js.map +1 -0
- package/dist/atxpServer.d.ts +12 -0
- package/dist/atxpServer.d.ts.map +1 -0
- package/dist/atxpServer.js +101 -0
- package/dist/atxpServer.js.map +1 -0
- package/dist/getResource.d.ts +4 -0
- package/dist/getResource.d.ts.map +1 -0
- package/dist/getResource.js +17 -0
- package/dist/getResource.js.map +1 -0
- package/dist/http.d.ts +7 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +51 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/oAuthChallenge.d.ts +4 -0
- package/dist/oAuthChallenge.d.ts.map +1 -0
- package/dist/oAuthChallenge.js +39 -0
- package/dist/oAuthChallenge.js.map +1 -0
- package/dist/oAuthMetadata.d.ts +6 -0
- package/dist/oAuthMetadata.d.ts.map +1 -0
- package/dist/oAuthMetadata.js +41 -0
- package/dist/oAuthMetadata.js.map +1 -0
- package/dist/paymentServer.d.ts +68 -0
- package/dist/paymentServer.d.ts.map +1 -0
- package/dist/paymentServer.js +97 -0
- package/dist/paymentServer.js.map +1 -0
- package/dist/protectedResourceMetadata.d.ts +5 -0
- package/dist/protectedResourceMetadata.d.ts.map +1 -0
- package/dist/protectedResourceMetadata.js +40 -0
- package/dist/protectedResourceMetadata.js.map +1 -0
- package/dist/requirePayment.d.ts +3 -0
- package/dist/requirePayment.d.ts.map +1 -0
- package/dist/requirePayment.js +35 -0
- package/dist/requirePayment.js.map +1 -0
- package/dist/serverTestHelpers.d.ts +55 -0
- package/dist/serverTestHelpers.d.ts.map +1 -0
- package/dist/serverTestHelpers.js +88 -0
- package/dist/serverTestHelpers.js.map +1 -0
- package/dist/src/atxpContext.d.ts +6 -0
- package/dist/src/atxpContext.d.ts.map +1 -0
- package/dist/src/atxpContext.js +41 -0
- package/dist/src/atxpContext.js.map +1 -0
- package/dist/src/atxpServer.d.ts +12 -0
- package/dist/src/atxpServer.d.ts.map +1 -0
- package/dist/src/atxpServer.js +95 -0
- package/dist/src/atxpServer.js.map +1 -0
- package/dist/src/getResource.d.ts +4 -0
- package/dist/src/getResource.d.ts.map +1 -0
- package/dist/src/getResource.js +17 -0
- package/dist/src/getResource.js.map +1 -0
- package/dist/src/http.d.ts +7 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +51 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/oAuthChallenge.d.ts +4 -0
- package/dist/src/oAuthChallenge.d.ts.map +1 -0
- package/dist/src/oAuthChallenge.js +39 -0
- package/dist/src/oAuthChallenge.js.map +1 -0
- package/dist/src/oAuthMetadata.d.ts +6 -0
- package/dist/src/oAuthMetadata.d.ts.map +1 -0
- package/dist/src/oAuthMetadata.js +41 -0
- package/dist/src/oAuthMetadata.js.map +1 -0
- package/dist/src/paymentServer.d.ts +68 -0
- package/dist/src/paymentServer.d.ts.map +1 -0
- package/dist/src/paymentServer.js +97 -0
- package/dist/src/paymentServer.js.map +1 -0
- package/dist/src/protectedResourceMetadata.d.ts +5 -0
- package/dist/src/protectedResourceMetadata.d.ts.map +1 -0
- package/dist/src/protectedResourceMetadata.js +40 -0
- package/dist/src/protectedResourceMetadata.js.map +1 -0
- package/dist/src/requirePayment.d.ts +3 -0
- package/dist/src/requirePayment.d.ts.map +1 -0
- package/dist/src/requirePayment.js +35 -0
- package/dist/src/requirePayment.js.map +1 -0
- package/dist/src/serverTestHelpers.d.ts +55 -0
- package/dist/src/serverTestHelpers.d.ts.map +1 -0
- package/dist/src/serverTestHelpers.js +88 -0
- package/dist/src/serverTestHelpers.js.map +1 -0
- package/dist/src/token.d.ts +4 -0
- package/dist/src/token.d.ts.map +1 -0
- package/dist/src/token.js +36 -0
- package/dist/src/token.js.map +1 -0
- package/dist/src/types.d.ts +60 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +10 -0
- package/dist/src/types.js.map +1 -0
- package/dist/token.d.ts +4 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +36 -0
- package/dist/token.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @atxp/server
|
|
2
|
+
|
|
3
|
+
ATXP Server - MCP server implementation with payment processing capabilities.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The ATXP Server package provides middleware and utilities for creating MCP (Model Context Protocol) servers that require OAuth authentication and payment processing. It integrates seamlessly with Express.js applications to add payment requirements to any MCP tool.
|
|
8
|
+
|
|
9
|
+
**🚀 Getting Started**: Learn more about ATXP in [the docs](https://docs.atxp.ai/atxp), and follow the [MCP Server Quickstart](https://docs.atxp.ai/server) to build your first monetized MCP server using ATXP.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🔐 **OAuth Integration**: Complete OAuth 2.0 server middleware
|
|
14
|
+
- 💰 **Payment Validation**: Solana payment verification and processing
|
|
15
|
+
- 🛠️ **MCP Protocol**: Full Model Context Protocol server implementation
|
|
16
|
+
- 🚀 **Express Middleware**: Easy integration with existing Express applications
|
|
17
|
+
- 🔄 **Challenge System**: Automatic authentication challenges for unauthorized requests
|
|
18
|
+
- 📊 **Payment Tracking**: Built-in payment confirmation and tracking
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @atxp/server
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Basic Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import express from 'express';
|
|
30
|
+
import { ATXPServer, requirePayment } from '@atxp/server';
|
|
31
|
+
|
|
32
|
+
const app = express();
|
|
33
|
+
const server = new ATXPServer({
|
|
34
|
+
clientId: 'your-oauth-client-id',
|
|
35
|
+
authServerUrl: 'https://auth.atxp.ai',
|
|
36
|
+
destinationAddress: 'your-solana-wallet-address'
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Add MCP tools with payment requirements
|
|
40
|
+
server.addTool({
|
|
41
|
+
name: 'hello_world',
|
|
42
|
+
description: 'A simple greeting tool',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
name: { type: 'string' }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}, requirePayment('0.01'), async ({ name }) => {
|
|
50
|
+
return `Hello, ${name || 'World'}!`;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Mount server middleware
|
|
54
|
+
app.use('/', server.middleware());
|
|
55
|
+
app.listen(3010);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Authentication & Payment Flow
|
|
59
|
+
|
|
60
|
+
1. **Unauthenticated Request**: Client sends MCP request without auth token
|
|
61
|
+
2. **OAuth Challenge**: Server responds with OAuth challenge and redirect URL
|
|
62
|
+
3. **Client Authentication**: Client completes OAuth flow and obtains access token
|
|
63
|
+
4. **Authenticated Request**: Client retries request with valid access token
|
|
64
|
+
5. **Payment Required**: Server responds with payment requirement (amount, destination)
|
|
65
|
+
6. **Payment Processing**: Client makes Solana payment to specified address
|
|
66
|
+
7. **Payment Verification**: Server confirms payment on-chain
|
|
67
|
+
8. **Tool Execution**: Server executes requested tool and returns results
|
|
68
|
+
|
|
69
|
+
## API Reference
|
|
70
|
+
|
|
71
|
+
### ATXPServer
|
|
72
|
+
|
|
73
|
+
Main server class for handling ATXP protocol requests.
|
|
74
|
+
|
|
75
|
+
#### Constructor Options
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
interface ATXPServerConfig {
|
|
79
|
+
clientId: string; // OAuth client ID
|
|
80
|
+
authServerUrl?: string; // Auth server URL (defaults to https://auth.atxp.ai)
|
|
81
|
+
destinationAddress: string; // Solana wallet address for payments
|
|
82
|
+
oAuthDb?: OAuthDb; // Custom OAuth database implementation
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### Methods
|
|
87
|
+
|
|
88
|
+
- `addTool(definition, middleware, handler)`: Register MCP tool with payment requirements
|
|
89
|
+
- `middleware()`: Get Express middleware function
|
|
90
|
+
- `handleMCPRequest(req, res)`: Process MCP protocol requests
|
|
91
|
+
|
|
92
|
+
### requirePayment(amount)
|
|
93
|
+
|
|
94
|
+
Middleware function that adds payment requirements to tools.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const paymentMiddleware = requirePayment('0.01'); // 0.01 SOL/USDC
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Payment Verification
|
|
101
|
+
|
|
102
|
+
The server automatically handles:
|
|
103
|
+
- On-chain payment confirmation
|
|
104
|
+
- Payment amount validation
|
|
105
|
+
- Duplicate payment prevention
|
|
106
|
+
- Transaction signature verification
|
|
107
|
+
|
|
108
|
+
## Tool Registration
|
|
109
|
+
|
|
110
|
+
Tools can be registered with various middleware combinations:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Free tool (no payment required)
|
|
114
|
+
server.addTool(toolDef, async (args) => {
|
|
115
|
+
return 'Free result';
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Paid tool with fixed amount
|
|
119
|
+
server.addTool(toolDef, requirePayment('0.01'), async (args) => {
|
|
120
|
+
return 'Paid result';
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Custom middleware chain
|
|
124
|
+
server.addTool(toolDef,
|
|
125
|
+
requirePayment('0.05'),
|
|
126
|
+
customValidation,
|
|
127
|
+
async (args) => {
|
|
128
|
+
return 'Premium result';
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Error Handling
|
|
134
|
+
|
|
135
|
+
The server provides structured error responses for:
|
|
136
|
+
- Authentication failures
|
|
137
|
+
- Payment requirement notifications
|
|
138
|
+
- Payment verification failures
|
|
139
|
+
- Tool execution errors
|
|
140
|
+
- Invalid MCP requests
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
See the `examples/server/` directory for a complete working server implementation.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ATXPConfig, TokenCheck } from "./types.js";
|
|
2
|
+
export declare function getATXPConfig(): ATXPConfig | null;
|
|
3
|
+
export declare function getATXPResource(): URL | null;
|
|
4
|
+
export declare function atxpAccountId(): string | null;
|
|
5
|
+
export declare function withATXPContext(config: ATXPConfig, resource: URL, tokenInfo: Pick<TokenCheck, 'token' | 'data'> | null, next: () => void): Promise<void>;
|
|
6
|
+
//# sourceMappingURL=atxpContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atxpContext.d.ts","sourceRoot":"","sources":["../src/atxpContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAWpD,wBAAgB,aAAa,IAAI,UAAU,GAAG,IAAI,CAGjD;AAED,wBAAgB,eAAe,IAAI,GAAG,GAAG,IAAI,CAG5C;AAGD,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAG7C;AAGD,wBAAsB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB9J"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
const contextStorage = new AsyncLocalStorage();
|
|
3
|
+
export function getATXPConfig() {
|
|
4
|
+
const context = contextStorage.getStore();
|
|
5
|
+
return context?.config ?? null;
|
|
6
|
+
}
|
|
7
|
+
export function getATXPResource() {
|
|
8
|
+
const context = contextStorage.getStore();
|
|
9
|
+
return context?.resource ?? null;
|
|
10
|
+
}
|
|
11
|
+
// Helper function to get the current request's user
|
|
12
|
+
export function atxpAccountId() {
|
|
13
|
+
const context = contextStorage.getStore();
|
|
14
|
+
return context?.tokenData?.sub ?? null;
|
|
15
|
+
}
|
|
16
|
+
// Helper function to run code within a user context
|
|
17
|
+
export async function withATXPContext(config, resource, tokenInfo, next) {
|
|
18
|
+
config.logger.debug(`Setting user context to ${tokenInfo?.data?.sub ?? 'null'}`);
|
|
19
|
+
if (tokenInfo && tokenInfo.data?.sub) {
|
|
20
|
+
if (tokenInfo.token) {
|
|
21
|
+
const dbData = {
|
|
22
|
+
accessToken: tokenInfo.token,
|
|
23
|
+
resourceUrl: ''
|
|
24
|
+
};
|
|
25
|
+
// Save the token to the oAuthDB so that other users of the DB can access it
|
|
26
|
+
// if needed (ie, for token-exchange for downstream services)
|
|
27
|
+
await config.oAuthDb.saveAccessToken(tokenInfo.data.sub, '', dbData);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
config.logger.warn(`Setting user context with token data, but there was no token provided. This probably indicates a bug, since the data should be derived from the token`);
|
|
31
|
+
config.logger.debug(`Token data: ${JSON.stringify(tokenInfo.data)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const ctx = {
|
|
35
|
+
tokenData: tokenInfo?.data || null,
|
|
36
|
+
config,
|
|
37
|
+
resource
|
|
38
|
+
};
|
|
39
|
+
return contextStorage.run(ctx, next);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=atxpContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atxpContext.js","sourceRoot":"","sources":["../src/atxpContext.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,cAAc,GAAG,IAAI,iBAAiB,EAAsB,CAAC;AAQnE,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,CAAC;IAC1C,OAAO,OAAO,EAAE,MAAM,IAAI,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,CAAC;IAC1C,OAAO,OAAO,EAAE,QAAQ,IAAI,IAAI,CAAC;AACnC,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE,CAAC;IAC1C,OAAO,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,IAAI,CAAC;AACzC,CAAC;AAED,oDAAoD;AACpD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAkB,EAAE,QAAa,EAAE,SAAoD,EAAE,IAAgB;IAC7I,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC;IAEjF,IAAG,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;QACpC,IAAG,SAAS,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG;gBACb,WAAW,EAAE,SAAS,CAAC,KAAM;gBAC7B,WAAW,EAAE,EAAE;aAChB,CAAC;YACF,4EAA4E;YAC5E,6DAA6D;YAC7D,MAAM,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,uJAAuJ,CAAC,CAAC;YAC5K,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG;QACV,SAAS,EAAE,SAAS,EAAE,IAAI,IAAI,IAAI;QAClC,MAAM;QACN,QAAQ;KACT,CAAC;IACF,OAAO,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ATXPConfig } from "./types.js";
|
|
2
|
+
import { Router } from "express";
|
|
3
|
+
type RequiredATXPConfigFields = 'destination';
|
|
4
|
+
type RequiredATXPConfig = Pick<ATXPConfig, RequiredATXPConfigFields>;
|
|
5
|
+
type OptionalATXPConfig = Omit<ATXPConfig, RequiredATXPConfigFields>;
|
|
6
|
+
export type ATXPArgs = RequiredATXPConfig & Partial<OptionalATXPConfig>;
|
|
7
|
+
type BuildableATXPConfigFields = 'oAuthDb' | 'oAuthClient' | 'paymentServer' | 'logger';
|
|
8
|
+
export declare const DEFAULT_CONFIG: Required<Omit<OptionalATXPConfig, BuildableATXPConfigFields>>;
|
|
9
|
+
export declare function buildServerConfig(args: ATXPArgs): ATXPConfig;
|
|
10
|
+
export declare function atxpServer(args: ATXPArgs): Router;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=atxpServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atxpServer.d.ts","sourceRoot":"","sources":["../src/atxpServer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAiB,MAAM,YAAY,CAAC;AAKvD,OAAO,EAAmC,MAAM,EAAE,MAAM,SAAS,CAAC;AAMlE,KAAK,wBAAwB,GAAG,aAAa,CAAC;AAC9C,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,wBAAwB,CAAC,CAAC;AACrE,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,wBAAwB,CAAC,CAAC;AACrE,MAAM,MAAM,QAAQ,GAAG,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;AACxE,KAAK,yBAAyB,GAAG,SAAS,GAAG,aAAa,GAAG,eAAe,GAAG,QAAQ,CAAC;AAExF,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,IAAI,CAAC,kBAAkB,EAAE,yBAAyB,CAAC,CASxF,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,UAAU,CA8B5D;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAyDjD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ConsoleLogger, OAuthResourceClient, DEFAULT_AUTHORIZATION_SERVER, MemoryOAuthDb } from "@atxp/common";
|
|
2
|
+
import { checkToken } from "./token.js";
|
|
3
|
+
import { sendOAuthChallenge } from "./oAuthChallenge.js";
|
|
4
|
+
import { withATXPContext } from "./atxpContext.js";
|
|
5
|
+
import { parseMcpRequests } from "./http.js";
|
|
6
|
+
import { Router } from "express";
|
|
7
|
+
import { getProtectedResourceMetadata as getPRMResponse, sendProtectedResourceMetadata } from "./protectedResourceMetadata.js";
|
|
8
|
+
import { getResource } from "./getResource.js";
|
|
9
|
+
import { ATXPPaymentServer } from "./paymentServer.js";
|
|
10
|
+
import { getOAuthMetadata, sendOAuthMetadata } from "./oAuthMetadata.js";
|
|
11
|
+
export const DEFAULT_CONFIG = {
|
|
12
|
+
mountPath: '/',
|
|
13
|
+
currency: 'USDC',
|
|
14
|
+
network: 'solana',
|
|
15
|
+
server: DEFAULT_AUTHORIZATION_SERVER,
|
|
16
|
+
atxpAuthClientToken: undefined, // Will be set in buildServerConfig
|
|
17
|
+
payeeName: 'An ATXP Server',
|
|
18
|
+
allowHttp: false, // May be overridden in buildServerConfig by process.env.NODE_ENV
|
|
19
|
+
resource: null, // Set dynamically from the request URL
|
|
20
|
+
};
|
|
21
|
+
export function buildServerConfig(args) {
|
|
22
|
+
if (!args.destination) {
|
|
23
|
+
throw new Error('destination is required');
|
|
24
|
+
}
|
|
25
|
+
// Read environment variables at runtime, not module load time
|
|
26
|
+
const envDefaults = {
|
|
27
|
+
...DEFAULT_CONFIG,
|
|
28
|
+
atxpAuthClientToken: process.env.ATXP_AUTH_CLIENT_TOKEN,
|
|
29
|
+
allowHttp: process.env.NODE_ENV === 'development',
|
|
30
|
+
};
|
|
31
|
+
const withDefaults = { ...envDefaults, ...args };
|
|
32
|
+
const oAuthDb = withDefaults.oAuthDb ?? new MemoryOAuthDb();
|
|
33
|
+
const oAuthClient = withDefaults.oAuthClient ?? new OAuthResourceClient({
|
|
34
|
+
db: oAuthDb,
|
|
35
|
+
allowInsecureRequests: withDefaults.allowHttp,
|
|
36
|
+
clientName: withDefaults.payeeName,
|
|
37
|
+
});
|
|
38
|
+
const logger = withDefaults.logger ?? new ConsoleLogger();
|
|
39
|
+
let paymentServer;
|
|
40
|
+
if (withDefaults.paymentServer) {
|
|
41
|
+
paymentServer = withDefaults.paymentServer;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
if (!withDefaults.atxpAuthClientToken) {
|
|
45
|
+
throw new Error('ATXP_AUTH_CLIENT_TOKEN is not set. If no payment server is provided, you must set ATXP_AUTH_CLIENT_TOKEN.');
|
|
46
|
+
}
|
|
47
|
+
paymentServer = new ATXPPaymentServer(withDefaults.server, withDefaults.atxpAuthClientToken, logger);
|
|
48
|
+
}
|
|
49
|
+
const built = { oAuthDb, oAuthClient, paymentServer, logger };
|
|
50
|
+
return Object.freeze({ ...withDefaults, ...built });
|
|
51
|
+
}
|
|
52
|
+
;
|
|
53
|
+
export function atxpServer(args) {
|
|
54
|
+
const config = buildServerConfig(args);
|
|
55
|
+
const router = Router();
|
|
56
|
+
// Regular middleware
|
|
57
|
+
const atxpMiddleware = async (req, res, next) => {
|
|
58
|
+
try {
|
|
59
|
+
const logger = config.logger; // Capture logger in closure
|
|
60
|
+
const requestUrl = new URL(req.url, req.protocol + '://' + req.host);
|
|
61
|
+
logger.debug(`Handling ${req.method} ${requestUrl.toString()}`);
|
|
62
|
+
const resource = getResource(config, requestUrl);
|
|
63
|
+
const prmResponse = getPRMResponse(config, requestUrl);
|
|
64
|
+
if (sendProtectedResourceMetadata(res, prmResponse)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Some older clients don't use PRM and assume the MCP server is an OAuth server
|
|
68
|
+
const oAuthMetadata = await getOAuthMetadata(config, requestUrl);
|
|
69
|
+
if (sendOAuthMetadata(res, oAuthMetadata)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const mcpRequests = await parseMcpRequests(config, requestUrl, req, req.body);
|
|
73
|
+
logger.debug(`${mcpRequests.length} MCP requests found in request`);
|
|
74
|
+
if (mcpRequests.length === 0) {
|
|
75
|
+
next();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
logger.debug(`Request started - ${req.method} ${req.path}`);
|
|
79
|
+
const tokenCheck = await checkToken(config, resource, req);
|
|
80
|
+
const user = tokenCheck.data?.sub ?? null;
|
|
81
|
+
// Listen for when the response is finished
|
|
82
|
+
res.on('finish', async () => {
|
|
83
|
+
logger.debug(`Request finished ${user ? `for user ${user} ` : ''}- ${req.method} ${req.path}`);
|
|
84
|
+
});
|
|
85
|
+
// Send the oauth challenge, if needed. If we do, we're done
|
|
86
|
+
if (sendOAuthChallenge(res, tokenCheck)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
return withATXPContext(config, resource, tokenCheck, next);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
config.logger.error(`Critical error in atxp middleware - returning HTTP 500. Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
93
|
+
config.logger.debug(JSON.stringify(error, null, 2));
|
|
94
|
+
res.status(500).json({ error: 'server_error', error_description: 'An internal server error occurred' });
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
// Add middleware to the router
|
|
98
|
+
router.use(atxpMiddleware);
|
|
99
|
+
return router;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=atxpServer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atxpServer.js","sourceRoot":"","sources":["../src/atxpServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getResource.d.ts","sourceRoot":"","sources":["../src/getResource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAGxC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,GAAG,GAAG,CAapE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function getPath(url) {
|
|
2
|
+
const fullPath = url.pathname.replace(/^\/$/, '');
|
|
3
|
+
return fullPath;
|
|
4
|
+
}
|
|
5
|
+
export function getResource(config, requestUrl) {
|
|
6
|
+
if (config.resource) {
|
|
7
|
+
return new URL(config.resource);
|
|
8
|
+
}
|
|
9
|
+
const protocol = process.env.NODE_ENV === 'development' ? requestUrl.protocol : 'https:';
|
|
10
|
+
const url = new URL(`${protocol}//${requestUrl.host}${requestUrl.pathname}`);
|
|
11
|
+
const fullPath = getPath(url);
|
|
12
|
+
// If this is a PRM path, convert it into the path for the resource this is the metadata for
|
|
13
|
+
const resourcePath = fullPath.replace('/.well-known/oauth-protected-resource', '').replace(/\/$/, '');
|
|
14
|
+
const resource = new URL(`${protocol}//${requestUrl.host}${resourcePath}`);
|
|
15
|
+
return resource;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=getResource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getResource.js","sourceRoot":"","sources":["../src/getResource.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,OAAO,CAAC,GAAQ;IAC9B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAkB,EAAE,UAAe;IAC7D,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IACzF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,KAAK,UAAU,CAAC,IAAI,GAAG,UAAU,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE7E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,4FAA4F;IAC5F,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,uCAAuC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAEtG,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,QAAQ,KAAK,UAAU,CAAC,IAAI,GAAG,YAAY,EAAE,CAAC,CAAC;IAC3E,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IncomingMessage } from "node:http";
|
|
2
|
+
import { JSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { ATXPConfig } from "./types.js";
|
|
4
|
+
import { Logger } from "@atxp/common";
|
|
5
|
+
export declare function parseMcpRequests(config: ATXPConfig, requestUrl: URL, req: IncomingMessage, parsedBody?: unknown): Promise<JSONRPCRequest[]>;
|
|
6
|
+
export declare function parseBody(req: IncomingMessage, logger: Logger): Promise<unknown>;
|
|
7
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,cAAc,EAAoB,MAAM,oCAAoC,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAoB,MAAM,EAAE,MAAM,cAAc,CAAC;AAQxD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CA0BjJ;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAmBtF"}
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import getRawBody from "raw-body";
|
|
2
|
+
import contentType from "content-type";
|
|
3
|
+
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { parseMcpMessages } from "@atxp/common";
|
|
5
|
+
// Useful reference for dealing with low-level http requests:
|
|
6
|
+
// https://github.com/modelcontextprotocol/typescript-sdk/blob/c6ac083b1b37b222b5bfba5563822daa5d03372e/src/server/streamableHttp.ts#L375
|
|
7
|
+
// Using the same value as MCP SDK
|
|
8
|
+
const MAXIMUM_MESSAGE_SIZE = "4mb";
|
|
9
|
+
export async function parseMcpRequests(config, requestUrl, req, parsedBody) {
|
|
10
|
+
if (!req.method) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
if (req.method.toLowerCase() !== 'post') {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
// The middleware has to be mounted at the root to serve the protected resource metadata,
|
|
17
|
+
// but the actual MCP server it's controlling is specified by the mountPath.
|
|
18
|
+
const path = requestUrl.pathname.replace(/\/$/, '');
|
|
19
|
+
const mountPath = config.mountPath.replace(/\/$/, '');
|
|
20
|
+
if (path !== mountPath && path !== `${mountPath}/message`) {
|
|
21
|
+
config.logger.debug(`Request path (${path}) does not match the mountPath (${mountPath}), skipping MCP middleware`);
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
parsedBody = parsedBody ?? await parseBody(req, config.logger);
|
|
25
|
+
const messages = await parseMcpMessages(parsedBody, config.logger);
|
|
26
|
+
const requests = messages.filter(msg => isJSONRPCRequest(msg));
|
|
27
|
+
if (requests.length !== messages.length) {
|
|
28
|
+
config.logger.debug(`Dropped ${messages.length - requests.length} MCP messages that were not MCP requests`);
|
|
29
|
+
}
|
|
30
|
+
return requests;
|
|
31
|
+
}
|
|
32
|
+
export async function parseBody(req, logger) {
|
|
33
|
+
try {
|
|
34
|
+
const ct = req.headers["content-type"];
|
|
35
|
+
let encoding = "utf-8";
|
|
36
|
+
if (ct) {
|
|
37
|
+
const parsedCt = contentType.parse(ct);
|
|
38
|
+
encoding = parsedCt.parameters.charset ?? "utf-8";
|
|
39
|
+
}
|
|
40
|
+
const body = await getRawBody(req, {
|
|
41
|
+
limit: MAXIMUM_MESSAGE_SIZE,
|
|
42
|
+
encoding,
|
|
43
|
+
});
|
|
44
|
+
return JSON.parse(body.toString());
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
logger.error(error.message);
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,UAAU,MAAM,UAAU,CAAC;AAClC,OAAO,WAAW,MAAM,cAAc,CAAC;AACvC,OAAO,EAAkB,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAEtF,OAAO,EAAE,gBAAgB,EAAU,MAAM,cAAc,CAAC;AAExD,6DAA6D;AAC7D,yIAAyI;AAEzI,kCAAkC;AAClC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAEnC,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAkB,EAAE,UAAe,EAAE,GAAoB,EAAE,UAAoB;IACpH,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;QACxC,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,yFAAyF;IACzF,4EAA4E;IAC5E,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,GAAG,SAAS,UAAU,EAAE,CAAC;QAC1D,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,IAAI,mCAAmC,SAAS,4BAA4B,CAAC,CAAC;QACnH,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,UAAU,GAAG,UAAU,IAAI,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAEnE,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,QAAQ,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,0CAA0C,CAAC,CAAC;IAC9G,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAoB,EAAE,MAAc;IAClE,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAEvC,IAAI,QAAQ,GAAG,OAAO,CAAC;QACvB,IAAI,EAAE,EAAE,CAAC;YACP,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACvC,QAAQ,GAAG,QAAQ,CAAC,UAAU,CAAC,OAAO,IAAI,OAAO,CAAC;QACpD,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE;YACjC,KAAK,EAAE,oBAAoB;YAC3B,QAAQ;SACT,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAE,KAAe,CAAC,OAAO,CAAC,CAAC;QACvC,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oAuthChallenge.d.ts","sourceRoot":"","sources":["../src/oAuthChallenge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AACtC,OAAO,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAGtD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAsCvF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { TokenProblem } from "./types.js";
|
|
2
|
+
import { assertNever } from "@atxp/common";
|
|
3
|
+
export function sendOAuthChallenge(res, tokenCheck) {
|
|
4
|
+
if (tokenCheck.passes) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
let status = 401;
|
|
8
|
+
let body = {};
|
|
9
|
+
// https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
|
|
10
|
+
switch (tokenCheck.problem) {
|
|
11
|
+
case TokenProblem.NO_TOKEN:
|
|
12
|
+
break;
|
|
13
|
+
case TokenProblem.NON_BEARER_AUTH_HEADER:
|
|
14
|
+
status = 400;
|
|
15
|
+
body = { error: 'invalid_request', error_description: 'Authorization header did not include a Bearer token' };
|
|
16
|
+
break;
|
|
17
|
+
case TokenProblem.INVALID_TOKEN:
|
|
18
|
+
body = { error: 'invalid_token', error_description: 'Token is not active' };
|
|
19
|
+
break;
|
|
20
|
+
case TokenProblem.INVALID_AUDIENCE:
|
|
21
|
+
body = { error: 'invalid_token', error_description: 'Token is does not match the expected audience' };
|
|
22
|
+
break;
|
|
23
|
+
case TokenProblem.NON_SUFFICIENT_FUNDS:
|
|
24
|
+
status = 403;
|
|
25
|
+
body = { error: 'insufficient_scope', error_description: 'Non sufficient funds' };
|
|
26
|
+
break;
|
|
27
|
+
case TokenProblem.INTROSPECT_ERROR:
|
|
28
|
+
status = 502;
|
|
29
|
+
body = { error: 'server_error', error_description: 'An internal server error occurred' };
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
assertNever(tokenCheck.problem);
|
|
33
|
+
}
|
|
34
|
+
res.setHeader('WWW-Authenticate', `Bearer resource_metadata="${tokenCheck.resourceMetadataUrl}"`);
|
|
35
|
+
res.writeHead(status);
|
|
36
|
+
res.end(JSON.stringify(body));
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=oAuthChallenge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oAuthChallenge.js","sourceRoot":"","sources":["../src/oAuthChallenge.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,YAAY,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,UAAU,kBAAkB,CAAC,GAAmB,EAAE,UAAsB;IAC5E,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,MAAM,GAAG,GAAG,CAAC;IACjB,IAAI,IAAI,GAAkE,EAAE,CAAC;IAC7E,4DAA4D;IAC5D,QAAQ,UAAU,CAAC,OAAO,EAAE,CAAC;QAC3B,KAAK,YAAY,CAAC,QAAQ;YACxB,MAAM;QACR,KAAK,YAAY,CAAC,sBAAsB;YACtC,MAAM,GAAG,GAAG,CAAC;YACb,IAAI,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qDAAqD,EAAE,CAAC;YAC9G,MAAM;QACR,KAAK,YAAY,CAAC,aAAa;YAC7B,IAAI,GAAG,EAAE,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,CAAC;YAC5E,MAAM;QACR,KAAK,YAAY,CAAC,gBAAgB;YAChC,IAAI,GAAG,EAAE,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,+CAA+C,EAAE,CAAC;YACtG,MAAM;QACR,KAAK,YAAY,CAAC,oBAAoB;YACpC,MAAM,GAAG,GAAG,CAAC;YACb,IAAI,GAAG,EAAE,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,CAAC;YAClF,MAAM;QACR,KAAK,YAAY,CAAC,gBAAgB;YAChC,MAAM,GAAG,GAAG,CAAC;YACb,IAAI,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,mCAAmC,EAAE,CAAC;YACzF,MAAM;QACR;YACE,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,GAAG,CAAC,SAAS,CAAC,kBAAkB,EAAE,6BAA6B,UAAU,CAAC,mBAAmB,GAAG,CAAC,CAAC;IAClG,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAE9B,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ATXPConfig } from "./types.js";
|
|
2
|
+
import * as oauth from 'oauth4webapi';
|
|
3
|
+
import { ServerResponse } from "http";
|
|
4
|
+
export declare function sendOAuthMetadata(res: ServerResponse, metadata: oauth.AuthorizationServer | null): boolean;
|
|
5
|
+
export declare function getOAuthMetadata(config: ATXPConfig, requestUrl: URL): Promise<oauth.AuthorizationServer | null>;
|
|
6
|
+
//# sourceMappingURL=oAuthMetadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oAuthMetadata.d.ts","sourceRoot":"","sources":["../src/oAuthMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,MAAM,CAAC;AAGtC,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,CAAC,mBAAmB,GAAG,IAAI,GAAG,OAAO,CAQ1G;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAyBrH"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getPath } from "./getResource.js";
|
|
2
|
+
export function sendOAuthMetadata(res, metadata) {
|
|
3
|
+
if (!metadata) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
res.setHeader('Content-Type', 'application/json');
|
|
7
|
+
res.writeHead(200);
|
|
8
|
+
res.end(JSON.stringify(metadata));
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
export async function getOAuthMetadata(config, requestUrl) {
|
|
12
|
+
if (isOAuthMetadataRequest(config, requestUrl)) {
|
|
13
|
+
try {
|
|
14
|
+
const authServer = await config.oAuthClient.authorizationServerFromUrl(new URL(config.server));
|
|
15
|
+
return {
|
|
16
|
+
issuer: config.server,
|
|
17
|
+
authorization_endpoint: authServer.authorization_endpoint,
|
|
18
|
+
response_types_supported: authServer.response_types_supported,
|
|
19
|
+
grant_types_supported: authServer.grant_types_supported,
|
|
20
|
+
token_endpoint: authServer.token_endpoint,
|
|
21
|
+
token_endpoint_auth_methods_supported: authServer.token_endpoint_auth_methods_supported,
|
|
22
|
+
registration_endpoint: authServer.registration_endpoint,
|
|
23
|
+
revocation_endpoint: authServer.revocation_endpoint,
|
|
24
|
+
introspection_endpoint: authServer.introspection_endpoint,
|
|
25
|
+
introspection_endpoint_auth_methods_supported: authServer.introspection_endpoint_auth_methods_supported,
|
|
26
|
+
code_challenge_methods_supported: authServer.code_challenge_methods_supported,
|
|
27
|
+
scopes_supported: authServer.scopes_supported
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
config.logger.error(`Error fetching authorization server configuration from ${config.server}: ${error}`);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function isOAuthMetadataRequest(config, requestUrl) {
|
|
38
|
+
const path = getPath(requestUrl).replace(/\/$/, '');
|
|
39
|
+
return path === '/.well-known/oauth-authorization-server';
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=oAuthMetadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oAuthMetadata.js","sourceRoot":"","sources":["../src/oAuthMetadata.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,UAAU,iBAAiB,CAAC,GAAmB,EAAE,QAA0C;IAC/F,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,CAAC;IACf,CAAC;IACD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IAClD,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACnB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAkB,EAAE,UAAe;IACxE,IAAI,sBAAsB,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,0BAA0B,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAE/F,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,sBAAsB,EAAE,UAAU,CAAC,sBAAsB;gBACzD,wBAAwB,EAAE,UAAU,CAAC,wBAAwB;gBAC7D,qBAAqB,EAAE,UAAU,CAAC,qBAAqB;gBACvD,cAAc,EAAE,UAAU,CAAC,cAAc;gBACzC,qCAAqC,EAAE,UAAU,CAAC,qCAAqC;gBACvF,qBAAqB,EAAE,UAAU,CAAC,qBAAqB;gBACvD,mBAAmB,EAAE,UAAU,CAAC,mBAAmB;gBACnD,sBAAsB,EAAE,UAAU,CAAC,sBAAsB;gBACzD,6CAA6C,EAAE,UAAU,CAAC,6CAA6C;gBACvG,gCAAgC,EAAE,UAAU,CAAC,gCAAgC;gBAC7E,gBAAgB,EAAE,UAAU,CAAC,gBAAgB;aAC9C,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,MAAM,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC,CAAC;YACzG,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAkB,EAAE,UAAe;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACpD,OAAO,IAAI,KAAK,yCAAyC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { PaymentServer, ChargeResponse } from "./types.js";
|
|
2
|
+
import { Network, Currency, AuthorizationServerUrl, FetchLike, Logger } from "@atxp/common";
|
|
3
|
+
import BigNumber from "bignumber.js";
|
|
4
|
+
/**
|
|
5
|
+
* ATXP Payment Server implementation
|
|
6
|
+
*
|
|
7
|
+
* This class handles payment operations with the ATXP authorization server.
|
|
8
|
+
*
|
|
9
|
+
* **Required Environment Variable:**
|
|
10
|
+
* - `ATXP_AUTH_CLIENT_TOKEN`: Authentication token for the ATXP authorization server.
|
|
11
|
+
* This token is used to authenticate API calls to the ATXP server for payment operations.
|
|
12
|
+
* Must be set when using this class, otherwise an error will be thrown.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* // Ensure ATXP_AUTH_CLIENT_TOKEN is set in your environment
|
|
17
|
+
* const paymentServer = new ATXPPaymentServer(
|
|
18
|
+
* 'https://auth.atxp.ai',
|
|
19
|
+
* oAuthDb,
|
|
20
|
+
* logger
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare class ATXPPaymentServer implements PaymentServer {
|
|
25
|
+
private readonly server;
|
|
26
|
+
private readonly authCredentials;
|
|
27
|
+
private readonly logger;
|
|
28
|
+
private readonly fetchFn;
|
|
29
|
+
constructor(server: AuthorizationServerUrl, authCredentials: string, logger: Logger, fetchFn?: FetchLike);
|
|
30
|
+
charge: ({ source, destination, network, currency, amount }: {
|
|
31
|
+
source: string;
|
|
32
|
+
destination: string;
|
|
33
|
+
network: Network;
|
|
34
|
+
currency: Currency;
|
|
35
|
+
amount: BigNumber;
|
|
36
|
+
}) => Promise<ChargeResponse>;
|
|
37
|
+
createPaymentRequest: ({ source, destination, network, currency, amount }: {
|
|
38
|
+
source: string;
|
|
39
|
+
destination: string;
|
|
40
|
+
network: Network;
|
|
41
|
+
currency: Currency;
|
|
42
|
+
amount: BigNumber;
|
|
43
|
+
}) => Promise<string>;
|
|
44
|
+
/**
|
|
45
|
+
* Makes authenticated requests to the ATXP authorization server
|
|
46
|
+
*
|
|
47
|
+
* This method automatically includes the `ATXP_AUTH_CLIENT_TOKEN` from environment variables
|
|
48
|
+
* in the Authorization header for all requests.
|
|
49
|
+
*
|
|
50
|
+
* @param method - HTTP method ('GET' or 'POST')
|
|
51
|
+
* @param path - API endpoint path
|
|
52
|
+
* @param body - Request body (for POST requests)
|
|
53
|
+
* @returns Promise<Response> - The HTTP response from the server
|
|
54
|
+
* @throws {Error} When `ATXP_AUTH_CLIENT_TOKEN` environment variable is not set
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Ensure ATXP_AUTH_CLIENT_TOKEN is set in your environment
|
|
59
|
+
* const response = await paymentServer.makeRequest('POST', '/charge', {
|
|
60
|
+
* source: 'user123',
|
|
61
|
+
* destination: 'merchant456',
|
|
62
|
+
* amount: new BigNumber('0.01')
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
protected makeRequest: (method: "GET" | "POST", path: string, body: unknown) => Promise<Response>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=paymentServer.d.ts.map
|