Skip to content

Commit 16435ed

Browse files
committed
feat: launch initial public release
2 parents c17eb66 + 768122e commit 16435ed

88 files changed

Lines changed: 852 additions & 7805 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 29 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,17 @@
44
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
55
[![CI](https://github.com/Abstract-Foundation/agw-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/Abstract-Foundation/agw-mcp/actions/workflows/ci.yml)
66

7-
MCP server for [Abstract Global Wallet](https://abs.xyz) session-key workflows — scoped wallet actions without custodial signing.
7+
MCP server for Abstract wallet, chain, and Portal API data.
88

99
## Quick Start
1010

11-
```bash
12-
npx -y @abstract-foundation/agw-mcp serve --chain-id 2741
13-
```
14-
15-
Or add it to Claude Code directly:
16-
17-
```bash
18-
claude mcp add agw -- npx -y @abstract-foundation/agw-mcp serve --chain-id 2741
19-
```
20-
21-
## Setup
22-
23-
### 1. Bootstrap a session
24-
2511
```bash
2612
npx -y @abstract-foundation/agw-mcp init --chain-id 2741
27-
```
28-
29-
This opens the hosted onboarding app (`https://mcp.abs.xyz` by default) where you:
30-
31-
1. Choose a policy preset (or provide custom policy JSON)
32-
2. Connect your Abstract Global Wallet
33-
3. Approve the session key
34-
35-
Session data is saved to `~/.agw-mcp/session.json` with `0o600` file permissions. The session signer key is stored separately in `~/.agw-mcp/session-signer.key`.
36-
If a previous active session exists locally, the CLI attempts to revoke it on-chain after creating the new one.
37-
Bootstrap is single-process per storage directory (lockfile: `~/.agw-mcp/.bootstrap-init.lock`) to prevent concurrent `init` races.
38-
When local sessions are revoked/cleared, the signer keyfile is deleted as part of local cleanup.
39-
40-
### 2. Start the MCP server
41-
42-
```bash
4313
npx -y @abstract-foundation/agw-mcp serve --chain-id 2741
4414
```
4515

16+
`init` opens the hosted onboarding app (`https://mcp.abs.xyz` by default), links your wallet address for local context, and writes `~/.agw-mcp/session.json`.
17+
4618
## Client Configuration
4719

4820
### Claude Code
@@ -51,46 +23,6 @@ npx -y @abstract-foundation/agw-mcp serve --chain-id 2741
5123
claude mcp add agw -- npx -y @abstract-foundation/agw-mcp serve --chain-id 2741
5224
```
5325

54-
### Claude Desktop
55-
56-
Add to your `claude_desktop_config.json`:
57-
58-
<details>
59-
<summary>macOS: ~/Library/Application Support/Claude/claude_desktop_config.json</summary>
60-
61-
```json
62-
{
63-
"mcpServers": {
64-
"agw-mcp": {
65-
"command": "npx",
66-
"args": ["-y", "@abstract-foundation/agw-mcp", "serve", "--chain-id", "2741"]
67-
}
68-
}
69-
}
70-
```
71-
72-
</details>
73-
74-
<details>
75-
<summary>Windows: %APPDATA%\Claude\claude_desktop_config.json</summary>
76-
77-
```json
78-
{
79-
"mcpServers": {
80-
"agw-mcp": {
81-
"command": "npx",
82-
"args": ["-y", "@abstract-foundation/agw-mcp", "serve", "--chain-id", "2741"]
83-
}
84-
}
85-
}
86-
```
87-
88-
</details>
89-
90-
### Cursor / Windsurf
91-
92-
Use the same JSON block as Claude Desktop in your editor's MCP configuration file.
93-
9426
### Generate config snippet
9527

9628
```bash
@@ -101,70 +33,59 @@ npx -y @abstract-foundation/agw-mcp config --npx --chain-id 2741
10133

10234
| Tool | Description |
10335
|------|-------------|
104-
| `get_wallet_address` | Returns AGW account address from local session |
105-
| `get_balances` | Native + ERC-20 balances with formatted amounts |
106-
| `get_token_list` | Wallet ERC-20 holdings via network discovery |
107-
| `get_session_status` | On-chain session state + local expiry metadata |
108-
| `sign_message` | Signs UTF-8 message via session signer |
109-
| `sign_transaction` | Signs EVM transaction, returns signed payload (no broadcast) |
110-
| `preview_transaction` | Impact/risk preview without signing |
111-
| `send_transaction` | Preview by default, broadcast on `execute: true` |
112-
| `send_calls` | EIP-5792 batch call execution |
113-
| `transfer_token` | Native/ERC-20 transfer with policy checks |
114-
| `swap_tokens` | 0x quote + execute via session key |
115-
| `write_contract` | Contract write with target/selector policy validation |
116-
| `deploy_contract` | Contract deployment with ABI/bytecode validation |
117-
| `revoke_session` | Revoke session key, invalidate local session |
36+
| `get_wallet_address` | Returns the linked AGW account address from local session storage |
37+
| `get_balances` | Returns native and ERC-20 balances |
38+
| `get_token_list` | Returns wallet ERC-20 holdings |
39+
| `portal_list_apps` | Lists Portal apps (`/api/v1/app/`) |
40+
| `portal_get_app` | Fetches Portal app detail (`/api/v1/app/{id}/`) |
41+
| `portal_list_streams` | Lists streams for a Portal app (`/api/v1/streams/{app}/`) |
42+
| `portal_get_user_profile` | Fetches Portal user profile (`/api/v1/user/profile/{address}/`) |
43+
| `abstract_rpc_call` | Calls supported Abstract JSON-RPC methods |
44+
45+
### `abstract_rpc_call` constraints
46+
47+
Blocked by design in v0:
48+
- `eth_sendRawTransaction`
49+
- `zks_sendRawTransactionWithDetailedOutput`
50+
- `debug_*`
51+
- `eth_subscribe`, `eth_unsubscribe`
52+
- filter lifecycle methods (`eth_newFilter`, `eth_getFilterChanges`, etc.)
11853

11954
## Network Configuration
12055

121-
Defaults to Abstract mainnet (chain ID `2741`). Override RPC or switch to testnet when needed:
56+
Defaults to Abstract mainnet (`2741`).
12257

12358
```bash
12459
# Mainnet
12560
npx -y @abstract-foundation/agw-mcp serve --chain-id 2741
12661

12762
# Custom RPC
12863
npx -y @abstract-foundation/agw-mcp serve --chain-id 2741 --rpc-url https://api.mainnet.abs.xyz
129-
130-
# 0x API key override (for swap_tokens quote requests)
131-
npx -y @abstract-foundation/agw-mcp serve --chain-id 2741 --zeroex-api-key YOUR_0X_API_KEY
13264
```
13365

134-
Environment variables are also supported:
66+
Environment variables:
13567

13668
```bash
13769
AGW_MCP_CHAIN_ID=2741 npx -y @abstract-foundation/agw-mcp serve
13870
AGW_MCP_RPC_URL=https://api.mainnet.abs.xyz npx -y @abstract-foundation/agw-mcp serve
139-
AGW_MCP_ZEROEX_API_KEY=YOUR_0X_API_KEY npx -y @abstract-foundation/agw-mcp serve
14071
AGW_MCP_APP_URL=https://mcp.abs.xyz npx -y @abstract-foundation/agw-mcp init --chain-id 2741
14172
```
14273

143-
`init` requires `https://` app URLs except for loopback local development URLs (`http://localhost`, `http://127.0.0.1`, `http://[::1]`).
144-
`init` defaults to `https://mcp.abs.xyz` if no app URL is configured via `--app-url` or `AGW_MCP_APP_URL`.
74+
`init` requires `https://` app URLs except loopback (`http://localhost`, `http://127.0.0.1`, `http://[::1]`).
14575

14676
For local hosted-app development:
14777

14878
```bash
14979
npx -y @abstract-foundation/agw-mcp init --chain-id 2741 --app-url http://localhost:3001
15080
```
15181

152-
## Security Model
153-
154-
- **Non-custodial**: Session keys are scoped and time-limited. No full wallet access.
155-
- **Default-deny policies**: Write tools fail unless a matching policy explicitly allows the target address, function selector, or transfer amount.
156-
- **Local-only transport**: stdio MCP — no network exposure. Session signer keys never leave the machine.
157-
- **Restrictive file permissions**: Session storage directory `0o700`, files `0o600`.
158-
- **Stderr-only logging**: stdout is reserved for MCP stdio transport. All operational logs go to stderr.
159-
160-
### Real Funds Checklist
161-
162-
For production usage with real money:
82+
## Security Model (v0)
16383

164-
1. Use a trusted onboarding host (`--app-url` or `AGW_MCP_APP_URL`) and pin it in deployment config.
165-
2. Start with minimal intent scope (prefer payments-only) and shortest practical expiry.
166-
3. Keep `execute` off by default and run preview-first workflows where possible.
167-
4. Revoke sessions after task completion (`revoke_session`) and confirm status with `get_session_status`.
84+
- **Scoped MCP surface**: no signing, transfers, swaps, deploys, or session-key actions exposed.
85+
- **No delegated signer provisioning in onboarding**: local context stores wallet address + chain only.
86+
- **Local-only transport**: stdio MCP (no network listener).
87+
- **Restrictive file permissions**: storage dir `0o700`, files `0o600`.
88+
- **Stderr-only logging**: stdout is reserved for MCP transport.
16889

16990
## Development
17091

app/README.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,27 @@ This package hosts the browser-side onboarding flow for `agw-mcp init`.
99
- Expected query parameters:
1010
- `callback_url`: loopback callback URL (`http://localhost`, `http://127.0.0.1`, `http://[::1]`)
1111
- `chain_id`: supported chains only (`11124`, `2741`)
12-
- `signer`: session signer EVM address generated by the CLI
1312

1413
If validation fails, the page renders a non-retryable parameter error and instructs the user to restart from CLI.
1514

1615
## Callback Contract
1716

18-
After successful session creation, the app redirects to `callback_url` with:
17+
After wallet link succeeds, the app redirects to `callback_url` with:
1918

2019
- `session=<base64url(json-bundle)>`
2120

2221
Bundle payload fields:
2322

2423
- `accountAddress`
2524
- `chainId`
26-
- `expiresAt`
27-
- `sessionConfig`
2825

29-
No signer private key or signer reference is ever returned in the redirect.
26+
No signer private key, signer reference, policy, or delegated session data is returned in the redirect.
3027

3128
## State Machine
3229

3330
Wizard state is centralized in `src/stores/useSessionWizardStore.ts`:
3431

35-
- `not_logged_in -> select_policy -> review_policy -> creating -> success`
36-
- Errors transition to `error` and retry goes back to `select_policy`.
32+
- `not_logged_in -> creating -> success`
33+
- Errors transition to `error` and retry returns to `not_logged_in`.
3734

38-
All transitions are invoked through explicit store actions (not direct step mutation in components).
35+
All transitions are invoked through explicit store actions.

app/src/app/session/new/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ export default async function NewSessionPage({
4444
<SessionFlowClient
4545
callbackUrl={result.params.callbackUrl}
4646
chainId={result.params.chainId}
47-
signerAddress={result.params.signerAddress}
4847
/>
4948
</div>
5049
);

app/src/components/SessionFlowClient.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ import AbstractProvider from '@/providers/AbstractProvider';
77
export default function SessionFlowClient({
88
callbackUrl,
99
chainId,
10-
signerAddress,
1110
}: {
1211
callbackUrl: string;
1312
chainId: SupportedChainId;
14-
signerAddress: `0x${string}`;
1513
}) {
1614
const chain = resolveChain(chainId);
1715

1816
return (
1917
<AbstractProvider chain={chain}>
20-
<SessionWizard callbackUrl={callbackUrl} chain={chain} signerAddress={signerAddress} />
18+
<SessionWizard callbackUrl={callbackUrl} chain={chain} />
2119
</AbstractProvider>
2220
);
2321
}

app/src/components/SessionWizard/index.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,22 @@ import { useSessionWizardState } from '@/hooks/useSessionWizardState';
55
import Creating from './steps/Creating';
66
import ErrorStep from './steps/Error';
77
import NotLoggedIn from './steps/NotLoggedIn';
8-
import SelectPolicy from './steps/SelectPolicy';
98
import Success from './steps/Success';
109

1110
export default function SessionWizard({
1211
callbackUrl,
13-
signerAddress,
1412
chain,
1513
}: {
1614
callbackUrl: string;
17-
signerAddress: `0x${string}`;
1815
chain: Chain;
1916
}) {
2017
const { currentStep } = useSessionWizardState();
2118

2219
switch (currentStep) {
2320
case 'not_logged_in':
2421
return <NotLoggedIn />;
25-
case 'select_policy':
26-
return <SelectPolicy />;
2722
case 'creating':
28-
return <Creating callbackUrl={callbackUrl} signerAddress={signerAddress} chain={chain} />;
23+
return <Creating callbackUrl={callbackUrl} chain={chain} />;
2924
case 'success':
3025
return <Success />;
3126
case 'error':

app/src/components/SessionWizard/steps/Creating.tsx

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,34 +14,27 @@ import {
1414
import { useCreateSession } from '@/hooks/useCreateSession';
1515
import { useSessionWizardState } from '@/hooks/useSessionWizardState';
1616
import { buildRedirectUrl } from '@/lib/redirect';
17-
import { serializeSessionConfig } from '@/lib/session-config';
1817
import styles from '../styles.module.scss';
1918

2019
export default function Creating({
2120
callbackUrl,
22-
signerAddress,
2321
chain,
2422
}: {
2523
callbackUrl: string;
26-
signerAddress: `0x${string}`;
2724
chain: Chain;
2825
}) {
2926
const hasStartedRef = useRef(false);
3027
const {
3128
agwAddress,
32-
policyPreview,
3329
markCreationError,
3430
markCreationSuccess,
3531
} = useSessionWizardState();
36-
const { createSession, isPending, isClientReady, isClientLoading } = useCreateSession(chain);
32+
const { createSession, isPending } = useCreateSession();
3733

3834
useEffect(() => {
3935
if (hasStartedRef.current) {
4036
return;
4137
}
42-
if (!isClientReady) {
43-
return;
44-
}
4538

4639
hasStartedRef.current = true;
4740

@@ -50,28 +43,15 @@ export default function Creating({
5043
if (!agwAddress) {
5144
throw new Error('Wallet is not connected.');
5245
}
53-
if (!policyPreview) {
54-
throw new Error('Policy preview is missing.');
55-
}
5646
const accountAddress = agwAddress as `0x${string}`;
5747

58-
const result = await createSession({
59-
accountAddress,
60-
signerAddress,
61-
policyPayload: policyPreview.policyPayload,
62-
});
63-
64-
const bundle = {
48+
const bundle = await createSession({
6549
accountAddress,
6650
chainId: chain.id,
67-
expiresAt: Number(result.sessionConfig.expiresAt),
68-
sessionConfig: serializeSessionConfig(result.sessionConfig),
69-
policyMeta: policyPreview.policyPayload.policyMeta,
70-
};
51+
});
7152

7253
const redirectUrl = buildRedirectUrl(callbackUrl, bundle);
7354
markCreationSuccess({
74-
transactionHash: result.transactionHash ?? null,
7555
redirectUrl,
7656
});
7757
} catch (error) {
@@ -82,12 +62,9 @@ export default function Creating({
8262
void run();
8363
}, [
8464
agwAddress,
85-
policyPreview,
8665
createSession,
87-
signerAddress,
8866
callbackUrl,
8967
chain.id,
90-
isClientReady,
9168
markCreationError,
9269
markCreationSuccess,
9370
]);
@@ -96,16 +73,14 @@ export default function Creating({
9673
<div className={styles.wrapper}>
9774
<Card>
9875
<CardHeader>
99-
<CardTitle>Creating Session</CardTitle>
100-
<CardDescription>Approve the transaction in your wallet to create the session key.</CardDescription>
76+
<CardTitle>Linking Wallet</CardTitle>
77+
<CardDescription>Preparing wallet context for your local MCP server.</CardDescription>
10178
</CardHeader>
10279
<CardContent>
10380
<p className={styles.helper}>
104-
{!isClientReady || isClientLoading
105-
? 'Preparing wallet client...'
106-
: isPending
107-
? 'Waiting for wallet approval...'
108-
: 'Preparing transaction...'}
81+
{isPending
82+
? 'Finalizing wallet link...'
83+
: 'Building local callback payload...'}
10984
</p>
11085
</CardContent>
11186
<CardFooter className={styles.footer}>

0 commit comments

Comments
 (0)