@rebasepro/mcp-server 0.0.1-canary.0
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/LICENSE +6 -0
- package/README.md +69 -0
- package/dist/api-client.d.ts +35 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +118 -0
- package/dist/api-client.js.map +1 -0
- package/dist/auth.d.ts +16 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +53 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +66 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/project.d.ts +7 -0
- package/dist/resources/project.d.ts.map +1 -0
- package/dist/resources/project.js +57 -0
- package/dist/resources/project.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +34 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/auth.d.ts +6 -0
- package/dist/tools/auth.d.ts.map +1 -0
- package/dist/tools/auth.js +89 -0
- package/dist/tools/auth.js.map +1 -0
- package/dist/tools/collections.d.ts +7 -0
- package/dist/tools/collections.d.ts.map +1 -0
- package/dist/tools/collections.js +42 -0
- package/dist/tools/collections.js.map +1 -0
- package/dist/tools/documents.d.ts +8 -0
- package/dist/tools/documents.d.ts.map +1 -0
- package/dist/tools/documents.js +178 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/export.d.ts +7 -0
- package/dist/tools/export.d.ts.map +1 -0
- package/dist/tools/export.js +44 -0
- package/dist/tools/export.js.map +1 -0
- package/dist/tools/projects.d.ts +7 -0
- package/dist/tools/projects.d.ts.map +1 -0
- package/dist/tools/projects.js +42 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/users.d.ts +7 -0
- package/dist/tools/users.d.ts.map +1 -0
- package/dist/tools/users.js +74 -0
- package/dist/tools/users.js.map +1 -0
- package/package.json +48 -0
- package/src/api-client.ts +146 -0
- package/src/auth.ts +75 -0
- package/src/cli.ts +70 -0
- package/src/index.ts +2 -0
- package/src/resources/project.ts +68 -0
- package/src/server.ts +42 -0
- package/src/tools/auth.ts +110 -0
- package/src/tools/collections.ts +52 -0
- package/src/tools/documents.ts +210 -0
- package/src/tools/export.ts +51 -0
- package/src/tools/projects.ts +52 -0
- package/src/tools/users.ts +92 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
|
|
2
|
+
import { getValidTokens } from "./auth.js";
|
|
3
|
+
|
|
4
|
+
const API_URL = "https://api.rebase.pro";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Typed HTTP client for the Rebase Cloud backend REST API.
|
|
8
|
+
* Uses the same tokens as the Rebase CLI (from ~/.rebase/tokens.json).
|
|
9
|
+
*/
|
|
10
|
+
export class RebaseApiClient {
|
|
11
|
+
private client: AxiosInstance;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.client = axios.create({
|
|
15
|
+
baseURL: API_URL,
|
|
16
|
+
timeout: 60_000,
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private async authHeaders(): Promise<Record<string, string>> {
|
|
22
|
+
const tokens = await getValidTokens();
|
|
23
|
+
if (!tokens) {
|
|
24
|
+
throw new Error("Not logged in. Use the rebase_login tool first.");
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
Authorization: `Bearer ${tokens.id_token}`,
|
|
28
|
+
"x-admin-authorization": `Bearer ${tokens.access_token}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private async request<T>(config: AxiosRequestConfig): Promise<T> {
|
|
33
|
+
const headers = await this.authHeaders();
|
|
34
|
+
const response = await this.client.request<T>({
|
|
35
|
+
...config,
|
|
36
|
+
headers: { ...config.headers, ...headers },
|
|
37
|
+
});
|
|
38
|
+
return response.data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── Projects ──────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
async listProjects(): Promise<any> {
|
|
44
|
+
return this.request({ method: "GET", url: "/projects" });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getRootCollections(projectId: string): Promise<any> {
|
|
48
|
+
return this.request({
|
|
49
|
+
method: "GET",
|
|
50
|
+
url: `/projects/${projectId}/firestore_root_collections`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Users ─────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
async listUsers(projectId: string): Promise<any> {
|
|
57
|
+
return this.request({ method: "GET", url: `/projects/${projectId}/users` });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async createUser(projectId: string, email: string, roles: string[]): Promise<any> {
|
|
61
|
+
return this.request({
|
|
62
|
+
method: "POST",
|
|
63
|
+
url: `/projects/${projectId}/users`,
|
|
64
|
+
data: { email, roles },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async updateUser(projectId: string, userId: string, roles: string[]): Promise<any> {
|
|
69
|
+
return this.request({
|
|
70
|
+
method: "PATCH",
|
|
71
|
+
url: `/projects/${projectId}/users/${userId}`,
|
|
72
|
+
data: { roles },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async deleteUser(projectId: string, userId: string): Promise<any> {
|
|
77
|
+
return this.request({ method: "DELETE", url: `/projects/${projectId}/users/${userId}` });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── Collections ───────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
async generateCollection(prompt: string, existingCollections: any[] = [], existingCollection?: any): Promise<any> {
|
|
83
|
+
return this.request({
|
|
84
|
+
method: "POST",
|
|
85
|
+
url: "/collections/generate",
|
|
86
|
+
data: { prompt, existingCollections, existingCollection },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Documents (Firestore CRUD via backend) ─────────────
|
|
91
|
+
|
|
92
|
+
async listDocuments(projectId: string, body: {
|
|
93
|
+
path: string;
|
|
94
|
+
limit?: number;
|
|
95
|
+
orderBy?: string;
|
|
96
|
+
orderDirection?: string;
|
|
97
|
+
filters?: Array<{ field: string; op: string; value: any }>;
|
|
98
|
+
databaseId?: string;
|
|
99
|
+
}): Promise<any> {
|
|
100
|
+
return this.request({
|
|
101
|
+
method: "POST",
|
|
102
|
+
url: `/projects/${projectId}/documents/list`,
|
|
103
|
+
data: body,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async getDocument(projectId: string, path: string, documentId: string, databaseId?: string): Promise<any> {
|
|
108
|
+
return this.request({
|
|
109
|
+
method: "POST",
|
|
110
|
+
url: `/projects/${projectId}/documents/get`,
|
|
111
|
+
data: { path, documentId, databaseId },
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async createDocument(projectId: string, path: string, data: Record<string, any>, documentId?: string, databaseId?: string): Promise<any> {
|
|
116
|
+
return this.request({
|
|
117
|
+
method: "POST",
|
|
118
|
+
url: `/projects/${projectId}/documents/create`,
|
|
119
|
+
data: { path, data, documentId, databaseId },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async updateDocument(projectId: string, path: string, documentId: string, data: Record<string, any>, databaseId?: string): Promise<any> {
|
|
124
|
+
return this.request({
|
|
125
|
+
method: "POST",
|
|
126
|
+
url: `/projects/${projectId}/documents/update`,
|
|
127
|
+
data: { path, documentId, data, databaseId },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async deleteDocument(projectId: string, path: string, documentId: string, databaseId?: string): Promise<any> {
|
|
132
|
+
return this.request({
|
|
133
|
+
method: "POST",
|
|
134
|
+
url: `/projects/${projectId}/documents/delete`,
|
|
135
|
+
data: { path, documentId, databaseId },
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async countDocuments(projectId: string, path: string, databaseId?: string): Promise<any> {
|
|
140
|
+
return this.request({
|
|
141
|
+
method: "POST",
|
|
142
|
+
url: `/projects/${projectId}/documents/count`,
|
|
143
|
+
data: { path, databaseId },
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication module for the Rebase MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Delegates to the CLI package (@rebasepro/cli) — same OAuth flow,
|
|
5
|
+
* same token storage at ~/.rebase/tokens.json.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
login,
|
|
9
|
+
logout,
|
|
10
|
+
getTokens,
|
|
11
|
+
refreshCredentials,
|
|
12
|
+
parseJwt,
|
|
13
|
+
} from "@rebasepro/cli";
|
|
14
|
+
|
|
15
|
+
const ENV = "prod" as const;
|
|
16
|
+
const DEBUG = false;
|
|
17
|
+
|
|
18
|
+
export interface StoredTokens {
|
|
19
|
+
access_token: string;
|
|
20
|
+
refresh_token: string;
|
|
21
|
+
id_token: string;
|
|
22
|
+
expiry_date: number;
|
|
23
|
+
scope: string;
|
|
24
|
+
token_type: string;
|
|
25
|
+
env?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─── Token access ──────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
export async function getStoredTokens(): Promise<StoredTokens | null> {
|
|
31
|
+
const tokens = await getTokens(ENV, DEBUG);
|
|
32
|
+
return tokens as StoredTokens | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function getValidTokens(): Promise<StoredTokens | null> {
|
|
36
|
+
const tokens = await getStoredTokens();
|
|
37
|
+
if (!tokens) return null;
|
|
38
|
+
|
|
39
|
+
const refreshed = await refreshCredentials(ENV, tokens);
|
|
40
|
+
return refreshed as StoredTokens | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Convenience helpers ───────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export function getCurrentUserEmail(): string | null {
|
|
46
|
+
// getTokens is async, but for a quick sync check we read the file directly.
|
|
47
|
+
// The CLI stores tokens at ~/.rebase/tokens.json.
|
|
48
|
+
try {
|
|
49
|
+
const fs = require("fs");
|
|
50
|
+
const path = require("path");
|
|
51
|
+
const os = require("os");
|
|
52
|
+
const filePath = path.join(os.homedir(), ".rebase", "tokens.json");
|
|
53
|
+
if (!fs.existsSync(filePath)) return null;
|
|
54
|
+
const tokens = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
55
|
+
if (!tokens?.id_token) return null;
|
|
56
|
+
const payload = parseJwt(tokens.id_token);
|
|
57
|
+
return (payload as any)["email"] ?? null;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isLoggedIn(): boolean {
|
|
64
|
+
return getCurrentUserEmail() !== null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Login / Logout ────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export async function loginFlow(): Promise<void> {
|
|
70
|
+
await login(ENV, DEBUG);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function logoutFlow(): Promise<void> {
|
|
74
|
+
await logout(ENV, DEBUG);
|
|
75
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createRebaseMcpServer } from "./server.js";
|
|
5
|
+
import { getCurrentUserEmail } from "./auth.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Rebase MCP Server CLI
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* rebase-mcp
|
|
12
|
+
*
|
|
13
|
+
* Auth is handled interactively via the rebase_login tool (browser OAuth),
|
|
14
|
+
* same flow as `rebase login` in the CLI. Tokens are stored at ~/.rebase/tokens.json.
|
|
15
|
+
*
|
|
16
|
+
* Claude Desktop config (claude_desktop_config.json):
|
|
17
|
+
* {
|
|
18
|
+
* "mcpServers": {
|
|
19
|
+
* "rebase": {
|
|
20
|
+
* "command": "node",
|
|
21
|
+
* "args": ["/path/to/packages/mcp_server/dist/cli.js"]
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
*/
|
|
26
|
+
async function main() {
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
|
|
29
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
30
|
+
console.log(`
|
|
31
|
+
Rebase MCP Server — Model Context Protocol server for Rebase Cloud
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
rebase-mcp
|
|
35
|
+
|
|
36
|
+
No configuration needed. Authentication is handled interactively
|
|
37
|
+
via the rebase_login tool, which opens a browser for Google OAuth.
|
|
38
|
+
Tokens are stored at ~/.rebase/tokens.json (shared with the Rebase CLI).
|
|
39
|
+
|
|
40
|
+
Claude Desktop config (claude_desktop_config.json):
|
|
41
|
+
{
|
|
42
|
+
"mcpServers": {
|
|
43
|
+
"rebase": {
|
|
44
|
+
"command": "npx",
|
|
45
|
+
"args": ["@rebasepro/mcp-server"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
`);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const server = createRebaseMcpServer();
|
|
54
|
+
const transport = new StdioServerTransport();
|
|
55
|
+
await server.connect(transport);
|
|
56
|
+
|
|
57
|
+
// Log to stderr (stdout is reserved for MCP protocol)
|
|
58
|
+
const email = getCurrentUserEmail();
|
|
59
|
+
console.error("Rebase MCP server started");
|
|
60
|
+
if (email) {
|
|
61
|
+
console.error(` Logged in as: ${email}`);
|
|
62
|
+
} else {
|
|
63
|
+
console.error(" Not logged in — use the rebase_login tool to sign in");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main().catch((error) => {
|
|
68
|
+
console.error("Fatal error:", error);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { RebaseApiClient } from "../api-client.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register MCP resources — read-only contextual data about Rebase projects.
|
|
6
|
+
*/
|
|
7
|
+
export function registerProjectResources(server: McpServer, api: RebaseApiClient) {
|
|
8
|
+
|
|
9
|
+
server.registerResource(
|
|
10
|
+
"project-collections",
|
|
11
|
+
new ResourceTemplate("rebase://projects/{projectId}/collections", { list: undefined }),
|
|
12
|
+
{
|
|
13
|
+
description: "Firestore root-level collections for a Rebase project",
|
|
14
|
+
mimeType: "application/json",
|
|
15
|
+
},
|
|
16
|
+
async (uri, variables) => {
|
|
17
|
+
const projectId = variables.projectId as string;
|
|
18
|
+
try {
|
|
19
|
+
const collections = await api.getRootCollections(projectId);
|
|
20
|
+
return {
|
|
21
|
+
contents: [{
|
|
22
|
+
uri: uri.href,
|
|
23
|
+
mimeType: "application/json",
|
|
24
|
+
text: JSON.stringify(collections, null, 2),
|
|
25
|
+
}],
|
|
26
|
+
};
|
|
27
|
+
} catch (error: any) {
|
|
28
|
+
return {
|
|
29
|
+
contents: [{
|
|
30
|
+
uri: uri.href,
|
|
31
|
+
mimeType: "application/json",
|
|
32
|
+
text: JSON.stringify({ error: error.message }),
|
|
33
|
+
}],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
server.registerResource(
|
|
40
|
+
"project-users",
|
|
41
|
+
new ResourceTemplate("rebase://projects/{projectId}/users", { list: undefined }),
|
|
42
|
+
{
|
|
43
|
+
description: "Users with access to a Rebase project",
|
|
44
|
+
mimeType: "application/json",
|
|
45
|
+
},
|
|
46
|
+
async (uri, variables) => {
|
|
47
|
+
const projectId = variables.projectId as string;
|
|
48
|
+
try {
|
|
49
|
+
const users = await api.listUsers(projectId);
|
|
50
|
+
return {
|
|
51
|
+
contents: [{
|
|
52
|
+
uri: uri.href,
|
|
53
|
+
mimeType: "application/json",
|
|
54
|
+
text: JSON.stringify(users, null, 2),
|
|
55
|
+
}],
|
|
56
|
+
};
|
|
57
|
+
} catch (error: any) {
|
|
58
|
+
return {
|
|
59
|
+
contents: [{
|
|
60
|
+
uri: uri.href,
|
|
61
|
+
mimeType: "application/json",
|
|
62
|
+
text: JSON.stringify({ error: error.message }),
|
|
63
|
+
}],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { RebaseApiClient } from "./api-client.js";
|
|
3
|
+
import { registerAuthTools } from "./tools/auth.js";
|
|
4
|
+
import { registerProjectTools } from "./tools/projects.js";
|
|
5
|
+
import { registerUserTools } from "./tools/users.js";
|
|
6
|
+
import { registerCollectionTools } from "./tools/collections.js";
|
|
7
|
+
import { registerDocumentTools } from "./tools/documents.js";
|
|
8
|
+
import { registerExportTools } from "./tools/export.js";
|
|
9
|
+
import { registerProjectResources } from "./resources/project.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create and configure the Rebase MCP server with all tools and resources.
|
|
13
|
+
*/
|
|
14
|
+
export function createRebaseMcpServer(): McpServer {
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "Rebase Cloud",
|
|
17
|
+
version: "0.1.0",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const api = new RebaseApiClient();
|
|
21
|
+
|
|
22
|
+
// Auth tools (login/logout)
|
|
23
|
+
registerAuthTools(server);
|
|
24
|
+
|
|
25
|
+
// Project & user management (via backend API)
|
|
26
|
+
registerProjectTools(server, api);
|
|
27
|
+
registerUserTools(server, api);
|
|
28
|
+
|
|
29
|
+
// Collection schema AI tools (via backend API)
|
|
30
|
+
registerCollectionTools(server, api);
|
|
31
|
+
|
|
32
|
+
// Firestore document CRUD (via backend API proxy)
|
|
33
|
+
registerDocumentTools(server, api);
|
|
34
|
+
|
|
35
|
+
// Data export (via backend API)
|
|
36
|
+
registerExportTools(server, api);
|
|
37
|
+
|
|
38
|
+
// Resources
|
|
39
|
+
registerProjectResources(server, api);
|
|
40
|
+
|
|
41
|
+
return server;
|
|
42
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import {
|
|
3
|
+
isLoggedIn,
|
|
4
|
+
getCurrentUserEmail,
|
|
5
|
+
loginFlow,
|
|
6
|
+
logoutFlow,
|
|
7
|
+
} from "../auth.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Register login/logout tools — same flow as `rebase login` CLI.
|
|
11
|
+
*/
|
|
12
|
+
export function registerAuthTools(server: McpServer) {
|
|
13
|
+
|
|
14
|
+
server.registerTool(
|
|
15
|
+
"rebase_login",
|
|
16
|
+
{
|
|
17
|
+
description: "Sign in to Rebase Cloud. Opens a browser window for Google OAuth authentication. Required before using any other tools.",
|
|
18
|
+
},
|
|
19
|
+
async () => {
|
|
20
|
+
const existingEmail = getCurrentUserEmail();
|
|
21
|
+
if (existingEmail) {
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: "text" as const,
|
|
25
|
+
text: `Already logged in as ${existingEmail}. Use rebase_logout to sign out first.`,
|
|
26
|
+
}],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await loginFlow();
|
|
32
|
+
const email = getCurrentUserEmail();
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text" as const,
|
|
36
|
+
text: `Successfully logged in as ${email ?? "unknown"}`,
|
|
37
|
+
}],
|
|
38
|
+
};
|
|
39
|
+
} catch (error: any) {
|
|
40
|
+
return {
|
|
41
|
+
content: [{
|
|
42
|
+
type: "text" as const,
|
|
43
|
+
text: `Login failed: ${error.message}`,
|
|
44
|
+
}],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
server.registerTool(
|
|
52
|
+
"rebase_logout",
|
|
53
|
+
{
|
|
54
|
+
description: "Sign out of Rebase Cloud. Revokes the current session.",
|
|
55
|
+
},
|
|
56
|
+
async () => {
|
|
57
|
+
if (!isLoggedIn()) {
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: "text" as const,
|
|
61
|
+
text: "Not currently logged in.",
|
|
62
|
+
}],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const email = getCurrentUserEmail();
|
|
67
|
+
try {
|
|
68
|
+
await logoutFlow();
|
|
69
|
+
return {
|
|
70
|
+
content: [{
|
|
71
|
+
type: "text" as const,
|
|
72
|
+
text: `Successfully logged out ${email ?? ""}`.trim(),
|
|
73
|
+
}],
|
|
74
|
+
};
|
|
75
|
+
} catch (error: any) {
|
|
76
|
+
return {
|
|
77
|
+
content: [{
|
|
78
|
+
type: "text" as const,
|
|
79
|
+
text: `Logout failed: ${error.message}`,
|
|
80
|
+
}],
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
server.registerTool(
|
|
88
|
+
"rebase_get_current_user",
|
|
89
|
+
{
|
|
90
|
+
description: "Get the currently authenticated Rebase user",
|
|
91
|
+
},
|
|
92
|
+
async () => {
|
|
93
|
+
const email = getCurrentUserEmail();
|
|
94
|
+
if (!email) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{
|
|
97
|
+
type: "text" as const,
|
|
98
|
+
text: "Not logged in. Use rebase_login to sign in.",
|
|
99
|
+
}],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [{
|
|
104
|
+
type: "text" as const,
|
|
105
|
+
text: `Logged in as: ${email}`,
|
|
106
|
+
}],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { RebaseApiClient } from "../api-client.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register collection schema tools.
|
|
7
|
+
*/
|
|
8
|
+
export function registerCollectionTools(server: McpServer, api: RebaseApiClient) {
|
|
9
|
+
|
|
10
|
+
server.registerTool(
|
|
11
|
+
"generate_collection",
|
|
12
|
+
{
|
|
13
|
+
description: `Generate a new Rebase collection schema using AI. Provide a natural language description
|
|
14
|
+
of the collection you want (e.g., "A blog with posts that have title, body, author, tags,
|
|
15
|
+
and a featured image"). Returns a complete Rebase collection configuration.`,
|
|
16
|
+
inputSchema: {
|
|
17
|
+
prompt: z.string().describe("Natural language description of the collection to generate"),
|
|
18
|
+
existingCollections: z.array(z.any()).optional().describe("Optional existing collection schemas for context"),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
async ({ prompt, existingCollections }) => {
|
|
22
|
+
try {
|
|
23
|
+
const result = await api.generateCollection(prompt, existingCollections ?? []);
|
|
24
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
return { content: [{ type: "text" as const, text: `Error: ${error.message}` }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
server.registerTool(
|
|
32
|
+
"modify_collection",
|
|
33
|
+
{
|
|
34
|
+
description: `Modify an existing Rebase collection schema using AI. Describe the changes you want
|
|
35
|
+
(e.g., "Add a priority enum with low/medium/high" or "Make title required with max 100 chars").
|
|
36
|
+
Returns the updated schema and a list of operations performed.`,
|
|
37
|
+
inputSchema: {
|
|
38
|
+
prompt: z.string().describe("Description of the modifications"),
|
|
39
|
+
existingCollection: z.any().describe("The current collection schema to modify"),
|
|
40
|
+
existingCollections: z.array(z.any()).optional().describe("Optional list of all collection schemas for context"),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
async ({ prompt, existingCollection, existingCollections }) => {
|
|
44
|
+
try {
|
|
45
|
+
const result = await api.generateCollection(prompt, existingCollections ?? [], existingCollection);
|
|
46
|
+
return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] };
|
|
47
|
+
} catch (error: any) {
|
|
48
|
+
return { content: [{ type: "text" as const, text: `Error: ${error.message}` }], isError: true };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|