Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c7f6c77

Browse files
authored
feat (AI): Add AI integration page with MCP configuration and PAT generation (#7379)
* Add AI tab & MCP configuration section * Add personal access token section * Issue token immediately, not in a dialog * Don't show PAT section for public projects * Copy tweaks * Commit `package-lock.json` * Fix base URL * Inject PAT into the MCP JSON snippet * Remove needless classes * Fix lint * Move Prism CSS import to `CodeBlock` component (used in only one place in our app)
1 parent 546525e commit c7f6c77

File tree

9 files changed

+203
-4
lines changed

9 files changed

+203
-4
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-admin/src/client/http-client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import Axios from "axios";
88
export const CANONICAL_ADMIN_URL =
99
import.meta.env.RILL_UI_PUBLIC_RILL_ADMIN_URL || "http://localhost:8080";
1010

11+
export const CANONICAL_ADMIN_API_URL = `${CANONICAL_ADMIN_URL.replace("https://admin", "https://api")}`;
12+
1113
/**
1214
* The URL of the admin server.
1315
*
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script lang="ts">
2+
import { CANONICAL_ADMIN_API_URL } from "@rilldata/web-admin/client/http-client";
3+
import CodeBlock from "@rilldata/web-common/components/code-block/CodeBlock.svelte";
4+
5+
export let organization: string;
6+
export let project: string;
7+
export let isPublic: boolean;
8+
export let issuedToken: string | null = null;
9+
10+
// Construct the API URL for the MCP server
11+
$: apiUrl = `${CANONICAL_ADMIN_API_URL}/v1/organizations/${organization}/projects/${project}/runtime/mcp/sse`;
12+
13+
// Config snippets with exact formatting
14+
$: publicConfig = `{
15+
"mcpServers": {
16+
"rill": {
17+
"command": "npx",
18+
"args": [
19+
"mcp-remote",
20+
"${apiUrl}"
21+
]
22+
}
23+
}
24+
}`;
25+
26+
$: privateConfig = `{
27+
"mcpServers": {
28+
"rill": {
29+
"command": "npx",
30+
"args": [
31+
"mcp-remote",
32+
"${apiUrl}",
33+
"--header",
34+
"Authorization:\${AUTH_HEADER}"
35+
],
36+
"env": {
37+
"AUTH_HEADER": "Bearer ${issuedToken ? issuedToken : "<Rill personal access token>"}"
38+
}
39+
}
40+
}`;
41+
</script>
42+
43+
<div class="mb-2">
44+
<h2 class="text-xl font-semibold mb-2">Configure your MCP client</h2>
45+
<p class="mb-4 text-gray-600">
46+
Use the below snippet to configure your AI client.
47+
</p>
48+
<CodeBlock code={isPublic ? publicConfig : privateConfig} language="json" />
49+
</div>

web-admin/src/features/navigation/nav-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function isProjectPage(page: Page): boolean {
1818
page.route.id === "/[organization]/[project]" ||
1919
page.route.id === "/[organization]/[project]/-/reports" ||
2020
page.route.id === "/[organization]/[project]/-/alerts" ||
21+
page.route.id === "/[organization]/[project]/-/ai" ||
2122
page.route.id === "/[organization]/[project]/-/status" ||
2223
page.route.id === "/[organization]/[project]/-/settings" ||
2324
page.route.id === "/[organization]/[project]/-/settings/public-urls" ||
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<script lang="ts">
2+
import { createAdminServiceIssueUserAuthToken } from "@rilldata/web-admin/client";
3+
import Button from "@rilldata/web-common/components/button/Button.svelte";
4+
5+
export let issuedToken: string | null = null;
6+
7+
let error: string | null = null;
8+
let issuing = false;
9+
10+
const issueTokenMutation = createAdminServiceIssueUserAuthToken();
11+
const manualClientId = "12345678-0000-0000-0000-000000000005"; // This comes from admin/database/database.go
12+
13+
async function issueToken() {
14+
issuing = true;
15+
error = null;
16+
issuedToken = null;
17+
try {
18+
const resp = await $issueTokenMutation.mutateAsync({
19+
userId: "current",
20+
data: {
21+
displayName: "MCP Token",
22+
clientId: manualClientId,
23+
ttlMinutes: "0",
24+
},
25+
});
26+
issuedToken = resp.token;
27+
} catch (e) {
28+
error = e?.message || "Failed to issue token. Please try again.";
29+
} finally {
30+
issuing = false;
31+
}
32+
}
33+
</script>
34+
35+
<div class="mb-2">
36+
<h2 class="text-xl font-semibold mb-2">Create a Personal Access Token</h2>
37+
<p class="mb-4 text-gray-600">
38+
Because this project is <span class="font-medium">private</span>, you need a
39+
<span class="font-medium">personal access token</span> to use in your MCP configuration.
40+
This token authenticates your requests.
41+
</p>
42+
<Button type="primary" on:click={issueToken} disabled={issuing}>
43+
{issuing ? "Issuing..." : "Create token"}
44+
</Button>
45+
46+
{#if issuedToken}
47+
<div class="mt-4 mb-2 text-green-700 text-sm font-semibold">
48+
Token created! Your new token is now included in the configuration snippet
49+
below.
50+
</div>
51+
{/if}
52+
53+
{#if error}
54+
<div class="text-red-600 mt-2">{error}</div>
55+
{/if}
56+
</div>

web-admin/src/features/projects/ProjectTabs.svelte

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<script lang="ts">
2-
import { type V1ProjectPermissions } from "../../client";
3-
import Tab from "@rilldata/web-admin/components/nav/Tab.svelte";
42
import {
5-
width,
63
position,
4+
width,
75
} from "@rilldata/web-admin//components/nav/Tab.svelte";
6+
import Tab from "@rilldata/web-admin/components/nav/Tab.svelte";
87
import { featureFlags } from "@rilldata/web-common/features/feature-flags";
8+
import { type V1ProjectPermissions } from "../../client";
99
1010
export let projectPermissions: V1ProjectPermissions;
1111
export let organization: string;
@@ -30,6 +30,11 @@
3030
label: "Alerts",
3131
hasPermission: $alerts,
3232
},
33+
{
34+
route: `/${organization}/${project}/-/ai`,
35+
label: "AI",
36+
hasPermission: true,
37+
},
3338
{
3439
route: `/${organization}/${project}/-/status`,
3540
label: "Status",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script lang="ts">
2+
import { page } from "$app/stores";
3+
import { createAdminServiceGetProject } from "@rilldata/web-admin/client";
4+
import ContentContainer from "@rilldata/web-admin/components/layout/ContentContainer.svelte";
5+
import MCPConfigSection from "@rilldata/web-admin/features/ai/MCPConfigSection.svelte";
6+
import PersonalAccessTokensSection from "@rilldata/web-admin/features/personal-access-tokens/PersonalAccessTokensSection.svelte";
7+
8+
$: organization = $page.params.organization;
9+
$: project = $page.params.project;
10+
11+
$: proj = createAdminServiceGetProject(organization, project);
12+
$: ({
13+
project: { public: isPublic },
14+
} = $proj.data);
15+
16+
let issuedToken: string | null = null;
17+
</script>
18+
19+
<ContentContainer maxWidth={1100}>
20+
<div class="flex flex-col gap-y-4 size-full">
21+
<div class="flex flex-col gap-y-2">
22+
<h1 class="text-2xl font-bold mt-4 mb-2">
23+
Integrate Rill with your AI client
24+
</h1>
25+
<p class="mb-2 text-gray-700">
26+
Ask questions of your Rill project using natural language in any AI
27+
client that supports the Model Context Protocol (MCP). <a
28+
href="https://docs.rilldata.com/explore/mcp"
29+
target="_blank"
30+
rel="noopener">Learn more about MCP in the Rill docs</a
31+
>
32+
</p>
33+
</div>
34+
{#if !isPublic}
35+
<PersonalAccessTokensSection bind:issuedToken />
36+
{/if}
37+
<MCPConfigSection {organization} {project} {isPublic} {issuedToken} />
38+
</div>
39+
</ContentContainer>

web-common/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"nearley": "^2.20.1",
8484
"orval": "^7.8.0",
8585
"posthog-js": "^1.188.0",
86+
"prismjs": "^1.30.0",
8687
"regular-table": "^0.5.9",
8788
"storybook": "^7.0.18",
8889
"svelte": "^4.2.19",

web-common/src/components/code-block/CodeBlock.svelte

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,53 @@
11
<script lang="ts">
2+
import Prism from "prismjs";
3+
import "prismjs/components/prism-json";
4+
import "prismjs/themes/prism.css";
5+
import { onMount } from "svelte";
6+
import Button from "../button/Button.svelte";
7+
28
export let code: string;
9+
export let language: string = "json";
10+
export let showCopyButton: boolean = true;
11+
12+
let codeElement: HTMLElement;
13+
let copied = false;
14+
15+
function copyCode() {
16+
navigator.clipboard.writeText(code);
17+
copied = true;
18+
setTimeout(() => (copied = false), 1500);
19+
}
20+
21+
onMount(() => {
22+
if (codeElement) {
23+
Prism.highlightElement(codeElement);
24+
}
25+
});
26+
27+
$: (async () => {
28+
if (codeElement && code !== undefined && language !== undefined) {
29+
Prism.highlightElement(codeElement);
30+
}
31+
})();
332
</script>
433

5-
<pre><code>{code}</code></pre>
34+
<div class="relative">
35+
{#if showCopyButton}
36+
<Button
37+
type="secondary"
38+
on:click={copyCode}
39+
small
40+
class="absolute top-2 right-2"
41+
>
42+
{#if copied}Copied!{:else}Copy{/if}
43+
</Button>
44+
{/if}
45+
{#key code + language}
46+
<pre><code bind:this={codeElement} class={`language-${language}`}
47+
>{code}</code
48+
></pre>
49+
{/key}
50+
</div>
651

752
<style lang="postcss">
853
pre {

0 commit comments

Comments
 (0)