Sign In

@deerdawn/mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@deerdawn/mcp-server - npm Package Compare versions

Comparing version
1.0.36
to
1.0.37
+567
dist/chunk-LRWQ2CHH.js
#!/usr/bin/env node
import { createRequire } from "module";
const require = createRequire(import.meta.url);
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require2() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// node_modules/tsup/assets/esm_shims.js
import path from "path";
import { fileURLToPath } from "url";
var init_esm_shims = __esm({
"node_modules/tsup/assets/esm_shims.js"() {
"use strict";
}
});
// src/client.ts
init_esm_shims();
import axios from "axios";
import http from "http";
import https from "https";
import fs from "fs";
import path2 from "path";
import os from "os";
function isLocalApiUrl(apiUrl) {
try {
const host = new URL(apiUrl).hostname;
return host === "localhost" || host === "127.0.0.1" || host === "::1";
} catch {
return false;
}
}
function getDeerdawnHome() {
return process.env.DEERDAWN_HOME || path2.join(os.homedir(), ".deerdawn");
}
function getQueuePath() {
return path2.join(getDeerdawnHome(), "cloud-write-queue.json");
}
function ensureDeerdawnHome() {
fs.mkdirSync(getDeerdawnHome(), { recursive: true });
}
function readQueueState() {
try {
return JSON.parse(fs.readFileSync(getQueuePath(), "utf8"));
} catch {
return { entries: [] };
}
}
function writeQueueState(state) {
ensureDeerdawnHome();
fs.writeFileSync(getQueuePath(), JSON.stringify(state, null, 2));
}
function mapOrgUsageToSettings(data) {
const plan = data?.plan || {};
return {
org_id: plan.org_id || "",
name: data?.org_name || "",
plan: typeof plan.tier === "string" ? plan.tier : "free",
fail_mode: plan.fail_mode === "open" ? "open" : "closed",
redaction_mode: plan.redaction_mode === "permissive" ? "permissive" : "strict",
is_locked: !!plan.is_locked,
shadow_mode: !!plan.shadow_mode,
redaction_keys: Array.isArray(plan.redaction_keys) ? plan.redaction_keys : [],
created_at: (/* @__PURE__ */ new Date()).toISOString(),
usage: data?.usage,
limits: data?.limits
};
}
var DeerdawnClient = class {
client;
cache;
config;
MAX_CACHE_SIZE = 100;
MAX_CACHE_ITEM_SIZE_MB = 5;
constructor(config) {
if (!config.apiKey || config.apiKey.trim() !== config.apiKey) {
throw new Error("Invalid API key: cannot be empty or contain whitespace");
}
if (!isLocalApiUrl(config.apiUrl) && !config.apiKey.startsWith("dd_")) {
throw new Error("Invalid API key format: must start with dd_ prefix");
}
this.config = config;
this.cache = /* @__PURE__ */ new Map();
this.client = axios.create({
baseURL: config.apiUrl,
headers: {
"x-api-key": config.apiKey,
"content-type": "application/json"
},
timeout: 3e4,
// Reuse TCP+TLS connections across calls. Without this, axios opens a
// fresh socket per request and pays a full handshake (~50ms) every time —
// multiplied across the several sequential calls a single tool can make.
httpAgent: new http.Agent({ keepAlive: true, maxSockets: 16 }),
httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 16 })
});
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const cfg = error.config;
const response = error.response;
if (!cfg) {
throw error;
}
if (!cfg.__retryCount) {
cfg.__retryCount = 0;
cfg.__firstRetryTimestamp = Date.now();
}
const totalRetryTime = Date.now() - (cfg.__firstRetryTimestamp || Date.now());
if (totalRetryTime > 6e4) {
throw new Error("Retry timeout exceeded (60s)");
}
const shouldRetry = (response?.status === 429 || response?.status && response.status >= 500) && cfg.__retryCount < this.config.maxRetries;
if (shouldRetry) {
cfg.__retryCount++;
const delay = Math.min(
this.config.retryBackoffMs * Math.pow(2, cfg.__retryCount - 1),
3e4
);
await new Promise((resolve) => setTimeout(resolve, delay));
return this.client.request(cfg);
}
throw error;
}
);
}
async recordProductEvent(event) {
const { data } = await this.client.post("/api/v1/product-events", event);
return data;
}
async recordContextEvent(event) {
try {
const { data } = await this.client.post("/api/v1/context/events", event);
return data;
} catch (error) {
const status = error?.response?.status;
const shouldQueue = !status || status >= 500 || error?.code === "ECONNREFUSED" || error?.code === "ENOTFOUND";
if (!shouldQueue) throw error;
const state = readQueueState();
state.entries.push({
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
method: "POST",
path: "/api/v1/context/events",
body: event,
created_at: (/* @__PURE__ */ new Date()).toISOString()
});
writeQueueState(state);
return { queued: true, queue_size: state.entries.length, event_type: event.event_type };
}
}
async flushQueuedContextWrites() {
const state = readQueueState();
if (!state.entries.length) return { flushed: 0, remaining: 0 };
const remaining = [];
let flushed = 0;
for (const entry of state.entries) {
try {
await this.client.post(entry.path, entry.body);
flushed += 1;
} catch {
remaining.push(entry);
}
}
writeQueueState({ entries: remaining });
return { flushed, remaining: remaining.length };
}
async getOrganizationSettings() {
const { data } = await this.client.get("/api/v1/orgs/usage");
return mapOrgUsageToSettings(data);
}
async getContextGraphProject(projectId) {
const { data } = await this.client.get(`/api/v1/context/graph/projects/${encodeURIComponent(projectId)}`);
return data.graph || data;
}
async getContextGraphSession(sessionId) {
const { data } = await this.client.get(`/api/v1/context/graph/sessions/${encodeURIComponent(sessionId)}`);
return data.graph || data;
}
async getContextGraphNode(nodeId) {
const { data } = await this.client.get(`/api/v1/context/graph/nodes/${encodeURIComponent(nodeId)}`);
return data.graph || data;
}
async queryContextGraph(input) {
const { data } = await this.client.post("/api/v1/context/graph/query", input);
return data.graph || data;
}
async updateOrganizationSettings(settings) {
const { data } = await this.client.patch("/api/v1/orgs/settings", settings);
return data;
}
async getAuditLogs(_params = {}) {
const { data } = await this.client.get("/api/v1/audit");
const logs = data.logs || [];
return { logs, total: logs.length };
}
async getActionRegistry() {
const cacheKey = "registry:actions";
const cached = this.getFromCache(cacheKey);
if (cached) return cached;
try {
const { data } = await this.client.get("/api/v1/registry/actions");
const actions = data.actions || data;
if (Array.isArray(actions) && actions.length > 0) {
this.setInCache(cacheKey, actions, 3e5);
return actions;
}
} catch {
}
return this.getDefaultActionRegistry();
}
getDefaultActionRegistry() {
return [
{
action_type: "auth.login",
category: "authentication",
description: "User login attempt",
risk_level: "medium"
},
{
action_type: "auth.password_reset",
category: "authentication",
description: "Password reset request",
risk_level: "medium"
},
{
action_type: "payment.process",
category: "financial",
description: "Payment processing",
risk_level: "high"
},
{
action_type: "user.delete",
category: "data_management",
description: "User account deletion",
risk_level: "high"
},
{
action_type: "data.export",
category: "data_management",
description: "Data export request",
risk_level: "medium"
}
];
}
getFromCache(key) {
if (!this.config.cacheEnabled) return null;
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expires) {
this.cache.delete(key);
return null;
}
return entry.data;
}
setInCache(key, data, ttlMs) {
if (!this.config.cacheEnabled) return;
const estimatedSize = JSON.stringify(data).length;
const estimatedSizeMB = estimatedSize / (1024 * 1024);
if (estimatedSizeMB > this.MAX_CACHE_ITEM_SIZE_MB) {
console.error(`[CACHE] Skipping large object (${estimatedSizeMB.toFixed(2)}MB)`);
return;
}
if (this.cache.size >= this.MAX_CACHE_SIZE) {
const firstKey = this.cache.keys().next().value;
if (firstKey) {
this.cache.delete(firstKey);
}
}
this.cache.set(key, {
data,
expires: Date.now() + ttlMs
});
}
invalidateCache(prefix) {
for (const key of this.cache.keys()) {
if (key.startsWith(prefix)) {
this.cache.delete(key);
}
}
}
// Context sync methods
async getActiveContext(projectId, tool = "claude_code", verbosity = "standard") {
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { tool, verbosity } }
);
return {
brief: data2.payload?.body ?? null,
project_name: projectId,
project_id: projectId,
last_updated: null
};
}
const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity } });
return data;
}
// Brief shape (?shape=spine) — the product-voice 4-slot view (SHIPPED / DECIDED
// / OPEN / LANDMINE). Returns the 4-slot TEXT body plus the structured
// brief_spine (per-item trust). Opt-in: never changes the default brief text.
async getActiveBrief(projectId, tool = "claude_code", verbosity = "standard") {
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { tool, verbosity, shape: "spine" } }
);
return {
brief: data2.payload?.body ?? null,
brief_spine: data2.payload?.brief_spine ?? null,
project_name: projectId,
project_id: projectId,
last_updated: null
};
}
const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, shape: "spine" } });
return data;
}
async getActiveContextByCwd(cwd, tool = "claude_code", verbosity = "standard", markSession = false, workspaceKey = null) {
const params = { cwd, tool, verbosity };
if (markSession) params.mark_session = 1;
if (workspaceKey) params.workspace_key = workspaceKey;
const { data } = await this.client.get("/api/v1/context/active", { params });
return data;
}
async pushContextUpdate(projectId, summary, tool = "claude_code", commands) {
const sessionId = `mcp-${Date.now()}`;
const [extractionResult] = await Promise.all([
this.client.post("/api/v1/context/extraction", {
session_id: sessionId,
tool,
project_id: projectId,
chunks: [{ role: "assistant", text: summary }]
}).then((r) => r.data),
commands?.length ? this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, { commands }).catch((err) => {
process.stderr.write(`[DeerDawn] commands not saved: ${err?.message ?? "unknown"}
`);
return null;
}) : Promise.resolve(null)
]);
return extractionResult;
}
async listContextProjects() {
const { data } = await this.client.get("/api/v1/context/projects");
return data;
}
async listContextProjectTree() {
const { data } = await this.client.get("/api/v1/context/projects/tree");
return data;
}
async createContextProject(projectName, description, parentProjectId) {
const { data } = await this.client.post("/api/v1/context/projects", {
project_name: projectName,
description,
...parentProjectId ? { parent_project_id: parentProjectId } : {}
});
return data;
}
async switchSubproject(projectId, workspaceKey) {
const { data } = await this.client.post("/api/v1/context/subproject/switch", {
project_id: projectId,
workspace_key: workspaceKey
});
return data;
}
async archiveProject(projectId, unarchive = false) {
const { data } = await this.client.post(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/archive`,
{ unarchive }
);
return data;
}
async setProjectParent(projectId, parentProjectId) {
const { data } = await this.client.post(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/parent`,
{ parent_project_id: parentProjectId ?? null }
);
return data;
}
async renameProject(projectId, projectName) {
const { data } = await this.client.post(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/rename`,
{ project_name: projectName }
);
return data;
}
async getContextSections(projectId, sections, tool = "claude_code", verbosity = "standard") {
const sectionsParam = sections.join(",");
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { tool, verbosity, sections: sectionsParam } }
);
return {
brief: data2.payload?.body ?? null,
project_name: projectId,
project_id: projectId
};
}
const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, sections: sectionsParam } });
return data;
}
async getContextMap(mcpAvailable = true, cwd, workspaceKey) {
const params = { mcp: mcpAvailable ? "true" : "false" };
if (cwd) params.cwd = cwd;
if (workspaceKey) params.workspace_key = workspaceKey;
const { data } = await this.client.get("/api/v1/context/map", { params });
return data;
}
async getContextJson(projectId) {
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { format: "json" } }
);
return data2.context ?? data2;
}
const { data } = await this.client.get("/api/v1/context/active", { params: { format: "json" } });
return data;
}
async searchContext(query, limit = 5) {
const { data } = await this.client.post("/api/v1/context/search", { query, limit });
return data;
}
async getWorkspaceOverview(params = {}) {
const query = {};
if (params.mode) query.mode = params.mode;
if (params.verbosity) query.verbosity = params.verbosity;
if (params.include?.length) query.include = params.include.join(",");
if (params.format) query.format = params.format;
const { data } = await this.client.get("/api/v1/context/workspace/overview", { params: query });
return data.overview || data;
}
async getWorkspaceEntities(params = {}) {
const query = {};
if (params.types?.length) query.types = params.types.join(",");
if (params.query) query.query = params.query;
if (params.project_id) query.project_id = params.project_id;
if (params.related_project_id) query.related_project_id = params.related_project_id;
if (params.scope) query.scope = params.scope;
if (params.surface) query.surface = params.surface;
if (params.status) query.status = params.status;
if (typeof params.include_inferred === "boolean") query.include_inferred = params.include_inferred ? 1 : 0;
if (typeof params.include_resolved === "boolean") query.include_resolved = params.include_resolved ? 1 : 0;
if (typeof params.curated_only === "boolean") query.curated_only = params.curated_only ? 1 : 0;
if (typeof params.limit === "number") query.limit = params.limit;
if (params.format) query.format = params.format;
const { data } = await this.client.get("/api/v1/context/workspace/entities", { params: query });
return data;
}
async getProjectMap(params = {}) {
const projectId = params.project_id;
if (!projectId) {
const { data: data2 } = await this.client.get("/api/v1/context/active");
if (!data2?.project_id) return null;
params.project_id = data2.project_id;
}
const query = {};
if (params.verbosity) query.verbosity = params.verbosity;
if (params.format) query.format = params.format;
const resolvedProjectId = String(params.project_id);
const { data } = await this.client.get(`/api/v1/context/projects/${encodeURIComponent(resolvedProjectId)}/map`, { params: query });
return data.project_map || data;
}
async findKnownPaths(params) {
const query = { query: params.query };
if (params.project_id) query.project_id = params.project_id;
if (typeof params.limit === "number") query.limit = params.limit;
if (params.format) query.format = params.format;
const { data } = await this.client.get("/api/v1/context/workspace/known-paths", { params: query });
return data;
}
async getNextBestContext(params = {}) {
const query = {};
if (params.tool) query.tool = params.tool;
if (params.cwd) query.cwd = params.cwd;
if (params.project_id) query.project_id = params.project_id;
if (typeof params.limit === "number") query.limit = params.limit;
const { data } = await this.client.get("/api/v1/context/workspace/next", { params: query });
return data.result || data;
}
async upsertWorkspaceEntities(input) {
const { data } = await this.client.post("/api/v1/context/workspace/entities", input);
return data;
}
async recordHotPaths(projectId, items) {
const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/hot-paths`, { items });
return data;
}
async recordDebugFinding(projectId, input) {
const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/debug-findings`, input);
return data;
}
async confirmClaim(projectId, input) {
const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/verify`, input);
return data;
}
async updateGlobalContext(patch) {
const { data } = await this.client.patch("/api/v1/context/global", patch);
return data;
}
async patchProjectContext(projectId, delta) {
const { data } = await this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, delta);
return data;
}
// ── Shared skills ──────────────────────────────────────────────────────────
async listSkills(projectId) {
const params = {};
if (projectId) params.project_id = projectId;
const { data } = await this.client.get("/api/v1/context/skills", { params });
return data?.skills ?? [];
}
async getSkill(idOrSlug) {
const { data } = await this.client.get(`/api/v1/context/skills/${encodeURIComponent(idOrSlug)}`);
return data?.skill ?? null;
}
async upsertSkill(input) {
const { data } = await this.client.post("/api/v1/context/skills", input);
return data?.skill ?? data;
}
// Import a shared skill by pasting a GitHub repo URL. The API fetches the
// repo's SKILL.md, parses its frontmatter, and creates the shared skill.
async importSkillFromGithub(repoUrl, projectId) {
const body = { repo_url: repoUrl };
if (projectId) body.project_id = projectId;
const { data } = await this.client.post("/api/v1/context/skills/import-github", body);
return data;
}
};
export {
__require,
__commonJS,
__toESM,
init_esm_shims,
DeerdawnClient
};
#!/usr/bin/env node
import { createRequire } from "module";
const require = createRequire(import.meta.url);
import {
DeerdawnClient
} from "./chunk-LRWQ2CHH.js";
export {
DeerdawnClient
};
+10
-1
{
"name": "@deerdawn/mcp-server",
"mcpName": "com.deerdawn/deerdawn",
"version": "1.0.36",
"version": "1.0.37",
"description": "MCP server for DeerDawn — AI session memory that briefs every new AI session",
"homepage": "https://deerdawn.com",
"repository": {
"type": "git",
"url": "git+https://github.com/HissingSpider/DeerDawn.git",
"directory": "packages/mcp-server"
},
"bugs": {
"url": "https://deerdawn.com/contact"
},
"type": "module",

@@ -7,0 +16,0 @@ "bin": {

+77
-61

@@ -5,26 +5,51 @@ # DeerDawn MCP Server

DeerDawn is AI session memory — your AI's chief of staff — for Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Zed, Codex, and any other MCP-compatible agent. Start a session anywhere and `start_session` hands the agent the brief: what you were working on, what was decided, and what's blocking — so no session starts cold.
DeerDawn is AI session memory — your AI's chief of staff — for Claude Code, Claude.ai, Claude Desktop, ChatGPT, Cursor, Windsurf, VS Code, Zed, Codex, and any other MCP-compatible agent. Start a session anywhere and `start_session` hands the agent the brief: what you were working on, what was decided, and what's blocking — so no session starts cold.
## Quick start
## Quick start — hosted server (recommended, no install)
**Step 1 — Get your API key**
Connect to the hosted MCP endpoint. **No API key needed** — you sign in with your browser the first time a DeerDawn tool is called.
Sign in at [app.deerdawn.com](https://app.deerdawn.com) → Settings → API Keys → Create Key.
Your key will start with `dd_`.
**Claude Code**
**Step 2 — Add to your tool**
```bash
claude mcp add -s user --transport http deerdawn https://api.deerdawn.com/api/v1/mcp
```
Pick your editor below and paste in the config. Restart the tool.
**Every other tool** — add a remote MCP server (sometimes called a connector) with this URL:
**Step 3 — Call `start_session`**
```
https://api.deerdawn.com/api/v1/mcp
```
At the beginning of any session, call `start_session`. You'll get the active project context and a map of everything else in one shot.
| Tool | Where |
|------|-------|
| Claude.ai | Settings → Connectors → Add custom connector |
| ChatGPT.com | Settings → Connectors → Add MCP server (choose OAuth) |
| Cursor | Settings → MCP → Add server, or add a `"url"` entry in `.cursor/mcp.json` |
| VS Code / Copilot | Command palette → MCP: Add Server → HTTP → paste the URL |
| Windsurf, Gemini, others | Add a custom remote / HTTP MCP server with the URL |
Leave any OAuth client fields blank. Click Connect (or restart the tool) and sign in when the DeerDawn tab opens. If your tool connects without prompting for sign-in, the first DeerDawn tool call returns the sign-in URL — open it, sign in once, and retry the call; setup completes automatically.
Then say: **"Start a DeerDawn session."**
The hosted server exposes 18 core session-memory tools. For repo-file writes, automatic capture hooks, and the full 35-tool set, use the local install below.
---
## Configuration
## Local install (stdio) — optional upgrade
Run the server on your machine via `npx -y @deerdawn/mcp-server@latest` when you want deeper integration:
- Writes `.deerdawn-context.md` / `.cursorrules` into your repo so context loads with no tool call.
- Installs SessionStart / Stop hooks that capture and flush context automatically.
- Exposes the full set of 35 tools (the hosted server exposes 18 core ones).
**Sign-in works the same way** — browser OAuth by default, no API key required. On first run without credentials, the server starts a device-flow sign-in: the URL is printed to stderr, saved to `~/.deerdawn/pending-auth.json`, and returned in-band by the `get_auth_status` tool (use that in GUI hosts where stderr isn't visible). After you approve in the browser, credentials are saved to `~/.deerdawn/credentials.json` and every future session is authenticated.
**Prefer non-interactive auth** (CI, headless, SSH)? Create a key at [app.deerdawn.com](https://app.deerdawn.com) → Settings → API Keys (it starts with `dd_`) and set `DEERDAWN_API_KEY` in the config's `env` block — browser sign-in is then skipped entirely.
### Claude Code (CLI)
Run the setup command — it registers the server and handles sign-in in one step:
Run the setup command — it registers the server, signs you in, and configures hooks in one step:

@@ -38,16 +63,7 @@ ```bash

```bash
claude mcp add -s user deerdawn -e DEERDAWN_API_URL=https://api.deerdawn.com -- npx -y @deerdawn/mcp-server@latest
claude mcp add -s user deerdawn -e DEERDAWN_API_URL=https://api.deerdawn.com -e DEERDAWN_SURFACE_ID=claude_code -- npx -y @deerdawn/mcp-server@latest
```
If you already have an API key, pass it directly:
Restart Claude Code after registering. If you haven't signed in yet, call `get_auth_status` — it returns the browser URL to complete sign-in.
```bash
claude mcp add -s user deerdawn \
-e DEERDAWN_API_URL=https://api.deerdawn.com \
-e DEERDAWN_API_KEY=dd_your_key_here \
-- npx -y @deerdawn/mcp-server@latest
```
Restart Claude Code after registering. On first start, call `get_auth_status` if you haven't signed in yet — it returns a browser URL to complete sign-in.
---

@@ -57,3 +73,3 @@

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

@@ -68,3 +84,3 @@

"env": {
"DEERDAWN_API_KEY": "dd_your_key_here"
"DEERDAWN_API_URL": "https://api.deerdawn.com"
}

@@ -80,4 +96,3 @@ }

**macOS/Linux**: `~/.cursor/mcp.json`
**Windows**: `%APPDATA%\Cursor\mcp.json`
`~/.cursor/mcp.json` (same path on macOS, Linux, and Windows):

@@ -91,3 +106,3 @@ ```json

"env": {
"DEERDAWN_API_KEY": "dd_your_key_here"
"DEERDAWN_API_URL": "https://api.deerdawn.com"
}

@@ -103,3 +118,3 @@ }

**macOS**: `~/.codeium/windsurf/mcp_config.json`
**macOS**: `~/.codeium/windsurf/mcp_config.json`
**Windows**: `%APPDATA%\Codeium\windsurf\mcp_config.json`

@@ -114,3 +129,3 @@

"env": {
"DEERDAWN_API_KEY": "dd_your_key_here"
"DEERDAWN_API_URL": "https://api.deerdawn.com"
}

@@ -136,3 +151,3 @@ }

"env": {
"DEERDAWN_API_KEY": "dd_your_key_here"
"DEERDAWN_API_URL": "https://api.deerdawn.com"
}

@@ -155,3 +170,3 @@ }

"env": {
"DEERDAWN_API_KEY": "dd_your_key_here"
"DEERDAWN_API_URL": "https://api.deerdawn.com"
}

@@ -177,3 +192,3 @@ }

"env": {
"DEERDAWN_API_KEY": "dd_your_key_here"
"DEERDAWN_API_URL": "https://api.deerdawn.com"
}

@@ -190,12 +205,20 @@ }

Open **Settings → MCP Servers → Add Server**:
Add to `~/.codex/config.toml`:
- **Command**: `npx`
- **Args**: `-y @deerdawn/mcp-server@latest`
- **Env**: `DEERDAWN_API_KEY` = your key from [app.deerdawn.com](https://app.deerdawn.com) → Settings → API Keys
```toml
[mcp_servers.deerdawn]
command = "npx"
args = ["-y", "@deerdawn/mcp-server@latest"]
startup_timeout_sec = 120
**No API key yet?** Add the server without `DEERDAWN_API_KEY` first, then call `get_auth_status` — it returns a browser sign-in URL. After signing in, your key is saved automatically.
[mcp_servers.deerdawn.env]
DEERDAWN_API_URL = "https://api.deerdawn.com"
```
---
To skip browser sign-in in any of these configs, add `"DEERDAWN_API_KEY": "dd_your_key_here"` to the `env` block (Codex TOML: `DEERDAWN_API_KEY = "dd_your_key_here"` under `[mcp_servers.deerdawn.env]`).
---
## Host naming note

@@ -210,10 +233,10 @@

All optional. With no env vars at all, the server starts and walks you through browser sign-in.
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `DEERDAWN_API_KEY` | Yes* | — | Your API key (`dd_...`). Get one at app.deerdawn.com → Settings → API Keys. |
| `DEERDAWN_API_URL` | No | `https://api.deerdawn.com` | API base URL. Only change for self-hosted or staging. |
| `DEERDAWN_ENVIRONMENT` | No | `production` | `sandbox` or `production` |
| `DEERDAWN_SURFACE_ID` | No | auto-detected | Which tool this server runs in (`claude_code`, `cursor`, `codex`). Detected from the MCP client when unset; set it explicitly to pin session attribution. |
| `DEERDAWN_API_KEY` | No | — | API key (`dd_...`) for non-interactive auth (CI/headless). Default is browser OAuth sign-in. Get one at app.deerdawn.com → Settings → API Keys. |
*Required unless using the browser device flow (see "No API key?" above).
---

@@ -223,3 +246,3 @@

These are the core tools. Call them in every session.
The local server exposes 35 tools (the hosted remote exposes 18 core ones). These are the ones to reach for in every session.

@@ -336,3 +359,3 @@ ### `start_session`

If it returns your active project context, DeerDawn is already connected — you're done, skip the rest. (If you connected through the claude.ai connector rather than this standalone server, this is the normal path: `apply_setup` and `import_local_context` won't exist on that build and aren't needed — context loads automatically.)
If it returns your active project context, DeerDawn is already connected — you're done, skip the rest. (If you connected through the hosted remote / claude.ai connector rather than the local server, this is the normal path: `apply_setup` and `import_local_context` won't exist on that build and aren't needed — context loads automatically.)

@@ -355,3 +378,3 @@ If `start_session` reports you're not authenticated, complete sign-in and seed context:

- Confirm `DEERDAWN_API_KEY` starts with `dd_`
- Call `get_auth_status` and finish browser sign-in via the returned URL (if you set `DEERDAWN_API_KEY` instead, confirm it starts with `dd_`)
- Fully restart the agent (not just reload)

@@ -364,6 +387,6 @@ - Check MCP logs: `~/Library/Logs/Claude/mcp*.log` (Claude Desktop) or equivalent

**Server won't start**
1. Verify `DEERDAWN_API_KEY` starts with `dd_`
2. Fully restart your editor after saving the config
3. Check MCP logs:
**DeerDawn tools don't appear in your agent**
The server itself starts with zero env vars — a missing API key never prevents startup. If no DeerDawn tools show up:
1. Fully restart your editor after saving the config (not just reload)
2. Check MCP logs:
- Claude Desktop: `~/Library/Logs/Claude/mcp*.log`

@@ -375,8 +398,11 @@ - Cursor: View → Output → MCP

**Authentication error / not signed in**
Call `get_auth_status` — it returns a browser sign-in URL. Open it, approve, then retry the tool call; setup completes automatically. If you set `DEERDAWN_API_KEY`, it must start with `dd_`.
**Sign-in reports a rate limit (HTTP 429)**
This happens after several quick restarts during setup. Wait a minute before retrying — re-running setup immediately will keep hitting the limit.
**`start_session` returns no projects**
Call `import_local_context` (without `confirmed: true` first) to seed context from a `CLAUDE.md` or `AGENTS.md` in your current directory.
**Authentication error**
API key must start with `dd_`. If you're using the device flow, call `get_auth_status` and open the returned URL in your browser.
**Slow responses**

@@ -387,12 +413,2 @@ The extraction pipeline runs on first import. Subsequent `start_session` calls are fast (cached context lookup, no LLM call unless something changed).

## Governance tools (separate sub-product)
DeerDawn also includes a policy decision engine for AI agent guardrails. These tools are available in the same server but serve a different use case:
`evaluate_decision`, `get_decision`, `list_decisions`, `get_decision_stats`, `list_policies`, `create_policy`, `update_policy`, `delete_policy`, `get_policy_versions`, `create_policy_from_template`, `list_escalations`, `get_escalation`, `resolve_escalation`, `get_organization_settings`, `update_organization_settings`
See [governance.deerdawn.com](https://governance.deerdawn.com) for documentation on the policy engine.
---
## Development

@@ -399,0 +415,0 @@

#!/usr/bin/env node
import { createRequire } from "module";
const require = createRequire(import.meta.url);
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __commonJS = (cb, mod) => function __require2() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// node_modules/tsup/assets/esm_shims.js
import path from "path";
import { fileURLToPath } from "url";
var init_esm_shims = __esm({
"node_modules/tsup/assets/esm_shims.js"() {
"use strict";
}
});
// src/client.ts
init_esm_shims();
import axios from "axios";
import fs from "fs";
import path2 from "path";
import os from "os";
function isLocalApiUrl(apiUrl) {
try {
const host = new URL(apiUrl).hostname;
return host === "localhost" || host === "127.0.0.1" || host === "::1";
} catch {
return false;
}
}
function getDeerdawnHome() {
return process.env.DEERDAWN_HOME || path2.join(os.homedir(), ".deerdawn");
}
function getQueuePath() {
return path2.join(getDeerdawnHome(), "cloud-write-queue.json");
}
function ensureDeerdawnHome() {
fs.mkdirSync(getDeerdawnHome(), { recursive: true });
}
function readQueueState() {
try {
return JSON.parse(fs.readFileSync(getQueuePath(), "utf8"));
} catch {
return { entries: [] };
}
}
function writeQueueState(state) {
ensureDeerdawnHome();
fs.writeFileSync(getQueuePath(), JSON.stringify(state, null, 2));
}
function mapOrgUsageToSettings(data) {
const plan = data?.plan || {};
return {
org_id: plan.org_id || "",
name: data?.org_name || "",
plan: typeof plan.tier === "string" ? plan.tier : "free",
fail_mode: plan.fail_mode === "open" ? "open" : "closed",
redaction_mode: plan.redaction_mode === "permissive" ? "permissive" : "strict",
is_locked: !!plan.is_locked,
shadow_mode: !!plan.shadow_mode,
redaction_keys: Array.isArray(plan.redaction_keys) ? plan.redaction_keys : [],
created_at: (/* @__PURE__ */ new Date()).toISOString(),
usage: data?.usage,
limits: data?.limits
};
}
var DeerdawnClient = class {
client;
cache;
config;
MAX_CACHE_SIZE = 100;
MAX_CACHE_ITEM_SIZE_MB = 5;
constructor(config) {
if (!config.apiKey || config.apiKey.trim() !== config.apiKey) {
throw new Error("Invalid API key: cannot be empty or contain whitespace");
}
if (!isLocalApiUrl(config.apiUrl) && !config.apiKey.startsWith("dd_")) {
throw new Error("Invalid API key format: must start with dd_ prefix");
}
this.config = config;
this.cache = /* @__PURE__ */ new Map();
this.client = axios.create({
baseURL: config.apiUrl,
headers: {
"x-api-key": config.apiKey,
"content-type": "application/json"
},
timeout: 3e4
});
this.client.interceptors.response.use(
(response) => response,
async (error) => {
const cfg = error.config;
const response = error.response;
if (!cfg) {
throw error;
}
if (!cfg.__retryCount) {
cfg.__retryCount = 0;
cfg.__firstRetryTimestamp = Date.now();
}
const totalRetryTime = Date.now() - (cfg.__firstRetryTimestamp || Date.now());
if (totalRetryTime > 6e4) {
throw new Error("Retry timeout exceeded (60s)");
}
const shouldRetry = (response?.status === 429 || response?.status && response.status >= 500) && cfg.__retryCount < this.config.maxRetries;
if (shouldRetry) {
cfg.__retryCount++;
const delay = Math.min(
this.config.retryBackoffMs * Math.pow(2, cfg.__retryCount - 1),
3e4
);
await new Promise((resolve) => setTimeout(resolve, delay));
return this.client.request(cfg);
}
throw error;
}
);
}
async recordProductEvent(event) {
const { data } = await this.client.post("/api/v1/product-events", event);
return data;
}
async recordContextEvent(event) {
try {
const { data } = await this.client.post("/api/v1/context/events", event);
return data;
} catch (error) {
const status = error?.response?.status;
const shouldQueue = !status || status >= 500 || error?.code === "ECONNREFUSED" || error?.code === "ENOTFOUND";
if (!shouldQueue) throw error;
const state = readQueueState();
state.entries.push({
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
method: "POST",
path: "/api/v1/context/events",
body: event,
created_at: (/* @__PURE__ */ new Date()).toISOString()
});
writeQueueState(state);
return { queued: true, queue_size: state.entries.length, event_type: event.event_type };
}
}
async flushQueuedContextWrites() {
const state = readQueueState();
if (!state.entries.length) return { flushed: 0, remaining: 0 };
const remaining = [];
let flushed = 0;
for (const entry of state.entries) {
try {
await this.client.post(entry.path, entry.body);
flushed += 1;
} catch {
remaining.push(entry);
}
}
writeQueueState({ entries: remaining });
return { flushed, remaining: remaining.length };
}
async getOrganizationSettings() {
const { data } = await this.client.get("/api/v1/orgs/usage");
return mapOrgUsageToSettings(data);
}
async getContextGraphProject(projectId) {
const { data } = await this.client.get(`/api/v1/context/graph/projects/${encodeURIComponent(projectId)}`);
return data.graph || data;
}
async getContextGraphSession(sessionId) {
const { data } = await this.client.get(`/api/v1/context/graph/sessions/${encodeURIComponent(sessionId)}`);
return data.graph || data;
}
async getContextGraphNode(nodeId) {
const { data } = await this.client.get(`/api/v1/context/graph/nodes/${encodeURIComponent(nodeId)}`);
return data.graph || data;
}
async queryContextGraph(input) {
const { data } = await this.client.post("/api/v1/context/graph/query", input);
return data.graph || data;
}
async updateOrganizationSettings(settings) {
const { data } = await this.client.patch("/api/v1/orgs/settings", settings);
return data;
}
async getAuditLogs(_params = {}) {
const { data } = await this.client.get("/api/v1/audit");
const logs = data.logs || [];
return { logs, total: logs.length };
}
async getActionRegistry() {
const cacheKey = "registry:actions";
const cached = this.getFromCache(cacheKey);
if (cached) return cached;
try {
const { data } = await this.client.get("/api/v1/registry/actions");
const actions = data.actions || data;
if (Array.isArray(actions) && actions.length > 0) {
this.setInCache(cacheKey, actions, 3e5);
return actions;
}
} catch {
}
return this.getDefaultActionRegistry();
}
getDefaultActionRegistry() {
return [
{
action_type: "auth.login",
category: "authentication",
description: "User login attempt",
risk_level: "medium"
},
{
action_type: "auth.password_reset",
category: "authentication",
description: "Password reset request",
risk_level: "medium"
},
{
action_type: "payment.process",
category: "financial",
description: "Payment processing",
risk_level: "high"
},
{
action_type: "user.delete",
category: "data_management",
description: "User account deletion",
risk_level: "high"
},
{
action_type: "data.export",
category: "data_management",
description: "Data export request",
risk_level: "medium"
}
];
}
getFromCache(key) {
if (!this.config.cacheEnabled) return null;
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expires) {
this.cache.delete(key);
return null;
}
return entry.data;
}
setInCache(key, data, ttlMs) {
if (!this.config.cacheEnabled) return;
const estimatedSize = JSON.stringify(data).length;
const estimatedSizeMB = estimatedSize / (1024 * 1024);
if (estimatedSizeMB > this.MAX_CACHE_ITEM_SIZE_MB) {
console.error(`[CACHE] Skipping large object (${estimatedSizeMB.toFixed(2)}MB)`);
return;
}
if (this.cache.size >= this.MAX_CACHE_SIZE) {
const firstKey = this.cache.keys().next().value;
if (firstKey) {
this.cache.delete(firstKey);
}
}
this.cache.set(key, {
data,
expires: Date.now() + ttlMs
});
}
invalidateCache(prefix) {
for (const key of this.cache.keys()) {
if (key.startsWith(prefix)) {
this.cache.delete(key);
}
}
}
// Context sync methods
async getActiveContext(projectId, tool = "claude_code", verbosity = "standard") {
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { tool, verbosity } }
);
return {
brief: data2.payload?.body ?? null,
project_name: projectId,
project_id: projectId,
last_updated: null
};
}
const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity } });
return data;
}
// Brief shape (?shape=spine) — the product-voice 4-slot view (SHIPPED / DECIDED
// / OPEN / LANDMINE). Returns the 4-slot TEXT body plus the structured
// brief_spine (per-item trust). Opt-in: never changes the default brief text.
async getActiveBrief(projectId, tool = "claude_code", verbosity = "standard") {
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { tool, verbosity, shape: "spine" } }
);
return {
brief: data2.payload?.body ?? null,
brief_spine: data2.payload?.brief_spine ?? null,
project_name: projectId,
project_id: projectId,
last_updated: null
};
}
const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, shape: "spine" } });
return data;
}
async getActiveContextByCwd(cwd, tool = "claude_code", verbosity = "standard", markSession = false, workspaceKey = null) {
const params = { cwd, tool, verbosity };
if (markSession) params.mark_session = 1;
if (workspaceKey) params.workspace_key = workspaceKey;
const { data } = await this.client.get("/api/v1/context/active", { params });
return data;
}
async pushContextUpdate(projectId, summary, tool = "claude_code", commands) {
const sessionId = `mcp-${Date.now()}`;
const [extractionResult] = await Promise.all([
this.client.post("/api/v1/context/extraction", {
session_id: sessionId,
tool,
project_id: projectId,
chunks: [{ role: "assistant", text: summary }]
}).then((r) => r.data),
commands?.length ? this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, { commands }).catch((err) => {
process.stderr.write(`[DeerDawn] commands not saved: ${err?.message ?? "unknown"}
`);
return null;
}) : Promise.resolve(null)
]);
return extractionResult;
}
async listContextProjects() {
const { data } = await this.client.get("/api/v1/context/projects");
return data;
}
async listContextProjectTree() {
const { data } = await this.client.get("/api/v1/context/projects/tree");
return data;
}
async createContextProject(projectName, description, parentProjectId) {
const { data } = await this.client.post("/api/v1/context/projects", {
project_name: projectName,
description,
...parentProjectId ? { parent_project_id: parentProjectId } : {}
});
return data;
}
async switchSubproject(projectId, workspaceKey) {
const { data } = await this.client.post("/api/v1/context/subproject/switch", {
project_id: projectId,
workspace_key: workspaceKey
});
return data;
}
async archiveProject(projectId, unarchive = false) {
const { data } = await this.client.post(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/archive`,
{ unarchive }
);
return data;
}
async setProjectParent(projectId, parentProjectId) {
const { data } = await this.client.post(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/parent`,
{ parent_project_id: parentProjectId ?? null }
);
return data;
}
async renameProject(projectId, projectName) {
const { data } = await this.client.post(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/rename`,
{ project_name: projectName }
);
return data;
}
async getContextSections(projectId, sections, tool = "claude_code", verbosity = "standard") {
const sectionsParam = sections.join(",");
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { tool, verbosity, sections: sectionsParam } }
);
return {
brief: data2.payload?.body ?? null,
project_name: projectId,
project_id: projectId
};
}
const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, sections: sectionsParam } });
return data;
}
async getContextMap(mcpAvailable = true, cwd, workspaceKey) {
const params = { mcp: mcpAvailable ? "true" : "false" };
if (cwd) params.cwd = cwd;
if (workspaceKey) params.workspace_key = workspaceKey;
const { data } = await this.client.get("/api/v1/context/map", { params });
return data;
}
async getContextJson(projectId) {
if (projectId) {
const { data: data2 } = await this.client.get(
`/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`,
{ params: { format: "json" } }
);
return data2.context ?? data2;
}
const { data } = await this.client.get("/api/v1/context/active", { params: { format: "json" } });
return data;
}
async searchContext(query, limit = 5) {
const { data } = await this.client.post("/api/v1/context/search", { query, limit });
return data;
}
async getWorkspaceOverview(params = {}) {
const query = {};
if (params.mode) query.mode = params.mode;
if (params.verbosity) query.verbosity = params.verbosity;
if (params.include?.length) query.include = params.include.join(",");
if (params.format) query.format = params.format;
const { data } = await this.client.get("/api/v1/context/workspace/overview", { params: query });
return data.overview || data;
}
async getWorkspaceEntities(params = {}) {
const query = {};
if (params.types?.length) query.types = params.types.join(",");
if (params.query) query.query = params.query;
if (params.project_id) query.project_id = params.project_id;
if (params.related_project_id) query.related_project_id = params.related_project_id;
if (params.scope) query.scope = params.scope;
if (params.surface) query.surface = params.surface;
if (params.status) query.status = params.status;
if (typeof params.include_inferred === "boolean") query.include_inferred = params.include_inferred ? 1 : 0;
if (typeof params.include_resolved === "boolean") query.include_resolved = params.include_resolved ? 1 : 0;
if (typeof params.curated_only === "boolean") query.curated_only = params.curated_only ? 1 : 0;
if (typeof params.limit === "number") query.limit = params.limit;
if (params.format) query.format = params.format;
const { data } = await this.client.get("/api/v1/context/workspace/entities", { params: query });
return data;
}
async getProjectMap(params = {}) {
const projectId = params.project_id;
if (!projectId) {
const { data: data2 } = await this.client.get("/api/v1/context/active");
if (!data2?.project_id) return null;
params.project_id = data2.project_id;
}
const query = {};
if (params.verbosity) query.verbosity = params.verbosity;
if (params.format) query.format = params.format;
const resolvedProjectId = String(params.project_id);
const { data } = await this.client.get(`/api/v1/context/projects/${encodeURIComponent(resolvedProjectId)}/map`, { params: query });
return data.project_map || data;
}
async findKnownPaths(params) {
const query = { query: params.query };
if (params.project_id) query.project_id = params.project_id;
if (typeof params.limit === "number") query.limit = params.limit;
if (params.format) query.format = params.format;
const { data } = await this.client.get("/api/v1/context/workspace/known-paths", { params: query });
return data;
}
async getNextBestContext(params = {}) {
const query = {};
if (params.tool) query.tool = params.tool;
if (params.cwd) query.cwd = params.cwd;
if (params.project_id) query.project_id = params.project_id;
if (typeof params.limit === "number") query.limit = params.limit;
const { data } = await this.client.get("/api/v1/context/workspace/next", { params: query });
return data.result || data;
}
async upsertWorkspaceEntities(input) {
const { data } = await this.client.post("/api/v1/context/workspace/entities", input);
return data;
}
async recordHotPaths(projectId, items) {
const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/hot-paths`, { items });
return data;
}
async recordDebugFinding(projectId, input) {
const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/debug-findings`, input);
return data;
}
async confirmClaim(projectId, input) {
const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/verify`, input);
return data;
}
async updateGlobalContext(patch) {
const { data } = await this.client.patch("/api/v1/context/global", patch);
return data;
}
async patchProjectContext(projectId, delta) {
const { data } = await this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, delta);
return data;
}
// ── Shared skills ──────────────────────────────────────────────────────────
async listSkills(projectId) {
const params = {};
if (projectId) params.project_id = projectId;
const { data } = await this.client.get("/api/v1/context/skills", { params });
return data?.skills ?? [];
}
async getSkill(idOrSlug) {
const { data } = await this.client.get(`/api/v1/context/skills/${encodeURIComponent(idOrSlug)}`);
return data?.skill ?? null;
}
async upsertSkill(input) {
const { data } = await this.client.post("/api/v1/context/skills", input);
return data?.skill ?? data;
}
// Import a shared skill by pasting a GitHub repo URL. The API fetches the
// repo's SKILL.md, parses its frontmatter, and creates the shared skill.
async importSkillFromGithub(repoUrl, projectId) {
const body = { repo_url: repoUrl };
if (projectId) body.project_id = projectId;
const { data } = await this.client.post("/api/v1/context/skills/import-github", body);
return data;
}
};
export {
__require,
__commonJS,
__toESM,
init_esm_shims,
DeerdawnClient
};
#!/usr/bin/env node
import { createRequire } from "module";
const require = createRequire(import.meta.url);
import {
DeerdawnClient
} from "./chunk-5Y3RCSO4.js";
export {
DeerdawnClient
};

Sorry, the diff of this file is too big to display