Skip to main content
Glama
kevics1

GeoOntology-MCP

by kevics1

GeoOntology-MCP

地理本体层 MCP 服务器——让 LLM 用地理结构推理,而非裸 SQL。

为什么需要这个项目

现有 PostGIS MCP 服务器只暴露原始 SQL 查询能力。LLM 拿到的是行和列,不是"南京属于江苏"或"苏州与上海相邻"这样的地理结构知识。

GeoOntology-MCP 暴露的是地理本体层——层级关系、拓扑邻接、语义消歧、空间运算、属性对比,以及核心创新:地理约束校验。LLM 生成地理断言后,MCP 可以校验真伪并返回结构化纠错,驱动 LLM 自我修正。

LLM: "苏州与上海不相邻"
  → check_adjacency("320500", "310000")
  ← { valid: false, correction: "苏州市 与 上海市 相邻,共享边界 42.3km",
       evidence: { shared_boundary_length_km: 42.3 } }
LLM: "我修正之前的说法,苏州与上海实际上是相邻的……"

Related MCP server: GIS Data Conversion MCP

架构

LLM Client ──MCP Protocol──► GeoOntology-MCP (TypeScript)
                                    │
                          ┌─────────┴─────────┐
                          │  Ontology Engine    │
                          │  - 层级遍历 (KG)    │
                          │  - 拓扑查询 (PostGIS)│
                          │  - 约束校验 (两层)  │
                          │  - 空间运算 (PostGIS)│
                          │  - 路径搜索 (BFS)   │
                          └─────────┬─────────┘
                                    │
                      ┌─────────────┴─────────────┐
                      │                           │
              Knowledge Graph (内存)        PostGIS (持久化)
              - 46,413 节点               - boundary geometries
              - 44,956 BELONGS_TO 边      - admin_adjacency (预计算)
              - 地名/拼音/英文索引         - admin_containment (预计算)
                                          - admin_attributes (属性数据)

数据策略:知识图谱 JSON 在启动时加载到内存,处理所有层级关系和地名消歧(纯图遍历,O(depth));邻接关系通过 PostGIS ST_Touches 预计算后存入 admin_adjacency 表,查询时 O(1)。

工具一览(10 个)

查询类

工具

参数

说明

resolve_place

name, context?

地名消歧,支持中文、拼音、英文。返回候选列表(ID、层级位置、坐标),按相关性排序取 top-5

get_ancestors

id

向上遍历层级链(县→市→省),返回从直接父到顶级的有序数组

get_neighbors

id

查询同层级邻接区域,含共享边界长度

describe_region

id, include_geometry?, geometry_tolerance_km?

区域全景:层级位置、邻居、面积、类型、子区数量,可选返回简化 GeoJSON

校验类(核心差异化)

工具

参数

说明

check_containment

child_id, parent_id

验证包含关系,KG 层级检查 + PostGIS 空间回退

check_adjacency

id_a, id_b

验证邻接关系,预计算表 + 实时空间回退

空间运算类

工具

参数

说明

find_path

id_a, id_b, max_hops?

BFS 搜索两区域间的邻接路径(如 江苏→浙江)

measure_distance

id_a, id_b

两区域质心间距离 (km)

find_within

id, radius_km

查找半径内的同层级区域

属性对比类

工具

参数

说明

compare_regions

ids[], metric

多区域属性排名对比(人口/GDP/面积)

校验返回格式

校验通过:

{
  "valid": true,
  "claim": "南京市 属于 江苏省",
  "explanation": "层级关系确认: 南京市(city) → 江苏省(province)"
}

校验失败 + 纠错:

{
  "valid": false,
  "claim": "南京市 属于 浙江省",
  "explanation": "南京市 不属于 浙江省,实际属于 江苏省(province)",
  "correction": "实际归属: 南京市 属于 江苏省(province)",
  "evidence": {
    "actual_parent_id": "320000",
    "actual_parent_name": "江苏省",
    "actual_parent_level": "province"
  }
}

这种结构化返回使 LLM 能理解错误原因并自我修正——这就是地理约束驱动的推理精炼环

地名消歧(多语言)

resolve_place 支持三级匹配:

  1. 中文精确/模糊匹配 — "南京"、"南京市"、"江宁"

  2. 拼音匹配 — "nanjing"、"jss"(首字母)、"jiangsusheng"

  3. 英文别名匹配 — "Nanjing"、"Jiangsu"、"Canton"

拼音索引使用 pinyin-pro 库在启动时构建,英文别名覆盖所有省和主要地级市(~60 个)。

Skills

GeoRiddle(地理谜题推理)

skills/geo-riddle/SKILL.md 定义地理谜题推理工作流。LLM 解谜时自动调用 MCP 工具逐步缩小候选范围:

谜题: "我是江苏省面积最小的与安徽接壤的地级市"
→ get_children("320000")        → 获取江苏所有地级市
→ describe_region(每个市)        → 获取面积
→ check_adjacency(每个市, "340000") → 筛选与安徽相邻的
→ 综合约束得出答案: 南京市

5 道示例谜题(1-5 分难度),每道附带完整 solution trace。

GeoFactCheck(地理断言校验)

skills/geo-fact-check/SKILL.md 定义 LLM 输出中的地理断言自动扫描与校验:

  1. 扫描 — 识别包含/邻接/否定断言模式

  2. 提取 — 解析为 (subject, relation, object) 三元组

  3. 校验 — 调用 check_containment / check_adjacency

  4. 报告 — verified / falsified / unverifiable 三类

支持中英文断言模式:

模式

示例

三元组

X属于Y

"南京属于江苏"

(南京, BELONGS_TO, 江苏)

X与Y相邻

"江苏与浙江相邻"

(江苏, ADJACENT_TO, 浙江)

X不属于Y

"南京不属于浙江"

(南京, NOT_BELONGS_TO, 浙江)

MCP Resources

mapping_knowledge/mapping_resources/ 目录下的 JSON 文件自动注册为 MCP Resource,LLM 可按需读取制图知识:

  • projection-advice.json — 中国地图投影选择指南(Albers / Lambert / Web Mercator)

  • admin-level-guide.json — 行政区划层级体系及编码规则

添加新 JSON 文件即可扩展,无需代码改动。

校验机制(双层)

  1. 层级检查(KG 内存遍历):遍历 parentOf 链,O(depth) ≈ 最多 4 次查找。无需数据库查询。

  2. 空间回退(PostGIS ST_Contains / ST_Touches):当 ID 不在 KG 中或需要空间验证时使用。单次查询约 16-23ms。

前置条件

  • Node.js 18+

  • PostgreSQL + PostGIS 扩展

  • 本地 PostGIS 数据库包含中国行政区划表(province, city, county, village

  • 知识图谱 JSON 文件(admin_knowledge_graph.json

数据库表结构

项目连接的 PostGIS 数据表:

表名

名称列

区划码列

几何列

province

省级码

geom

city

地名

区划码

geom

county

地名

区划码

geom

village

name

area_code

geom

预计算表(npm run materialize 创建):

表名

说明

admin_adjacency

邻接关系(70 + 963 + 8134 对)

admin_containment

包含关系闭包(预留)

admin_attributes

区域属性(人口/GDP/面积)

安装与运行

# 安装依赖
npm install

# 首次运行:预计算邻接表(约 30 秒)
npm run materialize

# 导入属性数据(人口/GDP/面积)
npx tsx scripts/materialize-attributes.ts

# 启动 MCP 服务器
npm run dev

启动后输出:

Loading knowledge graph...
Loaded 46413 nodes, 44956 edges
Connecting to PostGIS...
PostGIS connected
Registered 2 mapping resources
GeoOntology-MCP server running

配置

环境变量

变量

默认值

说明

KG_PATH

(required)

知识图谱 JSON 路径

MAPPING_RESOURCES_DIR

mapping_knowledge/mapping_resources

制图知识资源目录

PGHOST

127.0.0.1

PostgreSQL 主机

PGPORT

5432

PostgreSQL 端口

PGDATABASE

(required)

数据库名

PGUSER

(required)

数据库用户

PGPASSWORD

(required)

数据库密码

Claude Desktop / Claude Code 配置

{
  "mcpServers": {
    "geo-ontology": {
      "command": "npx",
      "args": ["tsx", "/path/to/GeoOntology-MCP/src/index.ts"],
      "env": {
        "KG_PATH": "/path/to/admin_knowledge_graph.json",
        "PGHOST": "127.0.0.1",
        "PGPORT": "5432",
        "PGDATABASE": "your_database",
        "PGUSER": "your_user",
        "PGPASSWORD": "your_password"
      }
    }
  }
}

使用示例

典型工作流:先消歧,再查询,最后校验

→ resolve_place(name="南京")
← [{ id: "320100", name: "南京市", level: "city", parentName: "江苏省", score: 104 }]

→ get_ancestors(id="320100")
← [{ id: "320000", name: "江苏省", level: "province", type: "省" }]

→ check_containment(child_id="320102", parent_id="320000")
← { valid: true, claim: "玄武区 属于 江苏省",
     explanation: "层级关系确认: 玄武区(county) → 南京市(city) → 江苏省(province)" }

拼音/英文搜索

→ resolve_place(name="Nanjing")
← [{ id: "320100", name: "南京市", level: "city", score: 90 }]

→ resolve_place(name="jss")
← [{ id: "320000", name: "江苏省", level: "province", score: 40 }]

路径搜索

→ find_path(id_a="320000", id_b="330000")
← { found: true, path: ["320000", "330000"], pathNames: ["江苏省", "浙江省"],
     hops: 1, totalBoundaryKm: 152.7 }

距离与范围查询

→ measure_distance(id_a="320100", id_b="330100")
← { idA: "320100", nameA: "南京市", idB: "330100", nameB: "杭州市",
     distanceKm: 267.4, method: "centroid-to-centroid" }

→ find_within(id="320100", radius_km=200)
← [{ id: "330100", name: "杭州市", distanceKm: 267.4, ... }, ...]

属性对比

→ compare_regions(ids=["110000", "310000", "320000"], metric="population")
← { metric: "population", regions: [
     { id: "320000", name: "江苏省", value: 85054000, rank: 1 },
     { id: "110000", name: "北京市", value: 21893095, rank: 2 },
     { id: "310000", name: "上海市", value: 24870895, rank: 3 }
   ] }

几何数据

→ describe_region(id="110000", include_geometry=true, geometry_tolerance_km=0.5)
← { id: "110000", name: "北京市", ..., geometry: { type: "Polygon", coordinates: [...] },
     geometryToleranceKm: 0.5 }

项目结构

src/
  index.ts                        # MCP 服务器入口
  data-source/
    interface.ts                  # GeoDataSource 接口 + 所有类型定义
    postgis-adapter.ts            # PostGIS 实现(核心适配器)
    column-map.ts                 # 表名/列名映射 + inferLevel()
  knowledge-graph/
    types.ts                      # KG 节点/边类型(含拼音/别名字段)
    loader.ts                     # KG JSON 加载 + 拼音索引 + 别名合并
    english-aliases.ts            # 省市英文名映射 (~60个)
  ontology/
    hierarchy.ts                  # 层级遍历(getAncestors, getChildren)
    resolver.ts                   # 地名消歧(中文/拼音/英文四级匹配)
    verifier.ts                   # 包含关系校验(KG 层级检查)
    pathfinder.ts                 # BFS 路径搜索(纯函数)
  tools/
    registration.ts               # 10 个工具注册到 McpServer
  resources/
    registration.ts               # MCP Resource 注册(mapping_knowledge)
scripts/
  materialize-adjacency.ts        # 邻接预计算脚本
  materialize-attributes.ts       # 属性数据导入脚本
skills/
  geo-riddle/SKILL.md             # GeoRiddle 谜题推理 skill
  geo-fact-check/SKILL.md         # GeoFactCheck 断言校验 skill
mapping_knowledge/
  mapping_resources/              # MCP Resource JSON 文件
    projection-advice.json        # 投影选择指南
    admin-level-guide.json        # 行政区划编码规则
data/
  admin-attributes.csv            # 省级属性数据(人口/GDP/面积)
tests/
  knowledge-graph.test.ts         # KG 加载器测试
  hierarchy.test.ts               # 层级遍历测试
  resolver.test.ts                # 地名消歧测试
  verifier.test.ts                # 校验逻辑测试
  column-map.test.ts              # 列映射测试
  pinyin-resolver.test.ts         # 拼音消歧测试
  path.test.ts                    # BFS 路径搜索测试
  fixtures/
    sample-kg.json                # 测试用样本 KG

可插拔数据源

所有工具通过 GeoDataSource 接口访问数据,PostGIS 只是默认实现:

interface GeoDataSource {
  connect(): Promise<void>;
  close(): Promise<void>;
  getRegionInfo(id: string): Promise<RegionInfo | null>;
  getAncestors(id: string): Promise<HierarchyNode[]>;
  getChildren(id: string): Promise<HierarchyNode[]>;
  getNeighbors(id: string): Promise<AdjacencyResult[]>;
  checkContainment(childId: string, parentId: string): Promise<VerificationResult>;
  checkAdjacency(idA: string, idB: string): Promise<VerificationResult>;
  resolvePlace(name: string, context?: string): Promise<PlaceCandidate[]>;
  findPath(idA: string, idB: string, maxHops?: number): Promise<PathResult>;
  getRegionGeometry(id: string, toleranceKm?: number): Promise<RegionGeometry | null>;
  measureDistance(idA: string, idB: string): Promise<DistanceResult | null>;
  findWithin(id: string, radiusKm: number): Promise<WithinResult[]>;
  getAttributes(id: string): Promise<RegionAttributes | null>;
  compareRegions(ids: string[], metric: 'population' | 'gdp' | 'areaSqKm'): Promise<CompareResult>;
}

测试

npm test

50 个单元测试覆盖:KG 加载器、层级遍历、地名消歧(中文+拼音)、校验逻辑、列映射、BFS 路径搜索。测试使用 tests/fixtures/sample-kg.json 样本数据,无需连接数据库。

技术栈

  • TypeScript + ESM (@modelcontextprotocol/sdk)

  • pg — PostgreSQL/PostGIS 客户端

  • pinyin-pro — 拼音转换

  • zod — 工具参数校验

  • Vitest — 单元测试

License

MIT

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kevics1/GeoOntology-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server