@anuma/sdk 6.26.2 → 6.27.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/dist/expo/index.cjs +223 -216
- package/dist/expo/index.d.mts +29 -1
- package/dist/expo/index.d.ts +29 -1
- package/dist/expo/index.mjs +220 -216
- package/dist/react/index.cjs +153 -110
- package/dist/react/index.mjs +151 -110
- package/dist/server/index.cjs +66 -1
- package/dist/server/index.mjs +66 -1
- package/dist/tools/index.cjs +1191 -1204
- package/dist/tools/index.d.mts +27 -47
- package/dist/tools/index.d.ts +27 -47
- package/dist/tools/index.mjs +1190 -1204
- package/package.json +1 -1
package/dist/tools/index.cjs
CHANGED
|
@@ -112,6 +112,7 @@ __export(index_exports, {
|
|
|
112
112
|
createNotionGetTeamsTool: () => createNotionGetTeamsTool,
|
|
113
113
|
createNotionGetUsersTool: () => createNotionGetUsersTool,
|
|
114
114
|
createNotionMovePagesTool: () => createNotionMovePagesTool,
|
|
115
|
+
createNotionProxyTools: () => createNotionProxyTools,
|
|
115
116
|
createNotionSearchTool: () => createNotionSearchTool,
|
|
116
117
|
createNotionTools: () => createNotionTools,
|
|
117
118
|
createNotionUpdateDataSourceTool: () => createNotionUpdateDataSourceTool,
|
|
@@ -1728,1306 +1729,1291 @@ function createDriveTools(getAccessToken, requestDriveAccess) {
|
|
|
1728
1729
|
];
|
|
1729
1730
|
}
|
|
1730
1731
|
|
|
1731
|
-
// src/
|
|
1732
|
-
var
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1732
|
+
// src/lib/connectors/agentres/errors.ts
|
|
1733
|
+
var AgentresError = class extends Error {
|
|
1734
|
+
constructor(status, code, message, retryable, nextStep) {
|
|
1735
|
+
super(message);
|
|
1736
|
+
this.name = "AgentresError";
|
|
1737
|
+
this.status = status;
|
|
1738
|
+
this.code = code;
|
|
1739
|
+
this.retryable = retryable;
|
|
1740
|
+
this.nextStep = nextStep;
|
|
1740
1741
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1742
|
+
};
|
|
1743
|
+
var SiwxUnsupportedError = class extends Error {
|
|
1744
|
+
constructor(message) {
|
|
1745
|
+
super(message);
|
|
1746
|
+
this.name = "SiwxUnsupportedError";
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
var AgentresPathError = class extends Error {
|
|
1750
|
+
constructor(message) {
|
|
1751
|
+
super(message);
|
|
1752
|
+
this.name = "AgentresPathError";
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
var SiwxChallengeError = class extends Error {
|
|
1756
|
+
constructor(message, options) {
|
|
1757
|
+
super(message);
|
|
1758
|
+
this.name = "SiwxChallengeError";
|
|
1759
|
+
this.cause = options?.cause;
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
function parseAgentresError(status, body) {
|
|
1763
|
+
const stated = statedError(body);
|
|
1764
|
+
if (!stated) {
|
|
1765
|
+
return new AgentresError(
|
|
1766
|
+
status,
|
|
1767
|
+
`HTTP_${status}`,
|
|
1768
|
+
body.trim() || `HTTP ${status}`,
|
|
1769
|
+
status >= 500
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
const code = typeof stated.code === "string" ? stated.code : `HTTP_${status}`;
|
|
1773
|
+
const message = typeof stated.message === "string" ? stated.message : `HTTP ${status}`;
|
|
1774
|
+
const retryable = typeof stated.retryable === "boolean" ? stated.retryable : status >= 500;
|
|
1775
|
+
const nextStep = typeof stated.next_step === "string" ? stated.next_step : void 0;
|
|
1776
|
+
return new AgentresError(status, code, message, retryable, nextStep);
|
|
1744
1777
|
}
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1778
|
+
function statedError(body) {
|
|
1779
|
+
let parsed;
|
|
1780
|
+
try {
|
|
1781
|
+
parsed = JSON.parse(body);
|
|
1782
|
+
} catch {
|
|
1783
|
+
return null;
|
|
1784
|
+
}
|
|
1785
|
+
const envelope = parsed;
|
|
1786
|
+
const stated = typeof envelope?.error === "object" && envelope.error !== null ? envelope.error : null;
|
|
1787
|
+
if (!stated) {
|
|
1788
|
+
return null;
|
|
1789
|
+
}
|
|
1790
|
+
const fields = stated;
|
|
1791
|
+
if (typeof fields.code !== "string" && typeof fields.message !== "string") {
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
return fields;
|
|
1749
1795
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
if (line.startsWith("data: ")) {
|
|
1757
|
-
dataLines.push(line.slice(6));
|
|
1758
|
-
} else if (line.startsWith("data:")) {
|
|
1759
|
-
dataLines.push(line.slice(5));
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
if (dataLines.length > 0) {
|
|
1763
|
-
lastEventData = dataLines.join("\n");
|
|
1764
|
-
}
|
|
1796
|
+
|
|
1797
|
+
// src/lib/processors/encoding.ts
|
|
1798
|
+
var B64_ENCODE_CHUNK = 32768 * 3;
|
|
1799
|
+
function uint8ArrayToBase64(data) {
|
|
1800
|
+
if (typeof Buffer !== "undefined") {
|
|
1801
|
+
return Buffer.from(data).toString("base64");
|
|
1765
1802
|
}
|
|
1766
|
-
|
|
1767
|
-
|
|
1803
|
+
let base64 = "";
|
|
1804
|
+
for (let i = 0; i < data.length; i += B64_ENCODE_CHUNK) {
|
|
1805
|
+
const slice = data.subarray(i, i + B64_ENCODE_CHUNK);
|
|
1806
|
+
let binary = "";
|
|
1807
|
+
for (let j = 0; j < slice.length; j++) {
|
|
1808
|
+
binary += String.fromCharCode(slice[j]);
|
|
1809
|
+
}
|
|
1810
|
+
base64 += btoa(binary);
|
|
1768
1811
|
}
|
|
1769
|
-
return
|
|
1812
|
+
return base64;
|
|
1770
1813
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
return parseSSEResponse(text);
|
|
1814
|
+
function base64ToUint8Array(base64) {
|
|
1815
|
+
if (typeof Buffer !== "undefined") {
|
|
1816
|
+
const buf = Buffer.from(base64, "base64");
|
|
1817
|
+
return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
|
|
1776
1818
|
}
|
|
1777
|
-
|
|
1819
|
+
const binary = atob(base64);
|
|
1820
|
+
const bytes = new Uint8Array(binary.length);
|
|
1821
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1822
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1823
|
+
}
|
|
1824
|
+
return bytes;
|
|
1778
1825
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1826
|
+
|
|
1827
|
+
// src/lib/connectors/agentres/siwx.ts
|
|
1828
|
+
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
1829
|
+
var SIWX_HEADER = "SIGN-IN-WITH-X";
|
|
1830
|
+
var SIWX_EXTENSION = "sign-in-with-x";
|
|
1831
|
+
var SOLANA_NAMESPACE = "solana:";
|
|
1832
|
+
var ED25519_SIGNATURE_BYTES = 64;
|
|
1833
|
+
function parseChallenge(paymentRequiredHeader, expectedOrigin) {
|
|
1834
|
+
const decoded = decodeHeader(paymentRequiredHeader);
|
|
1835
|
+
const extensions = asRecord(decoded.extensions);
|
|
1836
|
+
const extension = extensions ? asRecord(extensions[SIWX_EXTENSION]) : null;
|
|
1837
|
+
if (!extension) {
|
|
1838
|
+
throw new SiwxUnsupportedError(
|
|
1839
|
+
"the 402 carries no sign-in-with-x extension, so this endpoint is paid rather than identity-only"
|
|
1840
|
+
);
|
|
1841
|
+
}
|
|
1842
|
+
const info = asRecord(extension.info);
|
|
1843
|
+
if (!info) {
|
|
1844
|
+
throw new SiwxChallengeError("the sign-in-with-x extension has no info block");
|
|
1845
|
+
}
|
|
1846
|
+
const chain = solanaChain(extension.supportedChains);
|
|
1847
|
+
const challenge = {
|
|
1848
|
+
domain: requireString(info, "domain"),
|
|
1849
|
+
uri: requireString(info, "uri"),
|
|
1850
|
+
version: requireString(info, "version"),
|
|
1851
|
+
statement: requireString(info, "statement"),
|
|
1852
|
+
nonce: requireString(info, "nonce"),
|
|
1853
|
+
issuedAt: requireString(info, "issuedAt"),
|
|
1854
|
+
resources: requireResources(info),
|
|
1855
|
+
chainId: chain.chainId,
|
|
1856
|
+
signingType: chain.signingType
|
|
1793
1857
|
};
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
});
|
|
1803
|
-
if (!response.ok) {
|
|
1804
|
-
const errorBody = await response.text().catch(() => "");
|
|
1805
|
-
throw new Error(
|
|
1806
|
-
`MCP initialization failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
1858
|
+
assertScopedTo(challenge, expectedOrigin);
|
|
1859
|
+
return challenge;
|
|
1860
|
+
}
|
|
1861
|
+
function assertScopedTo(challenge, expectedOrigin) {
|
|
1862
|
+
const expected = parseUrl(expectedOrigin, `the agentres base URL "${expectedOrigin}"`);
|
|
1863
|
+
if (challenge.domain !== expected.host) {
|
|
1864
|
+
throw new SiwxChallengeError(
|
|
1865
|
+
`the challenge is scoped to "${challenge.domain}" but this client talks to "${expected.host}" \u2014 refusing to sign it`
|
|
1807
1866
|
);
|
|
1808
1867
|
}
|
|
1809
|
-
const
|
|
1810
|
-
if (
|
|
1811
|
-
throw new
|
|
1868
|
+
const uri = parseUrl(challenge.uri, `the sign-in-with-x uri "${challenge.uri}"`);
|
|
1869
|
+
if (uri.origin !== expected.origin) {
|
|
1870
|
+
throw new SiwxChallengeError(
|
|
1871
|
+
`the challenge addresses "${uri.origin}" but this client talks to "${expected.origin}" \u2014 refusing to sign it`
|
|
1872
|
+
);
|
|
1812
1873
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1874
|
+
}
|
|
1875
|
+
function parseUrl(value, description) {
|
|
1876
|
+
try {
|
|
1877
|
+
return new URL(value);
|
|
1878
|
+
} catch (cause) {
|
|
1879
|
+
throw new SiwxChallengeError(`${description} is not a URL`, { cause });
|
|
1818
1880
|
}
|
|
1819
|
-
await fetch(MCP_HTTP_ENDPOINT, {
|
|
1820
|
-
method: "POST",
|
|
1821
|
-
headers: {
|
|
1822
|
-
"Content-Type": "application/json",
|
|
1823
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1824
|
-
"Mcp-Session-Id": sessionId
|
|
1825
|
-
},
|
|
1826
|
-
body: JSON.stringify({
|
|
1827
|
-
jsonrpc: "2.0",
|
|
1828
|
-
method: "notifications/initialized"
|
|
1829
|
-
})
|
|
1830
|
-
});
|
|
1831
|
-
sessionCache.set(accessToken, sessionId);
|
|
1832
|
-
return sessionId;
|
|
1833
1881
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1882
|
+
function buildMessage(challenge, address) {
|
|
1883
|
+
if (!challenge.chainId.startsWith(SOLANA_NAMESPACE)) {
|
|
1884
|
+
throw new SiwxChallengeError(
|
|
1885
|
+
`expected a solana: chain id for the message, got "${challenge.chainId}"`
|
|
1886
|
+
);
|
|
1838
1887
|
}
|
|
1839
|
-
|
|
1888
|
+
const reference = challenge.chainId.slice(SOLANA_NAMESPACE.length);
|
|
1889
|
+
const resources = challenge.resources.map((resource) => `- ${resource}`).join("\n");
|
|
1890
|
+
return `${challenge.domain} wants you to sign in with your Solana account:
|
|
1891
|
+
${address}
|
|
1892
|
+
|
|
1893
|
+
${challenge.statement}
|
|
1894
|
+
|
|
1895
|
+
URI: ${challenge.uri}
|
|
1896
|
+
Version: ${challenge.version}
|
|
1897
|
+
Chain ID: ${reference}
|
|
1898
|
+
Nonce: ${challenge.nonce}
|
|
1899
|
+
Issued At: ${challenge.issuedAt}
|
|
1900
|
+
Resources:
|
|
1901
|
+
${resources}`;
|
|
1840
1902
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1903
|
+
function buildPayload(challenge, address, signature) {
|
|
1904
|
+
if (signature.length !== ED25519_SIGNATURE_BYTES) {
|
|
1905
|
+
throw new SiwxChallengeError(
|
|
1906
|
+
`expected a ${ED25519_SIGNATURE_BYTES}-byte ed25519 signature, got ${signature.length} bytes`
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
const payload = {
|
|
1910
|
+
domain: challenge.domain,
|
|
1911
|
+
address,
|
|
1912
|
+
statement: challenge.statement,
|
|
1913
|
+
uri: challenge.uri,
|
|
1914
|
+
version: challenge.version,
|
|
1915
|
+
// Full CAIP-2 here, unlike the bare reference the message carries.
|
|
1916
|
+
chainId: challenge.chainId,
|
|
1917
|
+
type: challenge.signingType,
|
|
1918
|
+
nonce: challenge.nonce,
|
|
1919
|
+
issuedAt: challenge.issuedAt,
|
|
1920
|
+
resources: challenge.resources,
|
|
1921
|
+
signature: base58Encode(signature)
|
|
1852
1922
|
};
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1873
|
-
"Mcp-Session-Id": newSessionId
|
|
1874
|
-
},
|
|
1875
|
-
body: JSON.stringify(jsonRpcRequest)
|
|
1876
|
-
});
|
|
1877
|
-
if (!retryResponse.ok) {
|
|
1878
|
-
const errorBody2 = await retryResponse.text().catch(() => "");
|
|
1879
|
-
throw new Error(
|
|
1880
|
-
`MCP request failed: ${retryResponse.status} ${retryResponse.statusText}${errorBody2 ? ` - ${errorBody2}` : ""}`
|
|
1881
|
-
);
|
|
1882
|
-
}
|
|
1883
|
-
const retryJsonRpcResponse = await parseResponseBody(retryResponse);
|
|
1884
|
-
if (retryJsonRpcResponse.error) {
|
|
1885
|
-
const err = retryJsonRpcResponse.error;
|
|
1886
|
-
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
1887
|
-
throw new Error(`MCP tool error: ${errMsg}`);
|
|
1888
|
-
}
|
|
1889
|
-
return truncateToolResult(retryJsonRpcResponse.result);
|
|
1923
|
+
return uint8ArrayToBase64(new TextEncoder().encode(JSON.stringify(payload)));
|
|
1924
|
+
}
|
|
1925
|
+
var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1926
|
+
function base58Encode(bytes) {
|
|
1927
|
+
let zeros = 0;
|
|
1928
|
+
while (zeros < bytes.length && bytes[zeros] === 0) {
|
|
1929
|
+
zeros++;
|
|
1930
|
+
}
|
|
1931
|
+
const digits = [];
|
|
1932
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
1933
|
+
let carry = bytes[i];
|
|
1934
|
+
for (let j = 0; j < digits.length; j++) {
|
|
1935
|
+
carry += digits[j] * 256;
|
|
1936
|
+
digits[j] = carry % 58;
|
|
1937
|
+
carry = Math.floor(carry / 58);
|
|
1938
|
+
}
|
|
1939
|
+
while (carry > 0) {
|
|
1940
|
+
digits.push(carry % 58);
|
|
1941
|
+
carry = Math.floor(carry / 58);
|
|
1890
1942
|
}
|
|
1891
|
-
const errorBody = await response.text().catch(() => "");
|
|
1892
|
-
throw new Error(
|
|
1893
|
-
`MCP request failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
1894
|
-
);
|
|
1895
1943
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
1900
|
-
throw new Error(`MCP tool error: ${errMsg}`);
|
|
1944
|
+
let encoded = "1".repeat(zeros);
|
|
1945
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
1946
|
+
encoded += BASE58_ALPHABET[digits[i]];
|
|
1901
1947
|
}
|
|
1902
|
-
return
|
|
1948
|
+
return encoded;
|
|
1903
1949
|
}
|
|
1904
|
-
function
|
|
1950
|
+
function decodeHeader(paymentRequiredHeader) {
|
|
1951
|
+
let parsed;
|
|
1952
|
+
try {
|
|
1953
|
+
parsed = JSON.parse(new TextDecoder().decode(base64ToUint8Array(paymentRequiredHeader)));
|
|
1954
|
+
} catch (cause) {
|
|
1955
|
+
throw new SiwxChallengeError("the PAYMENT-REQUIRED header is not base64-encoded JSON", {
|
|
1956
|
+
cause
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
const decoded = asRecord(parsed);
|
|
1960
|
+
if (!decoded) {
|
|
1961
|
+
throw new SiwxChallengeError("the PAYMENT-REQUIRED header did not decode to an object");
|
|
1962
|
+
}
|
|
1963
|
+
return decoded;
|
|
1964
|
+
}
|
|
1965
|
+
function solanaChain(supportedChains) {
|
|
1966
|
+
if (!Array.isArray(supportedChains)) {
|
|
1967
|
+
throw new SiwxChallengeError("the sign-in-with-x extension lists no supportedChains");
|
|
1968
|
+
}
|
|
1969
|
+
for (const entry of supportedChains) {
|
|
1970
|
+
const chain = asRecord(entry);
|
|
1971
|
+
const chainId = chain?.chainId;
|
|
1972
|
+
if (typeof chainId !== "string" || !chainId.startsWith(SOLANA_NAMESPACE)) {
|
|
1973
|
+
continue;
|
|
1974
|
+
}
|
|
1975
|
+
const signingType = chain?.type;
|
|
1976
|
+
if (typeof signingType !== "string" || signingType === "") {
|
|
1977
|
+
throw new SiwxChallengeError(`the ${chainId} entry declares no signing type`);
|
|
1978
|
+
}
|
|
1979
|
+
return { chainId, signingType };
|
|
1980
|
+
}
|
|
1981
|
+
throw new SiwxChallengeError("the sign-in-with-x extension offers no solana: chain");
|
|
1982
|
+
}
|
|
1983
|
+
function asRecord(value) {
|
|
1984
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1985
|
+
}
|
|
1986
|
+
function requireString(info, key) {
|
|
1987
|
+
const value = info[key];
|
|
1988
|
+
if (typeof value !== "string" || value === "") {
|
|
1989
|
+
throw new SiwxChallengeError(`the sign-in-with-x info block has no ${key}`);
|
|
1990
|
+
}
|
|
1991
|
+
return value;
|
|
1992
|
+
}
|
|
1993
|
+
function requireResources(info) {
|
|
1994
|
+
const resources = info.resources;
|
|
1995
|
+
if (!Array.isArray(resources) || resources.length === 0) {
|
|
1996
|
+
throw new SiwxChallengeError("the sign-in-with-x info block has no resources");
|
|
1997
|
+
}
|
|
1998
|
+
if (!resources.every((resource) => typeof resource === "string" && resource !== "")) {
|
|
1999
|
+
throw new SiwxChallengeError("the sign-in-with-x resources are not all strings");
|
|
2000
|
+
}
|
|
2001
|
+
return resources;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/lib/connectors/agentres/client.ts
|
|
2005
|
+
var DEFAULT_BASE_URL = "https://agentres.dev";
|
|
2006
|
+
function createAgentresClient(options) {
|
|
2007
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
2008
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
2009
|
+
const { address, signMessage } = options;
|
|
2010
|
+
async function withSiwx(request) {
|
|
2011
|
+
const url = resolveUrl(baseUrl, request.path);
|
|
2012
|
+
const init = requestInit(request);
|
|
2013
|
+
const challenged = await fetchImpl(url, init);
|
|
2014
|
+
if (challenged.status !== 402) {
|
|
2015
|
+
return readBody(challenged);
|
|
2016
|
+
}
|
|
2017
|
+
await challenged.body?.cancel().catch(() => {
|
|
2018
|
+
});
|
|
2019
|
+
const header = challenged.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
2020
|
+
if (!header) {
|
|
2021
|
+
throw new SiwxChallengeError(
|
|
2022
|
+
`the 402 from ${request.path} carried no ${PAYMENT_REQUIRED_HEADER} header`
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
const challenge = parseChallenge(header, baseUrl);
|
|
2026
|
+
const signature = await signMessage(new TextEncoder().encode(buildMessage(challenge, address)));
|
|
2027
|
+
const authorized = await fetchImpl(url, {
|
|
2028
|
+
...init,
|
|
2029
|
+
headers: { ...init.headers, [SIWX_HEADER]: buildPayload(challenge, address, signature) }
|
|
2030
|
+
});
|
|
2031
|
+
return readBody(authorized);
|
|
2032
|
+
}
|
|
2033
|
+
async function linkResy(body) {
|
|
2034
|
+
const response = await withSiwx({ method: "POST", path: "/api/link-resy", body });
|
|
2035
|
+
return { step: response.step, message: response.message, resyUserId: response.resy_user_id };
|
|
2036
|
+
}
|
|
1905
2037
|
return {
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
description: "Semantic search query over workspace and connected sources. For user search, a substring to match against name or email."
|
|
1916
|
-
},
|
|
1917
|
-
query_type: {
|
|
1918
|
-
type: "string",
|
|
1919
|
-
enum: ["internal", "user"],
|
|
1920
|
-
description: "Type of search: 'internal' for workspace/content search, 'user' for user search"
|
|
1921
|
-
},
|
|
1922
|
-
content_search_mode: {
|
|
1923
|
-
type: "string",
|
|
1924
|
-
enum: ["workspace_search", "ai_search"],
|
|
1925
|
-
description: "Override auto-selection of AI search vs workspace search"
|
|
1926
|
-
},
|
|
1927
|
-
data_source_url: {
|
|
1928
|
-
type: "string",
|
|
1929
|
-
description: "URL of a data source to search within"
|
|
1930
|
-
},
|
|
1931
|
-
page_url: {
|
|
1932
|
-
type: "string",
|
|
1933
|
-
description: "URL or ID of a page to search within"
|
|
1934
|
-
},
|
|
1935
|
-
teamspace_id: {
|
|
1936
|
-
type: "string",
|
|
1937
|
-
description: "ID of a teamspace to restrict search results to"
|
|
1938
|
-
},
|
|
1939
|
-
filters: {
|
|
1940
|
-
type: "object",
|
|
1941
|
-
description: "Filters for search results (only for query_type 'internal'). Supports created_date_range {start_date, end_date} and created_by_user_ids array."
|
|
1942
|
-
}
|
|
1943
|
-
},
|
|
1944
|
-
required: ["query"]
|
|
1945
|
-
}
|
|
2038
|
+
withSiwx,
|
|
2039
|
+
async ensureAccount(email) {
|
|
2040
|
+
const body = await withSiwx({ method: "POST", path: "/api/account", body: { email } });
|
|
2041
|
+
return {
|
|
2042
|
+
accountId: body.account_id,
|
|
2043
|
+
email: body.email,
|
|
2044
|
+
credentialKind: body.credential_kind,
|
|
2045
|
+
walletLinked: body.wallet_linked
|
|
2046
|
+
};
|
|
1946
2047
|
},
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2048
|
+
requestCode(email) {
|
|
2049
|
+
return linkResy({ em_address: email });
|
|
2050
|
+
},
|
|
2051
|
+
verifyCode(email, code) {
|
|
2052
|
+
return linkResy({ em_address: email, code });
|
|
2053
|
+
},
|
|
2054
|
+
async getLinkStatus() {
|
|
2055
|
+
const body = await withSiwx({ method: "GET", path: "/api/me" });
|
|
2056
|
+
return {
|
|
2057
|
+
accountId: body.account_id,
|
|
2058
|
+
email: body.email,
|
|
2059
|
+
resyLinked: body.resy_linked,
|
|
2060
|
+
walletAddress: body.wallet_address,
|
|
2061
|
+
resyLinkedAt: body.resy_linked_at ?? null
|
|
2062
|
+
};
|
|
1957
2063
|
}
|
|
1958
2064
|
};
|
|
1959
2065
|
}
|
|
1960
|
-
function
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
type: "boolean",
|
|
1975
|
-
description: "Include transcript if available"
|
|
1976
|
-
},
|
|
1977
|
-
include_discussions: {
|
|
1978
|
-
type: "boolean",
|
|
1979
|
-
description: "Include discussion counts and inline discussion markers"
|
|
1980
|
-
}
|
|
1981
|
-
},
|
|
1982
|
-
required: ["id"]
|
|
1983
|
-
}
|
|
1984
|
-
},
|
|
1985
|
-
executor: async (args) => {
|
|
1986
|
-
let token = getAccessToken();
|
|
1987
|
-
if (!token) {
|
|
1988
|
-
token = await requestNotionAccess();
|
|
1989
|
-
}
|
|
1990
|
-
try {
|
|
1991
|
-
return await callMCPTool(token, "notion-fetch", args);
|
|
1992
|
-
} catch (error) {
|
|
1993
|
-
return `Error fetching Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
function createNotionCreatePagesTool(getAccessToken, requestNotionAccess) {
|
|
1999
|
-
return {
|
|
2000
|
-
type: "function",
|
|
2001
|
-
function: {
|
|
2002
|
-
name: "notion-create-pages",
|
|
2003
|
-
description: "Create one or more Notion pages. All pages in a single call share the same parent. Properties use simplified format: title is a string, dates use date:{prop}:start format, checkboxes use __YES__/__NO__. Content is Notion-flavored Markdown.",
|
|
2004
|
-
arguments: {
|
|
2005
|
-
type: "object",
|
|
2006
|
-
properties: {
|
|
2007
|
-
pages: {
|
|
2008
|
-
type: "array",
|
|
2009
|
-
description: "Array of page objects to create (max 100)",
|
|
2010
|
-
items: {
|
|
2011
|
-
type: "object",
|
|
2012
|
-
properties: {
|
|
2013
|
-
properties: {
|
|
2014
|
-
type: "object",
|
|
2015
|
-
description: "JSON map of property names to values. Title is a string, numbers are JS numbers, checkboxes use __YES__/__NO__, dates use date:{prop}:start/end/is_datetime format."
|
|
2016
|
-
},
|
|
2017
|
-
content: {
|
|
2018
|
-
type: "string",
|
|
2019
|
-
description: "Page content in Notion-flavored Markdown format"
|
|
2020
|
-
}
|
|
2021
|
-
},
|
|
2022
|
-
required: ["properties", "content"]
|
|
2023
|
-
}
|
|
2024
|
-
},
|
|
2025
|
-
parent: {
|
|
2026
|
-
type: "object",
|
|
2027
|
-
description: "Parent for all pages. Use {type:'page_id', page_id:'...'}, {type:'database_id', database_id:'...'}, or {type:'data_source_id', data_source_id:'...'}. Do NOT pass this field to create standalone private pages."
|
|
2028
|
-
}
|
|
2029
|
-
},
|
|
2030
|
-
required: ["pages"]
|
|
2031
|
-
}
|
|
2032
|
-
},
|
|
2033
|
-
executor: async (args) => {
|
|
2034
|
-
let token = getAccessToken();
|
|
2035
|
-
if (!token) {
|
|
2036
|
-
token = await requestNotionAccess();
|
|
2037
|
-
}
|
|
2038
|
-
try {
|
|
2039
|
-
return await callMCPTool(token, "notion-create-pages", args);
|
|
2040
|
-
} catch (error) {
|
|
2041
|
-
return `Error creating Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
};
|
|
2045
|
-
}
|
|
2046
|
-
function createNotionUpdatePageTool(getAccessToken, requestNotionAccess) {
|
|
2047
|
-
return {
|
|
2048
|
-
type: "function",
|
|
2049
|
-
function: {
|
|
2050
|
-
name: "notion-update-page",
|
|
2051
|
-
description: "Update a Notion page's properties or content. Uses command-based operations: 'update_properties' to change properties, 'replace_content' to replace all content, 'replace_content_range' to replace specific text, 'insert_content_after' to insert after text.",
|
|
2052
|
-
arguments: {
|
|
2053
|
-
type: "object",
|
|
2054
|
-
properties: {
|
|
2055
|
-
data: {
|
|
2056
|
-
type: "object",
|
|
2057
|
-
description: "Update data containing page_id and a command",
|
|
2058
|
-
properties: {
|
|
2059
|
-
page_id: {
|
|
2060
|
-
type: "string",
|
|
2061
|
-
description: "The ID of the page to update (with or without dashes)"
|
|
2062
|
-
},
|
|
2063
|
-
command: {
|
|
2064
|
-
type: "string",
|
|
2065
|
-
enum: [
|
|
2066
|
-
"update_properties",
|
|
2067
|
-
"replace_content",
|
|
2068
|
-
"replace_content_range",
|
|
2069
|
-
"insert_content_after"
|
|
2070
|
-
],
|
|
2071
|
-
description: "The update command to execute"
|
|
2072
|
-
},
|
|
2073
|
-
properties: {
|
|
2074
|
-
type: "object",
|
|
2075
|
-
description: "For update_properties: JSON map of property names to values. Use null to remove a value."
|
|
2076
|
-
},
|
|
2077
|
-
new_str: {
|
|
2078
|
-
type: "string",
|
|
2079
|
-
description: "For replace_content/replace_content_range/insert_content_after: the new content string"
|
|
2080
|
-
},
|
|
2081
|
-
selection_with_ellipsis: {
|
|
2082
|
-
type: "string",
|
|
2083
|
-
description: "For replace_content_range/insert_content_after: unique start and end snippet (~10 chars each with ellipsis)"
|
|
2084
|
-
},
|
|
2085
|
-
allow_deleting_content: {
|
|
2086
|
-
type: "boolean",
|
|
2087
|
-
description: "For replace_content/replace_content_range: allow deletion of child pages/databases"
|
|
2088
|
-
}
|
|
2089
|
-
},
|
|
2090
|
-
required: ["page_id", "command"]
|
|
2091
|
-
}
|
|
2092
|
-
},
|
|
2093
|
-
required: ["data"]
|
|
2094
|
-
}
|
|
2095
|
-
},
|
|
2096
|
-
executor: async (args) => {
|
|
2097
|
-
let token = getAccessToken();
|
|
2098
|
-
if (!token) {
|
|
2099
|
-
token = await requestNotionAccess();
|
|
2100
|
-
}
|
|
2101
|
-
try {
|
|
2102
|
-
return await callMCPTool(token, "notion-update-page", args);
|
|
2103
|
-
} catch (error) {
|
|
2104
|
-
return `Error updating Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
};
|
|
2066
|
+
function resolveUrl(baseUrl, path) {
|
|
2067
|
+
if (!path.startsWith("/") || path.startsWith("//")) {
|
|
2068
|
+
throw new AgentresPathError(
|
|
2069
|
+
`"${path}" is not a path on ${baseUrl} \u2014 it must start with a single "/"`
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
const base = new URL(baseUrl);
|
|
2073
|
+
const resolved = new URL(path, base);
|
|
2074
|
+
if (resolved.origin !== base.origin) {
|
|
2075
|
+
throw new AgentresPathError(
|
|
2076
|
+
`"${path}" resolves to ${resolved.origin} but this client talks to ${base.origin} \u2014 refusing to send it`
|
|
2077
|
+
);
|
|
2078
|
+
}
|
|
2079
|
+
return resolved.toString();
|
|
2108
2080
|
}
|
|
2109
|
-
function
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
type: "object",
|
|
2117
|
-
properties: {
|
|
2118
|
-
page_or_database_ids: {
|
|
2119
|
-
type: "array",
|
|
2120
|
-
description: "Array of page or database IDs to move (UUIDs, max 100)",
|
|
2121
|
-
items: { type: "string" }
|
|
2122
|
-
},
|
|
2123
|
-
new_parent: {
|
|
2124
|
-
type: "object",
|
|
2125
|
-
description: "New parent: {type:'page_id', page_id:'...'}, {type:'database_id', database_id:'...'}, {type:'data_source_id', data_source_id:'...'}, or {type:'workspace'} for private pages."
|
|
2126
|
-
}
|
|
2127
|
-
},
|
|
2128
|
-
required: ["page_or_database_ids", "new_parent"]
|
|
2129
|
-
}
|
|
2130
|
-
},
|
|
2131
|
-
executor: async (args) => {
|
|
2132
|
-
let token = getAccessToken();
|
|
2133
|
-
if (!token) {
|
|
2134
|
-
token = await requestNotionAccess();
|
|
2135
|
-
}
|
|
2136
|
-
try {
|
|
2137
|
-
return await callMCPTool(token, "notion-move-pages", args);
|
|
2138
|
-
} catch (error) {
|
|
2139
|
-
return `Error moving Notion pages: ${error instanceof Error ? error.message : String(error)}`;
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
};
|
|
2081
|
+
function requestInit(request) {
|
|
2082
|
+
const headers = { Accept: "application/json" };
|
|
2083
|
+
if (request.body === void 0) {
|
|
2084
|
+
return { method: request.method, headers };
|
|
2085
|
+
}
|
|
2086
|
+
headers["Content-Type"] = "application/json";
|
|
2087
|
+
return { method: request.method, headers, body: JSON.stringify(request.body) };
|
|
2143
2088
|
}
|
|
2144
|
-
function
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
}
|
|
2160
|
-
},
|
|
2161
|
-
executor: async (args) => {
|
|
2162
|
-
let token = getAccessToken();
|
|
2163
|
-
if (!token) {
|
|
2164
|
-
token = await requestNotionAccess();
|
|
2165
|
-
}
|
|
2166
|
-
try {
|
|
2167
|
-
return await callMCPTool(token, "notion-duplicate-page", args);
|
|
2168
|
-
} catch (error) {
|
|
2169
|
-
return `Error duplicating Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
};
|
|
2089
|
+
async function readBody(response) {
|
|
2090
|
+
const body = await response.text();
|
|
2091
|
+
if (!response.ok) {
|
|
2092
|
+
throw parseAgentresError(response.status, body);
|
|
2093
|
+
}
|
|
2094
|
+
try {
|
|
2095
|
+
return JSON.parse(body);
|
|
2096
|
+
} catch {
|
|
2097
|
+
throw new AgentresError(
|
|
2098
|
+
response.status,
|
|
2099
|
+
"INVALID_JSON",
|
|
2100
|
+
`agentres answered ${response.status} with a body that is not JSON`,
|
|
2101
|
+
response.status >= 500
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2173
2104
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
type: "object",
|
|
2185
|
-
description: "Property schema for the database. Each key is a property name, value defines the type."
|
|
2186
|
-
},
|
|
2187
|
-
parent: {
|
|
2188
|
-
type: "object",
|
|
2189
|
-
description: "Parent page: {type:'page_id', page_id:'...'}. Omit for private workspace-level database."
|
|
2190
|
-
},
|
|
2191
|
-
title: {
|
|
2192
|
-
type: "array",
|
|
2193
|
-
description: "Title of the database as rich text array (max 100)",
|
|
2194
|
-
items: { type: "object" }
|
|
2195
|
-
},
|
|
2196
|
-
description: {
|
|
2197
|
-
type: "array",
|
|
2198
|
-
description: "Description of the database as rich text array (max 100)",
|
|
2199
|
-
items: { type: "object" }
|
|
2200
|
-
}
|
|
2201
|
-
},
|
|
2202
|
-
required: ["properties"]
|
|
2203
|
-
}
|
|
2204
|
-
},
|
|
2205
|
-
executor: async (args) => {
|
|
2206
|
-
let token = getAccessToken();
|
|
2207
|
-
if (!token) {
|
|
2208
|
-
token = await requestNotionAccess();
|
|
2209
|
-
}
|
|
2210
|
-
try {
|
|
2211
|
-
return await callMCPTool(token, "notion-create-database", args);
|
|
2212
|
-
} catch (error) {
|
|
2213
|
-
return `Error creating Notion database: ${error instanceof Error ? error.message : String(error)}`;
|
|
2214
|
-
}
|
|
2105
|
+
|
|
2106
|
+
// src/lib/connectors/client.ts
|
|
2107
|
+
var DEFAULT_REFRESH_BEFORE_MS = 3e4;
|
|
2108
|
+
function createConnectorTokenGetter(client, provider, opts = {}) {
|
|
2109
|
+
const refreshBeforeMs = opts.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;
|
|
2110
|
+
const now = opts.now ?? Date.now;
|
|
2111
|
+
let cached = null;
|
|
2112
|
+
return async () => {
|
|
2113
|
+
if (cached && cached.expiresAt - refreshBeforeMs > now()) {
|
|
2114
|
+
return cached.accessToken;
|
|
2215
2115
|
}
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
name: "notion-update-data-source",
|
|
2223
|
-
description: "Update a Notion data source's title, description, property schema, or other attributes. Use null to remove a property. Provide only 'name' to rename a property.",
|
|
2224
|
-
arguments: {
|
|
2225
|
-
type: "object",
|
|
2226
|
-
properties: {
|
|
2227
|
-
data_source_id: {
|
|
2228
|
-
type: "string",
|
|
2229
|
-
description: "The ID of the data source to update (UUID). Can be a data source ID or database ID."
|
|
2230
|
-
},
|
|
2231
|
-
title: {
|
|
2232
|
-
type: "array",
|
|
2233
|
-
description: "New title as rich text array (max 100)",
|
|
2234
|
-
items: { type: "object" }
|
|
2235
|
-
},
|
|
2236
|
-
description: {
|
|
2237
|
-
type: "array",
|
|
2238
|
-
description: "New description as rich text array (max 100)",
|
|
2239
|
-
items: { type: "object" }
|
|
2240
|
-
},
|
|
2241
|
-
properties: {
|
|
2242
|
-
type: "object",
|
|
2243
|
-
description: "Property schema updates. Use null to remove, {name:'...'} to rename, or full definition to add/update."
|
|
2244
|
-
},
|
|
2245
|
-
is_inline: {
|
|
2246
|
-
type: "boolean",
|
|
2247
|
-
description: "Whether the database is inline"
|
|
2248
|
-
},
|
|
2249
|
-
in_trash: {
|
|
2250
|
-
type: "boolean",
|
|
2251
|
-
description: "Trash the data source (requires Notion UI to undo)"
|
|
2252
|
-
}
|
|
2253
|
-
},
|
|
2254
|
-
required: ["data_source_id"]
|
|
2255
|
-
}
|
|
2256
|
-
},
|
|
2257
|
-
executor: async (args) => {
|
|
2258
|
-
let token = getAccessToken();
|
|
2259
|
-
if (!token) {
|
|
2260
|
-
token = await requestNotionAccess();
|
|
2261
|
-
}
|
|
2262
|
-
try {
|
|
2263
|
-
return await callMCPTool(token, "notion-update-data-source", args);
|
|
2264
|
-
} catch (error) {
|
|
2265
|
-
return `Error updating Notion data source: ${error instanceof Error ? error.message : String(error)}`;
|
|
2266
|
-
}
|
|
2116
|
+
let result;
|
|
2117
|
+
try {
|
|
2118
|
+
result = await client.mintConnectorToken(provider);
|
|
2119
|
+
} catch {
|
|
2120
|
+
cached = null;
|
|
2121
|
+
return null;
|
|
2267
2122
|
}
|
|
2268
|
-
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
return {
|
|
2272
|
-
type: "function",
|
|
2273
|
-
function: {
|
|
2274
|
-
name: "notion-create-comment",
|
|
2275
|
-
description: "Add a comment to a Notion page. Supports page-level comments, content-specific comments (using selection_with_ellipsis), or replies to existing discussions (using discussion_id).",
|
|
2276
|
-
arguments: {
|
|
2277
|
-
type: "object",
|
|
2278
|
-
properties: {
|
|
2279
|
-
page_id: {
|
|
2280
|
-
type: "string",
|
|
2281
|
-
description: "The ID of the page to comment on (with or without dashes)"
|
|
2282
|
-
},
|
|
2283
|
-
rich_text: {
|
|
2284
|
-
type: "array",
|
|
2285
|
-
description: "Comment content as rich text array (max 100). Objects can be text, mention, or equation.",
|
|
2286
|
-
items: {
|
|
2287
|
-
type: "object",
|
|
2288
|
-
properties: {
|
|
2289
|
-
text: {
|
|
2290
|
-
type: "object",
|
|
2291
|
-
properties: {
|
|
2292
|
-
content: { type: "string" }
|
|
2293
|
-
},
|
|
2294
|
-
required: ["content"]
|
|
2295
|
-
}
|
|
2296
|
-
},
|
|
2297
|
-
required: ["text"]
|
|
2298
|
-
}
|
|
2299
|
-
},
|
|
2300
|
-
discussion_id: {
|
|
2301
|
-
type: "string",
|
|
2302
|
-
description: "ID or URL of an existing discussion to reply to"
|
|
2303
|
-
},
|
|
2304
|
-
selection_with_ellipsis: {
|
|
2305
|
-
type: "string",
|
|
2306
|
-
description: "Unique start and end snippet (~10 chars each with ellipsis) of content to comment on"
|
|
2307
|
-
}
|
|
2308
|
-
},
|
|
2309
|
-
required: ["page_id", "rich_text"]
|
|
2310
|
-
}
|
|
2311
|
-
},
|
|
2312
|
-
executor: async (args) => {
|
|
2313
|
-
let token = getAccessToken();
|
|
2314
|
-
if (!token) {
|
|
2315
|
-
token = await requestNotionAccess();
|
|
2316
|
-
}
|
|
2317
|
-
try {
|
|
2318
|
-
return await callMCPTool(token, "notion-create-comment", args);
|
|
2319
|
-
} catch (error) {
|
|
2320
|
-
return `Error creating Notion comment: ${error instanceof Error ? error.message : String(error)}`;
|
|
2321
|
-
}
|
|
2123
|
+
if (result.ok) {
|
|
2124
|
+
cached = { accessToken: result.accessToken, expiresAt: result.expiresAt };
|
|
2125
|
+
return result.accessToken;
|
|
2322
2126
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
return {
|
|
2327
|
-
type: "function",
|
|
2328
|
-
function: {
|
|
2329
|
-
name: "notion-get-comments",
|
|
2330
|
-
description: "Get comments and discussions from a Notion page. Returns discussions with full comment content.",
|
|
2331
|
-
arguments: {
|
|
2332
|
-
type: "object",
|
|
2333
|
-
properties: {
|
|
2334
|
-
page_id: {
|
|
2335
|
-
type: "string",
|
|
2336
|
-
description: "Identifier for the Notion page"
|
|
2337
|
-
},
|
|
2338
|
-
include_all_blocks: {
|
|
2339
|
-
type: "boolean",
|
|
2340
|
-
description: "Include discussions on child blocks (default: false)"
|
|
2341
|
-
},
|
|
2342
|
-
include_resolved: {
|
|
2343
|
-
type: "boolean",
|
|
2344
|
-
description: "Include resolved discussions (default: false)"
|
|
2345
|
-
},
|
|
2346
|
-
discussion_id: {
|
|
2347
|
-
type: "string",
|
|
2348
|
-
description: "Fetch a specific discussion by ID or URL (e.g., discussion://pageId/blockId/discussionId)"
|
|
2349
|
-
}
|
|
2350
|
-
},
|
|
2351
|
-
required: ["page_id"]
|
|
2352
|
-
}
|
|
2353
|
-
},
|
|
2354
|
-
executor: async (args) => {
|
|
2355
|
-
let token = getAccessToken();
|
|
2356
|
-
if (!token) {
|
|
2357
|
-
token = await requestNotionAccess();
|
|
2358
|
-
}
|
|
2359
|
-
try {
|
|
2360
|
-
return await callMCPTool(token, "notion-get-comments", args);
|
|
2361
|
-
} catch (error) {
|
|
2362
|
-
return `Error retrieving Notion comments: ${error instanceof Error ? error.message : String(error)}`;
|
|
2363
|
-
}
|
|
2127
|
+
cached = null;
|
|
2128
|
+
if (opts.onNotConnected) {
|
|
2129
|
+
return opts.onNotConnected(result.error);
|
|
2364
2130
|
}
|
|
2131
|
+
return null;
|
|
2365
2132
|
};
|
|
2366
2133
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
if (!token) {
|
|
2398
|
-
token = await requestNotionAccess();
|
|
2399
|
-
}
|
|
2400
|
-
try {
|
|
2401
|
-
return await callMCPTool(token, "notion-get-users", args);
|
|
2402
|
-
} catch (error) {
|
|
2403
|
-
return `Error listing Notion users: ${error instanceof Error ? error.message : String(error)}`;
|
|
2134
|
+
|
|
2135
|
+
// src/tools/notion.ts
|
|
2136
|
+
var MCP_HTTP_ENDPOINT = "https://mcp.notion.com/mcp";
|
|
2137
|
+
var MCP_SSE_ENDPOINT = "https://mcp.notion.com/sse";
|
|
2138
|
+
var MAX_CONTENT_LENGTH2 = 5e4;
|
|
2139
|
+
function truncateToolResult(result) {
|
|
2140
|
+
if (result === void 0 || result === null) return result;
|
|
2141
|
+
const stringified = typeof result === "string" ? result : JSON.stringify(result);
|
|
2142
|
+
if (stringified.length <= MAX_CONTENT_LENGTH2) {
|
|
2143
|
+
return result;
|
|
2144
|
+
}
|
|
2145
|
+
return stringified.slice(0, MAX_CONTENT_LENGTH2) + `
|
|
2146
|
+
|
|
2147
|
+
... (content truncated, showing first ${MAX_CONTENT_LENGTH2} characters of ${stringified.length})`;
|
|
2148
|
+
}
|
|
2149
|
+
var requestIdCounter = Math.floor(Math.random() * 1e6);
|
|
2150
|
+
var sessionCache = /* @__PURE__ */ new Map();
|
|
2151
|
+
function generateRequestId() {
|
|
2152
|
+
return ++requestIdCounter;
|
|
2153
|
+
}
|
|
2154
|
+
function parseSSEResponse(text) {
|
|
2155
|
+
const events = text.split(/\n\n+/);
|
|
2156
|
+
let lastEventData = "";
|
|
2157
|
+
for (const event of events) {
|
|
2158
|
+
const dataLines = [];
|
|
2159
|
+
for (const line of event.split("\n")) {
|
|
2160
|
+
if (line.startsWith("data: ")) {
|
|
2161
|
+
dataLines.push(line.slice(6));
|
|
2162
|
+
} else if (line.startsWith("data:")) {
|
|
2163
|
+
dataLines.push(line.slice(5));
|
|
2404
2164
|
}
|
|
2405
2165
|
}
|
|
2406
|
-
|
|
2166
|
+
if (dataLines.length > 0) {
|
|
2167
|
+
lastEventData = dataLines.join("\n");
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
if (!lastEventData) {
|
|
2171
|
+
throw new Error("No data found in SSE response");
|
|
2172
|
+
}
|
|
2173
|
+
return JSON.parse(lastEventData);
|
|
2407
2174
|
}
|
|
2408
|
-
function
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
}
|
|
2429
|
-
try {
|
|
2430
|
-
return await callMCPTool(token, "notion-get-teams", args);
|
|
2431
|
-
} catch (error) {
|
|
2432
|
-
return `Error retrieving Notion teams: ${error instanceof Error ? error.message : String(error)}`;
|
|
2175
|
+
async function parseResponseBody(response) {
|
|
2176
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
2177
|
+
const text = await response.text();
|
|
2178
|
+
if (contentType.includes("text/event-stream") || text.startsWith("event:") || text.startsWith("data:")) {
|
|
2179
|
+
return parseSSEResponse(text);
|
|
2180
|
+
}
|
|
2181
|
+
return JSON.parse(text);
|
|
2182
|
+
}
|
|
2183
|
+
async function initializeMCPSession(accessToken) {
|
|
2184
|
+
const requestId = generateRequestId();
|
|
2185
|
+
const initRequest = {
|
|
2186
|
+
jsonrpc: "2.0",
|
|
2187
|
+
id: requestId,
|
|
2188
|
+
method: "initialize",
|
|
2189
|
+
params: {
|
|
2190
|
+
protocolVersion: "2024-11-05",
|
|
2191
|
+
capabilities: {},
|
|
2192
|
+
clientInfo: {
|
|
2193
|
+
name: "Anuma",
|
|
2194
|
+
version: "1.0.0"
|
|
2433
2195
|
}
|
|
2434
2196
|
}
|
|
2435
2197
|
};
|
|
2198
|
+
const response = await fetch(MCP_HTTP_ENDPOINT, {
|
|
2199
|
+
method: "POST",
|
|
2200
|
+
headers: {
|
|
2201
|
+
"Content-Type": "application/json",
|
|
2202
|
+
Accept: "application/json, text/event-stream",
|
|
2203
|
+
Authorization: `Bearer ${accessToken}`
|
|
2204
|
+
},
|
|
2205
|
+
body: JSON.stringify(initRequest)
|
|
2206
|
+
});
|
|
2207
|
+
if (!response.ok) {
|
|
2208
|
+
const errorBody = await response.text().catch(() => "");
|
|
2209
|
+
throw new Error(
|
|
2210
|
+
`MCP initialization failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
const sessionId = response.headers.get("Mcp-Session-Id");
|
|
2214
|
+
if (!sessionId) {
|
|
2215
|
+
throw new Error("No Mcp-Session-Id returned from initialization");
|
|
2216
|
+
}
|
|
2217
|
+
const jsonRpcResponse = await parseResponseBody(response);
|
|
2218
|
+
if (jsonRpcResponse.error) {
|
|
2219
|
+
const err = jsonRpcResponse.error;
|
|
2220
|
+
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
2221
|
+
throw new Error(`MCP initialization error: ${errMsg}`);
|
|
2222
|
+
}
|
|
2223
|
+
await fetch(MCP_HTTP_ENDPOINT, {
|
|
2224
|
+
method: "POST",
|
|
2225
|
+
headers: {
|
|
2226
|
+
"Content-Type": "application/json",
|
|
2227
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2228
|
+
"Mcp-Session-Id": sessionId
|
|
2229
|
+
},
|
|
2230
|
+
body: JSON.stringify({
|
|
2231
|
+
jsonrpc: "2.0",
|
|
2232
|
+
method: "notifications/initialized"
|
|
2233
|
+
})
|
|
2234
|
+
});
|
|
2235
|
+
sessionCache.set(accessToken, sessionId);
|
|
2236
|
+
return sessionId;
|
|
2436
2237
|
}
|
|
2437
|
-
function
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
createNotionCreatePagesTool(getAccessToken, requestNotionAccess),
|
|
2444
|
-
createNotionUpdatePageTool(getAccessToken, requestNotionAccess),
|
|
2445
|
-
createNotionMovePagesTool(getAccessToken, requestNotionAccess),
|
|
2446
|
-
createNotionDuplicatePageTool(getAccessToken, requestNotionAccess),
|
|
2447
|
-
// Data Sources (Databases)
|
|
2448
|
-
createNotionCreateDatabaseTool(getAccessToken, requestNotionAccess),
|
|
2449
|
-
createNotionUpdateDataSourceTool(getAccessToken, requestNotionAccess),
|
|
2450
|
-
// Comments
|
|
2451
|
-
createNotionCreateCommentTool(getAccessToken, requestNotionAccess),
|
|
2452
|
-
createNotionGetCommentsTool(getAccessToken, requestNotionAccess),
|
|
2453
|
-
// Users & Teams
|
|
2454
|
-
createNotionGetUsersTool(getAccessToken, requestNotionAccess),
|
|
2455
|
-
createNotionGetTeamsTool(getAccessToken, requestNotionAccess)
|
|
2456
|
-
];
|
|
2238
|
+
async function ensureMCPSession(accessToken) {
|
|
2239
|
+
const cached = sessionCache.get(accessToken);
|
|
2240
|
+
if (cached) {
|
|
2241
|
+
return cached;
|
|
2242
|
+
}
|
|
2243
|
+
return initializeMCPSession(accessToken);
|
|
2457
2244
|
}
|
|
2458
|
-
function
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2245
|
+
async function callMCPTool(accessToken, toolName, args) {
|
|
2246
|
+
const sessionId = await ensureMCPSession(accessToken);
|
|
2247
|
+
const requestId = generateRequestId();
|
|
2248
|
+
const jsonRpcRequest = {
|
|
2249
|
+
jsonrpc: "2.0",
|
|
2250
|
+
id: requestId,
|
|
2251
|
+
method: "tools/call",
|
|
2252
|
+
params: {
|
|
2253
|
+
name: toolName,
|
|
2254
|
+
arguments: args
|
|
2255
|
+
}
|
|
2462
2256
|
};
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
if (!
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2257
|
+
const response = await fetch(MCP_HTTP_ENDPOINT, {
|
|
2258
|
+
method: "POST",
|
|
2259
|
+
headers: {
|
|
2260
|
+
"Content-Type": "application/json",
|
|
2261
|
+
Accept: "application/json, text/event-stream",
|
|
2262
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2263
|
+
"Mcp-Session-Id": sessionId
|
|
2264
|
+
},
|
|
2265
|
+
body: JSON.stringify(jsonRpcRequest)
|
|
2266
|
+
});
|
|
2267
|
+
if (!response.ok) {
|
|
2268
|
+
if (response.status === 400 || response.status === 401) {
|
|
2269
|
+
sessionCache.delete(accessToken);
|
|
2270
|
+
const newSessionId = await initializeMCPSession(accessToken);
|
|
2271
|
+
const retryResponse = await fetch(MCP_HTTP_ENDPOINT, {
|
|
2272
|
+
method: "POST",
|
|
2273
|
+
headers: {
|
|
2274
|
+
"Content-Type": "application/json",
|
|
2275
|
+
Accept: "application/json, text/event-stream",
|
|
2276
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2277
|
+
"Mcp-Session-Id": newSessionId
|
|
2278
|
+
},
|
|
2279
|
+
body: JSON.stringify(jsonRpcRequest)
|
|
2280
|
+
});
|
|
2281
|
+
if (!retryResponse.ok) {
|
|
2282
|
+
const errorBody2 = await retryResponse.text().catch(() => "");
|
|
2283
|
+
throw new Error(
|
|
2284
|
+
`MCP request failed: ${retryResponse.status} ${retryResponse.statusText}${errorBody2 ? ` - ${errorBody2}` : ""}`
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
const retryJsonRpcResponse = await parseResponseBody(retryResponse);
|
|
2288
|
+
if (retryJsonRpcResponse.error) {
|
|
2289
|
+
const err = retryJsonRpcResponse.error;
|
|
2290
|
+
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
2291
|
+
throw new Error(`MCP tool error: ${errMsg}`);
|
|
2292
|
+
}
|
|
2293
|
+
return truncateToolResult(retryJsonRpcResponse.result);
|
|
2479
2294
|
}
|
|
2295
|
+
const errorBody = await response.text().catch(() => "");
|
|
2296
|
+
throw new Error(
|
|
2297
|
+
`MCP request failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
2298
|
+
);
|
|
2480
2299
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
throw new Error(
|
|
2300
|
+
const jsonRpcResponse = await parseResponseBody(response);
|
|
2301
|
+
if (jsonRpcResponse.error) {
|
|
2302
|
+
const err = jsonRpcResponse.error;
|
|
2303
|
+
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
2304
|
+
throw new Error(`MCP tool error: ${errMsg}`);
|
|
2486
2305
|
}
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2306
|
+
return truncateToolResult(jsonRpcResponse.result);
|
|
2307
|
+
}
|
|
2308
|
+
var NOTION_PROVIDER = "notion";
|
|
2309
|
+
function directRunner(getAccessToken, requestNotionAccess) {
|
|
2310
|
+
return async (toolName, args) => {
|
|
2311
|
+
let token = getAccessToken();
|
|
2312
|
+
if (!token) {
|
|
2313
|
+
token = await requestNotionAccess();
|
|
2314
|
+
}
|
|
2315
|
+
return callMCPTool(token, toolName, args);
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
function readField(json, key) {
|
|
2319
|
+
return typeof json === "object" && json !== null ? json[key] : void 0;
|
|
2320
|
+
}
|
|
2321
|
+
function readString(json, key) {
|
|
2322
|
+
const value = readField(json, key);
|
|
2323
|
+
return typeof value === "string" ? value : void 0;
|
|
2324
|
+
}
|
|
2325
|
+
function notionConnectorError(status, json) {
|
|
2326
|
+
const code = readField(json, "code");
|
|
2327
|
+
if (status === 503) {
|
|
2328
|
+
return code === "upstream_unavailable" ? buildConnectorErrorResult("upstream_unavailable", NOTION_PROVIDER) : null;
|
|
2329
|
+
}
|
|
2330
|
+
if (status !== 401 && status !== 403 && status !== 412) return null;
|
|
2331
|
+
switch (code) {
|
|
2332
|
+
case "scope_not_covered": {
|
|
2333
|
+
const missingScopes = readField(json, "missing_scopes");
|
|
2334
|
+
return buildConnectorErrorResult(
|
|
2335
|
+
"scope_not_covered",
|
|
2336
|
+
NOTION_PROVIDER,
|
|
2337
|
+
readString(json, "connect_url"),
|
|
2338
|
+
Array.isArray(missingScopes) ? { missingScopes } : void 0
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
case "insufficient_scope":
|
|
2342
|
+
return buildConnectorErrorResult("insufficient_scope", NOTION_PROVIDER, void 0, {
|
|
2343
|
+
required: readString(json, "required")
|
|
2344
|
+
});
|
|
2345
|
+
case "upstream_unavailable":
|
|
2346
|
+
return buildConnectorErrorResult("upstream_unavailable", NOTION_PROVIDER);
|
|
2347
|
+
default:
|
|
2348
|
+
return buildConnectorErrorResult(
|
|
2349
|
+
"connector_not_connected",
|
|
2350
|
+
NOTION_PROVIDER,
|
|
2351
|
+
readString(json, "connect_url")
|
|
2352
|
+
);
|
|
2490
2353
|
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
Accept: "application/vnd.github+json",
|
|
2500
|
-
"Content-Type": "application/json",
|
|
2501
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
2502
|
-
...options.headers
|
|
2354
|
+
}
|
|
2355
|
+
function proxyRunner(callMcp) {
|
|
2356
|
+
return async (toolName, args) => {
|
|
2357
|
+
const { status, json } = await callMcp(toolName, args);
|
|
2358
|
+
if (status >= 200 && status < 300) {
|
|
2359
|
+
const result = readField(json, "result");
|
|
2360
|
+
if (result === void 0) {
|
|
2361
|
+
throw new Error(`Notion returned no result (${status})`);
|
|
2503
2362
|
}
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2363
|
+
return truncateToolResult(result);
|
|
2364
|
+
}
|
|
2365
|
+
const connectorError = notionConnectorError(status, json);
|
|
2366
|
+
if (connectorError) return connectorError;
|
|
2367
|
+
const message = readField(json, "error");
|
|
2368
|
+
throw new Error(`${typeof message === "string" ? message : JSON.stringify(json)} (${status})`);
|
|
2369
|
+
};
|
|
2508
2370
|
}
|
|
2509
|
-
function
|
|
2371
|
+
function withNotionExecutor(run, errorPrefix, tool) {
|
|
2510
2372
|
return {
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
name: "github_get_authenticated_user",
|
|
2514
|
-
description: "Get the authenticated GitHub user's profile including username and organizations. Call this first to discover the user's identity before making other GitHub API calls.",
|
|
2515
|
-
parameters: {
|
|
2516
|
-
type: "object",
|
|
2517
|
-
properties: {}
|
|
2518
|
-
}
|
|
2519
|
-
},
|
|
2520
|
-
executor: async () => {
|
|
2521
|
-
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2522
|
-
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2373
|
+
...tool,
|
|
2374
|
+
executor: async (args) => {
|
|
2523
2375
|
try {
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
const orgsResp = await githubFetch(token, "/user/orgs?per_page=100");
|
|
2528
|
-
let orgs = [];
|
|
2529
|
-
if (orgsResp.ok) {
|
|
2530
|
-
const orgsData = await orgsResp.json();
|
|
2531
|
-
orgs = orgsData.map((o) => o.login);
|
|
2532
|
-
}
|
|
2533
|
-
return {
|
|
2534
|
-
login: user.login,
|
|
2535
|
-
name: user.name,
|
|
2536
|
-
email: user.email,
|
|
2537
|
-
organizations: orgs,
|
|
2538
|
-
public_repos: user.public_repos,
|
|
2539
|
-
html_url: user.html_url
|
|
2540
|
-
};
|
|
2541
|
-
} catch (err) {
|
|
2542
|
-
getLogger().error("github_get_authenticated_user error", err);
|
|
2543
|
-
return "Error: Failed to get user profile.";
|
|
2376
|
+
return await run(tool.function.name, args);
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
return `${errorPrefix}: ${error instanceof Error ? error.message : String(error)}`;
|
|
2544
2379
|
}
|
|
2545
2380
|
}
|
|
2546
2381
|
};
|
|
2547
2382
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
if (text.length <= maxLen) return text;
|
|
2551
|
-
return text.slice(0, maxLen) + `
|
|
2552
|
-
|
|
2553
|
-
... (truncated, ${text.length - maxLen} characters omitted)`;
|
|
2554
|
-
}
|
|
2555
|
-
function createGitHubApiTool(getAccessToken, requestAccess) {
|
|
2556
|
-
return {
|
|
2383
|
+
function notionSearchTool(run) {
|
|
2384
|
+
return withNotionExecutor(run, "Error searching Notion", {
|
|
2557
2385
|
type: "function",
|
|
2558
2386
|
function: {
|
|
2559
|
-
name: "
|
|
2560
|
-
description:
|
|
2561
|
-
|
|
2562
|
-
Examples:
|
|
2563
|
-
- List PRs: GET /repos/{owner}/{repo}/pulls?state=open
|
|
2564
|
-
- Get file: GET /repos/{owner}/{repo}/contents/{path}
|
|
2565
|
-
- Create issue: POST /repos/{owner}/{repo}/issues with body {"title": "...", "body": "..."}
|
|
2566
|
-
- Search repos: GET /search/repositories?q={query}
|
|
2567
|
-
|
|
2568
|
-
IMPORTANT: For write operations (POST, PUT, PATCH, DELETE), always confirm with the user before executing. This includes creating issues, opening PRs, merging, committing files, and submitting reviews.`,
|
|
2569
|
-
parameters: {
|
|
2387
|
+
name: "notion-search",
|
|
2388
|
+
description: "Semantic search over Notion workspace and connected sources (Slack, Google Drive, Github, Jira, etc). Can also search for users by name or email.",
|
|
2389
|
+
arguments: {
|
|
2570
2390
|
type: "object",
|
|
2571
2391
|
properties: {
|
|
2572
|
-
|
|
2392
|
+
query: {
|
|
2573
2393
|
type: "string",
|
|
2574
|
-
|
|
2575
|
-
description: "HTTP method"
|
|
2394
|
+
description: "Semantic search query over workspace and connected sources. For user search, a substring to match against name or email."
|
|
2576
2395
|
},
|
|
2577
|
-
|
|
2396
|
+
query_type: {
|
|
2578
2397
|
type: "string",
|
|
2579
|
-
|
|
2398
|
+
enum: ["internal", "user"],
|
|
2399
|
+
description: "Type of search: 'internal' for workspace/content search, 'user' for user search"
|
|
2580
2400
|
},
|
|
2581
|
-
|
|
2401
|
+
content_search_mode: {
|
|
2402
|
+
type: "string",
|
|
2403
|
+
enum: ["workspace_search", "ai_search"],
|
|
2404
|
+
description: "Override auto-selection of AI search vs workspace search"
|
|
2405
|
+
},
|
|
2406
|
+
data_source_url: {
|
|
2407
|
+
type: "string",
|
|
2408
|
+
description: "URL of a data source to search within"
|
|
2409
|
+
},
|
|
2410
|
+
page_url: {
|
|
2411
|
+
type: "string",
|
|
2412
|
+
description: "URL or ID of a page to search within"
|
|
2413
|
+
},
|
|
2414
|
+
teamspace_id: {
|
|
2415
|
+
type: "string",
|
|
2416
|
+
description: "ID of a teamspace to restrict search results to"
|
|
2417
|
+
},
|
|
2418
|
+
filters: {
|
|
2582
2419
|
type: "object",
|
|
2583
|
-
description: "
|
|
2584
|
-
additionalProperties: true
|
|
2420
|
+
description: "Filters for search results (only for query_type 'internal'). Supports created_date_range {start_date, end_date} and created_by_user_ids array."
|
|
2585
2421
|
}
|
|
2586
2422
|
},
|
|
2587
|
-
required: ["
|
|
2588
|
-
}
|
|
2589
|
-
},
|
|
2590
|
-
executor: async (args) => {
|
|
2591
|
-
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2592
|
-
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2593
|
-
const method = args.method;
|
|
2594
|
-
const path = args.path;
|
|
2595
|
-
const body = args.body;
|
|
2596
|
-
try {
|
|
2597
|
-
const resp = await githubFetch(token, path, {
|
|
2598
|
-
method,
|
|
2599
|
-
body: body ? JSON.stringify(body) : void 0
|
|
2600
|
-
});
|
|
2601
|
-
const text = await resp.text();
|
|
2602
|
-
if (!resp.ok) {
|
|
2603
|
-
return `Error: GitHub API ${method} ${path} returned ${resp.status}: ${truncate(text, 500)}`;
|
|
2604
|
-
}
|
|
2605
|
-
try {
|
|
2606
|
-
const data = JSON.parse(text);
|
|
2607
|
-
const json = JSON.stringify(data);
|
|
2608
|
-
return truncate(json, MAX_RESPONSE_SIZE);
|
|
2609
|
-
} catch {
|
|
2610
|
-
return truncate(text, MAX_RESPONSE_SIZE);
|
|
2611
|
-
}
|
|
2612
|
-
} catch (err) {
|
|
2613
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
2614
|
-
return `Error: GitHub API request timed out after ${FETCH_TIMEOUT_MS / 1e3}s.`;
|
|
2615
|
-
}
|
|
2616
|
-
getLogger().error("github_api error", err);
|
|
2617
|
-
return `Error: GitHub API request failed: ${err instanceof Error ? err.message : "unknown error"}`;
|
|
2423
|
+
required: ["query"]
|
|
2618
2424
|
}
|
|
2619
2425
|
}
|
|
2620
|
-
};
|
|
2621
|
-
}
|
|
2622
|
-
function createGitHubTools(getAccessToken, requestGitHubAccess) {
|
|
2623
|
-
return [
|
|
2624
|
-
createGetAuthenticatedUserTool(getAccessToken, requestGitHubAccess),
|
|
2625
|
-
createGitHubApiTool(getAccessToken, requestGitHubAccess)
|
|
2626
|
-
];
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
// src/lib/connectors/agentres/errors.ts
|
|
2630
|
-
var AgentresError = class extends Error {
|
|
2631
|
-
constructor(status, code, message, retryable, nextStep) {
|
|
2632
|
-
super(message);
|
|
2633
|
-
this.name = "AgentresError";
|
|
2634
|
-
this.status = status;
|
|
2635
|
-
this.code = code;
|
|
2636
|
-
this.retryable = retryable;
|
|
2637
|
-
this.nextStep = nextStep;
|
|
2638
|
-
}
|
|
2639
|
-
};
|
|
2640
|
-
var SiwxUnsupportedError = class extends Error {
|
|
2641
|
-
constructor(message) {
|
|
2642
|
-
super(message);
|
|
2643
|
-
this.name = "SiwxUnsupportedError";
|
|
2644
|
-
}
|
|
2645
|
-
};
|
|
2646
|
-
var AgentresPathError = class extends Error {
|
|
2647
|
-
constructor(message) {
|
|
2648
|
-
super(message);
|
|
2649
|
-
this.name = "AgentresPathError";
|
|
2650
|
-
}
|
|
2651
|
-
};
|
|
2652
|
-
var SiwxChallengeError = class extends Error {
|
|
2653
|
-
constructor(message, options) {
|
|
2654
|
-
super(message);
|
|
2655
|
-
this.name = "SiwxChallengeError";
|
|
2656
|
-
this.cause = options?.cause;
|
|
2657
|
-
}
|
|
2658
|
-
};
|
|
2659
|
-
function parseAgentresError(status, body) {
|
|
2660
|
-
const stated = statedError(body);
|
|
2661
|
-
if (!stated) {
|
|
2662
|
-
return new AgentresError(
|
|
2663
|
-
status,
|
|
2664
|
-
`HTTP_${status}`,
|
|
2665
|
-
body.trim() || `HTTP ${status}`,
|
|
2666
|
-
status >= 500
|
|
2667
|
-
);
|
|
2668
|
-
}
|
|
2669
|
-
const code = typeof stated.code === "string" ? stated.code : `HTTP_${status}`;
|
|
2670
|
-
const message = typeof stated.message === "string" ? stated.message : `HTTP ${status}`;
|
|
2671
|
-
const retryable = typeof stated.retryable === "boolean" ? stated.retryable : status >= 500;
|
|
2672
|
-
const nextStep = typeof stated.next_step === "string" ? stated.next_step : void 0;
|
|
2673
|
-
return new AgentresError(status, code, message, retryable, nextStep);
|
|
2426
|
+
});
|
|
2674
2427
|
}
|
|
2675
|
-
function
|
|
2676
|
-
|
|
2677
|
-
try {
|
|
2678
|
-
parsed = JSON.parse(body);
|
|
2679
|
-
} catch {
|
|
2680
|
-
return null;
|
|
2681
|
-
}
|
|
2682
|
-
const envelope = parsed;
|
|
2683
|
-
const stated = typeof envelope?.error === "object" && envelope.error !== null ? envelope.error : null;
|
|
2684
|
-
if (!stated) {
|
|
2685
|
-
return null;
|
|
2686
|
-
}
|
|
2687
|
-
const fields = stated;
|
|
2688
|
-
if (typeof fields.code !== "string" && typeof fields.message !== "string") {
|
|
2689
|
-
return null;
|
|
2690
|
-
}
|
|
2691
|
-
return fields;
|
|
2428
|
+
function createNotionSearchTool(getAccessToken, requestNotionAccess) {
|
|
2429
|
+
return notionSearchTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2692
2430
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
function
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2431
|
+
function notionFetchTool(run) {
|
|
2432
|
+
return withNotionExecutor(run, "Error fetching Notion page", {
|
|
2433
|
+
type: "function",
|
|
2434
|
+
function: {
|
|
2435
|
+
name: "notion-fetch",
|
|
2436
|
+
description: "Retrieve a Notion page or database by URL or ID. Pages return enhanced Markdown. Databases return all data sources.",
|
|
2437
|
+
arguments: {
|
|
2438
|
+
type: "object",
|
|
2439
|
+
properties: {
|
|
2440
|
+
id: {
|
|
2441
|
+
type: "string",
|
|
2442
|
+
description: "The ID or URL of the Notion page/database. Supports notion.so URLs, Notion Sites URLs (*.notion.site), and raw UUIDs."
|
|
2443
|
+
},
|
|
2444
|
+
include_transcript: {
|
|
2445
|
+
type: "boolean",
|
|
2446
|
+
description: "Include transcript if available"
|
|
2447
|
+
},
|
|
2448
|
+
include_discussions: {
|
|
2449
|
+
type: "boolean",
|
|
2450
|
+
description: "Include discussion counts and inline discussion markers"
|
|
2451
|
+
}
|
|
2452
|
+
},
|
|
2453
|
+
required: ["id"]
|
|
2454
|
+
}
|
|
2706
2455
|
}
|
|
2707
|
-
|
|
2708
|
-
}
|
|
2709
|
-
return base64;
|
|
2710
|
-
}
|
|
2711
|
-
function base64ToUint8Array(base64) {
|
|
2712
|
-
if (typeof Buffer !== "undefined") {
|
|
2713
|
-
const buf = Buffer.from(base64, "base64");
|
|
2714
|
-
return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
|
|
2715
|
-
}
|
|
2716
|
-
const binary = atob(base64);
|
|
2717
|
-
const bytes = new Uint8Array(binary.length);
|
|
2718
|
-
for (let i = 0; i < binary.length; i++) {
|
|
2719
|
-
bytes[i] = binary.charCodeAt(i);
|
|
2720
|
-
}
|
|
2721
|
-
return bytes;
|
|
2456
|
+
});
|
|
2722
2457
|
}
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
2726
|
-
var SIWX_HEADER = "SIGN-IN-WITH-X";
|
|
2727
|
-
var SIWX_EXTENSION = "sign-in-with-x";
|
|
2728
|
-
var SOLANA_NAMESPACE = "solana:";
|
|
2729
|
-
var ED25519_SIGNATURE_BYTES = 64;
|
|
2730
|
-
function parseChallenge(paymentRequiredHeader, expectedOrigin) {
|
|
2731
|
-
const decoded = decodeHeader(paymentRequiredHeader);
|
|
2732
|
-
const extensions = asRecord(decoded.extensions);
|
|
2733
|
-
const extension = extensions ? asRecord(extensions[SIWX_EXTENSION]) : null;
|
|
2734
|
-
if (!extension) {
|
|
2735
|
-
throw new SiwxUnsupportedError(
|
|
2736
|
-
"the 402 carries no sign-in-with-x extension, so this endpoint is paid rather than identity-only"
|
|
2737
|
-
);
|
|
2738
|
-
}
|
|
2739
|
-
const info = asRecord(extension.info);
|
|
2740
|
-
if (!info) {
|
|
2741
|
-
throw new SiwxChallengeError("the sign-in-with-x extension has no info block");
|
|
2742
|
-
}
|
|
2743
|
-
const chain = solanaChain(extension.supportedChains);
|
|
2744
|
-
const challenge = {
|
|
2745
|
-
domain: requireString(info, "domain"),
|
|
2746
|
-
uri: requireString(info, "uri"),
|
|
2747
|
-
version: requireString(info, "version"),
|
|
2748
|
-
statement: requireString(info, "statement"),
|
|
2749
|
-
nonce: requireString(info, "nonce"),
|
|
2750
|
-
issuedAt: requireString(info, "issuedAt"),
|
|
2751
|
-
resources: requireResources(info),
|
|
2752
|
-
chainId: chain.chainId,
|
|
2753
|
-
signingType: chain.signingType
|
|
2754
|
-
};
|
|
2755
|
-
assertScopedTo(challenge, expectedOrigin);
|
|
2756
|
-
return challenge;
|
|
2458
|
+
function createNotionFetchTool(getAccessToken, requestNotionAccess) {
|
|
2459
|
+
return notionFetchTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2757
2460
|
}
|
|
2758
|
-
function
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2461
|
+
function notionCreatePagesTool(run) {
|
|
2462
|
+
return withNotionExecutor(run, "Error creating Notion page", {
|
|
2463
|
+
type: "function",
|
|
2464
|
+
function: {
|
|
2465
|
+
name: "notion-create-pages",
|
|
2466
|
+
description: "Create one or more Notion pages. All pages in a single call share the same parent. Properties use simplified format: title is a string, dates use date:{prop}:start format, checkboxes use __YES__/__NO__. Content is Notion-flavored Markdown.",
|
|
2467
|
+
arguments: {
|
|
2468
|
+
type: "object",
|
|
2469
|
+
properties: {
|
|
2470
|
+
pages: {
|
|
2471
|
+
type: "array",
|
|
2472
|
+
description: "Array of page objects to create (max 100)",
|
|
2473
|
+
items: {
|
|
2474
|
+
type: "object",
|
|
2475
|
+
properties: {
|
|
2476
|
+
properties: {
|
|
2477
|
+
type: "object",
|
|
2478
|
+
description: "JSON map of property names to values. Title is a string, numbers are JS numbers, checkboxes use __YES__/__NO__, dates use date:{prop}:start/end/is_datetime format."
|
|
2479
|
+
},
|
|
2480
|
+
content: {
|
|
2481
|
+
type: "string",
|
|
2482
|
+
description: "Page content in Notion-flavored Markdown format"
|
|
2483
|
+
}
|
|
2484
|
+
},
|
|
2485
|
+
required: ["properties", "content"]
|
|
2486
|
+
}
|
|
2487
|
+
},
|
|
2488
|
+
parent: {
|
|
2489
|
+
type: "object",
|
|
2490
|
+
description: "Parent for all pages. Use {type:'page_id', page_id:'...'}, {type:'database_id', database_id:'...'}, or {type:'data_source_id', data_source_id:'...'}. Do NOT pass this field to create standalone private pages."
|
|
2491
|
+
}
|
|
2492
|
+
},
|
|
2493
|
+
required: ["pages"]
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2771
2497
|
}
|
|
2772
|
-
function
|
|
2773
|
-
|
|
2774
|
-
return new URL(value);
|
|
2775
|
-
} catch (cause) {
|
|
2776
|
-
throw new SiwxChallengeError(`${description} is not a URL`, { cause });
|
|
2777
|
-
}
|
|
2498
|
+
function createNotionCreatePagesTool(getAccessToken, requestNotionAccess) {
|
|
2499
|
+
return notionCreatePagesTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2778
2500
|
}
|
|
2779
|
-
function
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2501
|
+
function notionUpdatePageTool(run) {
|
|
2502
|
+
return withNotionExecutor(run, "Error updating Notion page", {
|
|
2503
|
+
type: "function",
|
|
2504
|
+
function: {
|
|
2505
|
+
name: "notion-update-page",
|
|
2506
|
+
description: "Update a Notion page's properties or content. Uses command-based operations: 'update_properties' to change properties, 'replace_content' to replace all content, 'replace_content_range' to replace specific text, 'insert_content_after' to insert after text.",
|
|
2507
|
+
arguments: {
|
|
2508
|
+
type: "object",
|
|
2509
|
+
properties: {
|
|
2510
|
+
data: {
|
|
2511
|
+
type: "object",
|
|
2512
|
+
description: "Update data containing page_id and a command",
|
|
2513
|
+
properties: {
|
|
2514
|
+
page_id: {
|
|
2515
|
+
type: "string",
|
|
2516
|
+
description: "The ID of the page to update (with or without dashes)"
|
|
2517
|
+
},
|
|
2518
|
+
command: {
|
|
2519
|
+
type: "string",
|
|
2520
|
+
enum: [
|
|
2521
|
+
"update_properties",
|
|
2522
|
+
"replace_content",
|
|
2523
|
+
"replace_content_range",
|
|
2524
|
+
"insert_content_after"
|
|
2525
|
+
],
|
|
2526
|
+
description: "The update command to execute"
|
|
2527
|
+
},
|
|
2528
|
+
properties: {
|
|
2529
|
+
type: "object",
|
|
2530
|
+
description: "For update_properties: JSON map of property names to values. Use null to remove a value."
|
|
2531
|
+
},
|
|
2532
|
+
new_str: {
|
|
2533
|
+
type: "string",
|
|
2534
|
+
description: "For replace_content/replace_content_range/insert_content_after: the new content string"
|
|
2535
|
+
},
|
|
2536
|
+
selection_with_ellipsis: {
|
|
2537
|
+
type: "string",
|
|
2538
|
+
description: "For replace_content_range/insert_content_after: unique start and end snippet (~10 chars each with ellipsis)"
|
|
2539
|
+
},
|
|
2540
|
+
allow_deleting_content: {
|
|
2541
|
+
type: "boolean",
|
|
2542
|
+
description: "For replace_content/replace_content_range: allow deletion of child pages/databases"
|
|
2543
|
+
}
|
|
2544
|
+
},
|
|
2545
|
+
required: ["page_id", "command"]
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2548
|
+
required: ["data"]
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
});
|
|
2799
2552
|
}
|
|
2800
|
-
function
|
|
2801
|
-
|
|
2802
|
-
throw new SiwxChallengeError(
|
|
2803
|
-
`expected a ${ED25519_SIGNATURE_BYTES}-byte ed25519 signature, got ${signature.length} bytes`
|
|
2804
|
-
);
|
|
2805
|
-
}
|
|
2806
|
-
const payload = {
|
|
2807
|
-
domain: challenge.domain,
|
|
2808
|
-
address,
|
|
2809
|
-
statement: challenge.statement,
|
|
2810
|
-
uri: challenge.uri,
|
|
2811
|
-
version: challenge.version,
|
|
2812
|
-
// Full CAIP-2 here, unlike the bare reference the message carries.
|
|
2813
|
-
chainId: challenge.chainId,
|
|
2814
|
-
type: challenge.signingType,
|
|
2815
|
-
nonce: challenge.nonce,
|
|
2816
|
-
issuedAt: challenge.issuedAt,
|
|
2817
|
-
resources: challenge.resources,
|
|
2818
|
-
signature: base58Encode(signature)
|
|
2819
|
-
};
|
|
2820
|
-
return uint8ArrayToBase64(new TextEncoder().encode(JSON.stringify(payload)));
|
|
2553
|
+
function createNotionUpdatePageTool(getAccessToken, requestNotionAccess) {
|
|
2554
|
+
return notionUpdatePageTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2821
2555
|
}
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2556
|
+
function notionMovePagesTool(run) {
|
|
2557
|
+
return withNotionExecutor(run, "Error moving Notion pages", {
|
|
2558
|
+
type: "function",
|
|
2559
|
+
function: {
|
|
2560
|
+
name: "notion-move-pages",
|
|
2561
|
+
description: "Move one or more Notion pages or databases to a new parent.",
|
|
2562
|
+
arguments: {
|
|
2563
|
+
type: "object",
|
|
2564
|
+
properties: {
|
|
2565
|
+
page_or_database_ids: {
|
|
2566
|
+
type: "array",
|
|
2567
|
+
description: "Array of page or database IDs to move (UUIDs, max 100)",
|
|
2568
|
+
items: { type: "string" }
|
|
2569
|
+
},
|
|
2570
|
+
new_parent: {
|
|
2571
|
+
type: "object",
|
|
2572
|
+
description: "New parent: {type:'page_id', page_id:'...'}, {type:'database_id', database_id:'...'}, {type:'data_source_id', data_source_id:'...'}, or {type:'workspace'} for private pages."
|
|
2573
|
+
}
|
|
2574
|
+
},
|
|
2575
|
+
required: ["page_or_database_ids", "new_parent"]
|
|
2576
|
+
}
|
|
2835
2577
|
}
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
function createNotionMovePagesTool(getAccessToken, requestNotionAccess) {
|
|
2581
|
+
return notionMovePagesTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2582
|
+
}
|
|
2583
|
+
function notionDuplicatePageTool(run) {
|
|
2584
|
+
return withNotionExecutor(run, "Error duplicating Notion page", {
|
|
2585
|
+
type: "function",
|
|
2586
|
+
function: {
|
|
2587
|
+
name: "notion-duplicate-page",
|
|
2588
|
+
description: "Duplicate a Notion page. The duplication completes asynchronously.",
|
|
2589
|
+
arguments: {
|
|
2590
|
+
type: "object",
|
|
2591
|
+
properties: {
|
|
2592
|
+
page_id: {
|
|
2593
|
+
type: "string",
|
|
2594
|
+
description: "The ID of the page to duplicate (UUID with or without dashes)"
|
|
2595
|
+
}
|
|
2596
|
+
},
|
|
2597
|
+
required: ["page_id"]
|
|
2598
|
+
}
|
|
2839
2599
|
}
|
|
2840
|
-
}
|
|
2841
|
-
let encoded = "1".repeat(zeros);
|
|
2842
|
-
for (let i = digits.length - 1; i >= 0; i--) {
|
|
2843
|
-
encoded += BASE58_ALPHABET[digits[i]];
|
|
2844
|
-
}
|
|
2845
|
-
return encoded;
|
|
2600
|
+
});
|
|
2846
2601
|
}
|
|
2847
|
-
function
|
|
2848
|
-
|
|
2849
|
-
try {
|
|
2850
|
-
parsed = JSON.parse(new TextDecoder().decode(base64ToUint8Array(paymentRequiredHeader)));
|
|
2851
|
-
} catch (cause) {
|
|
2852
|
-
throw new SiwxChallengeError("the PAYMENT-REQUIRED header is not base64-encoded JSON", {
|
|
2853
|
-
cause
|
|
2854
|
-
});
|
|
2855
|
-
}
|
|
2856
|
-
const decoded = asRecord(parsed);
|
|
2857
|
-
if (!decoded) {
|
|
2858
|
-
throw new SiwxChallengeError("the PAYMENT-REQUIRED header did not decode to an object");
|
|
2859
|
-
}
|
|
2860
|
-
return decoded;
|
|
2602
|
+
function createNotionDuplicatePageTool(getAccessToken, requestNotionAccess) {
|
|
2603
|
+
return notionDuplicatePageTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2861
2604
|
}
|
|
2862
|
-
function
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2605
|
+
function notionCreateDatabaseTool(run) {
|
|
2606
|
+
return withNotionExecutor(run, "Error creating Notion database", {
|
|
2607
|
+
type: "function",
|
|
2608
|
+
function: {
|
|
2609
|
+
name: "notion-create-database",
|
|
2610
|
+
description: "Create a new Notion database with a properties schema. If no title property is provided, 'Name' is auto-added. Supports property types: title, rich_text, number, select, multi_select, date, people, checkbox, url, email, phone_number, formula, relation, rollup, status, unique_id, etc.",
|
|
2611
|
+
arguments: {
|
|
2612
|
+
type: "object",
|
|
2613
|
+
properties: {
|
|
2614
|
+
properties: {
|
|
2615
|
+
type: "object",
|
|
2616
|
+
description: "Property schema for the database. Each key is a property name, value defines the type."
|
|
2617
|
+
},
|
|
2618
|
+
parent: {
|
|
2619
|
+
type: "object",
|
|
2620
|
+
description: "Parent page: {type:'page_id', page_id:'...'}. Omit for private workspace-level database."
|
|
2621
|
+
},
|
|
2622
|
+
title: {
|
|
2623
|
+
type: "array",
|
|
2624
|
+
description: "Title of the database as rich text array (max 100)",
|
|
2625
|
+
items: { type: "object" }
|
|
2626
|
+
},
|
|
2627
|
+
description: {
|
|
2628
|
+
type: "array",
|
|
2629
|
+
description: "Description of the database as rich text array (max 100)",
|
|
2630
|
+
items: { type: "object" }
|
|
2631
|
+
}
|
|
2632
|
+
},
|
|
2633
|
+
required: ["properties"]
|
|
2634
|
+
}
|
|
2875
2635
|
}
|
|
2876
|
-
|
|
2877
|
-
}
|
|
2878
|
-
throw new SiwxChallengeError("the sign-in-with-x extension offers no solana: chain");
|
|
2636
|
+
});
|
|
2879
2637
|
}
|
|
2880
|
-
function
|
|
2881
|
-
return
|
|
2638
|
+
function createNotionCreateDatabaseTool(getAccessToken, requestNotionAccess) {
|
|
2639
|
+
return notionCreateDatabaseTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2882
2640
|
}
|
|
2883
|
-
function
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2641
|
+
function notionUpdateDataSourceTool(run) {
|
|
2642
|
+
return withNotionExecutor(run, "Error updating Notion data source", {
|
|
2643
|
+
type: "function",
|
|
2644
|
+
function: {
|
|
2645
|
+
name: "notion-update-data-source",
|
|
2646
|
+
description: "Update a Notion data source's title, description, property schema, or other attributes. Use null to remove a property. Provide only 'name' to rename a property.",
|
|
2647
|
+
arguments: {
|
|
2648
|
+
type: "object",
|
|
2649
|
+
properties: {
|
|
2650
|
+
data_source_id: {
|
|
2651
|
+
type: "string",
|
|
2652
|
+
description: "The ID of the data source to update (UUID). Can be a data source ID or database ID."
|
|
2653
|
+
},
|
|
2654
|
+
title: {
|
|
2655
|
+
type: "array",
|
|
2656
|
+
description: "New title as rich text array (max 100)",
|
|
2657
|
+
items: { type: "object" }
|
|
2658
|
+
},
|
|
2659
|
+
description: {
|
|
2660
|
+
type: "array",
|
|
2661
|
+
description: "New description as rich text array (max 100)",
|
|
2662
|
+
items: { type: "object" }
|
|
2663
|
+
},
|
|
2664
|
+
properties: {
|
|
2665
|
+
type: "object",
|
|
2666
|
+
description: "Property schema updates. Use null to remove, {name:'...'} to rename, or full definition to add/update."
|
|
2667
|
+
},
|
|
2668
|
+
is_inline: {
|
|
2669
|
+
type: "boolean",
|
|
2670
|
+
description: "Whether the database is inline"
|
|
2671
|
+
},
|
|
2672
|
+
in_trash: {
|
|
2673
|
+
type: "boolean",
|
|
2674
|
+
description: "Trash the data source (requires Notion UI to undo)"
|
|
2675
|
+
}
|
|
2676
|
+
},
|
|
2677
|
+
required: ["data_source_id"]
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
});
|
|
2889
2681
|
}
|
|
2890
|
-
function
|
|
2891
|
-
|
|
2892
|
-
if (!Array.isArray(resources) || resources.length === 0) {
|
|
2893
|
-
throw new SiwxChallengeError("the sign-in-with-x info block has no resources");
|
|
2894
|
-
}
|
|
2895
|
-
if (!resources.every((resource) => typeof resource === "string" && resource !== "")) {
|
|
2896
|
-
throw new SiwxChallengeError("the sign-in-with-x resources are not all strings");
|
|
2897
|
-
}
|
|
2898
|
-
return resources;
|
|
2682
|
+
function createNotionUpdateDataSourceTool(getAccessToken, requestNotionAccess) {
|
|
2683
|
+
return notionUpdateDataSourceTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2899
2684
|
}
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
function
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2685
|
+
function notionCreateCommentTool(run) {
|
|
2686
|
+
return withNotionExecutor(run, "Error creating Notion comment", {
|
|
2687
|
+
type: "function",
|
|
2688
|
+
function: {
|
|
2689
|
+
name: "notion-create-comment",
|
|
2690
|
+
description: "Add a comment to a Notion page. Supports page-level comments, content-specific comments (using selection_with_ellipsis), or replies to existing discussions (using discussion_id).",
|
|
2691
|
+
arguments: {
|
|
2692
|
+
type: "object",
|
|
2693
|
+
properties: {
|
|
2694
|
+
page_id: {
|
|
2695
|
+
type: "string",
|
|
2696
|
+
description: "The ID of the page to comment on (with or without dashes)"
|
|
2697
|
+
},
|
|
2698
|
+
rich_text: {
|
|
2699
|
+
type: "array",
|
|
2700
|
+
description: "Comment content as rich text array (max 100). Objects can be text, mention, or equation.",
|
|
2701
|
+
items: {
|
|
2702
|
+
type: "object",
|
|
2703
|
+
properties: {
|
|
2704
|
+
text: {
|
|
2705
|
+
type: "object",
|
|
2706
|
+
properties: {
|
|
2707
|
+
content: { type: "string" }
|
|
2708
|
+
},
|
|
2709
|
+
required: ["content"]
|
|
2710
|
+
}
|
|
2711
|
+
},
|
|
2712
|
+
required: ["text"]
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2715
|
+
discussion_id: {
|
|
2716
|
+
type: "string",
|
|
2717
|
+
description: "ID or URL of an existing discussion to reply to"
|
|
2718
|
+
},
|
|
2719
|
+
selection_with_ellipsis: {
|
|
2720
|
+
type: "string",
|
|
2721
|
+
description: "Unique start and end snippet (~10 chars each with ellipsis) of content to comment on"
|
|
2722
|
+
}
|
|
2723
|
+
},
|
|
2724
|
+
required: ["page_id", "rich_text"]
|
|
2725
|
+
}
|
|
2913
2726
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
function createNotionCreateCommentTool(getAccessToken, requestNotionAccess) {
|
|
2730
|
+
return notionCreateCommentTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2731
|
+
}
|
|
2732
|
+
function notionGetCommentsTool(run) {
|
|
2733
|
+
return withNotionExecutor(run, "Error retrieving Notion comments", {
|
|
2734
|
+
type: "function",
|
|
2735
|
+
function: {
|
|
2736
|
+
name: "notion-get-comments",
|
|
2737
|
+
description: "Get comments and discussions from a Notion page. Returns discussions with full comment content.",
|
|
2738
|
+
arguments: {
|
|
2739
|
+
type: "object",
|
|
2740
|
+
properties: {
|
|
2741
|
+
page_id: {
|
|
2742
|
+
type: "string",
|
|
2743
|
+
description: "Identifier for the Notion page"
|
|
2744
|
+
},
|
|
2745
|
+
include_all_blocks: {
|
|
2746
|
+
type: "boolean",
|
|
2747
|
+
description: "Include discussions on child blocks (default: false)"
|
|
2748
|
+
},
|
|
2749
|
+
include_resolved: {
|
|
2750
|
+
type: "boolean",
|
|
2751
|
+
description: "Include resolved discussions (default: false)"
|
|
2752
|
+
},
|
|
2753
|
+
discussion_id: {
|
|
2754
|
+
type: "string",
|
|
2755
|
+
description: "Fetch a specific discussion by ID or URL (e.g., discussion://pageId/blockId/discussionId)"
|
|
2756
|
+
}
|
|
2757
|
+
},
|
|
2758
|
+
required: ["page_id"]
|
|
2759
|
+
}
|
|
2921
2760
|
}
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
function createNotionGetCommentsTool(getAccessToken, requestNotionAccess) {
|
|
2764
|
+
return notionGetCommentsTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2765
|
+
}
|
|
2766
|
+
function notionGetUsersTool(run) {
|
|
2767
|
+
return withNotionExecutor(run, "Error listing Notion users", {
|
|
2768
|
+
type: "function",
|
|
2769
|
+
function: {
|
|
2770
|
+
name: "notion-get-users",
|
|
2771
|
+
description: "List users in the Notion workspace. Supports filtering by name/email, pagination, fetching a specific user by ID, or fetching the current bot user (user_id: 'self').",
|
|
2772
|
+
arguments: {
|
|
2773
|
+
type: "object",
|
|
2774
|
+
properties: {
|
|
2775
|
+
query: {
|
|
2776
|
+
type: "string",
|
|
2777
|
+
description: "Search query to filter users by name or email (case-insensitive, max 100 chars)"
|
|
2778
|
+
},
|
|
2779
|
+
start_cursor: {
|
|
2780
|
+
type: "string",
|
|
2781
|
+
description: "Cursor for pagination from previous response"
|
|
2782
|
+
},
|
|
2783
|
+
page_size: {
|
|
2784
|
+
type: "integer",
|
|
2785
|
+
description: "Number of users per page (1-100, default 100)"
|
|
2786
|
+
},
|
|
2787
|
+
user_id: {
|
|
2788
|
+
type: "string",
|
|
2789
|
+
description: "Return only the user matching this ID. Pass 'self' to fetch the current bot user."
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
function createNotionGetUsersTool(getAccessToken, requestNotionAccess) {
|
|
2797
|
+
return notionGetUsersTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2798
|
+
}
|
|
2799
|
+
function notionGetTeamsTool(run) {
|
|
2800
|
+
return withNotionExecutor(run, "Error retrieving Notion teams", {
|
|
2801
|
+
type: "function",
|
|
2802
|
+
function: {
|
|
2803
|
+
name: "notion-get-teams",
|
|
2804
|
+
description: "Retrieve teams (teamspaces) in the Notion workspace. Shows team names, IDs, membership status, and roles.",
|
|
2805
|
+
arguments: {
|
|
2806
|
+
type: "object",
|
|
2807
|
+
properties: {
|
|
2808
|
+
query: {
|
|
2809
|
+
type: "string",
|
|
2810
|
+
description: "Search query to filter teams by name (case-insensitive, max 100 chars)"
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2960
2814
|
}
|
|
2815
|
+
});
|
|
2816
|
+
}
|
|
2817
|
+
function createNotionGetTeamsTool(getAccessToken, requestNotionAccess) {
|
|
2818
|
+
return notionGetTeamsTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2819
|
+
}
|
|
2820
|
+
var NOTION_TOOLS = [
|
|
2821
|
+
// Search
|
|
2822
|
+
notionSearchTool,
|
|
2823
|
+
// Pages
|
|
2824
|
+
notionFetchTool,
|
|
2825
|
+
notionCreatePagesTool,
|
|
2826
|
+
notionUpdatePageTool,
|
|
2827
|
+
notionMovePagesTool,
|
|
2828
|
+
notionDuplicatePageTool,
|
|
2829
|
+
// Data Sources (Databases)
|
|
2830
|
+
notionCreateDatabaseTool,
|
|
2831
|
+
notionUpdateDataSourceTool,
|
|
2832
|
+
// Comments
|
|
2833
|
+
notionCreateCommentTool,
|
|
2834
|
+
notionGetCommentsTool,
|
|
2835
|
+
// Users & Teams
|
|
2836
|
+
notionGetUsersTool,
|
|
2837
|
+
notionGetTeamsTool
|
|
2838
|
+
];
|
|
2839
|
+
function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
2840
|
+
const run = directRunner(getAccessToken, requestNotionAccess);
|
|
2841
|
+
return NOTION_TOOLS.map((tool) => tool(run));
|
|
2842
|
+
}
|
|
2843
|
+
function createNotionProxyTools(callMcp) {
|
|
2844
|
+
const run = proxyRunner(callMcp);
|
|
2845
|
+
return NOTION_TOOLS.map((tool) => tool(run));
|
|
2846
|
+
}
|
|
2847
|
+
function getMCPEndpoints() {
|
|
2848
|
+
return {
|
|
2849
|
+
http: MCP_HTTP_ENDPOINT,
|
|
2850
|
+
sse: MCP_SSE_ENDPOINT
|
|
2961
2851
|
};
|
|
2962
2852
|
}
|
|
2963
|
-
function
|
|
2964
|
-
|
|
2965
|
-
throw new AgentresPathError(
|
|
2966
|
-
`"${path}" is not a path on ${baseUrl} \u2014 it must start with a single "/"`
|
|
2967
|
-
);
|
|
2968
|
-
}
|
|
2969
|
-
const base = new URL(baseUrl);
|
|
2970
|
-
const resolved = new URL(path, base);
|
|
2971
|
-
if (resolved.origin !== base.origin) {
|
|
2972
|
-
throw new AgentresPathError(
|
|
2973
|
-
`"${path}" resolves to ${resolved.origin} but this client talks to ${base.origin} \u2014 refusing to send it`
|
|
2974
|
-
);
|
|
2975
|
-
}
|
|
2976
|
-
return resolved.toString();
|
|
2853
|
+
async function callNotionMCPTool(accessToken, toolName, args) {
|
|
2854
|
+
return callMCPTool(accessToken, toolName, args);
|
|
2977
2855
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2856
|
+
|
|
2857
|
+
// src/tools/github.ts
|
|
2858
|
+
var GITHUB_API = "https://api.github.com";
|
|
2859
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
2860
|
+
async function resolveToken(getAccessToken, requestGitHubAccess) {
|
|
2861
|
+
let token = getAccessToken();
|
|
2862
|
+
if (!token) {
|
|
2863
|
+
try {
|
|
2864
|
+
token = await requestGitHubAccess();
|
|
2865
|
+
} catch (err) {
|
|
2866
|
+
getLogger().error("Failed to resolve GitHub token", err);
|
|
2867
|
+
return null;
|
|
2868
|
+
}
|
|
2982
2869
|
}
|
|
2983
|
-
|
|
2984
|
-
return { method: request.method, headers, body: JSON.stringify(request.body) };
|
|
2870
|
+
return token;
|
|
2985
2871
|
}
|
|
2986
|
-
async function
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2872
|
+
async function githubFetch(token, path, options = {}) {
|
|
2873
|
+
if (!path.startsWith("http") && !path.startsWith("/")) {
|
|
2874
|
+
throw new Error("githubFetch: relative path must start with /");
|
|
2875
|
+
}
|
|
2876
|
+
const url = path.startsWith("http") ? path : `${GITHUB_API}${path}`;
|
|
2877
|
+
if (!url.startsWith(`${GITHUB_API}/`)) {
|
|
2878
|
+
throw new Error("githubFetch: URL must target GitHub API");
|
|
2990
2879
|
}
|
|
2880
|
+
const controller = new AbortController();
|
|
2881
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
2991
2882
|
try {
|
|
2992
|
-
return
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
2883
|
+
return await fetch(url, {
|
|
2884
|
+
...options,
|
|
2885
|
+
signal: controller.signal,
|
|
2886
|
+
headers: {
|
|
2887
|
+
Authorization: `Bearer ${token}`,
|
|
2888
|
+
Accept: "application/vnd.github+json",
|
|
2889
|
+
"Content-Type": "application/json",
|
|
2890
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2891
|
+
...options.headers
|
|
2892
|
+
}
|
|
2893
|
+
});
|
|
2894
|
+
} finally {
|
|
2895
|
+
clearTimeout(timeout);
|
|
3000
2896
|
}
|
|
3001
2897
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
function
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
2898
|
+
function createGetAuthenticatedUserTool(getAccessToken, requestAccess) {
|
|
2899
|
+
return {
|
|
2900
|
+
type: "function",
|
|
2901
|
+
function: {
|
|
2902
|
+
name: "github_get_authenticated_user",
|
|
2903
|
+
description: "Get the authenticated GitHub user's profile including username and organizations. Call this first to discover the user's identity before making other GitHub API calls.",
|
|
2904
|
+
parameters: {
|
|
2905
|
+
type: "object",
|
|
2906
|
+
properties: {}
|
|
2907
|
+
}
|
|
2908
|
+
},
|
|
2909
|
+
executor: async () => {
|
|
2910
|
+
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2911
|
+
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2912
|
+
try {
|
|
2913
|
+
const userResp = await githubFetch(token, "/user");
|
|
2914
|
+
if (!userResp.ok) return `Error: Failed to get user (${userResp.status})`;
|
|
2915
|
+
const user = await userResp.json();
|
|
2916
|
+
const orgsResp = await githubFetch(token, "/user/orgs?per_page=100");
|
|
2917
|
+
let orgs = [];
|
|
2918
|
+
if (orgsResp.ok) {
|
|
2919
|
+
const orgsData = await orgsResp.json();
|
|
2920
|
+
orgs = orgsData.map((o) => o.login);
|
|
2921
|
+
}
|
|
2922
|
+
return {
|
|
2923
|
+
login: user.login,
|
|
2924
|
+
name: user.name,
|
|
2925
|
+
email: user.email,
|
|
2926
|
+
organizations: orgs,
|
|
2927
|
+
public_repos: user.public_repos,
|
|
2928
|
+
html_url: user.html_url
|
|
2929
|
+
};
|
|
2930
|
+
} catch (err) {
|
|
2931
|
+
getLogger().error("github_get_authenticated_user error", err);
|
|
2932
|
+
return "Error: Failed to get user profile.";
|
|
2933
|
+
}
|
|
3023
2934
|
}
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
var MAX_RESPONSE_SIZE = 1e5;
|
|
2938
|
+
function truncate(text, maxLen) {
|
|
2939
|
+
if (text.length <= maxLen) return text;
|
|
2940
|
+
return text.slice(0, maxLen) + `
|
|
2941
|
+
|
|
2942
|
+
... (truncated, ${text.length - maxLen} characters omitted)`;
|
|
2943
|
+
}
|
|
2944
|
+
function createGitHubApiTool(getAccessToken, requestAccess) {
|
|
2945
|
+
return {
|
|
2946
|
+
type: "function",
|
|
2947
|
+
function: {
|
|
2948
|
+
name: "github_api",
|
|
2949
|
+
description: `Make an authenticated request to the GitHub REST API (https://api.github.com). You can call any endpoint documented at https://docs.github.com/en/rest.
|
|
2950
|
+
|
|
2951
|
+
Examples:
|
|
2952
|
+
- List PRs: GET /repos/{owner}/{repo}/pulls?state=open
|
|
2953
|
+
- Get file: GET /repos/{owner}/{repo}/contents/{path}
|
|
2954
|
+
- Create issue: POST /repos/{owner}/{repo}/issues with body {"title": "...", "body": "..."}
|
|
2955
|
+
- Search repos: GET /search/repositories?q={query}
|
|
2956
|
+
|
|
2957
|
+
IMPORTANT: For write operations (POST, PUT, PATCH, DELETE), always confirm with the user before executing. This includes creating issues, opening PRs, merging, committing files, and submitting reviews.`,
|
|
2958
|
+
parameters: {
|
|
2959
|
+
type: "object",
|
|
2960
|
+
properties: {
|
|
2961
|
+
method: {
|
|
2962
|
+
type: "string",
|
|
2963
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
2964
|
+
description: "HTTP method"
|
|
2965
|
+
},
|
|
2966
|
+
path: {
|
|
2967
|
+
type: "string",
|
|
2968
|
+
description: "API path (e.g., /repos/owner/repo/pulls) or full URL for pagination"
|
|
2969
|
+
},
|
|
2970
|
+
body: {
|
|
2971
|
+
type: "object",
|
|
2972
|
+
description: "Request body for POST/PUT/PATCH requests",
|
|
2973
|
+
additionalProperties: true
|
|
2974
|
+
}
|
|
2975
|
+
},
|
|
2976
|
+
required: ["method", "path"]
|
|
2977
|
+
}
|
|
2978
|
+
},
|
|
2979
|
+
executor: async (args) => {
|
|
2980
|
+
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2981
|
+
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2982
|
+
const method = args.method;
|
|
2983
|
+
const path = args.path;
|
|
2984
|
+
const body = args.body;
|
|
2985
|
+
try {
|
|
2986
|
+
const resp = await githubFetch(token, path, {
|
|
2987
|
+
method,
|
|
2988
|
+
body: body ? JSON.stringify(body) : void 0
|
|
2989
|
+
});
|
|
2990
|
+
const text = await resp.text();
|
|
2991
|
+
if (!resp.ok) {
|
|
2992
|
+
return `Error: GitHub API ${method} ${path} returned ${resp.status}: ${truncate(text, 500)}`;
|
|
2993
|
+
}
|
|
2994
|
+
try {
|
|
2995
|
+
const data = JSON.parse(text);
|
|
2996
|
+
const json = JSON.stringify(data);
|
|
2997
|
+
return truncate(json, MAX_RESPONSE_SIZE);
|
|
2998
|
+
} catch {
|
|
2999
|
+
return truncate(text, MAX_RESPONSE_SIZE);
|
|
3000
|
+
}
|
|
3001
|
+
} catch (err) {
|
|
3002
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
3003
|
+
return `Error: GitHub API request timed out after ${FETCH_TIMEOUT_MS / 1e3}s.`;
|
|
3004
|
+
}
|
|
3005
|
+
getLogger().error("github_api error", err);
|
|
3006
|
+
return `Error: GitHub API request failed: ${err instanceof Error ? err.message : "unknown error"}`;
|
|
3007
|
+
}
|
|
3027
3008
|
}
|
|
3028
|
-
return null;
|
|
3029
3009
|
};
|
|
3030
3010
|
}
|
|
3011
|
+
function createGitHubTools(getAccessToken, requestGitHubAccess) {
|
|
3012
|
+
return [
|
|
3013
|
+
createGetAuthenticatedUserTool(getAccessToken, requestGitHubAccess),
|
|
3014
|
+
createGitHubApiTool(getAccessToken, requestGitHubAccess)
|
|
3015
|
+
];
|
|
3016
|
+
}
|
|
3031
3017
|
|
|
3032
3018
|
// src/tools/gmail.ts
|
|
3033
3019
|
var GMAIL_PROVIDER = "gmail";
|
|
@@ -3890,7 +3876,7 @@ async function callSlack(callProxy, path, query, requestBody) {
|
|
|
3890
3876
|
const { status, json } = await callProxy(path, query, requestBody);
|
|
3891
3877
|
return interpretSlackResult(path, status, json);
|
|
3892
3878
|
}
|
|
3893
|
-
function
|
|
3879
|
+
function readString2(raw) {
|
|
3894
3880
|
return typeof raw === "string" ? raw : "";
|
|
3895
3881
|
}
|
|
3896
3882
|
function clampLimit(raw, fallback, min, max) {
|
|
@@ -4452,7 +4438,7 @@ function createSlackSearchMessagesTool(callProxy) {
|
|
|
4452
4438
|
}
|
|
4453
4439
|
},
|
|
4454
4440
|
executor: async (args) => searchSlackMessages(callProxy, {
|
|
4455
|
-
query:
|
|
4441
|
+
query: readString2(args.query),
|
|
4456
4442
|
count: args.count,
|
|
4457
4443
|
channel: typeof args.channel === "string" ? args.channel : void 0,
|
|
4458
4444
|
from_user: typeof args.from_user === "string" ? args.from_user : void 0,
|
|
@@ -4529,7 +4515,7 @@ function createSlackGetChannelHistoryTool(callProxy) {
|
|
|
4529
4515
|
}
|
|
4530
4516
|
},
|
|
4531
4517
|
executor: async (args) => getSlackChannelHistory(callProxy, {
|
|
4532
|
-
channel:
|
|
4518
|
+
channel: readString2(args.channel),
|
|
4533
4519
|
limit: args.limit
|
|
4534
4520
|
})
|
|
4535
4521
|
};
|
|
@@ -4560,8 +4546,8 @@ function createSlackGetThreadRepliesTool(callProxy) {
|
|
|
4560
4546
|
}
|
|
4561
4547
|
},
|
|
4562
4548
|
executor: async (args) => getSlackThreadReplies(callProxy, {
|
|
4563
|
-
channel:
|
|
4564
|
-
ts:
|
|
4549
|
+
channel: readString2(args.channel),
|
|
4550
|
+
ts: readString2(args.ts),
|
|
4565
4551
|
limit: args.limit
|
|
4566
4552
|
})
|
|
4567
4553
|
};
|
|
@@ -4592,8 +4578,8 @@ function createSlackPostMessageTool(callProxy) {
|
|
|
4592
4578
|
}
|
|
4593
4579
|
},
|
|
4594
4580
|
executor: async (args) => postSlackMessage(callProxy, {
|
|
4595
|
-
channel:
|
|
4596
|
-
text:
|
|
4581
|
+
channel: readString2(args.channel),
|
|
4582
|
+
text: readString2(args.text),
|
|
4597
4583
|
thread_ts: typeof args.thread_ts === "string" ? args.thread_ts : void 0
|
|
4598
4584
|
})
|
|
4599
4585
|
};
|
|
@@ -14206,6 +14192,7 @@ function createDocumentTools({
|
|
|
14206
14192
|
createNotionGetTeamsTool,
|
|
14207
14193
|
createNotionGetUsersTool,
|
|
14208
14194
|
createNotionMovePagesTool,
|
|
14195
|
+
createNotionProxyTools,
|
|
14209
14196
|
createNotionSearchTool,
|
|
14210
14197
|
createNotionTools,
|
|
14211
14198
|
createNotionUpdateDataSourceTool,
|