hoi4-agent-tools 3.0.9 → 3.2.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/CHANGELOG.md +32 -1
- package/README.md +10 -2
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/analysis-evidence.js +9 -0
- package/dist/hoi4_agent_tools/core/analysis-job-operations.js +38 -0
- package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +16 -0
- package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
- package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
- package/dist/hoi4_agent_tools/core/engine.js +48 -5
- package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
- package/dist/hoi4_agent_tools/core/impact-comparison.js +49 -0
- package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
- package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -0
- package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
- package/dist/hoi4_agent_tools/core/index.js +47 -4
- package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
- package/dist/hoi4_agent_tools/core/job-service.js +112 -0
- package/dist/hoi4_agent_tools/core/job-store.js +641 -0
- package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +202 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +64 -0
- package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
- package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
- package/dist/hoi4_agent_tools/core/operation-tasks.js +387 -0
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
- package/dist/hoi4_agent_tools/core/result.js +1 -0
- package/dist/hoi4_agent_tools/core/scanner.js +15 -75
- package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
- package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
- package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
- package/dist/hoi4_agent_tools/core/source-overlay.js +125 -0
- package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
- package/dist/hoi4_agent_tools/core/transactions.js +80 -27
- package/dist/hoi4_agent_tools/decision/ai.js +104 -0
- package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
- package/dist/hoi4_agent_tools/decision/cost.js +177 -0
- package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
- package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
- package/dist/hoi4_agent_tools/decision/service.js +172 -0
- package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
- package/dist/hoi4_agent_tools/event/graph.js +214 -290
- package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
- package/dist/hoi4_agent_tools/event/operations.js +179 -0
- package/dist/hoi4_agent_tools/event/service.js +67 -15
- package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
- package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
- package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
- package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
- package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
- package/dist/hoi4_agent_tools/impact/scenario-suites.js +74 -0
- package/dist/hoi4_agent_tools/impact/service.js +127 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
- package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
- package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
- package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js +18 -6
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
- package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
- package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
- package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +45 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
- package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +16 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
- package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
- package/dist/hoi4_agent_tools/mcp/tools/analysis.js +65 -0
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
- package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
- package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
- package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
- package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
- package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
- package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
- package/dist/hoi4_agent_tools/probability/operations.js +183 -0
- package/dist/hoi4_agent_tools/probability/service.js +24 -3
- package/dist/hoi4_agent_tools/schemas/analysis.js +228 -0
- package/dist/hoi4_agent_tools/schemas/common.js +2 -0
- package/dist/hoi4_agent_tools/schemas/event.js +72 -1
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
- package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
- package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
- package/dist/hoi4_agent_tools/technology/graph.js +83 -78
- package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
- package/dist/hoi4_agent_tools/technology/operations.js +125 -0
- package/dist/hoi4_agent_tools/technology/service.js +89 -15
- package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +3 -0
- package/docs/analysis.md +100 -0
- package/docs/development.md +2 -0
- package/docs/events.md +7 -2
- package/docs/helper-expansion.md +78 -0
- package/docs/http.md +2 -0
- package/docs/jobs.md +86 -0
- package/docs/technology.md +6 -2
- package/package.json +15 -6
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/decision-inspect-input.schema.json +290 -0
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +44 -0
- package/schemas/helper-expansion-summary.schema.json +70 -0
- package/schemas/impact-inspect-input.schema.json +203 -0
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/server.json +3 -3
|
@@ -1,83 +1,22 @@
|
|
|
1
|
+
import { registerLegacyTaskTools } from '../server/legacy-task-tools.js';
|
|
1
2
|
import { z } from 'zod/v4';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { IdleCacheLifetime } from '../../core/idle-cache-lifetime.js';
|
|
5
|
-
import { emptyServiceResult } from '../../core/result.js';
|
|
6
|
-
import { workspaceIdSchema } from '../../schemas/common.js';
|
|
7
|
-
import { technologyDefectClassSchema, technologyDirectionSchema, technologyGraphReferenceSchema, technologyIdSchema, technologyImpactSchema, technologyProposedSourceSchema, technologyRenderViewSchema, technologyUnlockKindSchema, } from '../../schemas/technology.js';
|
|
8
|
-
import { TechnologyTreeViewer, technologyDiagnostics, } from '../../technology/service.js';
|
|
3
|
+
import { helperExpansionRequestSchema, helperExpansionSummarySchema, } from '../../schemas/helper-expansion.js';
|
|
4
|
+
import { technologyAnalysisModeSchema, technologyCompareRequestSchema, technologyGraphReferenceSchema, technologyImpactSchema, technologyInspectRequestSchema, technologyProposedSourceSchema, technologyRenderRequestSchema, technologyRenderViewSchema, validateTechnologyCompareRequest, validateTechnologyInspectRequest, } from '../../schemas/technology.js';
|
|
9
5
|
import { compactValidatedInputSchema } from '../server/context-schemas.js';
|
|
10
6
|
import { nonNegativeIntegerSchema, sha256Schema } from '../server/output-schemas.js';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
import { resolveServerWorkspaceId } from '../server/base-tools.js';
|
|
14
|
-
const technologyMode = z.enum([
|
|
15
|
-
'scan',
|
|
16
|
-
'folders',
|
|
17
|
-
'trace',
|
|
18
|
-
'explain',
|
|
19
|
-
'unlocks',
|
|
20
|
-
'bonus_coverage',
|
|
21
|
-
'lint',
|
|
22
|
-
'impact',
|
|
23
|
-
]);
|
|
24
|
-
const inspectInput = z
|
|
7
|
+
import { strictOperationResultSchema } from '../server/result.js';
|
|
8
|
+
const inspectInputSchema = z
|
|
25
9
|
.object({
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
folderId: technologyIdSchema.optional(),
|
|
29
|
-
technologyId: technologyIdSchema.optional(),
|
|
30
|
-
categoryId: technologyIdSchema.optional(),
|
|
31
|
-
targetKind: technologyUnlockKindSchema.optional(),
|
|
32
|
-
targetId: technologyIdSchema.optional(),
|
|
33
|
-
direction: technologyDirectionSchema.optional(),
|
|
34
|
-
maxDepth: z.number().int().min(1).max(256).optional(),
|
|
35
|
-
maxNodes: z.number().int().min(1).max(25_000).optional(),
|
|
36
|
-
includeSubTechnologies: z.boolean().optional(),
|
|
37
|
-
classifications: z.array(technologyDefectClassSchema).max(4).optional(),
|
|
38
|
-
codes: z.array(z.string().min(1).max(256)).max(100).optional(),
|
|
10
|
+
...technologyInspectRequestSchema.shape,
|
|
11
|
+
helperExpansion: compactValidatedInputSchema(helperExpansionRequestSchema, 'Bounded helper paths; opaque continuationUri resumes the same source and roots. See docs/technology.md.').optional(),
|
|
39
12
|
impact: compactValidatedInputSchema(technologyImpactSchema, 'Rename or removal subject.').optional(),
|
|
40
|
-
refresh: z.boolean().optional(),
|
|
41
13
|
})
|
|
42
14
|
.strict()
|
|
43
|
-
.superRefine(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
code: 'custom',
|
|
47
|
-
path: ['technologyId'],
|
|
48
|
-
message: `${value.mode} requires technologyId`,
|
|
49
|
-
});
|
|
50
|
-
if (value.mode === 'impact' && value.impact === undefined)
|
|
51
|
-
context.addIssue({
|
|
52
|
-
code: 'custom',
|
|
53
|
-
path: ['impact'],
|
|
54
|
-
message: 'Impact mode requires impact',
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
const renderInput = z
|
|
15
|
+
.superRefine(validateTechnologyInspectRequest);
|
|
16
|
+
const renderInputSchema = z.object({ ...technologyRenderRequestSchema.shape }).strict();
|
|
17
|
+
const compareInputSchema = z
|
|
58
18
|
.object({
|
|
59
|
-
|
|
60
|
-
view: technologyRenderViewSchema.exclude(['comparison']),
|
|
61
|
-
folderId: technologyIdSchema.optional(),
|
|
62
|
-
technologyId: technologyIdSchema.optional(),
|
|
63
|
-
categoryId: technologyIdSchema.optional(),
|
|
64
|
-
targetId: technologyIdSchema.optional(),
|
|
65
|
-
maxNodes: z.number().int().min(1).max(2_000).optional(),
|
|
66
|
-
includeHtml: z.boolean().optional(),
|
|
67
|
-
refresh: z.boolean().optional(),
|
|
68
|
-
})
|
|
69
|
-
.strict()
|
|
70
|
-
.superRefine((value, context) => {
|
|
71
|
-
if (value.view === 'folder' && value.folderId === undefined)
|
|
72
|
-
context.addIssue({
|
|
73
|
-
code: 'custom',
|
|
74
|
-
path: ['folderId'],
|
|
75
|
-
message: 'Folder view requires folderId',
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
const compareInput = z
|
|
79
|
-
.object({
|
|
80
|
-
workspaceId: workspaceIdSchema,
|
|
19
|
+
...technologyCompareRequestSchema.shape,
|
|
81
20
|
before: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Revision or graph resource.').optional(),
|
|
82
21
|
after: compactValidatedInputSchema(technologyGraphReferenceSchema, 'Revision or graph resource.').optional(),
|
|
83
22
|
proposedSources: z
|
|
@@ -85,19 +24,9 @@ const compareInput = z
|
|
|
85
24
|
.min(1)
|
|
86
25
|
.max(128)
|
|
87
26
|
.optional(),
|
|
88
|
-
render: z.boolean().optional(),
|
|
89
|
-
maxRenderNodes: z.number().int().min(1).max(2_000).optional(),
|
|
90
|
-
refresh: z.boolean().optional(),
|
|
91
27
|
})
|
|
92
28
|
.strict()
|
|
93
|
-
.superRefine(
|
|
94
|
-
if (value.after !== undefined && value.proposedSources !== undefined)
|
|
95
|
-
context.addIssue({
|
|
96
|
-
code: 'custom',
|
|
97
|
-
path: ['after'],
|
|
98
|
-
message: 'after conflicts with proposedSources',
|
|
99
|
-
});
|
|
100
|
-
});
|
|
29
|
+
.superRefine(validateTechnologyCompareRequest);
|
|
101
30
|
const countsSchema = z
|
|
102
31
|
.object({
|
|
103
32
|
technologies: nonNegativeIntegerSchema,
|
|
@@ -112,15 +41,16 @@ const countsSchema = z
|
|
|
112
41
|
artifacts: nonNegativeIntegerSchema,
|
|
113
42
|
})
|
|
114
43
|
.strict();
|
|
115
|
-
const
|
|
44
|
+
const analysisOutputSchema = strictOperationResultSchema(z
|
|
116
45
|
.object({
|
|
117
|
-
mode:
|
|
46
|
+
mode: technologyAnalysisModeSchema,
|
|
118
47
|
revision: sha256Schema,
|
|
119
48
|
graphHash: sha256Schema,
|
|
120
49
|
counts: countsSchema,
|
|
50
|
+
helperExpansion: helperExpansionSummarySchema.optional(),
|
|
121
51
|
})
|
|
122
52
|
.strict());
|
|
123
|
-
const
|
|
53
|
+
const renderOutputSchema = strictOperationResultSchema(z
|
|
124
54
|
.object({
|
|
125
55
|
view: technologyRenderViewSchema,
|
|
126
56
|
revision: sha256Schema,
|
|
@@ -139,7 +69,7 @@ const renderOutput = strictOperationResultSchema(z
|
|
|
139
69
|
sourceAccurate: z.boolean(),
|
|
140
70
|
})
|
|
141
71
|
.strict());
|
|
142
|
-
const
|
|
72
|
+
const compareOutputSchema = strictOperationResultSchema(z
|
|
143
73
|
.object({
|
|
144
74
|
beforeRevision: sha256Schema,
|
|
145
75
|
afterRevision: sha256Schema,
|
|
@@ -155,232 +85,38 @@ const compareOutput = strictOperationResultSchema(z
|
|
|
155
85
|
.optional(),
|
|
156
86
|
})
|
|
157
87
|
.strict());
|
|
158
|
-
const
|
|
88
|
+
const readOnlyTechnologyTool = {
|
|
159
89
|
readOnlyHint: true,
|
|
160
90
|
destructiveHint: false,
|
|
161
91
|
idempotentHint: true,
|
|
162
92
|
openWorldHint: false,
|
|
163
93
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
analysisMode: graph.analysisMode ?? 'full',
|
|
168
|
-
workspaceIdentity: graph.workspaceIdentity,
|
|
169
|
-
revision: graph.revision,
|
|
170
|
-
statistics: graph.statistics,
|
|
171
|
-
sourceHashes: graph.sourceHashes,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
function counts(graph, artifacts) {
|
|
175
|
-
return {
|
|
176
|
-
technologies: graph.statistics.technologyCount,
|
|
177
|
-
legacyDoctrines: graph.statistics.legacyDoctrineCount,
|
|
178
|
-
folders: graph.statistics.folderCount,
|
|
179
|
-
placements: graph.statistics.placementCount,
|
|
180
|
-
edges: graph.edges.length,
|
|
181
|
-
unlocks: graph.statistics.unlockCount,
|
|
182
|
-
references: graph.statistics.externalReferenceCount,
|
|
183
|
-
issues: graph.statistics.issueCount,
|
|
184
|
-
unresolved: graph.statistics.unresolvedCount,
|
|
185
|
-
artifacts,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
function validation(graph) {
|
|
189
|
-
const blocking = technologyDiagnostics(graph).filter(({ severity }) => severity === 'error' || severity === 'blocker').length;
|
|
190
|
-
return {
|
|
191
|
-
passed: graph.complete && blocking === 0,
|
|
192
|
-
checks: [
|
|
193
|
-
{
|
|
194
|
-
id: 'technology-analysis',
|
|
195
|
-
passed: graph.complete && blocking === 0,
|
|
196
|
-
message: graph.complete
|
|
197
|
-
? `${blocking} blocking technology diagnostics; full evidence is linked`
|
|
198
|
-
: graph.analysisMode === 'focused'
|
|
199
|
-
? 'Helper projections were deferred for this large workspace; direct evidence is linked'
|
|
200
|
-
: `${graph.skippedSourceCount} source(s) were skipped; full evidence is linked`,
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
function analysisRequest(input, workspaceId, context, signal) {
|
|
206
|
-
const impact = input.impact === undefined
|
|
207
|
-
? undefined
|
|
208
|
-
: {
|
|
209
|
-
kind: input.impact.kind,
|
|
210
|
-
id: input.impact.id,
|
|
211
|
-
operation: input.impact.operation,
|
|
212
|
-
...(input.impact.replacementId === undefined
|
|
213
|
-
? {}
|
|
214
|
-
: { replacementId: input.impact.replacementId }),
|
|
215
|
-
};
|
|
216
|
-
const values = {
|
|
217
|
-
folderId: input.folderId,
|
|
218
|
-
technologyId: input.technologyId,
|
|
219
|
-
categoryId: input.categoryId,
|
|
220
|
-
targetKind: input.targetKind,
|
|
221
|
-
targetId: input.targetId,
|
|
222
|
-
direction: input.direction,
|
|
223
|
-
maxDepth: input.maxDepth,
|
|
224
|
-
maxNodes: input.maxNodes,
|
|
225
|
-
includeSubTechnologies: input.includeSubTechnologies,
|
|
226
|
-
classifications: input.classifications,
|
|
227
|
-
codes: input.codes,
|
|
228
|
-
impact,
|
|
229
|
-
refresh: input.refresh,
|
|
230
|
-
};
|
|
231
|
-
const compact = Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
232
|
-
return {
|
|
233
|
-
workspaceId,
|
|
234
|
-
mode: input.mode,
|
|
235
|
-
...compact,
|
|
236
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
237
|
-
signal,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
export function registerTechnologyTools(server, engine, context) {
|
|
241
|
-
const viewer = new TechnologyTreeViewer(engine);
|
|
242
|
-
const cacheLifetime = new IdleCacheLifetime(() => viewer.clearCaches());
|
|
243
|
-
server.registerTool('hoi4.tech_inspect', {
|
|
94
|
+
export const technologyTaskTools = [
|
|
95
|
+
{
|
|
96
|
+
name: 'hoi4.tech_inspect',
|
|
244
97
|
title: 'Inspect technology trees',
|
|
245
98
|
description: 'Scan, discover folders, trace, explain, inspect unlocks or bonuses, lint, and assess impact.',
|
|
246
|
-
inputSchema:
|
|
247
|
-
outputSchema:
|
|
248
|
-
annotations:
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
const progress = progressReporter(extra);
|
|
254
|
-
await progress.report(0, 2, 'Analyzing technologies');
|
|
255
|
-
const output = await viewer.analyze(analysisRequest(input, workspaceId, context, progress.signal));
|
|
256
|
-
const result = emptyServiceResult(workspaceId, {
|
|
257
|
-
mode: input.mode,
|
|
258
|
-
revision: output.graph.revision,
|
|
259
|
-
graphHash: graphHash(output.graph),
|
|
260
|
-
counts: counts(output.graph, output.artifacts.length),
|
|
261
|
-
});
|
|
262
|
-
result.code = output.graph.complete ? 'TECH_INSPECTED' : 'TECH_INSPECTED_PARTIAL';
|
|
263
|
-
setInlineFilesScanned(result, output.graph.filesScanned);
|
|
264
|
-
result.artifacts = output.artifacts.map(publicArtifactLink);
|
|
265
|
-
result.validation = validation(output.graph);
|
|
266
|
-
await progress.report(2, 2, 'Technology analysis complete');
|
|
267
|
-
return toolResult(result);
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
return errorResult(error, workspaceId);
|
|
271
|
-
}
|
|
272
|
-
finally {
|
|
273
|
-
releaseCaches();
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
server.registerTool('hoi4.tech_render', {
|
|
99
|
+
inputSchema: inspectInputSchema,
|
|
100
|
+
outputSchema: analysisOutputSchema,
|
|
101
|
+
annotations: readOnlyTechnologyTool,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'hoi4.tech_render',
|
|
277
105
|
title: 'Render technology trees',
|
|
278
106
|
description: 'Render source-linked JSON, SVG, PNG, and optional HTML technology views.',
|
|
279
|
-
inputSchema:
|
|
280
|
-
outputSchema:
|
|
281
|
-
annotations:
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
const progress = progressReporter(extra);
|
|
287
|
-
const request = {
|
|
288
|
-
workspaceId,
|
|
289
|
-
view: input.view,
|
|
290
|
-
...(input.folderId === undefined ? {} : { folderId: input.folderId }),
|
|
291
|
-
...(input.technologyId === undefined ? {} : { technologyId: input.technologyId }),
|
|
292
|
-
...(input.categoryId === undefined ? {} : { categoryId: input.categoryId }),
|
|
293
|
-
...(input.targetId === undefined ? {} : { targetId: input.targetId }),
|
|
294
|
-
...(input.maxNodes === undefined ? {} : { maxNodes: input.maxNodes }),
|
|
295
|
-
...(input.includeHtml === undefined ? {} : { includeHtml: input.includeHtml }),
|
|
296
|
-
...(input.refresh === undefined ? {} : { refresh: input.refresh }),
|
|
297
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
298
|
-
signal: progress.signal,
|
|
299
|
-
};
|
|
300
|
-
const output = await viewer.renderAndStore(request);
|
|
301
|
-
const result = emptyServiceResult(workspaceId, {
|
|
302
|
-
view: output.render.view,
|
|
303
|
-
revision: output.graph.revision,
|
|
304
|
-
graphHash: graphHash(output.graph),
|
|
305
|
-
hashes: output.render.hashes,
|
|
306
|
-
selectedNodes: output.render.selectedIds.length,
|
|
307
|
-
omittedNodes: output.render.omittedNodeCount,
|
|
308
|
-
focusedRenders: output.focused.length,
|
|
309
|
-
sourceAccurate: output.render.sourceAccurate,
|
|
310
|
-
});
|
|
311
|
-
result.code = output.graph.complete ? 'TECH_RENDERED' : 'TECH_RENDERED_PARTIAL';
|
|
312
|
-
setInlineFilesScanned(result, output.graph.filesScanned);
|
|
313
|
-
result.artifacts = output.artifacts.map(publicArtifactLink);
|
|
314
|
-
result.validation = validation(output.graph);
|
|
315
|
-
return toolResult(result);
|
|
316
|
-
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
return errorResult(error, workspaceId);
|
|
319
|
-
}
|
|
320
|
-
finally {
|
|
321
|
-
releaseCaches();
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
server.registerTool('hoi4.tech_compare', {
|
|
107
|
+
inputSchema: renderInputSchema,
|
|
108
|
+
outputSchema: renderOutputSchema,
|
|
109
|
+
annotations: readOnlyTechnologyTool,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'hoi4.tech_compare',
|
|
325
113
|
title: 'Compare technology trees',
|
|
326
114
|
description: 'Compare cached, resource-backed, current, or proposed source graphs without writes.',
|
|
327
|
-
inputSchema:
|
|
328
|
-
outputSchema:
|
|
329
|
-
annotations:
|
|
330
|
-
},
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const progress = progressReporter(extra);
|
|
335
|
-
const request = {
|
|
336
|
-
workspaceId,
|
|
337
|
-
...(input.before === undefined
|
|
338
|
-
? {}
|
|
339
|
-
: { before: input.before }),
|
|
340
|
-
...(input.after === undefined
|
|
341
|
-
? {}
|
|
342
|
-
: { after: input.after }),
|
|
343
|
-
...(input.proposedSources === undefined
|
|
344
|
-
? {}
|
|
345
|
-
: {
|
|
346
|
-
proposedSources: input.proposedSources,
|
|
347
|
-
}),
|
|
348
|
-
...(input.render === undefined ? {} : { render: input.render }),
|
|
349
|
-
...(input.maxRenderNodes === undefined ? {} : { maxRenderNodes: input.maxRenderNodes }),
|
|
350
|
-
...(input.refresh === undefined ? {} : { refresh: input.refresh }),
|
|
351
|
-
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
352
|
-
signal: progress.signal,
|
|
353
|
-
};
|
|
354
|
-
const output = await viewer.compareAndStore(request);
|
|
355
|
-
const comparison = output.comparison;
|
|
356
|
-
const result = emptyServiceResult(workspaceId, {
|
|
357
|
-
beforeRevision: comparison.beforeRevision,
|
|
358
|
-
afterRevision: comparison.afterRevision,
|
|
359
|
-
added: comparison.technologies.added.length,
|
|
360
|
-
removed: comparison.technologies.removed.length,
|
|
361
|
-
renamed: comparison.technologies.renamed.length,
|
|
362
|
-
moved: comparison.technologies.moved.length,
|
|
363
|
-
regressions: comparison.regressions.length,
|
|
364
|
-
artifacts: output.artifacts.length,
|
|
365
|
-
...(output.render === undefined ? {} : { renderHashes: output.render.hashes }),
|
|
366
|
-
});
|
|
367
|
-
result.code =
|
|
368
|
-
output.before.complete && output.after.complete
|
|
369
|
-
? 'TECH_COMPARED'
|
|
370
|
-
: 'TECH_COMPARED_PARTIAL';
|
|
371
|
-
setInlineFilesScanned(result, [...new Set([...output.before.filesScanned, ...output.after.filesScanned])].sort(compareCodeUnits));
|
|
372
|
-
result.artifacts = output.artifacts.map(publicArtifactLink);
|
|
373
|
-
result.validation = {
|
|
374
|
-
passed: validation(output.before).passed && validation(output.after).passed,
|
|
375
|
-
checks: [...validation(output.before).checks, ...validation(output.after).checks],
|
|
376
|
-
};
|
|
377
|
-
return toolResult(result);
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
return errorResult(error, workspaceId);
|
|
381
|
-
}
|
|
382
|
-
finally {
|
|
383
|
-
releaseCaches();
|
|
384
|
-
}
|
|
385
|
-
});
|
|
115
|
+
inputSchema: compareInputSchema,
|
|
116
|
+
outputSchema: compareOutputSchema,
|
|
117
|
+
annotations: readOnlyTechnologyTool,
|
|
118
|
+
},
|
|
119
|
+
];
|
|
120
|
+
export function registerTechnologyTools(server) {
|
|
121
|
+
registerLegacyTaskTools(server, technologyTaskTools);
|
|
386
122
|
}
|
|
@@ -2,6 +2,8 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { isIP } from 'node:net';
|
|
3
3
|
import { TextDecoder } from 'node:util';
|
|
4
4
|
import express, {} from 'express';
|
|
5
|
+
import { toNodeHandler, toWebRequest } from '@modelcontextprotocol/node';
|
|
6
|
+
import { createMcpHandler, isLegacyRequest } from '@modelcontextprotocol/server';
|
|
5
7
|
import { hostHeaderValidation } from '@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js';
|
|
6
8
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
7
9
|
import { isInitializeRequest, SUPPORTED_PROTOCOL_VERSIONS, } from '@modelcontextprotocol/sdk/types.js';
|
|
@@ -9,6 +11,7 @@ import { HTTP_MAX_AGGREGATE_BODY_BYTES } from '../../core/configuration.js';
|
|
|
9
11
|
import { canonicalJson } from '../../core/canonical.js';
|
|
10
12
|
import { authenticate, bearerChallenge, HttpAuthError, protectedResourceMetadata, validateHttpDeployment, } from '../security/auth.js';
|
|
11
13
|
import { BoundedEventStore, SharedEventStoreBudget } from './event-store.js';
|
|
14
|
+
import { createModernOperationServer } from '../server/create-modern-operations.js';
|
|
12
15
|
import { FinalProtocolTransport } from './protocol-gate.js';
|
|
13
16
|
const WRITE_SCOPED_TOOLS = new Set(['hoi4.focus_rewrite', 'hoi4.gui_rewrite', 'hoi4.map_rewrite']);
|
|
14
17
|
const MCP_ALLOWED_METHODS = 'GET, POST, DELETE';
|
|
@@ -18,6 +21,8 @@ const SINGLETON_SECURITY_HEADERS = new Set([
|
|
|
18
21
|
'content-type',
|
|
19
22
|
'host',
|
|
20
23
|
'last-event-id',
|
|
24
|
+
'mcp-method',
|
|
25
|
+
'mcp-name',
|
|
21
26
|
'mcp-protocol-version',
|
|
22
27
|
'mcp-session-id',
|
|
23
28
|
'origin',
|
|
@@ -211,7 +216,8 @@ function rejectAmbiguousSecurityHeaders(request, response, next) {
|
|
|
211
216
|
const counts = new Map();
|
|
212
217
|
for (let index = 0; index < request.rawHeaders.length; index += 2) {
|
|
213
218
|
const name = request.rawHeaders[index]?.toLowerCase();
|
|
214
|
-
if (name !== undefined &&
|
|
219
|
+
if (name !== undefined &&
|
|
220
|
+
(SINGLETON_SECURITY_HEADERS.has(name) || name.startsWith('mcp-param-'))) {
|
|
215
221
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
216
222
|
}
|
|
217
223
|
}
|
|
@@ -437,6 +443,51 @@ export async function startHttpServer(engine, configuration, serverFactory) {
|
|
|
437
443
|
sendAuthError(response, configuration, authError);
|
|
438
444
|
}
|
|
439
445
|
};
|
|
446
|
+
const routeModernRequest = async (request, response) => {
|
|
447
|
+
// Preserve the legacy adapter's response contract for every claim-less request, including
|
|
448
|
+
// malformed JSON-RPC bodies. Explicit modern claims alone enter the SDK's strict ladder.
|
|
449
|
+
const body = request.body;
|
|
450
|
+
const params = isRecord(body) && isRecord(body.params) ? body.params : undefined;
|
|
451
|
+
const metadata = params !== undefined && isRecord(params._meta) ? params._meta : undefined;
|
|
452
|
+
const modernClaim = (isRecord(body) && body.method === 'server/discover') ||
|
|
453
|
+
request.headers['mcp-protocol-version'] === '2026-07-28' ||
|
|
454
|
+
request.headers['mcp-method'] !== undefined ||
|
|
455
|
+
request.headers['mcp-name'] !== undefined ||
|
|
456
|
+
Object.keys(request.headers).some((name) => name.startsWith('mcp-param-')) ||
|
|
457
|
+
(metadata !== undefined &&
|
|
458
|
+
Object.keys(metadata).some((name) => name.startsWith('io.modelcontextprotocol/')));
|
|
459
|
+
if (!modernClaim)
|
|
460
|
+
return false;
|
|
461
|
+
// An explicit malformed modern claim belongs to modern validation, never to a legacy session.
|
|
462
|
+
// The Node SDK helper concatenates Host and req.url. A validated absolute-form target
|
|
463
|
+
// must therefore be changed to origin-form only while this SDK adapter consumes it.
|
|
464
|
+
const originalUrl = request.url;
|
|
465
|
+
if (!originalUrl.startsWith('/')) {
|
|
466
|
+
const target = new URL(originalUrl);
|
|
467
|
+
request.url = `${target.pathname}${target.search}`;
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
const probe = await toWebRequest(request, request.body);
|
|
471
|
+
if (await isLegacyRequest(probe, request.body))
|
|
472
|
+
return false;
|
|
473
|
+
const principal = request.authPrincipal;
|
|
474
|
+
if (principal === undefined) {
|
|
475
|
+
jsonRpcError(response, 401, 'Authentication context is missing');
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
// The only principal and scopes given to the modern core come from the authenticated
|
|
479
|
+
// middleware. No request envelope or caller-authored metadata can substitute for them.
|
|
480
|
+
const modern = createMcpHandler(() => createModernOperationServer(engine, {
|
|
481
|
+
principal: principal.principal,
|
|
482
|
+
scopes: [...principal.scopes],
|
|
483
|
+
}), { legacy: 'reject', responseMode: 'auto' });
|
|
484
|
+
await toNodeHandler(modern)(request, response, request.body);
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
finally {
|
|
488
|
+
request.url = originalUrl;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
440
491
|
app.all('/mcp', async (request, response, next) => {
|
|
441
492
|
if (['GET', 'POST', 'DELETE'].includes(request.method)) {
|
|
442
493
|
next();
|
|
@@ -575,6 +626,8 @@ export async function startHttpServer(engine, configuration, serverFactory) {
|
|
|
575
626
|
};
|
|
576
627
|
app.post('/mcp', authenticateMiddleware, async (request, response) => {
|
|
577
628
|
try {
|
|
629
|
+
if (await routeModernRequest(request, response))
|
|
630
|
+
return;
|
|
578
631
|
const sessionId = sessionHeader(request);
|
|
579
632
|
if (rejectInvalidProtocolHeader(request, response, sessionId !== undefined))
|
|
580
633
|
return;
|
|
@@ -696,6 +749,8 @@ export async function startHttpServer(engine, configuration, serverFactory) {
|
|
|
696
749
|
}
|
|
697
750
|
});
|
|
698
751
|
app.get('/mcp', authenticateMiddleware, async (request, response) => {
|
|
752
|
+
if (await routeModernRequest(request, response))
|
|
753
|
+
return;
|
|
699
754
|
if (rejectInvalidProtocolHeader(request, response, true))
|
|
700
755
|
return;
|
|
701
756
|
const session = await findSession(request, response);
|
|
@@ -740,6 +795,8 @@ export async function startHttpServer(engine, configuration, serverFactory) {
|
|
|
740
795
|
await session.transport.handleRequest(request, response);
|
|
741
796
|
});
|
|
742
797
|
app.delete('/mcp', authenticateMiddleware, async (request, response) => {
|
|
798
|
+
if (await routeModernRequest(request, response))
|
|
799
|
+
return;
|
|
743
800
|
if (rejectInvalidProtocolHeader(request, response, true))
|
|
744
801
|
return;
|
|
745
802
|
const session = await findSession(request, response);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { isJSONRPCRequest, isJSONRPCNotification, PROTOCOL_VERSION_META_KEY, Server, } from '@modelcontextprotocol/server';
|
|
2
|
+
import { CancelledNotificationSchema } from '@modelcontextprotocol/core';
|
|
3
|
+
import { secureId } from '../../core/canonical.js';
|
|
4
|
+
export const MODERN_TASK_ROUTES = new Map([
|
|
5
|
+
['tasks/get', 'io.github.klimPaskov/hoi4-agent-tools.internal/tasks/get'],
|
|
6
|
+
['tasks/update', 'io.github.klimPaskov/hoi4-agent-tools.internal/tasks/update'],
|
|
7
|
+
['tasks/cancel', 'io.github.klimPaskov/hoi4-agent-tools.internal/tasks/cancel'],
|
|
8
|
+
]);
|
|
9
|
+
const privateMethods = new Set(MODERN_TASK_ROUTES.values());
|
|
10
|
+
/**
|
|
11
|
+
* SDK 2.0.0 issue #2598: historical tasks/get and tasks/cancel are rejected by
|
|
12
|
+
* its core-method era gate before registered extension handlers can run.
|
|
13
|
+
* Rewrite only these extension method names at the public Transport seam.
|
|
14
|
+
* The SDK still validates the envelope and pins the era; HTTP/auth metadata,
|
|
15
|
+
* request IDs, cancellation, params, and every other method remain untouched.
|
|
16
|
+
*/
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- Public advanced Server API is needed to decorate its transport; McpServer cannot inject one.
|
|
18
|
+
export class ModernTaskRoutingServer extends Server {
|
|
19
|
+
async connect(transport) {
|
|
20
|
+
// SDK 2.0.0's cancellation handler ignores falsy IDs (0 and ''). Give only those
|
|
21
|
+
// active requests private truthy IDs internally and preserve their exact wire IDs.
|
|
22
|
+
const requestAliases = new Map();
|
|
23
|
+
const responseAliases = new Map();
|
|
24
|
+
const releaseAlias = (alias) => {
|
|
25
|
+
const original = responseAliases.get(alias);
|
|
26
|
+
if (original !== undefined)
|
|
27
|
+
requestAliases.delete(original);
|
|
28
|
+
responseAliases.delete(alias);
|
|
29
|
+
};
|
|
30
|
+
const routed = {
|
|
31
|
+
get sessionId() {
|
|
32
|
+
return transport.sessionId;
|
|
33
|
+
},
|
|
34
|
+
...(transport.hasPerRequestStream === undefined
|
|
35
|
+
? {}
|
|
36
|
+
: { hasPerRequestStream: transport.hasPerRequestStream }),
|
|
37
|
+
start: async () => {
|
|
38
|
+
transport.onclose = () => {
|
|
39
|
+
requestAliases.clear();
|
|
40
|
+
responseAliases.clear();
|
|
41
|
+
routed.onclose?.();
|
|
42
|
+
};
|
|
43
|
+
transport.onerror = (error) => routed.onerror?.(error);
|
|
44
|
+
transport.onmessage = (message, extra) => {
|
|
45
|
+
if (isJSONRPCRequest(message) && privateMethods.has(message.method)) {
|
|
46
|
+
void transport
|
|
47
|
+
.send({
|
|
48
|
+
jsonrpc: '2.0',
|
|
49
|
+
id: message.id,
|
|
50
|
+
error: { code: -32601, message: 'Method not found' },
|
|
51
|
+
})
|
|
52
|
+
.catch((error) => routed.onerror?.(error instanceof Error ? error : new Error('Response could not be sent')));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const alias = isJSONRPCRequest(message) &&
|
|
56
|
+
message.params?._meta?.[PROTOCOL_VERSION_META_KEY] === '2026-07-28'
|
|
57
|
+
? MODERN_TASK_ROUTES.get(message.method)
|
|
58
|
+
: undefined;
|
|
59
|
+
let inbound = alias === undefined ? message : { ...message, method: alias };
|
|
60
|
+
if (isJSONRPCRequest(inbound) && (inbound.id === 0 || inbound.id === '')) {
|
|
61
|
+
if (requestAliases.has(inbound.id)) {
|
|
62
|
+
void transport
|
|
63
|
+
.send({
|
|
64
|
+
jsonrpc: '2.0',
|
|
65
|
+
id: inbound.id,
|
|
66
|
+
error: { code: -32600, message: 'Request ID is already active' },
|
|
67
|
+
})
|
|
68
|
+
.catch((error) => routed.onerror?.(error instanceof Error ? error : new Error('Response could not be sent')));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const requestAlias = secureId('mcp_request');
|
|
72
|
+
requestAliases.set(inbound.id, requestAlias);
|
|
73
|
+
responseAliases.set(requestAlias, inbound.id);
|
|
74
|
+
inbound = { ...inbound, id: requestAlias };
|
|
75
|
+
}
|
|
76
|
+
else if (isJSONRPCNotification(inbound) &&
|
|
77
|
+
inbound.method === 'notifications/cancelled' &&
|
|
78
|
+
CancelledNotificationSchema.safeParse(inbound).success) {
|
|
79
|
+
const original = inbound.params?.requestId;
|
|
80
|
+
const requestAlias = original === 0 || original === '' ? requestAliases.get(original) : undefined;
|
|
81
|
+
if (requestAlias !== undefined) {
|
|
82
|
+
inbound = { ...inbound, params: { ...inbound.params, requestId: requestAlias } };
|
|
83
|
+
releaseAlias(requestAlias);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
routed.onmessage?.(inbound, extra);
|
|
87
|
+
};
|
|
88
|
+
await transport.start();
|
|
89
|
+
},
|
|
90
|
+
send: (message, options) => {
|
|
91
|
+
const original = 'id' in message && typeof message.id === 'string'
|
|
92
|
+
? responseAliases.get(message.id)
|
|
93
|
+
: undefined;
|
|
94
|
+
const related = typeof options?.relatedRequestId === 'string'
|
|
95
|
+
? responseAliases.get(options.relatedRequestId)
|
|
96
|
+
: undefined;
|
|
97
|
+
const outbound = original === undefined ? message : { ...message, id: original };
|
|
98
|
+
if (original !== undefined && 'id' in message && typeof message.id === 'string')
|
|
99
|
+
releaseAlias(message.id);
|
|
100
|
+
return transport.send(outbound, related === undefined ? options : { ...options, relatedRequestId: related });
|
|
101
|
+
},
|
|
102
|
+
close: () => transport.close(),
|
|
103
|
+
setProtocolVersion: (version) => transport.setProtocolVersion?.(version),
|
|
104
|
+
setSupportedProtocolVersions: (versions) => transport.setSupportedProtocolVersions?.(versions),
|
|
105
|
+
};
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- Connect the public advanced Server API after wrapping the transport.
|
|
107
|
+
await super.connect(routed);
|
|
108
|
+
}
|
|
109
|
+
}
|