Compare commits

...

6 Commits

Author SHA1 Message Date
Dax Raad
e8eaa77bf1 better mcp support - should fix hanging when streamable http server is added 2025-07-25 19:19:47 -04:00
Frank
a07f37073b wip: github actions 2025-07-25 19:05:55 -04:00
Frank
4d760a1984 wip: github action 2025-07-25 18:33:45 -04:00
Dax Raad
6b7058fe1c qwen optimizations it works good now 2025-07-25 18:31:08 -04:00
Frank
1149b984d9 wip: github actions 2025-07-25 18:29:53 -04:00
Michael Hanson
81fb1b313e Fix a broken example in the MCP documentation and add more clarity (#1322) 2025-07-25 17:47:01 -04:00
7 changed files with 94 additions and 34 deletions

View File

@@ -27,4 +27,4 @@ jobs:
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
./script/publish
working-directory: ./sdks/github
working-directory: ./github

View File

@@ -20,6 +20,10 @@
}
},
"mcp": {
"context7": {
"type": "remote",
"url": "https://mcp.context7.com/sse"
},
"weather": {
"type": "local",
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]

View File

@@ -386,7 +386,6 @@ export const GithubRunCommand = cmd({
type PromptFiles = Awaited<ReturnType<typeof getUserPrompt>>["promptFiles"]
try {
const { userPrompt, promptFiles } = await getUserPrompt()
const actionToken = isMock ? args.token! : await getOidcToken()
appToken = await exchangeForAppToken(actionToken)
octoRest = new Octokit({ auth: appToken })
@@ -394,6 +393,7 @@ export const GithubRunCommand = cmd({
headers: { authorization: `token ${appToken}` },
})
const { userPrompt, promptFiles } = await getUserPrompt()
await configureGit(appToken)
await assertPermissions()
@@ -425,7 +425,7 @@ export const GithubRunCommand = cmd({
const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles)
if (await branchIsDirty()) {
const summary = await summarize(response)
await pushToCurrentBranch(summary)
await pushToLocalBranch(summary)
}
const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${shareBaseUrl}/s/${shareId}`))
await updateComment(`${response}${footer({ image: !hasShared })}`)
@@ -451,7 +451,7 @@ export const GithubRunCommand = cmd({
const response = await chat(`${userPrompt}\n\n${dataPrompt}`, promptFiles)
if (await branchIsDirty()) {
const summary = await summarize(response)
await pushToCurrentBranch(summary)
await pushToNewBranch(summary, branch)
const pr = await createPR(
repoData.data.default_branch,
branch,
@@ -531,19 +531,28 @@ export const GithubRunCommand = cmd({
}[] = []
// Search for files
// ie. <img alt="Image" src="https://github.com/user-attachments/assets/xxxx" />
// ie. [api.json](https://github.com/user-attachments/files/21433810/api.json)
// ie. ![Image](https://github.com/user-attachments/assets/xxxx)
const imgTags = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi)
const mdMatches = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi)
const tagMatches = prompt.matchAll(/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi)
const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index)
console.log("Images", JSON.stringify(matches, null, 2))
let offset = 0
for (const imgTag of imgTags) {
const tag = imgTag[0]
const url = imgTag[1]
const start = imgTag.index
for (const m of matches) {
const tag = m[0]
const url = m[1]
const start = m.index
const filename = path.basename(url)
// Download image
const res = await fetch(url)
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${appToken}`,
Accept: "application/vnd.github.v3+json",
},
})
if (!res.ok) {
console.error(`Failed to download image: ${url}`)
continue
@@ -777,8 +786,17 @@ export const GithubRunCommand = cmd({
return `opencode/${type}${issueId}-${timestamp}`
}
async function pushToCurrentBranch(summary: string) {
console.log("Pushing to current branch...")
async function pushToNewBranch(summary: string, branch: string) {
console.log("Pushing to new branch...")
await $`git add .`
await $`git commit -m "${summary}
Co-authored-by: ${actor} <${actor}@users.noreply.github.com>"`
await $`git push -u origin ${branch}`
}
async function pushToLocalBranch(summary: string) {
console.log("Pushing to local branch...")
await $`git add .`
await $`git commit -m "${summary}

View File

@@ -1,5 +1,7 @@
import { experimental_createMCPClient, type Tool } from "ai"
import { Experimental_StdioMCPTransport } from "ai/mcp-stdio"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { App } from "../app/app"
import { Config } from "../config/config"
import { Log } from "../util/log"
@@ -32,15 +34,28 @@ export namespace MCP {
}
log.info("found", { key, type: mcp.type })
if (mcp.type === "remote") {
const client = await experimental_createMCPClient({
name: key,
transport: {
type: "sse",
url: mcp.url,
headers: mcp.headers,
},
}).catch(() => {})
if (!client) {
const transports = [
new StreamableHTTPClientTransport(new URL(mcp.url), {
requestInit: {
headers: mcp.headers,
},
}),
new SSEClientTransport(new URL(mcp.url), {
requestInit: {
headers: mcp.headers,
},
}),
]
for (const transport of transports) {
const client = await experimental_createMCPClient({
name: key,
transport,
}).catch(() => {})
if (!client) continue
clients[key] = client
break
}
if (!clients[key])
Bus.publish(Session.Event.Error, {
error: {
name: "UnknownError",
@@ -49,16 +64,13 @@ export namespace MCP {
},
},
})
continue
}
clients[key] = client
}
if (mcp.type === "local") {
const [cmd, ...args] = mcp.command
const client = await experimental_createMCPClient({
name: key,
transport: new Experimental_StdioMCPTransport({
transport: new StdioClientTransport({
stderr: "ignore",
command: cmd,
args,

View File

@@ -23,8 +23,9 @@ export namespace ProviderTransform {
}
for (const msg of unique([...system, ...final])) {
const shouldUseContentOptions = providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
const shouldUseContentOptions =
providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
if (shouldUseContentOptions) {
const lastContent = msg.content[msg.content.length - 1]
if (lastContent && typeof lastContent === "object") {
@@ -35,7 +36,7 @@ export namespace ProviderTransform {
continue
}
}
msg.providerOptions = {
...msg.providerOptions,
...providerOptions,
@@ -46,7 +47,7 @@ export namespace ProviderTransform {
}
export function temperature(_providerID: string, modelID: string) {
if (modelID.includes("qwen")) return 0.55
if (modelID.toLowerCase().includes("qwen")) return 0.55
return 0
}
}

View File

@@ -64,12 +64,12 @@ export namespace ToolRegistry {
}
export function enabled(_providerID: string, modelID: string): Record<string, boolean> {
if (modelID.includes("claude")) {
if (modelID.toLowerCase().includes("claude")) {
return {
patch: false,
}
}
if (modelID.includes("qwen")) {
if (modelID.toLowerCase().includes("qwen")) {
return {
patch: false,
todowrite: false,

View File

@@ -18,7 +18,7 @@ You can define MCP servers in your opencode config under `mcp`.
### Local
Add local MCP servers under `mcp` with `"type": "local"`.
Add local MCP servers using `"type": "local"` within the MCP object. Multiple MCP servers can be added. The key string for each server can be any arbitrary name.
```json title="opencode.json"
{
@@ -31,7 +31,7 @@ Add local MCP servers under `mcp` with `"type": "local"`.
"environment": {
"MY_ENV_VAR": "my_env_var_value"
}
}, {
},
"my-different-local-mcp-server": {
"type": "local",
"command": ["bun", "x", "my-other-mcp-command"],
@@ -62,3 +62,28 @@ Add remote MCP servers under `mcp` with `"type": "remote"`.
}
}
```
Local and remote servers can be used together within the same `mcp` config object.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"my-local-mcp-server": {
"type": "local",
"command": ["bun", "x", "my-mcp-command"],
"enabled": true,
"environment": {
"MY_ENV_VAR": "my_env_var_value"
}
},
"my-remote-mcp": {
"type": "remote",
"url": "https://my-mcp-server.com",
"enabled": true,
"headers": {
"Authorization": "Bearer MY_API_KEY"
}
}
}
}