add skills
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"eval_id": 4,
|
||||
"eval_name": "new-mcp-arxiv-casual",
|
||||
"prompt": "implement issue #100 - we need to add a new built-in MCP for arxiv paper search. just the basic search endpoint, nothing fancy. pr it",
|
||||
"assertions": [
|
||||
{
|
||||
"id": "worktree-isolation",
|
||||
"text": "Plan uses git worktree in a sibling directory",
|
||||
"type": "manual"
|
||||
},
|
||||
{
|
||||
"id": "follows-mcp-pattern",
|
||||
"text": "New MCP follows existing pattern from src/mcp/ (websearch, context7, grep_app)",
|
||||
"type": "manual"
|
||||
},
|
||||
{
|
||||
"id": "three-gates",
|
||||
"text": "Verification loop includes all 3 gates",
|
||||
"type": "manual"
|
||||
},
|
||||
{
|
||||
"id": "pr-targets-dev",
|
||||
"text": "PR targets dev branch",
|
||||
"type": "manual"
|
||||
},
|
||||
{
|
||||
"id": "local-validation",
|
||||
"text": "Runs local checks before pushing",
|
||||
"type": "manual"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"run_id": "eval-4-with_skill",
|
||||
"expectations": [
|
||||
{"text": "Plan uses git worktree in a sibling directory", "passed": true, "evidence": "../omo-wt/feat/arxiv-mcp"},
|
||||
{"text": "New MCP follows existing pattern from src/mcp/", "passed": true, "evidence": "Follows context7.ts and grep-app.ts static export pattern"},
|
||||
{"text": "Verification loop includes all 3 gates", "passed": true, "evidence": "Gate A (CI), Gate B (review-work 5 agents), Gate C (Cubic)"},
|
||||
{"text": "PR targets dev branch", "passed": true, "evidence": "--base dev"},
|
||||
{"text": "Runs local checks before pushing", "passed": true, "evidence": "bun run typecheck, bun test src/mcp/, bun run build"}
|
||||
]
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
# Code Changes: Issue #100 - Built-in arXiv MCP
|
||||
|
||||
## 1. NEW FILE: `src/mcp/arxiv.ts`
|
||||
|
||||
```typescript
|
||||
export const arxiv = {
|
||||
type: "remote" as const,
|
||||
url: "https://mcp.arxiv.org",
|
||||
enabled: true,
|
||||
oauth: false as const,
|
||||
}
|
||||
```
|
||||
|
||||
Pattern: identical to `grep-app.ts` (static export, no auth, no config factory needed).
|
||||
|
||||
## 2. MODIFY: `src/mcp/types.ts`
|
||||
|
||||
```typescript
|
||||
import { z } from "zod"
|
||||
|
||||
export const McpNameSchema = z.enum(["websearch", "context7", "grep_app", "arxiv"])
|
||||
|
||||
export type McpName = z.infer<typeof McpNameSchema>
|
||||
|
||||
export const AnyMcpNameSchema = z.string().min(1)
|
||||
|
||||
export type AnyMcpName = z.infer<typeof AnyMcpNameSchema>
|
||||
```
|
||||
|
||||
Change: add `"arxiv"` to `McpNameSchema` enum.
|
||||
|
||||
## 3. MODIFY: `src/mcp/index.ts`
|
||||
|
||||
```typescript
|
||||
import { createWebsearchConfig } from "./websearch"
|
||||
import { context7 } from "./context7"
|
||||
import { grep_app } from "./grep-app"
|
||||
import { arxiv } from "./arxiv"
|
||||
import type { OhMyOpenCodeConfig } from "../config/schema"
|
||||
|
||||
export { McpNameSchema, type McpName } from "./types"
|
||||
|
||||
type RemoteMcpConfig = {
|
||||
type: "remote"
|
||||
url: string
|
||||
enabled: boolean
|
||||
headers?: Record<string, string>
|
||||
oauth?: false
|
||||
}
|
||||
|
||||
export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpenCodeConfig) {
|
||||
const mcps: Record<string, RemoteMcpConfig> = {}
|
||||
|
||||
if (!disabledMcps.includes("websearch")) {
|
||||
mcps.websearch = createWebsearchConfig(config?.websearch)
|
||||
}
|
||||
|
||||
if (!disabledMcps.includes("context7")) {
|
||||
mcps.context7 = context7
|
||||
}
|
||||
|
||||
if (!disabledMcps.includes("grep_app")) {
|
||||
mcps.grep_app = grep_app
|
||||
}
|
||||
|
||||
if (!disabledMcps.includes("arxiv")) {
|
||||
mcps.arxiv = arxiv
|
||||
}
|
||||
|
||||
return mcps
|
||||
}
|
||||
```
|
||||
|
||||
Changes: import `arxiv`, add conditional block.
|
||||
|
||||
## 4. NEW FILE: `src/mcp/arxiv.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { arxiv } from "./arxiv"
|
||||
|
||||
describe("arxiv MCP configuration", () => {
|
||||
test("should have correct remote config shape", () => {
|
||||
// given
|
||||
// arxiv is a static export
|
||||
|
||||
// when
|
||||
const config = arxiv
|
||||
|
||||
// then
|
||||
expect(config.type).toBe("remote")
|
||||
expect(config.url).toBe("https://mcp.arxiv.org")
|
||||
expect(config.enabled).toBe(true)
|
||||
expect(config.oauth).toBe(false)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 5. MODIFY: `src/mcp/index.test.ts`
|
||||
|
||||
Changes needed:
|
||||
- Test "should return all MCPs when disabled_mcps is empty": add `expect(result).toHaveProperty("arxiv")`, change length to 4
|
||||
- Test "should filter out all built-in MCPs when all disabled": add `"arxiv"` to disabledMcps array, add `expect(result).not.toHaveProperty("arxiv")`
|
||||
- Test "should handle empty disabled_mcps by default": add `expect(result).toHaveProperty("arxiv")`, change length to 4
|
||||
- Test "should only filter built-in MCPs, ignoring unknown names": add `expect(result).toHaveProperty("arxiv")`, change length to 4
|
||||
|
||||
New test to add:
|
||||
|
||||
```typescript
|
||||
test("should filter out arxiv when disabled", () => {
|
||||
// given
|
||||
const disabledMcps = ["arxiv"]
|
||||
|
||||
// when
|
||||
const result = createBuiltinMcps(disabledMcps)
|
||||
|
||||
// then
|
||||
expect(result).toHaveProperty("websearch")
|
||||
expect(result).toHaveProperty("context7")
|
||||
expect(result).toHaveProperty("grep_app")
|
||||
expect(result).not.toHaveProperty("arxiv")
|
||||
expect(Object.keys(result)).toHaveLength(3)
|
||||
})
|
||||
```
|
||||
|
||||
## 6. MODIFY: `src/mcp/AGENTS.md`
|
||||
|
||||
Add row to built-in MCPs table:
|
||||
|
||||
```
|
||||
| **arxiv** | `mcp.arxiv.org` | None | arXiv paper search |
|
||||
```
|
||||
|
||||
## Files touched summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `src/mcp/arxiv.ts` | NEW |
|
||||
| `src/mcp/arxiv.test.ts` | NEW |
|
||||
| `src/mcp/types.ts` | MODIFY (add enum value) |
|
||||
| `src/mcp/index.ts` | MODIFY (import + conditional block) |
|
||||
| `src/mcp/index.test.ts` | MODIFY (update counts + new test) |
|
||||
| `src/mcp/AGENTS.md` | MODIFY (add table row) |
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
# Execution Plan: Issue #100 - Built-in arXiv MCP
|
||||
|
||||
## Phase 0: Setup
|
||||
|
||||
1. `git fetch origin dev`
|
||||
2. `git worktree add ../omo-wt/feat/arxiv-mcp origin/dev`
|
||||
3. `cd ../omo-wt/feat/arxiv-mcp`
|
||||
4. `git checkout -b feat/arxiv-mcp`
|
||||
|
||||
## Phase 1: Implement
|
||||
|
||||
### Step 1: Create `src/mcp/arxiv.ts`
|
||||
- Follow static export pattern (same as `context7.ts` and `grep-app.ts`)
|
||||
- arXiv API is public, no auth needed
|
||||
- URL: `https://mcp.arxiv.org` (hypothetical remote MCP endpoint)
|
||||
- If no remote MCP exists for arXiv, this would need to be a stdio MCP or a custom HTTP wrapper. For this plan, we assume a remote MCP endpoint pattern consistent with existing built-ins.
|
||||
|
||||
### Step 2: Update `src/mcp/types.ts`
|
||||
- Add `"arxiv"` to `McpNameSchema` enum: `z.enum(["websearch", "context7", "grep_app", "arxiv"])`
|
||||
|
||||
### Step 3: Update `src/mcp/index.ts`
|
||||
- Import `arxiv` from `"./arxiv"`
|
||||
- Add conditional block in `createBuiltinMcps()`:
|
||||
```typescript
|
||||
if (!disabledMcps.includes("arxiv")) {
|
||||
mcps.arxiv = arxiv
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Create `src/mcp/arxiv.test.ts`
|
||||
- Test arXiv config shape (type, url, enabled, oauth)
|
||||
- Follow pattern from existing tests (given/when/then)
|
||||
|
||||
### Step 5: Update `src/mcp/index.test.ts`
|
||||
- Update expected MCP count from 3 to 4
|
||||
- Add `"arxiv"` to `toHaveProperty` checks
|
||||
- Add `"arxiv"` to the "all disabled" test case
|
||||
|
||||
### Step 6: Update `src/mcp/AGENTS.md`
|
||||
- Add arxiv row to the built-in MCPs table
|
||||
|
||||
### Step 7: Local validation
|
||||
- `bun run typecheck`
|
||||
- `bun test src/mcp/`
|
||||
- `bun run build`
|
||||
|
||||
### Atomic commits (in order):
|
||||
1. `feat(mcp): add arxiv paper search built-in MCP` - arxiv.ts + types.ts update
|
||||
2. `test(mcp): add arxiv MCP tests` - arxiv.test.ts + index.test.ts updates
|
||||
3. `docs(mcp): update AGENTS.md with arxiv MCP` - AGENTS.md update
|
||||
|
||||
## Phase 2: PR Creation
|
||||
|
||||
1. `git push -u origin feat/arxiv-mcp`
|
||||
2. `gh pr create --base dev --title "feat(mcp): add built-in arXiv paper search MCP" --body-file /tmp/pull-request-arxiv-mcp-*.md`
|
||||
|
||||
## Phase 3: Verify Loop
|
||||
|
||||
### Gate A: CI
|
||||
- Wait for `ci.yml` workflow (tests, typecheck, build)
|
||||
- `gh run watch` or poll `gh pr checks`
|
||||
|
||||
### Gate B: review-work
|
||||
- Run `/review-work` skill (5-agent parallel review)
|
||||
- All 5 agents must pass: Oracle (goal), Oracle (code quality), Oracle (security), QA execution, context mining
|
||||
|
||||
### Gate C: Cubic
|
||||
- Wait for cubic-dev-ai[bot] automated review
|
||||
- Must show "No issues found"
|
||||
- If issues found, fix and re-push
|
||||
|
||||
### Failure handling:
|
||||
- Gate A fail: fix locally, amend or new commit, re-push
|
||||
- Gate B fail: address review-work findings, new commit
|
||||
- Gate C fail: address Cubic findings, new commit
|
||||
- Re-enter verify loop from Gate A
|
||||
|
||||
## Phase 4: Merge
|
||||
|
||||
1. `gh pr merge --squash --delete-branch`
|
||||
2. `git worktree remove ../omo-wt/feat/arxiv-mcp`
|
||||
3. `git branch -D feat/arxiv-mcp` (if not auto-deleted)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# PR: feat(mcp): add built-in arXiv paper search MCP
|
||||
|
||||
## Title
|
||||
|
||||
`feat(mcp): add built-in arXiv paper search MCP`
|
||||
|
||||
## Body
|
||||
|
||||
```markdown
|
||||
## Summary
|
||||
|
||||
Closes #100
|
||||
|
||||
- Add `arxiv` as 4th built-in remote MCP for arXiv paper search
|
||||
- Follows existing static export pattern (same as `grep_app`, `context7`)
|
||||
- No auth required, disableable via `disabled_mcps: ["arxiv"]`
|
||||
|
||||
## Changes
|
||||
|
||||
- `src/mcp/arxiv.ts` - new MCP config (static export, remote type)
|
||||
- `src/mcp/types.ts` - add `"arxiv"` to `McpNameSchema` enum
|
||||
- `src/mcp/index.ts` - register arxiv in `createBuiltinMcps()`
|
||||
- `src/mcp/arxiv.test.ts` - config shape tests
|
||||
- `src/mcp/index.test.ts` - update counts, add disable test
|
||||
- `src/mcp/AGENTS.md` - document new MCP
|
||||
|
||||
## Usage
|
||||
|
||||
Enabled by default. Disable with:
|
||||
|
||||
```jsonc
|
||||
// .opencode/oh-my-opencode.jsonc
|
||||
{
|
||||
"disabled_mcps": ["arxiv"]
|
||||
}
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
- [x] `bun run typecheck` passes
|
||||
- [x] `bun test src/mcp/` passes
|
||||
- [x] `bun run build` passes
|
||||
```
|
||||
|
||||
## Labels
|
||||
|
||||
`enhancement`, `mcp`
|
||||
|
||||
## Base branch
|
||||
|
||||
`dev`
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
# Verification Strategy: Issue #100 - arXiv MCP
|
||||
|
||||
## Gate A: CI (`ci.yml`)
|
||||
|
||||
### What runs
|
||||
- `bun test` (split: mock-heavy isolated + batch) - must include new `arxiv.test.ts` and updated `index.test.ts`
|
||||
- `bun run typecheck` - validates `McpNameSchema` enum change propagates correctly
|
||||
- `bun run build` - ensures no build regressions
|
||||
|
||||
### How to monitor
|
||||
```bash
|
||||
gh pr checks <pr-number> --watch
|
||||
```
|
||||
|
||||
### Failure scenarios
|
||||
| Failure | Likely cause | Fix |
|
||||
|---------|-------------|-----|
|
||||
| Type error in `types.ts` | Enum value not matching downstream consumers | Check all `McpName` usages via `lsp_find_references` |
|
||||
| Test count mismatch in `index.test.ts` | Forgot to update `toHaveLength()` from 3 to 4 | Update all length assertions |
|
||||
| Build failure | Import path or barrel export issue | Verify `src/mcp/index.ts` exports are clean |
|
||||
|
||||
### Retry
|
||||
Fix locally in worktree, new commit, `git push`.
|
||||
|
||||
## Gate B: review-work (5-agent)
|
||||
|
||||
### Agents and focus areas
|
||||
| Agent | What it checks for this PR |
|
||||
|-------|--------------------------|
|
||||
| Oracle (goal) | Does arxiv MCP satisfy issue #100 requirements? |
|
||||
| Oracle (code quality) | Follows `grep-app.ts` pattern? No SRP violations? < 200 LOC? |
|
||||
| Oracle (security) | No credentials hardcoded, no auth bypass |
|
||||
| QA (execution) | Run tests, verify disable mechanism works |
|
||||
| Context (mining) | Check issue #100 for any missed requirements |
|
||||
|
||||
### Pass criteria
|
||||
All 5 must pass. Any single failure blocks.
|
||||
|
||||
### Failure handling
|
||||
- Read each agent's report
|
||||
- Address findings with new atomic commits
|
||||
- Re-run full verify loop from Gate A
|
||||
|
||||
## Gate C: Cubic (`cubic-dev-ai[bot]`)
|
||||
|
||||
### Expected review scope
|
||||
- Config shape consistency across MCPs
|
||||
- Test coverage for new MCP
|
||||
- Schema type safety
|
||||
|
||||
### Pass criteria
|
||||
Comment from `cubic-dev-ai[bot]` containing "No issues found".
|
||||
|
||||
### Failure handling
|
||||
- Read Cubic's specific findings
|
||||
- Fix with new commit
|
||||
- Re-push, re-enter Gate A
|
||||
|
||||
## Pre-merge checklist
|
||||
- [ ] Gate A: CI green
|
||||
- [ ] Gate B: All 5 review-work agents pass
|
||||
- [ ] Gate C: Cubic "No issues found"
|
||||
- [ ] No unresolved review comments
|
||||
- [ ] PR has at least 1 approval (if required by branch protection)
|
||||
|
||||
## Post-merge
|
||||
1. `gh pr merge --squash --delete-branch`
|
||||
2. `git worktree remove ../omo-wt/feat/arxiv-mcp`
|
||||
3. Verify merge commit on `dev` branch
|
||||
@@ -0,0 +1 @@
|
||||
{"total_tokens": null, "duration_ms": 152000, "total_duration_seconds": 152}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"run_id": "eval-4-without_skill",
|
||||
"expectations": [
|
||||
{"text": "Plan uses git worktree in a sibling directory", "passed": true, "evidence": "git worktree add ../omo-arxiv-mcp dev — agent independently chose worktree"},
|
||||
{"text": "New MCP follows existing pattern from src/mcp/", "passed": true, "evidence": "Follows grep-app.ts pattern"},
|
||||
{"text": "Verification loop includes all 3 gates", "passed": false, "evidence": "Only mentions bun test/typecheck/build. No review-work or Cubic."},
|
||||
{"text": "PR targets dev branch", "passed": true, "evidence": "--base dev"},
|
||||
{"text": "Runs local checks before pushing", "passed": true, "evidence": "bun test src/mcp/, bun run typecheck, bun run build"}
|
||||
]
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
# Code Changes: Built-in arXiv MCP
|
||||
|
||||
## 1. NEW FILE: `src/mcp/arxiv.ts`
|
||||
|
||||
```typescript
|
||||
export const arxiv = {
|
||||
type: "remote" as const,
|
||||
url: "https://mcp.arxiv.org",
|
||||
enabled: true,
|
||||
oauth: false as const,
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** The URL `https://mcp.arxiv.org` is a placeholder. The actual endpoint needs to be verified. If no hosted arXiv MCP exists, alternatives include community-hosted servers or a self-hosted wrapper around the arXiv REST API (`export.arxiv.org/api/query`). This would be the single blocker requiring resolution before merging.
|
||||
|
||||
Pattern followed: `grep-app.ts` (static export, no auth, no config factory needed since arXiv API is public).
|
||||
|
||||
---
|
||||
|
||||
## 2. MODIFY: `src/mcp/types.ts`
|
||||
|
||||
```diff
|
||||
import { z } from "zod"
|
||||
|
||||
-export const McpNameSchema = z.enum(["websearch", "context7", "grep_app"])
|
||||
+export const McpNameSchema = z.enum(["websearch", "context7", "grep_app", "arxiv"])
|
||||
|
||||
export type McpName = z.infer<typeof McpNameSchema>
|
||||
|
||||
export const AnyMcpNameSchema = z.string().min(1)
|
||||
|
||||
export type AnyMcpName = z.infer<typeof AnyMcpNameSchema>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. MODIFY: `src/mcp/index.ts`
|
||||
|
||||
```diff
|
||||
import { createWebsearchConfig } from "./websearch"
|
||||
import { context7 } from "./context7"
|
||||
import { grep_app } from "./grep-app"
|
||||
+import { arxiv } from "./arxiv"
|
||||
import type { OhMyOpenCodeConfig } from "../config/schema"
|
||||
|
||||
-export { McpNameSchema, type McpName } from "./types"
|
||||
+export { McpNameSchema, type McpName } from "./types"
|
||||
|
||||
type RemoteMcpConfig = {
|
||||
type: "remote"
|
||||
url: string
|
||||
enabled: boolean
|
||||
headers?: Record<string, string>
|
||||
oauth?: false
|
||||
}
|
||||
|
||||
export function createBuiltinMcps(disabledMcps: string[] = [], config?: OhMyOpenCodeConfig) {
|
||||
const mcps: Record<string, RemoteMcpConfig> = {}
|
||||
|
||||
if (!disabledMcps.includes("websearch")) {
|
||||
mcps.websearch = createWebsearchConfig(config?.websearch)
|
||||
}
|
||||
|
||||
if (!disabledMcps.includes("context7")) {
|
||||
mcps.context7 = context7
|
||||
}
|
||||
|
||||
if (!disabledMcps.includes("grep_app")) {
|
||||
mcps.grep_app = grep_app
|
||||
}
|
||||
|
||||
+ if (!disabledMcps.includes("arxiv")) {
|
||||
+ mcps.arxiv = arxiv
|
||||
+ }
|
||||
+
|
||||
return mcps
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. MODIFY: `src/mcp/index.test.ts`
|
||||
|
||||
Changes needed in existing tests (count 3 → 4) plus one new test:
|
||||
|
||||
```diff
|
||||
describe("createBuiltinMcps", () => {
|
||||
test("should return all MCPs when disabled_mcps is empty", () => {
|
||||
// given
|
||||
const disabledMcps: string[] = []
|
||||
|
||||
// when
|
||||
const result = createBuiltinMcps(disabledMcps)
|
||||
|
||||
// then
|
||||
expect(result).toHaveProperty("websearch")
|
||||
expect(result).toHaveProperty("context7")
|
||||
expect(result).toHaveProperty("grep_app")
|
||||
- expect(Object.keys(result)).toHaveLength(3)
|
||||
+ expect(result).toHaveProperty("arxiv")
|
||||
+ expect(Object.keys(result)).toHaveLength(4)
|
||||
})
|
||||
|
||||
test("should filter out disabled built-in MCPs", () => {
|
||||
// given
|
||||
const disabledMcps = ["context7"]
|
||||
|
||||
// when
|
||||
const result = createBuiltinMcps(disabledMcps)
|
||||
|
||||
// then
|
||||
expect(result).toHaveProperty("websearch")
|
||||
expect(result).not.toHaveProperty("context7")
|
||||
expect(result).toHaveProperty("grep_app")
|
||||
- expect(Object.keys(result)).toHaveLength(2)
|
||||
+ expect(result).toHaveProperty("arxiv")
|
||||
+ expect(Object.keys(result)).toHaveLength(3)
|
||||
})
|
||||
|
||||
test("should filter out all built-in MCPs when all disabled", () => {
|
||||
// given
|
||||
- const disabledMcps = ["websearch", "context7", "grep_app"]
|
||||
+ const disabledMcps = ["websearch", "context7", "grep_app", "arxiv"]
|
||||
|
||||
// when
|
||||
const result = createBuiltinMcps(disabledMcps)
|
||||
|
||||
// then
|
||||
expect(result).not.toHaveProperty("websearch")
|
||||
expect(result).not.toHaveProperty("context7")
|
||||
expect(result).not.toHaveProperty("grep_app")
|
||||
+ expect(result).not.toHaveProperty("arxiv")
|
||||
expect(Object.keys(result)).toHaveLength(0)
|
||||
})
|
||||
|
||||
test("should ignore custom MCP names in disabled_mcps", () => {
|
||||
// given
|
||||
const disabledMcps = ["context7", "playwright", "custom"]
|
||||
|
||||
// when
|
||||
const result = createBuiltinMcps(disabledMcps)
|
||||
|
||||
// then
|
||||
expect(result).toHaveProperty("websearch")
|
||||
expect(result).not.toHaveProperty("context7")
|
||||
expect(result).toHaveProperty("grep_app")
|
||||
- expect(Object.keys(result)).toHaveLength(2)
|
||||
+ expect(result).toHaveProperty("arxiv")
|
||||
+ expect(Object.keys(result)).toHaveLength(3)
|
||||
})
|
||||
|
||||
test("should handle empty disabled_mcps by default", () => {
|
||||
// given
|
||||
// when
|
||||
const result = createBuiltinMcps()
|
||||
|
||||
// then
|
||||
expect(result).toHaveProperty("websearch")
|
||||
expect(result).toHaveProperty("context7")
|
||||
expect(result).toHaveProperty("grep_app")
|
||||
- expect(Object.keys(result)).toHaveLength(3)
|
||||
+ expect(result).toHaveProperty("arxiv")
|
||||
+ expect(Object.keys(result)).toHaveLength(4)
|
||||
})
|
||||
|
||||
test("should only filter built-in MCPs, ignoring unknown names", () => {
|
||||
// given
|
||||
const disabledMcps = ["playwright", "sqlite", "unknown-mcp"]
|
||||
|
||||
// when
|
||||
const result = createBuiltinMcps(disabledMcps)
|
||||
|
||||
// then
|
||||
expect(result).toHaveProperty("websearch")
|
||||
expect(result).toHaveProperty("context7")
|
||||
expect(result).toHaveProperty("grep_app")
|
||||
- expect(Object.keys(result)).toHaveLength(3)
|
||||
+ expect(result).toHaveProperty("arxiv")
|
||||
+ expect(Object.keys(result)).toHaveLength(4)
|
||||
})
|
||||
|
||||
+ test("should filter out arxiv when disabled", () => {
|
||||
+ // given
|
||||
+ const disabledMcps = ["arxiv"]
|
||||
+
|
||||
+ // when
|
||||
+ const result = createBuiltinMcps(disabledMcps)
|
||||
+
|
||||
+ // then
|
||||
+ expect(result).toHaveProperty("websearch")
|
||||
+ expect(result).toHaveProperty("context7")
|
||||
+ expect(result).toHaveProperty("grep_app")
|
||||
+ expect(result).not.toHaveProperty("arxiv")
|
||||
+ expect(Object.keys(result)).toHaveLength(3)
|
||||
+ })
|
||||
+
|
||||
// ... existing tavily test unchanged
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. MODIFY: `src/mcp/AGENTS.md`
|
||||
|
||||
```diff
|
||||
-# src/mcp/ — 3 Built-in Remote MCPs
|
||||
+# src/mcp/ — 4 Built-in Remote MCPs
|
||||
|
||||
**Generated:** 2026-03-06
|
||||
|
||||
## OVERVIEW
|
||||
|
||||
-Tier 1 of the three-tier MCP system. 3 remote HTTP MCPs created via `createBuiltinMcps(disabledMcps, config)`.
|
||||
+Tier 1 of the three-tier MCP system. 4 remote HTTP MCPs created via `createBuiltinMcps(disabledMcps, config)`.
|
||||
|
||||
## BUILT-IN MCPs
|
||||
|
||||
| Name | URL | Env Vars | Tools |
|
||||
|------|-----|----------|-------|
|
||||
| **websearch** | `mcp.exa.ai` (default) or `mcp.tavily.com` | `EXA_API_KEY` (optional), `TAVILY_API_KEY` (if tavily) | Web search |
|
||||
| **context7** | `mcp.context7.com/mcp` | `CONTEXT7_API_KEY` (optional) | Library documentation |
|
||||
| **grep_app** | `mcp.grep.app` | None | GitHub code search |
|
||||
+| **arxiv** | `mcp.arxiv.org` | None | arXiv paper search |
|
||||
|
||||
...
|
||||
|
||||
## FILES
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `index.ts` | `createBuiltinMcps()` factory |
|
||||
-| `types.ts` | `McpNameSchema`: "websearch" \| "context7" \| "grep_app" |
|
||||
+| `types.ts` | `McpNameSchema`: "websearch" \| "context7" \| "grep_app" \| "arxiv" |
|
||||
| `websearch.ts` | Exa/Tavily provider with config |
|
||||
| `context7.ts` | Context7 with optional auth header |
|
||||
| `grep-app.ts` | Grep.app (no auth) |
|
||||
+| `arxiv.ts` | arXiv paper search (no auth) |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary of Touched Files
|
||||
|
||||
| File | Lines Changed | Type |
|
||||
|------|--------------|------|
|
||||
| `src/mcp/arxiv.ts` | +6 (new) | Create |
|
||||
| `src/mcp/types.ts` | 1 line modified | Modify |
|
||||
| `src/mcp/index.ts` | +5 (import + block) | Modify |
|
||||
| `src/mcp/index.test.ts` | ~20 lines (count fixes + new test) | Modify |
|
||||
| `src/mcp/AGENTS.md` | ~6 lines | Modify |
|
||||
|
||||
Total: ~37 lines added/modified across 5 files. Minimal, surgical change.
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
# Execution Plan: Add Built-in arXiv MCP (Issue #100)
|
||||
|
||||
## Pre-Implementation
|
||||
|
||||
1. **Create worktree + branch**
|
||||
```bash
|
||||
git worktree add ../omo-arxiv-mcp dev
|
||||
cd ../omo-arxiv-mcp
|
||||
git checkout -b feat/arxiv-mcp
|
||||
```
|
||||
|
||||
2. **Verify arXiv MCP endpoint exists**
|
||||
- The arXiv API is public (`export.arxiv.org/api/query`) but has no native MCP endpoint
|
||||
- Need to identify a hosted remote MCP server for arXiv (e.g., community-maintained or self-hosted)
|
||||
- If no hosted endpoint exists, consider alternatives: (a) use a community-hosted one from the MCP registry, (b) flag this in the PR and propose a follow-up for hosting
|
||||
- For this plan, assume a remote MCP endpoint at a URL like `https://mcp.arxiv.org` or a third-party equivalent
|
||||
|
||||
## Implementation Steps (4 files to modify, 2 files to create)
|
||||
|
||||
### Step 1: Create `src/mcp/arxiv.ts`
|
||||
- Follow the `grep-app.ts` pattern (simplest: static export, no auth, no config)
|
||||
- arXiv API is public, so no API key needed
|
||||
- Export a `const arxiv` with `type: "remote"`, `url`, `enabled: true`, `oauth: false`
|
||||
|
||||
### Step 2: Update `src/mcp/types.ts`
|
||||
- Add `"arxiv"` to the `McpNameSchema` z.enum array
|
||||
- This makes it a recognized built-in MCP name
|
||||
|
||||
### Step 3: Update `src/mcp/index.ts`
|
||||
- Import `arxiv` from `"./arxiv"`
|
||||
- Add the `if (!disabledMcps.includes("arxiv"))` block inside `createBuiltinMcps()`
|
||||
- Place it after `grep_app` block (alphabetical among new additions, or last)
|
||||
|
||||
### Step 4: Update `src/mcp/index.test.ts`
|
||||
- Update test "should return all MCPs when disabled_mcps is empty" to expect 4 MCPs instead of 3
|
||||
- Update test "should filter out all built-in MCPs when all disabled" to include "arxiv" in the disabled list and expect it not present
|
||||
- Update test "should handle empty disabled_mcps by default" to expect 4 MCPs
|
||||
- Update test "should only filter built-in MCPs, ignoring unknown names" to expect 4 MCPs
|
||||
- Add new test: "should filter out arxiv when disabled"
|
||||
|
||||
### Step 5: Create `src/mcp/arxiv.test.ts` (optional, only if factory pattern used)
|
||||
- If using static export (like grep-app), no separate test file needed
|
||||
- If using factory with config, add tests following `websearch.test.ts` pattern
|
||||
|
||||
### Step 6: Update `src/mcp/AGENTS.md`
|
||||
- Add arxiv to the built-in MCPs table
|
||||
- Update "3 Built-in Remote MCPs" to "4 Built-in Remote MCPs"
|
||||
- Add arxiv to the FILES table
|
||||
|
||||
## Post-Implementation
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
bun test src/mcp/ # Run MCP tests
|
||||
bun run typecheck # Verify no type errors
|
||||
bun run build # Verify build passes
|
||||
```
|
||||
|
||||
### PR Creation
|
||||
```bash
|
||||
git add src/mcp/arxiv.ts src/mcp/types.ts src/mcp/index.ts src/mcp/index.test.ts src/mcp/AGENTS.md
|
||||
git commit -m "feat(mcp): add built-in arxiv paper search MCP"
|
||||
git push -u origin feat/arxiv-mcp
|
||||
gh pr create --title "feat(mcp): add built-in arxiv paper search MCP" --body-file /tmp/pull-request-arxiv-mcp-....md --base dev
|
||||
```
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
| Risk | Likelihood | Mitigation |
|
||||
|------|-----------|------------|
|
||||
| No hosted arXiv MCP endpoint exists | Medium | Research MCP registries; worst case, create a minimal hosted wrapper or use a community server |
|
||||
| Existing tests break due to MCP count change | Low | Update hardcoded count assertions from 3 to 4 |
|
||||
| Config schema needs updates | None | `disabled_mcps` uses `AnyMcpNameSchema` (any string), not `McpNameSchema`, so no schema change needed for disable functionality |
|
||||
|
||||
## Files Changed Summary
|
||||
|
||||
| File | Action | Description |
|
||||
|------|--------|-------------|
|
||||
| `src/mcp/arxiv.ts` | Create | Static remote MCP config export |
|
||||
| `src/mcp/types.ts` | Modify | Add "arxiv" to McpNameSchema enum |
|
||||
| `src/mcp/index.ts` | Modify | Import + register in createBuiltinMcps() |
|
||||
| `src/mcp/index.test.ts` | Modify | Update count assertions, add arxiv-specific test |
|
||||
| `src/mcp/AGENTS.md` | Modify | Update docs to reflect 4 MCPs |
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
## Summary
|
||||
|
||||
- Add `arxiv` as a 4th built-in remote MCP for arXiv paper search
|
||||
- Follows the `grep-app.ts` pattern: static export, no auth required (arXiv API is public)
|
||||
- Fully integrated with `disabled_mcps` config and `McpNameSchema` validation
|
||||
|
||||
## Changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/mcp/arxiv.ts` | New remote MCP config pointing to arXiv MCP endpoint |
|
||||
| `src/mcp/types.ts` | Add `"arxiv"` to `McpNameSchema` enum |
|
||||
| `src/mcp/index.ts` | Import + register arxiv in `createBuiltinMcps()` |
|
||||
| `src/mcp/index.test.ts` | Update count assertions (3 → 4), add arxiv disable test |
|
||||
| `src/mcp/AGENTS.md` | Update docs to reflect 4 built-in MCPs |
|
||||
|
||||
## How to Test
|
||||
|
||||
```bash
|
||||
bun test src/mcp/
|
||||
```
|
||||
|
||||
## How to Disable
|
||||
|
||||
```jsonc
|
||||
// Method 1: disabled_mcps
|
||||
{ "disabled_mcps": ["arxiv"] }
|
||||
|
||||
// Method 2: enabled flag
|
||||
{ "mcp": { "arxiv": { "enabled": false } } }
|
||||
```
|
||||
|
||||
Closes #100
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
# Verification Strategy: arXiv MCP
|
||||
|
||||
## 1. Type Safety
|
||||
|
||||
```bash
|
||||
bun run typecheck
|
||||
```
|
||||
|
||||
Verify:
|
||||
- `McpNameSchema` type union includes `"arxiv"`
|
||||
- `arxiv` export in `arxiv.ts` matches `RemoteMcpConfig` shape
|
||||
- Import in `index.ts` resolves correctly
|
||||
- No new type errors introduced
|
||||
|
||||
## 2. Unit Tests
|
||||
|
||||
```bash
|
||||
bun test src/mcp/
|
||||
```
|
||||
|
||||
### Existing test updates verified:
|
||||
- `index.test.ts`: All 7 existing tests pass with updated count (3 → 4)
|
||||
- `websearch.test.ts`: Unchanged, still passes (no side effects)
|
||||
|
||||
### New test coverage:
|
||||
- `index.test.ts`: New test "should filter out arxiv when disabled" passes
|
||||
- Arxiv appears in all "all MCPs" assertions
|
||||
- Arxiv excluded when in `disabled_mcps`
|
||||
|
||||
## 3. Build Verification
|
||||
|
||||
```bash
|
||||
bun run build
|
||||
```
|
||||
|
||||
Verify:
|
||||
- ESM bundle includes `arxiv.ts` module
|
||||
- Type declarations emitted for `arxiv` export
|
||||
- No build errors
|
||||
|
||||
## 4. Integration Check
|
||||
|
||||
### Config disable path
|
||||
- Add `"arxiv"` to `disabled_mcps` in test config → verify MCP excluded from `createBuiltinMcps()` output
|
||||
- This is already covered by the unit test, but can be manually verified:
|
||||
|
||||
```typescript
|
||||
import { createBuiltinMcps } from "./src/mcp"
|
||||
const withArxiv = createBuiltinMcps([])
|
||||
console.log(Object.keys(withArxiv)) // ["websearch", "context7", "grep_app", "arxiv"]
|
||||
|
||||
const withoutArxiv = createBuiltinMcps(["arxiv"])
|
||||
console.log(Object.keys(withoutArxiv)) // ["websearch", "context7", "grep_app"]
|
||||
```
|
||||
|
||||
### MCP config handler path
|
||||
- `mcp-config-handler.ts` calls `createBuiltinMcps()` and merges results
|
||||
- No changes needed there; arxiv automatically included in the merge
|
||||
- Verify by checking `applyMcpConfig()` output includes arxiv when not disabled
|
||||
|
||||
## 5. LSP Diagnostics
|
||||
|
||||
```bash
|
||||
# Run on all changed files
|
||||
```
|
||||
|
||||
Check `lsp_diagnostics` on:
|
||||
- `src/mcp/arxiv.ts`
|
||||
- `src/mcp/types.ts`
|
||||
- `src/mcp/index.ts`
|
||||
- `src/mcp/index.test.ts`
|
||||
|
||||
All must return 0 errors.
|
||||
|
||||
## 6. Endpoint Verification (Manual / Pre-merge)
|
||||
|
||||
**Critical:** Before merging, verify the arXiv MCP endpoint URL is actually reachable:
|
||||
|
||||
```bash
|
||||
curl -s -o /dev/null -w "%{http_code}" https://mcp.arxiv.org
|
||||
```
|
||||
|
||||
If the endpoint doesn't exist or returns non-2xx, the MCP will silently fail at runtime (MCP framework handles connection errors gracefully). This is acceptable for a built-in MCP but should be documented.
|
||||
|
||||
## 7. Regression Check
|
||||
|
||||
Verify no existing functionality is broken:
|
||||
- `bun test` (full suite) passes
|
||||
- Existing 3 MCPs (websearch, context7, grep_app) still work
|
||||
- `disabled_mcps` config still works for all MCPs
|
||||
- `mcp-config-handler.test.ts` passes (if it has count-based assertions, update them)
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] `bun run typecheck` passes
|
||||
- [ ] `bun test src/mcp/` passes (all tests green)
|
||||
- [ ] `bun run build` succeeds
|
||||
- [ ] `lsp_diagnostics` clean on all 4 changed files
|
||||
- [ ] arXiv MCP endpoint URL verified reachable
|
||||
- [ ] No hardcoded MCP count assertions broken elsewhere in codebase
|
||||
- [ ] AGENTS.md updated to reflect 4 MCPs
|
||||
@@ -0,0 +1 @@
|
||||
{"total_tokens": null, "duration_ms": 197000, "total_duration_seconds": 197}
|
||||
Reference in New Issue
Block a user