@shrkcrft/mcp-server 0.1.0-alpha.7 → 0.1.0-alpha.8

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.
@@ -1 +1 @@
1
- {"version":3,"file":"create-mcp-server.d.ts","sourceRoot":"","sources":["../../src/server/create-mcp-server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAQnE,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACV,eAAe,EAEf,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAK9B,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC;CAChC;AAED,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACrE,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,eAAe,CAAC;CAChC;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACzC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,mCAAmC;IACnC,YAAY,CAAC,EAAE,eAAe,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,iBAAiB,EAAE,MAAM,GAAG,SAAS,EACrC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,MAAM,CAIR;AAqBD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,6BAA6B,GAAG;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;CACpB,CAqHA;AAED,wBAAsB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAmEvF"}
1
+ {"version":3,"file":"create-mcp-server.d.ts","sourceRoot":"","sources":["../../src/server/create-mcp-server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAQnE,OAAO,EAAqB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACV,eAAe,EAEf,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAM9B,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC;CAChC;AAED,MAAM,WAAW,6BAA8B,SAAQ,gBAAgB;IACrE,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,eAAe,CAAC;CAChC;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,gBAAgB,CAAC;IACzB,UAAU,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACzC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC1C,mCAAmC;IACnC,YAAY,CAAC,EAAE,eAAe,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,iBAAiB,EAAE,MAAM,GAAG,SAAS,EACrC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,MAAM,CAIR;AAqBD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,6BAA6B,GAAG;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;CACpB,CAgIA;AAED,wBAAsB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAmEvF"}
@@ -5,6 +5,7 @@ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSche
5
5
  import { inspectSharkcraft } from '@shrkcrft/inspector';
6
6
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION, } from "./mcp-server-config.js";
7
7
  import { ALL_TOOLS } from "../tools/index.js";
8
+ import { PRIMARY_MCP_TOOLS, shouldAdvertiseFullToolset } from "../tools/primary-tools.js";
8
9
  import { validateToolInput } from "./tool-input-validators.js";
9
10
  import { buildResourceList, readResource } from "../resources/index.js";
10
11
  /**
@@ -52,13 +53,24 @@ export function createSharkcraftServer(config) {
52
53
  resources: { listChanged: true },
53
54
  },
54
55
  });
55
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
56
- tools: ALL_TOOLS.map((t) => ({
57
- name: t.name,
58
- description: t.description,
59
- inputSchema: t.inputSchema,
60
- })),
61
- }));
56
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
57
+ // Default `tools/list` advertises only the ~30 primary tools to
58
+ // keep the agent's tool-selection surface focused. The other ~200
59
+ // tools stay CALLABLE via `tools/call` for agents that already
60
+ // know the name (e.g. via `shrk export claude-skill` referencing
61
+ // them). Set SHRK_MCP_FULL_TOOLS=1 to advertise everything.
62
+ const advertiseFull = shouldAdvertiseFullToolset();
63
+ const advertised = advertiseFull
64
+ ? ALL_TOOLS
65
+ : ALL_TOOLS.filter((t) => PRIMARY_MCP_TOOLS.has(t.name));
66
+ return {
67
+ tools: advertised.map((t) => ({
68
+ name: t.name,
69
+ description: t.description,
70
+ inputSchema: t.inputSchema,
71
+ })),
72
+ };
73
+ });
62
74
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
63
75
  const name = request.params.name;
64
76
  const args = (request.params.arguments ?? {});
@@ -1 +1 @@
1
- {"version":3,"file":"all-tools.d.ts","sourceRoot":"","sources":["../../src/tools/all-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAmTpE,eAAO,MAAM,SAAS,EAAE,SAAS,eAAe,EA6P9C,CAAC"}
1
+ {"version":3,"file":"all-tools.d.ts","sourceRoot":"","sources":["../../src/tools/all-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAiTpE,eAAO,MAAM,SAAS,EAAE,SAAS,eAAe,EA0P9C,CAAC"}
@@ -116,7 +116,8 @@ import { getContractTemplateTool, getLanguageCommandsTool, getLanguageProfilesTo
116
116
  import { getTaskContextTool, understandTaskTool, validateChangeContextTool, } from "./r26-task-context.tool.js";
117
117
  import { getLanguageCacheStatusTool, getLanguageProfilesLiveTool, getLanguageRunPlanTool, getPolyglotBoundaryReportTool, } from "./r27-polyglot.tool.js";
118
118
  import { getChangedBoundaryReportTool } from "./r28-changed-boundary.tool.js";
119
- import { previewPluginRemoveTool, previewPluginRenameTool } from "./r28-plugin-lifecycle.tool.js";
119
+ import { getDiffCheckReportTool } from "./diff-check.tool.js";
120
+ import { getFileAdviceTool } from "./file-advice.tool.js";
120
121
  import { getHelperTool, listHelpersTool, previewHelperPlanTool } from "./r28-helpers.tool.js";
121
122
  import { getPackDevStatusTool, previewPackTestsTool } from "./r28-pack-author.tool.js";
122
123
  import { getRegistryLifecycleReportTool } from "./r28-registry-lifecycle.tool.js";
@@ -135,7 +136,7 @@ import { suggestCommandsTool, searchCommandsTool, explainCommandTool, } from "./
135
136
  import { previewFixTool, listFixKindsTool, } from "./r31-fix-preview.tool.js";
136
137
  import { getScaffoldCoverageReportTool } from "./r31-scaffold-coverage.tool.js";
137
138
  import { getChangesSummaryTool, getPrSummaryPreviewTool, getCiIntegrityReportTool, } from "./r31-changes-pr-ci.tool.js";
138
- import { listProfilesTool, getProfileTool, getProfilesDoctorTool, listPluginLifecycleProfilesTool, getPluginLifecycleProfileTool, getPluginLifecycleProfileDoctorTool, } from "./r32-profiles.tool.js";
139
+ import { listProfilesTool, getProfileTool, getProfilesDoctorTool, } from "./r32-profiles.tool.js";
139
140
  import { getProjectCouplingReportTool } from "./r32-project-coupling.tool.js";
140
141
  import { getPackContributionsTool, getPackConflictsTool, } from "./r33-pack-contributions.tool.js";
141
142
  import { listConventionsTool, getConventionTool, getConventionsDoctorTool, } from "./r33-conventions.tool.js";
@@ -334,8 +335,8 @@ export const ALL_TOOLS = Object.freeze([
334
335
  getLanguageCacheStatusTool,
335
336
  getLanguageProfilesLiveTool,
336
337
  getChangedBoundaryReportTool,
337
- previewPluginRenameTool,
338
- previewPluginRemoveTool,
338
+ getDiffCheckReportTool,
339
+ getFileAdviceTool,
339
340
  listHelpersTool,
340
341
  getHelperTool,
341
342
  previewHelperPlanTool,
@@ -370,9 +371,6 @@ export const ALL_TOOLS = Object.freeze([
370
371
  listProfilesTool,
371
372
  getProfileTool,
372
373
  getProfilesDoctorTool,
373
- listPluginLifecycleProfilesTool,
374
- getPluginLifecycleProfileTool,
375
- getPluginLifecycleProfileDoctorTool,
376
374
  getProjectCouplingReportTool,
377
375
  getPackContributionsTool,
378
376
  getPackConflictsTool,
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Read-only MCP tool: get_diff_check_report.
3
+ *
4
+ * The MCP-side mirror of `shrk diff-check`. Same envelope, same
5
+ * verdict logic — the agent gets a single structured answer to "did
6
+ * my edits pass this project's boundary + import-hygiene gates?".
7
+ *
8
+ * Still read-only: this tool DOES NOT fix anything, even when it
9
+ * could trivially suggest the fix. The agent reads the envelope, then
10
+ * the human (or the agent, via a separate write-path tool) runs the
11
+ * fix on the CLI. Keeps the safety contract intact.
12
+ */
13
+ import type { IToolDefinition } from '../server/tool-definition.js';
14
+ export declare const getDiffCheckReportTool: IToolDefinition;
15
+ //# sourceMappingURL=diff-check.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-check.tool.d.ts","sourceRoot":"","sources":["../../src/tools/diff-check.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAyBpE,eAAO,MAAM,sBAAsB,EAAE,eAyHpC,CAAC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Read-only MCP tool: get_diff_check_report.
3
+ *
4
+ * The MCP-side mirror of `shrk diff-check`. Same envelope, same
5
+ * verdict logic — the agent gets a single structured answer to "did
6
+ * my edits pass this project's boundary + import-hygiene gates?".
7
+ *
8
+ * Still read-only: this tool DOES NOT fix anything, even when it
9
+ * could trivially suggest the fix. The agent reads the envelope, then
10
+ * the human (or the agent, via a separate write-path tool) runs the
11
+ * fix on the CLI. Keeps the safety contract intact.
12
+ */
13
+ import { buildImportHygieneReport, filterViolationsToChangedScope, resolveChangedFiles, } from '@shrkcrft/inspector';
14
+ import { evaluateBoundaries, loadTsconfigPaths, scanImports, } from '@shrkcrft/boundaries';
15
+ const SCHEMA = 'sharkcraft.diff-check/v1';
16
+ function resolveScopeFromInput(input, cwd) {
17
+ const staged = input.staged === true;
18
+ const since = typeof input.since === 'string' ? input.since : undefined;
19
+ const files = Array.isArray(input.files)
20
+ ? input.files.filter((f) => typeof f === 'string')
21
+ : [];
22
+ if (files.length > 0) {
23
+ return { mode: 'files', options: { projectRoot: cwd, files } };
24
+ }
25
+ if (staged) {
26
+ return { mode: 'staged', options: { projectRoot: cwd, staged: true } };
27
+ }
28
+ if (since) {
29
+ return { mode: 'since', options: { projectRoot: cwd, since } };
30
+ }
31
+ return { mode: 'worktree', options: { projectRoot: cwd, includeWorktree: true } };
32
+ }
33
+ export const getDiffCheckReportTool = {
34
+ name: 'get_diff_check_report',
35
+ description: 'Self-check the current git diff against this project\'s boundary + import-hygiene rules. Single-call composite of the boundary-check and import-hygiene engines, scoped to the changed files, with one verdict (ok | warnings | errors) and one nextAction line. Use after editing code so you can validate before declaring done. Read-only.',
36
+ inputSchema: {
37
+ type: 'object',
38
+ additionalProperties: false,
39
+ properties: {
40
+ staged: { type: 'boolean', description: 'Scope to staged changes only.' },
41
+ since: { type: 'string', description: 'Compare against ref (HEAD, origin/main, SHA).' },
42
+ files: {
43
+ type: 'array',
44
+ items: { type: 'string' },
45
+ description: 'Explicit file list (overrides --staged / --since).',
46
+ },
47
+ },
48
+ },
49
+ async handler(input, ctx) {
50
+ const cwd = ctx.cwd;
51
+ const { mode, options: scopeOptions } = resolveScopeFromInput(input, cwd);
52
+ const changed = resolveChangedFiles(scopeOptions);
53
+ const changedFiles = changed.files;
54
+ const rules = ctx.inspection.boundaryRegistry.list();
55
+ let boundaryBlock = {
56
+ ran: false,
57
+ rulesEvaluated: 0,
58
+ counts: { error: 0, warning: 0, info: 0 },
59
+ violations: [],
60
+ };
61
+ if (rules.length > 0 && changedFiles.length > 0) {
62
+ const scan = scanImports({ projectRoot: cwd });
63
+ const tsconfigPaths = loadTsconfigPaths(cwd);
64
+ const evalResult = evaluateBoundaries(scan, rules, {
65
+ ...(tsconfigPaths.aliases.size > 0 ? { tsconfigPaths } : {}),
66
+ });
67
+ const filtered = filterViolationsToChangedScope(evalResult.violations, scopeOptions);
68
+ boundaryBlock = {
69
+ ran: true,
70
+ rulesEvaluated: evalResult.rulesEvaluated,
71
+ counts: {
72
+ error: filtered.includedViolations.filter((v) => v.severity === 'error').length,
73
+ warning: filtered.includedViolations.filter((v) => v.severity === 'warning').length,
74
+ info: filtered.includedViolations.filter((v) => v.severity === 'info').length,
75
+ },
76
+ violations: filtered.includedViolations,
77
+ };
78
+ }
79
+ else if (rules.length > 0) {
80
+ boundaryBlock = { ...boundaryBlock, ran: true, rulesEvaluated: rules.length };
81
+ }
82
+ let importsBlock = {
83
+ ran: false,
84
+ verdict: 'skipped',
85
+ counts: {},
86
+ findings: [],
87
+ };
88
+ if (changedFiles.length > 0) {
89
+ const report = buildImportHygieneReport(cwd, { files: changedFiles });
90
+ importsBlock = {
91
+ ran: true,
92
+ verdict: report.verdict,
93
+ counts: report.counts ?? {},
94
+ findings: report.findings,
95
+ };
96
+ }
97
+ // Derive verdict — same logic as the CLI command, duplicated
98
+ // intentionally to keep the MCP tool self-contained (no CLI
99
+ // import — preserves the package dependency direction).
100
+ const bErr = boundaryBlock.counts.error;
101
+ const bWarn = boundaryBlock.counts.warning;
102
+ const iErr = importsBlock.verdict === 'errors' ? (importsBlock.counts.error ?? importsBlock.findings.length) : 0;
103
+ const iWarn = importsBlock.verdict === 'warnings' ? (importsBlock.counts.warning ?? importsBlock.findings.length) : 0;
104
+ let verdict;
105
+ let summary;
106
+ let nextAction;
107
+ if (changedFiles.length === 0) {
108
+ verdict = 'ok';
109
+ summary = 'No files changed in the current diff scope.';
110
+ nextAction =
111
+ 'Nothing to check. If you expected changes, verify the `staged` / `since` argument or save edits first.';
112
+ }
113
+ else if (bErr > 0 || iErr > 0) {
114
+ verdict = 'errors';
115
+ const parts = [];
116
+ if (bErr > 0)
117
+ parts.push(`${bErr} boundary violation${bErr === 1 ? '' : 's'}`);
118
+ if (iErr > 0)
119
+ parts.push(`${iErr} import-hygiene error${iErr === 1 ? '' : 's'}`);
120
+ summary = `Diff fails the gate: ${parts.join(', ')}.`;
121
+ nextAction =
122
+ 'Fix every error in `boundaries.violations` and `imports.findings` (each entry\'s `suggestedFix` shows the fix), then re-run.';
123
+ }
124
+ else if (bWarn > 0 || iWarn > 0) {
125
+ verdict = 'warnings';
126
+ const parts = [];
127
+ if (bWarn > 0)
128
+ parts.push(`${bWarn} boundary warning${bWarn === 1 ? '' : 's'}`);
129
+ if (iWarn > 0)
130
+ parts.push(`${iWarn} import-hygiene warning${iWarn === 1 ? '' : 's'}`);
131
+ summary = `Diff passes the gate with ${parts.join(', ')}.`;
132
+ nextAction = 'Safe to declare done. Review warnings if the diff touches a sensitive area.';
133
+ }
134
+ else {
135
+ verdict = 'ok';
136
+ summary = `Diff passes the gate (${changedFiles.length} file${changedFiles.length === 1 ? '' : 's'}, 0 violations).`;
137
+ nextAction = 'Safe to declare done.';
138
+ }
139
+ return {
140
+ text: `verdict=${verdict}. ${summary} ${nextAction}`,
141
+ data: {
142
+ schema: SCHEMA,
143
+ generatedAt: new Date().toISOString(),
144
+ scope: {
145
+ mode,
146
+ files: changedFiles,
147
+ fileCount: changedFiles.length,
148
+ },
149
+ boundaries: boundaryBlock,
150
+ imports: importsBlock,
151
+ verdict,
152
+ summary,
153
+ nextAction,
154
+ },
155
+ };
156
+ },
157
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Read-only MCP tool: get_file_advice.
3
+ *
4
+ * For a given file path, returns the rules, path conventions, boundary
5
+ * rules, and knowledge entries that apply to it. The agent equivalent
6
+ * of `shrk why <file>` — same engine (`buildWhyReport`), shaped for
7
+ * MCP consumption.
8
+ *
9
+ * Why this exists alongside `inspect_workspace` and the various
10
+ * `list_*` tools: agents tend to over-explore when given browsable
11
+ * catalogs. Asking "give me everything for this file" in one call
12
+ * keeps the prompt window tight and the answer focused — the agent
13
+ * doesn't have to discover which rule matches the file's path glob,
14
+ * which boundary rule constrains its imports, or which knowledge
15
+ * entry is the right one to read first.
16
+ *
17
+ * Use after a `shrk diff-check` flags a violation, or before editing
18
+ * an unfamiliar file. Read-only.
19
+ */
20
+ import type { IToolDefinition } from '../server/tool-definition.js';
21
+ export declare const getFileAdviceTool: IToolDefinition;
22
+ //# sourceMappingURL=file-advice.tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-advice.tool.d.ts","sourceRoot":"","sources":["../../src/tools/file-advice.tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,eAAO,MAAM,iBAAiB,EAAE,eAyC/B,CAAC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Read-only MCP tool: get_file_advice.
3
+ *
4
+ * For a given file path, returns the rules, path conventions, boundary
5
+ * rules, and knowledge entries that apply to it. The agent equivalent
6
+ * of `shrk why <file>` — same engine (`buildWhyReport`), shaped for
7
+ * MCP consumption.
8
+ *
9
+ * Why this exists alongside `inspect_workspace` and the various
10
+ * `list_*` tools: agents tend to over-explore when given browsable
11
+ * catalogs. Asking "give me everything for this file" in one call
12
+ * keeps the prompt window tight and the answer focused — the agent
13
+ * doesn't have to discover which rule matches the file's path glob,
14
+ * which boundary rule constrains its imports, or which knowledge
15
+ * entry is the right one to read first.
16
+ *
17
+ * Use after a `shrk diff-check` flags a violation, or before editing
18
+ * an unfamiliar file. Read-only.
19
+ */
20
+ import { buildWhyReport } from '@shrkcrft/inspector';
21
+ export const getFileAdviceTool = {
22
+ name: 'get_file_advice',
23
+ description: 'For a given file path, return the rules, path conventions, boundary rules, and knowledge entries that apply to it. Single-call replacement for browsing the registry to figure out what constrains one file. Read-only.',
24
+ inputSchema: {
25
+ type: 'object',
26
+ additionalProperties: false,
27
+ required: ['file'],
28
+ properties: {
29
+ file: {
30
+ type: 'string',
31
+ description: 'Path to the file (absolute, or relative to the project root). The file does not need to exist — path-string matching still works.',
32
+ },
33
+ limit: {
34
+ type: 'number',
35
+ description: 'Cap rules and knowledge entries returned (default 10).',
36
+ },
37
+ },
38
+ },
39
+ async handler(input, ctx) {
40
+ const file = typeof input.file === 'string' ? input.file : '';
41
+ if (!file) {
42
+ return {
43
+ text: 'Error: `file` argument is required.',
44
+ data: { error: 'missing-argument', argument: 'file' },
45
+ };
46
+ }
47
+ const limit = typeof input.limit === 'number' && input.limit > 0 ? Math.floor(input.limit) : 10;
48
+ const report = buildWhyReport({
49
+ inspection: ctx.inspection,
50
+ projectRoot: ctx.cwd,
51
+ target: file,
52
+ limit,
53
+ });
54
+ const summary = buildSummary(report);
55
+ return {
56
+ text: summary,
57
+ data: report,
58
+ };
59
+ },
60
+ };
61
+ function buildSummary(report) {
62
+ const lines = [];
63
+ lines.push(`File: ${report.target.relativePath} (${report.target.kind})`);
64
+ if (report.inferredPackage)
65
+ lines.push(`Package: ${report.inferredPackage}`);
66
+ if (report.inferredLayer)
67
+ lines.push(`Layer: ${report.inferredLayer}`);
68
+ const counts = {
69
+ rules: report.rules.length,
70
+ boundaries: report.boundaries.length,
71
+ paths: report.pathConventions.length,
72
+ knowledge: report.knowledge.length,
73
+ };
74
+ lines.push(`Matches: ${counts.rules} rule${counts.rules === 1 ? '' : 's'}, ` +
75
+ `${counts.boundaries} boundary rule${counts.boundaries === 1 ? '' : 's'}, ` +
76
+ `${counts.paths} path convention${counts.paths === 1 ? '' : 's'}, ` +
77
+ `${counts.knowledge} knowledge entr${counts.knowledge === 1 ? 'y' : 'ies'}.`);
78
+ if (counts.rules === 0 &&
79
+ counts.boundaries === 0 &&
80
+ counts.paths === 0 &&
81
+ counts.knowledge === 0) {
82
+ lines.push('No registry entries matched. The file may be outside the conventions, or the workspace may not have rules / paths defined yet.');
83
+ }
84
+ else {
85
+ lines.push('See `data` for the full per-category list.');
86
+ }
87
+ return lines.join('\n');
88
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Primary MCP tools — the ~30 tools advertised to a connected agent
3
+ * by default. Every tool in {@link ALL_TOOLS} stays callable (so an
4
+ * agent that already knows the name can use it), but `tools/list`
5
+ * only advertises the primary set. Smaller surface = better
6
+ * tool-selection accuracy for the agent.
7
+ *
8
+ * Picked to match the CLI's allowlist semantics: anything an agent
9
+ * could realistically reach for during a normal task — discovery,
10
+ * context, planning, validation — is in. Internal introspection
11
+ * (catalog dumps, fix-preview internals, drift baselines) is out;
12
+ * still callable, just not in the default tool list.
13
+ *
14
+ * Escape hatch: set `SHRK_MCP_FULL_TOOLS=1` to advertise the full
15
+ * catalog (useful when debugging an agent's tool selection).
16
+ */
17
+ export declare const PRIMARY_MCP_TOOLS: ReadonlySet<string>;
18
+ /**
19
+ * Should `tools/list` advertise the full catalog instead of the
20
+ * primary set? Driven by the env var so an agent operator can flip it
21
+ * without rebuilding.
22
+ */
23
+ export declare function shouldAdvertiseFullToolset(): boolean;
24
+ //# sourceMappingURL=primary-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-tools.d.ts","sourceRoot":"","sources":["../../src/tools/primary-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CA4ChD,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAGpD"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Primary MCP tools — the ~30 tools advertised to a connected agent
3
+ * by default. Every tool in {@link ALL_TOOLS} stays callable (so an
4
+ * agent that already knows the name can use it), but `tools/list`
5
+ * only advertises the primary set. Smaller surface = better
6
+ * tool-selection accuracy for the agent.
7
+ *
8
+ * Picked to match the CLI's allowlist semantics: anything an agent
9
+ * could realistically reach for during a normal task — discovery,
10
+ * context, planning, validation — is in. Internal introspection
11
+ * (catalog dumps, fix-preview internals, drift baselines) is out;
12
+ * still callable, just not in the default tool list.
13
+ *
14
+ * Escape hatch: set `SHRK_MCP_FULL_TOOLS=1` to advertise the full
15
+ * catalog (useful when debugging an agent's tool selection).
16
+ */
17
+ export const PRIMARY_MCP_TOOLS = new Set([
18
+ // Project orientation
19
+ 'inspect_workspace',
20
+ 'get_project_overview',
21
+ 'get_agent_instructions',
22
+ 'get_start_here',
23
+ // Context / task routing
24
+ 'get_relevant_context',
25
+ 'get_task_packet',
26
+ 'get_action_hints',
27
+ 'create_agent_brief',
28
+ 'get_relevant_rules',
29
+ 'explain_command',
30
+ // Browse the registries
31
+ 'list_knowledge',
32
+ 'get_knowledge',
33
+ 'list_rules',
34
+ 'get_rule',
35
+ 'list_path_conventions',
36
+ 'list_templates',
37
+ 'get_template',
38
+ 'list_pipelines',
39
+ 'get_pipeline',
40
+ 'list_presets',
41
+ 'get_preset',
42
+ 'list_packs',
43
+ 'get_pack',
44
+ // Safe code generation (plan-first)
45
+ 'create_generation_plan',
46
+ 'render_template_preview',
47
+ 'explain_generation_target',
48
+ // Validation gates (read-only)
49
+ 'check_boundaries',
50
+ 'get_diff_check_report',
51
+ 'get_file_advice',
52
+ 'get_architecture_constraints',
53
+ 'get_architecture_violations',
54
+ // Doctor / readiness
55
+ 'get_ai_readiness_report',
56
+ 'doctor_packs',
57
+ // Search
58
+ 'search_all',
59
+ 'search_knowledge',
60
+ 'search_commands',
61
+ ]);
62
+ /**
63
+ * Should `tools/list` advertise the full catalog instead of the
64
+ * primary set? Driven by the env var so an agent operator can flip it
65
+ * without rebuilding.
66
+ */
67
+ export function shouldAdvertiseFullToolset() {
68
+ const v = process.env.SHRK_MCP_FULL_TOOLS;
69
+ return v === '1' || v === 'true' || v === 'yes';
70
+ }
@@ -93,7 +93,7 @@ export const getCommandTaxonomyTool = {
93
93
  inputSchema: { type: 'object', properties: {}, additionalProperties: false },
94
94
  async handler() {
95
95
  // The CLI carries the canonical catalog; here we describe the surface
96
- // without importing it (no inspector → CLI dependency). Adopters can
96
+ // without importing it (no inspector → CLI dependency). Consumers can
97
97
  // run `shrk commands taxonomy` for the full grouped output.
98
98
  return {
99
99
  data: {
@@ -2,7 +2,4 @@ import type { IToolDefinition } from '../server/tool-definition.js';
2
2
  export declare const listProfilesTool: IToolDefinition;
3
3
  export declare const getProfileTool: IToolDefinition;
4
4
  export declare const getProfilesDoctorTool: IToolDefinition;
5
- export declare const listPluginLifecycleProfilesTool: IToolDefinition;
6
- export declare const getPluginLifecycleProfileTool: IToolDefinition;
7
- export declare const getPluginLifecycleProfileDoctorTool: IToolDefinition;
8
5
  //# sourceMappingURL=r32-profiles.tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"r32-profiles.tool.d.ts","sourceRoot":"","sources":["../../src/tools/r32-profiles.tool.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,eAAO,MAAM,gBAAgB,EAAE,eAe9B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,eAsB5B,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAOnC,CAAC;AAEF,eAAO,MAAM,+BAA+B,EAAE,eAO7C,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,eAmB3C,CAAC;AAEF,eAAO,MAAM,mCAAmC,EAAE,eAqBjD,CAAC"}
1
+ {"version":3,"file":"r32-profiles.tool.d.ts","sourceRoot":"","sources":["../../src/tools/r32-profiles.tool.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,eAAO,MAAM,gBAAgB,EAAE,eAe9B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,eAsB5B,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAOnC,CAAC"}
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Read-only MCP tools for profiles + lifecycle profiles.
2
+ * Read-only MCP tools for profiles.
3
3
  *
4
4
  * Engine never writes from MCP. These tools surface pack-contributed and
5
5
  * locally configured profiles so an agent can decide which one to pass to
6
- * `shrk plugin rename|remove --profile <id>` (the human runs the CLI).
6
+ * a profile-aware command (the human runs the CLI).
7
7
  */
8
- import { checkPluginLifecycleProfileHealth, findProfile, listProfileIssues, listProfiles, listPluginLifecycleProfiles, ProfileKind, } from '@shrkcrft/inspector';
8
+ import { findProfile, listProfileIssues, listProfiles, ProfileKind, } from '@shrkcrft/inspector';
9
9
  export const listProfilesTool = {
10
10
  name: 'list_profiles',
11
11
  description: 'List all pack-contributed and locally configured profiles. Read-only.',
@@ -54,54 +54,3 @@ export const getProfilesDoctorTool = {
54
54
  return { data: { issues: await listProfileIssues(ctx.inspection) } };
55
55
  },
56
56
  };
57
- export const listPluginLifecycleProfilesTool = {
58
- name: 'list_plugin_lifecycle_profiles',
59
- description: 'List registered plugin lifecycle profiles. Read-only.',
60
- inputSchema: { type: 'object', additionalProperties: false, properties: {} },
61
- async handler(_input, ctx) {
62
- return { data: await listPluginLifecycleProfiles(ctx.inspection) };
63
- },
64
- };
65
- export const getPluginLifecycleProfileTool = {
66
- name: 'get_plugin_lifecycle_profile',
67
- description: 'Get one plugin lifecycle profile by id. Read-only.',
68
- inputSchema: {
69
- type: 'object',
70
- additionalProperties: false,
71
- required: ['id'],
72
- properties: { id: { type: 'string' } },
73
- },
74
- async handler(input, ctx) {
75
- const id = typeof input.id === 'string' ? input.id : '';
76
- if (!id)
77
- return { isError: true, error: { code: 'invalid-input', message: 'id is required.' } };
78
- const entries = await listPluginLifecycleProfiles(ctx.inspection);
79
- const entry = entries.find((e) => e.profile.id === id);
80
- if (!entry) {
81
- return { isError: true, error: { code: 'not-found', message: `Unknown lifecycle profile "${id}".` } };
82
- }
83
- return { data: entry };
84
- },
85
- };
86
- export const getPluginLifecycleProfileDoctorTool = {
87
- name: 'get_plugin_lifecycle_profile_doctor',
88
- description: 'Health check for plugin lifecycle profiles. Read-only.',
89
- inputSchema: {
90
- type: 'object',
91
- additionalProperties: false,
92
- properties: { id: { type: 'string' } },
93
- },
94
- async handler(input, ctx) {
95
- const id = typeof input.id === 'string' ? input.id : undefined;
96
- const entries = await listPluginLifecycleProfiles(ctx.inspection);
97
- const targets = id ? entries.filter((e) => e.profile.id === id) : entries;
98
- if (id && targets.length === 0) {
99
- return { isError: true, error: { code: 'not-found', message: `Unknown lifecycle profile "${id}".` } };
100
- }
101
- const health = {};
102
- for (const e of targets) {
103
- health[e.profile.id] = checkPluginLifecycleProfileHealth(ctx.cwd, e.profile);
104
- }
105
- return { data: { health } };
106
- },
107
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shrkcrft/mcp-server",
3
- "version": "0.1.0-alpha.7",
3
+ "version": "0.1.0-alpha.8",
4
4
  "description": "SharkCraft MCP server: 25 tools over @modelcontextprotocol/sdk's stdio transport.",
5
5
  "license": "MIT",
6
6
  "author": "SharkCraft contributors",
@@ -44,20 +44,20 @@
44
44
  "typecheck": "tsc --noEmit -p tsconfig.json"
45
45
  },
46
46
  "dependencies": {
47
- "@shrkcrft/core": "^0.1.0-alpha.7",
48
- "@shrkcrft/config": "^0.1.0-alpha.7",
49
- "@shrkcrft/workspace": "^0.1.0-alpha.7",
50
- "@shrkcrft/knowledge": "^0.1.0-alpha.7",
51
- "@shrkcrft/context": "^0.1.0-alpha.7",
52
- "@shrkcrft/rules": "^0.1.0-alpha.7",
53
- "@shrkcrft/paths": "^0.1.0-alpha.7",
54
- "@shrkcrft/templates": "^0.1.0-alpha.7",
55
- "@shrkcrft/pipelines": "^0.1.0-alpha.7",
56
- "@shrkcrft/presets": "^0.1.0-alpha.7",
57
- "@shrkcrft/boundaries": "^0.1.0-alpha.7",
58
- "@shrkcrft/packs": "^0.1.0-alpha.7",
59
- "@shrkcrft/generator": "^0.1.0-alpha.7",
60
- "@shrkcrft/inspector": "^0.1.0-alpha.7",
47
+ "@shrkcrft/core": "^0.1.0-alpha.8",
48
+ "@shrkcrft/config": "^0.1.0-alpha.8",
49
+ "@shrkcrft/workspace": "^0.1.0-alpha.8",
50
+ "@shrkcrft/knowledge": "^0.1.0-alpha.8",
51
+ "@shrkcrft/context": "^0.1.0-alpha.8",
52
+ "@shrkcrft/rules": "^0.1.0-alpha.8",
53
+ "@shrkcrft/paths": "^0.1.0-alpha.8",
54
+ "@shrkcrft/templates": "^0.1.0-alpha.8",
55
+ "@shrkcrft/pipelines": "^0.1.0-alpha.8",
56
+ "@shrkcrft/presets": "^0.1.0-alpha.8",
57
+ "@shrkcrft/boundaries": "^0.1.0-alpha.8",
58
+ "@shrkcrft/packs": "^0.1.0-alpha.8",
59
+ "@shrkcrft/generator": "^0.1.0-alpha.8",
60
+ "@shrkcrft/inspector": "^0.1.0-alpha.8",
61
61
  "@modelcontextprotocol/sdk": "^1.0.0",
62
62
  "zod": "^3.25.0 || ^4.0.0"
63
63
  },
@@ -1,4 +0,0 @@
1
- import type { IToolDefinition } from '../server/tool-definition.js';
2
- export declare const previewPluginRenameTool: IToolDefinition;
3
- export declare const previewPluginRemoveTool: IToolDefinition;
4
- //# sourceMappingURL=r28-plugin-lifecycle.tool.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"r28-plugin-lifecycle.tool.d.ts","sourceRoot":"","sources":["../../src/tools/r28-plugin-lifecycle.tool.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAMpE,eAAO,MAAM,uBAAuB,EAAE,eA0CrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAuCrC,CAAC"}
@@ -1,94 +0,0 @@
1
- /**
2
- * Plan-only plugin lifecycle previews (profile-driven).
3
- *
4
- * preview_plugin_rename — returns the rename plan (replace ops + manual steps).
5
- * preview_plugin_remove — returns the destructive remove plan.
6
- *
7
- * Both tools never write source — the human runs `shrk plugin rename|remove`
8
- * to produce the saved plan and applies it via `shrk apply --verify-signature`.
9
- *
10
- * A `profile` input is required. If exactly one lifecycle profile is
11
- * registered, the tool implicitly uses it; otherwise the caller must supply
12
- * `profile`.
13
- */
14
- import { buildPluginRemovePlan, buildPluginRenamePlan, resolvePluginLifecycleProfile, } from '@shrkcrft/inspector';
15
- function nextHint(cmd) {
16
- return `Next: \`${cmd}\` (CLI is the only write path).`;
17
- }
18
- export const previewPluginRenameTool = {
19
- name: 'preview_plugin_rename',
20
- description: 'Preview a plugin rename plan. Read-only — returns the structured plan only. Requires a registered plugin lifecycle profile (pass `profile` if more than one is registered).',
21
- inputSchema: {
22
- type: 'object',
23
- additionalProperties: false,
24
- required: ['oldName', 'newName'],
25
- properties: {
26
- oldName: { type: 'string' },
27
- newName: { type: 'string' },
28
- profile: { type: 'string' },
29
- },
30
- },
31
- async handler(input, ctx) {
32
- const oldName = String(input.oldName ?? '');
33
- const newName = String(input.newName ?? '');
34
- const profileId = typeof input.profile === 'string' ? input.profile : undefined;
35
- const resolved = await resolvePluginLifecycleProfile(ctx.inspection, {
36
- profileId,
37
- allowSingleDefault: true,
38
- });
39
- if (!resolved.entry) {
40
- return {
41
- isError: true,
42
- error: { code: 'profile-required', message: resolved.error ?? 'Profile resolution failed' },
43
- data: { availableProfiles: resolved.availableIds },
44
- };
45
- }
46
- const plan = buildPluginRenamePlan({
47
- projectRoot: ctx.cwd,
48
- profile: resolved.entry.profile,
49
- oldName,
50
- newName,
51
- });
52
- return {
53
- text: nextHint(`shrk plugin rename ${oldName} ${newName} --profile ${resolved.entry.profile.id} --output /tmp/plan.json`),
54
- data: plan,
55
- };
56
- },
57
- };
58
- export const previewPluginRemoveTool = {
59
- name: 'preview_plugin_remove',
60
- description: 'Preview a destructive plugin remove plan. Read-only — returns the structured plan only. Requires a registered plugin lifecycle profile (pass `profile` if more than one is registered).',
61
- inputSchema: {
62
- type: 'object',
63
- additionalProperties: false,
64
- required: ['name'],
65
- properties: {
66
- name: { type: 'string' },
67
- profile: { type: 'string' },
68
- },
69
- },
70
- async handler(input, ctx) {
71
- const name = String(input.name ?? '');
72
- const profileId = typeof input.profile === 'string' ? input.profile : undefined;
73
- const resolved = await resolvePluginLifecycleProfile(ctx.inspection, {
74
- profileId,
75
- allowSingleDefault: true,
76
- });
77
- if (!resolved.entry) {
78
- return {
79
- isError: true,
80
- error: { code: 'profile-required', message: resolved.error ?? 'Profile resolution failed' },
81
- data: { availableProfiles: resolved.availableIds },
82
- };
83
- }
84
- const plan = buildPluginRemovePlan({
85
- projectRoot: ctx.cwd,
86
- profile: resolved.entry.profile,
87
- oldName: name,
88
- });
89
- return {
90
- text: nextHint(`shrk plugin remove ${name} --profile ${resolved.entry.profile.id} --output /tmp/plan.json`),
91
- data: plan,
92
- };
93
- },
94
- };