dfns-mcp 1.2.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Josh Maddington
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # DFNS MCP Server
2
+
3
+ A Model Context Protocol (MCP) server that provides AI agents with access to the [DFNS](https://dfns.co) documentation, API reference, and SDK code.
4
+
5
+ ## Features
6
+
7
+ - **Full-Text Search**: Instantly search across all DFNS guides, API docs, and SDK source code
8
+ - **TypeScript Type Intelligence**: Search and retrieve SDK type definitions with import paths
9
+ - **Auto-Updating Docs**: Documentation is fetched from GitHub on first run and cached locally
10
+ - **Context Initialization**: Dedicated `init` tool to enforce documentation-first behavior
11
+ - **Smart Context**: Retrieve specific documents, code examples, and API endpoint definitions
12
+ - **SDK Intelligence**: Mapped knowledge of supported blockchains and their SDK packages
13
+
14
+ ## Requirements
15
+
16
+ This MCP server requires [Bun](https://bun.sh) - a fast JavaScript runtime.
17
+
18
+ **Linux/macOS:**
19
+ ```bash
20
+ curl -fsSL https://bun.sh/install | bash
21
+ ```
22
+
23
+ **Windows (PowerShell):**
24
+ ```powershell
25
+ powershell -c "irm bun.sh/install.ps1 | iex"
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### Claude Code (Recommended)
31
+
32
+ ```bash
33
+ claude mcp add -s user dfns-docs -- bunx dfns-mcp@latest
34
+ ```
35
+
36
+ The `-s user` flag installs to the user so it's available in all your projects.
37
+
38
+ ### Cursor
39
+
40
+ Add to your Cursor MCP settings (`.cursor/mcp.json`):
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "dfns-docs": {
46
+ "command": "bunx",
47
+ "args": ["dfns-mcp@latest"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### Codex
54
+
55
+ ```bash
56
+ codex mcp add dfns-docs -- bunx dfns-mcp@latest
57
+ ```
58
+
59
+ ### Gemini CLI
60
+
61
+ Add to your Gemini CLI config (`~/.gemini/settings.json`):
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "dfns-docs": {
67
+ "command": "bunx",
68
+ "args": ["dfns-mcp@latest"]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Global Installation
75
+
76
+ If you prefer to install globally instead of using `bunx`:
77
+
78
+ ```bash
79
+ bun install -g dfns-mcp
80
+
81
+ # Then reference as just "dfns-mcp" in your MCP config
82
+ ```
83
+
84
+ ## Agent Instructions (Recommended)
85
+
86
+ Add this to your global `~/.claude/CLAUDE.md` (or `~/AGENTS.md` for other agents) to ensure your AI always uses up-to-date DFNS documentation:
87
+
88
+ ```markdown
89
+ ## DFNS Development
90
+
91
+ When working with DFNS (wallet infrastructure, key management, blockchain integrations):
92
+
93
+ 1. **Always** call `mcp__dfns-docs__init` at the start of any DFNS-related task
94
+ 2. **Never** rely on training data for DFNS APIs - always use `search_docs` and `get_doc` to verify
95
+ 3. Use `search_types` and `get_type` to get accurate TypeScript type definitions
96
+ 4. DFNS APIs and SDKs change frequently - the MCP server has the latest documentation
97
+ ```
98
+
99
+ This prevents agents from hallucinating outdated API signatures or SDK patterns.
100
+
101
+ ## How It Works
102
+
103
+ On first run, the server automatically downloads the latest DFNS documentation from GitHub and caches it locally in `~/.cache/dfns-mcp/`. The cache is refreshed every 24 hours automatically, or you can force an update using the `update_docs` tool.
104
+
105
+ ## Available Tools
106
+
107
+ ### Documentation Tools
108
+
109
+ | Tool | Description |
110
+ |------|-------------|
111
+ | **`init`** | **CALL THIS FIRST.** Initializes the session and mandates strict documentation adherence |
112
+ | `search_docs` | Search all documentation and SDK files |
113
+ | `get_doc` | Retrieve the full content of a specific document |
114
+ | `list_docs` | List available documents by category |
115
+ | `get_code_examples` | Extract code snippets for a specific topic |
116
+ | `browse_api_structure` | View hierarchical API endpoint structure |
117
+ | `get_api_endpoint` | Get details for a specific API endpoint (e.g., `POST /wallets`) |
118
+ | `get_blockchain_info` | Get SDK package info for a specific chain (e.g., `Solana`) |
119
+ | `list_blockchains` | List all supported blockchains with their SDK packages |
120
+
121
+ ### TypeScript Type Tools
122
+
123
+ | Tool | Description |
124
+ |------|-------------|
125
+ | `search_types` | Search SDK types by name (e.g., "Wallet", "Signer", "Transaction") |
126
+ | `get_type` | Get full type definition, import path, and usage |
127
+ | `list_types` | List all types by category |
128
+
129
+ ### Cache Management
130
+
131
+ | Tool | Description |
132
+ |------|-------------|
133
+ | `update_docs` | Force update the documentation cache from GitHub |
134
+ | `cache_info` | Get information about the cache (location, last update, etc.) |
135
+
136
+ ## Resources
137
+
138
+ The server also exposes these quick-reference resources:
139
+
140
+ | Resource URI | Description |
141
+ |--------------|-------------|
142
+ | `dfns://quickref/authentication` | Authentication patterns and code samples |
143
+ | `dfns://quickref/sdk-setup` | Package installation and setup guide |
144
+ | `dfns://quickref/networks` | All supported blockchain networks |
145
+
146
+ ## Best Practices for Agents
147
+
148
+ When using this server, agents should follow this workflow:
149
+
150
+ 1. **Initialize**: Call `init` immediately to establish the "Documentation First" protocol
151
+ 2. **Search**: Use `search_docs` to find relevant information before answering ANY question
152
+ 3. **Read**: Use `get_doc` to read the actual source material
153
+ 4. **Answer**: Formulate responses based *only* on the retrieved context
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ # Clone the repo
159
+ git clone https://github.com/jmaddington/dfns-mcp.git
160
+ cd dfns-mcp
161
+
162
+ # Install dependencies
163
+ bun install
164
+
165
+ # Run in development mode with auto-reload
166
+ bun run dev
167
+
168
+ # Test with MCP Inspector
169
+ bun run inspect
170
+ ```
171
+
172
+ ## License
173
+
174
+ MIT
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "dfns-mcp",
3
+ "version": "1.2.0",
4
+ "description": "MCP server for DFNS documentation and SDK reference - provides AI agents with access to DFNS API docs, TypeScript types, and code examples",
5
+ "module": "src/index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "dfns-mcp": "./src/index.ts"
9
+ },
10
+ "files": [
11
+ "src/**/*.ts",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "start": "bun run src/index.ts",
17
+ "dev": "bun --watch run src/index.ts",
18
+ "inspect": "bunx @modelcontextprotocol/inspector bun run src/index.ts"
19
+ },
20
+ "keywords": [
21
+ "mcp",
22
+ "dfns",
23
+ "wallet",
24
+ "crypto",
25
+ "blockchain",
26
+ "ai",
27
+ "claude",
28
+ "documentation"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/jhubbardsf/dfns-mcp.git"
33
+ },
34
+ "author": "Josh Hubbard",
35
+ "license": "MIT",
36
+ "engines": {
37
+ "bun": ">=1.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/bun": "latest"
41
+ },
42
+ "peerDependencies": {
43
+ "typescript": "^5"
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.23.0",
47
+ "tar": "^7.4.3",
48
+ "zod": "^4.1.13"
49
+ }
50
+ }
@@ -0,0 +1,264 @@
1
+ import { mkdir, stat, rm, readFile, writeFile, rename } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { createWriteStream } from "node:fs";
5
+ import { pipeline } from "node:stream/promises";
6
+ import { Readable } from "node:stream";
7
+ import { createGunzip } from "node:zlib";
8
+ import { extract } from "tar";
9
+
10
+ // ============================================================================
11
+ // Configuration
12
+ // ============================================================================
13
+
14
+ const CACHE_DIR = join(homedir(), ".cache", "dfns-mcp");
15
+ const METADATA_FILE = join(CACHE_DIR, "metadata.json");
16
+
17
+ const REPOS = {
18
+ "dfns-api-docs": {
19
+ owner: "dfns",
20
+ repo: "dfns-api-docs",
21
+ branch: "m", // DFNS uses 'm' as their default branch
22
+ },
23
+ "dfns-sdk-ts": {
24
+ owner: "dfns",
25
+ repo: "dfns-sdk-ts",
26
+ branch: "m", // DFNS uses 'm' as their default branch
27
+ },
28
+ };
29
+
30
+ // How often to check for updates (24 hours)
31
+ const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
32
+
33
+ interface CacheMetadata {
34
+ lastUpdated: number;
35
+ repos: {
36
+ [key: string]: {
37
+ sha: string;
38
+ fetchedAt: number;
39
+ };
40
+ };
41
+ }
42
+
43
+ // ============================================================================
44
+ // Helper Functions
45
+ // ============================================================================
46
+
47
+ async function exists(path: string): Promise<boolean> {
48
+ try {
49
+ await stat(path);
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ async function readMetadata(): Promise<CacheMetadata | null> {
57
+ try {
58
+ const content = await readFile(METADATA_FILE, "utf-8");
59
+ return JSON.parse(content);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ async function writeMetadata(metadata: CacheMetadata): Promise<void> {
66
+ await writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2));
67
+ }
68
+
69
+ /**
70
+ * Fetch the latest commit SHA for a branch
71
+ */
72
+ async function getLatestSha(owner: string, repo: string, branch: string): Promise<string | null> {
73
+ try {
74
+ const response = await fetch(
75
+ `https://api.github.com/repos/${owner}/${repo}/commits/${branch}`,
76
+ {
77
+ headers: {
78
+ "Accept": "application/vnd.github.v3+json",
79
+ "User-Agent": "dfns-mcp",
80
+ },
81
+ }
82
+ );
83
+
84
+ if (!response.ok) {
85
+ console.error(`Failed to fetch SHA: ${response.status}`);
86
+ return null;
87
+ }
88
+
89
+ const data = await response.json() as { sha: string };
90
+ return data.sha;
91
+ } catch (err) {
92
+ console.error(`Error fetching SHA:`, err);
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Download and extract a GitHub repository tarball
99
+ */
100
+ async function downloadAndExtractRepo(
101
+ owner: string,
102
+ repo: string,
103
+ branch: string,
104
+ targetDir: string
105
+ ): Promise<void> {
106
+ const url = `https://github.com/${owner}/${repo}/archive/refs/heads/${branch}.tar.gz`;
107
+ const tempDir = join(CACHE_DIR, `${repo}-temp-${Date.now()}`);
108
+
109
+ console.error(`Downloading ${owner}/${repo}...`);
110
+
111
+ const response = await fetch(url);
112
+ if (!response.ok) {
113
+ throw new Error(`Failed to download ${url}: ${response.status}`);
114
+ }
115
+
116
+ // Create temp directory
117
+ await mkdir(tempDir, { recursive: true });
118
+
119
+ // Extract the tarball
120
+ const body = response.body;
121
+ if (!body) {
122
+ throw new Error("No response body");
123
+ }
124
+
125
+ // Convert web stream to Node stream and extract
126
+ const nodeStream = Readable.fromWeb(body as any);
127
+
128
+ await pipeline(
129
+ nodeStream,
130
+ createGunzip(),
131
+ extract({
132
+ cwd: tempDir,
133
+ strip: 1, // Remove the top-level directory (e.g., dfns-api-docs-main/)
134
+ })
135
+ );
136
+
137
+ // Remove old target if exists
138
+ if (await exists(targetDir)) {
139
+ await rm(targetDir, { recursive: true });
140
+ }
141
+
142
+ // Rename temp to target
143
+ await rename(tempDir, targetDir);
144
+
145
+ console.error(`Extracted to ${targetDir}`);
146
+ }
147
+
148
+ // ============================================================================
149
+ // Public API
150
+ // ============================================================================
151
+
152
+ export interface DocsPaths {
153
+ docsDir: string;
154
+ sdkDir: string;
155
+ fromCache: boolean;
156
+ }
157
+
158
+ /**
159
+ * Ensure docs are available, downloading if necessary.
160
+ * Returns paths to the docs and SDK directories.
161
+ */
162
+ export async function ensureDocs(forceUpdate: boolean = false): Promise<DocsPaths> {
163
+ // Create cache directory
164
+ await mkdir(CACHE_DIR, { recursive: true });
165
+
166
+ const docsDir = join(CACHE_DIR, "dfns-api-docs");
167
+ const sdkDir = join(CACHE_DIR, "dfns-sdk-ts");
168
+
169
+ const metadata = await readMetadata();
170
+ const now = Date.now();
171
+
172
+ // Check if we need to update
173
+ const docsExist = await exists(docsDir) && await exists(sdkDir);
174
+ const needsUpdate = forceUpdate ||
175
+ !docsExist ||
176
+ !metadata ||
177
+ (now - metadata.lastUpdated > UPDATE_CHECK_INTERVAL_MS);
178
+
179
+ if (!needsUpdate && docsExist) {
180
+ console.error("Using cached documentation");
181
+ return { docsDir, sdkDir, fromCache: true };
182
+ }
183
+
184
+ // Download/update repos
185
+ const newMetadata: CacheMetadata = {
186
+ lastUpdated: now,
187
+ repos: {},
188
+ };
189
+
190
+ for (const [name, config] of Object.entries(REPOS)) {
191
+ const targetDir = name === "dfns-api-docs" ? docsDir : sdkDir;
192
+ const currentSha = metadata?.repos[name]?.sha;
193
+
194
+ // Check if there's a new version
195
+ const latestSha = await getLatestSha(config.owner, config.repo, config.branch);
196
+
197
+ if (!forceUpdate && latestSha && latestSha === currentSha && await exists(targetDir)) {
198
+ console.error(`${name} is up to date (${latestSha.slice(0, 7)})`);
199
+ newMetadata.repos[name] = metadata!.repos[name];
200
+ continue;
201
+ }
202
+
203
+ // Download the repo
204
+ await downloadAndExtractRepo(
205
+ config.owner,
206
+ config.repo,
207
+ config.branch,
208
+ targetDir
209
+ );
210
+
211
+ newMetadata.repos[name] = {
212
+ sha: latestSha || "unknown",
213
+ fetchedAt: now,
214
+ };
215
+ }
216
+
217
+ // Save metadata
218
+ await writeMetadata(newMetadata);
219
+
220
+ return { docsDir, sdkDir, fromCache: false };
221
+ }
222
+
223
+ /**
224
+ * Force update all docs
225
+ */
226
+ export async function updateDocs(): Promise<{ success: boolean; message: string }> {
227
+ try {
228
+ const result = await ensureDocs(true);
229
+ return {
230
+ success: true,
231
+ message: `Documentation updated successfully. Docs at: ${result.docsDir}`,
232
+ };
233
+ } catch (err) {
234
+ return {
235
+ success: false,
236
+ message: `Failed to update documentation: ${err}`,
237
+ };
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Get the cache directory path
243
+ */
244
+ export function getCacheDir(): string {
245
+ return CACHE_DIR;
246
+ }
247
+
248
+ /**
249
+ * Get cached metadata
250
+ */
251
+ export async function getCacheInfo(): Promise<{
252
+ cacheDir: string;
253
+ metadata: CacheMetadata | null;
254
+ docsExist: boolean;
255
+ sdkExist: boolean;
256
+ }> {
257
+ const metadata = await readMetadata();
258
+ return {
259
+ cacheDir: CACHE_DIR,
260
+ metadata,
261
+ docsExist: await exists(join(CACHE_DIR, "dfns-api-docs")),
262
+ sdkExist: await exists(join(CACHE_DIR, "dfns-sdk-ts")),
263
+ };
264
+ }