@arcbridge/mcp-server 0.1.0 → 0.1.2
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 +9 -1
- package/dist/index.js +305 -7
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @arcbridge/mcp-server
|
|
2
2
|
|
|
3
|
-
MCP server for ArcBridge — exposes
|
|
3
|
+
MCP server for ArcBridge — exposes 29 architecture tools to AI coding agents via the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -88,6 +88,14 @@ Restart your AI agent (Claude Code, etc.) and approve the MCP server when prompt
|
|
|
88
88
|
| `arcbridge_verify_scenarios` | Run linked tests for quality scenarios |
|
|
89
89
|
| `arcbridge_run_role_check` | Run a role's quality checks against code |
|
|
90
90
|
|
|
91
|
+
### Metrics
|
|
92
|
+
|
|
93
|
+
| Tool | Description |
|
|
94
|
+
|------|-------------|
|
|
95
|
+
| `arcbridge_record_activity` | Record agent activity — model, tokens, cost, duration, quality snapshot |
|
|
96
|
+
| `arcbridge_get_metrics` | Query and aggregate activity by model, task, phase, tool, or day |
|
|
97
|
+
| `arcbridge_export_metrics` | Export metrics to JSON, CSV, or Markdown for git commits |
|
|
98
|
+
|
|
91
99
|
## How It Works
|
|
92
100
|
|
|
93
101
|
The server communicates over stdio using the MCP protocol. Each tool call receives a `target_dir` parameter pointing to an ArcBridge-initialized project. The server manages a SQLite database (`.arcbridge/index.db`) that caches architecture docs, indexed symbols, and planning state.
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
6
|
// src/server.ts
|
|
7
|
+
import { createRequire } from "module";
|
|
7
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
9
|
|
|
9
10
|
// src/context.ts
|
|
@@ -148,7 +149,7 @@ import { refreshFromDocs } from "@arcbridge/core";
|
|
|
148
149
|
// src/helpers.ts
|
|
149
150
|
import { join as join2 } from "path";
|
|
150
151
|
import { existsSync as existsSync2 } from "fs";
|
|
151
|
-
import { openDatabase } from "@arcbridge/core";
|
|
152
|
+
import { openDatabase, migrate } from "@arcbridge/core";
|
|
152
153
|
function ensureDb(ctx, targetDir) {
|
|
153
154
|
if (ctx.db) return ctx.db;
|
|
154
155
|
const dbPath = join2(targetDir, ".arcbridge", "index.db");
|
|
@@ -156,6 +157,7 @@ function ensureDb(ctx, targetDir) {
|
|
|
156
157
|
return null;
|
|
157
158
|
}
|
|
158
159
|
ctx.db = openDatabase(dbPath);
|
|
160
|
+
migrate(ctx.db);
|
|
159
161
|
ctx.projectRoot = targetDir;
|
|
160
162
|
return ctx.db;
|
|
161
163
|
}
|
|
@@ -761,6 +763,42 @@ function registerGetCurrentTasks(server, ctx) {
|
|
|
761
763
|
// src/tools/update-task.ts
|
|
762
764
|
import { z as z8 } from "zod";
|
|
763
765
|
import { syncTaskToYaml } from "@arcbridge/core";
|
|
766
|
+
|
|
767
|
+
// src/auto-record.ts
|
|
768
|
+
import { loadConfig, insertActivity } from "@arcbridge/core";
|
|
769
|
+
var configCache = /* @__PURE__ */ new Map();
|
|
770
|
+
var CACHE_TTL_MS = 3e4;
|
|
771
|
+
function isAutoRecordEnabled(projectRoot) {
|
|
772
|
+
const cached = configCache.get(projectRoot);
|
|
773
|
+
if (cached && Date.now() - cached.loadedAt < CACHE_TTL_MS) {
|
|
774
|
+
return cached.autoRecord;
|
|
775
|
+
}
|
|
776
|
+
const { config } = loadConfig(projectRoot);
|
|
777
|
+
const autoRecord2 = config?.metrics?.auto_record ?? false;
|
|
778
|
+
configCache.set(projectRoot, { autoRecord: autoRecord2, loadedAt: Date.now() });
|
|
779
|
+
return autoRecord2;
|
|
780
|
+
}
|
|
781
|
+
function autoRecord(db, projectRoot, params) {
|
|
782
|
+
try {
|
|
783
|
+
if (!isAutoRecordEnabled(projectRoot)) return;
|
|
784
|
+
insertActivity(db, {
|
|
785
|
+
toolName: params.toolName,
|
|
786
|
+
action: params.action,
|
|
787
|
+
taskId: params.taskId,
|
|
788
|
+
phaseId: params.phaseId,
|
|
789
|
+
durationMs: params.durationMs,
|
|
790
|
+
driftCount: params.driftCount,
|
|
791
|
+
driftErrors: params.driftErrors,
|
|
792
|
+
testPassCount: params.testPassCount,
|
|
793
|
+
testFailCount: params.testFailCount,
|
|
794
|
+
lintClean: params.lintClean,
|
|
795
|
+
typecheckClean: params.typecheckClean
|
|
796
|
+
});
|
|
797
|
+
} catch {
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/tools/update-task.ts
|
|
764
802
|
function registerUpdateTask(server, ctx) {
|
|
765
803
|
server.tool(
|
|
766
804
|
"arcbridge_update_task",
|
|
@@ -772,6 +810,7 @@ function registerUpdateTask(server, ctx) {
|
|
|
772
810
|
notes: z8.string().optional().describe("Optional notes about the status change")
|
|
773
811
|
},
|
|
774
812
|
async (params) => {
|
|
813
|
+
const start = Date.now();
|
|
775
814
|
const db = ensureDb(ctx, params.target_dir);
|
|
776
815
|
if (!db) return notInitialized();
|
|
777
816
|
const task = db.prepare("SELECT id, title, status, phase_id FROM tasks WHERE id = ?").get(params.task_id);
|
|
@@ -827,6 +866,13 @@ function registerUpdateTask(server, ctx) {
|
|
|
827
866
|
);
|
|
828
867
|
}
|
|
829
868
|
}
|
|
869
|
+
autoRecord(db, params.target_dir, {
|
|
870
|
+
toolName: "arcbridge_update_task",
|
|
871
|
+
action: `${task.id}: ${oldStatus} \u2192 ${params.status}`,
|
|
872
|
+
taskId: params.task_id,
|
|
873
|
+
phaseId: task.phase_id,
|
|
874
|
+
durationMs: Date.now() - start
|
|
875
|
+
});
|
|
830
876
|
return {
|
|
831
877
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
832
878
|
};
|
|
@@ -1017,7 +1063,7 @@ import { indexProject as indexProject2, refreshFromDocs as refreshFromDocs4 } fr
|
|
|
1017
1063
|
function registerReindex(server, ctx) {
|
|
1018
1064
|
server.tool(
|
|
1019
1065
|
"arcbridge_reindex",
|
|
1020
|
-
"Re-index
|
|
1066
|
+
"Re-index the project: refreshes architecture docs from arc42/YAML files, then reindexes code symbols (TypeScript & C#/.NET). This is the first step of the sync pipeline \u2014 use it to pick up manual doc edits and code changes.",
|
|
1021
1067
|
{
|
|
1022
1068
|
target_dir: z11.string().describe("Absolute path to the project directory"),
|
|
1023
1069
|
tsconfig_path: z11.string().optional().describe("Override tsconfig.json path (default: auto-detect). Only used for TypeScript projects."),
|
|
@@ -1025,6 +1071,7 @@ function registerReindex(server, ctx) {
|
|
|
1025
1071
|
language: z11.enum(["typescript", "csharp", "auto"]).optional().describe("Project language. 'auto' detects from project files (default: 'auto')")
|
|
1026
1072
|
},
|
|
1027
1073
|
async (params) => {
|
|
1074
|
+
const start = Date.now();
|
|
1028
1075
|
const db = ensureDb(ctx, params.target_dir);
|
|
1029
1076
|
if (!db) return notInitialized();
|
|
1030
1077
|
try {
|
|
@@ -1048,6 +1095,11 @@ function registerReindex(server, ctx) {
|
|
|
1048
1095
|
`- **Routes analyzed:** ${result.routesAnalyzed}`,
|
|
1049
1096
|
`- **Duration:** ${result.durationMs}ms`
|
|
1050
1097
|
];
|
|
1098
|
+
autoRecord(db, params.target_dir, {
|
|
1099
|
+
toolName: "arcbridge_reindex",
|
|
1100
|
+
action: `${result.symbolsIndexed} symbols, ${result.filesProcessed} files`,
|
|
1101
|
+
durationMs: Date.now() - start
|
|
1102
|
+
});
|
|
1051
1103
|
return textResult(lines.join("\n"));
|
|
1052
1104
|
} catch (err) {
|
|
1053
1105
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1759,6 +1811,7 @@ function registerCheckDrift(server, ctx) {
|
|
|
1759
1811
|
persist: z18.boolean().default(true).describe("Write findings to drift_log table (default: true)")
|
|
1760
1812
|
},
|
|
1761
1813
|
async (params) => {
|
|
1814
|
+
const start = Date.now();
|
|
1762
1815
|
const db = ensureDb(ctx, params.target_dir);
|
|
1763
1816
|
if (!db) return notInitialized();
|
|
1764
1817
|
const entries = detectDrift(db);
|
|
@@ -1814,6 +1867,13 @@ function registerCheckDrift(server, ctx) {
|
|
|
1814
1867
|
""
|
|
1815
1868
|
);
|
|
1816
1869
|
}
|
|
1870
|
+
autoRecord(db, params.target_dir, {
|
|
1871
|
+
toolName: "arcbridge_check_drift",
|
|
1872
|
+
action: `${entries.length} issues (${errors} errors)`,
|
|
1873
|
+
driftCount: entries.length,
|
|
1874
|
+
driftErrors: errors,
|
|
1875
|
+
durationMs: Date.now() - start
|
|
1876
|
+
});
|
|
1817
1877
|
return textResult(lines.join("\n"));
|
|
1818
1878
|
}
|
|
1819
1879
|
);
|
|
@@ -2651,7 +2711,7 @@ import {
|
|
|
2651
2711
|
inferTaskStatuses,
|
|
2652
2712
|
applyInferences,
|
|
2653
2713
|
verifyScenarios,
|
|
2654
|
-
loadConfig,
|
|
2714
|
+
loadConfig as loadConfig2,
|
|
2655
2715
|
refreshFromDocs as refreshFromDocs5,
|
|
2656
2716
|
syncPhaseToYaml
|
|
2657
2717
|
} from "@arcbridge/core";
|
|
@@ -2667,6 +2727,7 @@ function registerCompletePhase(server, ctx) {
|
|
|
2667
2727
|
run_tests: z23.boolean().default(false).describe("Run linked tests for quality scenarios before checking the quality gate")
|
|
2668
2728
|
},
|
|
2669
2729
|
async (params) => {
|
|
2730
|
+
const start = Date.now();
|
|
2670
2731
|
const db = ensureDb(ctx, params.target_dir);
|
|
2671
2732
|
if (!db) return notInitialized();
|
|
2672
2733
|
refreshFromDocs5(db, params.target_dir);
|
|
@@ -2714,7 +2775,7 @@ function registerCompletePhase(server, ctx) {
|
|
|
2714
2775
|
const projectRoot = ctx.projectRoot ?? params.target_dir;
|
|
2715
2776
|
let testCommand = "npx vitest run";
|
|
2716
2777
|
let timeoutMs = 6e4;
|
|
2717
|
-
const configResult =
|
|
2778
|
+
const configResult = loadConfig2(params.target_dir);
|
|
2718
2779
|
if (configResult.config) {
|
|
2719
2780
|
testCommand = configResult.config.testing.test_command;
|
|
2720
2781
|
timeoutMs = configResult.config.testing.timeout_ms;
|
|
@@ -2867,6 +2928,12 @@ function registerCompletePhase(server, ctx) {
|
|
|
2867
2928
|
"Resolve the issues above before completing this phase."
|
|
2868
2929
|
);
|
|
2869
2930
|
}
|
|
2931
|
+
autoRecord(db, params.target_dir, {
|
|
2932
|
+
toolName: "arcbridge_complete_phase",
|
|
2933
|
+
action: `${phase.name}: ${allPass ? "PASSED" : "BLOCKED"}`,
|
|
2934
|
+
phaseId: params.phase_id,
|
|
2935
|
+
durationMs: Date.now() - start
|
|
2936
|
+
});
|
|
2870
2937
|
return textResult(lines.join("\n"));
|
|
2871
2938
|
}
|
|
2872
2939
|
);
|
|
@@ -3187,7 +3254,7 @@ function getRoleDefinition(roleId) {
|
|
|
3187
3254
|
|
|
3188
3255
|
// src/tools/verify-scenarios.ts
|
|
3189
3256
|
import { z as z25 } from "zod";
|
|
3190
|
-
import { verifyScenarios as verifyScenarios2, loadConfig as
|
|
3257
|
+
import { verifyScenarios as verifyScenarios2, loadConfig as loadConfig3 } from "@arcbridge/core";
|
|
3191
3258
|
function registerVerifyScenarios(server, ctx) {
|
|
3192
3259
|
server.tool(
|
|
3193
3260
|
"arcbridge_verify_scenarios",
|
|
@@ -3206,7 +3273,7 @@ function registerVerifyScenarios(server, ctx) {
|
|
|
3206
3273
|
if (!db) return notInitialized();
|
|
3207
3274
|
let testCommand = "npx vitest run";
|
|
3208
3275
|
let timeoutMs = 6e4;
|
|
3209
|
-
const configResult =
|
|
3276
|
+
const configResult = loadConfig3(params.target_dir);
|
|
3210
3277
|
if (configResult.config) {
|
|
3211
3278
|
testCommand = configResult.config.testing.test_command;
|
|
3212
3279
|
timeoutMs = configResult.config.testing.timeout_ms;
|
|
@@ -3701,11 +3768,239 @@ function runCustomRoleCheck(db, lines, roleDef) {
|
|
|
3701
3768
|
appendDriftSection(db, lines);
|
|
3702
3769
|
}
|
|
3703
3770
|
|
|
3771
|
+
// src/tools/record-activity.ts
|
|
3772
|
+
import { z as z27 } from "zod";
|
|
3773
|
+
import { insertActivity as insertActivity2, getSessionTotals } from "@arcbridge/core";
|
|
3774
|
+
function registerRecordActivity(server, ctx) {
|
|
3775
|
+
server.tool(
|
|
3776
|
+
"arcbridge_record_activity",
|
|
3777
|
+
"Record agent activity \u2014 model, tokens, cost, duration, and optional quality snapshot. Use this to track what work was done and measure agent performance.",
|
|
3778
|
+
{
|
|
3779
|
+
target_dir: z27.string().describe("Absolute path to the project directory"),
|
|
3780
|
+
tool_name: z27.string().describe("Name of the tool or action performed (e.g., 'arcbridge_update_task', 'code_edit')"),
|
|
3781
|
+
action: z27.string().optional().describe("Human-readable label (e.g., 'implement login form')"),
|
|
3782
|
+
model: z27.string().optional().describe("Model identifier (e.g., 'claude-sonnet-4-20250514')"),
|
|
3783
|
+
agent_role: z27.string().optional().describe("Active ArcBridge role (e.g., 'implementer')"),
|
|
3784
|
+
task_id: z27.string().optional().describe("Associated task ID"),
|
|
3785
|
+
phase_id: z27.string().optional().describe("Associated phase ID"),
|
|
3786
|
+
input_tokens: z27.number().int().nonnegative().optional().describe("Input/prompt tokens"),
|
|
3787
|
+
output_tokens: z27.number().int().nonnegative().optional().describe("Output/completion tokens"),
|
|
3788
|
+
total_tokens: z27.number().int().nonnegative().optional().describe("Total tokens (auto-computed if input+output given)"),
|
|
3789
|
+
cost_usd: z27.number().nonnegative().optional().describe("Estimated cost in USD"),
|
|
3790
|
+
duration_ms: z27.number().int().nonnegative().optional().describe("Wall-clock duration in ms"),
|
|
3791
|
+
drift_count: z27.number().int().nonnegative().optional().describe("Current drift count"),
|
|
3792
|
+
drift_errors: z27.number().int().nonnegative().optional().describe("Current drift errors"),
|
|
3793
|
+
test_pass_count: z27.number().int().nonnegative().optional().describe("Passing tests"),
|
|
3794
|
+
test_fail_count: z27.number().int().nonnegative().optional().describe("Failing tests"),
|
|
3795
|
+
lint_clean: z27.boolean().optional().describe("Whether lint passes cleanly"),
|
|
3796
|
+
typecheck_clean: z27.boolean().optional().describe("Whether typecheck passes cleanly"),
|
|
3797
|
+
notes: z27.string().optional().describe("Free-form notes"),
|
|
3798
|
+
metadata: z27.record(z27.unknown()).optional().describe("Additional key-value metadata")
|
|
3799
|
+
},
|
|
3800
|
+
async (params) => {
|
|
3801
|
+
const db = ensureDb(ctx, params.target_dir);
|
|
3802
|
+
if (!db) return notInitialized();
|
|
3803
|
+
const rowId = insertActivity2(db, {
|
|
3804
|
+
toolName: params.tool_name,
|
|
3805
|
+
action: params.action,
|
|
3806
|
+
model: params.model,
|
|
3807
|
+
agentRole: params.agent_role,
|
|
3808
|
+
taskId: params.task_id,
|
|
3809
|
+
phaseId: params.phase_id,
|
|
3810
|
+
inputTokens: params.input_tokens,
|
|
3811
|
+
outputTokens: params.output_tokens,
|
|
3812
|
+
totalTokens: params.total_tokens,
|
|
3813
|
+
costUsd: params.cost_usd,
|
|
3814
|
+
durationMs: params.duration_ms,
|
|
3815
|
+
driftCount: params.drift_count,
|
|
3816
|
+
driftErrors: params.drift_errors,
|
|
3817
|
+
testPassCount: params.test_pass_count,
|
|
3818
|
+
testFailCount: params.test_fail_count,
|
|
3819
|
+
lintClean: params.lint_clean,
|
|
3820
|
+
typecheckClean: params.typecheck_clean,
|
|
3821
|
+
notes: params.notes,
|
|
3822
|
+
metadata: params.metadata
|
|
3823
|
+
});
|
|
3824
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3825
|
+
const totals = getSessionTotals(db, today, params.model);
|
|
3826
|
+
const totalTokens = params.total_tokens ?? (params.input_tokens != null && params.output_tokens != null ? params.input_tokens + params.output_tokens : null);
|
|
3827
|
+
const lines = [
|
|
3828
|
+
`# Activity Recorded (#${rowId})`,
|
|
3829
|
+
"",
|
|
3830
|
+
`- **Tool:** ${params.tool_name}`
|
|
3831
|
+
];
|
|
3832
|
+
if (params.action) lines.push(`- **Action:** ${params.action}`);
|
|
3833
|
+
if (params.model) lines.push(`- **Model:** ${params.model}`);
|
|
3834
|
+
if (totalTokens != null) {
|
|
3835
|
+
const detail = params.input_tokens != null && params.output_tokens != null ? `${params.input_tokens.toLocaleString()} in / ${params.output_tokens.toLocaleString()} out (${totalTokens.toLocaleString()} total)` : `${totalTokens.toLocaleString()} total`;
|
|
3836
|
+
lines.push(`- **Tokens:** ${detail}`);
|
|
3837
|
+
}
|
|
3838
|
+
if (params.cost_usd != null) lines.push(`- **Cost:** $${params.cost_usd.toFixed(4)}`);
|
|
3839
|
+
if (params.duration_ms != null) lines.push(`- **Duration:** ${params.duration_ms.toLocaleString()}ms`);
|
|
3840
|
+
lines.push(
|
|
3841
|
+
"",
|
|
3842
|
+
`## Session Totals (today${params.model ? `, ${params.model}` : ""})`,
|
|
3843
|
+
`- **Total cost:** $${totals.totalCost.toFixed(4)}`,
|
|
3844
|
+
`- **Total tokens:** ${totals.totalTokens.toLocaleString()}`,
|
|
3845
|
+
`- **Activities recorded:** ${totals.activityCount}`
|
|
3846
|
+
);
|
|
3847
|
+
return textResult(lines.join("\n"));
|
|
3848
|
+
}
|
|
3849
|
+
);
|
|
3850
|
+
}
|
|
3851
|
+
|
|
3852
|
+
// src/tools/get-metrics.ts
|
|
3853
|
+
import { z as z28 } from "zod";
|
|
3854
|
+
import { queryMetrics } from "@arcbridge/core";
|
|
3855
|
+
function registerGetMetrics(server, ctx) {
|
|
3856
|
+
server.tool(
|
|
3857
|
+
"arcbridge_get_metrics",
|
|
3858
|
+
"Query agent activity metrics \u2014 filter by model, task, phase, or time range. Group by model/task/phase/tool/day for aggregated views.",
|
|
3859
|
+
{
|
|
3860
|
+
target_dir: z28.string().describe("Absolute path to the project directory"),
|
|
3861
|
+
task_id: z28.string().optional().describe("Filter by task ID"),
|
|
3862
|
+
phase_id: z28.string().optional().describe("Filter by phase ID"),
|
|
3863
|
+
model: z28.string().optional().describe("Filter by model name"),
|
|
3864
|
+
agent_role: z28.string().optional().describe("Filter by agent role"),
|
|
3865
|
+
tool_name: z28.string().optional().describe("Filter by tool name"),
|
|
3866
|
+
since: z28.string().optional().describe("ISO 8601 timestamp \u2014 activity after this time"),
|
|
3867
|
+
until: z28.string().optional().describe("ISO 8601 timestamp \u2014 activity before this time"),
|
|
3868
|
+
group_by: z28.enum(["model", "task", "phase", "tool", "day", "none"]).default("none").describe("Group results for aggregation"),
|
|
3869
|
+
limit: z28.number().int().min(1).max(500).default(50).describe("Max rows in detail view (group_by=none)")
|
|
3870
|
+
},
|
|
3871
|
+
async (params) => {
|
|
3872
|
+
const db = ensureDb(ctx, params.target_dir);
|
|
3873
|
+
if (!db) return notInitialized();
|
|
3874
|
+
const result = queryMetrics(db, {
|
|
3875
|
+
taskId: params.task_id,
|
|
3876
|
+
phaseId: params.phase_id,
|
|
3877
|
+
model: params.model,
|
|
3878
|
+
agentRole: params.agent_role,
|
|
3879
|
+
toolName: params.tool_name,
|
|
3880
|
+
since: params.since,
|
|
3881
|
+
until: params.until,
|
|
3882
|
+
groupBy: params.group_by,
|
|
3883
|
+
limit: params.limit
|
|
3884
|
+
});
|
|
3885
|
+
if (result.totals.activityCount === 0) {
|
|
3886
|
+
return textResult("No agent activity recorded yet. Use `arcbridge_record_activity` to log agent work.");
|
|
3887
|
+
}
|
|
3888
|
+
const lines = [];
|
|
3889
|
+
if (result.grouped) {
|
|
3890
|
+
const rows = result.rows;
|
|
3891
|
+
lines.push(
|
|
3892
|
+
`# Agent Metrics (grouped by ${params.group_by})`,
|
|
3893
|
+
"",
|
|
3894
|
+
`| ${capitalize(params.group_by)} | Activities | Total Tokens | Avg Tokens | Total Cost | Avg Duration |`,
|
|
3895
|
+
`|${"-".repeat(20)}|-----------|-------------|-----------|-----------|-------------|`
|
|
3896
|
+
);
|
|
3897
|
+
for (const r of rows) {
|
|
3898
|
+
lines.push(
|
|
3899
|
+
`| ${mdCell(r.groupKey)} | ${r.activityCount} | ${r.sumTokens?.toLocaleString() ?? "-"} | ${r.avgTokens?.toLocaleString() ?? "-"} | ${r.sumCost != null ? "$" + r.sumCost.toFixed(4) : "-"} | ${r.avgDuration != null ? Math.round(r.avgDuration) + "ms" : "-"} |`
|
|
3900
|
+
);
|
|
3901
|
+
}
|
|
3902
|
+
} else {
|
|
3903
|
+
const rows = result.rows;
|
|
3904
|
+
lines.push(
|
|
3905
|
+
"# Agent Activity (recent)",
|
|
3906
|
+
"",
|
|
3907
|
+
"| Time | Tool | Action | Model | Tokens | Cost | Duration |",
|
|
3908
|
+
"|------|------|--------|-------|--------|------|----------|"
|
|
3909
|
+
);
|
|
3910
|
+
for (const r of rows) {
|
|
3911
|
+
lines.push(
|
|
3912
|
+
`| ${r.recorded_at.slice(0, 19)} | ${mdCell(r.tool_name)} | ${mdCell(r.action)} | ${mdCell(r.model)} | ${r.total_tokens?.toLocaleString() ?? ""} | ${r.cost_usd != null ? "$" + r.cost_usd.toFixed(4) : ""} | ${r.duration_ms != null ? r.duration_ms.toLocaleString() + "ms" : ""} |`
|
|
3913
|
+
);
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
const q = result.qualitySnapshot;
|
|
3917
|
+
if (q.capturedAt) {
|
|
3918
|
+
lines.push(
|
|
3919
|
+
"",
|
|
3920
|
+
"## Latest Quality Snapshot",
|
|
3921
|
+
""
|
|
3922
|
+
);
|
|
3923
|
+
if (q.driftCount != null) lines.push(`- **Drift:** ${q.driftCount} issues (${q.driftErrors ?? 0} errors)`);
|
|
3924
|
+
if (q.testPassCount != null) lines.push(`- **Tests:** ${q.testPassCount} pass / ${q.testFailCount ?? 0} fail`);
|
|
3925
|
+
if (q.lintClean != null) lines.push(`- **Lint:** ${q.lintClean ? "clean" : "errors"}`);
|
|
3926
|
+
if (q.typecheckClean != null) lines.push(`- **Typecheck:** ${q.typecheckClean ? "clean" : "errors"}`);
|
|
3927
|
+
}
|
|
3928
|
+
lines.push(
|
|
3929
|
+
"",
|
|
3930
|
+
"## Totals",
|
|
3931
|
+
"",
|
|
3932
|
+
`- **Activities:** ${result.totals.activityCount}`,
|
|
3933
|
+
`- **Total cost:** $${result.totals.totalCost.toFixed(4)}`,
|
|
3934
|
+
`- **Total tokens:** ${result.totals.totalTokens.toLocaleString()}`
|
|
3935
|
+
);
|
|
3936
|
+
if (result.timeSpan) {
|
|
3937
|
+
lines.push(`- **Time span:** ${result.timeSpan.first.slice(0, 10)} \u2192 ${result.timeSpan.last.slice(0, 10)}`);
|
|
3938
|
+
}
|
|
3939
|
+
return textResult(lines.join("\n"));
|
|
3940
|
+
}
|
|
3941
|
+
);
|
|
3942
|
+
}
|
|
3943
|
+
function capitalize(s) {
|
|
3944
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3945
|
+
}
|
|
3946
|
+
function mdCell(val) {
|
|
3947
|
+
if (val == null) return "";
|
|
3948
|
+
return val.replace(/\|/g, "\\|").replace(/\r?\n|\r/g, " ");
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3951
|
+
// src/tools/export-metrics.ts
|
|
3952
|
+
import { z as z29 } from "zod";
|
|
3953
|
+
import { exportMetrics } from "@arcbridge/core";
|
|
3954
|
+
function registerExportMetrics(server, ctx) {
|
|
3955
|
+
server.tool(
|
|
3956
|
+
"arcbridge_export_metrics",
|
|
3957
|
+
"Export agent activity metrics to a file (JSON, CSV, or Markdown) in .arcbridge/metrics/ for git commits or reporting.",
|
|
3958
|
+
{
|
|
3959
|
+
target_dir: z29.string().describe("Absolute path to the project directory"),
|
|
3960
|
+
format: z29.enum(["json", "csv", "markdown"]).default("json").describe("Export format"),
|
|
3961
|
+
task_id: z29.string().optional().describe("Filter by task ID"),
|
|
3962
|
+
phase_id: z29.string().optional().describe("Filter by phase ID"),
|
|
3963
|
+
model: z29.string().optional().describe("Filter by model name"),
|
|
3964
|
+
agent_role: z29.string().optional().describe("Filter by agent role"),
|
|
3965
|
+
tool_name: z29.string().optional().describe("Filter by tool name"),
|
|
3966
|
+
since: z29.string().optional().describe("ISO 8601 \u2014 activity after this time"),
|
|
3967
|
+
until: z29.string().optional().describe("ISO 8601 \u2014 activity before this time"),
|
|
3968
|
+
max_rows: z29.number().int().min(1).default(1e5).describe("Maximum rows to export (default: 100,000)")
|
|
3969
|
+
},
|
|
3970
|
+
async (params) => {
|
|
3971
|
+
const db = ensureDb(ctx, params.target_dir);
|
|
3972
|
+
if (!db) return notInitialized();
|
|
3973
|
+
const filePath = exportMetrics(
|
|
3974
|
+
db,
|
|
3975
|
+
params.target_dir,
|
|
3976
|
+
params.format,
|
|
3977
|
+
{
|
|
3978
|
+
taskId: params.task_id,
|
|
3979
|
+
phaseId: params.phase_id,
|
|
3980
|
+
model: params.model,
|
|
3981
|
+
agentRole: params.agent_role,
|
|
3982
|
+
toolName: params.tool_name,
|
|
3983
|
+
since: params.since,
|
|
3984
|
+
until: params.until
|
|
3985
|
+
},
|
|
3986
|
+
params.max_rows
|
|
3987
|
+
);
|
|
3988
|
+
return textResult(
|
|
3989
|
+
`Metrics exported to: ${filePath}
|
|
3990
|
+
|
|
3991
|
+
You can commit this file to preserve the activity record in git.`
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
);
|
|
3995
|
+
}
|
|
3996
|
+
|
|
3704
3997
|
// src/server.ts
|
|
3998
|
+
var require2 = createRequire(import.meta.url);
|
|
3999
|
+
var { version } = require2("../package.json");
|
|
3705
4000
|
function createArcBridgeServer() {
|
|
3706
4001
|
const server = new McpServer({
|
|
3707
4002
|
name: "arcbridge",
|
|
3708
|
-
version
|
|
4003
|
+
version
|
|
3709
4004
|
});
|
|
3710
4005
|
const ctx = createContext();
|
|
3711
4006
|
registerInitProject(server, ctx);
|
|
@@ -3734,6 +4029,9 @@ function createArcBridgeServer() {
|
|
|
3734
4029
|
registerActivateRole(server, ctx);
|
|
3735
4030
|
registerVerifyScenarios(server, ctx);
|
|
3736
4031
|
registerRunRoleCheck(server, ctx);
|
|
4032
|
+
registerRecordActivity(server, ctx);
|
|
4033
|
+
registerGetMetrics(server, ctx);
|
|
4034
|
+
registerExportMetrics(server, ctx);
|
|
3737
4035
|
return server;
|
|
3738
4036
|
}
|
|
3739
4037
|
|