@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.mjs
CHANGED
|
@@ -1573,1306 +1573,1291 @@ function createDriveTools(getAccessToken, requestDriveAccess) {
|
|
|
1573
1573
|
];
|
|
1574
1574
|
}
|
|
1575
1575
|
|
|
1576
|
-
// src/
|
|
1577
|
-
var
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1576
|
+
// src/lib/connectors/agentres/errors.ts
|
|
1577
|
+
var AgentresError = class extends Error {
|
|
1578
|
+
constructor(status, code, message, retryable, nextStep) {
|
|
1579
|
+
super(message);
|
|
1580
|
+
this.name = "AgentresError";
|
|
1581
|
+
this.status = status;
|
|
1582
|
+
this.code = code;
|
|
1583
|
+
this.retryable = retryable;
|
|
1584
|
+
this.nextStep = nextStep;
|
|
1585
1585
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1586
|
+
};
|
|
1587
|
+
var SiwxUnsupportedError = class extends Error {
|
|
1588
|
+
constructor(message) {
|
|
1589
|
+
super(message);
|
|
1590
|
+
this.name = "SiwxUnsupportedError";
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
var AgentresPathError = class extends Error {
|
|
1594
|
+
constructor(message) {
|
|
1595
|
+
super(message);
|
|
1596
|
+
this.name = "AgentresPathError";
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
var SiwxChallengeError = class extends Error {
|
|
1600
|
+
constructor(message, options) {
|
|
1601
|
+
super(message);
|
|
1602
|
+
this.name = "SiwxChallengeError";
|
|
1603
|
+
this.cause = options?.cause;
|
|
1604
|
+
}
|
|
1605
|
+
};
|
|
1606
|
+
function parseAgentresError(status, body) {
|
|
1607
|
+
const stated = statedError(body);
|
|
1608
|
+
if (!stated) {
|
|
1609
|
+
return new AgentresError(
|
|
1610
|
+
status,
|
|
1611
|
+
`HTTP_${status}`,
|
|
1612
|
+
body.trim() || `HTTP ${status}`,
|
|
1613
|
+
status >= 500
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
const code = typeof stated.code === "string" ? stated.code : `HTTP_${status}`;
|
|
1617
|
+
const message = typeof stated.message === "string" ? stated.message : `HTTP ${status}`;
|
|
1618
|
+
const retryable = typeof stated.retryable === "boolean" ? stated.retryable : status >= 500;
|
|
1619
|
+
const nextStep = typeof stated.next_step === "string" ? stated.next_step : void 0;
|
|
1620
|
+
return new AgentresError(status, code, message, retryable, nextStep);
|
|
1589
1621
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1622
|
+
function statedError(body) {
|
|
1623
|
+
let parsed;
|
|
1624
|
+
try {
|
|
1625
|
+
parsed = JSON.parse(body);
|
|
1626
|
+
} catch {
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
const envelope = parsed;
|
|
1630
|
+
const stated = typeof envelope?.error === "object" && envelope.error !== null ? envelope.error : null;
|
|
1631
|
+
if (!stated) {
|
|
1632
|
+
return null;
|
|
1633
|
+
}
|
|
1634
|
+
const fields = stated;
|
|
1635
|
+
if (typeof fields.code !== "string" && typeof fields.message !== "string") {
|
|
1636
|
+
return null;
|
|
1637
|
+
}
|
|
1638
|
+
return fields;
|
|
1594
1639
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
if (line.startsWith("data: ")) {
|
|
1602
|
-
dataLines.push(line.slice(6));
|
|
1603
|
-
} else if (line.startsWith("data:")) {
|
|
1604
|
-
dataLines.push(line.slice(5));
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
if (dataLines.length > 0) {
|
|
1608
|
-
lastEventData = dataLines.join("\n");
|
|
1609
|
-
}
|
|
1640
|
+
|
|
1641
|
+
// src/lib/processors/encoding.ts
|
|
1642
|
+
var B64_ENCODE_CHUNK = 32768 * 3;
|
|
1643
|
+
function uint8ArrayToBase64(data) {
|
|
1644
|
+
if (typeof Buffer !== "undefined") {
|
|
1645
|
+
return Buffer.from(data).toString("base64");
|
|
1610
1646
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1647
|
+
let base64 = "";
|
|
1648
|
+
for (let i = 0; i < data.length; i += B64_ENCODE_CHUNK) {
|
|
1649
|
+
const slice = data.subarray(i, i + B64_ENCODE_CHUNK);
|
|
1650
|
+
let binary = "";
|
|
1651
|
+
for (let j = 0; j < slice.length; j++) {
|
|
1652
|
+
binary += String.fromCharCode(slice[j]);
|
|
1653
|
+
}
|
|
1654
|
+
base64 += btoa(binary);
|
|
1613
1655
|
}
|
|
1614
|
-
return
|
|
1656
|
+
return base64;
|
|
1615
1657
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
return parseSSEResponse(text);
|
|
1658
|
+
function base64ToUint8Array(base64) {
|
|
1659
|
+
if (typeof Buffer !== "undefined") {
|
|
1660
|
+
const buf = Buffer.from(base64, "base64");
|
|
1661
|
+
return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
|
|
1621
1662
|
}
|
|
1622
|
-
|
|
1663
|
+
const binary = atob(base64);
|
|
1664
|
+
const bytes = new Uint8Array(binary.length);
|
|
1665
|
+
for (let i = 0; i < binary.length; i++) {
|
|
1666
|
+
bytes[i] = binary.charCodeAt(i);
|
|
1667
|
+
}
|
|
1668
|
+
return bytes;
|
|
1623
1669
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1670
|
+
|
|
1671
|
+
// src/lib/connectors/agentres/siwx.ts
|
|
1672
|
+
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
1673
|
+
var SIWX_HEADER = "SIGN-IN-WITH-X";
|
|
1674
|
+
var SIWX_EXTENSION = "sign-in-with-x";
|
|
1675
|
+
var SOLANA_NAMESPACE = "solana:";
|
|
1676
|
+
var ED25519_SIGNATURE_BYTES = 64;
|
|
1677
|
+
function parseChallenge(paymentRequiredHeader, expectedOrigin) {
|
|
1678
|
+
const decoded = decodeHeader(paymentRequiredHeader);
|
|
1679
|
+
const extensions = asRecord(decoded.extensions);
|
|
1680
|
+
const extension = extensions ? asRecord(extensions[SIWX_EXTENSION]) : null;
|
|
1681
|
+
if (!extension) {
|
|
1682
|
+
throw new SiwxUnsupportedError(
|
|
1683
|
+
"the 402 carries no sign-in-with-x extension, so this endpoint is paid rather than identity-only"
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
const info = asRecord(extension.info);
|
|
1687
|
+
if (!info) {
|
|
1688
|
+
throw new SiwxChallengeError("the sign-in-with-x extension has no info block");
|
|
1689
|
+
}
|
|
1690
|
+
const chain = solanaChain(extension.supportedChains);
|
|
1691
|
+
const challenge = {
|
|
1692
|
+
domain: requireString(info, "domain"),
|
|
1693
|
+
uri: requireString(info, "uri"),
|
|
1694
|
+
version: requireString(info, "version"),
|
|
1695
|
+
statement: requireString(info, "statement"),
|
|
1696
|
+
nonce: requireString(info, "nonce"),
|
|
1697
|
+
issuedAt: requireString(info, "issuedAt"),
|
|
1698
|
+
resources: requireResources(info),
|
|
1699
|
+
chainId: chain.chainId,
|
|
1700
|
+
signingType: chain.signingType
|
|
1638
1701
|
};
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
});
|
|
1648
|
-
if (!response.ok) {
|
|
1649
|
-
const errorBody = await response.text().catch(() => "");
|
|
1650
|
-
throw new Error(
|
|
1651
|
-
`MCP initialization failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
1702
|
+
assertScopedTo(challenge, expectedOrigin);
|
|
1703
|
+
return challenge;
|
|
1704
|
+
}
|
|
1705
|
+
function assertScopedTo(challenge, expectedOrigin) {
|
|
1706
|
+
const expected = parseUrl(expectedOrigin, `the agentres base URL "${expectedOrigin}"`);
|
|
1707
|
+
if (challenge.domain !== expected.host) {
|
|
1708
|
+
throw new SiwxChallengeError(
|
|
1709
|
+
`the challenge is scoped to "${challenge.domain}" but this client talks to "${expected.host}" \u2014 refusing to sign it`
|
|
1652
1710
|
);
|
|
1653
1711
|
}
|
|
1654
|
-
const
|
|
1655
|
-
if (
|
|
1656
|
-
throw new
|
|
1712
|
+
const uri = parseUrl(challenge.uri, `the sign-in-with-x uri "${challenge.uri}"`);
|
|
1713
|
+
if (uri.origin !== expected.origin) {
|
|
1714
|
+
throw new SiwxChallengeError(
|
|
1715
|
+
`the challenge addresses "${uri.origin}" but this client talks to "${expected.origin}" \u2014 refusing to sign it`
|
|
1716
|
+
);
|
|
1657
1717
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1718
|
+
}
|
|
1719
|
+
function parseUrl(value, description) {
|
|
1720
|
+
try {
|
|
1721
|
+
return new URL(value);
|
|
1722
|
+
} catch (cause) {
|
|
1723
|
+
throw new SiwxChallengeError(`${description} is not a URL`, { cause });
|
|
1663
1724
|
}
|
|
1664
|
-
await fetch(MCP_HTTP_ENDPOINT, {
|
|
1665
|
-
method: "POST",
|
|
1666
|
-
headers: {
|
|
1667
|
-
"Content-Type": "application/json",
|
|
1668
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1669
|
-
"Mcp-Session-Id": sessionId
|
|
1670
|
-
},
|
|
1671
|
-
body: JSON.stringify({
|
|
1672
|
-
jsonrpc: "2.0",
|
|
1673
|
-
method: "notifications/initialized"
|
|
1674
|
-
})
|
|
1675
|
-
});
|
|
1676
|
-
sessionCache.set(accessToken, sessionId);
|
|
1677
|
-
return sessionId;
|
|
1678
1725
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1726
|
+
function buildMessage(challenge, address) {
|
|
1727
|
+
if (!challenge.chainId.startsWith(SOLANA_NAMESPACE)) {
|
|
1728
|
+
throw new SiwxChallengeError(
|
|
1729
|
+
`expected a solana: chain id for the message, got "${challenge.chainId}"`
|
|
1730
|
+
);
|
|
1683
1731
|
}
|
|
1684
|
-
|
|
1732
|
+
const reference = challenge.chainId.slice(SOLANA_NAMESPACE.length);
|
|
1733
|
+
const resources = challenge.resources.map((resource) => `- ${resource}`).join("\n");
|
|
1734
|
+
return `${challenge.domain} wants you to sign in with your Solana account:
|
|
1735
|
+
${address}
|
|
1736
|
+
|
|
1737
|
+
${challenge.statement}
|
|
1738
|
+
|
|
1739
|
+
URI: ${challenge.uri}
|
|
1740
|
+
Version: ${challenge.version}
|
|
1741
|
+
Chain ID: ${reference}
|
|
1742
|
+
Nonce: ${challenge.nonce}
|
|
1743
|
+
Issued At: ${challenge.issuedAt}
|
|
1744
|
+
Resources:
|
|
1745
|
+
${resources}`;
|
|
1685
1746
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1747
|
+
function buildPayload(challenge, address, signature) {
|
|
1748
|
+
if (signature.length !== ED25519_SIGNATURE_BYTES) {
|
|
1749
|
+
throw new SiwxChallengeError(
|
|
1750
|
+
`expected a ${ED25519_SIGNATURE_BYTES}-byte ed25519 signature, got ${signature.length} bytes`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
const payload = {
|
|
1754
|
+
domain: challenge.domain,
|
|
1755
|
+
address,
|
|
1756
|
+
statement: challenge.statement,
|
|
1757
|
+
uri: challenge.uri,
|
|
1758
|
+
version: challenge.version,
|
|
1759
|
+
// Full CAIP-2 here, unlike the bare reference the message carries.
|
|
1760
|
+
chainId: challenge.chainId,
|
|
1761
|
+
type: challenge.signingType,
|
|
1762
|
+
nonce: challenge.nonce,
|
|
1763
|
+
issuedAt: challenge.issuedAt,
|
|
1764
|
+
resources: challenge.resources,
|
|
1765
|
+
signature: base58Encode(signature)
|
|
1697
1766
|
};
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
Authorization: `Bearer ${accessToken}`,
|
|
1718
|
-
"Mcp-Session-Id": newSessionId
|
|
1719
|
-
},
|
|
1720
|
-
body: JSON.stringify(jsonRpcRequest)
|
|
1721
|
-
});
|
|
1722
|
-
if (!retryResponse.ok) {
|
|
1723
|
-
const errorBody2 = await retryResponse.text().catch(() => "");
|
|
1724
|
-
throw new Error(
|
|
1725
|
-
`MCP request failed: ${retryResponse.status} ${retryResponse.statusText}${errorBody2 ? ` - ${errorBody2}` : ""}`
|
|
1726
|
-
);
|
|
1727
|
-
}
|
|
1728
|
-
const retryJsonRpcResponse = await parseResponseBody(retryResponse);
|
|
1729
|
-
if (retryJsonRpcResponse.error) {
|
|
1730
|
-
const err = retryJsonRpcResponse.error;
|
|
1731
|
-
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
1732
|
-
throw new Error(`MCP tool error: ${errMsg}`);
|
|
1733
|
-
}
|
|
1734
|
-
return truncateToolResult(retryJsonRpcResponse.result);
|
|
1767
|
+
return uint8ArrayToBase64(new TextEncoder().encode(JSON.stringify(payload)));
|
|
1768
|
+
}
|
|
1769
|
+
var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
1770
|
+
function base58Encode(bytes) {
|
|
1771
|
+
let zeros = 0;
|
|
1772
|
+
while (zeros < bytes.length && bytes[zeros] === 0) {
|
|
1773
|
+
zeros++;
|
|
1774
|
+
}
|
|
1775
|
+
const digits = [];
|
|
1776
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
1777
|
+
let carry = bytes[i];
|
|
1778
|
+
for (let j = 0; j < digits.length; j++) {
|
|
1779
|
+
carry += digits[j] * 256;
|
|
1780
|
+
digits[j] = carry % 58;
|
|
1781
|
+
carry = Math.floor(carry / 58);
|
|
1782
|
+
}
|
|
1783
|
+
while (carry > 0) {
|
|
1784
|
+
digits.push(carry % 58);
|
|
1785
|
+
carry = Math.floor(carry / 58);
|
|
1735
1786
|
}
|
|
1736
|
-
const errorBody = await response.text().catch(() => "");
|
|
1737
|
-
throw new Error(
|
|
1738
|
-
`MCP request failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
1739
|
-
);
|
|
1740
1787
|
}
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
1745
|
-
throw new Error(`MCP tool error: ${errMsg}`);
|
|
1788
|
+
let encoded = "1".repeat(zeros);
|
|
1789
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
1790
|
+
encoded += BASE58_ALPHABET[digits[i]];
|
|
1746
1791
|
}
|
|
1747
|
-
return
|
|
1792
|
+
return encoded;
|
|
1748
1793
|
}
|
|
1749
|
-
function
|
|
1794
|
+
function decodeHeader(paymentRequiredHeader) {
|
|
1795
|
+
let parsed;
|
|
1796
|
+
try {
|
|
1797
|
+
parsed = JSON.parse(new TextDecoder().decode(base64ToUint8Array(paymentRequiredHeader)));
|
|
1798
|
+
} catch (cause) {
|
|
1799
|
+
throw new SiwxChallengeError("the PAYMENT-REQUIRED header is not base64-encoded JSON", {
|
|
1800
|
+
cause
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
const decoded = asRecord(parsed);
|
|
1804
|
+
if (!decoded) {
|
|
1805
|
+
throw new SiwxChallengeError("the PAYMENT-REQUIRED header did not decode to an object");
|
|
1806
|
+
}
|
|
1807
|
+
return decoded;
|
|
1808
|
+
}
|
|
1809
|
+
function solanaChain(supportedChains) {
|
|
1810
|
+
if (!Array.isArray(supportedChains)) {
|
|
1811
|
+
throw new SiwxChallengeError("the sign-in-with-x extension lists no supportedChains");
|
|
1812
|
+
}
|
|
1813
|
+
for (const entry of supportedChains) {
|
|
1814
|
+
const chain = asRecord(entry);
|
|
1815
|
+
const chainId = chain?.chainId;
|
|
1816
|
+
if (typeof chainId !== "string" || !chainId.startsWith(SOLANA_NAMESPACE)) {
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
const signingType = chain?.type;
|
|
1820
|
+
if (typeof signingType !== "string" || signingType === "") {
|
|
1821
|
+
throw new SiwxChallengeError(`the ${chainId} entry declares no signing type`);
|
|
1822
|
+
}
|
|
1823
|
+
return { chainId, signingType };
|
|
1824
|
+
}
|
|
1825
|
+
throw new SiwxChallengeError("the sign-in-with-x extension offers no solana: chain");
|
|
1826
|
+
}
|
|
1827
|
+
function asRecord(value) {
|
|
1828
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1829
|
+
}
|
|
1830
|
+
function requireString(info, key) {
|
|
1831
|
+
const value = info[key];
|
|
1832
|
+
if (typeof value !== "string" || value === "") {
|
|
1833
|
+
throw new SiwxChallengeError(`the sign-in-with-x info block has no ${key}`);
|
|
1834
|
+
}
|
|
1835
|
+
return value;
|
|
1836
|
+
}
|
|
1837
|
+
function requireResources(info) {
|
|
1838
|
+
const resources = info.resources;
|
|
1839
|
+
if (!Array.isArray(resources) || resources.length === 0) {
|
|
1840
|
+
throw new SiwxChallengeError("the sign-in-with-x info block has no resources");
|
|
1841
|
+
}
|
|
1842
|
+
if (!resources.every((resource) => typeof resource === "string" && resource !== "")) {
|
|
1843
|
+
throw new SiwxChallengeError("the sign-in-with-x resources are not all strings");
|
|
1844
|
+
}
|
|
1845
|
+
return resources;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/lib/connectors/agentres/client.ts
|
|
1849
|
+
var DEFAULT_BASE_URL = "https://agentres.dev";
|
|
1850
|
+
function createAgentresClient(options) {
|
|
1851
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
1852
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1853
|
+
const { address, signMessage } = options;
|
|
1854
|
+
async function withSiwx(request) {
|
|
1855
|
+
const url = resolveUrl(baseUrl, request.path);
|
|
1856
|
+
const init = requestInit(request);
|
|
1857
|
+
const challenged = await fetchImpl(url, init);
|
|
1858
|
+
if (challenged.status !== 402) {
|
|
1859
|
+
return readBody(challenged);
|
|
1860
|
+
}
|
|
1861
|
+
await challenged.body?.cancel().catch(() => {
|
|
1862
|
+
});
|
|
1863
|
+
const header = challenged.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
1864
|
+
if (!header) {
|
|
1865
|
+
throw new SiwxChallengeError(
|
|
1866
|
+
`the 402 from ${request.path} carried no ${PAYMENT_REQUIRED_HEADER} header`
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
const challenge = parseChallenge(header, baseUrl);
|
|
1870
|
+
const signature = await signMessage(new TextEncoder().encode(buildMessage(challenge, address)));
|
|
1871
|
+
const authorized = await fetchImpl(url, {
|
|
1872
|
+
...init,
|
|
1873
|
+
headers: { ...init.headers, [SIWX_HEADER]: buildPayload(challenge, address, signature) }
|
|
1874
|
+
});
|
|
1875
|
+
return readBody(authorized);
|
|
1876
|
+
}
|
|
1877
|
+
async function linkResy(body) {
|
|
1878
|
+
const response = await withSiwx({ method: "POST", path: "/api/link-resy", body });
|
|
1879
|
+
return { step: response.step, message: response.message, resyUserId: response.resy_user_id };
|
|
1880
|
+
}
|
|
1750
1881
|
return {
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
description: "Semantic search query over workspace and connected sources. For user search, a substring to match against name or email."
|
|
1761
|
-
},
|
|
1762
|
-
query_type: {
|
|
1763
|
-
type: "string",
|
|
1764
|
-
enum: ["internal", "user"],
|
|
1765
|
-
description: "Type of search: 'internal' for workspace/content search, 'user' for user search"
|
|
1766
|
-
},
|
|
1767
|
-
content_search_mode: {
|
|
1768
|
-
type: "string",
|
|
1769
|
-
enum: ["workspace_search", "ai_search"],
|
|
1770
|
-
description: "Override auto-selection of AI search vs workspace search"
|
|
1771
|
-
},
|
|
1772
|
-
data_source_url: {
|
|
1773
|
-
type: "string",
|
|
1774
|
-
description: "URL of a data source to search within"
|
|
1775
|
-
},
|
|
1776
|
-
page_url: {
|
|
1777
|
-
type: "string",
|
|
1778
|
-
description: "URL or ID of a page to search within"
|
|
1779
|
-
},
|
|
1780
|
-
teamspace_id: {
|
|
1781
|
-
type: "string",
|
|
1782
|
-
description: "ID of a teamspace to restrict search results to"
|
|
1783
|
-
},
|
|
1784
|
-
filters: {
|
|
1785
|
-
type: "object",
|
|
1786
|
-
description: "Filters for search results (only for query_type 'internal'). Supports created_date_range {start_date, end_date} and created_by_user_ids array."
|
|
1787
|
-
}
|
|
1788
|
-
},
|
|
1789
|
-
required: ["query"]
|
|
1790
|
-
}
|
|
1882
|
+
withSiwx,
|
|
1883
|
+
async ensureAccount(email) {
|
|
1884
|
+
const body = await withSiwx({ method: "POST", path: "/api/account", body: { email } });
|
|
1885
|
+
return {
|
|
1886
|
+
accountId: body.account_id,
|
|
1887
|
+
email: body.email,
|
|
1888
|
+
credentialKind: body.credential_kind,
|
|
1889
|
+
walletLinked: body.wallet_linked
|
|
1890
|
+
};
|
|
1791
1891
|
},
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1892
|
+
requestCode(email) {
|
|
1893
|
+
return linkResy({ em_address: email });
|
|
1894
|
+
},
|
|
1895
|
+
verifyCode(email, code) {
|
|
1896
|
+
return linkResy({ em_address: email, code });
|
|
1897
|
+
},
|
|
1898
|
+
async getLinkStatus() {
|
|
1899
|
+
const body = await withSiwx({ method: "GET", path: "/api/me" });
|
|
1900
|
+
return {
|
|
1901
|
+
accountId: body.account_id,
|
|
1902
|
+
email: body.email,
|
|
1903
|
+
resyLinked: body.resy_linked,
|
|
1904
|
+
walletAddress: body.wallet_address,
|
|
1905
|
+
resyLinkedAt: body.resy_linked_at ?? null
|
|
1906
|
+
};
|
|
1802
1907
|
}
|
|
1803
1908
|
};
|
|
1804
1909
|
}
|
|
1805
|
-
function
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
type: "boolean",
|
|
1820
|
-
description: "Include transcript if available"
|
|
1821
|
-
},
|
|
1822
|
-
include_discussions: {
|
|
1823
|
-
type: "boolean",
|
|
1824
|
-
description: "Include discussion counts and inline discussion markers"
|
|
1825
|
-
}
|
|
1826
|
-
},
|
|
1827
|
-
required: ["id"]
|
|
1828
|
-
}
|
|
1829
|
-
},
|
|
1830
|
-
executor: async (args) => {
|
|
1831
|
-
let token = getAccessToken();
|
|
1832
|
-
if (!token) {
|
|
1833
|
-
token = await requestNotionAccess();
|
|
1834
|
-
}
|
|
1835
|
-
try {
|
|
1836
|
-
return await callMCPTool(token, "notion-fetch", args);
|
|
1837
|
-
} catch (error) {
|
|
1838
|
-
return `Error fetching Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
};
|
|
1842
|
-
}
|
|
1843
|
-
function createNotionCreatePagesTool(getAccessToken, requestNotionAccess) {
|
|
1844
|
-
return {
|
|
1845
|
-
type: "function",
|
|
1846
|
-
function: {
|
|
1847
|
-
name: "notion-create-pages",
|
|
1848
|
-
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.",
|
|
1849
|
-
arguments: {
|
|
1850
|
-
type: "object",
|
|
1851
|
-
properties: {
|
|
1852
|
-
pages: {
|
|
1853
|
-
type: "array",
|
|
1854
|
-
description: "Array of page objects to create (max 100)",
|
|
1855
|
-
items: {
|
|
1856
|
-
type: "object",
|
|
1857
|
-
properties: {
|
|
1858
|
-
properties: {
|
|
1859
|
-
type: "object",
|
|
1860
|
-
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."
|
|
1861
|
-
},
|
|
1862
|
-
content: {
|
|
1863
|
-
type: "string",
|
|
1864
|
-
description: "Page content in Notion-flavored Markdown format"
|
|
1865
|
-
}
|
|
1866
|
-
},
|
|
1867
|
-
required: ["properties", "content"]
|
|
1868
|
-
}
|
|
1869
|
-
},
|
|
1870
|
-
parent: {
|
|
1871
|
-
type: "object",
|
|
1872
|
-
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."
|
|
1873
|
-
}
|
|
1874
|
-
},
|
|
1875
|
-
required: ["pages"]
|
|
1876
|
-
}
|
|
1877
|
-
},
|
|
1878
|
-
executor: async (args) => {
|
|
1879
|
-
let token = getAccessToken();
|
|
1880
|
-
if (!token) {
|
|
1881
|
-
token = await requestNotionAccess();
|
|
1882
|
-
}
|
|
1883
|
-
try {
|
|
1884
|
-
return await callMCPTool(token, "notion-create-pages", args);
|
|
1885
|
-
} catch (error) {
|
|
1886
|
-
return `Error creating Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
};
|
|
1890
|
-
}
|
|
1891
|
-
function createNotionUpdatePageTool(getAccessToken, requestNotionAccess) {
|
|
1892
|
-
return {
|
|
1893
|
-
type: "function",
|
|
1894
|
-
function: {
|
|
1895
|
-
name: "notion-update-page",
|
|
1896
|
-
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.",
|
|
1897
|
-
arguments: {
|
|
1898
|
-
type: "object",
|
|
1899
|
-
properties: {
|
|
1900
|
-
data: {
|
|
1901
|
-
type: "object",
|
|
1902
|
-
description: "Update data containing page_id and a command",
|
|
1903
|
-
properties: {
|
|
1904
|
-
page_id: {
|
|
1905
|
-
type: "string",
|
|
1906
|
-
description: "The ID of the page to update (with or without dashes)"
|
|
1907
|
-
},
|
|
1908
|
-
command: {
|
|
1909
|
-
type: "string",
|
|
1910
|
-
enum: [
|
|
1911
|
-
"update_properties",
|
|
1912
|
-
"replace_content",
|
|
1913
|
-
"replace_content_range",
|
|
1914
|
-
"insert_content_after"
|
|
1915
|
-
],
|
|
1916
|
-
description: "The update command to execute"
|
|
1917
|
-
},
|
|
1918
|
-
properties: {
|
|
1919
|
-
type: "object",
|
|
1920
|
-
description: "For update_properties: JSON map of property names to values. Use null to remove a value."
|
|
1921
|
-
},
|
|
1922
|
-
new_str: {
|
|
1923
|
-
type: "string",
|
|
1924
|
-
description: "For replace_content/replace_content_range/insert_content_after: the new content string"
|
|
1925
|
-
},
|
|
1926
|
-
selection_with_ellipsis: {
|
|
1927
|
-
type: "string",
|
|
1928
|
-
description: "For replace_content_range/insert_content_after: unique start and end snippet (~10 chars each with ellipsis)"
|
|
1929
|
-
},
|
|
1930
|
-
allow_deleting_content: {
|
|
1931
|
-
type: "boolean",
|
|
1932
|
-
description: "For replace_content/replace_content_range: allow deletion of child pages/databases"
|
|
1933
|
-
}
|
|
1934
|
-
},
|
|
1935
|
-
required: ["page_id", "command"]
|
|
1936
|
-
}
|
|
1937
|
-
},
|
|
1938
|
-
required: ["data"]
|
|
1939
|
-
}
|
|
1940
|
-
},
|
|
1941
|
-
executor: async (args) => {
|
|
1942
|
-
let token = getAccessToken();
|
|
1943
|
-
if (!token) {
|
|
1944
|
-
token = await requestNotionAccess();
|
|
1945
|
-
}
|
|
1946
|
-
try {
|
|
1947
|
-
return await callMCPTool(token, "notion-update-page", args);
|
|
1948
|
-
} catch (error) {
|
|
1949
|
-
return `Error updating Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
};
|
|
1910
|
+
function resolveUrl(baseUrl, path) {
|
|
1911
|
+
if (!path.startsWith("/") || path.startsWith("//")) {
|
|
1912
|
+
throw new AgentresPathError(
|
|
1913
|
+
`"${path}" is not a path on ${baseUrl} \u2014 it must start with a single "/"`
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
const base = new URL(baseUrl);
|
|
1917
|
+
const resolved = new URL(path, base);
|
|
1918
|
+
if (resolved.origin !== base.origin) {
|
|
1919
|
+
throw new AgentresPathError(
|
|
1920
|
+
`"${path}" resolves to ${resolved.origin} but this client talks to ${base.origin} \u2014 refusing to send it`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
return resolved.toString();
|
|
1953
1924
|
}
|
|
1954
|
-
function
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
type: "object",
|
|
1962
|
-
properties: {
|
|
1963
|
-
page_or_database_ids: {
|
|
1964
|
-
type: "array",
|
|
1965
|
-
description: "Array of page or database IDs to move (UUIDs, max 100)",
|
|
1966
|
-
items: { type: "string" }
|
|
1967
|
-
},
|
|
1968
|
-
new_parent: {
|
|
1969
|
-
type: "object",
|
|
1970
|
-
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."
|
|
1971
|
-
}
|
|
1972
|
-
},
|
|
1973
|
-
required: ["page_or_database_ids", "new_parent"]
|
|
1974
|
-
}
|
|
1975
|
-
},
|
|
1976
|
-
executor: async (args) => {
|
|
1977
|
-
let token = getAccessToken();
|
|
1978
|
-
if (!token) {
|
|
1979
|
-
token = await requestNotionAccess();
|
|
1980
|
-
}
|
|
1981
|
-
try {
|
|
1982
|
-
return await callMCPTool(token, "notion-move-pages", args);
|
|
1983
|
-
} catch (error) {
|
|
1984
|
-
return `Error moving Notion pages: ${error instanceof Error ? error.message : String(error)}`;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
};
|
|
1925
|
+
function requestInit(request) {
|
|
1926
|
+
const headers = { Accept: "application/json" };
|
|
1927
|
+
if (request.body === void 0) {
|
|
1928
|
+
return { method: request.method, headers };
|
|
1929
|
+
}
|
|
1930
|
+
headers["Content-Type"] = "application/json";
|
|
1931
|
+
return { method: request.method, headers, body: JSON.stringify(request.body) };
|
|
1988
1932
|
}
|
|
1989
|
-
function
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
}
|
|
2005
|
-
},
|
|
2006
|
-
executor: async (args) => {
|
|
2007
|
-
let token = getAccessToken();
|
|
2008
|
-
if (!token) {
|
|
2009
|
-
token = await requestNotionAccess();
|
|
2010
|
-
}
|
|
2011
|
-
try {
|
|
2012
|
-
return await callMCPTool(token, "notion-duplicate-page", args);
|
|
2013
|
-
} catch (error) {
|
|
2014
|
-
return `Error duplicating Notion page: ${error instanceof Error ? error.message : String(error)}`;
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
};
|
|
1933
|
+
async function readBody(response) {
|
|
1934
|
+
const body = await response.text();
|
|
1935
|
+
if (!response.ok) {
|
|
1936
|
+
throw parseAgentresError(response.status, body);
|
|
1937
|
+
}
|
|
1938
|
+
try {
|
|
1939
|
+
return JSON.parse(body);
|
|
1940
|
+
} catch {
|
|
1941
|
+
throw new AgentresError(
|
|
1942
|
+
response.status,
|
|
1943
|
+
"INVALID_JSON",
|
|
1944
|
+
`agentres answered ${response.status} with a body that is not JSON`,
|
|
1945
|
+
response.status >= 500
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
2018
1948
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
type: "object",
|
|
2030
|
-
description: "Property schema for the database. Each key is a property name, value defines the type."
|
|
2031
|
-
},
|
|
2032
|
-
parent: {
|
|
2033
|
-
type: "object",
|
|
2034
|
-
description: "Parent page: {type:'page_id', page_id:'...'}. Omit for private workspace-level database."
|
|
2035
|
-
},
|
|
2036
|
-
title: {
|
|
2037
|
-
type: "array",
|
|
2038
|
-
description: "Title of the database as rich text array (max 100)",
|
|
2039
|
-
items: { type: "object" }
|
|
2040
|
-
},
|
|
2041
|
-
description: {
|
|
2042
|
-
type: "array",
|
|
2043
|
-
description: "Description of the database as rich text array (max 100)",
|
|
2044
|
-
items: { type: "object" }
|
|
2045
|
-
}
|
|
2046
|
-
},
|
|
2047
|
-
required: ["properties"]
|
|
2048
|
-
}
|
|
2049
|
-
},
|
|
2050
|
-
executor: async (args) => {
|
|
2051
|
-
let token = getAccessToken();
|
|
2052
|
-
if (!token) {
|
|
2053
|
-
token = await requestNotionAccess();
|
|
2054
|
-
}
|
|
2055
|
-
try {
|
|
2056
|
-
return await callMCPTool(token, "notion-create-database", args);
|
|
2057
|
-
} catch (error) {
|
|
2058
|
-
return `Error creating Notion database: ${error instanceof Error ? error.message : String(error)}`;
|
|
2059
|
-
}
|
|
1949
|
+
|
|
1950
|
+
// src/lib/connectors/client.ts
|
|
1951
|
+
var DEFAULT_REFRESH_BEFORE_MS = 3e4;
|
|
1952
|
+
function createConnectorTokenGetter(client, provider, opts = {}) {
|
|
1953
|
+
const refreshBeforeMs = opts.refreshBeforeMs ?? DEFAULT_REFRESH_BEFORE_MS;
|
|
1954
|
+
const now = opts.now ?? Date.now;
|
|
1955
|
+
let cached = null;
|
|
1956
|
+
return async () => {
|
|
1957
|
+
if (cached && cached.expiresAt - refreshBeforeMs > now()) {
|
|
1958
|
+
return cached.accessToken;
|
|
2060
1959
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
name: "notion-update-data-source",
|
|
2068
|
-
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.",
|
|
2069
|
-
arguments: {
|
|
2070
|
-
type: "object",
|
|
2071
|
-
properties: {
|
|
2072
|
-
data_source_id: {
|
|
2073
|
-
type: "string",
|
|
2074
|
-
description: "The ID of the data source to update (UUID). Can be a data source ID or database ID."
|
|
2075
|
-
},
|
|
2076
|
-
title: {
|
|
2077
|
-
type: "array",
|
|
2078
|
-
description: "New title as rich text array (max 100)",
|
|
2079
|
-
items: { type: "object" }
|
|
2080
|
-
},
|
|
2081
|
-
description: {
|
|
2082
|
-
type: "array",
|
|
2083
|
-
description: "New description as rich text array (max 100)",
|
|
2084
|
-
items: { type: "object" }
|
|
2085
|
-
},
|
|
2086
|
-
properties: {
|
|
2087
|
-
type: "object",
|
|
2088
|
-
description: "Property schema updates. Use null to remove, {name:'...'} to rename, or full definition to add/update."
|
|
2089
|
-
},
|
|
2090
|
-
is_inline: {
|
|
2091
|
-
type: "boolean",
|
|
2092
|
-
description: "Whether the database is inline"
|
|
2093
|
-
},
|
|
2094
|
-
in_trash: {
|
|
2095
|
-
type: "boolean",
|
|
2096
|
-
description: "Trash the data source (requires Notion UI to undo)"
|
|
2097
|
-
}
|
|
2098
|
-
},
|
|
2099
|
-
required: ["data_source_id"]
|
|
2100
|
-
}
|
|
2101
|
-
},
|
|
2102
|
-
executor: async (args) => {
|
|
2103
|
-
let token = getAccessToken();
|
|
2104
|
-
if (!token) {
|
|
2105
|
-
token = await requestNotionAccess();
|
|
2106
|
-
}
|
|
2107
|
-
try {
|
|
2108
|
-
return await callMCPTool(token, "notion-update-data-source", args);
|
|
2109
|
-
} catch (error) {
|
|
2110
|
-
return `Error updating Notion data source: ${error instanceof Error ? error.message : String(error)}`;
|
|
2111
|
-
}
|
|
1960
|
+
let result;
|
|
1961
|
+
try {
|
|
1962
|
+
result = await client.mintConnectorToken(provider);
|
|
1963
|
+
} catch {
|
|
1964
|
+
cached = null;
|
|
1965
|
+
return null;
|
|
2112
1966
|
}
|
|
2113
|
-
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
return {
|
|
2117
|
-
type: "function",
|
|
2118
|
-
function: {
|
|
2119
|
-
name: "notion-create-comment",
|
|
2120
|
-
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).",
|
|
2121
|
-
arguments: {
|
|
2122
|
-
type: "object",
|
|
2123
|
-
properties: {
|
|
2124
|
-
page_id: {
|
|
2125
|
-
type: "string",
|
|
2126
|
-
description: "The ID of the page to comment on (with or without dashes)"
|
|
2127
|
-
},
|
|
2128
|
-
rich_text: {
|
|
2129
|
-
type: "array",
|
|
2130
|
-
description: "Comment content as rich text array (max 100). Objects can be text, mention, or equation.",
|
|
2131
|
-
items: {
|
|
2132
|
-
type: "object",
|
|
2133
|
-
properties: {
|
|
2134
|
-
text: {
|
|
2135
|
-
type: "object",
|
|
2136
|
-
properties: {
|
|
2137
|
-
content: { type: "string" }
|
|
2138
|
-
},
|
|
2139
|
-
required: ["content"]
|
|
2140
|
-
}
|
|
2141
|
-
},
|
|
2142
|
-
required: ["text"]
|
|
2143
|
-
}
|
|
2144
|
-
},
|
|
2145
|
-
discussion_id: {
|
|
2146
|
-
type: "string",
|
|
2147
|
-
description: "ID or URL of an existing discussion to reply to"
|
|
2148
|
-
},
|
|
2149
|
-
selection_with_ellipsis: {
|
|
2150
|
-
type: "string",
|
|
2151
|
-
description: "Unique start and end snippet (~10 chars each with ellipsis) of content to comment on"
|
|
2152
|
-
}
|
|
2153
|
-
},
|
|
2154
|
-
required: ["page_id", "rich_text"]
|
|
2155
|
-
}
|
|
2156
|
-
},
|
|
2157
|
-
executor: async (args) => {
|
|
2158
|
-
let token = getAccessToken();
|
|
2159
|
-
if (!token) {
|
|
2160
|
-
token = await requestNotionAccess();
|
|
2161
|
-
}
|
|
2162
|
-
try {
|
|
2163
|
-
return await callMCPTool(token, "notion-create-comment", args);
|
|
2164
|
-
} catch (error) {
|
|
2165
|
-
return `Error creating Notion comment: ${error instanceof Error ? error.message : String(error)}`;
|
|
2166
|
-
}
|
|
1967
|
+
if (result.ok) {
|
|
1968
|
+
cached = { accessToken: result.accessToken, expiresAt: result.expiresAt };
|
|
1969
|
+
return result.accessToken;
|
|
2167
1970
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
return {
|
|
2172
|
-
type: "function",
|
|
2173
|
-
function: {
|
|
2174
|
-
name: "notion-get-comments",
|
|
2175
|
-
description: "Get comments and discussions from a Notion page. Returns discussions with full comment content.",
|
|
2176
|
-
arguments: {
|
|
2177
|
-
type: "object",
|
|
2178
|
-
properties: {
|
|
2179
|
-
page_id: {
|
|
2180
|
-
type: "string",
|
|
2181
|
-
description: "Identifier for the Notion page"
|
|
2182
|
-
},
|
|
2183
|
-
include_all_blocks: {
|
|
2184
|
-
type: "boolean",
|
|
2185
|
-
description: "Include discussions on child blocks (default: false)"
|
|
2186
|
-
},
|
|
2187
|
-
include_resolved: {
|
|
2188
|
-
type: "boolean",
|
|
2189
|
-
description: "Include resolved discussions (default: false)"
|
|
2190
|
-
},
|
|
2191
|
-
discussion_id: {
|
|
2192
|
-
type: "string",
|
|
2193
|
-
description: "Fetch a specific discussion by ID or URL (e.g., discussion://pageId/blockId/discussionId)"
|
|
2194
|
-
}
|
|
2195
|
-
},
|
|
2196
|
-
required: ["page_id"]
|
|
2197
|
-
}
|
|
2198
|
-
},
|
|
2199
|
-
executor: async (args) => {
|
|
2200
|
-
let token = getAccessToken();
|
|
2201
|
-
if (!token) {
|
|
2202
|
-
token = await requestNotionAccess();
|
|
2203
|
-
}
|
|
2204
|
-
try {
|
|
2205
|
-
return await callMCPTool(token, "notion-get-comments", args);
|
|
2206
|
-
} catch (error) {
|
|
2207
|
-
return `Error retrieving Notion comments: ${error instanceof Error ? error.message : String(error)}`;
|
|
2208
|
-
}
|
|
1971
|
+
cached = null;
|
|
1972
|
+
if (opts.onNotConnected) {
|
|
1973
|
+
return opts.onNotConnected(result.error);
|
|
2209
1974
|
}
|
|
1975
|
+
return null;
|
|
2210
1976
|
};
|
|
2211
1977
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
if (!token) {
|
|
2243
|
-
token = await requestNotionAccess();
|
|
2244
|
-
}
|
|
2245
|
-
try {
|
|
2246
|
-
return await callMCPTool(token, "notion-get-users", args);
|
|
2247
|
-
} catch (error) {
|
|
2248
|
-
return `Error listing Notion users: ${error instanceof Error ? error.message : String(error)}`;
|
|
1978
|
+
|
|
1979
|
+
// src/tools/notion.ts
|
|
1980
|
+
var MCP_HTTP_ENDPOINT = "https://mcp.notion.com/mcp";
|
|
1981
|
+
var MCP_SSE_ENDPOINT = "https://mcp.notion.com/sse";
|
|
1982
|
+
var MAX_CONTENT_LENGTH2 = 5e4;
|
|
1983
|
+
function truncateToolResult(result) {
|
|
1984
|
+
if (result === void 0 || result === null) return result;
|
|
1985
|
+
const stringified = typeof result === "string" ? result : JSON.stringify(result);
|
|
1986
|
+
if (stringified.length <= MAX_CONTENT_LENGTH2) {
|
|
1987
|
+
return result;
|
|
1988
|
+
}
|
|
1989
|
+
return stringified.slice(0, MAX_CONTENT_LENGTH2) + `
|
|
1990
|
+
|
|
1991
|
+
... (content truncated, showing first ${MAX_CONTENT_LENGTH2} characters of ${stringified.length})`;
|
|
1992
|
+
}
|
|
1993
|
+
var requestIdCounter = Math.floor(Math.random() * 1e6);
|
|
1994
|
+
var sessionCache = /* @__PURE__ */ new Map();
|
|
1995
|
+
function generateRequestId() {
|
|
1996
|
+
return ++requestIdCounter;
|
|
1997
|
+
}
|
|
1998
|
+
function parseSSEResponse(text) {
|
|
1999
|
+
const events = text.split(/\n\n+/);
|
|
2000
|
+
let lastEventData = "";
|
|
2001
|
+
for (const event of events) {
|
|
2002
|
+
const dataLines = [];
|
|
2003
|
+
for (const line of event.split("\n")) {
|
|
2004
|
+
if (line.startsWith("data: ")) {
|
|
2005
|
+
dataLines.push(line.slice(6));
|
|
2006
|
+
} else if (line.startsWith("data:")) {
|
|
2007
|
+
dataLines.push(line.slice(5));
|
|
2249
2008
|
}
|
|
2250
2009
|
}
|
|
2251
|
-
|
|
2010
|
+
if (dataLines.length > 0) {
|
|
2011
|
+
lastEventData = dataLines.join("\n");
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
if (!lastEventData) {
|
|
2015
|
+
throw new Error("No data found in SSE response");
|
|
2016
|
+
}
|
|
2017
|
+
return JSON.parse(lastEventData);
|
|
2252
2018
|
}
|
|
2253
|
-
function
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
}
|
|
2274
|
-
try {
|
|
2275
|
-
return await callMCPTool(token, "notion-get-teams", args);
|
|
2276
|
-
} catch (error) {
|
|
2277
|
-
return `Error retrieving Notion teams: ${error instanceof Error ? error.message : String(error)}`;
|
|
2019
|
+
async function parseResponseBody(response) {
|
|
2020
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
2021
|
+
const text = await response.text();
|
|
2022
|
+
if (contentType.includes("text/event-stream") || text.startsWith("event:") || text.startsWith("data:")) {
|
|
2023
|
+
return parseSSEResponse(text);
|
|
2024
|
+
}
|
|
2025
|
+
return JSON.parse(text);
|
|
2026
|
+
}
|
|
2027
|
+
async function initializeMCPSession(accessToken) {
|
|
2028
|
+
const requestId = generateRequestId();
|
|
2029
|
+
const initRequest = {
|
|
2030
|
+
jsonrpc: "2.0",
|
|
2031
|
+
id: requestId,
|
|
2032
|
+
method: "initialize",
|
|
2033
|
+
params: {
|
|
2034
|
+
protocolVersion: "2024-11-05",
|
|
2035
|
+
capabilities: {},
|
|
2036
|
+
clientInfo: {
|
|
2037
|
+
name: "Anuma",
|
|
2038
|
+
version: "1.0.0"
|
|
2278
2039
|
}
|
|
2279
2040
|
}
|
|
2280
2041
|
};
|
|
2042
|
+
const response = await fetch(MCP_HTTP_ENDPOINT, {
|
|
2043
|
+
method: "POST",
|
|
2044
|
+
headers: {
|
|
2045
|
+
"Content-Type": "application/json",
|
|
2046
|
+
Accept: "application/json, text/event-stream",
|
|
2047
|
+
Authorization: `Bearer ${accessToken}`
|
|
2048
|
+
},
|
|
2049
|
+
body: JSON.stringify(initRequest)
|
|
2050
|
+
});
|
|
2051
|
+
if (!response.ok) {
|
|
2052
|
+
const errorBody = await response.text().catch(() => "");
|
|
2053
|
+
throw new Error(
|
|
2054
|
+
`MCP initialization failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
const sessionId = response.headers.get("Mcp-Session-Id");
|
|
2058
|
+
if (!sessionId) {
|
|
2059
|
+
throw new Error("No Mcp-Session-Id returned from initialization");
|
|
2060
|
+
}
|
|
2061
|
+
const jsonRpcResponse = await parseResponseBody(response);
|
|
2062
|
+
if (jsonRpcResponse.error) {
|
|
2063
|
+
const err = jsonRpcResponse.error;
|
|
2064
|
+
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
2065
|
+
throw new Error(`MCP initialization error: ${errMsg}`);
|
|
2066
|
+
}
|
|
2067
|
+
await fetch(MCP_HTTP_ENDPOINT, {
|
|
2068
|
+
method: "POST",
|
|
2069
|
+
headers: {
|
|
2070
|
+
"Content-Type": "application/json",
|
|
2071
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2072
|
+
"Mcp-Session-Id": sessionId
|
|
2073
|
+
},
|
|
2074
|
+
body: JSON.stringify({
|
|
2075
|
+
jsonrpc: "2.0",
|
|
2076
|
+
method: "notifications/initialized"
|
|
2077
|
+
})
|
|
2078
|
+
});
|
|
2079
|
+
sessionCache.set(accessToken, sessionId);
|
|
2080
|
+
return sessionId;
|
|
2281
2081
|
}
|
|
2282
|
-
function
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
createNotionCreatePagesTool(getAccessToken, requestNotionAccess),
|
|
2289
|
-
createNotionUpdatePageTool(getAccessToken, requestNotionAccess),
|
|
2290
|
-
createNotionMovePagesTool(getAccessToken, requestNotionAccess),
|
|
2291
|
-
createNotionDuplicatePageTool(getAccessToken, requestNotionAccess),
|
|
2292
|
-
// Data Sources (Databases)
|
|
2293
|
-
createNotionCreateDatabaseTool(getAccessToken, requestNotionAccess),
|
|
2294
|
-
createNotionUpdateDataSourceTool(getAccessToken, requestNotionAccess),
|
|
2295
|
-
// Comments
|
|
2296
|
-
createNotionCreateCommentTool(getAccessToken, requestNotionAccess),
|
|
2297
|
-
createNotionGetCommentsTool(getAccessToken, requestNotionAccess),
|
|
2298
|
-
// Users & Teams
|
|
2299
|
-
createNotionGetUsersTool(getAccessToken, requestNotionAccess),
|
|
2300
|
-
createNotionGetTeamsTool(getAccessToken, requestNotionAccess)
|
|
2301
|
-
];
|
|
2082
|
+
async function ensureMCPSession(accessToken) {
|
|
2083
|
+
const cached = sessionCache.get(accessToken);
|
|
2084
|
+
if (cached) {
|
|
2085
|
+
return cached;
|
|
2086
|
+
}
|
|
2087
|
+
return initializeMCPSession(accessToken);
|
|
2302
2088
|
}
|
|
2303
|
-
function
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2089
|
+
async function callMCPTool(accessToken, toolName, args) {
|
|
2090
|
+
const sessionId = await ensureMCPSession(accessToken);
|
|
2091
|
+
const requestId = generateRequestId();
|
|
2092
|
+
const jsonRpcRequest = {
|
|
2093
|
+
jsonrpc: "2.0",
|
|
2094
|
+
id: requestId,
|
|
2095
|
+
method: "tools/call",
|
|
2096
|
+
params: {
|
|
2097
|
+
name: toolName,
|
|
2098
|
+
arguments: args
|
|
2099
|
+
}
|
|
2307
2100
|
};
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
if (!
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2101
|
+
const response = await fetch(MCP_HTTP_ENDPOINT, {
|
|
2102
|
+
method: "POST",
|
|
2103
|
+
headers: {
|
|
2104
|
+
"Content-Type": "application/json",
|
|
2105
|
+
Accept: "application/json, text/event-stream",
|
|
2106
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2107
|
+
"Mcp-Session-Id": sessionId
|
|
2108
|
+
},
|
|
2109
|
+
body: JSON.stringify(jsonRpcRequest)
|
|
2110
|
+
});
|
|
2111
|
+
if (!response.ok) {
|
|
2112
|
+
if (response.status === 400 || response.status === 401) {
|
|
2113
|
+
sessionCache.delete(accessToken);
|
|
2114
|
+
const newSessionId = await initializeMCPSession(accessToken);
|
|
2115
|
+
const retryResponse = await fetch(MCP_HTTP_ENDPOINT, {
|
|
2116
|
+
method: "POST",
|
|
2117
|
+
headers: {
|
|
2118
|
+
"Content-Type": "application/json",
|
|
2119
|
+
Accept: "application/json, text/event-stream",
|
|
2120
|
+
Authorization: `Bearer ${accessToken}`,
|
|
2121
|
+
"Mcp-Session-Id": newSessionId
|
|
2122
|
+
},
|
|
2123
|
+
body: JSON.stringify(jsonRpcRequest)
|
|
2124
|
+
});
|
|
2125
|
+
if (!retryResponse.ok) {
|
|
2126
|
+
const errorBody2 = await retryResponse.text().catch(() => "");
|
|
2127
|
+
throw new Error(
|
|
2128
|
+
`MCP request failed: ${retryResponse.status} ${retryResponse.statusText}${errorBody2 ? ` - ${errorBody2}` : ""}`
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
const retryJsonRpcResponse = await parseResponseBody(retryResponse);
|
|
2132
|
+
if (retryJsonRpcResponse.error) {
|
|
2133
|
+
const err = retryJsonRpcResponse.error;
|
|
2134
|
+
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
2135
|
+
throw new Error(`MCP tool error: ${errMsg}`);
|
|
2136
|
+
}
|
|
2137
|
+
return truncateToolResult(retryJsonRpcResponse.result);
|
|
2324
2138
|
}
|
|
2139
|
+
const errorBody = await response.text().catch(() => "");
|
|
2140
|
+
throw new Error(
|
|
2141
|
+
`MCP request failed: ${response.status} ${response.statusText}${errorBody ? ` - ${errorBody}` : ""}`
|
|
2142
|
+
);
|
|
2325
2143
|
}
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
throw new Error(
|
|
2144
|
+
const jsonRpcResponse = await parseResponseBody(response);
|
|
2145
|
+
if (jsonRpcResponse.error) {
|
|
2146
|
+
const err = jsonRpcResponse.error;
|
|
2147
|
+
const errMsg = typeof err.message === "string" ? err.message : JSON.stringify(err);
|
|
2148
|
+
throw new Error(`MCP tool error: ${errMsg}`);
|
|
2331
2149
|
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2150
|
+
return truncateToolResult(jsonRpcResponse.result);
|
|
2151
|
+
}
|
|
2152
|
+
var NOTION_PROVIDER = "notion";
|
|
2153
|
+
function directRunner(getAccessToken, requestNotionAccess) {
|
|
2154
|
+
return async (toolName, args) => {
|
|
2155
|
+
let token = getAccessToken();
|
|
2156
|
+
if (!token) {
|
|
2157
|
+
token = await requestNotionAccess();
|
|
2158
|
+
}
|
|
2159
|
+
return callMCPTool(token, toolName, args);
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
function readField(json, key) {
|
|
2163
|
+
return typeof json === "object" && json !== null ? json[key] : void 0;
|
|
2164
|
+
}
|
|
2165
|
+
function readString(json, key) {
|
|
2166
|
+
const value = readField(json, key);
|
|
2167
|
+
return typeof value === "string" ? value : void 0;
|
|
2168
|
+
}
|
|
2169
|
+
function notionConnectorError(status, json) {
|
|
2170
|
+
const code = readField(json, "code");
|
|
2171
|
+
if (status === 503) {
|
|
2172
|
+
return code === "upstream_unavailable" ? buildConnectorErrorResult("upstream_unavailable", NOTION_PROVIDER) : null;
|
|
2173
|
+
}
|
|
2174
|
+
if (status !== 401 && status !== 403 && status !== 412) return null;
|
|
2175
|
+
switch (code) {
|
|
2176
|
+
case "scope_not_covered": {
|
|
2177
|
+
const missingScopes = readField(json, "missing_scopes");
|
|
2178
|
+
return buildConnectorErrorResult(
|
|
2179
|
+
"scope_not_covered",
|
|
2180
|
+
NOTION_PROVIDER,
|
|
2181
|
+
readString(json, "connect_url"),
|
|
2182
|
+
Array.isArray(missingScopes) ? { missingScopes } : void 0
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
case "insufficient_scope":
|
|
2186
|
+
return buildConnectorErrorResult("insufficient_scope", NOTION_PROVIDER, void 0, {
|
|
2187
|
+
required: readString(json, "required")
|
|
2188
|
+
});
|
|
2189
|
+
case "upstream_unavailable":
|
|
2190
|
+
return buildConnectorErrorResult("upstream_unavailable", NOTION_PROVIDER);
|
|
2191
|
+
default:
|
|
2192
|
+
return buildConnectorErrorResult(
|
|
2193
|
+
"connector_not_connected",
|
|
2194
|
+
NOTION_PROVIDER,
|
|
2195
|
+
readString(json, "connect_url")
|
|
2196
|
+
);
|
|
2335
2197
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
Accept: "application/vnd.github+json",
|
|
2345
|
-
"Content-Type": "application/json",
|
|
2346
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
2347
|
-
...options.headers
|
|
2198
|
+
}
|
|
2199
|
+
function proxyRunner(callMcp) {
|
|
2200
|
+
return async (toolName, args) => {
|
|
2201
|
+
const { status, json } = await callMcp(toolName, args);
|
|
2202
|
+
if (status >= 200 && status < 300) {
|
|
2203
|
+
const result = readField(json, "result");
|
|
2204
|
+
if (result === void 0) {
|
|
2205
|
+
throw new Error(`Notion returned no result (${status})`);
|
|
2348
2206
|
}
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2207
|
+
return truncateToolResult(result);
|
|
2208
|
+
}
|
|
2209
|
+
const connectorError = notionConnectorError(status, json);
|
|
2210
|
+
if (connectorError) return connectorError;
|
|
2211
|
+
const message = readField(json, "error");
|
|
2212
|
+
throw new Error(`${typeof message === "string" ? message : JSON.stringify(json)} (${status})`);
|
|
2213
|
+
};
|
|
2353
2214
|
}
|
|
2354
|
-
function
|
|
2215
|
+
function withNotionExecutor(run, errorPrefix, tool) {
|
|
2355
2216
|
return {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
name: "github_get_authenticated_user",
|
|
2359
|
-
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.",
|
|
2360
|
-
parameters: {
|
|
2361
|
-
type: "object",
|
|
2362
|
-
properties: {}
|
|
2363
|
-
}
|
|
2364
|
-
},
|
|
2365
|
-
executor: async () => {
|
|
2366
|
-
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2367
|
-
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2217
|
+
...tool,
|
|
2218
|
+
executor: async (args) => {
|
|
2368
2219
|
try {
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
const orgsResp = await githubFetch(token, "/user/orgs?per_page=100");
|
|
2373
|
-
let orgs = [];
|
|
2374
|
-
if (orgsResp.ok) {
|
|
2375
|
-
const orgsData = await orgsResp.json();
|
|
2376
|
-
orgs = orgsData.map((o) => o.login);
|
|
2377
|
-
}
|
|
2378
|
-
return {
|
|
2379
|
-
login: user.login,
|
|
2380
|
-
name: user.name,
|
|
2381
|
-
email: user.email,
|
|
2382
|
-
organizations: orgs,
|
|
2383
|
-
public_repos: user.public_repos,
|
|
2384
|
-
html_url: user.html_url
|
|
2385
|
-
};
|
|
2386
|
-
} catch (err) {
|
|
2387
|
-
getLogger().error("github_get_authenticated_user error", err);
|
|
2388
|
-
return "Error: Failed to get user profile.";
|
|
2220
|
+
return await run(tool.function.name, args);
|
|
2221
|
+
} catch (error) {
|
|
2222
|
+
return `${errorPrefix}: ${error instanceof Error ? error.message : String(error)}`;
|
|
2389
2223
|
}
|
|
2390
2224
|
}
|
|
2391
2225
|
};
|
|
2392
2226
|
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
if (text.length <= maxLen) return text;
|
|
2396
|
-
return text.slice(0, maxLen) + `
|
|
2397
|
-
|
|
2398
|
-
... (truncated, ${text.length - maxLen} characters omitted)`;
|
|
2399
|
-
}
|
|
2400
|
-
function createGitHubApiTool(getAccessToken, requestAccess) {
|
|
2401
|
-
return {
|
|
2227
|
+
function notionSearchTool(run) {
|
|
2228
|
+
return withNotionExecutor(run, "Error searching Notion", {
|
|
2402
2229
|
type: "function",
|
|
2403
2230
|
function: {
|
|
2404
|
-
name: "
|
|
2405
|
-
description:
|
|
2406
|
-
|
|
2407
|
-
Examples:
|
|
2408
|
-
- List PRs: GET /repos/{owner}/{repo}/pulls?state=open
|
|
2409
|
-
- Get file: GET /repos/{owner}/{repo}/contents/{path}
|
|
2410
|
-
- Create issue: POST /repos/{owner}/{repo}/issues with body {"title": "...", "body": "..."}
|
|
2411
|
-
- Search repos: GET /search/repositories?q={query}
|
|
2412
|
-
|
|
2413
|
-
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.`,
|
|
2414
|
-
parameters: {
|
|
2231
|
+
name: "notion-search",
|
|
2232
|
+
description: "Semantic search over Notion workspace and connected sources (Slack, Google Drive, Github, Jira, etc). Can also search for users by name or email.",
|
|
2233
|
+
arguments: {
|
|
2415
2234
|
type: "object",
|
|
2416
2235
|
properties: {
|
|
2417
|
-
|
|
2236
|
+
query: {
|
|
2418
2237
|
type: "string",
|
|
2419
|
-
|
|
2420
|
-
description: "HTTP method"
|
|
2238
|
+
description: "Semantic search query over workspace and connected sources. For user search, a substring to match against name or email."
|
|
2421
2239
|
},
|
|
2422
|
-
|
|
2240
|
+
query_type: {
|
|
2423
2241
|
type: "string",
|
|
2424
|
-
|
|
2242
|
+
enum: ["internal", "user"],
|
|
2243
|
+
description: "Type of search: 'internal' for workspace/content search, 'user' for user search"
|
|
2425
2244
|
},
|
|
2426
|
-
|
|
2245
|
+
content_search_mode: {
|
|
2246
|
+
type: "string",
|
|
2247
|
+
enum: ["workspace_search", "ai_search"],
|
|
2248
|
+
description: "Override auto-selection of AI search vs workspace search"
|
|
2249
|
+
},
|
|
2250
|
+
data_source_url: {
|
|
2251
|
+
type: "string",
|
|
2252
|
+
description: "URL of a data source to search within"
|
|
2253
|
+
},
|
|
2254
|
+
page_url: {
|
|
2255
|
+
type: "string",
|
|
2256
|
+
description: "URL or ID of a page to search within"
|
|
2257
|
+
},
|
|
2258
|
+
teamspace_id: {
|
|
2259
|
+
type: "string",
|
|
2260
|
+
description: "ID of a teamspace to restrict search results to"
|
|
2261
|
+
},
|
|
2262
|
+
filters: {
|
|
2427
2263
|
type: "object",
|
|
2428
|
-
description: "
|
|
2429
|
-
additionalProperties: true
|
|
2264
|
+
description: "Filters for search results (only for query_type 'internal'). Supports created_date_range {start_date, end_date} and created_by_user_ids array."
|
|
2430
2265
|
}
|
|
2431
2266
|
},
|
|
2432
|
-
required: ["
|
|
2433
|
-
}
|
|
2434
|
-
},
|
|
2435
|
-
executor: async (args) => {
|
|
2436
|
-
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2437
|
-
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2438
|
-
const method = args.method;
|
|
2439
|
-
const path = args.path;
|
|
2440
|
-
const body = args.body;
|
|
2441
|
-
try {
|
|
2442
|
-
const resp = await githubFetch(token, path, {
|
|
2443
|
-
method,
|
|
2444
|
-
body: body ? JSON.stringify(body) : void 0
|
|
2445
|
-
});
|
|
2446
|
-
const text = await resp.text();
|
|
2447
|
-
if (!resp.ok) {
|
|
2448
|
-
return `Error: GitHub API ${method} ${path} returned ${resp.status}: ${truncate(text, 500)}`;
|
|
2449
|
-
}
|
|
2450
|
-
try {
|
|
2451
|
-
const data = JSON.parse(text);
|
|
2452
|
-
const json = JSON.stringify(data);
|
|
2453
|
-
return truncate(json, MAX_RESPONSE_SIZE);
|
|
2454
|
-
} catch {
|
|
2455
|
-
return truncate(text, MAX_RESPONSE_SIZE);
|
|
2456
|
-
}
|
|
2457
|
-
} catch (err) {
|
|
2458
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
2459
|
-
return `Error: GitHub API request timed out after ${FETCH_TIMEOUT_MS / 1e3}s.`;
|
|
2460
|
-
}
|
|
2461
|
-
getLogger().error("github_api error", err);
|
|
2462
|
-
return `Error: GitHub API request failed: ${err instanceof Error ? err.message : "unknown error"}`;
|
|
2267
|
+
required: ["query"]
|
|
2463
2268
|
}
|
|
2464
2269
|
}
|
|
2465
|
-
};
|
|
2466
|
-
}
|
|
2467
|
-
function createGitHubTools(getAccessToken, requestGitHubAccess) {
|
|
2468
|
-
return [
|
|
2469
|
-
createGetAuthenticatedUserTool(getAccessToken, requestGitHubAccess),
|
|
2470
|
-
createGitHubApiTool(getAccessToken, requestGitHubAccess)
|
|
2471
|
-
];
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
// src/lib/connectors/agentres/errors.ts
|
|
2475
|
-
var AgentresError = class extends Error {
|
|
2476
|
-
constructor(status, code, message, retryable, nextStep) {
|
|
2477
|
-
super(message);
|
|
2478
|
-
this.name = "AgentresError";
|
|
2479
|
-
this.status = status;
|
|
2480
|
-
this.code = code;
|
|
2481
|
-
this.retryable = retryable;
|
|
2482
|
-
this.nextStep = nextStep;
|
|
2483
|
-
}
|
|
2484
|
-
};
|
|
2485
|
-
var SiwxUnsupportedError = class extends Error {
|
|
2486
|
-
constructor(message) {
|
|
2487
|
-
super(message);
|
|
2488
|
-
this.name = "SiwxUnsupportedError";
|
|
2489
|
-
}
|
|
2490
|
-
};
|
|
2491
|
-
var AgentresPathError = class extends Error {
|
|
2492
|
-
constructor(message) {
|
|
2493
|
-
super(message);
|
|
2494
|
-
this.name = "AgentresPathError";
|
|
2495
|
-
}
|
|
2496
|
-
};
|
|
2497
|
-
var SiwxChallengeError = class extends Error {
|
|
2498
|
-
constructor(message, options) {
|
|
2499
|
-
super(message);
|
|
2500
|
-
this.name = "SiwxChallengeError";
|
|
2501
|
-
this.cause = options?.cause;
|
|
2502
|
-
}
|
|
2503
|
-
};
|
|
2504
|
-
function parseAgentresError(status, body) {
|
|
2505
|
-
const stated = statedError(body);
|
|
2506
|
-
if (!stated) {
|
|
2507
|
-
return new AgentresError(
|
|
2508
|
-
status,
|
|
2509
|
-
`HTTP_${status}`,
|
|
2510
|
-
body.trim() || `HTTP ${status}`,
|
|
2511
|
-
status >= 500
|
|
2512
|
-
);
|
|
2513
|
-
}
|
|
2514
|
-
const code = typeof stated.code === "string" ? stated.code : `HTTP_${status}`;
|
|
2515
|
-
const message = typeof stated.message === "string" ? stated.message : `HTTP ${status}`;
|
|
2516
|
-
const retryable = typeof stated.retryable === "boolean" ? stated.retryable : status >= 500;
|
|
2517
|
-
const nextStep = typeof stated.next_step === "string" ? stated.next_step : void 0;
|
|
2518
|
-
return new AgentresError(status, code, message, retryable, nextStep);
|
|
2270
|
+
});
|
|
2519
2271
|
}
|
|
2520
|
-
function
|
|
2521
|
-
|
|
2522
|
-
try {
|
|
2523
|
-
parsed = JSON.parse(body);
|
|
2524
|
-
} catch {
|
|
2525
|
-
return null;
|
|
2526
|
-
}
|
|
2527
|
-
const envelope = parsed;
|
|
2528
|
-
const stated = typeof envelope?.error === "object" && envelope.error !== null ? envelope.error : null;
|
|
2529
|
-
if (!stated) {
|
|
2530
|
-
return null;
|
|
2531
|
-
}
|
|
2532
|
-
const fields = stated;
|
|
2533
|
-
if (typeof fields.code !== "string" && typeof fields.message !== "string") {
|
|
2534
|
-
return null;
|
|
2535
|
-
}
|
|
2536
|
-
return fields;
|
|
2272
|
+
function createNotionSearchTool(getAccessToken, requestNotionAccess) {
|
|
2273
|
+
return notionSearchTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2537
2274
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
function
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2275
|
+
function notionFetchTool(run) {
|
|
2276
|
+
return withNotionExecutor(run, "Error fetching Notion page", {
|
|
2277
|
+
type: "function",
|
|
2278
|
+
function: {
|
|
2279
|
+
name: "notion-fetch",
|
|
2280
|
+
description: "Retrieve a Notion page or database by URL or ID. Pages return enhanced Markdown. Databases return all data sources.",
|
|
2281
|
+
arguments: {
|
|
2282
|
+
type: "object",
|
|
2283
|
+
properties: {
|
|
2284
|
+
id: {
|
|
2285
|
+
type: "string",
|
|
2286
|
+
description: "The ID or URL of the Notion page/database. Supports notion.so URLs, Notion Sites URLs (*.notion.site), and raw UUIDs."
|
|
2287
|
+
},
|
|
2288
|
+
include_transcript: {
|
|
2289
|
+
type: "boolean",
|
|
2290
|
+
description: "Include transcript if available"
|
|
2291
|
+
},
|
|
2292
|
+
include_discussions: {
|
|
2293
|
+
type: "boolean",
|
|
2294
|
+
description: "Include discussion counts and inline discussion markers"
|
|
2295
|
+
}
|
|
2296
|
+
},
|
|
2297
|
+
required: ["id"]
|
|
2298
|
+
}
|
|
2551
2299
|
}
|
|
2552
|
-
|
|
2553
|
-
}
|
|
2554
|
-
return base64;
|
|
2555
|
-
}
|
|
2556
|
-
function base64ToUint8Array(base64) {
|
|
2557
|
-
if (typeof Buffer !== "undefined") {
|
|
2558
|
-
const buf = Buffer.from(base64, "base64");
|
|
2559
|
-
return new Uint8Array(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
|
|
2560
|
-
}
|
|
2561
|
-
const binary = atob(base64);
|
|
2562
|
-
const bytes = new Uint8Array(binary.length);
|
|
2563
|
-
for (let i = 0; i < binary.length; i++) {
|
|
2564
|
-
bytes[i] = binary.charCodeAt(i);
|
|
2565
|
-
}
|
|
2566
|
-
return bytes;
|
|
2300
|
+
});
|
|
2567
2301
|
}
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
2571
|
-
var SIWX_HEADER = "SIGN-IN-WITH-X";
|
|
2572
|
-
var SIWX_EXTENSION = "sign-in-with-x";
|
|
2573
|
-
var SOLANA_NAMESPACE = "solana:";
|
|
2574
|
-
var ED25519_SIGNATURE_BYTES = 64;
|
|
2575
|
-
function parseChallenge(paymentRequiredHeader, expectedOrigin) {
|
|
2576
|
-
const decoded = decodeHeader(paymentRequiredHeader);
|
|
2577
|
-
const extensions = asRecord(decoded.extensions);
|
|
2578
|
-
const extension = extensions ? asRecord(extensions[SIWX_EXTENSION]) : null;
|
|
2579
|
-
if (!extension) {
|
|
2580
|
-
throw new SiwxUnsupportedError(
|
|
2581
|
-
"the 402 carries no sign-in-with-x extension, so this endpoint is paid rather than identity-only"
|
|
2582
|
-
);
|
|
2583
|
-
}
|
|
2584
|
-
const info = asRecord(extension.info);
|
|
2585
|
-
if (!info) {
|
|
2586
|
-
throw new SiwxChallengeError("the sign-in-with-x extension has no info block");
|
|
2587
|
-
}
|
|
2588
|
-
const chain = solanaChain(extension.supportedChains);
|
|
2589
|
-
const challenge = {
|
|
2590
|
-
domain: requireString(info, "domain"),
|
|
2591
|
-
uri: requireString(info, "uri"),
|
|
2592
|
-
version: requireString(info, "version"),
|
|
2593
|
-
statement: requireString(info, "statement"),
|
|
2594
|
-
nonce: requireString(info, "nonce"),
|
|
2595
|
-
issuedAt: requireString(info, "issuedAt"),
|
|
2596
|
-
resources: requireResources(info),
|
|
2597
|
-
chainId: chain.chainId,
|
|
2598
|
-
signingType: chain.signingType
|
|
2599
|
-
};
|
|
2600
|
-
assertScopedTo(challenge, expectedOrigin);
|
|
2601
|
-
return challenge;
|
|
2302
|
+
function createNotionFetchTool(getAccessToken, requestNotionAccess) {
|
|
2303
|
+
return notionFetchTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2602
2304
|
}
|
|
2603
|
-
function
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2305
|
+
function notionCreatePagesTool(run) {
|
|
2306
|
+
return withNotionExecutor(run, "Error creating Notion page", {
|
|
2307
|
+
type: "function",
|
|
2308
|
+
function: {
|
|
2309
|
+
name: "notion-create-pages",
|
|
2310
|
+
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.",
|
|
2311
|
+
arguments: {
|
|
2312
|
+
type: "object",
|
|
2313
|
+
properties: {
|
|
2314
|
+
pages: {
|
|
2315
|
+
type: "array",
|
|
2316
|
+
description: "Array of page objects to create (max 100)",
|
|
2317
|
+
items: {
|
|
2318
|
+
type: "object",
|
|
2319
|
+
properties: {
|
|
2320
|
+
properties: {
|
|
2321
|
+
type: "object",
|
|
2322
|
+
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."
|
|
2323
|
+
},
|
|
2324
|
+
content: {
|
|
2325
|
+
type: "string",
|
|
2326
|
+
description: "Page content in Notion-flavored Markdown format"
|
|
2327
|
+
}
|
|
2328
|
+
},
|
|
2329
|
+
required: ["properties", "content"]
|
|
2330
|
+
}
|
|
2331
|
+
},
|
|
2332
|
+
parent: {
|
|
2333
|
+
type: "object",
|
|
2334
|
+
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."
|
|
2335
|
+
}
|
|
2336
|
+
},
|
|
2337
|
+
required: ["pages"]
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2616
2341
|
}
|
|
2617
|
-
function
|
|
2618
|
-
|
|
2619
|
-
return new URL(value);
|
|
2620
|
-
} catch (cause) {
|
|
2621
|
-
throw new SiwxChallengeError(`${description} is not a URL`, { cause });
|
|
2622
|
-
}
|
|
2342
|
+
function createNotionCreatePagesTool(getAccessToken, requestNotionAccess) {
|
|
2343
|
+
return notionCreatePagesTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2623
2344
|
}
|
|
2624
|
-
function
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2345
|
+
function notionUpdatePageTool(run) {
|
|
2346
|
+
return withNotionExecutor(run, "Error updating Notion page", {
|
|
2347
|
+
type: "function",
|
|
2348
|
+
function: {
|
|
2349
|
+
name: "notion-update-page",
|
|
2350
|
+
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.",
|
|
2351
|
+
arguments: {
|
|
2352
|
+
type: "object",
|
|
2353
|
+
properties: {
|
|
2354
|
+
data: {
|
|
2355
|
+
type: "object",
|
|
2356
|
+
description: "Update data containing page_id and a command",
|
|
2357
|
+
properties: {
|
|
2358
|
+
page_id: {
|
|
2359
|
+
type: "string",
|
|
2360
|
+
description: "The ID of the page to update (with or without dashes)"
|
|
2361
|
+
},
|
|
2362
|
+
command: {
|
|
2363
|
+
type: "string",
|
|
2364
|
+
enum: [
|
|
2365
|
+
"update_properties",
|
|
2366
|
+
"replace_content",
|
|
2367
|
+
"replace_content_range",
|
|
2368
|
+
"insert_content_after"
|
|
2369
|
+
],
|
|
2370
|
+
description: "The update command to execute"
|
|
2371
|
+
},
|
|
2372
|
+
properties: {
|
|
2373
|
+
type: "object",
|
|
2374
|
+
description: "For update_properties: JSON map of property names to values. Use null to remove a value."
|
|
2375
|
+
},
|
|
2376
|
+
new_str: {
|
|
2377
|
+
type: "string",
|
|
2378
|
+
description: "For replace_content/replace_content_range/insert_content_after: the new content string"
|
|
2379
|
+
},
|
|
2380
|
+
selection_with_ellipsis: {
|
|
2381
|
+
type: "string",
|
|
2382
|
+
description: "For replace_content_range/insert_content_after: unique start and end snippet (~10 chars each with ellipsis)"
|
|
2383
|
+
},
|
|
2384
|
+
allow_deleting_content: {
|
|
2385
|
+
type: "boolean",
|
|
2386
|
+
description: "For replace_content/replace_content_range: allow deletion of child pages/databases"
|
|
2387
|
+
}
|
|
2388
|
+
},
|
|
2389
|
+
required: ["page_id", "command"]
|
|
2390
|
+
}
|
|
2391
|
+
},
|
|
2392
|
+
required: ["data"]
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
});
|
|
2644
2396
|
}
|
|
2645
|
-
function
|
|
2646
|
-
|
|
2647
|
-
throw new SiwxChallengeError(
|
|
2648
|
-
`expected a ${ED25519_SIGNATURE_BYTES}-byte ed25519 signature, got ${signature.length} bytes`
|
|
2649
|
-
);
|
|
2650
|
-
}
|
|
2651
|
-
const payload = {
|
|
2652
|
-
domain: challenge.domain,
|
|
2653
|
-
address,
|
|
2654
|
-
statement: challenge.statement,
|
|
2655
|
-
uri: challenge.uri,
|
|
2656
|
-
version: challenge.version,
|
|
2657
|
-
// Full CAIP-2 here, unlike the bare reference the message carries.
|
|
2658
|
-
chainId: challenge.chainId,
|
|
2659
|
-
type: challenge.signingType,
|
|
2660
|
-
nonce: challenge.nonce,
|
|
2661
|
-
issuedAt: challenge.issuedAt,
|
|
2662
|
-
resources: challenge.resources,
|
|
2663
|
-
signature: base58Encode(signature)
|
|
2664
|
-
};
|
|
2665
|
-
return uint8ArrayToBase64(new TextEncoder().encode(JSON.stringify(payload)));
|
|
2397
|
+
function createNotionUpdatePageTool(getAccessToken, requestNotionAccess) {
|
|
2398
|
+
return notionUpdatePageTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2666
2399
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2400
|
+
function notionMovePagesTool(run) {
|
|
2401
|
+
return withNotionExecutor(run, "Error moving Notion pages", {
|
|
2402
|
+
type: "function",
|
|
2403
|
+
function: {
|
|
2404
|
+
name: "notion-move-pages",
|
|
2405
|
+
description: "Move one or more Notion pages or databases to a new parent.",
|
|
2406
|
+
arguments: {
|
|
2407
|
+
type: "object",
|
|
2408
|
+
properties: {
|
|
2409
|
+
page_or_database_ids: {
|
|
2410
|
+
type: "array",
|
|
2411
|
+
description: "Array of page or database IDs to move (UUIDs, max 100)",
|
|
2412
|
+
items: { type: "string" }
|
|
2413
|
+
},
|
|
2414
|
+
new_parent: {
|
|
2415
|
+
type: "object",
|
|
2416
|
+
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."
|
|
2417
|
+
}
|
|
2418
|
+
},
|
|
2419
|
+
required: ["page_or_database_ids", "new_parent"]
|
|
2420
|
+
}
|
|
2680
2421
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
function createNotionMovePagesTool(getAccessToken, requestNotionAccess) {
|
|
2425
|
+
return notionMovePagesTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2426
|
+
}
|
|
2427
|
+
function notionDuplicatePageTool(run) {
|
|
2428
|
+
return withNotionExecutor(run, "Error duplicating Notion page", {
|
|
2429
|
+
type: "function",
|
|
2430
|
+
function: {
|
|
2431
|
+
name: "notion-duplicate-page",
|
|
2432
|
+
description: "Duplicate a Notion page. The duplication completes asynchronously.",
|
|
2433
|
+
arguments: {
|
|
2434
|
+
type: "object",
|
|
2435
|
+
properties: {
|
|
2436
|
+
page_id: {
|
|
2437
|
+
type: "string",
|
|
2438
|
+
description: "The ID of the page to duplicate (UUID with or without dashes)"
|
|
2439
|
+
}
|
|
2440
|
+
},
|
|
2441
|
+
required: ["page_id"]
|
|
2442
|
+
}
|
|
2684
2443
|
}
|
|
2685
|
-
}
|
|
2686
|
-
let encoded = "1".repeat(zeros);
|
|
2687
|
-
for (let i = digits.length - 1; i >= 0; i--) {
|
|
2688
|
-
encoded += BASE58_ALPHABET[digits[i]];
|
|
2689
|
-
}
|
|
2690
|
-
return encoded;
|
|
2444
|
+
});
|
|
2691
2445
|
}
|
|
2692
|
-
function
|
|
2693
|
-
|
|
2694
|
-
try {
|
|
2695
|
-
parsed = JSON.parse(new TextDecoder().decode(base64ToUint8Array(paymentRequiredHeader)));
|
|
2696
|
-
} catch (cause) {
|
|
2697
|
-
throw new SiwxChallengeError("the PAYMENT-REQUIRED header is not base64-encoded JSON", {
|
|
2698
|
-
cause
|
|
2699
|
-
});
|
|
2700
|
-
}
|
|
2701
|
-
const decoded = asRecord(parsed);
|
|
2702
|
-
if (!decoded) {
|
|
2703
|
-
throw new SiwxChallengeError("the PAYMENT-REQUIRED header did not decode to an object");
|
|
2704
|
-
}
|
|
2705
|
-
return decoded;
|
|
2446
|
+
function createNotionDuplicatePageTool(getAccessToken, requestNotionAccess) {
|
|
2447
|
+
return notionDuplicatePageTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2706
2448
|
}
|
|
2707
|
-
function
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2449
|
+
function notionCreateDatabaseTool(run) {
|
|
2450
|
+
return withNotionExecutor(run, "Error creating Notion database", {
|
|
2451
|
+
type: "function",
|
|
2452
|
+
function: {
|
|
2453
|
+
name: "notion-create-database",
|
|
2454
|
+
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.",
|
|
2455
|
+
arguments: {
|
|
2456
|
+
type: "object",
|
|
2457
|
+
properties: {
|
|
2458
|
+
properties: {
|
|
2459
|
+
type: "object",
|
|
2460
|
+
description: "Property schema for the database. Each key is a property name, value defines the type."
|
|
2461
|
+
},
|
|
2462
|
+
parent: {
|
|
2463
|
+
type: "object",
|
|
2464
|
+
description: "Parent page: {type:'page_id', page_id:'...'}. Omit for private workspace-level database."
|
|
2465
|
+
},
|
|
2466
|
+
title: {
|
|
2467
|
+
type: "array",
|
|
2468
|
+
description: "Title of the database as rich text array (max 100)",
|
|
2469
|
+
items: { type: "object" }
|
|
2470
|
+
},
|
|
2471
|
+
description: {
|
|
2472
|
+
type: "array",
|
|
2473
|
+
description: "Description of the database as rich text array (max 100)",
|
|
2474
|
+
items: { type: "object" }
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
2477
|
+
required: ["properties"]
|
|
2478
|
+
}
|
|
2720
2479
|
}
|
|
2721
|
-
|
|
2722
|
-
}
|
|
2723
|
-
throw new SiwxChallengeError("the sign-in-with-x extension offers no solana: chain");
|
|
2480
|
+
});
|
|
2724
2481
|
}
|
|
2725
|
-
function
|
|
2726
|
-
return
|
|
2482
|
+
function createNotionCreateDatabaseTool(getAccessToken, requestNotionAccess) {
|
|
2483
|
+
return notionCreateDatabaseTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2727
2484
|
}
|
|
2728
|
-
function
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2485
|
+
function notionUpdateDataSourceTool(run) {
|
|
2486
|
+
return withNotionExecutor(run, "Error updating Notion data source", {
|
|
2487
|
+
type: "function",
|
|
2488
|
+
function: {
|
|
2489
|
+
name: "notion-update-data-source",
|
|
2490
|
+
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.",
|
|
2491
|
+
arguments: {
|
|
2492
|
+
type: "object",
|
|
2493
|
+
properties: {
|
|
2494
|
+
data_source_id: {
|
|
2495
|
+
type: "string",
|
|
2496
|
+
description: "The ID of the data source to update (UUID). Can be a data source ID or database ID."
|
|
2497
|
+
},
|
|
2498
|
+
title: {
|
|
2499
|
+
type: "array",
|
|
2500
|
+
description: "New title as rich text array (max 100)",
|
|
2501
|
+
items: { type: "object" }
|
|
2502
|
+
},
|
|
2503
|
+
description: {
|
|
2504
|
+
type: "array",
|
|
2505
|
+
description: "New description as rich text array (max 100)",
|
|
2506
|
+
items: { type: "object" }
|
|
2507
|
+
},
|
|
2508
|
+
properties: {
|
|
2509
|
+
type: "object",
|
|
2510
|
+
description: "Property schema updates. Use null to remove, {name:'...'} to rename, or full definition to add/update."
|
|
2511
|
+
},
|
|
2512
|
+
is_inline: {
|
|
2513
|
+
type: "boolean",
|
|
2514
|
+
description: "Whether the database is inline"
|
|
2515
|
+
},
|
|
2516
|
+
in_trash: {
|
|
2517
|
+
type: "boolean",
|
|
2518
|
+
description: "Trash the data source (requires Notion UI to undo)"
|
|
2519
|
+
}
|
|
2520
|
+
},
|
|
2521
|
+
required: ["data_source_id"]
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
});
|
|
2734
2525
|
}
|
|
2735
|
-
function
|
|
2736
|
-
|
|
2737
|
-
if (!Array.isArray(resources) || resources.length === 0) {
|
|
2738
|
-
throw new SiwxChallengeError("the sign-in-with-x info block has no resources");
|
|
2739
|
-
}
|
|
2740
|
-
if (!resources.every((resource) => typeof resource === "string" && resource !== "")) {
|
|
2741
|
-
throw new SiwxChallengeError("the sign-in-with-x resources are not all strings");
|
|
2742
|
-
}
|
|
2743
|
-
return resources;
|
|
2526
|
+
function createNotionUpdateDataSourceTool(getAccessToken, requestNotionAccess) {
|
|
2527
|
+
return notionUpdateDataSourceTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2744
2528
|
}
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
function
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2529
|
+
function notionCreateCommentTool(run) {
|
|
2530
|
+
return withNotionExecutor(run, "Error creating Notion comment", {
|
|
2531
|
+
type: "function",
|
|
2532
|
+
function: {
|
|
2533
|
+
name: "notion-create-comment",
|
|
2534
|
+
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).",
|
|
2535
|
+
arguments: {
|
|
2536
|
+
type: "object",
|
|
2537
|
+
properties: {
|
|
2538
|
+
page_id: {
|
|
2539
|
+
type: "string",
|
|
2540
|
+
description: "The ID of the page to comment on (with or without dashes)"
|
|
2541
|
+
},
|
|
2542
|
+
rich_text: {
|
|
2543
|
+
type: "array",
|
|
2544
|
+
description: "Comment content as rich text array (max 100). Objects can be text, mention, or equation.",
|
|
2545
|
+
items: {
|
|
2546
|
+
type: "object",
|
|
2547
|
+
properties: {
|
|
2548
|
+
text: {
|
|
2549
|
+
type: "object",
|
|
2550
|
+
properties: {
|
|
2551
|
+
content: { type: "string" }
|
|
2552
|
+
},
|
|
2553
|
+
required: ["content"]
|
|
2554
|
+
}
|
|
2555
|
+
},
|
|
2556
|
+
required: ["text"]
|
|
2557
|
+
}
|
|
2558
|
+
},
|
|
2559
|
+
discussion_id: {
|
|
2560
|
+
type: "string",
|
|
2561
|
+
description: "ID or URL of an existing discussion to reply to"
|
|
2562
|
+
},
|
|
2563
|
+
selection_with_ellipsis: {
|
|
2564
|
+
type: "string",
|
|
2565
|
+
description: "Unique start and end snippet (~10 chars each with ellipsis) of content to comment on"
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
required: ["page_id", "rich_text"]
|
|
2569
|
+
}
|
|
2758
2570
|
}
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
function createNotionCreateCommentTool(getAccessToken, requestNotionAccess) {
|
|
2574
|
+
return notionCreateCommentTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2575
|
+
}
|
|
2576
|
+
function notionGetCommentsTool(run) {
|
|
2577
|
+
return withNotionExecutor(run, "Error retrieving Notion comments", {
|
|
2578
|
+
type: "function",
|
|
2579
|
+
function: {
|
|
2580
|
+
name: "notion-get-comments",
|
|
2581
|
+
description: "Get comments and discussions from a Notion page. Returns discussions with full comment content.",
|
|
2582
|
+
arguments: {
|
|
2583
|
+
type: "object",
|
|
2584
|
+
properties: {
|
|
2585
|
+
page_id: {
|
|
2586
|
+
type: "string",
|
|
2587
|
+
description: "Identifier for the Notion page"
|
|
2588
|
+
},
|
|
2589
|
+
include_all_blocks: {
|
|
2590
|
+
type: "boolean",
|
|
2591
|
+
description: "Include discussions on child blocks (default: false)"
|
|
2592
|
+
},
|
|
2593
|
+
include_resolved: {
|
|
2594
|
+
type: "boolean",
|
|
2595
|
+
description: "Include resolved discussions (default: false)"
|
|
2596
|
+
},
|
|
2597
|
+
discussion_id: {
|
|
2598
|
+
type: "string",
|
|
2599
|
+
description: "Fetch a specific discussion by ID or URL (e.g., discussion://pageId/blockId/discussionId)"
|
|
2600
|
+
}
|
|
2601
|
+
},
|
|
2602
|
+
required: ["page_id"]
|
|
2603
|
+
}
|
|
2766
2604
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2607
|
+
function createNotionGetCommentsTool(getAccessToken, requestNotionAccess) {
|
|
2608
|
+
return notionGetCommentsTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2609
|
+
}
|
|
2610
|
+
function notionGetUsersTool(run) {
|
|
2611
|
+
return withNotionExecutor(run, "Error listing Notion users", {
|
|
2612
|
+
type: "function",
|
|
2613
|
+
function: {
|
|
2614
|
+
name: "notion-get-users",
|
|
2615
|
+
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').",
|
|
2616
|
+
arguments: {
|
|
2617
|
+
type: "object",
|
|
2618
|
+
properties: {
|
|
2619
|
+
query: {
|
|
2620
|
+
type: "string",
|
|
2621
|
+
description: "Search query to filter users by name or email (case-insensitive, max 100 chars)"
|
|
2622
|
+
},
|
|
2623
|
+
start_cursor: {
|
|
2624
|
+
type: "string",
|
|
2625
|
+
description: "Cursor for pagination from previous response"
|
|
2626
|
+
},
|
|
2627
|
+
page_size: {
|
|
2628
|
+
type: "integer",
|
|
2629
|
+
description: "Number of users per page (1-100, default 100)"
|
|
2630
|
+
},
|
|
2631
|
+
user_id: {
|
|
2632
|
+
type: "string",
|
|
2633
|
+
description: "Return only the user matching this ID. Pass 'self' to fetch the current bot user."
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
function createNotionGetUsersTool(getAccessToken, requestNotionAccess) {
|
|
2641
|
+
return notionGetUsersTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2642
|
+
}
|
|
2643
|
+
function notionGetTeamsTool(run) {
|
|
2644
|
+
return withNotionExecutor(run, "Error retrieving Notion teams", {
|
|
2645
|
+
type: "function",
|
|
2646
|
+
function: {
|
|
2647
|
+
name: "notion-get-teams",
|
|
2648
|
+
description: "Retrieve teams (teamspaces) in the Notion workspace. Shows team names, IDs, membership status, and roles.",
|
|
2649
|
+
arguments: {
|
|
2650
|
+
type: "object",
|
|
2651
|
+
properties: {
|
|
2652
|
+
query: {
|
|
2653
|
+
type: "string",
|
|
2654
|
+
description: "Search query to filter teams by name (case-insensitive, max 100 chars)"
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2805
2658
|
}
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
function createNotionGetTeamsTool(getAccessToken, requestNotionAccess) {
|
|
2662
|
+
return notionGetTeamsTool(directRunner(getAccessToken, requestNotionAccess));
|
|
2663
|
+
}
|
|
2664
|
+
var NOTION_TOOLS = [
|
|
2665
|
+
// Search
|
|
2666
|
+
notionSearchTool,
|
|
2667
|
+
// Pages
|
|
2668
|
+
notionFetchTool,
|
|
2669
|
+
notionCreatePagesTool,
|
|
2670
|
+
notionUpdatePageTool,
|
|
2671
|
+
notionMovePagesTool,
|
|
2672
|
+
notionDuplicatePageTool,
|
|
2673
|
+
// Data Sources (Databases)
|
|
2674
|
+
notionCreateDatabaseTool,
|
|
2675
|
+
notionUpdateDataSourceTool,
|
|
2676
|
+
// Comments
|
|
2677
|
+
notionCreateCommentTool,
|
|
2678
|
+
notionGetCommentsTool,
|
|
2679
|
+
// Users & Teams
|
|
2680
|
+
notionGetUsersTool,
|
|
2681
|
+
notionGetTeamsTool
|
|
2682
|
+
];
|
|
2683
|
+
function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
2684
|
+
const run = directRunner(getAccessToken, requestNotionAccess);
|
|
2685
|
+
return NOTION_TOOLS.map((tool) => tool(run));
|
|
2686
|
+
}
|
|
2687
|
+
function createNotionProxyTools(callMcp) {
|
|
2688
|
+
const run = proxyRunner(callMcp);
|
|
2689
|
+
return NOTION_TOOLS.map((tool) => tool(run));
|
|
2690
|
+
}
|
|
2691
|
+
function getMCPEndpoints() {
|
|
2692
|
+
return {
|
|
2693
|
+
http: MCP_HTTP_ENDPOINT,
|
|
2694
|
+
sse: MCP_SSE_ENDPOINT
|
|
2806
2695
|
};
|
|
2807
2696
|
}
|
|
2808
|
-
function
|
|
2809
|
-
|
|
2810
|
-
throw new AgentresPathError(
|
|
2811
|
-
`"${path}" is not a path on ${baseUrl} \u2014 it must start with a single "/"`
|
|
2812
|
-
);
|
|
2813
|
-
}
|
|
2814
|
-
const base = new URL(baseUrl);
|
|
2815
|
-
const resolved = new URL(path, base);
|
|
2816
|
-
if (resolved.origin !== base.origin) {
|
|
2817
|
-
throw new AgentresPathError(
|
|
2818
|
-
`"${path}" resolves to ${resolved.origin} but this client talks to ${base.origin} \u2014 refusing to send it`
|
|
2819
|
-
);
|
|
2820
|
-
}
|
|
2821
|
-
return resolved.toString();
|
|
2697
|
+
async function callNotionMCPTool(accessToken, toolName, args) {
|
|
2698
|
+
return callMCPTool(accessToken, toolName, args);
|
|
2822
2699
|
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2700
|
+
|
|
2701
|
+
// src/tools/github.ts
|
|
2702
|
+
var GITHUB_API = "https://api.github.com";
|
|
2703
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
2704
|
+
async function resolveToken(getAccessToken, requestGitHubAccess) {
|
|
2705
|
+
let token = getAccessToken();
|
|
2706
|
+
if (!token) {
|
|
2707
|
+
try {
|
|
2708
|
+
token = await requestGitHubAccess();
|
|
2709
|
+
} catch (err) {
|
|
2710
|
+
getLogger().error("Failed to resolve GitHub token", err);
|
|
2711
|
+
return null;
|
|
2712
|
+
}
|
|
2827
2713
|
}
|
|
2828
|
-
|
|
2829
|
-
return { method: request.method, headers, body: JSON.stringify(request.body) };
|
|
2714
|
+
return token;
|
|
2830
2715
|
}
|
|
2831
|
-
async function
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2716
|
+
async function githubFetch(token, path, options = {}) {
|
|
2717
|
+
if (!path.startsWith("http") && !path.startsWith("/")) {
|
|
2718
|
+
throw new Error("githubFetch: relative path must start with /");
|
|
2719
|
+
}
|
|
2720
|
+
const url = path.startsWith("http") ? path : `${GITHUB_API}${path}`;
|
|
2721
|
+
if (!url.startsWith(`${GITHUB_API}/`)) {
|
|
2722
|
+
throw new Error("githubFetch: URL must target GitHub API");
|
|
2835
2723
|
}
|
|
2724
|
+
const controller = new AbortController();
|
|
2725
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
2836
2726
|
try {
|
|
2837
|
-
return
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2727
|
+
return await fetch(url, {
|
|
2728
|
+
...options,
|
|
2729
|
+
signal: controller.signal,
|
|
2730
|
+
headers: {
|
|
2731
|
+
Authorization: `Bearer ${token}`,
|
|
2732
|
+
Accept: "application/vnd.github+json",
|
|
2733
|
+
"Content-Type": "application/json",
|
|
2734
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
2735
|
+
...options.headers
|
|
2736
|
+
}
|
|
2737
|
+
});
|
|
2738
|
+
} finally {
|
|
2739
|
+
clearTimeout(timeout);
|
|
2845
2740
|
}
|
|
2846
2741
|
}
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
function
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2742
|
+
function createGetAuthenticatedUserTool(getAccessToken, requestAccess) {
|
|
2743
|
+
return {
|
|
2744
|
+
type: "function",
|
|
2745
|
+
function: {
|
|
2746
|
+
name: "github_get_authenticated_user",
|
|
2747
|
+
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.",
|
|
2748
|
+
parameters: {
|
|
2749
|
+
type: "object",
|
|
2750
|
+
properties: {}
|
|
2751
|
+
}
|
|
2752
|
+
},
|
|
2753
|
+
executor: async () => {
|
|
2754
|
+
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2755
|
+
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2756
|
+
try {
|
|
2757
|
+
const userResp = await githubFetch(token, "/user");
|
|
2758
|
+
if (!userResp.ok) return `Error: Failed to get user (${userResp.status})`;
|
|
2759
|
+
const user = await userResp.json();
|
|
2760
|
+
const orgsResp = await githubFetch(token, "/user/orgs?per_page=100");
|
|
2761
|
+
let orgs = [];
|
|
2762
|
+
if (orgsResp.ok) {
|
|
2763
|
+
const orgsData = await orgsResp.json();
|
|
2764
|
+
orgs = orgsData.map((o) => o.login);
|
|
2765
|
+
}
|
|
2766
|
+
return {
|
|
2767
|
+
login: user.login,
|
|
2768
|
+
name: user.name,
|
|
2769
|
+
email: user.email,
|
|
2770
|
+
organizations: orgs,
|
|
2771
|
+
public_repos: user.public_repos,
|
|
2772
|
+
html_url: user.html_url
|
|
2773
|
+
};
|
|
2774
|
+
} catch (err) {
|
|
2775
|
+
getLogger().error("github_get_authenticated_user error", err);
|
|
2776
|
+
return "Error: Failed to get user profile.";
|
|
2777
|
+
}
|
|
2868
2778
|
}
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
var MAX_RESPONSE_SIZE = 1e5;
|
|
2782
|
+
function truncate(text, maxLen) {
|
|
2783
|
+
if (text.length <= maxLen) return text;
|
|
2784
|
+
return text.slice(0, maxLen) + `
|
|
2785
|
+
|
|
2786
|
+
... (truncated, ${text.length - maxLen} characters omitted)`;
|
|
2787
|
+
}
|
|
2788
|
+
function createGitHubApiTool(getAccessToken, requestAccess) {
|
|
2789
|
+
return {
|
|
2790
|
+
type: "function",
|
|
2791
|
+
function: {
|
|
2792
|
+
name: "github_api",
|
|
2793
|
+
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.
|
|
2794
|
+
|
|
2795
|
+
Examples:
|
|
2796
|
+
- List PRs: GET /repos/{owner}/{repo}/pulls?state=open
|
|
2797
|
+
- Get file: GET /repos/{owner}/{repo}/contents/{path}
|
|
2798
|
+
- Create issue: POST /repos/{owner}/{repo}/issues with body {"title": "...", "body": "..."}
|
|
2799
|
+
- Search repos: GET /search/repositories?q={query}
|
|
2800
|
+
|
|
2801
|
+
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.`,
|
|
2802
|
+
parameters: {
|
|
2803
|
+
type: "object",
|
|
2804
|
+
properties: {
|
|
2805
|
+
method: {
|
|
2806
|
+
type: "string",
|
|
2807
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
2808
|
+
description: "HTTP method"
|
|
2809
|
+
},
|
|
2810
|
+
path: {
|
|
2811
|
+
type: "string",
|
|
2812
|
+
description: "API path (e.g., /repos/owner/repo/pulls) or full URL for pagination"
|
|
2813
|
+
},
|
|
2814
|
+
body: {
|
|
2815
|
+
type: "object",
|
|
2816
|
+
description: "Request body for POST/PUT/PATCH requests",
|
|
2817
|
+
additionalProperties: true
|
|
2818
|
+
}
|
|
2819
|
+
},
|
|
2820
|
+
required: ["method", "path"]
|
|
2821
|
+
}
|
|
2822
|
+
},
|
|
2823
|
+
executor: async (args) => {
|
|
2824
|
+
const token = await resolveToken(getAccessToken, requestAccess);
|
|
2825
|
+
if (!token) return "Error: GitHub not connected. Please connect your GitHub account first.";
|
|
2826
|
+
const method = args.method;
|
|
2827
|
+
const path = args.path;
|
|
2828
|
+
const body = args.body;
|
|
2829
|
+
try {
|
|
2830
|
+
const resp = await githubFetch(token, path, {
|
|
2831
|
+
method,
|
|
2832
|
+
body: body ? JSON.stringify(body) : void 0
|
|
2833
|
+
});
|
|
2834
|
+
const text = await resp.text();
|
|
2835
|
+
if (!resp.ok) {
|
|
2836
|
+
return `Error: GitHub API ${method} ${path} returned ${resp.status}: ${truncate(text, 500)}`;
|
|
2837
|
+
}
|
|
2838
|
+
try {
|
|
2839
|
+
const data = JSON.parse(text);
|
|
2840
|
+
const json = JSON.stringify(data);
|
|
2841
|
+
return truncate(json, MAX_RESPONSE_SIZE);
|
|
2842
|
+
} catch {
|
|
2843
|
+
return truncate(text, MAX_RESPONSE_SIZE);
|
|
2844
|
+
}
|
|
2845
|
+
} catch (err) {
|
|
2846
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
2847
|
+
return `Error: GitHub API request timed out after ${FETCH_TIMEOUT_MS / 1e3}s.`;
|
|
2848
|
+
}
|
|
2849
|
+
getLogger().error("github_api error", err);
|
|
2850
|
+
return `Error: GitHub API request failed: ${err instanceof Error ? err.message : "unknown error"}`;
|
|
2851
|
+
}
|
|
2872
2852
|
}
|
|
2873
|
-
return null;
|
|
2874
2853
|
};
|
|
2875
2854
|
}
|
|
2855
|
+
function createGitHubTools(getAccessToken, requestGitHubAccess) {
|
|
2856
|
+
return [
|
|
2857
|
+
createGetAuthenticatedUserTool(getAccessToken, requestGitHubAccess),
|
|
2858
|
+
createGitHubApiTool(getAccessToken, requestGitHubAccess)
|
|
2859
|
+
];
|
|
2860
|
+
}
|
|
2876
2861
|
|
|
2877
2862
|
// src/tools/gmail.ts
|
|
2878
2863
|
var GMAIL_PROVIDER = "gmail";
|
|
@@ -3735,7 +3720,7 @@ async function callSlack(callProxy, path, query, requestBody) {
|
|
|
3735
3720
|
const { status, json } = await callProxy(path, query, requestBody);
|
|
3736
3721
|
return interpretSlackResult(path, status, json);
|
|
3737
3722
|
}
|
|
3738
|
-
function
|
|
3723
|
+
function readString2(raw) {
|
|
3739
3724
|
return typeof raw === "string" ? raw : "";
|
|
3740
3725
|
}
|
|
3741
3726
|
function clampLimit(raw, fallback, min, max) {
|
|
@@ -4297,7 +4282,7 @@ function createSlackSearchMessagesTool(callProxy) {
|
|
|
4297
4282
|
}
|
|
4298
4283
|
},
|
|
4299
4284
|
executor: async (args) => searchSlackMessages(callProxy, {
|
|
4300
|
-
query:
|
|
4285
|
+
query: readString2(args.query),
|
|
4301
4286
|
count: args.count,
|
|
4302
4287
|
channel: typeof args.channel === "string" ? args.channel : void 0,
|
|
4303
4288
|
from_user: typeof args.from_user === "string" ? args.from_user : void 0,
|
|
@@ -4374,7 +4359,7 @@ function createSlackGetChannelHistoryTool(callProxy) {
|
|
|
4374
4359
|
}
|
|
4375
4360
|
},
|
|
4376
4361
|
executor: async (args) => getSlackChannelHistory(callProxy, {
|
|
4377
|
-
channel:
|
|
4362
|
+
channel: readString2(args.channel),
|
|
4378
4363
|
limit: args.limit
|
|
4379
4364
|
})
|
|
4380
4365
|
};
|
|
@@ -4405,8 +4390,8 @@ function createSlackGetThreadRepliesTool(callProxy) {
|
|
|
4405
4390
|
}
|
|
4406
4391
|
},
|
|
4407
4392
|
executor: async (args) => getSlackThreadReplies(callProxy, {
|
|
4408
|
-
channel:
|
|
4409
|
-
ts:
|
|
4393
|
+
channel: readString2(args.channel),
|
|
4394
|
+
ts: readString2(args.ts),
|
|
4410
4395
|
limit: args.limit
|
|
4411
4396
|
})
|
|
4412
4397
|
};
|
|
@@ -4437,8 +4422,8 @@ function createSlackPostMessageTool(callProxy) {
|
|
|
4437
4422
|
}
|
|
4438
4423
|
},
|
|
4439
4424
|
executor: async (args) => postSlackMessage(callProxy, {
|
|
4440
|
-
channel:
|
|
4441
|
-
text:
|
|
4425
|
+
channel: readString2(args.channel),
|
|
4426
|
+
text: readString2(args.text),
|
|
4442
4427
|
thread_ts: typeof args.thread_ts === "string" ? args.thread_ts : void 0
|
|
4443
4428
|
})
|
|
4444
4429
|
};
|
|
@@ -14050,6 +14035,7 @@ export {
|
|
|
14050
14035
|
createNotionGetTeamsTool,
|
|
14051
14036
|
createNotionGetUsersTool,
|
|
14052
14037
|
createNotionMovePagesTool,
|
|
14038
|
+
createNotionProxyTools,
|
|
14053
14039
|
createNotionSearchTool,
|
|
14054
14040
|
createNotionTools,
|
|
14055
14041
|
createNotionUpdateDataSourceTool,
|