@hachej/boring-mcp 0.1.81 → 0.1.83
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/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +69 -12
- package/package.json +5 -5
package/dist/server/index.d.ts
CHANGED
|
@@ -358,6 +358,7 @@ interface RegisterBoringMcpRoutesOptions {
|
|
|
358
358
|
provider?: ManagedConnectorProvider;
|
|
359
359
|
env?: NodeJS.ProcessEnv;
|
|
360
360
|
transport?: McpTransportClient;
|
|
361
|
+
resolveTrustedWorkspaceId?: (request: FastifyRequest) => string | undefined;
|
|
361
362
|
/**
|
|
362
363
|
* Behavior when boring-mcp is disabled for this deployment.
|
|
363
364
|
* - `skip` (default): do not register the routes at all (client sees 404).
|
package/dist/server/index.js
CHANGED
|
@@ -1812,10 +1812,10 @@ function createBoringMcpAppAgentToolsForRequest(app, ctx, options) {
|
|
|
1812
1812
|
if (!userId) return [];
|
|
1813
1813
|
return createBoringMcpAppAgentTools(app, { workspaceId: ctx.workspaceId, userId }, options);
|
|
1814
1814
|
}
|
|
1815
|
-
async function requireBoringMcpActor(app, request) {
|
|
1815
|
+
async function requireBoringMcpActor(app, request, admittedWorkspaceId) {
|
|
1816
1816
|
const body = asRecord(request.body);
|
|
1817
1817
|
const query = asRecord(request.query);
|
|
1818
|
-
const workspaceId = validateWorkspaceId(
|
|
1818
|
+
const workspaceId = admittedWorkspaceId ?? validateWorkspaceId(
|
|
1819
1819
|
request.headers["x-boring-workspace-id"] ?? body.workspaceId ?? query.workspaceId,
|
|
1820
1820
|
request.id
|
|
1821
1821
|
);
|
|
@@ -1831,6 +1831,33 @@ function sourceActionRateLimitKey(request) {
|
|
|
1831
1831
|
const workspaceId = typeof request.headers["x-boring-workspace-id"] === "string" ? request.headers["x-boring-workspace-id"] : "unknown-workspace";
|
|
1832
1832
|
return `${request.user?.id ?? request.ip}:${workspaceId}`;
|
|
1833
1833
|
}
|
|
1834
|
+
function d1HostScopeViolation(request) {
|
|
1835
|
+
throw routeError(421, ERROR_CODES.D1_HOST_SCOPE_VIOLATION, ERROR_CODES.D1_HOST_SCOPE_VIOLATION, request.id);
|
|
1836
|
+
}
|
|
1837
|
+
function presentedWorkspaceIds(request) {
|
|
1838
|
+
const presented = [];
|
|
1839
|
+
const append = (value) => {
|
|
1840
|
+
if (Array.isArray(value)) presented.push(...value.length > 0 ? value : [value]);
|
|
1841
|
+
else presented.push(value);
|
|
1842
|
+
};
|
|
1843
|
+
let foundRawHeader = false;
|
|
1844
|
+
for (let index = 0; index < request.raw.rawHeaders.length; index += 2) {
|
|
1845
|
+
if (request.raw.rawHeaders[index]?.toLowerCase() !== "x-boring-workspace-id") continue;
|
|
1846
|
+
foundRawHeader = true;
|
|
1847
|
+
append(request.raw.rawHeaders[index + 1]?.split(","));
|
|
1848
|
+
}
|
|
1849
|
+
if (!foundRawHeader) {
|
|
1850
|
+
for (const [key, value] of Object.entries(request.headers)) {
|
|
1851
|
+
if (key.toLowerCase() === "x-boring-workspace-id") append(value);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
for (const value of [request.query, request.body]) {
|
|
1855
|
+
if (value && typeof value === "object" && !Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, "workspaceId")) {
|
|
1856
|
+
append(value.workspaceId);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return presented;
|
|
1860
|
+
}
|
|
1834
1861
|
function sourceIdFromBody(body, requestId) {
|
|
1835
1862
|
const sourceId = parseString(asRecord(body).sourceId);
|
|
1836
1863
|
if (!sourceId) throw routeError(400, ERROR_CODES.VALIDATION_FAILED, "sourceId is required", requestId);
|
|
@@ -1850,6 +1877,29 @@ function registerBoringMcpRoutes(app, options) {
|
|
|
1850
1877
|
};
|
|
1851
1878
|
const routeTransport = options.transport ?? createComposioMcpTransportFor(options.config, options.env);
|
|
1852
1879
|
const routeGate = new InMemoryMcpRateBudgetGate({ maxCalls: 100, maxToolCalls: 10, windowMs: 6e4 });
|
|
1880
|
+
const admittedWorkspaceIds = /* @__PURE__ */ new WeakMap();
|
|
1881
|
+
const trustedWorkspaceId = (request) => options.resolveTrustedWorkspaceId?.(request);
|
|
1882
|
+
const sourceRouteRateLimitKey = (request) => {
|
|
1883
|
+
const trusted = trustedWorkspaceId(request);
|
|
1884
|
+
if (trusted === void 0) return sourceActionRateLimitKey(request);
|
|
1885
|
+
const userId = request.user?.id;
|
|
1886
|
+
if (!userId) throw routeError(401, ERROR_CODES.UNAUTHORIZED, "Authentication required", request.id);
|
|
1887
|
+
return `${userId}:${trusted}`;
|
|
1888
|
+
};
|
|
1889
|
+
const admitTrustedWorkspace = async (request) => {
|
|
1890
|
+
const trusted = trustedWorkspaceId(request);
|
|
1891
|
+
if (trusted === void 0) return;
|
|
1892
|
+
for (const presented of presentedWorkspaceIds(request)) {
|
|
1893
|
+
let normalized;
|
|
1894
|
+
try {
|
|
1895
|
+
normalized = validateWorkspaceId(presented, request.id);
|
|
1896
|
+
} catch {
|
|
1897
|
+
d1HostScopeViolation(request);
|
|
1898
|
+
}
|
|
1899
|
+
if (normalized !== trusted) d1HostScopeViolation(request);
|
|
1900
|
+
}
|
|
1901
|
+
admittedWorkspaceIds.set(request, trusted);
|
|
1902
|
+
};
|
|
1853
1903
|
function adapterFor(actor) {
|
|
1854
1904
|
const registry = createUserSettingsMcpSourceRegistry(app, actor);
|
|
1855
1905
|
return {
|
|
@@ -1865,18 +1915,22 @@ function registerBoringMcpRoutes(app, options) {
|
|
|
1865
1915
|
hardening: { gate: routeGate, timeoutMs: 3e4 }
|
|
1866
1916
|
});
|
|
1867
1917
|
}
|
|
1868
|
-
app.get("/api/v1/boring-mcp/sources",
|
|
1918
|
+
app.get("/api/v1/boring-mcp/sources", {
|
|
1919
|
+
config: { rateLimit: { ...SOURCE_ACTION_RATE_LIMIT, keyGenerator: sourceRouteRateLimitKey, allowList: (request) => trustedWorkspaceId(request) === void 0 } },
|
|
1920
|
+
preHandler: admitTrustedWorkspace
|
|
1921
|
+
}, async (request) => {
|
|
1869
1922
|
assertEnabled(request.id);
|
|
1870
|
-
const actor = await requireBoringMcpActor(app, request);
|
|
1923
|
+
const actor = await requireBoringMcpActor(app, request, admittedWorkspaceIds.get(request));
|
|
1871
1924
|
const { registry } = adapterFor(actor);
|
|
1872
1925
|
const sources = await registry.listSources(actor);
|
|
1873
1926
|
return { sourceStatuses: sources.map(createMcpSourceStatusPayload) };
|
|
1874
1927
|
});
|
|
1875
1928
|
app.post("/api/v1/boring-mcp/connect", {
|
|
1876
|
-
config: { rateLimit: { ...SOURCE_CONNECT_RATE_LIMIT, keyGenerator:
|
|
1929
|
+
config: { rateLimit: { ...SOURCE_CONNECT_RATE_LIMIT, keyGenerator: sourceRouteRateLimitKey } },
|
|
1930
|
+
preHandler: admitTrustedWorkspace
|
|
1877
1931
|
}, async (request, reply) => {
|
|
1878
1932
|
assertEnabled(request.id);
|
|
1879
|
-
const actor = await requireBoringMcpActor(app, request);
|
|
1933
|
+
const actor = await requireBoringMcpActor(app, request, admittedWorkspaceIds.get(request));
|
|
1880
1934
|
const { adapter } = adapterFor(actor);
|
|
1881
1935
|
const provider = providerFromBody(request.body, request.id);
|
|
1882
1936
|
const result = await withMcpHttpErrors(request.id, () => adapter.startConnect(actor, { provider }));
|
|
@@ -1885,27 +1939,30 @@ function registerBoringMcpRoutes(app, options) {
|
|
|
1885
1939
|
return { status, connectUrl };
|
|
1886
1940
|
});
|
|
1887
1941
|
app.post("/api/v1/boring-mcp/refresh", {
|
|
1888
|
-
config: { rateLimit: { ...SOURCE_ACTION_RATE_LIMIT, keyGenerator:
|
|
1942
|
+
config: { rateLimit: { ...SOURCE_ACTION_RATE_LIMIT, keyGenerator: sourceRouteRateLimitKey } },
|
|
1943
|
+
preHandler: admitTrustedWorkspace
|
|
1889
1944
|
}, async (request) => {
|
|
1890
1945
|
assertEnabled(request.id);
|
|
1891
|
-
const actor = await requireBoringMcpActor(app, request);
|
|
1946
|
+
const actor = await requireBoringMcpActor(app, request, admittedWorkspaceIds.get(request));
|
|
1892
1947
|
const { adapter } = adapterFor(actor);
|
|
1893
1948
|
const result = await withMcpHttpErrors(request.id, () => adapter.refreshStatus(actor, sourceIdFromBody(request.body, request.id)));
|
|
1894
1949
|
return { status: result };
|
|
1895
1950
|
});
|
|
1896
1951
|
app.post("/api/v1/boring-mcp/disconnect", {
|
|
1897
|
-
config: { rateLimit: { ...SOURCE_ACTION_RATE_LIMIT, keyGenerator:
|
|
1952
|
+
config: { rateLimit: { ...SOURCE_ACTION_RATE_LIMIT, keyGenerator: sourceRouteRateLimitKey } },
|
|
1953
|
+
preHandler: admitTrustedWorkspace
|
|
1898
1954
|
}, async (request) => {
|
|
1899
1955
|
assertEnabled(request.id);
|
|
1900
|
-
const actor = await requireBoringMcpActor(app, request);
|
|
1956
|
+
const actor = await requireBoringMcpActor(app, request, admittedWorkspaceIds.get(request));
|
|
1901
1957
|
const status = await withMcpHttpErrors(request.id, () => adapterFor(actor).adapter.disconnectSource(actor, sourceIdFromBody(request.body, request.id)));
|
|
1902
1958
|
return { status };
|
|
1903
1959
|
});
|
|
1904
1960
|
app.post("/api/v1/boring-mcp/tools", {
|
|
1905
|
-
config: { rateLimit: { ...SOURCE_ACTION_RATE_LIMIT, keyGenerator:
|
|
1961
|
+
config: { rateLimit: { ...SOURCE_ACTION_RATE_LIMIT, keyGenerator: sourceRouteRateLimitKey } },
|
|
1962
|
+
preHandler: admitTrustedWorkspace
|
|
1906
1963
|
}, async (request) => {
|
|
1907
1964
|
assertEnabled(request.id);
|
|
1908
|
-
const actor = await requireBoringMcpActor(app, request);
|
|
1965
|
+
const actor = await requireBoringMcpActor(app, request, admittedWorkspaceIds.get(request));
|
|
1909
1966
|
const body = asRecord(request.body);
|
|
1910
1967
|
const result = await withMcpHttpErrors(request.id, () => handlersFor(actor).searchTools(actor, {
|
|
1911
1968
|
sourceId: sourceIdFromBody(request.body, request.id),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.83",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"fastify": "^5.9.0",
|
|
50
50
|
"react": "^18.0.0 || ^19.0.0",
|
|
51
51
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
52
|
-
"@hachej/boring-core": "0.1.
|
|
53
|
-
"@hachej/boring-workspace": "0.1.
|
|
52
|
+
"@hachej/boring-core": "0.1.83",
|
|
53
|
+
"@hachej/boring-workspace": "0.1.83"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"fastify": "^5.9.0",
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
"tsup": "^8.4.0",
|
|
67
67
|
"typescript": "~6.0.3",
|
|
68
68
|
"vitest": "^4.1.9",
|
|
69
|
-
"@hachej/boring-core": "0.1.
|
|
70
|
-
"@hachej/boring-workspace": "0.1.
|
|
69
|
+
"@hachej/boring-core": "0.1.83",
|
|
70
|
+
"@hachej/boring-workspace": "0.1.83"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@modelcontextprotocol/sdk": "^1.29.0"
|