@toolstop/export-control
Advanced tools
@@ -243,2 +243,19 @@ // Stateless MCP over HTTP, for Cloudflare Workers. | ||
| // --------------------------------------------------------------- maintainers | ||
| // Glama verifies a connector claim by matching these against the email on a | ||
| // Glama account, so a value that is merely well-formed verifies nothing: it has | ||
| // to be the account's own address or the file is served, fetched, and silently | ||
| // ignored. There is no error to observe when it is wrong. | ||
| // | ||
| // Fleet-wide rather than per-server because there is one maintainer, and three | ||
| // copies would be three chances to drift. Public by design: it is served from a | ||
| // public endpoint and asserts a claim rather than guarding one. | ||
| // | ||
| // Note the shape. Glama has two schemas and they disagree: `connector.json`, | ||
| // used here, wants objects carrying `email`, while the repo-root `server.json` | ||
| // wants bare GitHub username strings. Passing one where the other is expected | ||
| // validates as neither. | ||
| const MAINTAINERS = [{ email: "jhohbein@gmail.com" }]; | ||
| // ------------------------------------------------------------- fetch handler | ||
@@ -256,2 +273,15 @@ | ||
| // The one well-known path that gets an answer instead of the 404 below. | ||
| // Glama's crawler fetches it to decide who may claim the connector, so | ||
| // without it a listing stays unclaimed however well it indexes. Serving it | ||
| // is not a courtesy to a crawler; it is the only way to assert ownership of | ||
| // a remote server, since the repo-root `glama.json` route covers only | ||
| // servers Glama found through GitHub. | ||
| if (read && url.pathname === "/.well-known/glama.json") { | ||
| return Response.json({ | ||
| $schema: "https://glama.ai/mcp/schemas/connector.json", | ||
| maintainers: MAINTAINERS, | ||
| }); | ||
| } | ||
| // A GET on the MCP endpoint is how a client opens the server-initiated SSE | ||
@@ -258,0 +288,0 @@ // stream, and the spec says a server that does not offer one answers 405. |
+24
-3
@@ -88,2 +88,3 @@ // Licence-requirement lookup against the Commerce Country Chart. | ||
| "export and is controlled separately; see 15 CFR 734.13.", | ||
| sourceEdition: SOURCE_EDITION, | ||
| }; | ||
@@ -158,2 +159,3 @@ } | ||
| notCovered: NOT_COVERED, | ||
| sourceEdition: SOURCE_EDITION, | ||
| }; | ||
@@ -181,2 +183,3 @@ } | ||
| message: `"${String(eccnRaw ?? "")}" is not an ECCN. An ECCN is a digit, a letter A to E, then three digits, for example 3A001.`, | ||
| sourceEdition: SOURCE_EDITION, | ||
| }; | ||
@@ -191,2 +194,3 @@ } | ||
| message: `${eccn} is not in the Commerce Control List edition of ${SOURCE_EDITION}. It may have been removed, or it may never have existed. Do not read this as "no licence required".`, | ||
| sourceEdition: SOURCE_EDITION, | ||
| }; | ||
@@ -206,2 +210,3 @@ } | ||
| : `"${String(countryRaw ?? "")}" is not a destination on the Commerce Country Chart.`, | ||
| sourceEdition: SOURCE_EDITION, | ||
| }; | ||
@@ -221,2 +226,3 @@ } | ||
| notCovered: NOT_COVERED, | ||
| sourceEdition: SOURCE_EDITION, | ||
| }; | ||
@@ -231,2 +237,3 @@ } | ||
| const marks = new Set(row.c); | ||
| const checked = []; | ||
| const triggered = []; | ||
@@ -236,3 +243,9 @@ const notOnChart = []; | ||
| if (column) { | ||
| if (marks.has(column)) triggered.push({ reason: column.slice(0, 2), column, scope, reasonName: REASON_CODES[column.slice(0, 2)] ?? null }); | ||
| const reason = column.slice(0, 2); | ||
| const marked = marks.has(column); | ||
| // Every chart-determined control is recorded with the cell that was read, | ||
| // marked or not. This is what makes a negative answer checkable: see | ||
| // `checked` below. | ||
| checked.push({ column, reason, reasonName: REASON_CODES[reason] ?? null, scope, marked }); | ||
| if (marked) triggered.push({ reason, column, scope, reasonName: REASON_CODES[reason] ?? null }); | ||
| } else { | ||
@@ -250,2 +263,9 @@ notOnChart.push({ scope, requirement: chartText }); | ||
| triggeredBy: triggered, | ||
| // The evidence for the verdict, and the reason this tool reports rather | ||
| // than rules. `triggeredBy` justifies a `true`; until this existed a | ||
| // `false` arrived as a bare boolean with an empty array beside it, which is | ||
| // the answer a caller acts on by shipping and the one they could not check | ||
| // without reading the chart themselves. `checked` names every cell that was | ||
| // read and what it held, so both verdicts cite the same grid. | ||
| checked, | ||
| controlsNotOnChart: notOnChart, | ||
@@ -261,5 +281,5 @@ countryColumns: row.c, | ||
| const eccn = normalizeEccn(eccnRaw); | ||
| if (!eccn) return { status: "invalid_eccn", message: `"${String(eccnRaw ?? "")}" is not a well-formed ECCN.` }; | ||
| if (!eccn) return { status: "invalid_eccn", message: `"${String(eccnRaw ?? "")}" is not a well-formed ECCN.`, sourceEdition: SOURCE_EDITION }; | ||
| const e = ECCNS[eccn]; | ||
| if (!e) return { status: "unknown_eccn", eccn, message: `${eccn} is not in the ${SOURCE_EDITION} Commerce Control List.` }; | ||
| if (!e) return { status: "unknown_eccn", eccn, message: `${eccn} is not in the ${SOURCE_EDITION} Commerce Control List.`, sourceEdition: SOURCE_EDITION }; | ||
| if (!e.c.some((c) => c[2])) { | ||
@@ -292,2 +312,3 @@ return { | ||
| message: `"${String(countryRaw ?? "")}" did not resolve to exactly one destination on the chart.`, | ||
| sourceEdition: SOURCE_EDITION, | ||
| }; | ||
@@ -294,0 +315,0 @@ } |
+50
-4
@@ -36,3 +36,3 @@ // The single source of truth for this server. Both transports import this and | ||
| name: "export-control", | ||
| version: "0.3.1", | ||
| version: "0.4.0", | ||
| instructions: | ||
@@ -76,2 +76,12 @@ "Answers one question: does the Commerce Country Chart require an export " + | ||
| "licence exceptions that may change the answer in the other direction.\n\n" + | ||
| "**Report the citation, not just the verdict.** This server tells you what " + | ||
| "the tables say; it is not an authority on what you may do, and the " + | ||
| "difference matters because the exporter carries the liability either way. " + | ||
| "Every answer returns `checked`, which names each chart cell that was read " + | ||
| "and whether it was marked, and `sourceEdition`, which says how current the " + | ||
| "tables are. A negative answer is the one a caller acts on by shipping, so " + | ||
| "it is the one that most needs its evidence passed on rather than " + | ||
| "summarised. Where a row carries `footnotes`, say so: this server does not " + | ||
| "apply them, and footnote 6 alone covers the Russia and Belarus " + | ||
| "sanctions.\n\n" + | ||
| "If a lookup here disagrees with the current regulation, the data is a " + | ||
@@ -99,3 +109,11 @@ "snapshot and the regulation is the authority. Report it at " + | ||
| "is an unanswered question rather than a negative answer. This tool " + | ||
| "cannot classify an item into an ECCN and will not try.", | ||
| "cannot classify an item into an ECCN and will not try.\n\n" + | ||
| "Every verdict carries its own evidence and you should report it " + | ||
| "rather than the boolean alone. `checked` lists each chart cell that " + | ||
| "was read and what it held, so a negative answer can be verified " + | ||
| "against 15 CFR part 738 Supplement No. 1 without trusting this " + | ||
| "server; `footnotes` carries row conditions this tool does not apply " + | ||
| "and which can change the answer; `sourceEdition` says which CFR " + | ||
| "edition the tables came from, and a snapshot older than the current " + | ||
| "regulation gives a stale answer that still reads as confident.", | ||
| annotations: { readOnlyHint: true, openWorldHint: false }, | ||
@@ -136,2 +154,12 @@ inputSchema: { | ||
| }, | ||
| checked: { | ||
| type: "array", | ||
| description: | ||
| "Every chart-determined control on the entry, each with the column read, its reason code, " + | ||
| "the scope it applies to, and whether that column is marked for this destination. This is " + | ||
| "the citation for the verdict and it is the only way to check a `licenseRequired: false`, " + | ||
| "which otherwise arrives as a bare boolean. Verify it against 15 CFR part 738 Supplement " + | ||
| "No. 1 before relying on a negative answer.", | ||
| items: { type: "object" }, | ||
| }, | ||
| controlsNotOnChart: { | ||
@@ -142,2 +170,20 @@ type: "array", | ||
| }, | ||
| countryColumns: { | ||
| type: "array", | ||
| description: "Every control column marked for this destination on the chart row, whether or not it bears on this ECCN.", | ||
| items: { type: "string" }, | ||
| }, | ||
| footnotes: { | ||
| type: "array", | ||
| description: | ||
| "Footnotes on this chart row, each with its number and text. **They can change the answer " + | ||
| "and this tool does not apply them.** Footnote 6 is the Russia and Belarus sanctions and " + | ||
| "footnote 8 is Crimea. A `licenseRequired: false` on a row carrying footnotes is not a " + | ||
| "clear result until the footnote text has been read.", | ||
| items: { type: "object" }, | ||
| }, | ||
| sourceEdition: { | ||
| type: "string", | ||
| description: "The CFR edition the tables were taken from. Present on every response, including errors. Data older than the current regulation is a stale answer, not a wrong one, and the regulation is the authority.", | ||
| }, | ||
| candidates: { | ||
@@ -153,6 +199,6 @@ type: "array", | ||
| }, | ||
| title: { type: "string", description: "The Control List entry heading, on an indeterminate result." }, | ||
| title: { type: "string", description: "The Control List entry heading." }, | ||
| message: { type: "string", description: "Why no verdict was returned, when status is not `ok`." }, | ||
| }, | ||
| required: ["status"], | ||
| required: ["status", "sourceEdition"], | ||
| }, | ||
@@ -159,0 +205,0 @@ examples: [ |
+1
-1
| { | ||
| "name": "@toolstop/export-control", | ||
| "version": "0.3.1", | ||
| "version": "0.4.0", | ||
| "mcpName": "dev.toolstop/export-control", | ||
@@ -5,0 +5,0 @@ "description": "MCP server that answers whether the US Commerce Country Chart requires an export licence for a given ECCN to a given destination, from the published CFR tables.", |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
358104
1.62%2223
4.42%