hakira-mcp 0.1.0 → 0.1.1
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/README.md +1 -1
- package/dist/git/repo-key.d.ts +15 -0
- package/dist/git/repo-key.js +30 -0
- package/dist/tools/list-audits.js +12 -7
- package/dist/tools/list-workspaces.js +10 -7
- package/package.json +15 -5
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ It exposes eight tools and one resource:
|
|
|
14
14
|
| `get_audit_findings` | free | Finding **summaries** only. Then call `get_finding` for critical/high or when explaining/fixing. |
|
|
15
15
|
| `get_finding` | free | Full finding (description, evidence, recommendation). Use after `get_audit_findings`. |
|
|
16
16
|
| `list_audits` | free | Recent audits/sessions across your account (`is_current` marks this local project). Optional `workspace_id` filter. |
|
|
17
|
-
| `list_workspaces` | free | All workspaces on your account; `is_current` marks this local project. |
|
|
17
|
+
| `list_workspaces` | free | All workspaces on your account; `is_current` marks this local project (`current_workspace_id` is `null` until the first audit creates one). |
|
|
18
18
|
| `cancel_audit` | free | Request cancellation of a running audit. |
|
|
19
19
|
|
|
20
20
|
Resource `hakira://finding/<id>` returns the same full detail as `get_finding` (kept for hosts that prefer resources).
|
package/dist/git/repo-key.d.ts
CHANGED
|
@@ -5,3 +5,18 @@ export declare function normalizeRepoKey(url: string): string;
|
|
|
5
5
|
* 2. else a stable uuid persisted in a locally-excluded `.hakira/mcp.json`.
|
|
6
6
|
*/
|
|
7
7
|
export declare function resolveRepoKey(projectRoot: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* READ-ONLY twin of `resolveRepoKey` — returns the key this project is ALREADY
|
|
10
|
+
* bound by, or null. Never mints a uuid, never writes `.hakira/mcp.json`, never
|
|
11
|
+
* touches `info/exclude`.
|
|
12
|
+
*
|
|
13
|
+
* The free listing tools (`list_workspaces`, `list_audits`) use this. They only
|
|
14
|
+
* need to LABEL which workspace is the current project; calling `resolveRepoKey`
|
|
15
|
+
* + `bindWorkspace` made a read-only tool create a workspace server-side, and in
|
|
16
|
+
* a non-git directory that meant a workspace named after a fresh uuid plus a
|
|
17
|
+
* `.hakira/mcp.json` dropped in a folder the user never opted in from.
|
|
18
|
+
*
|
|
19
|
+
* `start_audit` deliberately keeps `resolveRepoKey` — creating the binding is
|
|
20
|
+
* exactly its job.
|
|
21
|
+
*/
|
|
22
|
+
export declare function peekRepoKey(projectRoot: string): string | null;
|
package/dist/git/repo-key.js
CHANGED
|
@@ -40,6 +40,36 @@ export function resolveRepoKey(projectRoot) {
|
|
|
40
40
|
writeFileSync(file, JSON.stringify({ repo_key: id }, null, 2) + '\n');
|
|
41
41
|
return id;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* READ-ONLY twin of `resolveRepoKey` — returns the key this project is ALREADY
|
|
45
|
+
* bound by, or null. Never mints a uuid, never writes `.hakira/mcp.json`, never
|
|
46
|
+
* touches `info/exclude`.
|
|
47
|
+
*
|
|
48
|
+
* The free listing tools (`list_workspaces`, `list_audits`) use this. They only
|
|
49
|
+
* need to LABEL which workspace is the current project; calling `resolveRepoKey`
|
|
50
|
+
* + `bindWorkspace` made a read-only tool create a workspace server-side, and in
|
|
51
|
+
* a non-git directory that meant a workspace named after a fresh uuid plus a
|
|
52
|
+
* `.hakira/mcp.json` dropped in a folder the user never opted in from.
|
|
53
|
+
*
|
|
54
|
+
* `start_audit` deliberately keeps `resolveRepoKey` — creating the binding is
|
|
55
|
+
* exactly its job.
|
|
56
|
+
*/
|
|
57
|
+
export function peekRepoKey(projectRoot) {
|
|
58
|
+
const origin = tryGit(projectRoot, ['remote', 'get-url', 'origin']);
|
|
59
|
+
if (origin && origin.length > 0)
|
|
60
|
+
return normalizeRepoKey(origin);
|
|
61
|
+
// A uuid only counts if a PREVIOUS resolveRepoKey already persisted it.
|
|
62
|
+
try {
|
|
63
|
+
const file = join(projectRoot, '.hakira', 'mcp.json');
|
|
64
|
+
if (!existsSync(file))
|
|
65
|
+
return null;
|
|
66
|
+
const j = JSON.parse(readFileSync(file, 'utf8'));
|
|
67
|
+
return typeof j.repo_key === 'string' && j.repo_key.length > 0 ? j.repo_key : null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
43
73
|
const EXCLUDE_RULE = '# Hakira MCP local id\n.hakira/\n';
|
|
44
74
|
/**
|
|
45
75
|
* Ignore `.hakira/` via `$GIT_DIR/info/exclude` — NOT the project's `.gitignore`.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { wrap, textResult } from './wrap.js';
|
|
3
|
-
import {
|
|
3
|
+
import { peekRepoKey } from '../git/repo-key.js';
|
|
4
4
|
export function registerListAudits(server, deps) {
|
|
5
5
|
server.registerTool('list_audits', {
|
|
6
6
|
title: 'List recent Hakira audits and sessions',
|
|
7
|
-
description: 'Free. List recent audits/sessions across your Hakira account (most recent first),
|
|
8
|
-
'workspace, origin (mcp|ui|gate), findings count and cost. `current_workspace_id` /
|
|
9
|
-
'mark the workspace bound to this local project
|
|
7
|
+
description: 'Free and read-only. List recent audits/sessions across your Hakira account (most recent first), ' +
|
|
8
|
+
'with status, workspace, origin (mcp|ui|gate), findings count and cost. `current_workspace_id` / ' +
|
|
9
|
+
'`is_current` mark the workspace bound to this local project, and `current_workspace_id` is null ' +
|
|
10
|
+
'when this project has no workspace yet. Optional `workspace_id` filters to one workspace. ' +
|
|
10
11
|
'Use get_audit_events / get_audit_status / get_audit_findings with any owned audit_id.',
|
|
11
12
|
inputSchema: {
|
|
12
13
|
workspace_id: z
|
|
@@ -17,9 +18,13 @@ export function registerListAudits(server, deps) {
|
|
|
17
18
|
},
|
|
18
19
|
}, async (args) => wrap(deps, async () => {
|
|
19
20
|
await deps.resolveToken();
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// Match, never bind (see peekRepoKey). The workspace list is the only
|
|
22
|
+
// place repo_key is exposed, so resolving `current` costs one extra free
|
|
23
|
+
// GET — worth it to keep a read-only tool read-only.
|
|
24
|
+
const repoKey = peekRepoKey(deps.root);
|
|
25
|
+
const current = repoKey
|
|
26
|
+
? ((await deps.cp.listWorkspaces()).workspaces.find((w) => w.repo_key === repoKey)?.workspace_id ?? null)
|
|
27
|
+
: null;
|
|
23
28
|
const { audits } = await deps.cp.listAudits({
|
|
24
29
|
...(args.workspace_id ? { workspace_id: args.workspace_id } : {}),
|
|
25
30
|
limit: args.limit,
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { wrap, textResult } from './wrap.js';
|
|
2
|
-
import {
|
|
2
|
+
import { peekRepoKey } from '../git/repo-key.js';
|
|
3
3
|
export function registerListWorkspaces(server, deps) {
|
|
4
4
|
server.registerTool('list_workspaces', {
|
|
5
5
|
title: 'List all Hakira workspaces',
|
|
6
|
-
description: 'Free. List every workspace on your Hakira account. `current_workspace_id` /
|
|
7
|
-
'the workspace bound to this local project (via git remote / .hakira/mcp.json)
|
|
8
|
-
'
|
|
6
|
+
description: 'Free and read-only. List every workspace on your Hakira account. `current_workspace_id` / ' +
|
|
7
|
+
'`is_current` mark the workspace bound to this local project (via git remote / .hakira/mcp.json); ' +
|
|
8
|
+
'`current_workspace_id` is null when this project has no workspace yet — the first start_audit ' +
|
|
9
|
+
'creates it. Use list_audits to see sessions; start_audit always runs on the current project workspace.',
|
|
9
10
|
inputSchema: {},
|
|
10
11
|
}, async () => wrap(deps, async () => {
|
|
11
12
|
await deps.resolveToken();
|
|
12
|
-
const repoKey = resolveRepoKey(deps.root);
|
|
13
|
-
const bind = await deps.cp.bindWorkspace(repoKey);
|
|
14
|
-
const current = bind.workspace_id;
|
|
15
13
|
const { workspaces } = await deps.cp.listWorkspaces();
|
|
14
|
+
// Match, never bind: listing must not create a workspace as a side effect.
|
|
15
|
+
const repoKey = peekRepoKey(deps.root);
|
|
16
|
+
const current = repoKey
|
|
17
|
+
? (workspaces.find((w) => w.repo_key === repoKey)?.workspace_id ?? null)
|
|
18
|
+
: null;
|
|
16
19
|
return textResult({
|
|
17
20
|
current_workspace_id: current,
|
|
18
21
|
workspaces: workspaces.map((w) => ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hakira-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Hakira MCP server — trigger Hakira cloud security audits from a coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,10 +17,20 @@
|
|
|
17
17
|
"ai",
|
|
18
18
|
"hakira"
|
|
19
19
|
],
|
|
20
|
-
"bin": {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
20
|
+
"bin": {
|
|
21
|
+
"hakira-mcp": "dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
24
34
|
"scripts": {
|
|
25
35
|
"build": "rm -rf dist && tsc && chmod +x dist/index.js",
|
|
26
36
|
"dev": "tsx src/index.ts",
|