# Tengence GEO Agent (npm · @tengence/geo-mcp)

GEO/SEO content pipeline MCP: plan, write, gate-check, publish, submit, monitor AI visibility.

- Trust score: 77/100 (medium)
- Change this week: +15
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-04

## Components

- npm · `@tengence/geo-mcp`: 77/100 (this document), [markdown](https://verifymcp.io/servers/tengence-team-geo/tengence-geo-mcp.md), [page](https://verifymcp.io/servers/tengence-team-geo/tengence-geo-mcp)

## Channel facts

- Registry: `npm`
- Package: `@tengence/geo-mcp`
- Version: `0.1.1`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-04.

- **Supply Chain Security**: 90/100
  - No malware found by supply-chain analysis.
  - CVE check failed: a known high-severity CVE affects @xmldom/xmldom 0.9.10, reached via @tengence/geo-sdk > @rbbtsn0w/wechat-markdown > mathjax-full > speech-rule-engine > @xmldom/xmldom. A fixed version is available.
  - No install/post-install scripts declared.
  - 53 of 225 dependencies flagged as unhealthy (3 deprecated).
- **Provenance & Transparency**: 97/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to tengence-team/tengence-geo-agent).
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 1 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 77/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 7333 tokens (~130/item across 56 items; 56 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 0/100
  - Stability not yet verified: not enough scan history yet (needs a 30-day window).
- **Tool Coverage**: 100/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 100% of tool parameters carry a description.
- **Tool Safety**: 75/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - 0 of 13 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "check_article" implies "publish" and declares no destructiveHint at all, which the MCP spec reads as destructive by default.
  - An AI judge read all 56 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

**Unverified: 1 category.** A category scored 0 because we could not verify it: a data source with nothing on this package, evidence we could not reach, or a check we could not run. We only credit what we can confirm.

## Install

### How do I install the Tengence GEO Agent MCP server?

Tengence GEO Agent runs locally as an npm package, launched with npx -y @tengence/geo-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add tengence-team-geo -- npx -y @tengence/geo-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "tengence-team-geo": {
      "command": "npx",
      "args": [
        "-y",
        "@tengence/geo-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "tengence-team-geo": {
      "command": "npx",
      "args": [
        "-y",
        "@tengence/geo-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add tengence-team-geo -- npx -y @tengence/geo-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "tengence-team-geo": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "@tengence/geo-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add tengence-team-geo --command npx --arg -y --arg @tengence/geo-mcp
```

### Hermes

```yaml
mcp_servers:
  tengence-team-geo:
    command: "npx"
    args: ["-y", "@tengence/geo-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "tengence-team-geo": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "@tengence/geo-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add tengence-team-geo -t stdio -c npx -a -y @tengence/geo-mcp
```

### Other

```json
{
  "mcpServers": {
    "tengence-team-geo": {
      "command": "npx",
      "args": [
        "-y",
        "@tengence/geo-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-28 (score 77, +15)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server

### 2026-09-27 (score 62)

First indexed and scored.

## MCP tools (56)

### `workspace_use` (~91 tokens)

Bind the user workspace directory (call this first): all site config, credentials and output files live under it. Ask the user which directory to use if it was not mentioned; `~` is supported and expanded here.

Input parameters:

- `create` (boolean): create the directory when missing (default false)
- `path` (string, required): absolute directory path chosen by the user (e.g. ~/my-geo-workspace)

### `site_init` (~102 tokens)

Scaffold a site inside the bound workspace: <workspace>/<key>/{config,data/inbox,data/reports,.env} (idempotent, never overwrites existing files)

Input parameters:

- `domain` (string): canonical domain, defaults to the key
- `key` (string, required): site key (lowercase letters, digits, underscore)
- `lang` (string): default language (default zh-CN)
- `name` (string): display name, defaults to the domain

### `site_list` (~39 tokens)

List the sites inside the bound user workspace (site key, domain, app_id, storage driver). Returns a hint instead when no workspace is bound yet.

### `site_status` (~44 tokens)

Site status: config paths, .env readiness, DB driver and SQLite init state

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `article_list` (~68 tokens)

List articles (articles table), with status/limit filters

Input parameters:

- `limit` (number): max rows (default 50)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `status` (string): draft|queued|published etc. (optional)

### `article_ingest` (~106 tokens)

Content ingest (single entry): body md + research brief → articles table (CLI canonical orchestration)

Input parameters:

- `lang` (string): language (defaults to the site default)
- `md_path` (string, required): absolute path to the body Markdown file
- `research_path` (string): absolute path to the research brief (required by the publish gate)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `slug` (string, required): article slug

### `article_export` (~55 tokens)

Export an article md + brief from the DB to the site data/inbox (for rewriting)

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `slug` (string, required): article slug

### `check_article` (~140 tokens)

Publish gate: enforces the editorial rules codified in the "article-writing-standards" standard (citation dual-channel, word count by T1–T7 type, banned words, the three GEO blocks — Summary / Key Takeaways / FAQ / Data Sources — and the research-brief hard check). Read that standard via standards_read before authoring, and run article_draft first to scaffold per-spec.

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `slug` (string, required): article slug
- `type` (string): T1..T7 (hard word-count check by type when declared)

### `publish_draft` (~77 tokens)

Publish a draft (default draft → WP; can --status=publish directly, must pass the gate first)

Input parameters:

- `md_path` (string, required): absolute path to the body Markdown
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `status` (string): draft|publish (default draft)

### `publish_from_db` (~80 tokens)

Publish an article from the DB to WordPress (--force updates an existing article)

Input parameters:

- `article_id` (number, required): articles.id
- `force` (boolean): force-update when it already exists
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `status` (string): draft|publish (default draft)

### `publish_update_article` (~250 tokens)

Bypass update of an existing article (title/content/status + sync SEO/GEO meta). When meta_title / meta_description / post_title is provided, runs meta-only mode: updates ONLY those fields (meta_title → DB seo.title + WP seo_meta_title; meta_description → DB seo.meta_description + WP seo_meta_description, 165–175 chars hard gate; post_title → DB title + WP post_title itself); body/content/status/excerpt untouched.

Input parameters:

- `md_path` (string): absolute path to the body Markdown (required for full update; omit in meta-only mode)
- `meta_description` (string): new meta_description (165–175 characters). Providing it switches to meta-only mode
- `meta_title` (string): new SEO title (≤200 characters). Providing it switches to meta-only mode
- `post_id` (number): target WP post id (either slug or post_id)
- `post_title` (string): new WordPress post title itself (≤200 characters, also updates DB title column). Providing it switches to meta-only mode
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `slug` (string): target slug

### `publish_daily` (~109 tokens)

Run the daily promotion task (promotes due drafts to publish by plan publish_order; default 1/day). Optional date sets the promoted article publish/modified time (YYYY-MM-DDTHH:MM:SS, site timezone).

Input parameters:

- `date` (string): publish/modified time for the promoted article, YYYY-MM-DDTHH:MM:SS in the site timezone; omit to leave WordPress untouched
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `plan_list` (~75 tokens)

Article plan table list (status/batch/category/node filters)

Input parameters:

- `batch` (string): batch
- `limit` (number): max rows (default 100)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `status` (string): todo|written|queued|published etc.

### `plan_import` (~63 tokens)

Import/update the plan table from the site plan directory (idempotent)

Input parameters:

- `file` (string): absolute path to a plan file (default: scan the site plan directory)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `plan_mark_status` (~63 tokens)

Update a plan row status (todo/written/queued/published/archived)

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `slug` (string, required): article slug
- `status` (string, required): target status

### `image_acquire` (~98 tokens)

Auto image pipeline (search → red-line filter → download → WebP crop → upload to WP media library)

Input parameters:

- `count` (number): count (default 3)
- `dry_run` (boolean): preview only, nothing persisted
- `query` (string, required): image search keyword
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `slug` (string, required): target article slug (for registration)

### `monitor_run` (~77 tokens)

Run GEO monitoring (one round of site prompts × models; results persisted)

Input parameters:

- `layers` (array): subset of layers
- `models` (array): subset of model keys
- `run_id` (string): YYYYMMDD (default today)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `monitor_report` (~53 tokens)

Read monitoring results (latest round or a given run_id)

Input parameters:

- `run_id` (string): YYYYMMDD (default latest)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `db_init` (~62 tokens)

Explicitly initialize the database (sqlite idempotent table creation / mysql DDL; --drop rebuilds)

Input parameters:

- `drop` (boolean): drop and rebuild (dangerous)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `db_status` (~43 tokens)

Database status: driver, path, schema version, table list, missing tables

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `search_gsc_stats` (~241 tokens)

Read Google Search Console Search Analytics data: clicks / impressions / CTR / average position, grouped by query (default), page, date, country or device. This is the ONLY Google source for search performance — and the only source at all for page-level data (Bing dropped its page/traffic endpoints on 2026-08-31). GSC data is finalised 2–3 days late: the default window is the 28 days ending 3 days ago. Requires the GSC service account (secrets/gsc-service-account.json or GOOGLE_SA_JSON).

Input parameters:

- `dimensions` (array): group-by dimensions; default ["query"]. Use ["page"] for per-URL clicks/impressions, ["date"] for a daily trend, ["query","page"] for both.
- `end_date` (string): YYYY-MM-DD (default: 3 days ago)
- `row_limit` (number): max rows to return (default 100)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `start_date` (string): YYYY-MM-DD (default: 27 days before end_date)

### `search_gsc_inspect` (~110 tokens)

Query one URL's Google indexing status (URL Inspection API, read-only): verdict, coverageState, indexingState, lastCrawlTime, googleCanonical. Use it to check whether a specific article page is indexed. Quota: 2000 calls/day.

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `url` (string, required): fully-qualified URL to inspect, e.g. https://www.tengence.com/blog/article/<slug>/

### `search_submit_gsc` (~56 tokens)

Submit the sitemap to Google Search Console (soft-fail)

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `sitemap_url` (string): sitemap URL (default: auto-derived)

### `search_submit_indexnow` (~56 tokens)

Submit URLs to IndexNow (requires INDEXNOW_KEY)

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `url` (string, required): URLs to submit (multiple allowed, comma-separated)

### `search_submit_baidu` (~178 tokens)

Submit URLs to Baidu normal inclusion (requires BAIDU_TOKEN / BAIDU_SITE). Two modes: pass url for an explicit list, or all=true to push the sitemap incrementally — it recursively flattens sitemap_index.xml, skips every URL already logged as submitted in data/baidu-log.jsonl, and pushes at most limit URLs (default 10) so a scheduled run stays inside the daily quota.

Input parameters:

- `all` (boolean): sitemap incremental mode: push only URLs not yet logged as submitted
- `limit` (number): max URLs to push when all=true (default 10)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `url` (string): URLs to submit (multiple allowed, comma-separated, ≤2000 per call); omit when all=true

### `webmaster_bing_status` (~109 tokens)

Read Bing Webmaster console data (verified sites / URL submission quota / query stats / crawl issues). Requires BING_WEBMASTER_API_KEY in the site .env. Note: Bing trimmed its JSON API on 2026-08-31 (GetPages/GetSitemaps/GetTrafficStats/GetUrlSubmissionStatus now 404).

Input parameters:

- `action` (string, required): what to read from the Bing Webmaster console
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `webmaster_bing_submit` (~81 tokens)

Write to Bing Webmaster: submit URL(s) (comma-separated = batch, ≤10000 per call). Requires BING_WEBMASTER_API_KEY in the site .env.

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `url` (string, required): URL(s) to submit; multiple allowed, comma-separated

### `diagnose_site` (~174 tokens)

Full GEO/SEO site diagnosis: domain (DNS/TLS cert/WHOIS), transport & server fingerprint (CMS/framework/platform/CDN), robots.txt, sitemap, homepage + representative pages (meta, H1..H6 structure, JSON-LD, OG, images/alt, links, mixed content), performance and security checks. Bootstraps the site on first use (url → site key, dots → underscores).

Input parameters:

- `max_pages` (integer): max representative pages to sample (default 4; the site is sampled, never fully crawled)
- `pages` (array): extra page URLs to diagnose in addition to the homepage
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `url` (string, required): site URL, e.g. https://www.example.com

### `report_write` (~98 tokens)

Write a Markdown report into the site data/reports/ directory (path-safe: filename is basenamed and confined to the reports dir). Auto-bootstraps the site on first use.

Input parameters:

- `content` (string, required): Markdown report content
- `filename` (string): report filename (default <YYYYMMDD>-<site>-diagnosis.md)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `util_ping` (~27 tokens)

Connectivity self-check: SDK version, driver, internal data directory, bound workspace and site count

### `standards_list` (~51 tokens)

List the generic writing & GEO standards shipped with the engine (titles, whether a site-specific supplement exists). Read one with standards_read before authoring an article; see article_draft for a ready-to-use brief.

### `standards_read` (~100 tokens)

Read a generic standard document (base + any site-specific supplement merged). Names are repository-relative without `.md`, e.g. "article-writing-standards", "content-strategy", "block-conventions", "templates/skeletons/howto", "templates/research-brief". This is the canonical way to pull the requirements an article must satisfy.

Input parameters:

- `name` (string, required): standard name without .md, e.g. article-writing-standards

### `article_draft` (~161 tokens)

Assemble a ready-to-use writing brief for a new article: the matched T1–T7 body skeleton, the pre-writing research-brief template, and the full writing standard (base + supplement). The body you author MUST follow this brief; the check_article gate enforces the same G1–G14 editorial gates before publishing. Call this first, then write the Markdown body, then check_article.

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `topic` (string): working topic / target keyword (echoed into the brief for context)
- `type` (string): T1–T7 body type: definition|howto|product|case|industry|comparison|guide (default guide)

### `channel_list` (~199 tokens)

List all external content platforms in the syndicate registry: API type (official|cookie|none), status (ready|pending|manual), capabilities (multi-article merge, per-platform limits, draft/cover/tags) and the full platform rewrite rules (styles). The rewrite rules are the HARNESS rewriting guide — the server never rewrites content: read the original via article_export, apply these rules yourself, then channel_publish to land the draft/export. READY PLATFORMS: wechat (微信公众号, official API), juejin (掘金, cookie — can publish article drafts) and csdn (CSDN, cookie — the creator-console gateway; needs CSDN_COOKIE before status becomes ready). To publish to Juejin/CSDN pass platform="juejin"/"csdn" to channel_publish / channel_plan_*; this server is multi-channel.

Input parameters:

- `platform` (string): filter to one platform key

### `channel_style_get` (~148 tokens)

Return the FULL platform style / rewrite rules for ONE platform (wechat / juejin / blog / …). This is the RULES half of the rewrite surface: the server only serves the rules, the harness/Skill does the actual rewriting. Covers rewrite.strategy (source|title|body|both — what to change), title hooks + forbidden words, body structure + style, internalLinks requirement, cta and the acceptance checklist. ALWAYS call this BEFORE rewriting an article for a platform so the latest rules are used (never hardcode rules in the client). Multi-channel: pick by platform=.

Input parameters:

- `platform` (string, required): platform key, e.g. wechat / juejin / blog

### `channel_check` (~147 tokens)

Validate a title/body against a platform's style rules — the 校验 (validation) half. Deterministic, no content generation. Returns errors (HARD: title length / forbidden words / body length / removeBlocks) and warnings (SOFT: CTA presence / internal links / keepBlocks) plus the platform checklist and rewrite.strategy. Run AFTER rewriting to confirm it passes before channel_publish. Platform-scoped (wechat / juejin / blog / any registry platform).

Input parameters:

- `body` (string): article body (markdown) to validate
- `platform` (string, required): platform key, e.g. wechat / juejin / blog
- `title` (string): article title to validate

### `channel_publish` (~561 tokens)

Publish/export to one external platform. Mode A: pass slugs[] and the server reads the DB and runs the platform pipeline (wechat drafts box — preview in mp.weixin.qq.com before mass-sending; juejin draft → publish; csdn draft → publish; devto publish). Mode B: pass pre-written articles[] ({slug,title,contentMd,summary,tags,sourceUrl,cover}) — the harness-rewritten draft — and the server exports a publish package to <site>/data/channel-export/<platform>/<slug>.md with full front matter (manual publishing on every platform; required for api:none platforms). asDraft defaults true (never mass-sends). PLATFORMS: wechat, juejin (cookie — 原文直发 via juejin draft), csdn (cookie — 通过创作中心网关一步发文), devto. Juejin pipeline: reads DB article → resolves category/tags against the cached platform dictionary (channel_taxonomy; our article_plan.category/tags → the platform's category_id/tag_ids — see channel_taxonomy_resolve) → creates draft → writes body → publishes; dryRun=true previews the plan AND the resolved taxonomy with no external calls; idempotent — skips a slug already on Juejin (draft or published). Category and tag are both required by Juejin, so a fallback chain (alias → dictionary → env → dictionary default) guarantees both are always sent. CSDN pipeline: reads DB article → Markdown is converted to CSDN HTML locally (CSDN does NOT convert server-side) → one saveArticle call (status 2=draft / 0=publish); tags come from article_plan.tags else target_keywords (1~5 required to publish). Idempotent through the SAME slug-based dedup as juejin. Every publish outcome is logged to channel_plan under its own platform, so each platform keeps an INDEPENDENT publishing calendar (only real outcomes are recorded — the blog plan is never bulk-imported). This server is multi-channel; choose by platform=.

Input parameters:

- `articles` (array): Mode B: pre-written (harness-rewritten) articles to export as publish packages
- `asDraft` (boolean): default true: create draft (wechat/juejin), never mass-send
- `dryRun` (boolean): print the plan, no external calls
- `keepOrder` (boolean): wechat only: keep slugs order (1st = headline)
- `platform` (string, required): platform key (see channel_list)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `slugs` (array): Mode A: article slugs to publish through the platform pipeline

### `channel_plan_next` (~233 tokens)

Per-platform publishing calendar: return the next article to publish for a platform plus all calendar rows (status/period/topic/weekday/slugs). Each platform has its OWN rows in the same channel_plan table. wechat keeps a real per-issue calendar (returns the earliest row still todo). juejin / csdn (and other api platforms) treat the table as a PUBLISH LOG — nextDue is DERIVED from the blog article_plan: it returns the next blog-published article (by publish_order) that is NOT yet recorded as published on THAT platform, so each platform's queue follows the blog order and resumes right after the last article published to it (failed rows retry). Nothing is ever bulk-copied from the blog plan: only real outcomes are recorded, so juejin and csdn each keep their own independent calendar in the shared table. Seed already-published articles with channel_plan_reconcile (platform=csdn) before the first nextDue, otherwise the csdn queue starts from the very first blog article.

Input parameters:

- `platform` (string): filter rows to one platform (default: all platforms)

### `channel_plan_mark` (~112 tokens)

Update a channel_plan row status (todo | draft | published | paused) and optionally record its draft ids (media ids). Call after channel_publish to persist the created draft (e.g. wechat media_id) and keep the calendar in sync.

Input parameters:

- `draftIds` (array): draft/media ids to record (e.g. wechat media_id); omit to keep existing
- `id` (number, required): channel_plan row id (see channel_plan_next rows)
- `status` (string, required): new row status

### `juejin_status` (~130 tokens)

Read the Juejin (掘金) backend for this site: the draft box (article_draft/list_by_user) and the published list (article/list_by_user). Read-only. Requires JUEJIN_COOKIE + JUEJIN_UID in the site .env. Use it to verify before publishing (idempotency) and to see what is already on Juejin. page defaults to 0 (50 per page).

Input parameters:

- `page` (number): page index (default 0)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `juejin_draft_delete` (~109 tokens)

Delete a Juejin draft (article_draft/delete). IRREVERSIBLE — pass confirm=true only after verifying the draft id via juejin_status. Requires JUEJIN_COOKIE + JUEJIN_UID in the site .env.

Input parameters:

- `confirm` (boolean, required): must be true to actually delete
- `draftId` (string, required): draft id from juejin_status drafts[].id
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `juejin_article_delete` (~107 tokens)

Delete a published Juejin article (article/delete). IRREVERSIBLE — pass confirm=true only after verifying the article id via juejin_status. Requires JUEJIN_COOKIE + JUEJIN_UID in the site .env.

Input parameters:

- `articleId` (string, required): article id from juejin_status published[].id
- `confirm` (boolean, required): must be true to actually delete
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `csdn_status` (~180 tokens)

Read the CSDN blog backend for this site: the draft box and the published list (blog/phoenix/console/v1/article/list with status=draft / all_v2). Read-only. Requires CSDN_COOKIE in the site .env. Use it to verify before publishing (idempotency) and to see what is already on CSDN. page is 1-based (default 1). An expired login is surfaced as cookieExpired=true + code 401 instead of an empty list — renew CSDN_COOKIE then. This is the CSDN counterpart of juejin_status.

Input parameters:

- `page` (number): 1-based page index (default 1)
- `pageSize` (number): page size (default 20)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `csdn_article_delete` (~174 tokens)

Delete a CSDN article (blog/phoenix/console/v1/article/del). IRREVERSIBLE — pass confirm=true only after verifying the article id via csdn_status. Requires CSDN_COOKIE in the site .env. NOTE: this removes it from CSDN only; the local channel_plan publish log (the CSDN publishing calendar) is not touched — re-run channel_plan_reconcile(platform=csdn) afterwards if you want the log back in sync.

Input parameters:

- `articleId` (string, required): article id from csdn_status items[].id
- `confirm` (boolean, required): must be true to actually delete
- `deep` (boolean): also purge from the recycle bin (default false)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `channel_plan_reconcile` (~206 tokens)

Reconcile a platform channel_plan with what is ACTUALLY published on that platform (platform=juejin | csdn). This SEEDS the publish log with already-published articles — we do NOT bulk-import the blog plan (the table only logs real outcomes, so each platform keeps its own calendar). Pass map[] of {slug, title?, blogOrder?} for the explicitly-known published articles (recommended — avoids API rate limits), or omit map to live-read the platform published list and match titles back to blog slugs. Idempotent (upsert by slug). Run this once per platform before the first channel_plan_next so the already-published articles are skipped instead of re-published.

Input parameters:

- `map` (array): explicit known published articles; omit to live-read the platform
- `platform` (string, required): platform key, e.g. juejin or csdn
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `channel_taxonomy_sync` (~218 tokens)

Refresh the cached dictionary of an EXTERNAL platform's own taxonomy (category + tag id/name) into the workspace table channel_taxonomy. Currently juejin: 8 categories + ~725 tags fetched from the platform APIs. The dictionary is PLATFORM-scoped, shared by every site — a site publishing to Juejin needs NO mapping file of its own. It is used to map our article_plan category/tags onto the platform's category_id/tag_ids at publish time. Idempotent (upsert by platform id); publish auto-runs this once when the dictionary is empty, so calling it manually is only needed to refresh after a platform word-list change. Names are indexed for exact/prefix lookup (infix search runs in memory). Requires JUEJIN_COOKIE in the site .env. Read-only on the platform — it fetches lists, it does not publish.

Input parameters:

- `platform` (string): platform key (default juejin)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `channel_taxonomy_list` (~189 tokens)

Read the cached external-platform taxonomy dictionary (channel_taxonomy) — the platform's OWN categories and tags with their ids. Filter by kind (category|tag); look one up exactly with name=, or by prefix with prefix= (prefix lookups use the NOCASE index; infix/contains search is NOT index-accelerated, so do it client-side over this list). Use it to see which platform tags exist before choosing an alias.

Input parameters:

- `kind` (string): category | tag (omit for both)
- `limit` (number): max rows (default 100)
- `name` (string): exact name lookup (case-insensitive)
- `platform` (string): platform key (default juejin)
- `prefix` (string): name prefix lookup, e.g. "搜索"
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `channel_taxonomy_resolve` (~178 tokens)

Preview how OUR taxonomy maps onto a platform's (article_plan.category/tags → platform category_id/tag_ids) WITHOUT publishing. Returns the chosen ids/names plus a per-item origin showing which fallback layer fired (alias / exact / token:<kw> / fuzzy / default / env / first / hardcoded). Use it to sanity-check a mapping before a real publish; it never makes external calls and never writes.

Input parameters:

- `category` (string): our category slug, e.g. geo-ai-search
- `keywords` (array): extra keywords (article target_keywords)
- `platform` (string): platform key (default juejin)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `tags` (array): our tag slugs, e.g. ["geo-seo","search-system"]

### `wechat_status` (~96 tokens)

Read the WeChat official-account backend: the draft box (draft/batchget — media_id + titles) and the mass-sent list (freepublish/batchget — article_id + titles). Read-only. Requires WECHAT_APP_ID/SECRET in the site .env and the current outbound IP whitelisted in mp.weixin.qq.com.

Input parameters:

- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `wechat_stats` (~348 tokens)

Read official-account statistics from the WeChat backend (datacube/*, served WITHOUT the /cgi-bin/ prefix — the /cgi-bin/datacube/* variant is blocked by the egress proxy in this environment). Returns: user growth (user_summary), cumulative users (user_cumulate), upstream/interactive messages (upstream_msg, maxSpan 30d), interface quality (interface_summary, 30d), account biz summary (biz_summary, 30d), daily article reads (article_read, single-day), shares (article_share, single-day) and per-article detail incl. 送达率/读完率/平均阅读时长/跳出 (article_detail, single-day). Read-only. Constraints: data is T+1 (end_date defaults to yesterday); most endpoints allow a <=7d window, the *_read/_share/_detail ones require begin_date === end_date. Requires 用户分析/图文分析 permissions (认证公众号); an unauthorised account returns errcode 48001. Legacy endpoints getarticletotal/getuserread/getusershare are OFFLINE (47009) and have been replaced by the new “发表内容” APIs above.

Input parameters:

- `action` (string): report to read; default overview = user_summary + user_cumulate + biz_summary + upstream_msg in one 7-day window
- `begin_date` (string): YYYY-MM-DD; default = 6 days before end_date
- `end_date` (string): YYYY-MM-DD; default = yesterday (data is T+1, today is never available)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `wechat_sync_progress` (~131 tokens)

Reconcile the channel_plan calendar with the real WeChat backend and auto-fix the DB: draft rows whose media_id is missing from the draft box and not found in the publish list are rolled back to todo (draft_ids cleared); draft rows whose titles appear in the publish list are upgraded to published. Published rows are never downgraded. Returns the reconciliation report. dryRun=true previews without writing.

Input parameters:

- `dryRun` (boolean): preview only — compute and report, do not write to the DB
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `wechat_mass_preview` (~146 tokens)

Send a draft (media_id from the draft box) to one user as a preview (message/mass/preview). Requires a verified account; pass openid (touser) or wxname (towxname). Use this to check layout before mass-sending.

Input parameters:

- `dryRun` (boolean): print the payload, do not send
- `media_id` (string, required): draft box media_id (see wechat_status drafts)
- `openid` (string): receiver openid
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `wxname` (string): receiver wxname (user must have interacted with the account)

### `wechat_mass_send` (~237 tokens)

Mass-send (PUSH to followers) a draft via message/mass/sendall (all followers or one tag) or message/mass/send (specific openids). Subscription accounts get 1 mass-send per day. After a successful send the draft is consumed (auto-deleted from the draft box). If 风险操作保护 is on, the admin must confirm in mp.weixin.qq.com before it really goes out. Safety: execution requires confirm="YES"; dryRun=true only prints the payload.

Input parameters:

- `client_msg_id` (string): clientmsgid — de-duplicates repeated sends
- `confirm` (string): must be "YES" to actually push to followers (not needed for dryRun)
- `dryRun` (boolean): print the payload, do not send
- `media_id` (string, required): draft box media_id (see wechat_status drafts)
- `site` (string): site key; auto-selected when the workspace holds exactly one site
- `tag_id` (number): send to one user tag only (omit = all followers)
- `to_users` (array): specific openids (message/mass/send)

### `wechat_mass_status` (~62 tokens)

Query a mass-send task status (message/mass/get, msg_id from wechat_mass_send).

Input parameters:

- `msg_id` (number, required): mass-send task msg_id
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `wechat_article_delete` (~156 tokens)

Delete a published article (freepublish/delete, article_id from wechat_status published). IRREVERSIBLE — verify the article_id first; index (1-based) deletes one article of a multi-article message, omit to delete the whole message.

Input parameters:

- `article_id` (string, required): published article_id (see wechat_status published)
- `confirm` (string): must be "YES" to actually delete (irreversible); not needed for dryRun
- `dryRun` (boolean): print the payload, do not delete
- `index` (number): 1-based position within the message; omit to delete the whole message
- `site` (string): site key; auto-selected when the workspace holds exactly one site

### `wechat_draft_publish` (~129 tokens)

Publish a draft box media_id to the account homepage via freepublish/submit (NO push to followers — the draft is consumed and moves to the published list, where it is visible in the account homepage). Use for the "发布" step; mass push (推送粉丝) is a separate tool wechat_mass_send.

Input parameters:

- `dryRun` (boolean): print the payload, do not publish
- `media_id` (string, required): draft box media_id (see wechat_status drafts)
- `site` (string): site key; auto-selected when the workspace holds exactly one site

## Diagnostics

Captured diagnostic sections: Provenance, Vulnerabilities, Dependencies. The full working is on the page: https://verifymcp.io/servers/tengence-team-geo/tengence-geo-mcp#diagnostics

## Score history

- 2026-10-04: 77
- 2026-10-03: 77
- 2026-10-02: 77
- 2026-10-01: 77
- 2026-09-30: 77
- 2026-09-29: 77
- 2026-09-28: 77
- 2026-09-27: 62

## Common questions

### What is the Tengence GEO Agent MCP server?

Tengence GEO Agent is an MCP server listed in the public MCP registry as io.github.tengence-team/geo. GEO/SEO content pipeline MCP: plan, write, gate-check, publish, submit, monitor AI visibility. This page covers its npm package (@tengence/geo-mcp).

### Is the Tengence GEO Agent MCP server safe to use?

Tengence GEO Agent scores 77 out of 100 on VerifyMCP. We recorded 13 known advisories against it as of 4 October 2026. It declares no install or post-install scripts. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Tengence GEO Agent MCP server expose?

Tengence GEO Agent exposes 56 tools: workspace_use, site_init, site_list, site_status, article_list, and 51 more. Their descriptions and schemas cost roughly 7,333 tokens of context every time the server is loaded.

### Is the Tengence GEO Agent MCP server still maintained?

Tengence GEO Agent is still listed as active in the MCP registry. We last reached this channel on 4 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Tengence GEO Agent MCP server under?

Tengence GEO Agent declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/@tengence/geo-mcp
- Socket report: https://socket.dev/npm/package/@tengence/geo-mcp
- Repository: https://github.com/tengence-team/tengence-geo-agent
- Changelog RSS feed: https://verifymcp.io/servers/tengence-team-geo/tengence-geo-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/tengence-team-geo/tengence-geo-mcp.json
- HTML version of this page: https://verifymcp.io/servers/tengence-team-geo/tengence-geo-mcp
