auramaxx 1.0.0-alpha.4
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/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AuraWallet MCP Server
|
|
4
|
+
* =====================
|
|
5
|
+
* Standalone MCP stdio server that gives any AI agent access to the wallet API.
|
|
6
|
+
*
|
|
7
|
+
* Auth bootstrap (in order):
|
|
8
|
+
* 1. Unix socket auto-approve (ephemeral RSA keypair, encrypted token, zero config)
|
|
9
|
+
* 2. AURA_TOKEN env var (CI/CD fallback)
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* npx tsx server/mcp/server.ts
|
|
13
|
+
* AURA_TOKEN=<token> npx tsx server/mcp/server.ts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
17
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
18
|
+
import { z } from 'zod';
|
|
19
|
+
import { readFileSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
21
|
+
import * as net from 'net';
|
|
22
|
+
import {
|
|
23
|
+
constants,
|
|
24
|
+
createDecipheriv,
|
|
25
|
+
generateKeyPairSync,
|
|
26
|
+
privateDecrypt,
|
|
27
|
+
} from 'crypto';
|
|
28
|
+
import { TOOLS, executeTool, jsonSchemaToZod } from './tools.js';
|
|
29
|
+
import { buildScopedReadTokenIssueRequest, buildScopedWriteTokenIssueRequest } from '../lib/credential-transport';
|
|
30
|
+
import { resolveMcpIssuanceProfile } from './profile-policy';
|
|
31
|
+
import { evaluateProjectScopeAccess, emitProjectScopeEvent } from '../lib/project-scope';
|
|
32
|
+
|
|
33
|
+
let token: string | undefined = process.env.AURA_TOKEN;
|
|
34
|
+
|
|
35
|
+
const WALLET_BASE = () => process.env.WALLET_SERVER_URL || 'http://127.0.0.1:4242';
|
|
36
|
+
|
|
37
|
+
// ── Socket Bootstrap ───────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
interface HybridEnvelope {
|
|
40
|
+
v: number;
|
|
41
|
+
alg: string;
|
|
42
|
+
key: string;
|
|
43
|
+
iv: string;
|
|
44
|
+
tag: string;
|
|
45
|
+
data: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Decrypt an encrypted blob (token or credential) using our private key.
|
|
50
|
+
* Supports both direct RSA-OAEP and hybrid RSA-OAEP/AES-256-GCM envelopes.
|
|
51
|
+
*/
|
|
52
|
+
function decryptWithPrivateKey(encryptedBase64: string, privateKeyPem: string): string {
|
|
53
|
+
const decoded = Buffer.from(encryptedBase64, 'base64');
|
|
54
|
+
let envelope: HybridEnvelope;
|
|
55
|
+
try {
|
|
56
|
+
envelope = JSON.parse(decoded.toString('utf8')) as HybridEnvelope;
|
|
57
|
+
} catch {
|
|
58
|
+
// Direct RSA-OAEP ciphertext (small payloads)
|
|
59
|
+
return privateDecrypt(
|
|
60
|
+
{ key: privateKeyPem, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
|
|
61
|
+
decoded,
|
|
62
|
+
).toString('utf8');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (envelope.v !== 1 || envelope.alg !== 'RSA-OAEP/AES-256-GCM') {
|
|
66
|
+
throw new Error(`Unexpected envelope: v=${envelope.v} alg=${envelope.alg}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const sessionKey = privateDecrypt(
|
|
70
|
+
{ key: privateKeyPem, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
|
|
71
|
+
Buffer.from(envelope.key, 'base64'),
|
|
72
|
+
);
|
|
73
|
+
const decipher = createDecipheriv('aes-256-gcm', sessionKey, Buffer.from(envelope.iv, 'base64'));
|
|
74
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, 'base64'));
|
|
75
|
+
return Buffer.concat([
|
|
76
|
+
decipher.update(Buffer.from(envelope.data, 'base64')),
|
|
77
|
+
decipher.final(),
|
|
78
|
+
]).toString('utf8');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Ephemeral keypair for this session (used for bootstrap + credential reads)
|
|
82
|
+
const { publicKey: ephemeralPubPem, privateKey: ephemeralPrivPem } = generateKeyPairSync('rsa', {
|
|
83
|
+
modulusLength: 2048,
|
|
84
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
85
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
86
|
+
});
|
|
87
|
+
const ephemeralPubBase64 = Buffer.from(ephemeralPubPem, 'utf8').toString('base64');
|
|
88
|
+
|
|
89
|
+
/** Token TTL from bootstrap (seconds). Used for refresh scheduling. */
|
|
90
|
+
let tokenTtl = 3600;
|
|
91
|
+
let refreshTimer: ReturnType<typeof setTimeout> | null = null;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Bootstrap auth via Unix socket auto-approve.
|
|
95
|
+
* Returns true if successful.
|
|
96
|
+
*/
|
|
97
|
+
function bootstrapViaSocket(): Promise<boolean> {
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const uid = process.getuid?.() ?? 'unknown';
|
|
100
|
+
const socketPath = `/tmp/aura-cli-${uid}.sock`;
|
|
101
|
+
|
|
102
|
+
const socket = net.createConnection(socketPath);
|
|
103
|
+
let buffer = '';
|
|
104
|
+
let resolved = false;
|
|
105
|
+
|
|
106
|
+
const timeout = setTimeout(() => {
|
|
107
|
+
if (!resolved) {
|
|
108
|
+
resolved = true;
|
|
109
|
+
socket.destroy();
|
|
110
|
+
resolve(false);
|
|
111
|
+
}
|
|
112
|
+
}, 5000);
|
|
113
|
+
|
|
114
|
+
socket.on('connect', () => {
|
|
115
|
+
socket.write(JSON.stringify({
|
|
116
|
+
type: 'auth',
|
|
117
|
+
agentId: 'mcp-stdio',
|
|
118
|
+
autoApprove: true,
|
|
119
|
+
pubkey: ephemeralPubPem,
|
|
120
|
+
}) + '\n');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
socket.on('data', (data) => {
|
|
124
|
+
buffer += data.toString();
|
|
125
|
+
let newlineIndex;
|
|
126
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
127
|
+
const line = buffer.substring(0, newlineIndex);
|
|
128
|
+
buffer = buffer.substring(newlineIndex + 1);
|
|
129
|
+
|
|
130
|
+
if (!line.trim()) continue;
|
|
131
|
+
try {
|
|
132
|
+
const msg = JSON.parse(line.trim()) as {
|
|
133
|
+
type: string;
|
|
134
|
+
encryptedToken?: string;
|
|
135
|
+
ttl?: number;
|
|
136
|
+
message?: string;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if (msg.type === 'auth_approved' && msg.encryptedToken) {
|
|
140
|
+
token = decryptWithPrivateKey(msg.encryptedToken, ephemeralPrivPem);
|
|
141
|
+
tokenTtl = msg.ttl || 3600;
|
|
142
|
+
if (!resolved) {
|
|
143
|
+
resolved = true;
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
socket.destroy();
|
|
146
|
+
console.error('[mcp] Bootstrapped via Unix socket (auto-approve)');
|
|
147
|
+
resolve(true);
|
|
148
|
+
}
|
|
149
|
+
} else if (msg.type === 'error') {
|
|
150
|
+
if (!resolved) {
|
|
151
|
+
resolved = true;
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
socket.destroy();
|
|
154
|
+
console.error(`[mcp] Socket bootstrap error: ${msg.message}`);
|
|
155
|
+
resolve(false);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch { /* ignore parse errors */ }
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
socket.on('error', (err) => {
|
|
163
|
+
if (!resolved) {
|
|
164
|
+
resolved = true;
|
|
165
|
+
clearTimeout(timeout);
|
|
166
|
+
console.error(`[mcp] Socket bootstrap connection error: ${err.message} (code=${(err as NodeJS.ErrnoException).code})`);
|
|
167
|
+
resolve(false);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Schedule a token refresh before expiry.
|
|
175
|
+
*/
|
|
176
|
+
function scheduleRefresh(): void {
|
|
177
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
178
|
+
// Refresh 60s before expiry, minimum 10s
|
|
179
|
+
const refreshMs = Math.max((tokenTtl - 60) * 1000, 10_000);
|
|
180
|
+
refreshTimer = setTimeout(() => attemptRefresh(0), refreshMs);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Retry token refresh with exponential backoff (30s, 60s, 120s, 240s, cap 300s). */
|
|
184
|
+
async function attemptRefresh(attempt: number): Promise<void> {
|
|
185
|
+
console.error(`[mcp] Refreshing token (attempt ${attempt + 1})...`);
|
|
186
|
+
const ok = await bootstrapViaSocket();
|
|
187
|
+
if (ok) {
|
|
188
|
+
scheduleRefresh();
|
|
189
|
+
} else {
|
|
190
|
+
const backoffMs = Math.min(30_000 * Math.pow(2, attempt), 300_000); // cap at 5 min
|
|
191
|
+
console.error(`[mcp] Token refresh failed — retrying in ${backoffMs / 1000}s`);
|
|
192
|
+
refreshTimer = setTimeout(() => attemptRefresh(attempt + 1), backoffMs);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── MCP Server Setup ───────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
const server = new McpServer({
|
|
199
|
+
name: 'aurawallet',
|
|
200
|
+
version: '1.0.0',
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ── Resources ──────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
function loadApiDocs(): string {
|
|
206
|
+
try { return readFileSync(join(__dirname, '..', '..', 'docs', 'API.md'), 'utf-8'); }
|
|
207
|
+
catch { return 'API documentation not found.'; }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function loadAuthDocs(): string {
|
|
211
|
+
try { return readFileSync(join(__dirname, '..', '..', 'docs', 'AUTH.md'), 'utf-8'); }
|
|
212
|
+
catch { return 'Auth documentation not found.'; }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function loadAgentGuide(): string {
|
|
216
|
+
try { return readFileSync(join(__dirname, '..', '..', 'skills', 'aurawallet', 'SKILL.md'), 'utf-8'); }
|
|
217
|
+
catch { return 'Agent guide not found.'; }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function loadSetupSkillGuide(): string {
|
|
221
|
+
try { return readFileSync(join(__dirname, '..', '..', 'skills', 'aurawallet-setup', 'SKILL.md'), 'utf-8'); }
|
|
222
|
+
catch { return 'Setup skill guide not found.'; }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
server.resource(
|
|
226
|
+
'api-reference', 'docs://api',
|
|
227
|
+
{ description: 'Full AuraWallet HTTP API reference — all endpoints, parameters, and examples' },
|
|
228
|
+
async () => ({ contents: [{ uri: 'docs://api', text: loadApiDocs(), mimeType: 'text/markdown' }] }),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
server.resource(
|
|
232
|
+
'auth-reference', 'docs://auth',
|
|
233
|
+
{ description: 'Authentication, permissions, and spending limits reference' },
|
|
234
|
+
async () => ({ contents: [{ uri: 'docs://auth', text: loadAuthDocs(), mimeType: 'text/markdown' }] }),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
server.resource(
|
|
238
|
+
'agent-guide', 'docs://guide',
|
|
239
|
+
{ description: 'Agent skill reference — setup, operations, hook modes, permissions, error recovery' },
|
|
240
|
+
async () => ({ contents: [{ uri: 'docs://guide', text: loadAgentGuide(), mimeType: 'text/markdown' }] }),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
server.resource(
|
|
244
|
+
'setup-skill-guide', 'docs://setup-guide',
|
|
245
|
+
{ description: 'Setup-focused MCP skill guide — install, onboarding flow, and validation checklist' },
|
|
246
|
+
async () => ({ contents: [{ uri: 'docs://setup-guide', text: loadSetupSkillGuide(), mimeType: 'text/markdown' }] }),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// ── Credential helpers ─────────────────────────────────────────────────
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Decrypt a credential payload encrypted to our ephemeral RSA key.
|
|
253
|
+
*/
|
|
254
|
+
async function fetchVaultNameMap(): Promise<Map<string, string>> {
|
|
255
|
+
const res = await fetch(`${WALLET_BASE()}/setup/vaults`, {
|
|
256
|
+
signal: AbortSignal.timeout(5000),
|
|
257
|
+
});
|
|
258
|
+
if (!res.ok) return new Map();
|
|
259
|
+
const data = await res.json() as { vaults?: Array<{ id: string; name: string }> };
|
|
260
|
+
return new Map((data.vaults || []).map((v) => [v.id, v.name]));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function decryptCredentialPayload(encryptedBase64: string): {
|
|
264
|
+
id: string;
|
|
265
|
+
vaultId: string;
|
|
266
|
+
type: string;
|
|
267
|
+
fields: Array<{ key: string; value: string; type?: string; sensitive?: boolean }>;
|
|
268
|
+
} {
|
|
269
|
+
const plaintext = decryptWithPrivateKey(encryptedBase64, ephemeralPrivPem);
|
|
270
|
+
return JSON.parse(plaintext);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Create a scoped token with our ephemeral pubkey for credential reads.
|
|
275
|
+
*/
|
|
276
|
+
async function createCredentialReadToken(): Promise<{ token: string; error?: string }> {
|
|
277
|
+
if (!token) return { token: '', error: 'No auth token — start AuraWallet server for auto-bootstrap, or set AURA_TOKEN env var' };
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const res = await fetch(`${WALLET_BASE()}/actions/token`, {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: {
|
|
283
|
+
'Content-Type': 'application/json',
|
|
284
|
+
'Authorization': `Bearer ${token}`,
|
|
285
|
+
},
|
|
286
|
+
body: JSON.stringify(buildScopedReadTokenIssueRequest({
|
|
287
|
+
agentId: 'mcp-credential-reader',
|
|
288
|
+
pubkey: ephemeralPubBase64,
|
|
289
|
+
...(resolveMcpIssuanceProfile('read') || {}),
|
|
290
|
+
})),
|
|
291
|
+
signal: AbortSignal.timeout(5000),
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (res.ok) {
|
|
295
|
+
const data = await res.json() as { encryptedToken?: string };
|
|
296
|
+
if (!data.encryptedToken) {
|
|
297
|
+
return { token: '', error: 'No encryptedToken in response' };
|
|
298
|
+
}
|
|
299
|
+
return { token: decryptWithPrivateKey(data.encryptedToken, ephemeralPrivPem) };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const text = await res.text();
|
|
303
|
+
return { token: '', error: `Failed to create scoped token (${res.status}): ${text}` };
|
|
304
|
+
} catch (err) {
|
|
305
|
+
return { token: '', error: `Token creation failed: ${err}` };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Create a scoped token with our ephemeral pubkey for credential writes.
|
|
311
|
+
* Falls back to the base token when scoped minting is not available.
|
|
312
|
+
*/
|
|
313
|
+
async function createCredentialWriteToken(vaultId: string): Promise<{ token: string; error?: string }> {
|
|
314
|
+
if (!token) return { token: '', error: 'No auth token — start AuraWallet server for auto-bootstrap, or set AURA_TOKEN env var' };
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const res = await fetch(`${WALLET_BASE()}/actions/token`, {
|
|
318
|
+
method: 'POST',
|
|
319
|
+
headers: {
|
|
320
|
+
'Content-Type': 'application/json',
|
|
321
|
+
'Authorization': `Bearer ${token}`,
|
|
322
|
+
},
|
|
323
|
+
body: JSON.stringify(buildScopedWriteTokenIssueRequest({
|
|
324
|
+
agentId: 'mcp-credential-writer',
|
|
325
|
+
vaultId,
|
|
326
|
+
pubkey: ephemeralPubBase64,
|
|
327
|
+
...(resolveMcpIssuanceProfile('write') || {}),
|
|
328
|
+
})),
|
|
329
|
+
signal: AbortSignal.timeout(5000),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (res.ok) {
|
|
333
|
+
const data = await res.json() as { encryptedToken?: string };
|
|
334
|
+
if (!data.encryptedToken) {
|
|
335
|
+
return { token: '', error: 'No encryptedToken in response' };
|
|
336
|
+
}
|
|
337
|
+
return { token: decryptWithPrivateKey(data.encryptedToken, ephemeralPrivPem) };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const text = await res.text();
|
|
341
|
+
return { token: '', error: `Failed to create scoped write token (${res.status}): ${text}` };
|
|
342
|
+
} catch (err) {
|
|
343
|
+
return { token: '', error: `Write token creation failed: ${err}` };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ── get_secret rate limiter ─────────────────────────────────────────────
|
|
348
|
+
const GET_SECRET_WINDOW_MS = 60_000; // 1 minute
|
|
349
|
+
const GET_SECRET_MAX = 10; // max 10 requests per window
|
|
350
|
+
const getSecretRequests: number[] = [];
|
|
351
|
+
|
|
352
|
+
function isGetSecretRateLimited(): boolean {
|
|
353
|
+
const now = Date.now();
|
|
354
|
+
// Prune old entries
|
|
355
|
+
while (getSecretRequests.length > 0 && getSecretRequests[0] <= now - GET_SECRET_WINDOW_MS) {
|
|
356
|
+
getSecretRequests.shift();
|
|
357
|
+
}
|
|
358
|
+
if (getSecretRequests.length >= GET_SECRET_MAX) return true;
|
|
359
|
+
getSecretRequests.push(now);
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ── get_secret ─────────────────────────────────────────────────────────
|
|
364
|
+
server.tool(
|
|
365
|
+
'get_secret',
|
|
366
|
+
'Look up a stored credential/secret by name or tag and return its decrypted value. Searches credential vaults for a matching entry and returns all fields (including sensitive ones like passwords, API keys, etc.).',
|
|
367
|
+
{ name: z.string().describe('Name or tag to search for (e.g. "GitHub", "openai", "deploy")') },
|
|
368
|
+
async (input) => {
|
|
369
|
+
const { name } = input as { name: string };
|
|
370
|
+
|
|
371
|
+
if (isGetSecretRateLimited()) {
|
|
372
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Rate limited — too many get_secret requests. Try again in 1 minute.' }) }] };
|
|
373
|
+
}
|
|
374
|
+
if (!token) {
|
|
375
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'No auth token — start AuraWallet server for auto-bootstrap, or set AURA_TOKEN env var' }) }] };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const base = WALLET_BASE();
|
|
379
|
+
|
|
380
|
+
// Step 1: Search for credential by name/tag
|
|
381
|
+
let credentialId: string | null = null;
|
|
382
|
+
let credentialName = '';
|
|
383
|
+
try {
|
|
384
|
+
const vaultNames = await fetchVaultNameMap();
|
|
385
|
+
for (const queryParam of [`q=${encodeURIComponent(name)}`, `tag=${encodeURIComponent(name)}`]) {
|
|
386
|
+
const res = await fetch(`${base}/credentials?${queryParam}`, {
|
|
387
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
388
|
+
signal: AbortSignal.timeout(5000),
|
|
389
|
+
});
|
|
390
|
+
if (!res.ok) continue;
|
|
391
|
+
|
|
392
|
+
const data = await res.json() as { credentials: Array<{ id: string; name: string; vaultId: string }> };
|
|
393
|
+
if (!data.credentials || data.credentials.length === 0) continue;
|
|
394
|
+
|
|
395
|
+
const decision = evaluateProjectScopeAccess({
|
|
396
|
+
surface: 'mcp_get_secret',
|
|
397
|
+
requested: { vaultName: null, credentialName: name },
|
|
398
|
+
candidates: data.credentials.map((c) => ({
|
|
399
|
+
id: c.id,
|
|
400
|
+
name: c.name,
|
|
401
|
+
vaultName: vaultNames.get(c.vaultId) || null,
|
|
402
|
+
})),
|
|
403
|
+
actor: 'mcp-stdio',
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
emitProjectScopeEvent({
|
|
407
|
+
actor: 'mcp-stdio',
|
|
408
|
+
surface: 'mcp_get_secret',
|
|
409
|
+
requestedCredential: { vaultName: null, credentialName: name },
|
|
410
|
+
decision,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
if (!decision.allowed) {
|
|
414
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `${decision.code}: ${decision.remediation}` }) }] };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const allowedIds = new Set(decision.allowedCandidates.map((c) => c.id).filter(Boolean));
|
|
418
|
+
const scopedCandidates = data.credentials.filter((c) => allowedIds.has(c.id));
|
|
419
|
+
if (scopedCandidates.length === 0) continue;
|
|
420
|
+
|
|
421
|
+
credentialId = scopedCandidates[0].id;
|
|
422
|
+
credentialName = scopedCandidates[0].name;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
} catch (err) {
|
|
426
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Search failed: ${err}` }) }] };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!credentialId) {
|
|
430
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `No credential found matching "${name}"` }) }] };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Step 2: Get a scoped token with our ephemeral pubkey
|
|
434
|
+
const { token: readToken, error: tokenError } = await createCredentialReadToken();
|
|
435
|
+
if (!readToken) {
|
|
436
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: tokenError }) }] };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Step 3: Read credential (encrypted to our ephemeral key)
|
|
440
|
+
try {
|
|
441
|
+
const res = await fetch(`${base}/credentials/${credentialId}/read`, {
|
|
442
|
+
method: 'POST',
|
|
443
|
+
headers: { 'Authorization': `Bearer ${readToken}` },
|
|
444
|
+
signal: AbortSignal.timeout(5000),
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (!res.ok) {
|
|
448
|
+
const text = await res.text();
|
|
449
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Read failed (${res.status}): ${text}` }) }] };
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const data = await res.json() as { encrypted: string };
|
|
453
|
+
const decrypted = decryptCredentialPayload(data.encrypted);
|
|
454
|
+
|
|
455
|
+
// If credential has a TOTP field, generate current code
|
|
456
|
+
const totpField = decrypted.fields?.find((f: { key: string }) => f.key === 'totp' || f.key === 'otp');
|
|
457
|
+
let totpCode: string | undefined;
|
|
458
|
+
let totpRemaining: number | undefined;
|
|
459
|
+
if (totpField) {
|
|
460
|
+
try {
|
|
461
|
+
const totpRes = await fetch(`${base}/credentials/${credentialId}/totp`, {
|
|
462
|
+
method: 'POST',
|
|
463
|
+
headers: { 'Authorization': `Bearer ${token}` },
|
|
464
|
+
signal: AbortSignal.timeout(5000),
|
|
465
|
+
});
|
|
466
|
+
if (totpRes.ok) {
|
|
467
|
+
const totpData = await totpRes.json() as { code: string; remaining: number };
|
|
468
|
+
totpCode = totpData.code;
|
|
469
|
+
totpRemaining = totpData.remaining;
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
// TOTP generation failed — skip, still return credential
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
content: [{
|
|
478
|
+
type: 'text' as const,
|
|
479
|
+
text: JSON.stringify({
|
|
480
|
+
success: true,
|
|
481
|
+
name: credentialName,
|
|
482
|
+
credentialId: decrypted.id,
|
|
483
|
+
type: decrypted.type,
|
|
484
|
+
fields: decrypted.fields,
|
|
485
|
+
...(totpCode && { totpCode, totpRemaining }),
|
|
486
|
+
}),
|
|
487
|
+
}],
|
|
488
|
+
};
|
|
489
|
+
} catch (err) {
|
|
490
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Decryption failed: ${err}` }) }] };
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
// ── put_secret ─────────────────────────────────────────────────────────
|
|
496
|
+
server.tool(
|
|
497
|
+
'put_secret',
|
|
498
|
+
'Store a new credential/secret with the given name and value. Creates a "note" type credential in the default vault with a single sensitive field.',
|
|
499
|
+
{
|
|
500
|
+
name: z.string().describe('Name for the credential (e.g. "OpenAI API Key", "GitHub Token")'),
|
|
501
|
+
value: z.string().describe('The secret value to store'),
|
|
502
|
+
vault: z.string().optional().describe('Vault ID to store in (defaults to "agent", falls back to "primary")'),
|
|
503
|
+
tags: z.array(z.string()).optional().describe('Optional tags for organization'),
|
|
504
|
+
},
|
|
505
|
+
async (input) => {
|
|
506
|
+
const { name, value, vault, tags } = input as { name: string; value: string; vault?: string; tags?: string[] };
|
|
507
|
+
|
|
508
|
+
if (!token) {
|
|
509
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'No auth token — start AuraWallet server for auto-bootstrap, or set AURA_TOKEN env var' }) }] };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const base = WALLET_BASE();
|
|
513
|
+
let vaultId = vault || 'agent';
|
|
514
|
+
if (!vault) {
|
|
515
|
+
try {
|
|
516
|
+
const { listVaults } = await import('../lib/cold');
|
|
517
|
+
const vaults = listVaults();
|
|
518
|
+
const agentVault = vaults.find(v => v.name === 'agent');
|
|
519
|
+
vaultId = agentVault ? agentVault.id : 'primary';
|
|
520
|
+
} catch {
|
|
521
|
+
vaultId = 'primary';
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
const { token: scopedWriteToken } = await createCredentialWriteToken(vaultId);
|
|
527
|
+
const writeToken = scopedWriteToken || token;
|
|
528
|
+
|
|
529
|
+
const res = await fetch(`${base}/credentials`, {
|
|
530
|
+
method: 'POST',
|
|
531
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${writeToken}` },
|
|
532
|
+
body: JSON.stringify({
|
|
533
|
+
vaultId,
|
|
534
|
+
type: 'note',
|
|
535
|
+
name,
|
|
536
|
+
meta: tags ? { tags } : {},
|
|
537
|
+
fields: [{ key: 'value', value, type: 'secret', sensitive: true }],
|
|
538
|
+
}),
|
|
539
|
+
signal: AbortSignal.timeout(5000),
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
const text = await res.text();
|
|
543
|
+
if (!res.ok) {
|
|
544
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Store failed (${res.status}): ${text}` }) }] };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const data = JSON.parse(text) as { credential: { id: string; name: string } };
|
|
548
|
+
return {
|
|
549
|
+
content: [{
|
|
550
|
+
type: 'text' as const,
|
|
551
|
+
text: JSON.stringify({
|
|
552
|
+
success: true,
|
|
553
|
+
credentialId: data.credential.id,
|
|
554
|
+
name: data.credential.name,
|
|
555
|
+
message: `Secret "${name}" stored successfully`,
|
|
556
|
+
}),
|
|
557
|
+
}],
|
|
558
|
+
};
|
|
559
|
+
} catch (err) {
|
|
560
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: `Store failed: ${err}` }) }] };
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
// ── Register shared tools ──────────────────────────────────────────────
|
|
566
|
+
|
|
567
|
+
for (const tool of TOOLS) {
|
|
568
|
+
const shape = jsonSchemaToZod(tool.parameters.properties, tool.parameters.required || []);
|
|
569
|
+
|
|
570
|
+
server.tool(
|
|
571
|
+
tool.name,
|
|
572
|
+
tool.description,
|
|
573
|
+
shape,
|
|
574
|
+
async (input) => {
|
|
575
|
+
const result = await executeTool(tool.name, input as Record<string, unknown>, token);
|
|
576
|
+
|
|
577
|
+
// Auto-activate session token when vault is created via MCP
|
|
578
|
+
if (tool.name === 'create_vault') {
|
|
579
|
+
try {
|
|
580
|
+
const parsed = JSON.parse(result);
|
|
581
|
+
if (parsed.token) {
|
|
582
|
+
token = parsed.token;
|
|
583
|
+
}
|
|
584
|
+
} catch { /* skip */ }
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return { content: [{ type: 'text' as const, text: result }] };
|
|
588
|
+
},
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ── Main ───────────────────────────────────────────────────────────────
|
|
593
|
+
|
|
594
|
+
async function main() {
|
|
595
|
+
// Bootstrap auth: try socket first, then env var
|
|
596
|
+
if (!token) {
|
|
597
|
+
const ok = await bootstrapViaSocket();
|
|
598
|
+
if (ok) {
|
|
599
|
+
scheduleRefresh();
|
|
600
|
+
console.error('[mcp] Auth: socket bootstrap');
|
|
601
|
+
} else if (process.env.AURA_TOKEN) {
|
|
602
|
+
token = process.env.AURA_TOKEN;
|
|
603
|
+
console.error('[mcp] Auth: AURA_TOKEN env var');
|
|
604
|
+
} else {
|
|
605
|
+
console.error('[mcp] Auth: none (tools will return auth errors until server is running)');
|
|
606
|
+
}
|
|
607
|
+
} else {
|
|
608
|
+
// Have AURA_TOKEN from env — still try socket for encrypted upgrade
|
|
609
|
+
console.error('[mcp] Auth: AURA_TOKEN env var (pre-configured)');
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const transport = new StdioServerTransport();
|
|
613
|
+
await server.connect(transport);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
main().catch((err) => {
|
|
617
|
+
console.error('MCP server error:', err);
|
|
618
|
+
process.exit(1);
|
|
619
|
+
});
|