mirror of
https://github.com/kennethnym/aris.git
synced 2026-06-13 11:01:18 +01:00
feat: add google maps mcp source (#125)
This commit is contained in:
@@ -66,11 +66,17 @@ export function SourceConfigPanel({ source, onUpdate }: SourceConfigPanelProps)
|
||||
return creds
|
||||
}
|
||||
|
||||
function hasUserConfigFields(): boolean {
|
||||
return Object.values(source.fields).some((field) => !isCredentialField(field))
|
||||
}
|
||||
|
||||
function buildReplaceBody(enabledValue: boolean): Parameters<typeof replaceSource>[1] {
|
||||
const body: Parameters<typeof replaceSource>[1] = { enabled: enabledValue }
|
||||
if (Object.keys(source.fields).length > 0) {
|
||||
|
||||
if (hasUserConfigFields()) {
|
||||
body.config = getUserConfig()
|
||||
}
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,12 @@ const sourceDefinitions: SourceDefinition[] = [
|
||||
description: "Exa web search action. Requires EXA_API_KEY on the backend.",
|
||||
fields: {},
|
||||
},
|
||||
{
|
||||
id: "freya.google-maps",
|
||||
name: "Google Maps",
|
||||
description: "Google Maps Grounding Lite MCP tools for places, weather, routes, and Place IDs.",
|
||||
fields: {},
|
||||
},
|
||||
]
|
||||
|
||||
export function fetchSources(): Promise<SourceDefinition[]> {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Loader2,
|
||||
TrainFront,
|
||||
LogOut,
|
||||
Map as MapIcon,
|
||||
MapPin,
|
||||
Rss,
|
||||
Server,
|
||||
@@ -49,6 +50,7 @@ const SOURCE_ICONS: Record<string, React.ComponentType<{ className?: string }>>
|
||||
"freya.weather": CloudSun,
|
||||
"freya.caldav": CalendarDays,
|
||||
"freya.google-calendar": Calendar,
|
||||
"freya.google-maps": MapIcon,
|
||||
"freya.tfl": TrainFront,
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"@freya/core": "workspace:*",
|
||||
"@freya/source-caldav": "workspace:*",
|
||||
"@freya/source-google-calendar": "workspace:*",
|
||||
"@freya/source-google-maps": "workspace:*",
|
||||
"@freya/source-location": "workspace:*",
|
||||
"@freya/source-tfl": "workspace:*",
|
||||
"@freya/source-weatherkit": "workspace:*",
|
||||
|
||||
55
apps/freya-backend/src/google-maps/provider.test.ts
Normal file
55
apps/freya-backend/src/google-maps/provider.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { GoogleMapsSourceOptions } from "@freya/source-google-maps"
|
||||
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
import { GoogleMapsSourceProvider } from "./provider.ts"
|
||||
|
||||
type McpClient = NonNullable<GoogleMapsSourceOptions["client"]>
|
||||
|
||||
class MockMcpClient implements McpClient {
|
||||
async listTools(): ReturnType<McpClient["listTools"]> {
|
||||
return { tools: [] }
|
||||
}
|
||||
|
||||
async readResource(
|
||||
_params: Parameters<McpClient["readResource"]>[0],
|
||||
): ReturnType<McpClient["readResource"]> {
|
||||
throw new Error("unexpected resource read")
|
||||
}
|
||||
|
||||
async callTool(_params: Parameters<McpClient["callTool"]>[0]): ReturnType<McpClient["callTool"]> {
|
||||
return { structuredContent: {} }
|
||||
}
|
||||
}
|
||||
|
||||
describe("GoogleMapsSourceProvider", () => {
|
||||
test("sourceId is freya.google-maps", () => {
|
||||
const provider = new GoogleMapsSourceProvider({ apiKey: "key" })
|
||||
expect(provider.sourceId).toBe("freya.google-maps")
|
||||
})
|
||||
|
||||
test("throws when service API key is empty", () => {
|
||||
expect(() => new GoogleMapsSourceProvider({ apiKey: "" })).toThrow(
|
||||
"Google Maps MCP API key must be configured",
|
||||
)
|
||||
})
|
||||
|
||||
test("returns source with service API key", async () => {
|
||||
const provider = new GoogleMapsSourceProvider({ apiKey: "key" })
|
||||
|
||||
const source = await provider.feedSourceForUser("user-1", {}, null)
|
||||
|
||||
expect(source.id).toBe("freya.google-maps")
|
||||
})
|
||||
|
||||
test("allows injected test client with service API key", async () => {
|
||||
const provider = new GoogleMapsSourceProvider({
|
||||
apiKey: "key",
|
||||
client: new MockMcpClient(),
|
||||
})
|
||||
|
||||
const source = await provider.feedSourceForUser("user-1", {}, null)
|
||||
|
||||
expect(source.id).toBe("freya.google-maps")
|
||||
})
|
||||
})
|
||||
39
apps/freya-backend/src/google-maps/provider.ts
Normal file
39
apps/freya-backend/src/google-maps/provider.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { GoogleMapsSource, type GoogleMapsSourceOptions } from "@freya/source-google-maps"
|
||||
|
||||
import type { FeedSourceProvider } from "../session/feed-source-provider.ts"
|
||||
|
||||
export interface GoogleMapsSourceProviderOptions {
|
||||
readonly apiKey: string
|
||||
readonly client?: GoogleMapsSourceOptions["client"]
|
||||
}
|
||||
|
||||
export class GoogleMapsSourceProvider implements FeedSourceProvider {
|
||||
readonly sourceId = "freya.google-maps"
|
||||
|
||||
private readonly apiKey: string
|
||||
private readonly client: GoogleMapsSourceProviderOptions["client"]
|
||||
|
||||
constructor(options: GoogleMapsSourceProviderOptions) {
|
||||
if (!nonEmptyString(options.apiKey)) {
|
||||
throw new Error("Google Maps MCP API key must be configured")
|
||||
}
|
||||
|
||||
this.apiKey = options.apiKey
|
||||
this.client = options.client
|
||||
}
|
||||
|
||||
async feedSourceForUser(
|
||||
_userId: string,
|
||||
_config: unknown,
|
||||
_credentials: unknown,
|
||||
): Promise<GoogleMapsSource> {
|
||||
return new GoogleMapsSource({
|
||||
apiKey: this.apiKey,
|
||||
client: this.client,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function nonEmptyString(value: string): boolean {
|
||||
return typeof value === "string" && value.trim().length > 0
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { createDatabase } from "./db/index.ts"
|
||||
import { registerFeedHttpHandlers } from "./engine/http.ts"
|
||||
import { createFeedEnhancer } from "./enhancement/enhance-feed.ts"
|
||||
import { createLlmClient } from "./enhancement/llm-client.ts"
|
||||
import { GoogleMapsSourceProvider } from "./google-maps/provider.ts"
|
||||
import { CredentialEncryptor } from "./lib/crypto.ts"
|
||||
import { registerLocationHttpHandlers } from "./location/http.ts"
|
||||
import { LocationSourceProvider } from "./location/provider.ts"
|
||||
@@ -47,6 +48,11 @@ function main() {
|
||||
)
|
||||
}
|
||||
|
||||
const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY ?? process.env.GOOGLE_MAPS_MCP_API_KEY
|
||||
if (!googleMapsApiKey) {
|
||||
throw new Error("GOOGLE_MAPS_API_KEY or GOOGLE_MAPS_MCP_API_KEY must be set")
|
||||
}
|
||||
|
||||
const sessionManager = new UserSessionManager({
|
||||
db,
|
||||
providers: [
|
||||
@@ -62,6 +68,9 @@ function main() {
|
||||
}),
|
||||
new TflSourceProvider({ apiKey: process.env.TFL_API_KEY! }),
|
||||
new WebSearchSourceProvider({ apiKey: process.env.EXA_API_KEY }),
|
||||
new GoogleMapsSourceProvider({
|
||||
apiKey: googleMapsApiKey,
|
||||
}),
|
||||
],
|
||||
feedEnhancer,
|
||||
credentialEncryptor,
|
||||
|
||||
Reference in New Issue
Block a user