Compare commits

..

9 Commits

Author SHA1 Message Date
orangecoding
6ccbdd8afc upgrading dependencies 2026-03-16 10:41:53 +01:00
orangecoding
2a30c89eb2 improving version banner 2026-03-16 10:37:36 +01:00
orangecoding
4878dc98e3 Merge branch 'master' of github.com:orangecoding/fredy 2026-03-11 15:26:56 +01:00
orangecoding
dc2704997d upgrading dependencies 2026-03-11 15:26:25 +01:00
orangecoding
e107b0fb00 next release version 2026-03-11 15:25:20 +01:00
Promises
6c08675fee Add new properties to real estate translation mappings (#275)
Added few more properties for buying a house
2026-03-11 14:49:21 +01:00
orangecoding
34c4de7267 fixing stdin for mcp 2026-03-10 09:27:04 +01:00
orangecoding
b64a118a18 moving mcp into lib to make it available in docker setup 2026-03-09 16:26:53 +01:00
orangecoding
03cb4d18cb fix formatting 2026-03-09 15:40:29 +01:00
13 changed files with 500 additions and 556 deletions

View File

@@ -159,7 +159,7 @@ Jobs run automatically at the interval you configure (see
Starting with **V20**, Fredy ships with a built-in **MCP Server **. This allows you to connect Fredy to LLMs (like Claude, ChatGPT, or local models via LM Studio) and query your real estate data using natural language.
The local LLM can even enrich existing listings by checking the listing online.
For more information on how to set it up and use it, please refer to the [MCP Readme](mcp/README.md).
For more information on how to set it up and use it, please refer to the [MCP Readme](lib/mcp/README.md).
------------------------------------------------------------------------

View File

@@ -24,7 +24,7 @@ import { getSettings } from '../services/storage/settingsStorage.js';
import { dashboardRouter } from './routes/dashboardRouter.js';
import { backupRouter } from './routes/backupRouter.js';
import { trackingRouter } from './routes/trackingRoute.js';
import { registerMcpRoutes } from '../../mcp/mcpHttpRoute.js';
import { registerMcpRoutes } from '../mcp/mcpHttpRoute.js';
const service = restana();
const staticService = files(path.join(getDirName(), '../ui/public'));
const PORT = (await getSettings()).port || 9998;

View File

@@ -9,8 +9,8 @@
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { queryJobs, getJob } from '../lib/services/storage/jobStorage.js';
import { queryListings, getListingById } from '../lib/services/storage/listingsStorage.js';
import { queryJobs, getJob } from '../services/storage/jobStorage.js';
import { queryListings, getListingById } from '../services/storage/listingsStorage.js';
import { authenticateToolCall, checkJobAccess } from './mcpAuthentication.js';
import {
normalizeListJobs,

View File

@@ -10,7 +10,7 @@
* and HTTP requests. Ensures consistent access control across all transports.
*/
import { getUser, validateMcpToken } from '../lib/services/storage/userStorage.js';
import { getUser, validateMcpToken } from '../services/storage/userStorage.js';
/**
* Authenticate an MCP tool call by extracting and validating the user from authInfo.

View File

@@ -10,7 +10,7 @@
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createMcpServer } from './mcpAdapter.js';
import { authenticateRequest } from './mcpAuthentication.js';
import logger from '../lib/services/logger.js';
import logger from '../services/logger.js';
import crypto from 'crypto';
/**

View File

@@ -3,12 +3,10 @@
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
#!/usr/bin/env node
/*
* Copyright (c) 2026 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
/**
* Fredy MCP Server stdio transport
*
@@ -24,15 +22,15 @@
import { fileURLToPath } from 'url';
import path from 'path';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import SqliteConnection from '../lib/services/storage/SqliteConnection.js';
import { runMigrations } from '../lib/services/storage/migrations/migrate.js';
import SqliteConnection from '../services/storage/SqliteConnection.js';
import { runMigrations } from '../services/storage/migrations/migrate.js';
import { createMcpServer } from './mcpAdapter.js';
import { validateMcpToken } from '../lib/services/storage/userStorage.js';
import { validateMcpToken } from '../services/storage/userStorage.js';
// Ensure cwd is the project root so that relative DB/config paths resolve correctly
// (LM Studio and other MCP hosts may spawn this process from an arbitrary directory)
const __dirname = path.dirname(fileURLToPath(import.meta.url));
process.chdir(path.resolve(__dirname, '..'));
process.chdir(path.resolve(__dirname, '..', '..'));
// Initialize the database (required for standalone usage)
await SqliteConnection.init();

View File

@@ -79,6 +79,8 @@ const PARAM_NAME_MAP = {
price: 'price',
constructionyear: 'constructionyear',
apartmenttypes: 'apartmenttypes',
buildingtypes: 'buildingtypes',
ground: 'ground',
pricetype: 'pricetype',
floor: 'floor',
geocodes: 'geocodes',
@@ -98,6 +100,7 @@ const EQUIPMENT_MAP = {
guesttoilet: 'guestToilet',
balcony: 'balcony',
handicappedaccessible: 'handicappedAccessible',
lodgerflat: 'lodgerflat',
};
const REAL_ESTATE_TYPE = {
@@ -107,6 +110,10 @@ const REAL_ESTATE_TYPE = {
'wohnung-kaufen-mit-balkon': 'apartmentbuy',
'eigentumswohnung-mit-garten': 'apartmentbuy',
'haus-kaufen': 'housebuy',
'haus-mit-keller-kaufen': 'housebuy',
'luxushaus-kaufen': 'housebuy',
'villa-kaufen': 'housebuy',
'neubauhaus-kaufen': 'housebuy',
};
const WEB_PATH_TO_APARTMENT_EQUIPMENT_MAP = {

View File

@@ -1,6 +1,6 @@
{
"name": "fredy",
"version": "20.0.0",
"version": "20.0.4",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": {
"prepare": "husky",
@@ -14,7 +14,7 @@
"test": "node --import ./test/esmock-loader.mjs ./node_modules/mocha/bin/mocha.js --timeout 60000 test/**/*.test.js",
"testGH": "node --import ./test/esmock-loader.mjs ./node_modules/mocha/bin/mocha.js --timeout 60000 --exclude test/provider/immonet.test.js --exclude test/provider/immobilienDe.test.js --exclude test/provider/immowelt.test.js test/**/*.test.js",
"lint": "eslint .",
"mcp:stdio": "node mcp/stdio.js",
"mcp:stdio": "node lib/mcp/stdio.js",
"lint:fix": "yarn lint --fix",
"migratedb": "node lib/services/storage/migrations/migrate.js",
"migratedb:overwrite": "x-var MIGRATION_ALLOW_CHECKSUM_UPDATE=true node lib/services/storage/migrations/migrate.js",
@@ -61,15 +61,15 @@
"Firefox ESR"
],
"dependencies": {
"@douyinfe/semi-icons": "^2.92.2",
"@douyinfe/semi-ui": "2.92.2",
"@douyinfe/semi-ui-19": "^2.92.2",
"@douyinfe/semi-icons": "^2.93.0",
"@douyinfe/semi-ui": "2.93.0",
"@douyinfe/semi-ui-19": "^2.93.0",
"@mapbox/mapbox-gl-draw": "^1.5.1",
"@sendgrid/mail": "8.1.6",
"@vitejs/plugin-react": "5.1.4",
"@modelcontextprotocol/sdk": "^1.27.0",
"@vitejs/plugin-react": "6.0.1",
"@modelcontextprotocol/sdk": "^1.27.1",
"adm-zip": "^0.5.16",
"better-sqlite3": "^12.6.2",
"better-sqlite3": "^12.8.0",
"body-parser": "2.2.2",
"chart.js": "^4.5.1",
"cheerio": "^1.2.0",
@@ -77,14 +77,14 @@
"cookie-session": "2.1.1",
"handlebars": "4.7.8",
"lodash": "4.17.23",
"maplibre-gl": "^5.19.0",
"nanoid": "5.1.6",
"maplibre-gl": "^5.20.1",
"nanoid": "5.1.7",
"node-cron": "^4.2.1",
"node-fetch": "3.3.2",
"node-mailjet": "6.0.11",
"p-throttle": "^8.1.0",
"package-up": "^5.0.0",
"puppeteer": "^24.38.0",
"puppeteer": "^24.39.1",
"puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2",
"query-string": "9.3.1",
@@ -99,9 +99,9 @@
"semver": "^7.7.4",
"serve-static": "2.2.1",
"slack": "11.0.2",
"vite": "7.3.1",
"vite": "8.0.0",
"x-var": "^3.0.1",
"zustand": "^5.0.11"
"zustand": "^5.0.12"
},
"devDependencies": {
"@babel/core": "7.29.0",
@@ -118,8 +118,8 @@
"globals": "^17.4.0",
"history": "5.3.0",
"husky": "9.1.7",
"less": "4.5.1",
"lint-staged": "16.3.2",
"less": "4.6.4",
"lint-staged": "16.4.0",
"mocha": "11.7.5",
"nodemon": "^3.1.14",
"prettier": "3.8.1"

View File

@@ -3,36 +3,86 @@
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
import { Collapse, Descriptions } from '@douyinfe/semi-ui-19';
import { useState } from 'react';
import { Banner, Button, Modal, Tag, Space, Typography, Descriptions, MarkdownRender } from '@douyinfe/semi-ui-19';
import { IconAlertCircle, IconArrowRight } from '@douyinfe/semi-icons';
import { useSelector } from '../../services/state/store.js';
import { MarkdownRender } from '@douyinfe/semi-ui-19';
import './VersionBanner.less';
const { Text } = Typography;
export default function VersionBanner() {
const [modalVisible, setModalVisible] = useState(false);
const versionUpdate = useSelector((state) => state.versionUpdate.versionUpdate);
return (
<Collapse>
<Collapse.Panel header="A new version of Fredy is available" itemKey="1" className="versionBanner">
<div className="versionBanner__content">
<p>A new version of Fredy is available. Update now to take advantage of the latest features and bug fixes.</p>
<Descriptions row size="small">
<Descriptions.Item itemKey="Your Version">{versionUpdate.localFredyVersion}</Descriptions.Item>
<Descriptions.Item itemKey="Latest Version">{versionUpdate.version}</Descriptions.Item>
<Descriptions.Item itemKey="Github Release">
<a href={versionUpdate.url} target="_blank" rel="noreferrer">
{versionUpdate.url}
</a>{' '}
</Descriptions.Item>
</Descriptions>
<p>
<b>
<small>Release Notes</small>
</b>
</p>
<>
<Banner
className="versionBanner"
type="warning"
bordered
closeIcon={null}
description={
<div className="versionBanner__bar">
<Space spacing={8} align="center">
<IconAlertCircle size="small" />
<Text strong size="small">
New version available
</Text>
<Tag color="amber" size="small" shape="circle">
{versionUpdate.version}
</Tag>
<Text type="tertiary" size="small">
Current: {versionUpdate.localFredyVersion}
</Text>
</Space>
<Button
theme="borderless"
size="small"
icon={<IconArrowRight />}
iconPosition="right"
onClick={() => setModalVisible(true)}
>
Release notes
</Button>
</div>
}
/>
<Modal
title={
<Space spacing={8} align="center">
<Text strong>Fredy {versionUpdate.version}</Text>
<Tag color="amber" size="small">
New
</Tag>
</Space>
}
visible={modalVisible}
onCancel={() => setModalVisible(false)}
width={640}
footer={
<Space>
<Button onClick={() => setModalVisible(false)}>Close</Button>
<Button
type="primary"
icon={<IconArrowRight />}
iconPosition="right"
onClick={() => window.open(versionUpdate.url, '_blank')}
>
View on GitHub
</Button>
</Space>
}
>
<Descriptions row size="small" className="versionBanner__details">
<Descriptions.Item itemKey="Your Version">{versionUpdate.localFredyVersion}</Descriptions.Item>
<Descriptions.Item itemKey="Latest Version">{versionUpdate.version}</Descriptions.Item>
</Descriptions>
<div className="versionBanner__notes">
<MarkdownRender raw={versionUpdate.body} />
</div>
</Collapse.Panel>
</Collapse>
</Modal>
</>
);
}

View File

@@ -1,7 +1,24 @@
.versionBanner {
background: rgba(var(--semi-teal-1), 1);
margin-bottom: 0 !important;
&__content {
overflow: auto;
.semi-banner-body {
padding: 6px 16px;
}
}
&__bar {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
&__details {
margin-bottom: 16px;
}
&__notes {
max-height: 400px;
overflow-y: auto;
padding-right: 4px;
}
}

878
yarn.lock

File diff suppressed because it is too large Load Diff