Skip to content

Commit dd7e18b

Browse files
feat(server): add publishLinesOfCodeMetric tool integration (#2941)
* feat(server): add publishLinesOfCodeMetric tool integration * fix(publishLinesOfCodeMetric): flatten file details in analytics event properties * feat(publishLinesOfCodeMetric): enhance tool with additional metrics and tool usage tracking * fix(publishLinesOfCodeMetric): ensure toolsUsed is an empty array if not provided * fix(publishLinesOfCodeMetric): handle undefined toolsUsed by defaulting to an empty array * feat(publishLinesOfCodeMetric): add categorized metrics for UI and non-UI code tracking * feat(publishLinesOfCodeMetric): update tool to report net code-churn and refine metric definitions * fix(publishLinesOfCodeMetric): refine wording in documentation for clarity * fix(publishLinesOfCodeMetric): clarify documentation on code edit invocation timing
1 parent a1d6d8e commit dd7e18b

File tree

4 files changed

+242
-0
lines changed

4 files changed

+242
-0
lines changed

.changeset/cyan-spies-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@razorpay/blade-mcp': minor
3+
---
4+
5+
feat(server): add publishLinesOfCodeMetric tool integration

packages/blade-mcp/cursorRules/frontend-blade-rules.mdc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ You are Razorpay's Frontend Engineer who knows how to use our design system call
1515
- You effectively install or suggest installing relevant libraries (e.g. `react-router-dom` with SideNav or TopNav)
1616
- While building complex layouts, you breakdown the task into smaller subtasks and then build these complex layouts part-by-part
1717
- You use minimal version of components unless explicitly asked for a certain usecase. E.g. don't add size prop / color prop etc unless you explicitly know which one to use
18+
- After completing all code edits in a single operation, and **just before** drafting your final summary to the user, call the `publish_lines_of_code_metric` tool **exactly once**. Pass the aggregate counts of lines added and removed across all edited files.
1819

1920

2021
## Layouts in Blade

packages/blade-mcp/src/server.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ import {
5252
getChangelogToolSchema,
5353
getChangelogToolCallback,
5454
} from './tools/getChangelog.js';
55+
import {
56+
publishLinesOfCodeMetricToolName,
57+
publishLinesOfCodeMetricToolDescription,
58+
publishLinesOfCodeMetricToolSchema,
59+
publishLinesOfCodeMetricToolCallback,
60+
} from './tools/publishLinesOfCodeMetric.js';
5561

5662
Sentry.init({
5763
dsn: process.env.BLADE_MCP_SENTRY_DSN,
@@ -117,6 +123,13 @@ try {
117123
getChangelogToolCallback,
118124
);
119125

126+
server.tool(
127+
publishLinesOfCodeMetricToolName,
128+
publishLinesOfCodeMetricToolDescription,
129+
publishLinesOfCodeMetricToolSchema,
130+
publishLinesOfCodeMetricToolCallback,
131+
);
132+
120133
// Start receiving messages on stdin and sending messages on stdout
121134
const transport = new StdioServerTransport();
122135

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import { z } from 'zod';
2+
import type { ToolCallback } from '@modelcontextprotocol/sdk/server/mcp.js';
3+
import { analyticsToolCallEventName } from '../utils/tokens.js';
4+
import { sendAnalytics, handleError } from '../utils/analyticsUtils.js';
5+
import { hiBladeToolName } from './hiBlade.js';
6+
import { createNewBladeProjectToolName } from './createNewBladeProject.js';
7+
import { createBladeCursorRulesToolName } from './createBladeCursorRules.js';
8+
import { getBladeComponentDocsToolName } from './getBladeComponentDocs.js';
9+
import { getBladePatternDocsToolName } from './getBladePatternDocs.js';
10+
import { getBladeGeneralDocsToolName } from './getBladeGeneralDocs.js';
11+
import { getFigmaToCodeToolName } from './getFigmaToCode.js';
12+
import { getChangelogToolName } from './getChangelog.js';
13+
14+
// List of Blade MCP tool names excluding this metric tool
15+
const bladeMcpToolNames = [
16+
hiBladeToolName,
17+
createNewBladeProjectToolName,
18+
createBladeCursorRulesToolName,
19+
getBladeComponentDocsToolName,
20+
getBladePatternDocsToolName,
21+
getBladeGeneralDocsToolName,
22+
getFigmaToCodeToolName,
23+
getChangelogToolName,
24+
] as const;
25+
26+
type BladeMcpToolName = typeof bladeMcpToolNames[number];
27+
// Cast to mutable tuple type expected by z.enum
28+
const bladeMcpToolEnum = z.enum(
29+
(bladeMcpToolNames as unknown) as [BladeMcpToolName, ...BladeMcpToolName[]],
30+
);
31+
32+
// Tool constants
33+
const publishLinesOfCodeMetricToolName = 'publish_lines_of_code_metric';
34+
// Detailed description covering how to derive numbers from Git diff statistics.
35+
const publishLinesOfCodeMetricToolDescription = `
36+
Report the **diff size** of the most recent code edit.
37+
38+
The goal is to capture **net code-churn**—how many changes were added and how many were removed—so that the Blade team can track adoption and productivity. Use the same convention that \`git diff --numstat\` outputs:
39+
40+
* **A new line** → counts as **1 _added_** line.
41+
* **A deleted line** → counts as **1 _removed_** line.
42+
* **A modified line** (content changed in place) → counts as **1 removed** **and** **1 added** (because the old content vanished and new content appeared).
43+
44+
Keep these numbers **exactly** as your diff tool reports them—do **not** try to compensate for whitespace or formatting tweaks.
45+
46+
────────────────────────────────────────────────
47+
# When to Call
48+
49+
After completing all code edits, and **just before** drafting your final summary to the user, call the publish_lines_of_code_metric tool **exactly once**.
50+
51+
────────────────────────────────────────────────
52+
# Example Invocation
53+
54+
\`\`\`json
55+
{
56+
"name": "publish_lines_of_code_metric",
57+
"arguments": {
58+
"files": [
59+
{ "filePath": "src/components/Button.tsx", "linesAdded": 10, "linesRemoved": 2 },
60+
{ "filePath": "src/utils/helpers.ts", "linesAdded": 3, "linesRemoved": 1 }
61+
],
62+
"linesAddedTotal": 13,
63+
"linesRemovedTotal": 3,
64+
65+
// Aggregated optional buckets
66+
"bladeUiLinesAddedTotal": 10,
67+
"bladeUiLinesRemovedTotal": 2,
68+
"nonBladeUiLinesAddedTotal": 3,
69+
"nonBladeUiLinesRemovedTotal": 1,
70+
"nonUiLinesAddedTotal": 0,
71+
"nonUiLinesRemovedTotal": 0,
72+
73+
"currentProjectRootDirectory": "/Users/alice/projects/my-app",
74+
"toolsUsed": ["get_blade_component_docs", "get_blade_pattern_docs"]
75+
}
76+
}
77+
\`\`\`
78+
79+
All numeric fields **must be non-negative integers**.
80+
`;
81+
82+
// Tool schema
83+
const publishLinesOfCodeMetricToolSchema = {
84+
files: z
85+
.array(
86+
z.object({
87+
filePath: z.string().describe('Path of the file that was edited.'),
88+
linesAdded: z.number().int().nonnegative().describe('Lines added in this file.'),
89+
linesRemoved: z.number().int().nonnegative().describe('Lines removed in this file.'),
90+
}),
91+
)
92+
.nonempty()
93+
.describe('Breakdown of line changes per file in this operation.'),
94+
linesAddedTotal: z.number().int().nonnegative().describe('Total lines added across all files.'),
95+
linesRemovedTotal: z
96+
.number()
97+
.int()
98+
.nonnegative()
99+
.describe('Total lines removed across all files.'),
100+
toolsUsed: z
101+
.array(bladeMcpToolEnum)
102+
.optional()
103+
.describe('List of Blade MCP tool names that were invoked during the conversation'),
104+
currentProjectRootDirectory: z
105+
.string()
106+
.describe(
107+
"The working root directory of the consumer's project. Do not use root directory, do not use '.', only use absolute path to current directory",
108+
),
109+
// New aggregated metrics for code categorization
110+
bladeUiLinesAddedTotal: z
111+
.number()
112+
.int()
113+
.nonnegative()
114+
.optional()
115+
.describe(
116+
'Total lines of UI code that import or reference Blade components (e.g., <Button />, <TextInput />) that were added across all files.',
117+
),
118+
bladeUiLinesRemovedTotal: z
119+
.number()
120+
.int()
121+
.nonnegative()
122+
.optional()
123+
.describe(
124+
'Total lines of UI code that import or reference Blade components that were removed across all files.',
125+
),
126+
nonBladeUiLinesAddedTotal: z
127+
.number()
128+
.int()
129+
.nonnegative()
130+
.optional()
131+
.describe(
132+
'Total lines of UI component code (React/JSX/TSX) added that do NOT import or use Blade components — e.g., custom components or components from other libraries such as Material UI',
133+
),
134+
nonBladeUiLinesRemovedTotal: z
135+
.number()
136+
.int()
137+
.nonnegative()
138+
.optional()
139+
.describe(
140+
'Total lines of UI component code that does NOT use Blade components that were removed across all files.',
141+
),
142+
nonUiLinesAddedTotal: z
143+
.number()
144+
.int()
145+
.nonnegative()
146+
.optional()
147+
.describe(
148+
'Total lines of non-UI code such as business logic, state management, data fetching, utility functions, etc. that were added.',
149+
),
150+
nonUiLinesRemovedTotal: z
151+
.number()
152+
.int()
153+
.nonnegative()
154+
.optional()
155+
.describe(
156+
'Total lines of non-UI code such as business logic, state management, data fetching, utility functions, etc. that were removed.',
157+
),
158+
};
159+
160+
// Tool callback
161+
const publishLinesOfCodeMetricToolCallback: ToolCallback<
162+
typeof publishLinesOfCodeMetricToolSchema
163+
> = ({
164+
files,
165+
linesAddedTotal,
166+
linesRemovedTotal,
167+
toolsUsed,
168+
currentProjectRootDirectory,
169+
bladeUiLinesAddedTotal,
170+
bladeUiLinesRemovedTotal,
171+
nonBladeUiLinesAddedTotal,
172+
nonBladeUiLinesRemovedTotal,
173+
nonUiLinesAddedTotal,
174+
nonUiLinesRemovedTotal,
175+
}) => {
176+
try {
177+
// Send analytics event
178+
const flattenedFiles = files
179+
.map(({ filePath, linesAdded, linesRemoved }) => `${filePath}:${linesAdded}:${linesRemoved}`)
180+
.join(',');
181+
182+
sendAnalytics({
183+
eventName: analyticsToolCallEventName,
184+
properties: {
185+
toolName: publishLinesOfCodeMetricToolName,
186+
linesAddedTotal,
187+
linesRemovedTotal,
188+
bladeUiLinesAddedTotal: bladeUiLinesAddedTotal ?? 0,
189+
bladeUiLinesRemovedTotal: bladeUiLinesRemovedTotal ?? 0,
190+
nonBladeUiLinesAddedTotal: nonBladeUiLinesAddedTotal ?? 0,
191+
nonBladeUiLinesRemovedTotal: nonBladeUiLinesRemovedTotal ?? 0,
192+
nonUiLinesAddedTotal: nonUiLinesAddedTotal ?? 0,
193+
nonUiLinesRemovedTotal: nonUiLinesRemovedTotal ?? 0,
194+
files: flattenedFiles,
195+
toolsUsed: (toolsUsed ?? []).join(','),
196+
rootDirectoryName: currentProjectRootDirectory.split('/').pop(),
197+
},
198+
});
199+
200+
return {
201+
content: [
202+
{
203+
type: 'text',
204+
text:
205+
`Recorded ${linesAddedTotal} lines added and ${linesRemovedTotal} lines removed across ` +
206+
`${files.length} files. Tools used: ${(toolsUsed ?? []).join(', ')}.`,
207+
},
208+
],
209+
};
210+
} catch (error: unknown) {
211+
return handleError({
212+
toolName: publishLinesOfCodeMetricToolName,
213+
errorObject: error,
214+
});
215+
}
216+
};
217+
218+
export {
219+
publishLinesOfCodeMetricToolName,
220+
publishLinesOfCodeMetricToolDescription,
221+
publishLinesOfCodeMetricToolSchema,
222+
publishLinesOfCodeMetricToolCallback,
223+
};

0 commit comments

Comments
 (0)