@dashclaw/mcp-server 1.0.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) 2026 DashClaw
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,127 @@
1
+ # @dashclaw/mcp-server
2
+
3
+ MCP server for [DashClaw](https://github.com/ucsandman/DashClaw) governance. Exposes 23 governance tools and 4 read-only resources over [Model Context Protocol](https://modelcontextprotocol.io/). Works with Claude Code, Claude Desktop, Claude Managed Agents, and any MCP-compatible client.
4
+
5
+ ## Quick Start
6
+
7
+ ### Claude Desktop / Claude Code (stdio)
8
+
9
+ ```bash
10
+ npx @dashclaw/mcp-server --url https://your-dashclaw.vercel.app --key oc_live_xxx
11
+ ```
12
+
13
+ Or add to `claude_desktop_config.json`:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "dashclaw": {
19
+ "command": "npx",
20
+ "args": ["@dashclaw/mcp-server"],
21
+ "env": {
22
+ "DASHCLAW_URL": "https://your-dashclaw.vercel.app",
23
+ "DASHCLAW_API_KEY": "oc_live_xxx"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### Claude Managed Agents (Streamable HTTP)
31
+
32
+ If you're running DashClaw, the MCP endpoint is built in at `/api/mcp`:
33
+
34
+ ```python
35
+ agent = client.beta.agents.create(
36
+ name="Governed Agent",
37
+ model="claude-sonnet-4-6",
38
+ tools=[{"type": "agent_toolset_20260401"}],
39
+ mcp_servers=[{
40
+ "type": "url",
41
+ "url": "https://your-dashclaw.vercel.app/api/mcp",
42
+ "headers": {"x-api-key": "oc_live_xxx"},
43
+ "name": "dashclaw"
44
+ }],
45
+ )
46
+ ```
47
+
48
+ ## Tools (23)
49
+
50
+ Grouped by domain. See [`lib/tools.js`](./lib/tools.js) for the canonical definitions.
51
+
52
+ **Core governance (8)** — the guard / record / invoke loop plus discovery and session lifecycle.
53
+
54
+ | Tool | Description |
55
+ |---|---|
56
+ | `dashclaw_guard` | Evaluate policies before risky actions |
57
+ | `dashclaw_record` | Log actions to audit trail |
58
+ | `dashclaw_invoke` | Execute governed capabilities (guard + run + record) |
59
+ | `dashclaw_capabilities_list` | Discover available APIs |
60
+ | `dashclaw_policies_list` | See active governance policies |
61
+ | `dashclaw_wait_for_approval` | Block until a human resolves an approval |
62
+ | `dashclaw_session_start` | Register agent session |
63
+ | `dashclaw_session_end` | Close agent session |
64
+
65
+ **Optimal files (2)** — Code Sessions optimizer output (root CLAUDE.md, path-scoped rules, hooks, skill packs).
66
+
67
+ | Tool | Description |
68
+ |---|---|
69
+ | `dashclaw_optimal_files_preview` | Preview optimizer output for a session |
70
+ | `dashclaw_optimal_files_manifest` | Generate optimal-files manifest |
71
+
72
+ **Session continuity (3)** — agent-runtime handoff bundle for the next session.
73
+
74
+ | Tool | Description |
75
+ |---|---|
76
+ | `dashclaw_handoff_create` | Write handoff bundle for next session |
77
+ | `dashclaw_handoff_latest` | Fetch latest unconsumed handoff |
78
+ | `dashclaw_handoff_consume` | Mark handoff consumed (idempotent) |
79
+
80
+ **Credential hygiene (3)** — check rotation due-dates before acting on tracked credentials.
81
+
82
+ | Tool | Description |
83
+ |---|---|
84
+ | `dashclaw_secret_list` | List tracked secrets (metadata only) |
85
+ | `dashclaw_secret_due` | Secrets coming due for rotation |
86
+ | `dashclaw_secret_mark_rotated` | Mark secret rotated (operator-confirmed) |
87
+
88
+ **Skill safety (1)** — static safety scan of untrusted skill files; results cached by content hash.
89
+
90
+ | Tool | Description |
91
+ |---|---|
92
+ | `dashclaw_skill_scan` | Scan skill files for unsafe patterns |
93
+
94
+ **Open loops (3)** — action-scoped commitments ("I will X later" tracker).
95
+
96
+ | Tool | Description |
97
+ |---|---|
98
+ | `dashclaw_loop_add` | Register action-scoped commitment |
99
+ | `dashclaw_loop_list` | List open/resolved loops |
100
+ | `dashclaw_loop_close` | Resolve an open loop |
101
+
102
+ **Learning + retrospection (3)** — log and query non-obvious decisions; recent governed-action ledger.
103
+
104
+ | Tool | Description |
105
+ |---|---|
106
+ | `dashclaw_learning_log` | Log non-obvious decision + outcome |
107
+ | `dashclaw_learning_query` | Query prior decisions/lessons |
108
+ | `dashclaw_decisions_recent` | Recent governed-action ledger |
109
+
110
+ ## Resources (4)
111
+
112
+ | URI | Description |
113
+ |---|---|
114
+ | `dashclaw://policies` | Active policy set |
115
+ | `dashclaw://capabilities` | Available capabilities and health |
116
+ | `dashclaw://agent/{agent_id}/history` | Recent action history (last 50) |
117
+ | `dashclaw://status` | Instance health + operational metrics |
118
+
119
+ ## Configuration
120
+
121
+ | CLI Arg | Env Var | Default | Description |
122
+ |---|---|---|---|
123
+ | `--url` | `DASHCLAW_URL` | `http://localhost:3000` | DashClaw instance URL |
124
+ | `--key` | `DASHCLAW_API_KEY` | (empty) | API key (`oc_live_` prefix) |
125
+ | `--agent-id` | `DASHCLAW_AGENT_ID` | (empty) | Default agent ID |
126
+
127
+ CLI args take precedence over environment variables.
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { StdioServerTransport } from '@modelcontextprotocol/server';
4
+ import { createServer } from '../lib/server.js';
5
+
6
+ process.on('unhandledRejection', (reason) => {
7
+ console.error('Unhandled Rejection:', reason);
8
+ process.exit(1);
9
+ });
10
+
11
+ // Parse CLI args: --url, --key, --agent-id
12
+ const args = process.argv.slice(2);
13
+ const config = {};
14
+
15
+ for (let i = 0; i < args.length; i++) {
16
+ switch (args[i]) {
17
+ case '--url':
18
+ config.url = args[++i];
19
+ break;
20
+ case '--key':
21
+ config.apiKey = args[++i];
22
+ break;
23
+ case '--agent-id':
24
+ config.agentId = args[++i];
25
+ break;
26
+ case '--help':
27
+ console.error(`Usage: dashclaw-mcp [options]
28
+
29
+ Options:
30
+ --url <url> DashClaw instance URL (default: http://localhost:3000)
31
+ --key <key> API key (oc_live_ prefix)
32
+ --agent-id <id> Default agent ID
33
+
34
+ Environment variables (fallback):
35
+ DASHCLAW_URL DashClaw instance URL
36
+ DASHCLAW_API_KEY API key
37
+ DASHCLAW_AGENT_ID Default agent ID`);
38
+ process.exit(0);
39
+ break;
40
+ }
41
+ }
42
+
43
+ // Env vars as fallback
44
+ config.url = config.url || process.env.DASHCLAW_URL;
45
+ config.apiKey = config.apiKey || process.env.DASHCLAW_API_KEY;
46
+ config.agentId = config.agentId || process.env.DASHCLAW_AGENT_ID;
47
+
48
+ const server = createServer(config);
49
+ const transport = new StdioServerTransport();
50
+ await server.connect(transport);
51
+ console.error('@dashclaw/mcp-server running on stdio');
package/lib/client.js ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * HTTP client for DashClaw REST API.
3
+ * Used by MCP tool and resource handlers.
4
+ */
5
+ export class DashClawClient {
6
+ /**
7
+ * @param {Object} options
8
+ * @param {string} [options.url] - DashClaw instance URL
9
+ * @param {string} [options.apiKey] - API key (oc_live_ prefix)
10
+ * @param {string} [options.agentId] - Default agent ID for tool calls
11
+ */
12
+ constructor({ url, apiKey, agentId } = {}) {
13
+ this.baseUrl = (url || 'http://localhost:3000').replace(/\/$/, '');
14
+ this.apiKey = apiKey || '';
15
+ this.agentId = agentId || '';
16
+ }
17
+
18
+ async post(path, body, { timeout = 10000 } = {}) {
19
+ try {
20
+ const res = await fetch(`${this.baseUrl}${path}`, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ 'x-api-key': this.apiKey,
25
+ },
26
+ body: JSON.stringify(body),
27
+ signal: AbortSignal.timeout(timeout),
28
+ });
29
+ const data = await res.json();
30
+ if (!res.ok) return { ...data, _status: res.status };
31
+ return data;
32
+ } catch (err) {
33
+ return { error: err.message, _status: 0 };
34
+ }
35
+ }
36
+
37
+ async get(path, params = {}, { timeout = 10000 } = {}) {
38
+ const filtered = Object.fromEntries(
39
+ Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== ''),
40
+ );
41
+ const qs = new URLSearchParams(filtered).toString();
42
+ const url = qs ? `${this.baseUrl}${path}?${qs}` : `${this.baseUrl}${path}`;
43
+ try {
44
+ const res = await fetch(url, {
45
+ method: 'GET',
46
+ headers: { 'x-api-key': this.apiKey },
47
+ signal: AbortSignal.timeout(timeout),
48
+ });
49
+ const data = await res.json();
50
+ if (!res.ok) return { ...data, _status: res.status };
51
+ return data;
52
+ } catch (err) {
53
+ return { error: err.message, _status: 0 };
54
+ }
55
+ }
56
+
57
+ async patch(path, body, { timeout = 10000 } = {}) {
58
+ try {
59
+ const res = await fetch(`${this.baseUrl}${path}`, {
60
+ method: 'PATCH',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ 'x-api-key': this.apiKey,
64
+ },
65
+ body: JSON.stringify(body),
66
+ signal: AbortSignal.timeout(timeout),
67
+ });
68
+ const data = await res.json();
69
+ if (!res.ok) return { ...data, _status: res.status };
70
+ return data;
71
+ } catch (err) {
72
+ return { error: err.message, _status: 0 };
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Low-level fetch passthrough used by toolkit MCP handlers that need
78
+ * direct access to status codes (e.g., 404-as-null) and per-call methods.
79
+ * Returns the raw Response-like object: { ok, status, json() }.
80
+ */
81
+ async fetch(path, opts = {}) {
82
+ const method = (opts.method || 'GET').toUpperCase();
83
+ const headers = { 'x-api-key': this.apiKey, ...(opts.headers || {}) };
84
+ if (opts.body && !headers['Content-Type']) headers['Content-Type'] = 'application/json';
85
+ const timeout = opts.timeout ?? 10000;
86
+ try {
87
+ const res = await fetch(`${this.baseUrl}${path}`, {
88
+ method,
89
+ headers,
90
+ body: opts.body,
91
+ signal: AbortSignal.timeout(timeout),
92
+ });
93
+ return res;
94
+ } catch (err) {
95
+ return {
96
+ ok: false,
97
+ status: 0,
98
+ json: async () => ({ error: err.message, _status: 0 }),
99
+ };
100
+ }
101
+ }
102
+ }
@@ -0,0 +1,94 @@
1
+ // mcp-server/lib/resources.js
2
+
3
+ /**
4
+ * DashClaw MCP resource definitions and handlers.
5
+ * Resources provide read-only governance context.
6
+ */
7
+
8
+ export const RESOURCE_DEFINITIONS = [
9
+ {
10
+ uri: 'dashclaw://policies',
11
+ name: 'DashClaw Policies',
12
+ description: 'Current active governance policy set for the organization.',
13
+ mimeType: 'application/json',
14
+ },
15
+ {
16
+ uri: 'dashclaw://capabilities',
17
+ name: 'DashClaw Capabilities',
18
+ description: 'Available capabilities and their health status.',
19
+ mimeType: 'application/json',
20
+ },
21
+ {
22
+ uri: 'dashclaw://agent/{agent_id}/history',
23
+ name: 'Agent Action History',
24
+ description: 'Recent action history for a specific agent (last 50 records).',
25
+ mimeType: 'application/json',
26
+ isTemplate: true,
27
+ },
28
+ {
29
+ uri: 'dashclaw://status',
30
+ name: 'DashClaw Status',
31
+ description: 'Instance health and operational summary metrics.',
32
+ mimeType: 'application/json',
33
+ },
34
+ {
35
+ uri: 'dashclaw://code-sessions/projects',
36
+ name: 'Code Sessions Projects',
37
+ description: 'All Claude Code projects with ingested session data plus per-project rollups.',
38
+ mimeType: 'application/json',
39
+ },
40
+ {
41
+ uri: 'dashclaw://code-sessions/sessions/{session_id}',
42
+ name: 'Code Session Detail',
43
+ description: 'Full detail for one ingested Code Session: session row, messages, tool uses.',
44
+ mimeType: 'application/json',
45
+ isTemplate: true,
46
+ },
47
+ ];
48
+
49
+ /**
50
+ * Create resource handler functions bound to a DashClawClient instance.
51
+ * Each handler returns a JSON string of the resource content.
52
+ * @param {import('./client.js').DashClawClient} client
53
+ * @returns {Object<string, function>}
54
+ */
55
+ export function createResourceHandlers(client) {
56
+ return {
57
+ 'dashclaw://policies': async () => {
58
+ const result = await client.get('/api/policies', {}, { timeout: 10000 });
59
+ return JSON.stringify(result);
60
+ },
61
+
62
+ 'dashclaw://capabilities': async () => {
63
+ const result = await client.get('/api/capabilities', {}, { timeout: 10000 });
64
+ return JSON.stringify(result);
65
+ },
66
+
67
+ 'dashclaw://agent/{agent_id}/history': async ({ agent_id }) => {
68
+ const result = await client.get('/api/actions', {
69
+ agent_id,
70
+ limit: '50',
71
+ }, { timeout: 10000 });
72
+ return JSON.stringify(result);
73
+ },
74
+
75
+ 'dashclaw://status': async () => {
76
+ const [health, operations] = await Promise.all([
77
+ client.get('/api/health', {}, { timeout: 10000 }),
78
+ client.get('/api/operations/summary', {}, { timeout: 10000 }),
79
+ ]);
80
+ return JSON.stringify({ health, operations });
81
+ },
82
+
83
+ 'dashclaw://code-sessions/projects': async () => {
84
+ const result = await client.get('/api/code-sessions/projects', {}, { timeout: 15000 });
85
+ return JSON.stringify(result);
86
+ },
87
+
88
+ 'dashclaw://code-sessions/sessions/{session_id}': async ({ session_id }) => {
89
+ const result = await client.get(`/api/code-sessions/sessions/${encodeURIComponent(session_id)}`,
90
+ {}, { timeout: 15000 });
91
+ return JSON.stringify(result);
92
+ },
93
+ };
94
+ }