@skeletiq/mcp 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.js +28 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ Run `npx -y @skeletiq/mcp` over stdio with `SKELETIQ_API_KEY` in the environment
|
|
|
56
56
|
| `SKELETIQ_API_KEY` | yes | — | A personal API token, starting `skq_`. Not a browser session token. |
|
|
57
57
|
| `SKELETIQ_API_URL` | no | `https://api.skeletiq.com` | For self-hosted installs. A trailing `/api/v1` is accepted and trimmed. |
|
|
58
58
|
|
|
59
|
+
Every request carries a User-Agent of the form `skeletiq-mcp/<version> (<host name>/<host version>)`, naming the MCP host that launched the server.
|
|
60
|
+
|
|
59
61
|
## Scopes
|
|
60
62
|
|
|
61
63
|
A token grants only what you tick. The server's tools need:
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
7
|
var SERVER_NAME = "skeletiq";
|
|
8
|
-
var SERVER_VERSION = "0.
|
|
8
|
+
var SERVER_VERSION = "0.7.1";
|
|
9
9
|
var AGENT_SCHEMA_VERSION = 2;
|
|
10
10
|
function schemaNote(serverVersion) {
|
|
11
11
|
if (serverVersion === void 0 || serverVersion <= AGENT_SCHEMA_VERSION) return null;
|
|
@@ -199,12 +199,28 @@ function describeApiError(error) {
|
|
|
199
199
|
|
|
200
200
|
// src/http/client.ts
|
|
201
201
|
var API_PREFIX = "/api/v1";
|
|
202
|
+
var HOST_TOKEN_MAX = 64;
|
|
203
|
+
function hostToken(value) {
|
|
204
|
+
return value.replace(/[^A-Za-z0-9._~+-]/g, "-").slice(0, HOST_TOKEN_MAX);
|
|
205
|
+
}
|
|
206
|
+
function userAgent(host) {
|
|
207
|
+
const base = `${SERVER_NAME}-mcp/${SERVER_VERSION}`;
|
|
208
|
+
const name = hostToken(host?.name ?? "");
|
|
209
|
+
if (!name) return base;
|
|
210
|
+
const version = hostToken(host?.version ?? "");
|
|
211
|
+
return version ? `${base} (${name}/${version})` : `${base} (${name})`;
|
|
212
|
+
}
|
|
202
213
|
var READ_TIMEOUT_MS = 6e5;
|
|
203
214
|
var SkeletiqClient = class {
|
|
204
215
|
config;
|
|
216
|
+
hostSource;
|
|
205
217
|
constructor(config) {
|
|
206
218
|
this.config = config;
|
|
207
219
|
}
|
|
220
|
+
/** Where to read the connected host's name and version from, for the User-Agent. */
|
|
221
|
+
setHostSource(source) {
|
|
222
|
+
this.hostSource = source;
|
|
223
|
+
}
|
|
208
224
|
/** A parsed JSON response, or a thrown `SkeletiqApiError` carrying the server's own words. */
|
|
209
225
|
async request(path2, options = {}) {
|
|
210
226
|
const response = await this.fetch(path2, options);
|
|
@@ -223,7 +239,7 @@ var SkeletiqClient = class {
|
|
|
223
239
|
headers: {
|
|
224
240
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
225
241
|
Accept: "application/json",
|
|
226
|
-
"User-Agent":
|
|
242
|
+
"User-Agent": userAgent(this.hostSource?.()),
|
|
227
243
|
...options.body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
228
244
|
...options.headers
|
|
229
245
|
},
|
|
@@ -625,6 +641,11 @@ var CritiqueFindingSchema = z.looseObject({
|
|
|
625
641
|
remediation: z.string().optional(),
|
|
626
642
|
/** Reported in full and never charged against the score — advice, not a problem to fix. */
|
|
627
643
|
advisory_only: z.boolean().optional(),
|
|
644
|
+
/**
|
|
645
|
+
* Whether this finding can hold a design back from Ready. Absent or null means not stated, and
|
|
646
|
+
* the standing Ready rule applies to it.
|
|
647
|
+
*/
|
|
648
|
+
holds_ready: z.boolean().nullable().optional(),
|
|
628
649
|
/**
|
|
629
650
|
* The problem this finding is about. Stable across rewording, and shared with the advisor
|
|
630
651
|
* suggestion and decision record raised by the same problem — the key a dismissal is recorded
|
|
@@ -1455,6 +1476,7 @@ var input3 = z5.object({
|
|
|
1455
1476
|
});
|
|
1456
1477
|
var SCORE_BASIS_FINDINGS_ONLY = "findings_only";
|
|
1457
1478
|
var SCORE_BASIS_NOTE = "This score is findings only. It is not held to a traceability ceiling, because this tool is given a design and no requirement set to trace it against. The app can show a lower number for the same stored design for exactly that reason.";
|
|
1479
|
+
var COMPLIANCE_INDICATIVE_NOTE = "Indicative automated check based on architecture structure \u2014 not a certified compliance audit or legal advice.";
|
|
1458
1480
|
var output3 = z5.object({
|
|
1459
1481
|
architecture_score: z5.number(),
|
|
1460
1482
|
score_basis: z5.literal(SCORE_BASIS_FINDINGS_ONLY).describe(SCORE_BASIS_NOTE),
|
|
@@ -1537,7 +1559,7 @@ function registerCritique(server, client) {
|
|
|
1537
1559
|
// An agent that reads only the text has to see this too. Without it, "the app
|
|
1538
1560
|
// said 59 and the tool said 74" is an unexplained contradiction it will either
|
|
1539
1561
|
// resolve wrongly or report as a bug.
|
|
1540
|
-
complianceAssessed ? `Compliance checked against ${(critique.frameworks_checked ?? []).join(", ")} \u2014 the frameworks systems in this domain are usually held to, inferred from the domain you sent rather than from a regime anyone named. Confirm them
|
|
1562
|
+
complianceAssessed ? `Compliance checked against ${(critique.frameworks_checked ?? []).join(", ")} \u2014 the frameworks systems in this domain are usually held to, inferred from the domain you sent rather than from a regime anyone named. Confirm them. ` + COMPLIANCE_INDICATIVE_NOTE : `${critique.compliance_note ?? "Compliance was not assessed."} ${COMPLIANCE_INDICATIVE_NOTE}`,
|
|
1541
1563
|
// Only when the exposure used is not the one the agent chose. When a
|
|
1542
1564
|
// non-public exposure *was* applied the server returns an
|
|
1543
1565
|
// `exposure_scoped_checks` finding naming exactly which checks it took off
|
|
@@ -1660,13 +1682,13 @@ function frameDetail(frame) {
|
|
|
1660
1682
|
var constraintsInput = z6.strictObject({
|
|
1661
1683
|
budget: z6.enum(["low", "medium", "high", "unlimited"]).optional(),
|
|
1662
1684
|
max_latency_ms: z6.number().int().positive().optional(),
|
|
1663
|
-
// Free strings, not an enum, and the description has to say why. SkeletIQ assesses
|
|
1685
|
+
// Free strings, not an enum, and the description has to say why. SkeletIQ assesses six
|
|
1664
1686
|
// frameworks; naming anything else is still worth doing, because an unassessed regime is
|
|
1665
1687
|
// recorded and reaches the design brief as a requirement the agent still has to satisfy.
|
|
1666
1688
|
// The old text read 'e.g. HIPAA, SOC2, PCI-DSS, GDPR' — it omitted SOX, which is the one
|
|
1667
1689
|
// an agent is least likely to guess, and said nothing about what happens to the rest.
|
|
1668
1690
|
compliance: z6.array(z6.string()).optional().describe(
|
|
1669
|
-
"Regimes the design must satisfy. SOC2, HIPAA, PCI-DSS, GDPR and
|
|
1691
|
+
"Regimes the design must satisfy. SOC2, HIPAA, PCI-DSS, GDPR, SOX and DPDP (India) are checked against requirement catalogues; any other regime is recorded and carried into the design brief as a requirement to satisfy, rather than checked."
|
|
1670
1692
|
),
|
|
1671
1693
|
preferred_cloud: z6.enum(["aws", "gcp", "azure", "any"]).optional(),
|
|
1672
1694
|
preferred_technologies: z6.array(z6.string()).optional(),
|
|
@@ -2833,6 +2855,7 @@ function buildServer(client, resolver) {
|
|
|
2833
2855
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
2834
2856
|
{ instructions: INSTRUCTIONS, capabilities: { tools: {} } }
|
|
2835
2857
|
);
|
|
2858
|
+
client.setHostSource(() => server.server.getClientVersion());
|
|
2836
2859
|
registerListProjects(server, resolver);
|
|
2837
2860
|
registerGetDesign(server, client, resolver);
|
|
2838
2861
|
registerGenerate(server, client, resolver);
|
package/package.json
CHANGED