@namingsignal/mcp 0.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/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/agent-guidance.js +7 -0
- package/dist/core/batch.js +142 -0
- package/dist/core/brief-constraints.js +72 -0
- package/dist/core/brief.js +402 -0
- package/dist/core/candidate-sort.js +47 -0
- package/dist/core/candidates.js +210 -0
- package/dist/core/domain.js +607 -0
- package/dist/core/editorial-review.js +27 -0
- package/dist/core/export.js +235 -0
- package/dist/core/import-safety.js +298 -0
- package/dist/core/index.js +31 -0
- package/dist/core/namesake.js +53 -0
- package/dist/core/namespaces.js +295 -0
- package/dist/core/normalize.js +75 -0
- package/dist/core/scoring.js +409 -0
- package/dist/core/types.js +3 -0
- package/dist/core/url-input.js +28 -0
- package/dist/core/usage.js +271 -0
- package/dist/index.js +19 -0
- package/dist/server-core.js +418 -0
- package/package.json +48 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BudgetGuard = exports.BudgetExceededError = exports.UsageLedger = void 0;
|
|
4
|
+
exports.estimateModelCost = estimateModelCost;
|
|
5
|
+
exports.checkBudget = checkBudget;
|
|
6
|
+
exports.assertWithinBudget = assertWithinBudget;
|
|
7
|
+
const ZERO_USAGE = {
|
|
8
|
+
providerCalls: 0,
|
|
9
|
+
modelCalls: 0,
|
|
10
|
+
domainRequests: 0,
|
|
11
|
+
namespaceRequests: 0,
|
|
12
|
+
searchQueries: 0,
|
|
13
|
+
fetchedPages: 0,
|
|
14
|
+
inputTokens: 0,
|
|
15
|
+
cachedInputTokens: 0,
|
|
16
|
+
reasoningTokens: 0,
|
|
17
|
+
outputTokens: 0,
|
|
18
|
+
estimatedUsd: 0,
|
|
19
|
+
cacheHits: 0,
|
|
20
|
+
retries: 0,
|
|
21
|
+
failures: 0,
|
|
22
|
+
latencyMs: 0,
|
|
23
|
+
events: 0,
|
|
24
|
+
};
|
|
25
|
+
function finiteNonNegative(value) {
|
|
26
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
27
|
+
}
|
|
28
|
+
function eventCost(event) {
|
|
29
|
+
if (event.estimatedUsd !== undefined)
|
|
30
|
+
return finiteNonNegative(event.estimatedUsd);
|
|
31
|
+
if (event.kind === "model" && event.pricing)
|
|
32
|
+
return estimateModelCost(event, event.pricing);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
function addUsage(left, right) {
|
|
36
|
+
return {
|
|
37
|
+
providerCalls: left.providerCalls + finiteNonNegative(right.providerCalls),
|
|
38
|
+
modelCalls: left.modelCalls + finiteNonNegative(right.modelCalls),
|
|
39
|
+
domainRequests: left.domainRequests + finiteNonNegative(right.domainRequests),
|
|
40
|
+
namespaceRequests: left.namespaceRequests + finiteNonNegative(right.namespaceRequests),
|
|
41
|
+
searchQueries: left.searchQueries + finiteNonNegative(right.searchQueries),
|
|
42
|
+
fetchedPages: left.fetchedPages + finiteNonNegative(right.fetchedPages),
|
|
43
|
+
inputTokens: left.inputTokens + finiteNonNegative(right.inputTokens),
|
|
44
|
+
cachedInputTokens: left.cachedInputTokens + finiteNonNegative(right.cachedInputTokens),
|
|
45
|
+
reasoningTokens: left.reasoningTokens + finiteNonNegative(right.reasoningTokens),
|
|
46
|
+
outputTokens: left.outputTokens + finiteNonNegative(right.outputTokens),
|
|
47
|
+
estimatedUsd: left.estimatedUsd + finiteNonNegative(right.estimatedUsd),
|
|
48
|
+
cacheHits: left.cacheHits + finiteNonNegative(right.cacheHits),
|
|
49
|
+
retries: left.retries + finiteNonNegative(right.retries),
|
|
50
|
+
failures: left.failures + finiteNonNegative(right.failures),
|
|
51
|
+
latencyMs: left.latencyMs + finiteNonNegative(right.latencyMs),
|
|
52
|
+
events: left.events + finiteNonNegative(right.events),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function eventToUsage(event) {
|
|
56
|
+
const calls = event.calls ?? 1;
|
|
57
|
+
const requests = event.requests ?? calls;
|
|
58
|
+
return {
|
|
59
|
+
...ZERO_USAGE,
|
|
60
|
+
providerCalls: event.kind === "model" ? calls : 0,
|
|
61
|
+
modelCalls: event.kind === "model" ? calls : 0,
|
|
62
|
+
domainRequests: event.kind === "domain" ? requests : 0,
|
|
63
|
+
namespaceRequests: event.kind === "namespace" ? requests : 0,
|
|
64
|
+
searchQueries: event.searchQueries ?? (event.kind === "search" ? requests : 0),
|
|
65
|
+
fetchedPages: event.fetchedPages ?? 0,
|
|
66
|
+
inputTokens: finiteNonNegative(event.inputTokens),
|
|
67
|
+
cachedInputTokens: finiteNonNegative(event.cachedInputTokens),
|
|
68
|
+
reasoningTokens: finiteNonNegative(event.reasoningTokens),
|
|
69
|
+
outputTokens: finiteNonNegative(event.outputTokens),
|
|
70
|
+
estimatedUsd: eventCost(event),
|
|
71
|
+
cacheHits: finiteNonNegative(event.cacheHits),
|
|
72
|
+
retries: finiteNonNegative(event.retries),
|
|
73
|
+
failures: finiteNonNegative(event.failures),
|
|
74
|
+
latencyMs: finiteNonNegative(event.latencyMs),
|
|
75
|
+
events: 1,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function estimateModelCost(usageOrInput, pricingOrOutput, maybePricing, maybeCached = 0, maybeReasoning = 0) {
|
|
79
|
+
const usage = typeof usageOrInput === "number"
|
|
80
|
+
? {
|
|
81
|
+
inputTokens: usageOrInput,
|
|
82
|
+
outputTokens: typeof pricingOrOutput === "number" ? pricingOrOutput : 0,
|
|
83
|
+
cachedInputTokens: maybeCached,
|
|
84
|
+
reasoningTokens: maybeReasoning,
|
|
85
|
+
}
|
|
86
|
+
: usageOrInput;
|
|
87
|
+
const pricing = (typeof pricingOrOutput === "object" ? pricingOrOutput : maybePricing);
|
|
88
|
+
if (!pricing)
|
|
89
|
+
throw new TypeError("Model pricing is required.");
|
|
90
|
+
const input = finiteNonNegative(usage.inputTokens);
|
|
91
|
+
const cached = Math.min(input, finiteNonNegative(usage.cachedInputTokens));
|
|
92
|
+
const uncached = Math.max(0, input - cached);
|
|
93
|
+
const output = finiteNonNegative(usage.outputTokens);
|
|
94
|
+
const reasoning = finiteNonNegative(usage.reasoningTokens);
|
|
95
|
+
const cost = (uncached * finiteNonNegative(pricing.inputPerMillionUsd)) / 1_000_000 +
|
|
96
|
+
(cached * finiteNonNegative(pricing.cachedInputPerMillionUsd ?? pricing.inputPerMillionUsd)) / 1_000_000 +
|
|
97
|
+
(output * finiteNonNegative(pricing.outputPerMillionUsd)) / 1_000_000 +
|
|
98
|
+
(reasoning * finiteNonNegative(pricing.reasoningPerMillionUsd ?? pricing.outputPerMillionUsd)) / 1_000_000;
|
|
99
|
+
return cost;
|
|
100
|
+
}
|
|
101
|
+
class UsageLedger {
|
|
102
|
+
eventsInternal = [];
|
|
103
|
+
now;
|
|
104
|
+
sequence = 0;
|
|
105
|
+
constructor(options = {}) {
|
|
106
|
+
this.now = options.now ?? (() => new Date());
|
|
107
|
+
}
|
|
108
|
+
record(event) {
|
|
109
|
+
if (!event || !event.kind || !event.provider || !event.task) {
|
|
110
|
+
throw new TypeError("Usage events require kind, provider, and task.");
|
|
111
|
+
}
|
|
112
|
+
this.sequence += 1;
|
|
113
|
+
const recorded = {
|
|
114
|
+
...event,
|
|
115
|
+
id: event.id ?? `usage_${this.sequence}`,
|
|
116
|
+
occurredAt: event.occurredAt ?? this.now().toISOString(),
|
|
117
|
+
estimatedUsd: eventCost(event),
|
|
118
|
+
};
|
|
119
|
+
this.eventsInternal.push(Object.freeze(recorded));
|
|
120
|
+
return recorded;
|
|
121
|
+
}
|
|
122
|
+
recordModel(event) {
|
|
123
|
+
return this.record({ ...event, kind: "model" });
|
|
124
|
+
}
|
|
125
|
+
recordDomain(provider, task, requests = 1, extra = {}) {
|
|
126
|
+
return this.record({ ...extra, provider, task, requests, kind: "domain" });
|
|
127
|
+
}
|
|
128
|
+
recordNamespace(provider, task, requests = 1, extra = {}) {
|
|
129
|
+
return this.record({ ...extra, provider, task, requests, kind: "namespace" });
|
|
130
|
+
}
|
|
131
|
+
getEvents() {
|
|
132
|
+
return this.eventsInternal.map((event) => ({ ...event }));
|
|
133
|
+
}
|
|
134
|
+
snapshot() {
|
|
135
|
+
return this.eventsInternal.reduce((summary, event) => addUsage(summary, eventToUsage(event)), { ...ZERO_USAGE });
|
|
136
|
+
}
|
|
137
|
+
merge(other) {
|
|
138
|
+
const events = other instanceof UsageLedger ? other.getEvents() : other;
|
|
139
|
+
for (const event of events)
|
|
140
|
+
this.record({ ...event, id: undefined });
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
clear() {
|
|
144
|
+
this.eventsInternal.length = 0;
|
|
145
|
+
}
|
|
146
|
+
toJSON() {
|
|
147
|
+
return { ...this.snapshot(), eventLog: this.getEvents() };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
exports.UsageLedger = UsageLedger;
|
|
151
|
+
function usageFrom(input) {
|
|
152
|
+
if (input instanceof UsageLedger)
|
|
153
|
+
return input.snapshot();
|
|
154
|
+
if ("kind" in input && "provider" in input && "task" in input)
|
|
155
|
+
return eventToUsage(input);
|
|
156
|
+
return addUsage({ ...ZERO_USAGE }, input);
|
|
157
|
+
}
|
|
158
|
+
const LIMIT_METRICS = [
|
|
159
|
+
{ limit: "maxProviderCalls", actual: (usage) => usage.providerCalls },
|
|
160
|
+
{ limit: "maxModelCalls", actual: (usage) => usage.modelCalls },
|
|
161
|
+
{ limit: "maxDomainRequests", actual: (usage) => usage.domainRequests },
|
|
162
|
+
{ limit: "maxNamespaceRequests", actual: (usage) => usage.namespaceRequests },
|
|
163
|
+
{ limit: "maxSearchQueries", actual: (usage) => usage.searchQueries },
|
|
164
|
+
{ limit: "maxFetchedPages", actual: (usage) => usage.fetchedPages },
|
|
165
|
+
{ limit: "maxInputTokens", actual: (usage) => usage.inputTokens },
|
|
166
|
+
{ limit: "maxCachedInputTokens", actual: (usage) => usage.cachedInputTokens },
|
|
167
|
+
{ limit: "maxReasoningTokens", actual: (usage) => usage.reasoningTokens },
|
|
168
|
+
{ limit: "maxOutputTokens", actual: (usage) => usage.outputTokens },
|
|
169
|
+
{ limit: "maxTotalTokens", actual: (usage) => usage.inputTokens + usage.reasoningTokens + usage.outputTokens },
|
|
170
|
+
{ limit: "maxEstimatedUsd", actual: (usage) => usage.estimatedUsd },
|
|
171
|
+
{ limit: "maxRetries", actual: (usage) => usage.retries },
|
|
172
|
+
{ limit: "maxFailures", actual: (usage) => usage.failures },
|
|
173
|
+
];
|
|
174
|
+
function checkBudget(usageInput, limits, pending) {
|
|
175
|
+
const current = usageFrom(usageInput);
|
|
176
|
+
const usage = pending ? addUsage(current, usageFrom(pending)) : current;
|
|
177
|
+
const violations = [];
|
|
178
|
+
const remaining = {};
|
|
179
|
+
for (const item of LIMIT_METRICS) {
|
|
180
|
+
const rawLimit = limits[item.limit];
|
|
181
|
+
if (rawLimit === undefined)
|
|
182
|
+
continue;
|
|
183
|
+
const limit = finiteNonNegative(rawLimit);
|
|
184
|
+
const actual = item.actual(usage);
|
|
185
|
+
remaining[item.limit] = Math.max(0, limit - actual);
|
|
186
|
+
if (actual > limit)
|
|
187
|
+
violations.push({ metric: item.limit, limit, actual, overBy: actual - limit });
|
|
188
|
+
}
|
|
189
|
+
return { ok: violations.length === 0, withinBudget: violations.length === 0, violations, remaining, usage };
|
|
190
|
+
}
|
|
191
|
+
class BudgetExceededError extends Error {
|
|
192
|
+
result;
|
|
193
|
+
constructor(result) {
|
|
194
|
+
const detail = result.violations
|
|
195
|
+
.map((violation) => `${violation.metric}=${violation.actual} exceeds ${violation.limit}`)
|
|
196
|
+
.join(", ");
|
|
197
|
+
super(`Budget exceeded: ${detail}`);
|
|
198
|
+
this.name = "BudgetExceededError";
|
|
199
|
+
this.result = result;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.BudgetExceededError = BudgetExceededError;
|
|
203
|
+
function assertWithinBudget(usage, limits, pending) {
|
|
204
|
+
const result = checkBudget(usage, limits, pending);
|
|
205
|
+
if (!result.ok)
|
|
206
|
+
throw new BudgetExceededError(result);
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
class BudgetGuard {
|
|
210
|
+
ledger;
|
|
211
|
+
limits;
|
|
212
|
+
reserved = { ...ZERO_USAGE };
|
|
213
|
+
reservations = new Map();
|
|
214
|
+
sequence = 0;
|
|
215
|
+
constructor(limits, ledger = new UsageLedger()) {
|
|
216
|
+
this.limits = { ...limits };
|
|
217
|
+
this.ledger = ledger;
|
|
218
|
+
}
|
|
219
|
+
check(pending) {
|
|
220
|
+
const withReservations = addUsage(this.ledger.snapshot(), this.reserved);
|
|
221
|
+
return checkBudget(withReservations, this.limits, pending);
|
|
222
|
+
}
|
|
223
|
+
assert(pending) {
|
|
224
|
+
const result = this.check(pending);
|
|
225
|
+
if (!result.ok)
|
|
226
|
+
throw new BudgetExceededError(result);
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
record(event) {
|
|
230
|
+
this.assert(event);
|
|
231
|
+
return this.ledger.record(event);
|
|
232
|
+
}
|
|
233
|
+
reserve(pending) {
|
|
234
|
+
this.assert(pending);
|
|
235
|
+
const usage = usageFrom(pending);
|
|
236
|
+
this.sequence += 1;
|
|
237
|
+
const id = `reservation_${this.sequence}`;
|
|
238
|
+
this.reservations.set(id, usage);
|
|
239
|
+
this.reserved = addUsage(this.reserved, usage);
|
|
240
|
+
return id;
|
|
241
|
+
}
|
|
242
|
+
release(reservationId) {
|
|
243
|
+
const usage = this.reservations.get(reservationId);
|
|
244
|
+
if (!usage)
|
|
245
|
+
return;
|
|
246
|
+
this.reservations.delete(reservationId);
|
|
247
|
+
this.reserved = {
|
|
248
|
+
providerCalls: Math.max(0, this.reserved.providerCalls - usage.providerCalls),
|
|
249
|
+
modelCalls: Math.max(0, this.reserved.modelCalls - usage.modelCalls),
|
|
250
|
+
domainRequests: Math.max(0, this.reserved.domainRequests - usage.domainRequests),
|
|
251
|
+
namespaceRequests: Math.max(0, this.reserved.namespaceRequests - usage.namespaceRequests),
|
|
252
|
+
searchQueries: Math.max(0, this.reserved.searchQueries - usage.searchQueries),
|
|
253
|
+
fetchedPages: Math.max(0, this.reserved.fetchedPages - usage.fetchedPages),
|
|
254
|
+
inputTokens: Math.max(0, this.reserved.inputTokens - usage.inputTokens),
|
|
255
|
+
cachedInputTokens: Math.max(0, this.reserved.cachedInputTokens - usage.cachedInputTokens),
|
|
256
|
+
reasoningTokens: Math.max(0, this.reserved.reasoningTokens - usage.reasoningTokens),
|
|
257
|
+
outputTokens: Math.max(0, this.reserved.outputTokens - usage.outputTokens),
|
|
258
|
+
estimatedUsd: Math.max(0, this.reserved.estimatedUsd - usage.estimatedUsd),
|
|
259
|
+
cacheHits: Math.max(0, this.reserved.cacheHits - usage.cacheHits),
|
|
260
|
+
retries: Math.max(0, this.reserved.retries - usage.retries),
|
|
261
|
+
failures: Math.max(0, this.reserved.failures - usage.failures),
|
|
262
|
+
latencyMs: Math.max(0, this.reserved.latencyMs - usage.latencyMs),
|
|
263
|
+
events: Math.max(0, this.reserved.events - usage.events),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
commit(reservationId, event) {
|
|
267
|
+
this.release(reservationId);
|
|
268
|
+
return this.record(event);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
exports.BudgetGuard = BudgetGuard;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// Thin stdio entry: environment parsing plus transport wiring only. The
|
|
4
|
+
// McpServer construction and all tool registrations live in server-core.ts so
|
|
5
|
+
// the remote Streamable HTTP endpoint at /mcp serves the identical contract.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
8
|
+
const server_core_1 = require("./server-core");
|
|
9
|
+
const apiBase = (process.env.NAMING_SIGNAL_API_URL?.trim() || process.env.NAME_LEDGER_API_URL?.trim() || server_core_1.DEFAULT_API_URL).replace(/\/+$/, "");
|
|
10
|
+
const apiKey = process.env.NAMING_SIGNAL_API_KEY?.trim();
|
|
11
|
+
async function main() {
|
|
12
|
+
const server = (0, server_core_1.createNamingSignalMcpServer)({ baseUrl: apiBase, apiKey });
|
|
13
|
+
await server.connect(new stdio_js_1.StdioServerTransport());
|
|
14
|
+
console.error(`NamingSignal MCP connected over stdio; API: ${apiBase}`);
|
|
15
|
+
}
|
|
16
|
+
main().catch((error) => {
|
|
17
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
});
|